From 2c857474649633bd8d9a9050cbd1e9480f9ed0f4 Mon Sep 17 00:00:00 2001 From: "Snow W. Lee (Sungwon)" Date: Mon, 27 Jul 2026 17:31:53 +0900 Subject: [PATCH] feat(sync): .bdriveignore always syncs, regardless of scope or rules (#54) The ignore file is exempt from filtering in Filter.Skip: it syncs even on --shared mounts (where it sat outside the include list and was local-only) and even when one of its own patterns matches it. One guard covers both scan and materialize since they share the filter. Docs updated on all three surfaces (SKILL.md, install.md, web/docs). Claude-Session: https://claude.ai/code/session_01G3AdFdps7seYbW6FhyK58t Co-authored-by: Claude Fable 5 --- internal/syncer/ignore.go | 3 +++ internal/syncer/ignore_test.go | 12 ++++++++++++ plugin/commands/install.md | 6 +++--- plugin/skills/beardrive/SKILL.md | 2 +- web/docs/src/content/docs/guides/scoping.md | 5 +++-- .../src/content/docs/use-cases/business-context.md | 5 ++--- 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/internal/syncer/ignore.go b/internal/syncer/ignore.go index 8e9adec..d50925b 100644 --- a/internal/syncer/ignore.go +++ b/internal/syncer/ignore.go @@ -147,6 +147,9 @@ func compile(line string) (pattern, bool) { // Skip reports whether a file path should not sync. func (f *Filter) Skip(rel string) bool { + if rel == IgnoreFile { + return false // the ignore file itself always syncs so devices share rules + } if f.underNestedMount(rel) || f.ignoredFile(rel) { return true } diff --git a/internal/syncer/ignore_test.go b/internal/syncer/ignore_test.go index ade0b5a..be16770 100644 --- a/internal/syncer/ignore_test.go +++ b/internal/syncer/ignore_test.go @@ -76,6 +76,18 @@ func TestIgnoreBeatsInclude(t *testing.T) { } } +func TestIgnoreFileAlwaysSyncs(t *testing.T) { + for _, f := range []*Filter{ + filterFrom(t, ".bdriveignore\n*", nil), // self-ignored + filterFrom(t, "", []string{"docs/"}), // include list without it + filterFrom(t, ".bdriveignore", []string{"src"}), // both + } { + if f.Skip(IgnoreFile) { + t.Error(".bdriveignore must always sync") + } + } +} + func TestPruneDir(t *testing.T) { f := filterFrom(t, "node_modules/", nil) if !f.PruneDir("node_modules") || !f.PruneDir("sub/node_modules") { diff --git a/plugin/commands/install.md b/plugin/commands/install.md index b71c11b..94aa16e 100644 --- a/plugin/commands/install.md +++ b/plugin/commands/install.md @@ -62,9 +62,9 @@ subfolders later, use `bdrive scope add ` / `bdrive scope rm ` from the mount root — never hand-edit `.bdrive/config.json`. After init, tell git what's what: add `.bdrive/` to `.gitignore` (per-machine -state, never committed) and COMMIT `.bdriveignore` (on a `--shared` mount the -root `.bdriveignore` is local-only to each clone, so git is how the team -shares it). +state, never committed). `.bdriveignore` always syncs through BearDrive — +including on `--shared` mounts — so the team shares rules automatically; +committing it to git too is fine but optional. ## 4. Teach agents about the shared folder (ask first — never do this silently) diff --git a/plugin/skills/beardrive/SKILL.md b/plugin/skills/beardrive/SKILL.md index ff18776..64d908f 100644 --- a/plugin/skills/beardrive/SKILL.md +++ b/plugin/skills/beardrive/SKILL.md @@ -40,7 +40,7 @@ Use this skill whenever the user is working with the `bdrive` CLI: initializing Two files at the mount root control a folder's sync behavior: - **`.bdrive/`** — the folder's settings **directory**; `config.json` inside holds the **stable mount id** (`m-xxxxxxxx`) plus `volume`, `remote`, optional `include`. Written by `bdrive init`; safe to hand-edit (a running daemon picks changes up on its next tick). It is **never synced**, holds **no credentials** (the token lives in `~/.bdrive/settings.json`), and because all state is keyed by the mount id — not the path — the folder can be **renamed or moved freely**; the daemon exits on a move and the next bdrive command at the new location resumes. -- **`.bdriveignore`** — opt-out list, gitignore-style. On a whole-folder mount it **syncs like a normal file**, so all devices share the same rules; on a `--shared ` mount the seeded root `.bdriveignore` sits OUTSIDE the include list and is **local-only** — commit it to git (or place ignore rules inside the shared dir) if the team should share it. Syntax subset: `#` comments, `*` within a segment, `**` across segments, `?`, trailing `/` for directories-only, a `/` elsewhere anchors to the mount root, `!` re-includes. +- **`.bdriveignore`** — opt-out list, gitignore-style. It **always syncs** — even on a `--shared ` mount where it sits outside the include list, and even if a rule matches it — so all devices share the same rules. Syntax subset: `#` comments, `*` within a segment, `**` across segments, `?`, trailing `/` for directories-only, a `/` elsewhere anchors to the mount root, `!` re-includes. ```jsonc // .bdrive/config.json diff --git a/web/docs/src/content/docs/guides/scoping.md b/web/docs/src/content/docs/guides/scoping.md index 27101fe..7a72984 100644 --- a/web/docs/src/content/docs/guides/scoping.md +++ b/web/docs/src/content/docs/guides/scoping.md @@ -71,8 +71,9 @@ build/ Supported: `#` comments, `*`, `**`, `?`, a trailing `/` for directories, a leading (or any) `/` for root-anchoring, and `!` to re-include. -It syncs like a normal file, so every device shares the same rules — one person -excluding `*.tmp` fixes it for the whole team. +It always syncs — even on an include-list mount where it sits outside the +scope, and even if a pattern matches it — so every device shares the same +rules: one person excluding `*.tmp` fixes it for the whole team. `bdrive init` seeds a starter one covering `node_modules`, build directories, caches, and `.env*`. diff --git a/web/docs/src/content/docs/use-cases/business-context.md b/web/docs/src/content/docs/use-cases/business-context.md index b37e94d..a411813 100644 --- a/web/docs/src/content/docs/use-cases/business-context.md +++ b/web/docs/src/content/docs/use-cases/business-context.md @@ -123,9 +123,8 @@ and it is also the thing to be deliberate about. [Turn a personal brain into a company brain](/use-cases/company-brain/) for how that model works. - **Opt things out** with `.bdriveignore` — see - [Scoping the folder](/guides/scoping/). On a `--shared` mount the seeded root - `.bdriveignore` sits outside the include list and stays local, so put shared - rules inside `context/` instead. + [Scoping the folder](/guides/scoping/). It always syncs, even on a + `--shared` mount, so one person's rules apply for the whole team. - **Watch what actually gets read.** [Read heat](/guides/what-agents-read/) shows which context pages agents consume. Pages nothing has read in a month are candidates for deletion, and deleting them makes the rest work better.