feat(cli): bdrive scope --explain — prove what leaves this machine (BEA-24) (#70)

The local-first claim was asserted, never demonstrated: nothing anywhere
told you what your laptop chose *not* to send. `bdrive scope --explain`
walks the folder and prints two sorted lists — synced and not synced —
with counts and a pointer at what it does not answer.

The decisions come from the same walk the sync cycle uses. scan()'s
WalkDir decision tree moves into walkFolder (internal/syncer/walk.go),
the only copy of the rules; scan and Explain both go through it, so the
output provably cannot drift from real sync behavior.

Pure read: its own Filter, no Session, no volume flock, no network.
Fully-excluded directories collapse to one counted line; nested mounts
are annotated as syncing through their own project rather than called
"not synced", which would be a lie in a trust surface.

Known gap, deliberate: this answers "what leaves from now on", not
"what is already on the hub" — the footer points at `bdrive forget`.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow W. Lee (Sungwon)
2026-07-29 17:10:51 +09:00
committed by GitHub
co-authored by Claude Opus 5
parent ecc8328512
commit dcd0517e92
10 changed files with 518 additions and 24 deletions
@@ -94,6 +94,56 @@ the hub keeps everything already synced (the same
block means the whole folder syncs; if you want to stop syncing entirely, that's
`bdrive stop`.
## Check what actually leaves your machine
Rules are one thing; their effect is another. `bdrive scope --explain` walks the
folder and prints every path it found, split into what syncs and what does not:
```sh
bdrive scope --explain
```
```
synced (4)
.bdriveignore
docs/architecture.md
docs/onboarding.md
specs/BEA-24.md
not synced (2,486)
.DS_Store
.bdrive/ (1 file)
.env
.git/ (312 files)
node_modules/ (2,481 files)
scratch/notes.md
vendor/acme/ (own project — syncs separately)
4 files sync, 2,486 do not.
```
A directory that is excluded whole collapses to one counted line, so a folder
with `node_modules` in it prints a handful of lines, not thousands. A nested
mount is labelled rather than called "not synced" — it *does* sync, through its
own project.
The decisions come from the same walk the sync cycle itself uses, so what this
prints cannot drift from what actually leaves. It is a pure read: safe to run
while the daemon is running and while you are offline, it takes no lock and
makes no network call. Output is sorted and stable, which makes it diffable —
the way to prove a rule change did what you meant:
```sh
bdrive scope --explain > before.txt
# edit .bdriveignore
bdrive scope --explain > after.txt
diff before.txt after.txt
```
One thing it does **not** answer: whether a path you exclude *today* is already
on the hub from before the rule existed. Excluding it stops future syncs but
leaves the copy up there — `bdrive forget <path>` is what takes it off.
:::tip
A scoped mount is also where the two-file
[`AGENTS.md` pattern](/guides/shared-agent-memory/) earns its keep — the synced
@@ -14,6 +14,7 @@ One binary, `bdrive` — the CLI, the sync daemon, and the web server.
| `bdrive init [folder]` | Create or connect a project and start syncing — the mount is always exactly the folder named. Interactive on a TTY; flags (`--name`, `--project`, `--server`, `--only`, `--yes`) for scripts. Also installs the agent skill, registers agent sync hooks for detected platforms (`--no-hooks` skips the hooks only), and prints the project's hub link. Re-run to resume |
| `bdrive stop [folder]` | Stop syncing — daemon and agent sync hooks both pause. Files stay on disk; `bdrive init` resumes |
| `bdrive scope [add\|rm <dirs...>]` | Show or change which subfolders sync — edits the managed block of `.bdriveignore` rules that `init --only` writes. Run from the mount root; the daemon picks changes up in seconds. `rm` stops syncing a folder but deletes nothing, locally or on the hub |
| `bdrive scope --explain` | List every path in the folder, split into what syncs and what does not, with counts — the verifiable answer to "what leaves this machine". Pure read: no daemon, no lock, no network |
| `bdrive forget <path>...` | Stop syncing a path and remove it from the hub. Adds the rule to `.bdriveignore` (which syncs) and prunes in one step. Local files are never touched, here or on teammates' devices |
| `bdrive url [path]` | Internal hub link for a file or folder — sign-in and membership required. `--sync` pushes first; no argument gives the project home. Computed locally |
| `bdrive share <file>` | Public URL for a synced file. `--list`, `--revoke`, `--expires` (the hub's Share dialog can also set an expiry on an existing link) |