From 9088127176dcd6f1e00ddc12eeb909a290c7a9ee Mon Sep 17 00:00:00 2001 From: "Snow W. Lee (Sungwon)" Date: Tue, 28 Jul 2026 07:22:11 +0900 Subject: [PATCH] feat(sync): bdrive forget + sync --prune to take ignored paths off the hub (BEA-20) (#68) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding a path to .bdriveignore only stopped future uploads: anything that synced before the rule existed stayed on the hub forever, with no command that removed it without deleting it from local disk on every device. Two engine changes make an explicit removal safe: - materialize's delete loop now consults the filter. A cached path absent from the replayed target that the rules exclude is dropped from tracking instead of unlinked — without this, any delete op for a now-filtered path wipes every peer's local copy, which is the data loss this issue is about. - the filter is reloaded mid-cycle from the pulled .bdriveignore, before materialize. A peer receiving the new rules and the deletes they justify in one batch would otherwise materialize with stale rules and the guard would never fire. materialize's write side is split into materializeFile so the ignore file can land on its own. On top of that, Session.Prune journals a delete for every path the replayed state still holds that the SHARED rules exclude — reconciling against the replay, not the local cache, because a path filtered out in an earlier cycle was dropped from the cache back then and is invisible locally today. The rules are deliberately ignore-only: the include scope lives in each device's own .bdrive/config.json and does not sync, so pruning against it would let a narrow-scope device delete a whole-folder teammate's files. Plain `bdrive sync` and the daemon are unchanged — pruning is never a side effect of editing .bdriveignore. Co-authored-by: Claude Opus 5 (1M context) --- README.md | 9 +- architecture/cli-sync.md | 8 +- cmd/bdrive/cmds.go | 16 +- cmd/bdrive/forget.go | 144 ++++++++++++ cmd/bdrive/forget_test.go | 79 +++++++ cmd/bdrive/helpers.go | 3 + cmd/bdrive/main.go | 1 + cmd/bdrive/scope.go | 3 +- internal/syncer/prune_test.go | 257 +++++++++++++++++++++ internal/syncer/syncer.go | 186 ++++++++++++--- plugin/skills/beardrive/SKILL.md | 6 +- web/docs/src/content/docs/reference/cli.md | 39 +++- 12 files changed, 712 insertions(+), 39 deletions(-) create mode 100644 cmd/bdrive/forget.go create mode 100644 cmd/bdrive/forget_test.go create mode 100644 internal/syncer/prune_test.go diff --git a/README.md b/README.md index cdceb57..2e634bc 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,10 @@ hub's own storage, never something a syncing client points at directly: | `bdrive init [folder]` | Create/connect a project and start syncing — interactive on a TTY, flags (`--name/--project/--shared/--yes`) for scripts; re-run to resume | | `bdrive stop [folder]` | Stop syncing, including agent sync hooks (files stay; `bdrive init` resumes) | | `bdrive scope [add\|rm ]` | Show or change which subfolders sync (the include list set by `init --shared`) — no JSON editing; the daemon picks changes up in seconds. `rm` deletes nothing, locally or on the hub | +| `bdrive forget ...` | 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/folder (sign-in + membership required; `--sync` pushes first; no arg = project home). Computed locally | | `bdrive share ` | Public URL for a synced file (`--list`, `--revoke`, `--expires`) | -| `bdrive sync [folder]` | Run one sync cycle now. `--note ` stamps session context (e.g. an agent session id) onto changes — shown in `bdrive log` and hub history; keeps applying to daemon-committed changes until `--note-ttl` (default 30m) expires. `--hook