dog/abc/FSW.h already ships the cross-platform watcher — inotify on Linux, kqueue on macOS/BSD, #error elsewhere — and FSWInit hands back exactly the pollable fd that pol.watch(fd, pol.IN, fn) wants. No jab leaf calls it: dog/abc/INDEX.md lists FSW but the whole tree has zero consumers, so JS has no way to make a watcher and the kqueue half is unexercised. This ticket adds the fsw binding in jab/, shaped like pol.cpp (stateless C leaves, all per-watch state in a JS bundle).
The pager is the first plausible consumer (live re-render when a watched file changes), but the binding is missing, not the pager wiring.
jab/dog/abc/FSW.{h,c,md} — FSWInit/FSWDir/FSWPoll/FSWDrain/FSWClose; FSWUndir was deleted as a no-op lie under ABC-013.RLIMIT_NOFILE is the macOS ceiling.FSWDrain reports an empty path there, a wake-up signal the caller answers with a dir rescan.pol's existing poll(2) loop drives it with no repeated polling and no idle wakeups.A fsw object in JS that opens a watcher, arms directories, and delivers changed names through the existing pol loop.
fsw.init() -> wfd, fsw.dir(wfd, path) -> wd, fsw.drain(wfd, buf) -> n, fsw.close(wfd).fsw.watch(dir, fn) that owns the wd -> dir map and registers the pol handler.jab/API.md + jab/INDEX.md entries; a repro test that arms a temp dir and sees a created file's name.JABC rule #4 — the native layer is leaf-only, holds no memory and no long-lived JS refs; only fds and buffers cross the boundary.
fsw.drain fills a caller-owned Buf with packed records and JS parses them.call() so BASS scratch rewinds (JAB-008).dog/abc/FSW.c — the watcher's own lifetime bugs belong to ABC-013.The binding is a thin leaf over the existing C API; framing and bookkeeping live in JS.
fsw.drain passes an internal collector FSWcb that packs wd + name length + name bytes into the Buf, returning the record count.wd says which dir it fell in, so the JS wd -> dir map is what composes a usable path.wd's dir rather than pretending it knows the file.FSWPoll gets no binding — pol.run owns the blocking, and a second blocking primitive would fight it.fsw.close wraps FSWClose; on kqueue the pinned dir fds survive it, so the JS sugar tracks them and the doc says so.jab/fsw.cpp — the four leaves plus JABCFswInstall, wired into the API tablefsw.watch(dir, fn), the wd -> dir map, pol.watch(wfd, pol.IN, ...) registrationjab/API.md and jab/INDEX.md entriesmax_user_watches (8192 on stock kernels) is the arm-a-big-tree wall; a recursive-watch helper is not in this ticket's scope.max_queued_events, 16384) and drop events — the JS consumer must tolerate a missed name.