Discovery
Discovery is the “find” half, and it’s the piece I’m happiest with architecturally.
A provider is about as small as an interface gets. It answers one question, given a directory: which files here are importable? It also has to be a cheap check that cannot fail.
type DiscoveryProvider interface {
Name() string
Imports(dir string) []string
}
It returns file paths, not executables. Turning a file into executables is Flow’s job (or, for
the formats Flow doesn’t know about, a parser on Mochi’s side). Nine providers ship today:
Makefile, package.json, docker-compose, loose shell scripts, Justfile, Taskfile, GitHub
Actions, Dockerfile, and Cargo. The first four are parsed by Flow itself; the rest are Mochi’s.
Registration order matters. The first provider to claim a file wins, which is how conflicts get
resolved without a merge policy.
Nothing is written to disk
The important design decision: discovered executables are generated at read time, not imported into your repo.
Mochi decorates Flow’s executable cache. Discovery persists only a selection of importable files
per workspace, and the decorator regenerates their executables on every read, so they appear
identically in the desktop, in mochi run, and in flow browse, while your repo stays exactly
as it was. Generation happens against a virtual flow file that only ever exists in memory; its
directory is the only thing that matters, because that’s what imports resolve relative to.
Failures degrade to nothing rather than propagating, so a bad provider can never break the cache it’s wrapping. Repeat scans are cheap: state stores file modification times as a fingerprint and short-circuits when nothing has changed.
Attribution has a constraint worth mentioning, because it shaped the design. Flow doesn’t inherit flow-file annotations into generated executables, and a slashed namespace would break its reference parser. Discovered executables get a single namespace plus a per-executable annotation naming the provider that found them.