Documentation
¶
Overview ¶
Package buildctl exposes the Gothic build pipeline as a structured Go API: each tool runs one stage (templ, routes_gen, topic_gen, wasm, css, the app build), is incremental against the on-disk caches the pipeline already keeps, and returns a BuildResult carrying what it recompiled, how long it took, translated diagnostics for any failure, and the artifacts its output just invalidated ("now stale: routes_gen, wasm:/counter").
The tool layer never prints. Callers (the hot-reload command today, the MCP in the future) render results to their own output contract; the narration the underlying helpers print (Recompiling..., size lines) is theirs.
Concurrency: the generator stages and the app build share one build lock, which is also what an edit session holds across a multi-file edit; the WASM stage serialises on its own lock because the hot-reload session compiles it in the background while the foreground build proceeds.
Index ¶
- Constants
- func CollectLocalDirs(pages []helpers.WasmPage) []string
- func ComputeDigest(localDirs []string) string
- func InputChanged() bool
- func RecordDigestFor(snap Snapshot, localDirs []string)
- func RecordDigestNow(localDirs []string)
- func SameDirs(a, b []string) bool
- func WriteDigest(digest string, localDirs []string)
- type BuildResult
- type Controller
- func (c *Controller) BuildCSS(page string) BuildResult
- func (c *Controller) BuildGo() BuildResult
- func (c *Controller) BuildRoutes() BuildResult
- func (c *Controller) BuildStatus() StatusResult
- func (c *Controller) BuildTempl(file string) BuildResult
- func (c *Controller) BuildTopics() BuildResult
- func (c *Controller) BuildWasm(target string) BuildResult
- func (c *Controller) EditBegin()
- func (c *Controller) EditEnd() BuildResult
- func (c *Controller) Editing() bool
- func (c *Controller) Sync() BuildResult
- type Diagnostic
- type DigestData
- type Options
- type Recompiled
- type Snapshot
- type StageCounts
- type StatusArtifact
- type StatusResult
Constants ¶
const ( StageTempl = "templ" StageRoutesGen = "routes_gen" StageTopicGen = "topic_gen" StageWasm = "wasm" StageCSS = "css" StageGo = "go" StageSync = "sync" )
Stage names — the stage field of every BuildResult.
const ( StepConfig = "config" StepEmbedded = "embedded" )
Sub-step names a stage can fail at (the routes stage reads the config first and syncs the embedded public file last, and callers want to know which of the three failed without parsing the error text).
const ( StatusFresh = "fresh" StatusStale = "stale" StatusMidRebuild = "mid-rebuild" )
Status states for one artifact.
const BuildCachePath = ".gothicCli/wasm-cache.json"
BuildCachePath mirrors the build package's own per-unit cache. It is read here, never written, so the gate can tell that something outside this session rewrote the artifacts.
const DigestPath = ".gothicCli/wasm-digest.json"
DigestPath is the on-disk location of the WASM input digest.
Variables ¶
This section is empty.
Functions ¶
func CollectLocalDirs ¶
CollectLocalDirs is collectLocalDirs for the host layer (which owns the page scan through the same helper).
func ComputeDigest ¶
ComputeDigest returns a hex-encoded SHA-256 digest of everything the WASM scan and generate stages consume.
func InputChanged ¶
func InputChanged() bool
InputChanged reports whether any WASM-stage input changed since the last recorded build, for callers that only read the gate and never record.
func RecordDigestFor ¶
RecordDigestFor persists the digest of what the build actually consumed.
It records the snapshot taken BEFORE the build, not a fresh reading. A save landing while the stage is running is the whole reason: recomputing here would store a digest describing content this build never compiled, and the next cycle would compare against it, see no change, and skip the stage forever, leaving a stale binary with no output saying so. Measured: an edit made during the 25s initial rebuild left the page running the previous logic until an unrelated file was touched.
A changed local-package set is the one case that has to recompute: the snapshot was taken over a different file set, so it does not describe these inputs either. That costs one extra rebuild, which is the safe direction.
func RecordDigestNow ¶
func RecordDigestNow(localDirs []string)
RecordDigestNow records a digest read at call time. A build stage must NOT use this: it cannot tell a file the build compiled from one saved while it ran. Use RecordDigestFor there.
func WriteDigest ¶
Types ¶
type BuildResult ¶
type BuildResult struct {
OK bool `json:"ok"`
Stage string `json:"stage"`
Step string `json:"step,omitempty"`
Target string `json:"target,omitempty"`
Recompiled []Recompiled `json:"recompiled,omitempty"`
MS int64 `json:"ms"`
Raw string `json:"raw,omitempty"`
Diagnostics []Diagnostic `json:"diagnostics,omitempty"`
// NowStale lists the artifacts this build's output just invalidated, in
// pipeline order: e.g. "routes_gen", "wasm:/counter". The names are the
// stage/artifact names; a wasm entry may be per-route ("wasm:/counter")
// or the whole inventory ("wasm").
NowStale []string `json:"nowStale,omitempty"`
Counts *StageCounts `json:"counts,omitempty"`
Suppressed bool `json:"suppressed,omitempty"`
// Err is the raw error text of a failed stage ("" when OK).
Err string `json:"err,omitempty"`
}
BuildResult is the structured outcome of one build tool. OK is false only when a stage failed; Suppressed marks a call that ran during an edit session, which performs no work by design.
type Controller ¶
type Controller struct {
// Executor seams. Nil = the production default bound over the CLI helpers;
// tests replace them with fakes.
TemplRun func(file string) (string, error) // "" = all dirty files
RoutesRun func(goModName string) error
CssRun func() error
GoRun func(binary string) (string, error) // combined captured output
WasmRun func(target string, snap Snapshot) (BuildResult, error) // the WASM stage incl. gate
// contains filtered or unexported fields
}
Controller owns the build pipeline for one session (a hot-reload command today; the MCP server in the future). All build calls are serialised against the caches they share.
func New ¶
func New(cli *gothic_cli.GothicCli, opts Options) *Controller
New builds a Controller over the CLI's build helpers.
func (*Controller) BuildCSS ¶
func (c *Controller) BuildCSS(page string) BuildResult
BuildCSS compiles the Tailwind output. Tailwind scans every template for classes, so the build is project-wide; an optional page name is recorded as the target. The output file's mtime gates the build: fresh CSS is a no-op.
func (*Controller) BuildGo ¶
func (c *Controller) BuildGo() BuildResult
BuildGo compiles the app server binary, capturing its output for diagnostics while echoing it to the terminal.
func (*Controller) BuildRoutes ¶
func (c *Controller) BuildRoutes() BuildResult
BuildRoutes runs the routes_gen stage: config read, the file-based route registry, and (when wired) the EMBEDDED-mode embed file. The stage's output feeds the app build, so a rewrite declares it stale.
func (*Controller) BuildStatus ¶
func (c *Controller) BuildStatus() StatusResult
func (*Controller) BuildTempl ¶
func (c *Controller) BuildTempl(file string) BuildResult
BuildTempl runs the templ generate stage. An empty file compiles every dirty .templ file (the incremental cached path); a named file regenerates just that one. Templ output becomes the pipeline's routes_gen and WASM inputs, so a run that compiled anything declares them stale.
func (*Controller) BuildTopics ¶
func (c *Controller) BuildTopics() BuildResult
BuildTopics regenerates src/topics/topic_gen.go from the topic declarations. The generated accessors are inlined into every page's WASM entry point and compiled into the server, so a rewrite declares both stale.
func (*Controller) BuildWasm ¶
func (c *Controller) BuildWasm(target string) BuildResult
BuildWasm runs the WASM stage: the digest gate, topic-stub ordering (stubs before the scan), the page scan, and the incremental per-symbol compile. A target (route path, component path, or topic name) scopes the reporting to the matching units; the stage itself always runs the full inventory because the per-symbol cache makes everything else instant.
func (*Controller) EditBegin ¶
func (c *Controller) EditBegin()
EditBegin suspends watcher-driven rebuilds and holds the build lock across a multi-file edit sequence. While the session is open, every build call short-circuits with a Suppressed result instead of racing the edit. A second Begin while a session is open is a no-op.
func (*Controller) EditEnd ¶
func (c *Controller) EditEnd() BuildResult
EditEnd closes the edit session and runs exactly one Sync over the changed state. Returns the sync's BuildResult, and fires the host's AfterSync hook so it can re-arm its own rebuild cycle for the app binary and the reload. When no session is open, it is a no-op.
func (*Controller) Editing ¶
func (c *Controller) Editing() bool
Editing reports whether an edit session currently holds the build lock. The hot-reload watcher consults this to suspend its own rebuilds.
func (*Controller) Sync ¶
func (c *Controller) Sync() BuildResult
Sync brings every generated artifact up to date in pipeline order — templ → routes_gen → topic_gen → wasm → css — incrementally and idempotently: when nothing changed, no stage recompiles and the call returns immediately. It does NOT run BuildGo; the caller compiles the app when it wants the server binary fresh.
type Diagnostic ¶
type Diagnostic struct {
Raw string `json:"raw"`
Diagnosis string `json:"diagnosis"`
Fix string `json:"fix"`
Skill string `json:"skill,omitempty"`
}
Diagnostic is a translated build failure. Raw carries the tool's own compiler/generator text verbatim; Diagnosis and Fix are the plain-language translation; Skill, when set, names a bundled skill document covering the area (see internal/skills).
func DiagnoseAll ¶
func DiagnoseAll(raw string) []Diagnostic
DiagnoseAll translates raw tool output into diagnostics. The raw text is preserved verbatim inside every diagnostic; an output no entry recognizes still comes back untranslated, so no signal is ever dropped.
type DigestData ¶
type DigestData struct {
Digest string `json:"digest"`
LocalPackageDirs []string `json:"localPackageDirs,omitempty"`
}
DigestData is the on-disk format under DigestPath.
func LoadDigest ¶
func LoadDigest() *DigestData
LoadDigest reads the stored digest. Returns nil on any error.
type Options ¶
type Options struct {
// MainBinary is the app build target ("tmp/main"); derived from the CLI
// runtime when empty.
MainBinary string
// SyncEmbedded syncs the EMBEDDED-mode gothic_embed_gen.go — a host-layer
// concern (the file's shape is decided by the command layer). Nil skips
// the step.
SyncEmbedded func(config *gothic_cli.Config) error
// WasmLogf / WasmErrorf narrate the WASM stage the way the hosting
// command's stream expects. No-ops when nil.
WasmLogf func(format string, args ...any)
WasmErrorf func(format string, args ...any)
// WasmInventory fires right before the WASM generate phase with the scan
// inventory (pages, components, topics); the host prints its own line.
WasmInventory func(pages, components, topics int)
// BeginEdit suspends the host's watcher-driven rebuilds (stops arming
// them) for the duration of an edit session; the returned function is
// called when the session ends.
BeginEdit func() (end func())
// AfterSync fires after EditEnd's one Sync, so the host can re-arm its
// rebuild cycle for the app binary and the reload.
AfterSync func(res BuildResult)
}
Options wire the controller to its host. Every field is optional.
type Recompiled ¶
Recompiled is one artifact a build tool actually wrote.
type Snapshot ¶
Snapshot is the gate reading taken BEFORE a build: the digest of the inputs the build is about to consume, the dirs it was computed over, and whether it differs from the last recorded build.
func TakeSnapshot ¶
func TakeSnapshot() Snapshot
TakeSnapshot reads the gate. Fail-open: on any error the stage runs.
type StageCounts ¶
StageCounts mirror the WASM helper's own counters after a stage.
type StatusArtifact ¶
type StatusArtifact struct {
Name string `json:"name"`
// State is one of the Status* constants.
State string `json:"state"`
Detail string `json:"detail"`
}
StatusArtifact is the freshness of one build artifact.
type StatusResult ¶
type StatusResult struct {
// Rebuilding is true while any stage (foreground or the background WASM
// compile) is running.
Rebuilding bool `json:"rebuilding"`
// Editing is true while an edit session holds the build lock.
Editing bool `json:"editing"`
Artifacts []StatusArtifact `json:"artifacts"`
}
StatusResult is the freshness report of the whole artifact graph.