Files
beardrive/architecture/cli-sync.md
T
0236e1b272 feat(cli): multi-folder --shared at init + bdrive scope to edit the sync scope (#53)
* feat(cli): init --shared accepts multiple subfolders (repeatable or comma-separated)

--shared is now a slice flag: `--shared wiki --shared docs` or
`--shared wiki,docs` sync several subfolders into one project
(include list ["wiki/", "docs/"]). The interactive scope prompt takes a
space- or comma-separated list. Entries resolving to ".", "", or ".."
error out — silently dropping them would widen scope to the whole folder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AubcaQscjcQucXwh578vqG

* docs(plugin): skills/commands propose multiple --shared folders at init

The init/install flows now scan for all knowledge folder candidates and
offer them as one --shared list (one project, one permission set), noting
that folders needing different access belong in separate projects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AubcaQscjcQucXwh578vqG

* feat(cli): bdrive scope — add/remove shared subfolders without editing JSON

`bdrive scope` shows the include list; `scope add`/`scope rm` edit it from
the mount root. The daemon re-reads config each tick, so changes apply in
seconds. rm deletes nothing (newly filtered paths drop from the cache with
no delete op); removing the last entry is refused since an empty include
list means whole-folder sync. add onto a whole-folder project is refused
for the same narrowing hazard. Skill/README/docs updated; scope added to
the cli-sync diagram's command list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AubcaQscjcQucXwh578vqG

* docs+cli: scoping guide covers multi-folder --shared and bdrive scope; init hints on ignored --shared at resume

The scoping guide (the dedicated page for this feature) now shows
--shared wiki,docs and a "Change the scope later" section for bdrive
scope; setup-by-hand and project-files point at it. init resume with an
explicit --shared now says the flag is ignored instead of staying silent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AubcaQscjcQucXwh578vqG

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:49:58 +09:00

5.9 KiB

bdrive CLI & sync engine — class diagram

Source of truth: cmd/bdrive (commands, gates) and internal/{syncer,store, journal,config,daemon,agenthooks,agentskills}; the internal/remote seam is drawn in webapp-server.md. Reflects the code as of this commit; update this file in any PR that changes these types or their relationships.

Sync engine — one cycle

classDiagram
    direction LR

    class Session {
        +Folder string
        +MountID string
        +Store *store.Store
        +Device config.Device
        +Account config.Settings
        +Backend remote.Backend
        +Note string
        +OnProgress func
        +Cycle(ctx) Result
    }
    note for Session "internal/syncer — scan → commit local ops → pull peer journals → preserve conflicts → materialize → push blobs then own journal"

    class Result {
        +LocalOps +PulledOps
        +Conflicts +Materialized
        +Pushed +Offline +OfflineErr
        +ReadOnly +NoAccess +AccessErr
    }
    note for Result "Offline / ReadOnly / NoAccess are three different answers: unreachable (retry all), push refused (pull-only), pull refused (pause, touch nothing)"

    class Filter {
        +Skip(rel) bool
        +PruneDir(rel) bool
    }
    note for Filter "ignore.go — .bdriveignore rules + .bdrive include list, applied symmetrically in scan and materialize"

    class Store {
        -dir volume dir
        +PutBlob / OpenBlob / HasBlob
        +AppendOps / DeviceOps / AllOps
        +LoadCache / SaveCache mountID
        +LoadSync / SaveSync
        +SaveNote / LoadNote
        +PendingReads read spool
        +Lock() flock
    }
    note for Store "internal/store — ~/.bdrive/volumes/mount-id: content-addressed blobs, per-device journal copies, state cache, paused marker (free funcs Paused/SetPaused, no flock)"

    class Op {
        +Seq +Lamport +Time +Device
        +Author +User +UserName
        +Kind put or delete
        +Path +Blob +Size +Mode +Note
    }
    note for Op "internal/journal — Less orders by (lamport, time, device, seq); Replay folds to LWW-per-path state; each device writes only its own journal"

    class Backend {
        <<interface>>
        +Put +Get +List +Exists +Close
        +ErrForbidden sentinel
    }
    note for Backend "internal/remote — client devices use the https:// hub backend (token from BDRIVE_TOKEN / settings.json); a hub 403 wraps ErrForbidden, which is what Result turns into ReadOnly/NoAccess instead of Offline"

    class daemon {
        +Run(folder, scan, remote)
        +Start / Stop / Running
    }
    note for daemon "per-mount detached loop; re-reads .bdrive/config.json each tick, exits without deletes if it vanishes"

    Session --> Store : volume state
    Session --> Backend : pull and push
    Session --> Filter : scan and materialize
    Session ..> Op : commits, replays
    Session --> Result
    Store o-- Op : journal files
    daemon --> Session : one Cycle per tick

CLI commands, device state, and the opt-in gate

classDiagram
    direction LR

    class Commands {
        init login logout
        sync stop scope status log
        url share export import
        web daemon hooks read-log skill
    }
    note for Commands "cmd/bdrive — thin cobra layer; init is the front door, stop pauses"

    class syncBlocked {
        <<gate>>
        enrolled in mounts.json?
        volume not paused?
    }
    note for syncBlocked "cmd/bdrive/helpers.go — sync, sync --hook, and read-log must pass it; reads the registry WITHOUT ResolveMount's enrolling self-heal. Hook mode fails silent; plain sync errors with a bdrive init pointer"

    class openSession {
        mustProject → ResolveMount
        store.Open + remote.Open
    }
    class startSync {
        enroll + clear paused
        initial Cycle
        daemon.Start
    }
    note for startSync "cmd/bdrive/sync_run.go — init's engine; the ONLY enroller and the only thing that resumes a pause"

    class Project {
        +ID stable mount id
        +Volume +Remote +Include
    }
    note for Project ".bdrive/config.json — travels with the folder (git clone, copy); presence alone is NOT consent to sync"

    class MountRegistry {
        mounts.json
        id → Path Volume Remote
    }
    class Device {
        device.json
    }
    class Settings {
        settings.json
        server + token + account
    }
    note for MountRegistry "internal/config — per-device state under BDRIVE_HOME; ResolveMount self-heals the path for enrolled mounts (renames/moves stay free)"

    class AgentHooks {
        Detect / Install / Registered
        turn-start: sync --hook
        post-edit: sync --note
        post-read: read-log
    }
    note for AgentHooks "internal/agenthooks — registers per-platform hook commands (claude, codex, gemini, hermes); they fire in every folder, every turn"

    class PausedMarker {
        volumes/id/paused
    }
    note for PausedMarker "set by bdrive stop, cleared only by bdrive init (startSync)"

    class AgentSkills {
        Detect / Install
        embedded SKILL.md
    }
    note for AgentSkills "internal/agentskills — installs the beardrive skill user-level (per-platform skills dir) from the binary's embedded copy; idempotent, refreshed on upgrade"

    Commands --> AgentSkills : skill install
    AgentHooks --> Commands : runs sync and read-log
    Commands --> syncBlocked : sync and read-log gate first
    syncBlocked --> MountRegistry : reads only, never enrolls
    syncBlocked --> PausedMarker : Paused check
    Commands --> openSession : after the gate
    openSession --> MountRegistry : path self-heal (enrolled only)
    Commands --> startSync : init
    startSync --> MountRegistry : enrolls
    startSync --> PausedMarker : clears
    Commands --> PausedMarker : stop sets
    openSession ..> Project : loads
    openSession ..> Device : identity
    openSession ..> Settings : account and token