Documentation
¶
Overview ¶
Package devloop is the embedded fsnotify dev orchestrator behind the `dockyard dev` command (RFC §9.2).
One `dockyard dev` process supervises a process tree from a single command. devloop embeds an fsnotify file watcher — Dockyard does not shell out to air or wgo (RFC §9.2, brief 06 §2.6) — and choreographs three concerns:
- the Go MCP server, rebuilt and restarted on a .go source change (Go has no in-process hot reload, so the server is restarted, not patched);
- codegen, re-run in-process via internal/generate on a contract-source change, so the generated types are live before the server restarts;
- the Vite dev server, started and supervised for the project's web/ UI (Vite owns Svelte HMR — Dockyard never reimplements it).
The orchestrator is a reusable, concurrency-safe artifact: Run is the single public entrypoint, the `dockyard dev` cobra verb is a thin wrapper over it, and the integration test drives the same Run. It holds no global state.
Lifecycle. Run blocks until the supplied context is cancelled (Ctrl-C) or a fatal error occurs. On return, the whole process tree — the Go server, Vite, and the watcher — is torn down: no orphan processes, no leaked goroutines, no leaked ports. A child-process crash is reported through log/slog and the loop survives; devloop never panics across the boundary (CLAUDE.md §5, §13).
Graceful degradation. A scaffolded blank server has no web/ UI project; in that case devloop supervises only the Go server, logs that no UI project was found, and does not error (RFC §4.1: a UI resource is additive).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Run ¶
Run starts the dev orchestrator and blocks until ctx is cancelled or a fatal error occurs. On return the whole process tree — the Go server, Vite, and the file watcher — has been torn down: no orphan processes, no leaked goroutines, no leaked ports.
Run is safe to call once per Config and holds no global state. A child-process crash is reported through the logger and the loop survives; a context cancellation is a clean, non-error shutdown (Run returns nil).
Types ¶
type Config ¶
type Config struct {
// ProjectDir is the scaffolded project root — the directory holding
// dockyard.app.yaml. Required.
ProjectDir string
// Logger receives the orchestrator's dev output. Required-ish: a nil
// Logger falls back to a discarding logger so Run never panics on a
// missing logger, but a caller should pass the dev-mode text handler.
Logger *slog.Logger
// Debounce is the file-event coalescing window. Zero uses defaultDebounce.
Debounce time.Duration
// GoServerCommand overrides the default `go run .` for the supervised Go
// MCP server. Empty uses the default. It is the test/seam injection point:
// the integration test injects a controllable stub so it does not need to
// build a real server on every restart.
GoServerCommand []string
// ViteCommand overrides the default `npm run dev` for the supervised Vite
// dev server. Empty uses the default. Test/seam injection point.
ViteCommand []string
// SkipCodegen disables the in-process codegen step on a contract change.
// It exists for tests that exercise the watch+restart choreography without
// a full Go toolchain codegen run; production always leaves it false.
SkipCodegen bool
// DisableInspector skips the supervised inspector child. By default the
// dev loop auto-attaches the inspector as a third supervised child
// (v1.1 Wave A; closes D-101). A developer who wants the headless dev
// loop — a CI run, a screen-sharing session, a no-UI iteration —
// passes --no-inspector at the CLI which sets this flag.
DisableInspector bool
// InspectorAddr is the inspector's loopback bind address. Empty selects
// `127.0.0.1:0` (an OS-assigned port). A non-loopback or malformed
// address is rejected by internal/inspector.requireLoopback before the
// listener opens; the inspector child stays down and the dev loop's
// other children continue.
InspectorAddr string
// ServerHTTPAddr is the deterministic HTTP transport address the dev
// loop pins for the Go server when the inspector auto-attaches.
// Empty selects 127.0.0.1:8080 — the same default the scaffolded
// templates and `examples/prompts-demo` use, so a default dev session
// works without configuration. The pin lands as a default env-var on
// the supervised Go server; a developer who already exported
// DOCKYARD_HTTP_ADDR in their shell wins via the later-entry-wins
// env-var ordering.
ServerHTTPAddr string
// contains filtered or unexported fields
}
Config configures one dev-orchestrator run.
func WithTestHooks ¶
WithTestHooks returns a copy of cfg with the given test hooks attached. It is the only way to populate the orchestrator's unexported hooks field from another package. Production callers never use it.
type TestHooks ¶
type TestHooks struct {
// OnReady fires once, after the initial process tree is up and the watcher
// is running.
OnReady func()
// OnServerRestart fires after each successful Go-server (re)start triggered
// by a file change.
OnServerRestart func()
// OnCodegen fires after each in-process codegen run with its error (nil on
// success).
OnCodegen func(error)
// OnInspectorReady fires once, after the supervised inspector child has its
// loopback listener open and is serving. The argument is the resolved
// inspector URL (e.g. http://127.0.0.1:54321) so the test can drive the
// inspector directly without polling for a port. When DisableInspector is
// set (the --no-inspector flag's effect), the hook never fires. Added by
// v1.1 Wave A (D-161) to make the auto-attach observable from an
// out-of-package integration test.
OnInspectorReady func(url string)
}
TestHooks is the set of lifecycle callbacks an out-of-package test attaches to a dev-orchestrator run. Every field is optional; a nil callback is not invoked.