This project implements some of Beagle SCM's functionality for regular git repos. The work is ongoing; so far it can do:
- CRDT merges (use
bee installto add a hook to a git repo), - TUI code browser (use
bee ./myfileto open), - HTML code browser (use
bee http --port 8888to run, then openhttp://localhost:8888/myprojectin a browser), - token-level diffs (may start with
bee logorhttp://localhost:8888/myproject/logthen click around), - permalink support, version-aware links that survive edits
(e.g.
index/perma.js:6:ur, see them in the pager/browser,bee seefor CLI use, commit hook orbe mintto create)
Reproducing Beagle's smooth worktree workflows has proven difficult, as git submodule and git worktree implementations are rather abrasive.
Bundle with the quickjab runtime for a standalone binary. That carries dog/ (libdog), dog/abc/ (libabc) and test/ (the jab-shared API suite) as submodules:
cmake -S <quickjab> -B build -GNinja -DCMAKE_BUILD_TYPE=Release \
-DJAB_JSRC=<js bundle tree> -DQUICKJAB_JSRC_PACK=ON -DJAB_BIN=bee
ninja -C build quickjab
ctest --test-dir build -R '^JSRC'
That leaves the binary at build/bin/bee and runs this tree's own
test/*/run.sh suites against it, exactly as CI does
(.github/build-bee.sh). Tagging X.Y.Z releases the same build:
a source tarball plus bee for linux x86_64/arm64, macOS arm64 and a
fully static linux x86_64.
The standalone (bundled) bee binary call patterns:
bee-- in a git repo: index it, open thelistbrowser;bee <path>...-- page files/dirs off the filesystembee //<name> <verb> [args]-- the CONTEXT slot (BEE-023): the verb runs in the repo<name>, and//<name>/<rel>is a path inside it wherever an arg is a path (bee cat //bee-journal/todo/BEE/BEE-023.mkd)<name>is a basename off~/.config/bee/repos, else the directory$SRC_ROOT/<name>($HOME/srcby default) when a git repo or a linked worktree sits there- a
$SRC_ROOThit is read-only for that run -- it is never registered; usebee installfor that - the slot is POSITIONAL and first: after a verb,
//<name>is an argument like any other - bare
bee //<name>is the zero-arg story in that repo: index it, open thelistbrowser
bee index [<repo>]-- build/refresh<repo>/.git/be/; lazy, fully derived; ALL THREE passes, the commit walk, the tip-blob link scan and thekvmeta sweep, each off its own mark, all on the one summary line- every initialized submodule is indexed too, depth-first; an uninitialised one is skipped in words
- inside a linked worktree it registers the original -- one repository is one line, and it says which
- the tail
kv: N files, M pairsis the keyed lane (BEE-024): the.md/.mkdfiles this worktree carries and the live meta pairs they hold
bee lindex [<target>]-- backlink suspects; lazy, tip blobs only- bare: bring the link rows up to the tip, this repo only
<target>: the paths that may link to a file or a ticket code, looked up in every repo of~/.config/bee/repos(read-only) and printed repo-qualified, the local repo first
bee sym [--paths] <ident> [.ext|<dir>]...-- where a symbol is mentioned (BEE-063, BEE-066); one prefix scan of the SYM rows the tip-blob pass mints- every hit is a canonical hunk:
urithe address<path>:<line>, text the raw mention line with a line of context either side, the file's own tags with the mention marked; two windows merge where they overlap - the rows are suspects and store no positions, so each file is OPENED and one that no longer carries the symbol prints nothing at all
- the bytes are the worktree file when there is one, else the tip blob; a binary or over-cap blob prints its path bare
- every repo of
~/.config/bee/reposanswers (read-only), the local first - the symbol is hashed VERBATIM, so
u8bFeedandU8BFeedare two queries - past ~200 suspects a repo prints the count and asks for a narrower query
- a further word narrows:
.can extension,dog/abc/a SEGMENT-ALIGNED path run at any depth (nevercatalog/abc); alike words OR, the two kinds AND, and a path run prunes the rows before the cap is weighed --paths: one repo-qualified path per line and no file opened, for scripts
- every hit is a canonical hunk:
bee log [<n>] [<hex>|<path>][?<rev>]-- commit log, newest first; off-mainline rows grey- bare: the checked-out tip's ancestry;
<n>caps the rows,0= all <hex>: that commit's ancestry<path>: the file's revisions?<rev>: a branch, tag or hexlet names the tip --log ?main,log f.c?v2<sub>/<path>: the submodule's own history, its rows opening in the sub- a ticket code in a summary is a link, like any other reference
- bare: the checked-out tip's ancestry;
bee commit [<hex>]-- one commit, header + message; tree/parent hashes are linksbee diff [<hex> [<hex>]|<path>]-- the token-level diff: one weave per path, seeded at the two revisions' merge base, every token naming its own commit- bare: worktree vs HEAD
<hex>: that commit vs its first parent<hex> <hex>: any two revisions, rooted at their merge base<path>: that file/dir only
bee status— the quad: one row per path that is not quiet, four chars wide,upstream HEAD index worktree,.same,xremoved,ocreated,vadvanced,!conflicted; position is authoritativebee list [<path>][?<rev>]-- dir browser: marker, name, last commit, agebee cat <path>[?<rev>]-- the file's own bytes, syntax-paintedbee tree [<hex>|<path>][?<rev>]-- raw git-tree rows,..row when descendedbee blob <hexlet>-- a blob by object namebee see [-C<n>] <ref>...-- the chunk each reference names, two lines of context each way; the only CLI door onto a permalink- every shape
door.jsresolves: permalink, path, partial, ticket code, pocket page, another registered repo - one hunk per ref, in the order given; a miss names the repos searched, an ambiguity shows the chooser, neither ends the batch
- every shape
bee cite <path>[?<rev>]-- the file with every anchored reference on it already quoted, one line above the landing and two belowf.c:12and the permalinkf.c:12:aBalike, resolved through the same doorseeasks; a bare filename or a ticket code is left alone- a miss, an ambiguity or a target quoted higher up adds nothing, so the page still reads as the file
bee cited <path>-- the mirror ofcite: every reference ELSEWHERE that points at this file, quoted under the line it lands on- the backlink rows only narrow the search -- each suspect is opened, lexed once and resolved in its own repo, so a same-named file in another tree quotes nothing
- the quote is the CITING lines, banded with the carrier's path and line, so its header opens the page that cited; a bare name or a ticket code sits in a block above the file
bee todo [TOPIC] [Key:Value]...-- the ticket board:<●> KEY ┄ titleper open ticket, the row a click ontobee see KEY- bare, the board of the context repo's
todo/-- topic headers with counts,Sub:families on dotted rails; when that repo (or, with no context, the local one) has notodo/, every registered repo's, newest first, each row prefixed by its repo TOPICis one topic's list,Key:Valuea filter -- keys AND, one key repeated ORs,Key:*is presence andKey:absence;Sev:paints the bullet CRIT red, HIGH orange, LOW dim- open is
Now: OPEN, or noNow:and no closed header mark;todo/done/never lists and the answer comes off the BEE-024 lane, one query per repo per run - a ticket that has a
$SRC_ROOTworktree carries the two BEE-027 frames; a worktree whose tail is no ticket code lists in a trailingworktreesblock
- bare, the board of the context repo's
bee http [--port <n>]-- the same views over HTTP on localhost:8034, read-only, every registered repo, links to known files resolved and clickable- every URL is
/<repo>/<verb>/<arg>--/<repo>/cat/<path>the file (a.mdrendered),/<repo>/list/<dir>/the browser; a verb-less/<repo>/<path>301s to that form, and a segment that spells a verb (todo/) IS the verb (BEE-028) /<repo>/raw/<path>is the highlighted source,/<repo>/bytes/<path>[?<rev>]ships the file's raw bytes,- submodule is served through its parent (
/quickjab/cat/dog/abc/TCP.c), never under a name of its own
- every URL is
bee fork //repo-TKT-123-- a ticket worktree of a registered repo at$SRC_ROOT/repo-TKT-123, on a branch named by the tail- the split takes the LONGEST registry name followed by
-(//bee-journal-BEE-023is the journal's); the tail is free text, usually a ticket code - every submodule the HEAD records gets a worktree of its own repo at the same relative path, detached at the recorded commit, recursively
- an existing branch is checked out, not re-created; the tree is not registered, and
git worktree removeis how it goes away
- the split takes the LONGEST registry name followed by
bee wts-- the ticket worktrees under$SRC_ROOT, one line each:<name>-<tail>then the two board frames- found by NAMING CONVENTION alone -- a dir
<registry name>-<tail>holding a repo;git worktree listis never consulted and nothing is registered - the file frame
[v3 v1 v2 v5 !1]is the BEE-022 quad's column counts -- upstream, head, stage, worktree, conflicted, position authoritative - the commit frame
[o2 o1 a1b2c]is commits ahead, commits behind and the current tip - nothing is stored: the
bee todoboard (BEE-025) is what spends these rows
- found by NAMING CONVENTION alone -- a dir
bee install [<repo>]-- wire bee as the repo's git merge driver and its pre- and post-commit hooks, list it in~/.config/bee/repos, index it- a linked worktree is refused: a repo is known by its path (
bee indexredirects to the original instead) - a submodule is an ordinary repo: each initialised one is registered, indexed and lindexed too
- the post-commit hook runs
bee hook --poston the commit just made, quietly; git ignores its status, so it can never block or undo a commit
- a linked worktree is refused: a repo is known by its path (
bee hook [<repo>]-- the pre-commit pass: freshfile:line(:col)refs in the staged text becomefile:line:hashpermalinks, re-stagedbee hook --post [<repo>]is the post-commit pass:index's three passes over the commit just made, quiet, and it never writes~/.config/bee/repos--installis the one verb that registers a repo
bee mint [--dry-run] <file>...-- the same upgrade for refs that ALREADY landed transient, in the working copy only; the hook cannot reach those- the file list names the carriers: refs written in them, pointing anywhere; a target not listed must be clean, or it is refused
- everything left alone is reported, one line per ref -- unresolved, ambiguous, past the end, on a link cycle
bee merge <base> <ours> <theirs> [-o <out>] [-p <path>]-- CRDT 3-way file merge- clean: merged bytes over
<ours>(or<out>), exit 0 - conflict: markerless weave, exit 1
- binary/oversize: falls back to
git merge-file
- clean: merged bytes over
bee mark <file.mkd>-- that page rendered to ONE HTML document on stdout (.mkd,.md,.rst), the writing left to the shell- a
.mkd/.md/.rstlink takes the.htmlname rendered beside it, so a dumped tree links up; every other destination rides verbatim - the head links the site's own
/assets/css/style.css, and the body carries no bee chrome
- a
bee chat [dir] [outdir]-- Claude session logs to StrictMark pages, append-only reruns
