Specter watches paths and fires commands when the tree settles — not when "something happened," but when nothing has happened for long enough that the tree is observably stable. Commands run against a snapshot that includes every change up to the quiescent point.
Conventional file-watch tools fire on every kernel event. The result is a flurry of redundant runs against partially-written files. Specter inverts that contract:
- Coarse file-tree settling — no double-fires on
git checkout, multi-file editor saves, or build outputs writing dozens of artifacts. - Hierarchical subtree hashing — each directory caches a fingerprint of everything beneath it, so unchanged subtrees are pruned in a single comparison. Reaction cost scales with the size of the change, not the size of the watched tree.
- Self-event absorption — the reaction itself usually writes inside the watched tree; Specter folds those events into the post-fire rebase rather than treating them as a fresh burst.
- Hot config reload — SIGHUP, an operator IPC verb, or edit-and-save against a watched config path.
- Watching non-existent paths — watched paths (and discovery pattern anchors) do not need to exist at startup; Specter dynamically probes and descends parent directories to install watches as components materialize.
- Operator control surface — nine client verbs over a UNIX socket
(
status,list,show,disable,enable,absorb,reload,tail,wait) for live inspection and runtime overrides.
Under the hood, a pure engine drives a kqueue/inotify sensor (BSD, macOS, Linux) and a subprocess actuator over bounded channels.
make install-all # binary + config + host-OS service template
$EDITOR /usr/local/etc/specter.toml
# launchd / systemd / FreeBSD rc.d will start the daemon
specter status # confirm it's upSee docs/install.md for variables, scopes, and service-template details.
[[watch]]
name = "rebuild"
path = "/srv/repo/src"
actions = [{ exec = ["cargo", "build"] }]Each [[watch]] block declares one reaction: a name, an absolute path,
and an actions array. Optional knobs cover settle window, scope
(subtree vs per-file), glob filters, event mask, recursion, and child
stdio routing. The actions array supports sequences, pipes (a | b | c
with pipefail-on), and conditionals (when / then / else). Argv
slots accept ${specter.*} and ${env.*} placeholders; the child
process receives a matching SPECTER_* environment set.
See:
- docs/configuration.md — TOML reference, actions, pipes, conditionals, scope, events.
- docs/placeholders.md — placeholder catalog, environment substitution, child-process env vars.
specter run --config <file> # the daemon (typically run by the supervisor)
specter status # daemon snapshot
specter list # every watch + state
specter show <name> # one watch in detail
specter disable <name> # runtime override (survives reload)
specter enable <name> # clear the runtime override
specter absorb <name> [--for …] # fold the next change(s) instead of firing
specter reload # equivalent to SIGHUP
specter tail [--filter <tag> …] # stream diagnostics
specter wait <name> [--timeout …] # block until the watch fires (or detaches)
specter --help prints the full surface. Clients reach the daemon by
resolving the same per-platform socket convention it binds, so no flag
is needed in the common case; --socket <path> or $SPECTER_SOCK
overrides it.
See docs/control.md for the IPC reference — socket resolution, wire format, error codes, subscribe semantics, exit codes.
Child stdout/stderr go to /dev/null by default — Specter doesn't
parse, format, or annotate user command output. Set log_output = true
per watch and the actuator inherits Specter's own stdio fds, so the
supervisor's log facility captures the bytes. For richer routing
(notifications, conditional teeing), wrap the action with sh -c.
crates/
specter-core # types, snapshot, diff, traits — pure
specter-engine # Engine::step — pure, depends only on core
specter-sensor # kqueue/inotify watcher + worker prober pool
specter-actuator # subprocess pool, coalescing, env vars
specter-config # TOML + CLI parse / validate / diff
specter-bin # wiring, signals, hot reload, IPC surface
etc/ # systemd / launchd / FreeBSD rc.d templates
docs/ # operator-facing reference
Makefile # build + install conventions
Dual-licensed under MIT or Apache-2.0, at your option.