From 59580b6e45f8f537d85f4ce123df9fea17f71bc6 Mon Sep 17 00:00:00 2001 From: Paperclip Builder Date: Mon, 26 Oct 2026 22:50:09 +1100 Subject: [PATCH] site: publish the platform guides at /platforms/ and link them from an About page (cherry picked from commit 408d6cdfd76d13945db1d9727b58feca23d1e48b) --- content/about/l3/about-sources.md | 49 ++++++++++++ internal/site/build.go | 7 ++ internal/site/platforms.go | 128 ++++++++++++++++++++++++++++++ internal/site/platforms_test.go | 42 ++++++++++ 4 files changed, 226 insertions(+) create mode 100644 content/about/l3/about-sources.md create mode 100644 internal/site/platforms.go create mode 100644 internal/site/platforms_test.go diff --git a/content/about/l3/about-sources.md b/content/about/l3/about-sources.md new file mode 100644 index 0000000..41bfab0 --- /dev/null +++ b/content/about/l3/about-sources.md @@ -0,0 +1,49 @@ +--- +id: about-sources +area: about +level: 3 +title: "What the assistant reads" +claim: "Four sources, one pool, and a published rule for every one of them." +confidence: running +period: "2026–" +tech: [Gitea, Obsidian, Graphify, Qdrant, SQLite] +metrics: [] +artefacts: [] +public: true +last_verified: 2026-10-26 +tags: [ai, sources, corpus, retrieval] +--- + +# What the assistant reads + +The assistant answers from a pool built out of four sources, not from a single +folder of copy. + +| Source | Tier | What it contributes | +|---|---|---| +| This site's content | 1 | The curated claims: what Sam says is true about the work | +| The Gitea estate | 2 | README files, file trees, selected source files, commit messages and language statistics | +| The notes vault | 2 | Project and area notes, in Sam's own words | +| The knowledge graph | 2 | Nodes and edges over the vault, each joined back to the note it came from | + +Tier 1 wins when the two disagree. A harvested README can be out of date; the +curated claim is the one that is maintained. + +Every chunk records the platform it came from, the URL or repository path it can +be opened at, the area it feeds, its tier, its honest confidence and when it last +changed. A chunk without an origin is refused, not defaulted. + +The rules are not hidden in code. Each platform has a guide that says what it is, +where it lives, what is included, what is excluded and whether it is enabled. +Those guides are shown at [the sources page](/platforms/) and are the same files +the ingestion pipeline reads. + +## What is excluded + +`node_modules`, virtualenvs, git internals, trash, editor state, the SilverBullet +auth file, credential-shaped files, binaries and media. Media is recorded as an +image reference and never turned into text. Personal and family notes are not +read. + +The finished pool is scanned for credential-shaped content before a run can +succeed, so the exclusion list is checked rather than trusted. diff --git a/internal/site/build.go b/internal/site/build.go index 8572e20..8c33a97 100644 --- a/internal/site/build.go +++ b/internal/site/build.go @@ -100,6 +100,13 @@ func (s *Site) Build() error { if err := writePage("/404.html", templates.NotFoundPage(s.NotFoundPage())); err != nil { return err } + // The platform guides are configuration and public copy at the same time. + // Rendering them here keeps the page and the pipeline's include/exclude rules + // from drifting: both read platforms/*.yml. + if _, err := RenderPlatforms(s.Config.Root, s.Config.OutDir); err != nil { + return fmt.Errorf("platform guides: %w", err) + } + pages++ for _, f := range s.Fragments { if err := writeFile(filepath.Join(s.Config.OutDir, filepath.FromSlash(f.File)), []byte(f.HTML)); err != nil { diff --git a/internal/site/platforms.go b/internal/site/platforms.go new file mode 100644 index 0000000..4c149ec --- /dev/null +++ b/internal/site/platforms.go @@ -0,0 +1,128 @@ +package site + +import ( + "encoding/json" + "fmt" + "html/template" + "os" + "path/filepath" + "sort" + "strings" + + "gopkg.in/yaml.v3" +) + +// PlatformGuide is one `platforms/.yml` file: configuration and public +// copy at the same time. The pipeline reads include/exclude from here; the site +// shows what, where it lives, and what is read. +type PlatformGuide struct { + Name string `yaml:"name" json:"name"` + Enabled bool `yaml:"enabled" json:"enabled"` + What string `yaml:"what" json:"what"` + Fits []string `yaml:"fits" json:"fits"` + Base string `yaml:"base" json:"base"` + Read []string `yaml:"read" json:"read"` + Public bool `yaml:"public" json:"public"` + Include struct { + Repos any `yaml:"repos" json:"repos"` + Paths []string `yaml:"paths" json:"paths"` + } `yaml:"include" json:"include"` + Exclude struct { + Repos []string `yaml:"repos" json:"repos"` + Paths []string `yaml:"paths" json:"paths"` + } `yaml:"exclude" json:"exclude"` +} + +// LoadPlatforms reads every guide. A file that does not parse is an error: the +// site must not silently show a different list from the one the pipeline reads. +func LoadPlatforms(root string) ([]PlatformGuide, error) { + dir := filepath.Join(root, "platforms") + entries, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + var out []PlatformGuide + for _, entry := range entries { + if entry.IsDir() || !(strings.HasSuffix(entry.Name(), ".yml") || strings.HasSuffix(entry.Name(), ".yaml")) { + continue + } + raw, err := os.ReadFile(filepath.Join(dir, entry.Name())) + if err != nil { + return nil, err + } + var guide PlatformGuide + if err := yaml.Unmarshal(raw, &guide); err != nil { + return nil, fmt.Errorf("%s: %w", entry.Name(), err) + } + if guide.Name == "" || guide.What == "" { + return nil, fmt.Errorf("%s: name and what are required", entry.Name()) + } + out = append(out, guide) + } + sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) + return out, nil +} + +// RenderPlatforms writes /platforms/index.html and /platforms/index.json. +// +// The page is generated outside the templ layout because templ needs a Node-free +// generator on PATH and the build host has no network requirement. It uses the +// same stylesheet, the same navigation and the same wording as the rest of the +// site; the only thing it does not have is the presentation panel, which has no +// meaning on a reference page. +func RenderPlatforms(root, outDir string) (int, error) { + guides, err := LoadPlatforms(root) + if err != nil { + return 0, err + } + target := filepath.Join(outDir, "platforms") + if err := os.MkdirAll(target, 0o755); err != nil { + return 0, err + } + + var body strings.Builder + body.WriteString(`

The assistant reads four sources. Three are harvested from the estate; the fourth is the curated text this site is built from. Everything below is the same configuration the pipeline runs.

`) + for _, guide := range guides { + state := "enabled" + if !guide.Enabled { + state = "disabled" + } + body.WriteString(`
`) + fmt.Fprintf(&body, `

%s %s

`, template.HTMLEscapeString(guide.Name), state) + fmt.Fprintf(&body, `

%s

`, template.HTMLEscapeString(strings.TrimSpace(guide.What))) + fmt.Fprintf(&body, `
Where it lives
%s
`, template.HTMLEscapeString(guide.Base)) + fmt.Fprintf(&body, `
Feeds
%s
`, template.HTMLEscapeString(strings.Join(guide.Fits, ", "))) + fmt.Fprintf(&body, `
Reads
%s
`, template.HTMLEscapeString(strings.Join(guide.Read, ", "))) + if len(guide.Include.Paths) > 0 { + fmt.Fprintf(&body, `
Includes
%s
`, template.HTMLEscapeString(strings.Join(guide.Include.Paths, ", "))) + } + if len(guide.Exclude.Paths) > 0 { + fmt.Fprintf(&body, `
Excludes
%s
`, template.HTMLEscapeString(strings.Join(guide.Exclude.Paths, ", "))) + } + body.WriteString(`
`) + } + + page, err := template.New("platforms").Parse(platformsPage) + if err != nil { + return 0, err + } + file, err := os.Create(filepath.Join(target, "index.html")) + if err != nil { + return 0, err + } + defer file.Close() + if err := page.Execute(file, map[string]any{"Body": template.HTML(body.String())}); err != nil { + return 0, err + } + + raw, err := json.MarshalIndent(map[string]any{"version": 1, "platforms": guides}, "", " ") + if err != nil { + return 0, err + } + if err := os.WriteFile(filepath.Join(target, "index.json"), append(raw, '\n'), 0o644); err != nil { + return 0, err + } + return len(guides), nil +} + +const platformsPage = `Sources and platform guides — Sam Rolfe
SR Sam Rolfe AI · Platform · Edge

AI overlay

What the assistant reads

Four sources, one pool. Content is tier 1: a curated claim outranks harvested text when the two disagree.

{{.Body}}

Every artefact · Search the site

` diff --git a/internal/site/platforms_test.go b/internal/site/platforms_test.go new file mode 100644 index 0000000..e8fd189 --- /dev/null +++ b/internal/site/platforms_test.go @@ -0,0 +1,42 @@ +package site + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestRenderPlatformsShowsTheGuides(t *testing.T) { + root, err := filepath.Abs(filepath.Join("..", "..")) + if err != nil { + t.Fatal(err) + } + out := t.TempDir() + count, err := RenderPlatforms(root, out) + if err != nil { + t.Fatalf("RenderPlatforms: %v", err) + } + if count != 3 { + t.Fatalf("got %d guides, want 3", count) + } + raw, err := os.ReadFile(filepath.Join(out, "platforms", "index.html")) + if err != nil { + t.Fatalf("read page: %v", err) + } + page := strings.ToLower(string(raw)) + for _, want := range []string{ + "what the assistant reads", + "self-hosted git", + "personal knowledge base", + "knowledge graph over the notes", + "node_modules", + } { + if !strings.Contains(page, want) { + t.Errorf("page does not contain %q", want) + } + } + if _, err := os.Stat(filepath.Join(out, "platforms", "index.json")); err != nil { + t.Errorf("index.json missing: %v", err) + } +}