From f78a677eb2e05f549b1722be1693a5b11b50d76f Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Thu, 1 Oct 2026 01:46:28 +0200 Subject: [PATCH] docs(11.1): add gap closure plan 11.1-07 for fail-open docs checkers --- .planning/ROADMAP.md | 7 +- .../11.1-07-PLAN.md | 471 ++++++++++++++++++ 2 files changed, 476 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-07-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 50e96fd..24fad4c 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -544,7 +544,7 @@ Plans: 4. Every Go example in a `docs/` page is compiled and run by `go test ./...` (Go code in ingested module READMEs is identifier-checked, not compiled — D-18). An identifier checker and an internal link/anchor checker also run there and fail on stale names or broken links. 5. A "Coming from WinterCMS" concept map and an `acme/blog` porting walkthrough exist, and the walkthrough's code is verified under criterion 4. -**Plans:** 6/6 plans executed +**Plans:** 6/7 plans executed Plans: **Wave 1** @@ -565,6 +565,9 @@ Plans: **Wave 6** *(blocked on Wave 5 completion)* - [x] 11.1-06-PLAN.md — Unit tests last: planted-violation fixtures, internal/docsite coverage, SC1-SC5 acceptance, final gate, validated VALIDATION.md +**Wave 7** *(gap closure, blocked on Wave 6 completion)* +- [ ] 11.1-07-PLAN.md — Checkers fail closed: AST fence discovery (nested and README src= refused, captions only on verified fences, Go-lexer aliases need src=), go test roots and go/build membership for src= targets, `go doc -c`, env/flag/go run/bin command forms; planted fixtures and unit tests for every hole + ### Phase 11.2: Ready to share: summercms.io website and newsletter plugin (INSERTED) **Goal:** summercms.io is ready to share publicly. A fresh Nuxt 4 website in English and Polish runs on a SummerCMS binary. Visitors subscribe for updates through the initial Go version of the Golem15 Newsletter plugin, which confirms each email by double opt-in. The Phase 11.1 docs are served at `/docs` and linked from the site. @@ -674,7 +677,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → | 9. Backend admin authentication and schema pipeline | 12/12 | In Progress| | | 10. Admin Vue SPA | 5/5 | Complete | 2026-09-27 | | 11. Jobs, realtime and search infrastructure | 8/8 | In Progress| | -| 11.1. SummerCMS documentation for humans and AI agents | 6/6 | In Progress| | +| 11.1. SummerCMS documentation for humans and AI agents | 6/7 | In Progress| | | 11.2. Ready to share: summercms.io website and newsletter plugin | 0/TBD | Not started | - | | 12. Płytarium API — Collections and Albums | 0/TBD | Not started | - | | 13. Płytarium API — wishlist, notifications, CSV, credentials, public routes | 0/TBD | Not started | - | diff --git a/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-07-PLAN.md b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-07-PLAN.md new file mode 100644 index 0000000..8e2112f --- /dev/null +++ b/.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-07-PLAN.md @@ -0,0 +1,471 @@ +--- +phase: 11.1-summercms-documentation-for-humans-and-ai-agents +plan: 07 +type: execute +wave: 7 +depends_on: ["11.1-06"] +gap_closure: true +files_modified: + - internal/docsite/fences.go + - internal/docsite/render.go + - internal/docsite/snippet.go + - internal/docsite/docsite.go + - internal/docsite/load.go + - internal/docsite/check_policy.go + - internal/docsite/check_commands.go + - internal/docsite/check_identifiers.go + - internal/docsite/highlight.go + - internal/docsite/fences_test.go + - internal/docsite/render_test.go + - internal/docsite/highlight_test.go + - internal/docsite/checks_test.go + - internal/docsite/snippet_test.go + - internal/docsite/testdata/violations/ + - cmd/summer/phase11_1_acceptance_test.go + - scripts/check-phase11.1.sh + - docs/console/utilities.md + - README.md + - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md + - .planning/todos/pending/readme-go-fences-src.md +autonomous: true +requirements: [DOCS-04, DOCS-05] +assumption_delta_decision: no-change +user_setup: [] + +estimate: + tokens: 180000 + raw_tokens: 180000 + tasks: 5 + confidence: low + +must_haves: + truths: + - "Per D-07 and DOCS-04, the snippet, go-fence policy and shell-command checks find fences in the goldmark AST that the renderer uses (every *ast.FencedCodeBlock, at any depth), not by scanning lines; a fence goldmark renders is never invisible to the checkers." + - "A `src=` fence that is not a top-level block of a docs/ page (inside a callout, a plain blockquote or a list item) fails Check, `summer docs:build --check`, TestDocsTree and `summer docs:sync` with `snippet: {ref}: src= code block must be a top-level block of the page, not inside a callout, blockquote or list item`, whatever its body and whether or not its path exists; docs:sync writes nothing while one exists." + - "Per D-18, a `src=` fence in a module README fails with `snippet: {ref}: module README code blocks are rendered as written; src= is verified only in docs/ pages`, and no built page (guide or API reference) carries a source `
` over a fence the snippet checker did not compare: fenceAnnotator captions only top-level `src=` fences of docs/ pages." + - "Any fence in a docs/ page whose language resolves, through the chroma lexer lookup the highlighter uses, to the Go lexer (go, Go, GO, golang, Golang, main.go) fails with `snippet: {lang} code block has no src= reference` when it has no src=, including inside callouts, blockquotes and list items; go-html-template, go-text-template, text and an empty language do not." + - "A sh/shell/bash/console fence inside a callout, blockquote or list item is command-checked like a top-level one, and top-level text indented by four or more spaces is an indented code block, not a fence (IN-02)." + - "Per D-07 proof of execution, the reachability roots of a src= helper, type or region in a _test.go file are the Test functions go test runs (cmd/go naming rule) and the Examples that go/doc reports with Output or EmptyOutput, taken only from the test files in the default build; a helper or region reached only from an Example without `// Output:`, or only from a Test in a build-ignored file, fails with the not-run message." + - "A .go src= target must be compiled by go test ./...: it fails when a path segment is `testdata` or starts with `_`, or when build.ImportDir under build.Default does not list it in GoFiles/CgoFiles (non-test) or TestGoFiles/XTestGoFiles (test), which covers `//go:build ignore` and foreign GOOS/GOARCH file-name suffixes." + - "Per D-12 and DOCS-05, the identifier checker's go doc fallback runs `go doc -c`, so a wrong-case span such as `demo.Openfromapp` (declared `OpenFromApp`) fails with `identifier: demo.Openfromapp does not exist in modules/demo`." + - "Per DOCS-05, the command checker finds the command word after leading VAR=value assignments and after flags (cobra stripFlags semantics for a root whose only flag is the bool --help/-h) for the programs `summer`, `go run ./cmd/summer`, `./bin/{app}` and `bin/{app}`; an unknown name in each form fails with `command: \"{name}\" is not a summer or application command`, and `summer --help` alone is not a command." + - "Every hole above has a planted fixture in internal/docsite/testdata/violations that produces exactly its own problem; the existing corpus and the clean fixture still pass; the real tree still passes TestDocsTree, `summer docs:build --check` and TestPhase11_1Acceptance, whose independent fence scanner now strips `>` prefixes, resolves Go aliases through chroma and cross-checks built captions; `scripts/check-phase11.1.sh --all` prints `phase11.1 all passed`." + prohibitions: + - requirement_id: DOCS-04 + category: transparency + status: resolved + verification: judgment + resolution: "fenceAnnotator captions only the fences checkSnippets compares (top-level src= fences of docs/ pages); nested and README src= fences are refused; the acceptance test cross-checks built captions against an independent scan." + reason: "A caption that names and links a source file tells readers and agents the code was verified; over an unchecked body it is a false claim." + statement: "The site must not show a source caption over a code block the snippet checker did not compare with its source." + artifacts: + - path: "internal/docsite/fences.go" + provides: "AST fence discovery shared by the snippet, policy and command checks and by docs:sync" + contains: "func collectFences" + - path: "internal/docsite/snippet.go" + provides: "top-level-only src= verification, README refusal, go/build membership, go/doc Example roots" + contains: "build.ImportDir" + - path: "internal/docsite/highlight.go" + provides: "goLang: Go-lexer detection through the highlighter's lookup" + contains: "func goLang" + - path: "internal/docsite/check_commands.go" + provides: "commandWord: env-prefix, flag-first, go run and bin/ command forms" + contains: "func commandWord" + - path: "internal/docsite/check_identifiers.go" + provides: "case-sensitive go doc fallback" + contains: "\"-c\"" + - path: "internal/docsite/testdata/violations/snippet-callout-drift/want.txt" + provides: "CR-01 planted fixture" + contains: "top-level block" + - path: "cmd/summer/phase11_1_acceptance_test.go" + provides: "independent scanner that sees blockquoted fences and Go aliases; caption cross-check" + contains: "TestAcceptanceFenceScanner" + key_links: + - from: "internal/docsite/render.go" + to: "internal/docsite/snippet.go" + via: "fenceAnnotator captions exactly the fence set checkSnippets verifies (top-level src= fences of docs/ pages)" + pattern: "Module == \"\"" + - from: "internal/docsite/check_policy.go" + to: "internal/docsite/highlight.go" + via: "goLang reuses lexerFor, the renderer's chroma lookup, so policy and highlighting agree on what is Go" + pattern: "goLang\\(" + - from: "internal/docsite/snippet.go" + to: "go/build and go/doc" + via: "buildPackage (build.ImportDir) file lists and doc.Examples output roots" + pattern: "doc\\.Examples" + - from: "internal/docsite/check_identifiers.go" + to: "go doc -c" + via: "goDoc argv" + pattern: "\"doc\", \"-c\"" + - from: "scripts/check-phase11.1.sh" + to: "internal/docsite new tests and fixtures" + via: "--named DOCSITE_TESTS list and --self-test plants" + pattern: "TestNestedFenceChecks" +--- + + +Close the three verification gaps of Phase 11.1 (11.1-VERIFICATION.md `gaps:`), all of which are docs checkers that fail open, and ship the planted-violation fixtures and unit tests for every hole in this same plan (locked at the plan-count checkpoint: one gap-closure plan, tests as its final tasks). + +1. Fence discovery (BLOCKER, CR-01 + WR-04, IN-02): the snippet, go-fence policy and command checks read fences from the goldmark AST; `src=` fences must be top-level blocks of a docs/ page; module README `src=` fences are refused and never captioned; any Go-lexer alias needs `src=`; the acceptance scanner strips `>` and resolves aliases. +2. Proof of execution (WR-01 + WR-02): reachability roots are Test functions plus Examples with output; a `.go` src= target must be in the default build. +3. Identifier and command forms (WR-03 + WR-05): `go doc -c`; env-prefix, flag-first, `go run ./cmd/summer` and `bin/{app}` command forms. + +Purpose: restore the guarantees DOCS-04 ("a test fails on a missing or drifted snippet", D-07) and DOCS-05 ("checkers fail on stale identifiers ... unknown command names", D-12) promise, so `go test ./...` and `summer docs:build --check` refuse every form goldmark renders or a reader would copy. + +Output: `internal/docsite/fences.go`, changes to the docsite checkers, 18 new planted fixtures, new unit tests, an updated acceptance test and gate, a sentence in docs/console/utilities.md and the root README, and 11.1-VALIDATION.md rows. + +Out of scope (not cited by the verification gaps): WR-06, WR-07, IN-01, IN-03 to IN-08. No phase SPEC.md exists; as a gap-closure plan its edge coverage comes from the VERIFICATION gaps and the REVIEW findings they cite, so the spec-less fallback is not applied. Do not tick DOCS-04 or DOCS-05 in REQUIREMENTS.md; re-verification does that. + + + +@~/.claude/gsd-core/workflows/execute-plan.md +@~/.claude/gsd-core/templates/summary.md + + + +@.planning/STATE.md +@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-CONTEXT.md +@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VERIFICATION.md +@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-REVIEW.md +@.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-06-SUMMARY.md +@internal/docsite/render.go +@internal/docsite/snippet.go +@internal/docsite/check_policy.go +@internal/docsite/check_commands.go +@internal/docsite/check_identifiers.go +@internal/docsite/violations_test.go + +Repository conventions: stdlib `testing` only in these packages (no testify), `t.TempDir()` fixtures, `t.Fatalf("x = %v, want %v")`. goldmark v1.8.6 and chroma/v2 v2.27.0 are already in go.mod; go/ast, go/build, go/doc and go/parser are stdlib: this plan adds no dependency (D-02, D-15) and go.mod/go.sum must not change. `go vet ./...` and `go test ./...` stay green at every commit. Commits: one logical change each, code and planning docs in separate commits, no co-author tags. Stage only each task's files (the worktree has unrelated untracked files). + +Planted-fixture format (from violations_test.go): each `testdata/violations/{case}/` holds only the files that differ from `testdata/clean/` (copied over it at the same relative path), an optional `remove.txt`, and a `want.txt` with `rule: ...`, optional `file: ...`, `message: ...` (substring). A case passes only when Check reports exactly one problem matching it, and Build refuses the tree. The clean fixture's `docs/extras/faq.md` ends with a text fence; new plants append after it. + + + + + + Task 1: Tracer: a src= fence inside a callout is found in the goldmark AST and refused end to end, and only verified fences get a source caption + internal/docsite/fences.go, internal/docsite/snippet.go, internal/docsite/render.go, internal/docsite/docsite.go, internal/docsite/load.go + + - internal/docsite/render.go (fenceAnnotator, fence, scanFences, openFence, closesFence, parseRaw, pageKey, pageContext) + - internal/docsite/snippet.go (Extract, checkFences, drift, fenceBody, checkSnippets, Sync) + - internal/docsite/check_identifiers.go (parsedDoc, parseDocs, lineOf) + - internal/docsite/docsite.go (checkContent) and internal/docsite/load.go (assemble, splitFrontmatter, Page.Body, Page.BodyLine, Page.Module) + - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-REVIEW.md CR-01 and 11.1-VERIFICATION.md gap 1 + - internal/docsite/testdata/clean/docs/extras/faq.md and internal/docsite/violations_test.go (fixture format) + + +Wire one path through every layer: AST discovery, the snippet checker, docs:sync, the renderer's caption, and `summer docs:build --check` (per D-07 a snippet the checker cannot compare is refused, never passed). + +1. New file `internal/docsite/fences.go` with type `fenceLine` (fields `line int`, 0-based body line, and `text string`) and type `mdFence` with fields `info string` (the Info segment value, "" when Info is nil), `lang string` (first field of info), `line int` (0-based body line of the opening fence: from `Info.Segment.Start` counted with `bytes.Count` of newlines; when Info is nil, the line before the first body line; -1 when the fence has neither), `topLevel bool` (the node's parent is the document, `Parent().Kind() == gast.KindDocument`), `code []fenceLine` (one entry per `Lines()` segment: the line from the segment start and the text from `Segment.Value(body)` with the trailing newline trimmed; goldmark already strips container markers such as the blockquote `> `), and `top fence` (set only for top-level fences that have an info string). Add `collectFences(doc gast.Node, body []byte) ([]mdFence, error)` that walks every `*gast.FencedCodeBlock` of a document parsed with `newMarkdown()` (the renderer's pipeline, so a fence in a callout has the callout node as parent). For a top-level fence with info, build `top` from the raw body line at `line` with `openFence` (indent, char, count, info), `open = line`, and `close` = the line after the last body line (or `line+1` for an empty body) when that line exists and `closesFence` accepts it, otherwise -1 (unterminated). If `openFence` rejects the opening line of a top-level fence, return an error naming the line: the AST and the line view disagree and that must never be silent (fail closed). +2. `snippet.go`: change `checkFences` to take the collected fences instead of scanning: `checkFences(root, file string, lines []string, lineBase int, fences []mdFence) ([]drift, []Problem, error)`, where `lines` are the body lines the fences index into. For each fence whose info has `src=` (ParseSrc): when `!topLevel`, add a `snippet` problem at `lineBase + line` with message `{ref}: ` followed by a new constant `nestedSrcMessage` = "src= code block must be a top-level block of the page, not inside a callout, blockquote or list item", and do not call Extract (docs:sync cannot rewrite `> `-prefixed lines). For top-level fences keep the existing logic unchanged on `top` (unclosed fence, Extract, not found, snippetError, `fenceBody` comparison, drift). Change `checkSnippets` to `(s *site) checkSnippets(docs []parsedDoc) ([]Problem, error)`: for every doc with `page != nil` and `page.Module == ""` (module READMEs are Task 2), `collectFences(d.doc, d.body)` then `checkFences(s.opts.Root, d.file, lines of d.body, d.line, fences)`. Remove the `checkSnippets` call from `assemble` in load.go and call it from `checkContent` in docsite.go right after `parseDocs`, so snippets use the same parse as every other checker. +3. `Sync`: per file, `splitFrontmatter(raw)` gives the body and bodyLine (use the whole file with bodyLine 1 when there is no frontmatter block); parse the body with `s.parseRaw(newMarkdown(), body)`, `collectFences`, `checkFences` with lineBase bodyLine; rewrite drifted top-level fences in the body lines (drift offsets are body-relative) and write the unchanged prefix `raw[:len(raw)-len(body)]` plus the rewritten body. Nested src= fences are problems, so the existing all-or-nothing rule makes Sync write nothing while one exists. Keep the message filter for "body differs from " as today. +4. `render.go` fenceAnnotator: set `data-src`/`data-href` only when `pctx != nil`, `pctx.page != nil`, `pctx.page.Module == ""` and the fence's parent is the document: the exact set checkSnippets compares, so a source caption can never sit over an unchecked body (CR-01; per D-18 module README fences render as written, without a caption). +Commit as `fix(11.1-07): find src= fences in the goldmark AST and refuse nested ones`. + + + go vet ./... && go test ./internal/docsite ./cmd/summer -count=1 + non-zero exit or a "FAIL" line (for example TestCleanFixture, TestPlantedViolations, TestSyncPreservesAndReports or TestDocsTree) + d=$(mktemp -d) && cp -r internal/docsite/testdata/clean/. "$d" && go run ./cmd/summer docs:build --root "$d" --check && printf '\n> [!TIP]\n> \140\140\140go src=modules/demo/example_test.go#ExampleHello\n> BOGUS\n> \140\140\140\n' >> "$d/docs/extras/faq.md" && ! go run ./cmd/summer docs:build --root "$d" --check > "$d/check.log" 2>&1 && grep -F 'modules/demo/example_test.go#ExampleHello: src= code block must be a top-level block' "$d/check.log" + non-zero exit: the unplanted clean copy is refused, the planted copy passes docs:build --check (the CR-01 reproduction), or check.log has no "src= code block must be a top-level block" line + go run ./cmd/summer docs:build --check + non-zero exit or no "no problems found" line (the real tree must stay clean) + + + - `grep -n 'func collectFences' internal/docsite/fences.go` finds one match, and `grep -n 'collectFences(' internal/docsite/snippet.go` finds at least two (checkSnippets and Sync). + - `grep -n 'Module == ""' internal/docsite/render.go` finds the fenceAnnotator condition. + - The second automated command prints the line `docs/extras/faq.md:{n}: snippet: modules/demo/example_test.go#ExampleHello: src= code block must be a top-level block of the page, not inside a callout, blockquote or list item`. + - `go test ./internal/docsite -run '^(TestCleanFixture|TestPlantedViolations|TestSyncPreservesAndReports|TestSyncRewritesDrift|TestEditAndSourceURLs)$' -count=1` passes (existing corpus, Sync and caption behaviour unchanged for top-level fences). + - `go run ./cmd/summer docs:build --check` prints `docs:build: no problems found`. + + A src= fence goldmark renders inside a callout is refused by Check, docs:build --check and docs:sync, top-level fences are verified and synced exactly as before, and only verified fences get a source caption. + Refusing non-top-level src= fences (instead of checking them) is one branch in checkFences; lifting it later only needs Sync to rewrite prefixed lines. + + + + Task 2: Go-fence policy, module README src= refusal and shell-command checks read the same AST fences, and every Go-lexer alias needs src= + internal/docsite/highlight.go, internal/docsite/check_policy.go, internal/docsite/snippet.go, internal/docsite/check_commands.go, internal/docsite/render.go, docs/console/utilities.md, README.md + + - internal/docsite/highlight.go (lexerFor, highlight, shellLangs use) + - internal/docsite/check_policy.go (checkPolicy, calloutMarker, headingProblems) + - internal/docsite/render.go (calloutTransformer, calloutLine, calloutTypes use, openFence) + - internal/docsite/check_commands.go (checkCommands fence loop) + - internal/docsite/fences.go and internal/docsite/snippet.go as changed by Task 1 + - internal/docsite/checks_test.go TestFencePolicy and TestCommandChecker (expected problem lines that must not change) + - docs/console/utilities.md "Documentation commands" section and README.md Documentation paragraph (line 149) + - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-REVIEW.md WR-04 and IN-02 + + +1. `highlight.go`: add `goLang(lang string) bool`, true when `lexerFor(lang)` (the highlighter's own chroma lookup, per D-15) returns a lexer whose `Config().Name` is "Go". chroma's registry resolves names, aliases, lowercase forms and file names, so go, Go, GO, golang, Golang and main.go are Go, while go-html-template, go-text-template, text and "" are not. +2. `check_policy.go`: build the go-fence rule from `collectFences(d.doc, d.body)` for guide pages (`page.Module == ""`; module READMEs stay exempt per D-18): a fence with `goLang(f.lang)` and no `src=` in its info adds a `snippet` problem at `d.line + f.line` with message `{lang} code block has no src= reference` (the language as written, so the existing go message is unchanged). It applies at every depth: callouts, blockquotes, list items. Replace the raw-line callout check and its fence mask with an AST check: after calloutTransformer, every `*gast.Blockquote` whose first child is a paragraph whose first line matches `calloutLine` with a type not in `calloutTypes` is a `callout` problem at that line with the existing message; remove the `calloutMarker` regex. The heading rule is unchanged. TestFencePolicy's expected lines must still hold. +3. `snippet.go` checkSnippets: include module README pages. On a page with `Module != ""` every `src=` fence, top-level or nested, adds a `snippet` problem `{ref}: ` plus new constant `readmeSrcMessage` = "module README code blocks are rendered as written; src= is verified only in docs/ pages" (D-18 keeps README fences unverified, so a src= there would promise a check that never runs; `.planning/todos/pending/readme-go-fences-src.md` replaces this refusal with a real check later). +4. `check_commands.go`: take shell fences from `collectFences`: for each fence whose `lang` is in `shellLangs`, run `check` on every `code` line at `d.line + line`, so a sh fence inside a callout or list item is checked like a top-level one. Code-span checking is unchanged. +5. `render.go` openFence: accept at most three leading spaces (CommonMark); four or more is an indented code block, as goldmark parses it (IN-02). This keeps rewriteMarkdown and serve's watch list in line with the renderer. +6. Docs (same change as the behaviour, per the CLAUDE.md documentation rule): in docs/console/utilities.md, extend the paragraph that lists what makes `docs:build` fail so it also names a Go code block (including `golang`) without a `src=` reference, a `src=` code block inside a callout, blockquote or list item (src= blocks must be top-level), and a `src=` code block in a module README. In README.md's Documentation paragraph about `summer docs:build`, add that `src=` works on top-level code blocks of `docs/` pages only. Run the docs checker on both files through the real-tree check below. +Commit as `fix(11.1-07): check go fences, README src= and shell fences from the AST`. + + + go vet ./... && go test ./internal/docsite ./cmd/summer -count=1 + non-zero exit or a "FAIL" line (TestFencePolicy, TestCommandChecker, TestCommandTokenForms, TestPlantedViolations or TestDocsTree regressing) + d=$(mktemp -d) && cp -r internal/docsite/testdata/clean/. "$d" && printf '\n\140\140\140golang\nx := 1\n\140\140\140\n' >> "$d/docs/extras/faq.md" && ! go run ./cmd/summer docs:build --root "$d" --check > "$d/check.log" 2>&1 && grep -F 'snippet: golang code block has no src= reference' "$d/check.log" + non-zero exit: the golang fence without src= passes docs:build --check (WR-04), or check.log lacks "snippet: golang code block has no src= reference" + d=$(mktemp -d) && cp -r internal/docsite/testdata/clean/. "$d" && printf '\n\140\140\140go src=modules/demo/example_test.go#ExampleHello\nfmt.Println(demo.Hello("blog"))\n// Output: Hello, blog\n\140\140\140\n' >> "$d/modules/demo/README.md" && ! go run ./cmd/summer docs:build --root "$d" --check > "$d/check.log" 2>&1 && grep -F 'module README code blocks are rendered as written' "$d/check.log" + non-zero exit: a src= fence in a module README passes docs:build --check, or check.log lacks "module README code blocks are rendered as written" + go run ./cmd/summer docs:build --check + non-zero exit or no "no problems found" line + + + - `grep -n 'func goLang' internal/docsite/highlight.go` finds one match and `grep -n 'goLang(' internal/docsite/check_policy.go` finds the go-fence rule. + - `grep -n 'collectFences(' internal/docsite/check_policy.go internal/docsite/check_commands.go` finds a match in each file. + - `grep -n 'readmeSrcMessage' internal/docsite/snippet.go` finds the constant and its use. + - `grep -n 'top-level' docs/console/utilities.md` and `grep -n 'top-level' README.md` each find a match. + - The second and third automated commands print their expected problem lines; `go run ./cmd/summer docs:build --check` prints `docs:build: no problems found`. + + Go-lexer fences without src= are refused at any depth, README src= fences are refused, shell fences in callouts and lists are command-checked, indented backticks are not fences, and the docs page and README describe the rules. + The README refusal is one branch in checkSnippets that the pending README todo replaces with a check. + + + + Task 3: Proof of execution and name forms: roots are what go test runs, .go src= targets are in the default build, go doc -c, and every summer and app command form is parsed + internal/docsite/snippet.go, internal/docsite/check_identifiers.go, internal/docsite/check_commands.go, docs/console/utilities.md + + - internal/docsite/snippet.go (Extract, checkRootModule, extractIdent, exampleHasOutput, testGraph, isRoot, loadTestGraph, notRunMessage, checkTestIdent, checkTestRegion) + - internal/docsite/check_identifiers.go (goDoc, goDocIdent) + - internal/docsite/check_commands.go (commandToken, commandSeparators, checkCommands check closure) + - internal/docsite/snippet_test.go TestSnippetGenericsAndGroups and TestSnippetTestGraph, internal/docsite/docsite_test.go TestSnippetConfinement (messages that must keep matching, including "has no // Output: comment") + - internal/docsite/checks_test.go TestCommandTokenForms and TestCommandChecker (forms that must keep passing: `summer --version`, `summer --help`, `go install ./cmd/summer`, `echo summer x`, comment lines) + - the vendored cobra `stripFlags` in the module cache (`go list -m -f '{{.Dir}}' github.com/spf13/cobra`, command.go) for the flag rule to mirror + - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-REVIEW.md WR-01, WR-02, WR-03, WR-05 + + +Proof of execution (D-07, gap 2): +1. `snippet.go`: add `buildPackage(dir string) (*build.Package, error)` wrapping `build.ImportDir(dir, 0)` on `build.Default` (host GOOS/GOARCH, CGO_ENABLED and release tags: the build `go test ./...` runs). In `Extract`, for a `.go` target after `checkRootModule`: refuse a repo-relative path with a segment named `testdata` or starting with `_` with snippetError "file is in a directory go test ./... skips (testdata or a _-prefixed directory)" (non-Go targets such as YAML under testdata stay allowed); then call buildPackage: a `*build.NoGoError`, or the file's base name missing from GoFiles plus CgoFiles (non-test file) or from TestGoFiles plus XTestGoFiles (`_test.go` file), gives snippetError "file is excluded from the default build (build constraint or GOOS/GOARCH file-name suffix)"; any other error gives snippetError "cannot load package: " plus `firstLine(err.Error())`. For a non-test file, "package has no _test.go files, so go test does not cover it" is now decided by empty TestGoFiles and XTestGoFiles instead of a glob. +2. `loadTestGraph`: take the file list from buildPackage's TestGoFiles plus XTestGoFiles (so a build-ignored test file contributes nothing), parse each with `parser.ParseComments`, and keep the parse error message format ("cannot parse {base}: ..."). Replace `isRoot` with roots = functions without a receiver whose name passes a new `isTestName(name string) bool` (the cmd/go rule: "Test" alone, or "Test" followed by a rune that is not lowercase) plus the Examples `doc.Examples(files...)` reports with `Output != ""` or `EmptyOutput` (key "Example"+ex.Name). Delete `isRoot`. +3. `extractIdent`: for an Example function (no receiver, name starting "Example") in a test file, run the existing `// Output:` check first and skip `checkTestIdent` (an Example with output is a root), so the "has no // Output: comment" message stays; every other func, type, var and const keeps `checkTestIdent`. Reword `notRunMessage` to "fragment is not inside a Test or Example function (an Example counts only with an // Output: comment), or a function one of them calls, so go test does not run it" (the substring the committed want.txt files match is unchanged). + +Identifier and command forms (D-12, DOCS-05, gap 3): +4. `check_identifiers.go` goDoc: run `go doc -c ./{dir} {query}` (argv only, no shell; `goDocIdent` still gates the query), so matching is case-sensitive (WR-03). +5. `check_commands.go`: replace the `commandToken` regex with `commandWord(cmd string) (name string, tool bool, ok bool)` over `strings.Fields` of the trimmed command: drop a leading "$" prompt token; skip leading assignment tokens (a name of letters, digits and underscores not starting with a digit, then "="); the program is `summer` (tool), the three tokens `go run ./cmd/summer` (tool), or `./bin/{app}` or `bin/{app}` with app in [A-Za-z0-9._-]+ (application); anything else is not a command (ok false). After the program, find the command word the way cobra's stripFlags does for a root whose only flag is the bool --help/-h: "--" ends the search with no word; a token starting with "-" that contains "=" is skipped alone; "--help" and "-h" are skipped alone; any other "--name" or two-character "-x" also consumes the next token, and when only that value remains there is no word; other "-" tokens are skipped alone; the first remaining token is the word. `checkCommands` uses it (tool words against the tool set, application words against the app set) with the existing separators and message. +6. docs/console/utilities.md: add one sentence to the same paragraph: a `src=` target must be code `go test ./...` compiles and runs, that is a file in the default build, inside a `Test` function or an `Example` with an `// Output:` comment, or a function one of them calls. +Commit as `fix(11.1-07): require built, run src= code; case-sensitive go doc; parse command forms`. + + + go vet ./... && go test ./internal/docsite ./cmd/summer -count=1 + non-zero exit or a "FAIL" line (TestSnippetGenericsAndGroups, TestSnippetTestGraph, TestSnippetConfinement, TestIdentifierGoDocFallback, TestCommandTokenForms, TestPlantedViolations or TestDocsTree regressing) + d=$(mktemp -d) && cp -r internal/docsite/testdata/clean/. "$d" && printf '\n\140\140\140sh\nFOO=1 summer no:such\n\140\140\140\n' >> "$d/docs/extras/faq.md" && ! go run ./cmd/summer docs:build --root "$d" --check > "$d/check.log" 2>&1 && grep -F '"no:such" is not a summer or application command' "$d/check.log" + non-zero exit: the env-prefixed unknown command passes (WR-05), or check.log lacks the command problem line + d=$(mktemp -d) && cp -r internal/docsite/testdata/clean/. "$d" && printf 'package demo\n\n// OpenFromApp is a fixture.\nfunc OpenFromApp() {}\n' > "$d/modules/demo/open.go" && printf '\nCall \140demo.Openfromapp\140.\n' >> "$d/docs/extras/faq.md" && ! go run ./cmd/summer docs:build --root "$d" --check > "$d/check.log" 2>&1 && grep -F 'identifier: demo.Openfromapp does not exist in modules/demo' "$d/check.log" + non-zero exit: the wrong-case identifier passes through the go doc fallback (WR-03), or check.log lacks the identifier problem line + d=$(mktemp -d) && cp -r internal/docsite/testdata/clean/. "$d" && printf '//go:build ignore\n\npackage demo\n\n// Broken is never built.\nfunc Broken() { undefinedCall() }\n' > "$d/modules/demo/broken.go" && printf '\n\140\140\140go src=modules/demo/broken.go#Broken\n// Broken is never built.\nfunc Broken() { undefinedCall() }\n\140\140\140\n' >> "$d/docs/extras/faq.md" && ! go run ./cmd/summer docs:build --root "$d" --check > "$d/check.log" 2>&1 && grep -F 'file is excluded from the default build' "$d/check.log" + non-zero exit: a //go:build ignore source passes as compiled (WR-02), or check.log lacks "file is excluded from the default build" + go run ./cmd/summer docs:build --check + non-zero exit or no "no problems found" line (a real src= target or command span now refused must be fixed at its source in this task) + + + - `grep -n 'build.ImportDir' internal/docsite/snippet.go` and `grep -n 'doc.Examples(' internal/docsite/snippet.go` each find a match; `grep -n 'func isTestName' internal/docsite/snippet.go` finds one. + - `grep -n '"doc", "-c"' internal/docsite/check_identifiers.go` finds the goDoc argv. + - `grep -n 'func commandWord' internal/docsite/check_commands.go` finds one match. + - `go test ./internal/docsite -run '^(TestSnippetGenericsAndGroups|TestSnippetTestGraph|TestSnippetConfinement|TestSnippetForms|TestIdentifierGoDocFallback|TestCommandTokenForms|TestCommandChecker)$' -count=1` passes. + - Each of the three planted commands prints its expected problem line, and `go run ./cmd/summer docs:build --check` prints `docs:build: no problems found`. + + Only code go test compiles and runs counts as verified, identifier spans must match case exactly, and env-prefix, flag-first, go run and bin/ command forms are checked. + + + + Task 4: Unit tests (1/2): a planted fixture for every hole plus branch tests for fences, captions, Go aliases, roots, build membership, go doc -c and command words + internal/docsite/testdata/violations/, internal/docsite/fences_test.go, internal/docsite/render_test.go, internal/docsite/highlight_test.go, internal/docsite/checks_test.go, internal/docsite/snippet_test.go + + - internal/docsite/violations_test.go (plantCase, parseWant, assertOneProblem, TestCleanFixture) + - internal/docsite/testdata/clean/ (docs/extras/faq.md, modules/demo/demo.go, demo_test.go, example_test.go, README.md) + - internal/docsite/testdata/violations/snippet-unrun-func/ and go-fence-no-src/ (overlay examples) + - internal/docsite/docsite_test.go (page, fixtureCommands, snippetTree, writeTree helpers), checks_test.go (checkFixture, assertProblems), render_test.go (renderFixture, renderHTML), snippet_test.go (genericTree, writeFile) + - internal/docsite/fences.go, snippet.go, check_policy.go, check_commands.go, check_identifiers.go, highlight.go as changed by Tasks 1 to 3 + + + - Each new violation case yields exactly one problem with its own rule and message, and Build writes nothing. + - collectFences reports top-level, callout, blockquote and list-item fences with correct lines, topLevel flags, `>`-free code text and top open/close; four-space-indented backticks are not collected. + - A guide page renders a caption for a top-level src= fence and none for a nested one; a module README page renders none. + - goLang is true for go, Go, GO, golang, Golang, main.go and false for go-html-template, go-text-template, text, yaml, sh and "". + - Roots: Test, Test_x and TestMain are roots, Testable is not; an Example with Output or EmptyOutput is a root, one without is not; a Test in a //go:build ignore test file is not. + - commandWord table cases return the expected name, tool flag and ok. + + +1. Planted fixtures, each a new directory under `internal/docsite/testdata/violations/` in the existing overlay format with a `want.txt` (`rule`, `file`, `message` substring). Plants in `docs/extras/faq.md` are the clean faq.md plus the lines described; a "backtick fence" is opened and closed with three backticks. + Gap 1 (fence discovery): + - `snippet-callout-drift`: a `> [!TIP]` callout holding a backtick fence with info `go src=modules/demo/example_test.go#ExampleHello` and body BOGUS, every line prefixed `> `. want: rule snippet, file docs/extras/faq.md, message `modules/demo/example_test.go#ExampleHello: src= code block must be a top-level block`. + - `snippet-callout-missing`: a `> [!NOTE]` callout holding a fence `go src=modules/demo/missing_test.go#ExampleNope` with body `fmt.Println()`. message `modules/demo/missing_test.go#ExampleNope: src= code block must be a top-level block`. + - `snippet-blockquote`: a plain blockquote (no callout marker) holding a fence `yaml src=config/app.yaml` with the file's exact content. message `config/app.yaml: src= code block must be a top-level block`. + - `snippet-list-item`: a list item "- Greet:" followed by a two-space-indented fence `go src=modules/demo/example_test.go#ExampleHello` with the correct body. message `modules/demo/example_test.go#ExampleHello: src= code block must be a top-level block`. + - `go-fence-callout-no-src`: a `> [!NOTE]` callout holding a fence with info `go` and body `x := 1`. message `go code block has no src= reference`. + - `go-fence-golang`: a top-level fence with info `golang` and body `x := 1`. message `golang code block has no src= reference`. + - `snippet-readme-src`: `modules/demo/README.md` = the clean README plus a top-level fence `go src=modules/demo/example_test.go#ExampleHello` with the correct body. want: rule snippet, file modules/demo/README.md, message `modules/demo/example_test.go#ExampleHello: module README code blocks are rendered as written`. + - `command-in-callout`: a `> [!NOTE]` callout holding a `sh` fence with `summer no:such`. want: rule command, message `"no:such" is not a summer or application command`. + Gap 2 (proof of execution): + - `snippet-example-no-output-helper`: `modules/demo/never_test.go` (package demo_test) with `func Example_neverRun() { neverRunHelper() }` without an output comment and `func neverRunHelper() {}`; faq.md fence `go src=modules/demo/never_test.go#neverRunHelper` with body `func neverRunHelper() {}`. message `fragment is not inside a Test or Example function`. + - `snippet-example-no-output-region`: the same never_test.go with a `// docs:start never` / `// docs:end never` region inside neverRunHelper; fence `go src=modules/demo/never_test.go#never` with the region's body. Same message. + - `snippet-build-ignored`: `modules/demo/broken.go` with a `//go:build ignore` line, `package demo`, and `// Broken is never built.` over `func Broken() { undefinedCall() }`; fence `go src=modules/demo/broken.go#Broken` with that exact declaration. message `modules/demo/broken.go#Broken: file is excluded from the default build`. + - `snippet-build-ignored-test-root`: `modules/demo/demo_test.go` = the clean file plus `func ignoredOnly() string { return "x" }`, and `modules/demo/ignored_test.go` with `//go:build ignore`, package demo, and `func TestIgnored(t *testing.T) { _ = ignoredOnly() }`; fence `go src=modules/demo/demo_test.go#ignoredOnly` with that declaration. message `fragment is not inside a Test or Example function`. + - `snippet-testdata-go`: `modules/demo/testdata/fixture_test.go` (package demo) with a Test holding a `td` region; fence `go src=modules/demo/testdata/fixture_test.go#td`. message `file is in a directory go test ./... skips`. + Gap 3 (names): + - `identifier-wrong-case`: `modules/demo/open.go` (package demo, `// OpenFromApp is a fixture.` over `func OpenFromApp() {}`) and faq.md text "Call `demo.Openfromapp`." want: rule identifier, message `demo.Openfromapp does not exist in modules/demo`. + - `command-env-prefix` (`FOO=1 summer no:such`), `command-flag-first` (`summer --root . no:such`), `command-go-run` (`go run ./cmd/summer no:such`), `command-bin-no-dot` (`bin/demo no:such`): each a top-level `sh` fence in faq.md; want: rule command, message `"no:such" is not a summer or application command`. + All Go fixture files sit under internal/docsite/testdata, which the go tool ignores; write each so it would compile if built (except broken.go, whose point is that it is never built). No fixture names a consuming application. +2. Unit tests (stdlib testing, table-driven where natural): + - `internal/docsite/fences_test.go` `TestCollectFences`: one body with a top-level closed fence, a top-level unterminated fence at EOF (top.close -1), a fence in a callout, in a plain blockquote and in a list item (topLevel false, code text without `> ` or list indentation, correct lines), a fence without info (info "", line from the body), and four-space-indented backticks (not collected); plus `openFence` accepting three leading spaces and rejecting four. + - `render_test.go` `TestFenceCaptionsOnlyVerified`: via renderFixture, a guide page's top-level src= fence has a `
`, a src= fence inside a callout on the same page has none, and a module README page's top-level src= fence has none. + - `highlight_test.go` `TestGoLang`: the true and false cases in the behavior list. + - `checks_test.go` `TestNestedFenceChecks`: one checkFixture tree whose docs page has a callout go fence without src=, a list-item go fence without src=, a `GO` fence and a `main.go` fence, four-space-indented backticks holding `go` (no problem), a callout sh fence with `summer bogus:nested`, and a blockquote src= fence, and whose module README has a go fence without src= (no problem, D-18) and a src= fence; assert the exact problem lines (file:line: rule: message). + - `checks_test.go` `TestIdentifierGoDocCaseSensitive`: a writeTree module declaring `OpenFromApp` and `Outer` with a method `HelloWorld`; `goDoc` accepts `OpenFromApp` and `Outer.HelloWorld`, refuses `Openfromapp` and `Outer.Helloworld`; `checkSpan` reports `forms.Openfromapp does not exist in modules/forms` (use the module name the tree declares). + - `checks_test.go` `TestCommandWord`: table over `commandWord` with at least: `summer docs:build`; `$ summer docs:build --out site`; `FOO=1 summer no:such`; `FOO=1 BAR=x ./bin/acme serve`; `summer --root . no:such`; `summer --root=. docs:build`; `summer -r . docs:build`; `summer --help` (no word); `summer --help docs:build`; `summer -h docs:build`; `summer --root` (no word); `summer --root .` (no word); `summer -- docs:build` (no word); `go run ./cmd/summer no:such`; `go run ./cmd/summer --root . docs:build`; `bin/acme migrate`; `./bin/acme migrate`; `echo summer x`; `# summer x`; `go test ./modules/demo`; `go install ./cmd/summer`; `summer`; `FOO=1`; `summer --version` (no word). + - `snippet_test.go` `TestSnippetRootsAndBuild`: in writeTree packages, Test, Test_x and TestMain are roots and a helper called only from `Testable` is refused; an Example with `// Output:` and one with an empty `// Output:` make their helpers reachable, one without output does not; a `//go:build ignore` non-test target and test target are refused as excluded; a file named `only_{goos}.go` for a GOOS other than runtime.GOOS is refused as excluded; `.go` targets under `testdata/` and `_old/` are refused as skipped directories while a YAML target under `testdata/` is still extracted; a helper called only from a Test in a build-ignored test file is refused as not run. + - `snippet_test.go` `TestSyncParsedFences`: Sync on a page with frontmatter rewrites a drifted top-level fence and keeps the frontmatter and a callout's text byte for byte; Sync on a page with a src= fence inside a callout returns the nested problem and leaves every file unchanged. +3. If an existing unit-test tree relied on an Example without output as a root, that tree encoded the WR-01 hole: add the output comment to the fixture rather than weakening the rule, and list the change in the SUMMARY. Fix any production defect these tests expose in the same commit as its test, and list each fix in the SUMMARY. +4. Mutation evidence (by hand once, recorded in the SUMMARY): in a scratch copy, removing the nested branch of checkFences turns `TestPlantedViolations/snippet-callout-drift` red; dropping `-c` from goDoc turns `TestPlantedViolations/identifier-wrong-case` red; treating every Example as a root again turns `TestPlantedViolations/snippet-example-no-output-helper` red. +Commit as `test(11.1-07): planted fixtures and unit tests for fence, execution and name-form holes`. + + + go vet ./... && go test ./internal/docsite -run '^(TestPlantedViolations|TestCleanFixture|TestCollectFences|TestFenceCaptionsOnlyVerified|TestGoLang|TestNestedFenceChecks|TestIdentifierGoDocCaseSensitive|TestCommandWord|TestSnippetRootsAndBuild|TestSyncParsedFences)$' -count=1 -v + non-zero exit, a "--- FAIL" line, or "no tests to run" + go test ./internal/docsite -count=1 -coverprofile="${TMPDIR:-/tmp}/docsite.cover" && go tool cover -func="${TMPDIR:-/tmp}/docsite.cover" | awk '/^total:/ { sub("%", "", $3); if ($3 + 0 < 85.0) { print "coverage " $3 "% below 85%"; exit 1 } }' + non-zero exit, a "FAIL" line, or a "coverage ... below 85%" line + + + - `ls -d internal/docsite/testdata/violations/*/ | wc -l` prints at least 81 (63 existing plus 18 new). + - `go test ./internal/docsite -run '^TestPlantedViolations$' -count=1 -v | grep -cE -- '--- PASS: TestPlantedViolations/(snippet-callout-drift|snippet-callout-missing|snippet-blockquote|snippet-list-item|go-fence-callout-no-src|go-fence-golang|snippet-readme-src|command-in-callout|snippet-example-no-output-helper|snippet-example-no-output-region|snippet-build-ignored|snippet-build-ignored-test-root|snippet-testdata-go|identifier-wrong-case|command-env-prefix|command-flag-first|command-go-run|command-bin-no-dot) '` prints 18. + - `go test ./internal/docsite -run '^(TestCollectFences|TestFenceCaptionsOnlyVerified|TestGoLang|TestNestedFenceChecks|TestIdentifierGoDocCaseSensitive|TestCommandWord|TestSnippetRootsAndBuild|TestSyncParsedFences)$' -count=1 -v | grep -c -- '^--- PASS'` prints 8. + - `grep -rniE 'fonoteka|p(l|ł)ytarium' internal/docsite/testdata internal/docsite/fences_test.go` finds nothing (exit 1). + - `go test -cover ./internal/docsite -count=1` reports at least 85.0%. + - The SUMMARY records the three mutation checks from action step 4 and their red test names. + + Every hole in the three gaps is pinned by a fixture that fails for its own rule and by branch-level unit tests, the old corpus still passes, and coverage stays at or above 85%. + + + + Task 5: Unit tests (2/2): the acceptance scanner sees blockquoted fences and Go aliases, the gate plants every hole, and VALIDATION records plan 07 + cmd/summer/phase11_1_acceptance_test.go, scripts/check-phase11.1.sh, .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md, .planning/todos/pending/readme-go-fences-src.md + + - cmd/summer/phase11_1_acceptance_test.go (TestPhase11_1Acceptance SC4 and SC5, docFence, pageFences, docsGoFences, buildRealTree output `out`) + - scripts/check-phase11.1.sh (run_self_test, expect_refusal, restore, DOCSITE_TESTS, SUMMER_TESTS, run_named, --all) + - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md (Per-Task Verification Map format) + - .planning/todos/pending/readme-go-fences-src.md + - .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VERIFICATION.md gap 1 (acceptance scanner item) + + The Docker daemon is reachable (`docker info` exits 0): the gate's --named and --go stages run the walkthrough's Postgres tests without -short. + +1. `cmd/summer/phase11_1_acceptance_test.go` (the independent scanner must not share docsite's code or its old blind spot): + - Split `pageFences` into a content scanner `scanDocFences(rel, content string) []docFence` that pageFences calls. Before fence detection, strip from each line any run of leading spaces and `>` container markers (each `>` optionally followed by one space); add `nested bool` to `docFence`, true when a `>` was stripped or the opening line was indented. Body lines are stripped the same way, and the closing fence is detected on stripped lines. + - Add `goFenceLang(lang string) bool` using chroma directly (`lexers.Get(lang)` and `Config().Name == "Go"`, the same registry the renderer uses but not docsite's helper); `docsGoFences` keeps every fence for which it is true. + - SC4 additionally asserts: no fence with src= under docs/ is nested; no module README (every `modules/**/README.md`) has a src= fence; and in the built site `out`, each guide page's HTML has exactly as many `
` elements as the scanner found top-level src= fences in its source, and every `api/*.html` has none. + - New test `TestAcceptanceFenceScanner` feeding `scanDocFences` a body with a `> [!TIP]` src= fence, a plain blockquote go fence, a `golang` fence and a top-level src= fence, asserting the nested flags, the src values and that goFenceLang accepts go and golang and rejects go-html-template and text. +2. `scripts/check-phase11.1.sh`: + - `run_self_test`: after the existing plants, add expect_refusal plants on the real-tree scratch copy, each restored afterwards: a `> [!TIP]` callout holding a fence `go src=modules/bonfire/example_test.go#ExampleCall` with body BOGUS (want `snippet: modules/bonfire/example_test.go#ExampleCall: src= code block must be a top-level block`); a top-level `golang` fence without src= (want `snippet: golang code block has no src= reference`); an sh fence `FOO=1 summer no:such` and one `summer --root . no:such` (want `"no:such" is not a summer or application command`); and the text "See `lagoon.Openfromapp`." (want `identifier: lagoon.Openfromapp does not exist in modules/lagoon`). Build every backtick plant with printf as the existing go-fence plant does. + - Append to DOCSITE_TESTS: TestCollectFences TestFenceCaptionsOnlyVerified TestGoLang TestNestedFenceChecks TestIdentifierGoDocCaseSensitive TestCommandWord TestSnippetRootsAndBuild TestSyncParsedFences; append TestAcceptanceFenceScanner to SUMMER_TESTS. + - Once by hand, rename TestCommandWord in a scratch copy and confirm `--named` refuses; record it in the SUMMARY. +3. Planning docs (a separate `docs(11.1-07): ...` commit, never mixed with code): + - 11.1-VALIDATION.md: add rows 11.1-07-T1 to 11.1-07-T5 to the Per-Task Verification Map with requirement (DOCS-04 or DOCS-05), behaviour, test type, the exact automated command from each task's verify, File Exists and a green status after running it; mention plan 07 in the map's intro sentence; keep `status: validated` and `nyquist_compliant: true`. + - `.planning/todos/pending/readme-go-fences-src.md`: add a paragraph saying that since 11.1-07 checkSnippets refuses src= in module READMEs (readmeSrcMessage) and fenceAnnotator captions only docs/ pages, so converting README fences means replacing that refusal with checkFences over README pages, captioning their top-level src= fences, and extending Sync to README files. +4. Run `scripts/check-phase11.1.sh --all` and fix any failure at its source. +Commit code as `test(11.1-07): acceptance scanner sees blockquoted and aliased fences; gate plants the holes`. + + + go vet ./... && go test ./cmd/summer -run '^(TestPhase11_1Acceptance|TestAcceptanceFenceScanner)$' -count=1 -v + non-zero exit, a "--- FAIL" line, fewer than five "--- PASS: TestPhase11_1Acceptance/SC" lines, no "--- PASS: TestAcceptanceFenceScanner" line, or "no tests to run" + bash -n scripts/check-phase11.1.sh && scripts/check-phase11.1.sh --self-test && scripts/check-phase11.1.sh --all + non-zero exit, a line starting "refuse:", or no "phase11.1 all passed" line + + + - `go test ./cmd/summer -run '^TestPhase11_1Acceptance$' -count=1 -v | grep -c -- '--- PASS: TestPhase11_1Acceptance/SC'` prints 5. + - `grep -n 'func scanDocFences' cmd/summer/phase11_1_acceptance_test.go` and `grep -n 'func TestAcceptanceFenceScanner' cmd/summer/phase11_1_acceptance_test.go` each find one match. + - `grep -c 'top-level block' scripts/check-phase11.1.sh` prints at least 1, and `grep -n 'TestNestedFenceChecks' scripts/check-phase11.1.sh` finds the DOCSITE_TESTS entry. + - `scripts/check-phase11.1.sh --all` prints `phase11.1 all passed`. + - `grep -c '11.1-07-T' .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md` prints at least 5, and `grep -n '^status: validated$' .planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-VALIDATION.md` finds a match. + - `grep -n '11.1-07' .planning/todos/pending/readme-go-fences-src.md` finds the added paragraph. + - `scripts/check-phase11.1.sh --deps` passes, and `git diff --exit-code "$PLAN_BASE" HEAD -- go.mod go.sum` exits 0, where PLAN_BASE is the sha `git rev-parse HEAD` printed before Task 1's first commit (named in the SUMMARY): this plan adds no dependency. + + The acceptance test's own scanner would catch every CR-01/WR-04 form and cross-checks captions, the gate plants each hole and requires every new test by name, and VALIDATION and the README todo reflect plan 07. + + + + + +The assumption-delta detector fired on two pluralization cues ("another", "also") in the ROADMAP success-criteria prose, not on a model change. This plan tightens checker rules; no identity noun changes (a page, a fence, a src= reference and a module stay what they were). Decision: no-change. + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| docs/ pages and module READMEs → built site and llms outputs | Markdown authored by developers and agents becomes published "verified" documentation | +| docs checkers → go test / docs:build sign-off | A green check is the evidence that shown code compiles, runs and names real APIs | +| code-span text → go doc argv | A page's span text becomes an argument of a subprocess | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-11.1-20 | Repudiation (false "verified source" caption) | internal/docsite/render.go fenceAnnotator, snippet.go checkSnippets | high | mitigate | Captions only on top-level src= fences of docs/ pages, the set checkSnippets compares; nested and module README src= fences refused by Check, docs:build and docs:sync; fixtures snippet-callout-drift, snippet-callout-missing, snippet-blockquote, snippet-list-item, snippet-readme-src; SC4 caption cross-check in the built site | +| T-11.1-21 | Repudiation (unverified Go published as an example) | internal/docsite/check_policy.go go-fence rule | medium | mitigate | AST discovery at every depth; goLang through the highlighter's chroma lookup; fixtures go-fence-golang and go-fence-callout-no-src; acceptance scanner strips `>` and resolves aliases independently | +| T-11.1-22 | Repudiation (code never compiled or run shown as verified) | internal/docsite/snippet.go Extract, loadTestGraph | medium | mitigate | build.ImportDir membership under build.Default; testdata and _-prefixed Go targets refused; roots are Test functions and Examples with output from built test files; fixtures snippet-example-no-output-helper, -region, snippet-build-ignored, snippet-build-ignored-test-root, snippet-testdata-go | +| T-11.1-23 | Repudiation (stale identifier or command names pass) | internal/docsite/check_identifiers.go goDoc, check_commands.go commandWord | medium | mitigate | go doc -c; commandWord mirrors cobra stripFlags and covers env prefixes, go run ./cmd/summer and bin/{app}; fixtures identifier-wrong-case and command-env-prefix, -flag-first, -go-run, -bin-no-dot, command-in-callout; gate self-test plants | +| T-11.1-24 | Tampering (argument injection into go doc) | internal/docsite/check_identifiers.go goDoc | low | mitigate | `-c` is a constant argv element; the query is still gated by goDocIdent (an identifier or Ident.Member), so a span cannot add flags; exec without a shell, 30 s timeout unchanged | +| T-11.1-25 | Denial of service (a src= path makes the checker load arbitrary directories) | internal/docsite/snippet.go buildPackage | low | accept | build.ImportDir runs only on the directory of a src= path already confined to the repository root and root module (checkRefPath, EvalSymlinks, checkRootModule); it reads file headers and starts no process | +| T-11.1-SC | Tampering | go module installs | high | accept | This plan adds no module or package; go.mod and go.sum are unchanged (gate --deps) | + + + +- `go vet ./... && go test ./internal/docsite ./cmd/summer -count=1` green after every task. +- `go run ./cmd/summer docs:build --check` prints `docs:build: no problems found` on the real tree. +- The CR-01, WR-04, WR-02, WR-03 and WR-05 reproductions from 11.1-VERIFICATION.md are refused (Tasks 1 to 3 planted commands; Task 4 fixtures; Task 5 gate plants). +- `scripts/check-phase11.1.sh --all` prints `phase11.1 all passed` (Docker available). +- `go test -cover ./internal/docsite` at least 85%. + + + +- Gap 1 closed: fences for the snippet, policy and command checks come from the goldmark AST; nested and README src= fences are refused and never captioned; Go-lexer aliases need src=; the acceptance scanner strips `>` and resolves aliases; fixtures for the callout drift, callout missing file, callout go fence without src= and golang fence exist and pass. +- Gap 2 closed: roots are Test functions plus Examples with Output or EmptyOutput; .go src= targets are in the default build per go/build ImportDir; fixtures for an Example without Output and a //go:build ignore file exist and pass. +- Gap 3 closed: go doc runs with -c; env-prefix, go run ./cmd/summer, flag-first and bin/{app} command forms are parsed; fixtures for a wrong-case identifier and each command form exist and pass. +- DOCS-04 and DOCS-05 enforcement restored with the real tree still clean. + + +## Source audit + +| Source | ID | Item | Task | Status | +|--------|----|------|------|--------| +| GOAL | — | "Every code example compiles and is tested" (enforcement, not just content) | 1-5 | COVERED | +| REQ | DOCS-04 | Every Go fence src=; a test fails on missing or drifted snippet; Examples carry Output and run | 1, 2, 3, 4, 5 | COVERED | +| REQ | DOCS-05 | Checkers fail on stale identifiers and unknown command names | 3, 4, 5 | COVERED | +| REQ | DOCS-01..03, 06..08 | Not affected by the gaps; covered by plans 01 to 06 | — | COVERED (unchanged) | +| VERIFICATION | gap 1 missing 1 | AST fence collection for snippet, policy and command checks | 1, 2 | COVERED | +| VERIFICATION | gap 1 missing 2 | Refuse README src= fences and stop captioning them | 1, 2 | COVERED | +| VERIFICATION | gap 1 missing 3 | Normalise fence language through chroma | 2 | COVERED | +| VERIFICATION | gap 1 missing 4 | Fixtures (callout drift, callout missing file, callout go fence, golang fence) and acceptance scanner `>` stripping | 4, 5 | COVERED | +| VERIFICATION | gap 2 missing 1-3 | Roots from Test plus Examples with output; go/build ImportDir membership; fixtures | 3, 4 | COVERED | +| VERIFICATION | gap 3 missing 1-3 | go doc -c; env, go run, flag-first, bin/ forms; fixtures | 3, 4 | COVERED | +| RESEARCH | Q4 rules 1, 3, 5 | Every go fence src=; Examples need Output; missing refs fail | 2, 3 | COVERED | +| RESEARCH | Q6 | go doc fallback only on index miss; CLI checker forms | 3 | COVERED | +| CONTEXT | D-07 | Verified examples, compiled and run | 1, 3 | COVERED | +| CONTEXT | D-12 | Identifier checker must be exact | 3 | COVERED | +| CONTEXT | D-15 | chroma/v2 only; reused for alias detection, no new dependency | 2 | COVERED | +| CONTEXT | D-18 | README fences rendered as written: src= refused there, never captioned | 1, 2 | COVERED | + +Task count note: the user locked exactly one gap-closure plan carrying its own tests at the plan-count checkpoint, so this plan has five tasks (tracer, two expansion tasks, two closing test tasks) instead of the default two or three. + +## Artifacts this phase produces + +- New file `internal/docsite/fences.go`: type `mdFence` (fields `info`, `lang`, `line`, `topLevel`, `code`, `top`), type `fenceLine` (fields `line`, `text`), func `collectFences(doc gast.Node, body []byte) ([]mdFence, error)`. +- `internal/docsite/highlight.go`: func `goLang(lang string) bool`. +- `internal/docsite/snippet.go`: constants `nestedSrcMessage`, `readmeSrcMessage`; reworded `notRunMessage`; funcs `buildPackage(dir string) (*build.Package, error)`, `isTestName(name string) bool`; changed signatures `checkFences(root, file string, lines []string, lineBase int, fences []mdFence) ([]drift, []Problem, error)` and `(s *site) checkSnippets(docs []parsedDoc) ([]Problem, error)`; `isRoot` removed; Sync parses bodies after `splitFrontmatter`. +- `internal/docsite/check_commands.go`: func `commandWord(cmd string) (name string, tool bool, ok bool)`; the `commandToken` regex removed. +- `internal/docsite/check_policy.go`: AST go-fence rule via `collectFences` and `goLang`; AST callout-type check; the `calloutMarker` regex removed. +- `internal/docsite/check_identifiers.go`: goDoc argv gains `-c`. +- `internal/docsite/render.go`: fenceAnnotator restricted to top-level fences of docs/ pages; openFence accepts at most three leading spaces. +- `internal/docsite/docsite.go` / `load.go`: checkSnippets runs inside checkContent on the parsed docs. +- Tests: `TestCollectFences` (fences_test.go), `TestFenceCaptionsOnlyVerified` (render_test.go), `TestGoLang` (highlight_test.go), `TestNestedFenceChecks`, `TestIdentifierGoDocCaseSensitive`, `TestCommandWord` (checks_test.go), `TestSnippetRootsAndBuild`, `TestSyncParsedFences` (snippet_test.go), `TestAcceptanceFenceScanner` and helpers `scanDocFences`, `goFenceLang`, field `docFence.nested` (cmd/summer/phase11_1_acceptance_test.go). +- Fixtures under `internal/docsite/testdata/violations/`: `snippet-callout-drift`, `snippet-callout-missing`, `snippet-blockquote`, `snippet-list-item`, `go-fence-callout-no-src`, `go-fence-golang`, `snippet-readme-src`, `command-in-callout`, `snippet-example-no-output-helper`, `snippet-example-no-output-region`, `snippet-build-ignored`, `snippet-build-ignored-test-root`, `snippet-testdata-go`, `identifier-wrong-case`, `command-env-prefix`, `command-flag-first`, `command-go-run`, `command-bin-no-dot` (each with `want.txt`). +- Gate: five new `--self-test` plants; eight names added to DOCSITE_TESTS and one to SUMMER_TESTS. +- Docs: docs/console/utilities.md and README.md sentences on the src= rules. +- Planning: 11.1-VALIDATION.md rows 11.1-07-T1 to T5; a note in `.planning/todos/pending/readme-go-fences-src.md`. + + +Create `.planning/phases/11.1-summercms-documentation-for-humans-and-ai-agents/11.1-07-SUMMARY.md` when done. Record the mutation checks, any unit-test fixture corrected for the WR-01 hole, any production defect fixed with its test, and (as a follow-up candidate, not fixed here) that the identifier index still reads build-ignored files, which no gap cites. +