HOVEL // chapter 27
Chapter 27 Part 05 / Engineering

Development Guide

Hovel is developed as a monorepo with a sparse-checkout friendly layout. The core framework lives in its own build workspace, while SDKs, modules, and documentation live beside it. The root task system is the compatibility layer that lets contributors keep using stable commands from either a full checkout or a small Sapling checkout.

Build Entry Point

All local and automated build operations go through task. Contributors, CI, hooks, and automation do not call Bazel, gofmt, uv, rustfmt, clang-format, or Lefthook directly. If a workflow needs a new operation, add a Task-backed command and make that command own the tool invocation.

task --list
task checkout:status
task check
task ci

The root Taskfile.yml is intentionally small enough to stay present in every sparse checkout. It delegates into slice-local task systems and keeps checkout detection in repo-tools/tasks/checkout.py.

Checkout Slices

The partial-checkout model treats the repository as named slices. A contributor working on the daemon or CLI can check out core/ plus the root dispatcher. A module author can check out core/, sdk/, and the specific module subtree they need.

Slice Path Purpose
core core/ Self-contained Go/Bazel workspace for the Hovel framework binary, daemon, CLI/TUI/MCP front ends, schemas, core tests, and core build tooling.
sdk sdk/ Python, Go, and Rust module SDKs. SDKs are outside core so SDK work does not require framework internals beyond the public protocol contract.
modules modules/ In-repo example modules, Squatter payload/provider code, module packaging tools, and lab helpers.
docs docs/ Pages source, book content, terminal demos, generated API documentation tooling, and site assets.
repo-tools repo-tools/ Small repository-level helpers used by the root Task dispatcher. This stays independent of the core build workspace.

Core Boundary

core/ is deliberately narrow: it contains only the code and build system needed to produce the Hovel Go binary and its front ends. That includes cmd/hovel, internal, schemas, platform declarations, pinned core build inputs, and core tests. It does not contain the language SDKs, in-repo modules, Squatter payload, documentation site, or demo materializers.

This boundary keeps a core checkout small and defensible. Core may define the daemon, application services, storage, protocol framing, and module runtime adapter interfaces. Modules and SDKs should depend on public descriptors and wire contracts instead of importing core internals. Test-only module implementations that are required by core tests live under core/internal/testmodules; the Python module runtime lives under core/internal/moduleruntime so it is clearly framework runtime code, not an in-repo module.

Check And CI

task check is partial-checkout tolerant. It detects the slices present in the working tree and runs each corresponding slice check. Missing optional slices are reported as skipped, not as failures.

$ task check
core: running task core:check
sdk: running task sdk:ci
modules: running task modules:ci
docs: running task docs:check

task ci is the remote-compatible full-checkout gate. It first verifies that the expected top-level slices are present, then runs repository policy, core, SDK, module, and docs verification gates. CI uses the same root tasks instead of calling Bazel, Go, Rust, Python, or docs tooling directly.

task check  # available-slice gate for sparse work
task ci     # full-checkout gate for every wired slice

This distinction is deliberate: sparse work should be ergonomic, but a release or merge gate must not silently pretend an absent slice was validated. Missing slices are tolerated only by the sparse-aware aggregate; present slices run their real checks. Normal documentation builds consume reviewed GIFs from docs/site/public/assets/demos/; only the explicit docs:demos... refresh tasks cross the host-service boundary to drive tmux, Chrome, and, for the Wine recording, Docker.

Explicit Slice Tasks

When you know what you are working on, call the slice task directly. Direct slice tasks fail if their own inputs are absent or broken.

task core:check
task core:build -- //cmd/hovel
task core:test -- //internal/domain/...
task sdk:fmt
task sdk:ci
task modules:ci
task docs:check
task docs:site
task docs:ci
task release:modules-package

task docs:check is the remote-compatible docs test and assembly gate. task docs:site builds and stages the same deterministic site from declared inputs, while task docs:ci composes both checks. Run task docs:demos, task docs:demos:wine, or task docs:demos:all separately only when refreshing the checked-in recordings.

General tasks such as task build, task test, task lint, and task coverage dispatch to the core workspace because those names describe the Hovel binary workspace. task fmt formats the wired source slices. task check is the tolerant aggregate.

Design Rules

  1. Missing checkout slices are only tolerated by tolerant aggregate tasks.
  2. Explicit tasks fail clearly when their required paths are absent.
  3. Real build, test, lint, docs, or coverage failures are never converted into skips.
  4. Root task names are stable. Directory moves should update slice detection and target lists without changing the everyday command contract.
  5. CI and release tasks require the full checkout so published artifacts are built from the whole declared source tree.

Current Limitations

The non-core slices share a root integration Bazel workspace so their checks can depend on the core workspace without moving SDKs, modules, or docs back under core/. That keeps sparse checkout boundaries clear, but it also means cross-slice labels should be treated as public integration points and kept narrow.

The module release package task currently stages Linux and Darwin compiled module launchers. The Windows Squatter payload remains covered by the Wine task and demo path, but Windows compiled module package launchers are not advertised until the MinGW cross-link path is made reliable for those targets.