| id | 102 |
|---|---|
| title | Builder interface and mdsmith build subcommand |
| status | 🔲 |
| summary | Implement the `Builder` interface, built-in recipe drivers (`screenshot` via chromedp, `vhs` via exec, custom via `os/exec` argv), and the `mdsmith build` subcommand that walks files, dispatches to recipe drivers, and writes artifacts atomically. |
| model | opus |
Execute <?build?> directives: run the declared
recipe, write the artifact, and report per-file
success or failure. mdsmith check and mdsmith fix
remain unaffected — no external tool runs at lint
time.
Depends on plan 101 (<?build?> directive and
MDS039). The directive parser and recipe resolution
from plan 101 are reused here. The build: config
schema from plan 100 provides recipe declarations
and base-url.
// internal/build/builder.go
type Builder interface {
Build(ctx context.Context, params map[string]string,
output string) error
}params contains the directive's key/value pairs.
output is the resolved absolute path for the
artifact. Each recipe driver implements Builder.
A registry maps recipe name → Builder. At startup,
the mdsmith build command registers built-ins and
constructs a custom Builder for each entry in
build.recipes.
screenshot — uses
chromedp:
| Param | Required | Default |
|---|---|---|
url |
yes | — |
selector |
no | full page |
viewport |
no | 1280x800 |
wait |
no | 0 ms |
click |
no | — |
hide |
no | [] |
build.base-url is prepended to path-only url
values (starting with /).
vhs — runs the vhs binary via os/exec:
| Param | Required |
|---|---|
input |
yes |
Skipped when vhs is not in PATH.
User-declared recipes in build.recipes are
compiled into a Builder at config load time:
commandis split on whitespace into an argv list (the same split used by MDS040 in plan 100).{param}tokens are replaced with the corresponding directive param value at call time.- The resulting argv is passed directly to
os/exec.Cmd— nosh -c, no shell metacharacter expansion.
A value like foo; rm -rf / is passed literally to
the binary as a single argument, not interpreted by
a shell.
mdsmith build [paths...] [flags]
Flags:
| Flag | Description |
|---|---|
--recipe NAME |
Only build directives using this recipe |
--base-url URL |
Override build.base-url from config |
--dry-run |
List every target; run no tool |
--timeout DURATION |
Per-recipe timeout (default 30s) |
Behavior:
- Walk
paths(default: current directory); collect all<?build?>blocks via the plan 101 directive parser. - Apply
--recipefilter when set. --dry-run: print each target (recipe → output) and exit 0.- Dispatch to the recipe driver. Write the artifact
to
outputatomically (write to a temp file, then rename). - Print a per-file
OK | FAILsummary. Exit non-zero if any recipe fails.
Custom recipe command values are split into an argv
list at config load; {param} tokens become
individual arguments passed to exec.Cmd. No shell is
involved at any stage. MDS040 (plan 100) enforces
this at lint time.
Path params (output, input) are validated by
MDS039 (plan 101) as relative paths with no ..
components before the build command runs.
The screenshot builder is skipped and its tests
are marked t.Skip when chromium is not in PATH.
All other tests run normally. CI without chromium
still passes.
- Define the
Builderinterface and recipe registry ininternal/build/. Implement the custom recipe driver that tokenizescommandat config load and dispatches viaos/exec. - Implement the
screenshotbuilder using chromedp. Supportselector,viewport,wait,click, andhideparams. Prependbuild.base-urlto path-onlyurlvalues. - Implement the
vhsbuilder viaos/exec. Skip whenvhsis not inPATH. - Add
mdsmith buildsubcommand with all flags. Wire the directive parser (plan 101) and recipe registry. Write artifacts atomically. PrintOK | FAILsummary; exit non-zero on failure. - Integration tests:
screenshotagainsthttptest.Serverwrites a non-empty PNG. Skip when chromium absent.- A
cp-based custom recipe declared inbuild.recipeswrites the output file.
- Update
docs/guides/directives/build.md(from plan 101) with a section coveringmdsmith buildflags, the--dry-runoutput format, and theOK | FAILsummary. - Update
demo.tapeto use a static HTML file (no dev server) as the screenshot source.
-
mdsmith buildagainsthttptest.Serverwrites a non-empty PNG for ascreenshotrecipe - A
cp-based custom recipe declared inbuild.recipeswrites the output file - Custom recipe
commandis executed viaos/execwith an explicit argv list — no shell interpreter is invoked -
build.base-urlis prepended to path-onlyurlvalues; a full URL is passed unchanged -
mdsmith build --dry-runlists every target (recipe → output) without running any tool and exits 0 -
mdsmith buildexits non-zero on failure with a per-fileOK | FAILsummary -
mdsmith build --recipe screenshotonly runsscreenshotdirectives; other recipes are skipped -
mdsmith build --timeout 5sapplies the timeout per recipe invocation - Artifacts are written atomically (temp file + rename); a failed recipe leaves no partial file
- CI without chromium still passes;
screenshottests are skipped, not failed -
mdsmith checkdoes not run any external tool (lint and build remain separate) - All tests pass:
go test ./... -
go tool golangci-lint runreports no issues