mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
main
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
20f59352ca |
fix(webapp): badge the credential the share gate already found (BEA-147) (#183)
* fix(webapp): badge the credential the share gate already found (BEA-147) The hub could identify an AWS access key on line 3 well enough to refuse to publish the file, and rendered that same key to every member as ordinary body text. scanSecrets had exactly one caller — share minting — so the strongest protection in the product sat on the rarest path and was absent from the path every file takes. The render response now carries the same finding, omitted when the file is clean, and the markdown file view shows an advisory strip above the content. Advisory only: nothing is blocked and nothing is redacted, because a member who can open the file could already read the key. The label vocabulary moves out of Browser.tsx into lib/secrets.ts, shared by the badge and the share dialog, so the two surfaces cannot drift apart on the wording of the same finding. The ?sha= history render is scanned too — two lines, and it stops the badge vanishing the moment you click into history on the file it was warning about. Rule ids and line numbers only. The matched text reaches no response body and no log line, pinned by a test on the new caller the way shares_test.go pins the old one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(architecture): the credential scan gains a render-path caller (BEA-147) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a3dfa73fef |
Catch a credential when it syncs, not only when you share it (#162)
* refactor(secrets): lift the share-time credential rules into internal/secrets The rules only ever ran on the rarest path a file takes. Moving them out of internal/webapp is what lets internal/syncer run the same six rules on the path every file takes, without inverting the dependency. Pure move plus one addition: Label(), the six human strings that until now lived only in the frontend's SECRET_LABELS — so 'bdrive share' stops printing a bare rule id where the web dialog says 'an AWS access key'. Rule ids and the rule/line JSON tags are unchanged: Browser.tsx keys off them, so they are a wire contract. * feat(sync): warn when a synced file looks like it holds a credential The six share-time rules now run on the path every file takes. A file with an AWS key in it used to ride a normal sync to the hub, to every teammate's disk and into every future agent's context with no badge and no warning — while the Share dialog one click later blocked that exact file. Warn, never block: the op is journaled and pushed exactly as before. A hold arm would mean a false positive silently parks someone's changes, and it would break the cycle's degrade-to-offline posture. - scan() reads the blob PutBlobFile just wrote (the bytes that were actually journaled), only on the branches that wrote one — an unchanged file is still never re-read. - Findings persist per path in secrets-<mount>.json, merged rather than replaced: nearly every cycle scans zero files, and a whole-set rewrite would erase the warning seconds after it appeared. Fixing the file clears it. - bdrive status grows a secrets block; the agent hook appends one advisory sentence. Rule ids and line numbers only, never the matched bytes. - SaveSecrets failing logs and continues: advisory telemetry never gets a veto over convergence. * docs: the credential check now runs on sync, not only on share README, the CLI reference and project-files get the new bdrive status block and the warn-never-block posture, with the three limits stated (checked when it changes, first 1 MiB, writing device only). Diagrams: internal/secrets is a package of its own in the overview, secretLog joins the sync engine, and the share-gate class notes that it no longer owns the rules. * test(sync): assert an unchanged file is never re-read for credentials The check must ride the branch that already reads the file. Clearing the record by hand and cycling proves it: a scan that re-read unchanged files would put the finding back, and the daemon's 3-second tick would pay for it on every file. |
||
|
|
4031495c81 |
feat(cli,docs): say that agent skills sync, and refuse ~/.claude as a mount root (BEA-117) (#138)
`.claude/skills/**` has always synced — deliberately, per the reservation rule's own comment — but the only sentence saying so sits under the heading "What beardrive does not sync". Nobody knows. Track B, the one real bug: `bdrive init ~/.claude` was accepted. The reserved-path rule matches ".claude/settings.json" on its directory segment, so at that mount root the file is bare "settings.json" — reserved by nothing — along with .credentials.json and every saved session under projects/. New exported config.AgentConfigDir folds the keys of agentHookConfigs the way ReservedDir folds (case, trailing dots), and init refuses before any network call or file write. Only that direction leaks: a mount CONTAINING ~/.claude still sees .claude/settings.json, reserved at any depth. Track A, the content job: a README Features bullet stating the positive claim, a 7th use-case page (plus its astro.config.mjs sidebar entry, without which it is invisible), and a `skills` template appended last to the registry so `docs` keeps the RECOMMENDED badge. The embed directive becomes `//go:embed all:files` — a plain pattern drops dot-prefixed paths silently, so the template whose whole payload is .claude/skills/<name>/SKILL.md would have shipped empty. templates_test.go's every-directory-holds-a-file rule now marks ancestors, not just the direct parent: skills is the first template more than one level deep, and the rule was stricter than its own stated reason (an intermediate directory on the way to a file is not empty). Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1c703c95f8 |
feat(templates): start a project from a structure, not an empty folder (#97)
* feat(templates): start a project from a structure, not an empty folder A new project was an empty folder with a .bdriveignore in it, so every agent session invented its own layout and the folder rotted into a pile. Both surfaces now offer the same three starting points — from a template, from scratch, from an existing folder (which is just a non-empty folder, and is never restructured). internal/templates holds the shipped set as literal go:embed'ed files: `docs` (docs/, decisions/) and `para` (projects/, areas/, resources/, archives/). cmd/bdrive is one binary for the CLI and the hub, so both read the identical set — no gallery, no drift. The AGENTS.md in each is the deliverable: where a new note goes, when something is archived, what a good filename looks like. Every directory holds a real file, because BearDrive syncs paths and an empty directory would never reach a teammate. The hub seeds at creation through the existing Upload+Commit path, journaled under its own device, and records the choice on the project record — so a user who picked PARA in a browser sees PARA in the browser, and a later init cannot seed a second copy. `bdrive init --template <name>` goes through the same endpoint, with a local-seed fallback for a hub too old to know the field, and seeds in place when re-run in an already-initialized folder (the agent's post-init path). Seeding never overwrites an existing path, which is what makes a double-seed a no-op rather than a divergence. Refusals cost nothing: an unknown name and --template with --only are both rejected before any network call or write. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(cli): joining a project that already has a template is refused by name The one acceptance case with no test behind it: connecting to an existing project with --template must say what the project was actually created from, and must not write the other skeleton on the way out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(templates): name the docs template in plain English, not an acronym "Plain docs + ADRs" was the recommended, first, preselected-adjacent option in a picker that non-engineers see — and it's the label people accept without reading further, so half of it not parsing is the worst place for jargon. The title also disagreed with its own blurb: "ADRs" over "docs/, decisions/", two words for the same folder one line apart. Now "Docs + decision records", which says the same thing to everyone and matches the folder names. The term itself moves into decisions/0001-record-decisions.md, where the reader is already inside the structure and the file can teach it in passing. One line in the registry drives both the web dialog and the CLI menu; the rest is prose echoing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(templates): add the LLM wiki template The third starting point from the issue title, unblocked: the spec parked it because shipping an approximation under someone's name needed a source, and there is now one — Karpathy's LLM Wiki gist. Worth noting the issue's own one-line description of it ("few large, append-heavy topic pages") does not match the source, which is the opposite: many interlinked pages, where a single ingest touches 10-15 of them. The pattern is three layers and three operations, not a folder shape. sources/ is yours and immutable; wiki/ is the agent's and it owns every page; AGENTS.md is the schema layer — which is exactly the file this template system already treats as the deliverable, so the fit is direct. index.md and log.md ship as the two navigation files the pattern turns on. Three of the things the gist tells you to go set up, BearDrive already is: version history and collaboration (per-file history, bdrive log), an Obsidian- style reader for [[wikilinks]] (the hub viewer), and a surface for the lint pass (the dashboard is literally reads x staleness). Two rules in the AGENTS.md are load-bearing and deliberate. A page write that has not updated the index is an incomplete write — a stale index is worse than a missing page, because it is read first and believed. And with no sources yet, build nothing: the structure grows out of the material rather than ahead of it. Shipped second, not first: docs stays the recommendation because a default is the option chosen by people not reading closely, and this pattern degrades badly when half-followed. Promoting it later is one line in the registry. The shipped-template test now checks the "what happens when something stops being true" question through a set of alternatives — PARA archives, a wiki supersedes and revises — since the vocabulary honestly differs by structure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(web): "I already have a folder" as a starting point The third way to start from the spec — adopt what you already have — had no presence in the browser. Templates and "empty" were the only visible answers, so someone with a folder of notes either hesitated or picked a template and got four directories merged into their material. The constraint that shapes it: the browser cannot reach your disk, so this cannot change what is created. It creates the same empty project "Empty project" does; what it changes is the next screen. Create therefore stays enabled — disabling it would leave the dialog a dead end AND produce no project id, which is the one thing the paste prompt actually needs. Landing on the project home with the intent, three things differ: the guide says "in the folder you already have", a note states plainly that connecting never moves, renames or overwrites anything, and the paste prompt tells the agent a folder already exists. That last one is the part that isn't cosmetic — without it an agent reads an empty project and proposes creating shared/, the one recommendation that is wrong here. It still asks which folder: that is the runbook's hard gate and nothing here weakens it. The intent rides in the URL (?connect=existing) rather than onto the project record, the same way ?v= pins a file version. It belongs to whoever is connecting right now — a teammate who connects next week has their own answer and would be told the wrong thing by a persisted flag. Five rows made the dialog tall enough to push Create off a short viewport, so .modal scrolls internally. A hairline divider between the seeding and non-seeding rows was tried and removed: --border is 7% white, which at 1px in a gap renders as literally nothing. The gap is the cue that reads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(web): with no projects, open the create dialog and give the page a way in A signed-in account with no projects landed on a page whose only path forward was pasting a prompt into a coding agent. Now the create dialog opens itself — with nothing to browse there is nothing else on that page to do — and the page behind it leads with "Start a project" and a button, so closing the dialog is not a dead end. The dialog moves up to HubApp because three things ask for it now: the sidebar's +, the empty state's button, and the auto-open. ProjectNav keeps only an onNew callback; one owner beats three copies of the create handler. Two guards on the auto-open. It fires once per mount, keyed off a ref rather than the empty state, or closing it would immediately reopen it. And it never fires on a read-only hub, which refuses creation server-side with a 403 — opening a dialog that cannot succeed is worse than the page it covers. The agent paste-prompt stays, demoted to "Or let your agent do it": it is still the right path for someone who wants the folder connected in the same breath, and it is the only path on a hub where this account cannot create. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
118abc67e2 | feat(cli): "bdrive serve" replaces "bdrive web" (web stays as an alias), and the README leads with the agent install (#94) | ||
|
|
31f705e287 |
chore: drop the Claude plugin and the bundled skill — hooks are the integration (#85)
Two front doors for the same setup, and one of them was a second copy of everything. The plugin shipped the skill the CLI already installs (to four platforms, not one), hooks that ran the identical commands `bdrive hooks install` writes machine-level, and an install flow duplicating INSTALL_FOR_AGENTS.md. Nothing deduped, so a machine with both pulled twice per turn — two blocking syncs — and showed two identical `beardrive` skills in the picker. What remains is `internal/agenthooks` plus the runbook: init registers a blocking pull (which also injects the gated-link convention as additionalContext), an async push on Write/Edit, and read-log for the heatmap, in each platform's user config, once per machine. That is the whole integration, and it is the part that was never optional. Removed: plugin/, .claude-plugin/marketplace.json, internal/agentskills, `bdrive skill`, `bdrive hook-approve` (its PreToolUse auto-approve only ever helped when a plugin pre-installed it; the substitute is a `Bash(bdrive:*)` permission entry, which is user-owned config and needs no code). The e2e now asserts the absence: no SKILL.md in any platform's skills dir after init, and no `skill` subcommand. login_test keeps the "no revoke surface" wording check on logoutNote alone. Claude-Session: https://claude.ai/code/session_016aYntCWwdUhpzUfEk3ddyJ Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6628718f1d |
fix(cli): agent hooks never sync folders this device didn't opt into (#44)
* fix(cli): agent hooks never sync folders this device didn't opt into The turn hooks decided "this folder is managed" from the mere presence of .bdrive/config.json — a file designed to travel with the folder. Two holes: - A config.json arriving via git clone / copied dir made one hook firing silently mint a device identity, register the mount, create a volume store, journal the whole folder, and inject the hub-link formula — on a device that never ran init or login. - `bdrive stop` only killed the daemon: the next agent turn's `bdrive sync --hook` resumed a full sync cycle and kept injecting links, and `stop --forget` was undone within one turn by registry self-heal. Fix: one gate (`syncBlocked`) in the paths all hooks route through — sync/sync --hook/read-log now require the mount to already be enrolled in this device's mounts.json (read without ResolveMount's enrolling self-heal) and not paused. Hook mode exits silently; plain `bdrive sync` errors with a `bdrive init` pointer. New per-device paused marker in the volume dir: set by `bdrive stop`, cleared by `bdrive init` (startSync). Only init enrolls or resumes; folder moves still self-heal since enrollment is keyed by mount id, not path. Docs updated (README, SKILL.md, docs cli reference, CHANGELOG). Tests: hook/read-log no-op + no-enrollment on unenrolled and paused mounts, plain-sync refusals, stop→pause→forget regression, paused marker contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: architecture-diagram PRs must show before/after excerpts of changed classes The "Architecture changes" PR section now names exactly what changed and shows Before and After mermaid excerpts scoped to the affected classes and their immediate relationships — never the full diagram (Before = merge base). Convention updated in CLAUDE.md, architecture/README.md, and the pre-PR hook's reminder text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: add cli-sync architecture diagram; widen diagram convention to the CLI architecture/cli-sync.md draws the CLI and sync engine (cmd/bdrive + internal/{syncer,store,journal,config,daemon,agenthooks}): the Session cycle over Store/journal/remote, and the command layer with the new syncBlocked opt-in gate, paused marker, and enrollment ownership. The pre-PR hook and CLAUDE.md now watch these packages too, so CLI-side structural changes trigger the before/after-excerpt convention the same way server changes do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: full-coverage architecture diagrams — overview, frontend, agentskills Every application package is now drawn somewhere: overview.md (system diagram — package map, device↔hub↔storage flow, agent surfaces, and the private cloud/ repo as an external seam consumer), webapp-frontend.md (the hub SPA's modules: App/HubApp/VolumeApp/Browser, the in-repo nav/router, api layer, hooks, components), and agentskills added to cli-sync.md. The pre-PR hook now watches all of cmd/, internal Go code, and frontend/src (generated static/ excluded); CLAUDE.md and architecture/README.md state the coverage rule: every code change lands in exactly one detail diagram's scope, web/docs and cloud/ deliberately excluded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: PR bodies start with a TL;DR — max 5 informal one-liners Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |