docsgen

package
v0.0.0-...-4ff976b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package docsgen generates the reference documentation under docs/reference/, from the things that cannot drift.

Four tables in this repository's prose were hand-maintained beside a registry that already knew the answer, and each one drifted the same silent way: the env-var table shipped ten variables short, the task table sent readers to a summary the schema had already outgrown, and a command added without a row was a capability nobody found. A table beside a source of truth is a second source of truth, and the second one is always the stale one.

So these are derived, and pinned in CI with `git diff --exit-code` — the same mechanism that keeps committed generated code honest against `buf generate`. A change that adds a task, a flag, an RPC or a variable and does not regenerate fails in CI rather than in somebody's afternoon six weeks later.

Why live Go values rather than compiled descriptors

docs/VISION.md warns that SourceCodeInfo — the leading comments a `.proto` carries — does not survive into the descriptors compiled into the binary: protoc-gen-go strips them, so a generator reading `protoregistry` finds field names and types and no prose. That is true and it is why nothing here reads a descriptor for documentation *text*.

This generator runs inside the binary instead, over the same live values the running program uses: the task catalog (the registry, which is also what the GetCatalog RPC answers with), the cobra tree (whose Short, Long and Example fields are Go string literals and so are present at runtime), and the MCP tool table. Prose written in Go is prose a generator can read, and the derivation is from the *same object the code dispatches on* rather than from a parallel artifact — which is the stronger property anyway. Descriptors still supply everything structural, by way of the catalog: a task's field names, types, and required-ness come from protovalidate rules on the schema.

The one thing that could change this is a custom protoc option carrying documentation into the descriptor set. That would be worth it only for prose that has nowhere else to live; today every sentence a reader needs is already a Go value.

Why a package, and why it is handed its sources

The generator lived in `package main` beside the twenty-odd command implementations it documents, which made it a correctness gate — CI runs it and pins the bytes — that nothing could exercise except through its own output (#410). It is here now, behind Sources: every live value it derives from arrives as a parameter, so what it claims (this document describes the objects this binary dispatches on) is the thing the call site says out loud, and a test can hand it a smaller tree and get a document about that instead.

`flow docs generate` is still the only caller that passes the real ones. This is a build step rather than a capability, and `internal/` is what says so.

Determinism

Every listing here sorts, and the environment is cleared before the cobra tree is built — flag defaults are read from the environment at construction, so a developer with FLOWSTATE_ADDRESS set would otherwise generate a diff. Running the generator twice must produce identical bytes; TestGeneratedDocsAreStable is that assertion.

Index

Constants

View Source
const DefaultDir = "docs/reference"

DefaultDir is where the generated reference lives, relative to the repository root.

Variables

This section is empty.

Functions

This section is empty.

Types

type Document

type Document struct {
	Name   string
	Render func() string
}

Document is one generated file: its name under the reference directory, and the function that renders its bytes.

Exported as a pair rather than as a written document, because the tests that keep this honest are about the bytes: rendering twice and comparing catches an unsorted iteration, and rendering once and comparing against the committed file is CI's pin, made locally first.

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator renders the reference from one set of sources.

func New

func New(src Sources) (*Generator, error)

New checks the sources and returns a generator over them.

It refuses an incomplete set rather than filling one in. A missing NewRoot would render a CLI reference documenting no commands, and an empty MCPTools an agent surface with no tools — both perfectly valid markdown, both wrong in the one way CI's `git diff --exit-code` pin would report as a legitimate change. Failing closed here turns that into a message naming what is missing.

func (*Generator) Documents

func (g *Generator) Documents() []Document

Documents is the whole set, in a fixed order.

func (*Generator) EnvironmentMirrors

func (g *Generator) EnvironmentMirrors() map[string]string

EnvironmentMirrors is [Generator.environmentMirrors] with the environment cleared around it, which is the only way the answer means anything.

Exported for the test that pins the derivation. Setting a sentinel and looking for it is clever enough to deserve one: a change to how a default is composed (wrapping it in a `cmp.Or`, say, which is already how two of them are written) could silently empty the whole Environment column, and an empty column reads as "no flag takes a variable" rather than as a broken derivation. That test has to run where the real command tree is built, which is cmd/flow, so this is the seam it asks through.

func (*Generator) Generate

func (g *Generator) Generate(dir string) ([]string, error)

Generate writes every generated document into dir, returning their paths.

type MCPTool

type MCPTool struct {
	// Name is the tool as an agent calls it, e.g. flowstate_run_local.
	Name string

	// Description is the sentence a model chooses the tool by.
	Description string

	// Request is the full name of the request message, where the tool is an RPC
	// and there is one. Empty for a tool with no RPC behind it.
	Request string

	// Local marks a tool that answers in the `flow mcp` process itself, with no
	// server and no Temporal.
	Local bool
}

MCPTool is one tool `flow mcp` registers, as the reference describes it.

Handed in rather than derived here, because the derivation needs the dispatch table itself: which side a tool answers on is carried by a Go func value that nothing outside can inspect, and the descriptions are assembled beside the tools they belong to. cmd/flow builds this list next to that table, where a tool added without a row is a compile-time neighbour rather than a document that quietly lost a line.

type Sources

type Sources struct {
	// NewRoot builds the cobra tree to document.
	//
	// A constructor rather than a built command, because the environment-mirror
	// probe rebuilds the tree once per documented variable: a flag default is
	// read from the environment when the command is constructed, so a shared
	// command would answer with whatever the environment held at the first call.
	NewRoot func() *cobra.Command

	// UseLine renders a command's usage line and FlagName a flag's spelling, the
	// way `flow help` renders each.
	//
	// Handed in rather than reimplemented here. Two spellings of one line is the
	// written-twice defect this repository keeps refinding, and a reference that
	// disagreed with `flow help` about what somebody types would be the version
	// nobody notices is wrong.
	UseLine  func(*cobra.Command) string
	FlagName func(*pflag.Flag) string

	// MCPTools is every tool `flow mcp` registers, in the order it registers
	// them. Which side a tool answers on is carried by a Go func value in the
	// dispatch table, which nothing outside can inspect, so it arrives as data.
	MCPTools []MCPTool

	// DefaultAddress is what `--address` defaults to with FLOWSTATE_ADDRESS
	// unset: one cell of the env-var table, and the only fact in it this package
	// cannot read from what it was handed.
	DefaultAddress string
}

Sources are the live values a reference is derived from.

They are handed in rather than reached for, and that is the whole point of the package boundary: this generator's claim is that it documents the objects the running program dispatches on, and a parameter is where such a claim can be checked. cmd/flow passes the real ones.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL