post_sync: run a local command when teammates' changes land (#163)

Inbound sync was invisible to the machine it landed on — a local index,
cache or notifier had to poll. A `post_sync` command in the folder's own
.bdrive/config.json now runs once per cycle that applied peer changes,
with the batch as JSON on stdin.

The batch rides out on a new Result.Inbound rather than the inbound
spool: DrainInbound is destructive and `bdrive sync --hook` is its only
consumer, so a second drainer would silently empty the agent's
"teammates changed X" context. Both are kept, and both comments now say
why.

Cycle becomes a thin wrapper over cycleLocked so the hook is spawned
after the volume flock drops — a property of the code shape, not a rule
each of the seven call sites has to remember.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow Lee (Sungwon)
2026-08-13 11:04:23 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 0dd474baab
commit edfe46c0aa
11 changed files with 601 additions and 6 deletions
@@ -30,6 +30,42 @@ It is **never synced** and holds **no credentials**; the session token stays in
Because everything is keyed by the mount id, the folder can be renamed or moved
freely. Copy it to another machine and `bdrive init` resumes the same project.
### `post_sync`
An optional shell command run **on this device** after a sync applies changes
from the hub — the event a local search index, cache or notifier can hang off
instead of polling.
```jsonc
// .bdrive/config.json
{ "id": "m-5a10b713", "volume": "notes",
"remote": "https://drive.example.com/p/7f3a2c91-…",
"post_sync": "qmd update && qmd embed" }
```
The applied batch arrives as JSON on stdin, with the command's working
directory set to the folder:
```json
{ "project": "m-5a10b713", "folder": "/Users/you/notes",
"changed": [ { "path": "wiki/onboarding.md", "op": "write" },
{ "path": "notes/retired.md", "op": "delete" } ] }
```
- **Once per cycle** that applied at least one path — an initial sync of 400
files is one invocation, not 400.
- **Inbound only.** A cycle that only commits and pushes your own edits fires
nothing. (`bdrive restore` does count: it writes an older version back into
the folder.)
- **Never blocks sync.** The command is spawned detached; one that hangs, exits
non-zero or does not exist is logged to `daemon.log` and forgotten.
- **Off unless set.** No `post_sync` key, no new behavior.
This is local configuration and nothing else can set it: `.bdrive/` never syncs,
so no hub response and no teammate's change can put a command on your machine.
The one path in is your own hands — `.bdrive/` travels with a folder, so a
folder you copy from a teammate brings their `post_sync` along with it.
## `.bdriveignore`
A gitignore-style opt-out list at the mount root. It syncs like a normal file,