mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
docs: architecture class diagrams + keep-updated-in-PRs workflow (#40)
architecture/ holds mermaid class diagrams of the bdrive web server: the Server core with its seams (Source, AuthProvider, Directory, QuotaProvider, remote.Backend) and the MetaStore persistence layer. Convention (CLAUDE.md): a PR that changes the drawn structure updates the affected diagram in the same branch and embeds only the changed diagrams' mermaid blocks in the PR description. A PreToolUse hook on gh pr create (.claude/hooks/check-arch-diagrams.sh) blocks PR creation when internal/webapp or internal/remote changed but architecture/ didn't; override with '# skip-diagram-check' when nothing structural changed. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
34afef17cc
commit
2b9beaf41a
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
# PreToolUse(Bash) hook: block `gh pr create` when server code changed but
|
||||
# architecture/ didn't. Heuristic only — a pure bug fix changes no
|
||||
# structure; override by appending `# skip-diagram-check` to the command.
|
||||
input=$(cat)
|
||||
cmd=$(printf '%s' "$input" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("tool_input",{}).get("command",""))' 2>/dev/null)
|
||||
case "$cmd" in
|
||||
*"gh pr create"*) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
case "$cmd" in
|
||||
*skip-diagram-check*) exit 0 ;;
|
||||
esac
|
||||
base=$(git merge-base origin/main HEAD 2>/dev/null || git merge-base main HEAD 2>/dev/null) || exit 0
|
||||
code=$(git diff --name-only "$base" HEAD -- 'internal/webapp/*.go' 'internal/remote/*.go')
|
||||
diag=$(git diff --name-only "$base" HEAD -- architecture/)
|
||||
if [ -n "$code" ] && [ -z "$diag" ]; then
|
||||
cat >&2 <<EOF
|
||||
This branch changes server code but architecture/ is untouched:
|
||||
$code
|
||||
|
||||
Before creating the PR: if any of these change types or relationships drawn
|
||||
in architecture/, update the affected diagram, commit it, and embed the
|
||||
changed diagram(s) in the PR body. If nothing structural changed, re-run the
|
||||
same command with '# skip-diagram-check' appended.
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
exit 0
|
||||
@@ -10,5 +10,18 @@
|
||||
"Bash(go doc:*)",
|
||||
"Bash(go env:*)"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-arch-diagrams.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ The real coverage is the integration tests in `internal/syncer/syncer_test.go`:
|
||||
|
||||
`web/docs/` is the public documentation at docs.beardrive.ai — Astro 7 + [Starlight](https://starlight.astro.build) (Starlight requires Astro ^7; the cloud landing is still on Astro 5 — separate projects, so they upgrade independently), static output, Pagefind search, `llms.txt` via `starlight-llms-txt`. Unlike the hub frontend (`internal/webapp/static`) and the cloud landing page (`cloud/internal/landing/dist`) it is **not** `go:embed`ed: docs change far more often than the binary, and a search index has no business shipping in every self-hoster's install. It deploys on its own from `dist/`, so `go build` never touches it. Sidebar order is explicit in `astro.config.mjs` — a new page under `src/content/docs/` is invisible until listed there, and every page needs a `description` (meta description, search snippet, and `llms.txt` line). **The sidebar order is the recommended path, and that path is agent-first**: `Start here` (what it is → set up with your agent → your first hour) never mentions installing a binary; the CLI route lives in `Manual setup (optional)` (install the CLI, set up by hand, skills and hooks in detail) — same destination, one click away, never on the critical path. New onboarding content belongs in `Start here` and should say what to ask an agent, not what to type. `Use cases` (after `Manual setup`) holds job-shaped pages — "Share work across your team's agents", "Turn a personal brain into a company brain" — with the persona named in the first line and the `description`, not in the title; they route into the guides and never re-teach a feature. Moved URLs keep `redirects` entries in `astro.config.mjs` (static builds emit meta-refresh; real 301s live in the host config, see `web/docs/README.md`). **Guides are agent-workflow docs, not CLI tutorials** (`Working with agents`: shared agent memory, artifacts/links, read heat, scoping); command-by-command detail belongs in `reference/cli.md`. Design tokens are **generated, never copied**: `scripts/tokens.mjs` reads the `@theme` block in `internal/webapp/frontend/src/tw.css` and emits the gitignored `src/styles/tokens.gen.css`, which `src/styles/custom.css` maps onto Starlight's `--sl-color-*` — so the palette cannot drift and there is no checker to maintain (contrast the cloud landing, which keeps a copy policed by its own `check-tokens.mjs`). Because the build reads that file *outside* `web/docs`, a deploy host must check out the whole repo, not the subdirectory. Note `llms.txt` convention wants the root domain, so `beardrive.ai/llms.txt` should point at the docs subdomain — that redirect lives in the cloud landing and is the one cross-repo coordination point.
|
||||
|
||||
## Architecture diagrams in PRs
|
||||
|
||||
`architecture/` holds mermaid class diagrams of the server (`webapp-server.md`). Before `gh pr create`: if the branch changes types or relationships drawn there (new/removed types, new seams, changed fields/implements/ownership in `internal/webapp` or `internal/remote`), update the affected diagram and commit it on the branch, then include ONLY the changed diagrams' mermaid blocks in the PR description under an "Architecture changes" section (GitHub renders them). No structural change → no section, and append `# skip-diagram-check` to the `gh pr create` command to satisfy the pre-PR hook.
|
||||
|
||||
## Docs to keep in sync
|
||||
|
||||
- `README.md` and `plugin/skills/beardrive/SKILL.md` both document CLI behavior, flags, output formats, and the on-disk layout. When changing CLI commands, flags, output, or layout, update both — the skill is what makes Claude Code beardrive-aware for end users and must match the actual binary.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Architecture diagrams
|
||||
|
||||
Mermaid diagrams of the current implementation, kept next to the code so PRs
|
||||
can update them alongside the change.
|
||||
|
||||
**Convention:** when a PR changes the structure drawn here (new/removed types,
|
||||
new seams, changed relationships), update the affected diagram in the same PR
|
||||
and embed the changed diagrams' mermaid blocks in the PR description under an
|
||||
"Architecture changes" section, so reviewers see the structural delta. A
|
||||
pre-PR hook (`.claude/hooks/check-arch-diagrams.sh`) reminds Claude Code
|
||||
sessions when server code changed but no diagram did.
|
||||
|
||||
- [webapp-server.md](webapp-server.md) — class diagram of the `bdrive web` server (`internal/webapp` + its `internal/remote` seam)
|
||||
@@ -0,0 +1,283 @@
|
||||
# `bdrive web` server — class diagram
|
||||
|
||||
Source of truth: `internal/webapp` (server, services, persistence) and
|
||||
`internal/remote` (storage backends). Reflects the code as of this commit;
|
||||
update this file in any PR that changes these types or their relationships.
|
||||
|
||||
## Server core, sources, and services
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
direction LR
|
||||
|
||||
class Server {
|
||||
+Source Source
|
||||
+Volume string
|
||||
+Root remote.Backend
|
||||
+Projects *ProjectDB
|
||||
+Device Identity
|
||||
+Refresh time.Duration
|
||||
+Upload UploadConfig
|
||||
+Auth AuthProvider
|
||||
+Devices *DeviceRegistry
|
||||
+Shares *ShareDB
|
||||
+Reads *ReadLedger
|
||||
+Dir Directory
|
||||
+Quota QuotaProvider
|
||||
+ShareRPM int
|
||||
-vols per-project volume cache
|
||||
+Handler() http.Handler
|
||||
}
|
||||
|
||||
class volume {
|
||||
-source Source
|
||||
-refresh time.Duration
|
||||
-snap *snapshot
|
||||
+snapshot(ctx)
|
||||
+invalidate()
|
||||
}
|
||||
|
||||
class Source {
|
||||
<<interface>>
|
||||
+Files(ctx) map path→FileInfo
|
||||
+Open(ctx, path, fi) io.ReadCloser
|
||||
}
|
||||
class DirSource {
|
||||
+Dir string
|
||||
}
|
||||
class RemoteSource {
|
||||
+Backend remote.Backend
|
||||
+Device Identity
|
||||
}
|
||||
class Uploader {
|
||||
<<interface>>
|
||||
+Upload(ctx, path, r, size, who)
|
||||
}
|
||||
class DirectUploader {
|
||||
<<interface>>
|
||||
+SignBlobPut(ctx, blob, size, ttl)
|
||||
+HasBlob(ctx, blob)
|
||||
+Commit(ctx, path, blob, size, who)
|
||||
}
|
||||
|
||||
class Backend {
|
||||
<<interface>>
|
||||
+Put +Get +List +Exists +Close
|
||||
}
|
||||
class PutSigner {
|
||||
<<interface>>
|
||||
+SignPut(ctx, key, size, ttl)
|
||||
}
|
||||
note for Backend "internal/remote — impls: localBackend (file://), s3Backend, gcsBackend, httpBackend (https:// hub), Prefixed wrapper"
|
||||
|
||||
class AuthProvider {
|
||||
<<interface>>
|
||||
+CLILoginPath()
|
||||
+Authenticate(r) User
|
||||
+Register(mux)
|
||||
+Accounts() []User
|
||||
}
|
||||
class AccountApprover {
|
||||
<<interface>>
|
||||
+PendingUsers() +Approve +Deny +SetPolicy +Policy
|
||||
}
|
||||
class BuiltinAuth {
|
||||
+AllowSignup bool
|
||||
+AllowedDomains
|
||||
+RequireVerification bool
|
||||
+RequireApproval bool
|
||||
+Admins
|
||||
+InviteValid func(token)
|
||||
-store AccountRepo
|
||||
-users, tokens, pending
|
||||
}
|
||||
class Mailer
|
||||
class User {
|
||||
+ID +Email +Name +Admin
|
||||
}
|
||||
|
||||
class Directory {
|
||||
<<interface>>
|
||||
+Role(org, email)
|
||||
+Get +OrgsFor +ListInvites +ValidInvite +ManageURL
|
||||
+Create +Rename +AddMember +SetRole +RemoveMember
|
||||
+CreateInvite +RevokeInvite +Redeem
|
||||
}
|
||||
class LocalDirectory {
|
||||
+ManageURL(orgID)
|
||||
}
|
||||
class OrgDB {
|
||||
-repo OrgRepo
|
||||
-byID, invites
|
||||
}
|
||||
class Org {
|
||||
+ID +Name +Members email→role +Created
|
||||
}
|
||||
class OrgInvite {
|
||||
+Token +Org +Creator +Expires +Uses
|
||||
}
|
||||
|
||||
class ProjectDB {
|
||||
-repo ProjectRepo
|
||||
-byID
|
||||
+Get +Create +Rename +List
|
||||
}
|
||||
class Project {
|
||||
+ID +Name +Org +Created
|
||||
}
|
||||
|
||||
class ShareDB {
|
||||
-repo ShareRepo
|
||||
-byToken
|
||||
+Create +Get +Revoke
|
||||
}
|
||||
class Share {
|
||||
+Token +Project +Path +Creator +Expires
|
||||
}
|
||||
|
||||
class DeviceRegistry {
|
||||
-repo DeviceRepo
|
||||
-byID
|
||||
+Observe(DeviceInfo)
|
||||
}
|
||||
class DeviceInfo {
|
||||
+ID +Name +OS +User +IP +LastSeen
|
||||
}
|
||||
|
||||
class ReadLedger {
|
||||
-repo ReadRepo
|
||||
-retention
|
||||
-byKey, dirty, seen
|
||||
+Record(...)
|
||||
+Heat(project, prefix, days)
|
||||
}
|
||||
class ReadStat {
|
||||
+Project +Path +Day +Kind +Actor +Count +Last
|
||||
}
|
||||
class HeatEntry {
|
||||
+Human +Agent +Share +Readers +LastRead
|
||||
}
|
||||
|
||||
class QuotaProvider {
|
||||
<<interface>>
|
||||
+CheckWrite(org, bytes)
|
||||
+CheckSeat(org, members)
|
||||
+RecordUsage(org, bytes)
|
||||
}
|
||||
class UnlimitedQuota
|
||||
|
||||
Server o-- "0..1" Source : single-volume mode
|
||||
Server o-- "0..1" Backend : Root (hub mode)
|
||||
Server o-- ProjectDB
|
||||
Server o-- AuthProvider
|
||||
Server o-- Directory
|
||||
Server o-- DeviceRegistry
|
||||
Server o-- ShareDB
|
||||
Server o-- ReadLedger
|
||||
Server o-- QuotaProvider
|
||||
Server *-- volume : per project, cached
|
||||
volume o-- Source
|
||||
|
||||
Source <|.. DirSource
|
||||
Source <|.. RemoteSource
|
||||
Uploader <|-- DirectUploader
|
||||
DirectUploader <|.. RemoteSource
|
||||
RemoteSource o-- Backend : Prefixed(Root, projectID)
|
||||
Backend <|-- PutSigner : optional capability
|
||||
|
||||
AuthProvider <|.. BuiltinAuth
|
||||
AccountApprover <|.. BuiltinAuth
|
||||
BuiltinAuth o-- Mailer : nil → log links
|
||||
AuthProvider ..> User
|
||||
|
||||
Directory <|.. LocalDirectory
|
||||
LocalDirectory *-- OrgDB : embeds
|
||||
OrgDB ..> Org
|
||||
OrgDB ..> OrgInvite
|
||||
BuiltinAuth ..> OrgDB : InviteValid wiring
|
||||
|
||||
ProjectDB ..> Project
|
||||
ShareDB ..> Share
|
||||
DeviceRegistry ..> DeviceInfo
|
||||
ReadLedger ..> ReadStat
|
||||
ReadLedger ..> HeatEntry
|
||||
QuotaProvider <|.. UnlimitedQuota
|
||||
```
|
||||
|
||||
## Metadata persistence (`MetaStore`)
|
||||
|
||||
Service structs keep in-memory maps + logic; every change persists as one
|
||||
record through a typed repo. Blobs and journals never touch this layer.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
direction LR
|
||||
|
||||
class MetaStore {
|
||||
<<interface>>
|
||||
+Accounts() AccountRepo
|
||||
+Projects() ProjectRepo
|
||||
+Orgs() OrgRepo
|
||||
+Shares() ShareRepo
|
||||
+Devices() DeviceRepo
|
||||
+Reads() ReadRepo
|
||||
+Close()
|
||||
}
|
||||
|
||||
class AccountRepo {
|
||||
<<interface>>
|
||||
+Load() +PutAccount +DeleteAccount +PutToken +DeleteToken +PutPolicy
|
||||
}
|
||||
class ProjectRepo {
|
||||
<<interface>>
|
||||
+Load() +Put +Delete
|
||||
}
|
||||
class OrgRepo {
|
||||
<<interface>>
|
||||
+Load() +PutOrg +DeleteOrg +PutInvite +DeleteInvite
|
||||
}
|
||||
class ShareRepo {
|
||||
<<interface>>
|
||||
+Load() +Put +Delete
|
||||
}
|
||||
class DeviceRepo {
|
||||
<<interface>>
|
||||
+Load() +Put
|
||||
}
|
||||
class ReadRepo {
|
||||
<<interface>>
|
||||
+Load() +PutBatch +DeleteBatch
|
||||
}
|
||||
note for ReadRepo "batch-oriented: one flush = one write"
|
||||
|
||||
class fileMetaStore {
|
||||
JSON files, atomic rewrite per change
|
||||
}
|
||||
class sqlMetaStore {
|
||||
one database/sql impl
|
||||
sqlite (modernc) or postgres (pgx)
|
||||
}
|
||||
|
||||
MetaStore <|.. fileMetaStore
|
||||
MetaStore <|.. sqlMetaStore
|
||||
MetaStore *-- AccountRepo
|
||||
MetaStore *-- ProjectRepo
|
||||
MetaStore *-- OrgRepo
|
||||
MetaStore *-- ShareRepo
|
||||
MetaStore *-- DeviceRepo
|
||||
MetaStore *-- ReadRepo
|
||||
|
||||
class BuiltinAuth
|
||||
class ProjectDB
|
||||
class OrgDB
|
||||
class ShareDB
|
||||
class DeviceRegistry
|
||||
class ReadLedger
|
||||
|
||||
BuiltinAuth o-- AccountRepo
|
||||
ProjectDB o-- ProjectRepo
|
||||
OrgDB o-- OrgRepo
|
||||
ShareDB o-- ShareRepo
|
||||
DeviceRegistry o-- DeviceRepo
|
||||
ReadLedger o-- ReadRepo
|
||||
```
|
||||
Reference in New Issue
Block a user