Repository navigation
Reef 0.2.0
The first release of Phase 2: a typed API, reef.api.v1, with the CLI as a thin client of it, so reef now needs reef-daemon beside it or on PATH; commands that look at and revert what agents did without jj; an MCP server; live operation and presence streams; a symbol index; and garbage collection on a timer. Coming from 0.1, read Upgrading from 0.1 first.
Added
- The symbol index (ADR 0042):
SymbolServicewithIndexSymbols,UploadScipDocumentsandListSymbols(API revision 7). The daemon extracts tree-sitter tags for Python, TypeScript, TSX, JavaScript, Go and Rust itself, in the background for new snapshots and edits and on demand, and stores definitions and references with their ranges and enclosing definitions in a per-repository cache (repos/<id>/symbols.sqlite3), keyed by path and file content so unchanged files share entries across revisions. SCIP documents an indexer wrote are uploaded and checked against the file they describe. Four error reasons:SYMBOLS_DISABLED,SYMBOLS_NOT_INDEXED,INVALID_SYMBOL_QUERYandINVALID_UPLOAD. GC collects the index as its fifth step. reef symbols index,uploadandlist(aliassym): index a revision, upload a SCIP index, and list a file's or directory's symbols or a name's occurrences, with JSON when piped.[symbols]inconfig.toml:enabled,background,workers,max_file_sizeandmax_size.reef-symbols, the extraction crate: Reef's tag queries, SCIP's symbol grammar and SCIP normalization, with no jj-lib, gRPC or SQLite dependency.- The daemon runs garbage collection by itself every
[gc] interval(default a day;0sturns it off), over every repository, 10 minutes after it starts at the earliest and a full interval after the last full run, timed orreef ws gc(#95, ADR 0041). Its reports go to the daemon's log. - API revision 6:
GcReport.base_bytes_unshared, what removing the base cache entries freed on disk, counting blocks shared between incremental bases once (measured on APFS).reef ws gcprints it beside the allocated size. reef-broker, the subscription broker (ADR 0038): bounded in-memory rings between a log and the streams that follow it, generic over topic, position and item, with no jj-lib or gRPC dependency.- A subscription load test (
crates/reef-daemon/tests/subscription_load.rs, docs/load-tests.md): 500 operation-log and 500 presence subscribers at 1,000 operations a minute, every delivery checked, gated on p99 within 100 ms.subscription-load.ymlruns it on both runners. reef ws runreports the command it runs as a presence entry (running <command line>), refreshed every 10 seconds and cleared when the command exits, before the release.reef mcpreports, per repository or workspace its calls name, the last tool called there, for a minute after the call, and clears its entries when it exits. Both name the agent from--agentorREEF_AGENT, else the workspace's owner.reef presence reportreports what you are doing:TEXTfor an activity,--pathfor files you are editing,--phaseand--donefor progress, in the current workspace or repository; it prints the entry's id (-o text) or the entry.--idreplaces an entry, and--id-filekeeps the id in a file for callers that are separate processes, such as a harness's hooks; an entry that had ended is reported anew.reef presence clearends entries by id or--id-file.reef ws renewrenews a workspace's lease, the one you are in by default: whatws rundoes for its command, for a workspace held outsidews run.reef heresuggests it for a lease in its grace period.reef_client::presence: theReporterthatws runandreef mcpuse, the reporting contract thatReportPresence's comment now states, andPresenceApi.- Presence (ADR 0037):
ReportPresence,ClearPresenceandSubscribePresence. Entries say what an agent is doing (activity), the paths it is editing, a lease heartbeat, or a long operation's progress; each has a TTL (30 seconds by default, 1 second to 10 minutes) and lives in the daemon's memory only. A subscription receives the current set, then changes, the latest state of each entry, at least 50 ms apart. At most 4,096 client entries, 32 per agent. Six error reasons:PRESENCE_NOT_FOUND,INVALID_PRESENCE_ID,PRESENCE_MISMATCH,DAEMON_PRESENCE,PRESENCE_TOO_LARGEandTOO_MANY_PRESENCE_ENTRIES.Client::report_presence,clear_presenceandsubscribe_presence. - The daemon reports presence itself: a lease entry per workspace on every heartbeat, lapsing after two missed ones; the paths each snapshot changes; and the progress of workspace creates and store builds that run longer than 250 ms.
reef presence watchprints presence as it changes, one JSON line per event when piped, filtered by--repo,--workspace,--byand--kind;--for 0prints who is doing what now. It stops after 30 seconds unless--follow, and stays a correct stream of changes across a daemon restart.- API revision 5:
Presence.lease,Presence.by_daemon,PresenceEvent.end_reasonandPresenceEndReason. SubscribeOperationsstreams the operation log (ADR 0036): frames of operations as they are recorded, filtered likeListOperations, from now, after an operation, or from a frame's cursor, with nothing dropped. Operations Reef records arrive within 100 ms, otherjjprocesses' within about 200 ms. Four error reasons:INVALID_CURSOR,TOO_FAR_BEHIND,OPERATION_LOG_REWRITTENandTOO_MANY_SUBSCRIPTIONS.Client::subscribe_operationsreturns the stream.reef op watchprints operations as they are recorded, one JSON line each when piped, withop log's filters,--afterto start from an operation, and--count; it stops after 30 seconds (--for) unless--follow, and says on stderr how to continue.- API revision 4:
SubscribeOperationsResponse.head_operation_ids. reef mcp, an MCP server over stdio (ADR 0035): twenty tools named after the API's RPCs (get_status,list_changes,get_diff,read_file,edit_files,describe_change,undo_operation,create_workspace,wait_for_operationsand the rest), returning the JSONreef -o jsonprints, with errors as tool results carrying the daemon'shint,nextandmetadata. No ADMIN RPC is a tool. Workspaces the server creates are heartbeated while it runs.wait_for_operationswaits for operations matching a filter, readingSubscribeOperations.reef initwrites Reef's entry in.mcp.jsonwith Claude Code's files, and with--agents mcp; it owns onlymcpServers.reefand leaves the rest of the file as it was.json::Edited,edit_files' result, inreef-client.- Commands over the VCS services (ADR 0034):
reef status,log,show,diff,describe,abandon,undo,op log,op show,ws snapshot,file readandfile list, from a workspace or a checkout, or with--workspaceor--repo. Paths given are relative to the current directory; paths printed are repository-relative. Each prints JSON when piped (file readprints the file's bytes unless-o json), and errors from the API carry the CLI's ownhintand anextbuilt from the command and the error. Writes in a workspace return it afterwards and note on stderr when it is divergent, naming how to resolve it. - The JSON error object carries the daemon's
metadatawhen there is any, such asAMBIGUOUS_REVISION'scommit_ids. - An error reason,
SNAPSHOT_FAILED(metadataworkspace_id), on a snapshot that fails before a read or a write; its kind is the snapshot error's. - The read side of the API (ADR 0030):
GetStatus(the repository's trunk, active workspaces, mutable heads, conflicted and divergent changes, and a workspace's changed and conflicted files),ListChanges(the log, with graph edges, over jj's revset language),GetChange,GetDiff(summary, line counts, or unified hunks, with renames and copies),ListOperationsandGetOperation. Revisions are jj revsets with Reef'strunk(),immutable_heads(),immutable()andmutable(); reads can be made at any operation, and reads of a workspace's@snapshot it first. - Every operation a response reports carries its effects: changes created, rewritten, hidden or abandoned, bookmarks moved, working copies moved. Operations Reef did not just write are classified by kind.
- The write side of the API (ADR 0031):
DescribeChangesets a commit's description;AbandonChangeabandons a commit, and on one commit of a divergent change resolves the divergence, handing its descendants, bookmarks and working copies to the surviving commit;UndoOperationreverts exactly one operation, refusing what it cannot revert exactly (workspace creation, release and moves, git ref imports, operations later ones changed again). Each takes an optionalexpected_commit_id(describe, abandon) and returns the operation it recorded, with effects. Workspaces a write moves are held still and, for abandon and undo, snapshotted first, so a write never overwrites unsnapshotted edits; they take the result afterwards. - Every operation Reef records carries
reef.kind,reef.workspace,reef.agentand, for snapshots,reef.snapshot-kindattributes. - The
reef-agentgRPC request metadata names the agent behind a write; operations record it as their agent instead of the workspace's owner.Client::with_agentsends it. - Files without a workspace (ADR 0032):
ReadFilereads a file at a revision, 1 MiB at a time;ListFileslists a directory, or everything below it, a page at a time;EditFilesapplies edits (whole content, text replacement, delete, copy or rename, the executable bit) in order, all or none, to a change or as a new change on one, and records the result as one commit and one operation of kindEDIT, undoable withUndoOperation. No directory is materialized; workspaces on an edited commit are snapshotted first and take the result. Edits never write through a symlink, into.gitor.jj, or under a shared store's mount.Clientgainsread_file,read_file_to_end,list_files_page,list_filesandedit_files. reef --agent <name>andREEF_AGENTname the agent on every operation a command causes;ws runpasses it on to its command asREEF_AGENT. An invalid name is exit 2, reasonINVALID_AGENT.- Three error reasons:
REPOSITORY_NOT_REGISTERED(a path in no registered repository),REPOSITORY_NOT_FOUNDandWORKSPACE_NOT_FOUND, with the path, repository or workspace as metadata. - Two crates:
reef-client, the thin client of the API, andreef-error, the error conventions (ADR 0033). reef-daemon config show [--json]andreef-daemon restore, whichreef config showandreef restorerun.- API revision 3:
FileEdit.copy_from. - API revision 2:
OperationEffects.change_countandbookmark_count,CHANGE_EFFECT_KIND_HIDDEN,BookmarkEffect.conflicted,WorkspaceStatus.conflicted_path_count, andFileDiff'sold_no_final_newline,new_no_final_newline,too_largeandinvalid_utf8.
Fixed
reef ws gcno longer leavesjjunusable in the repository's own checkout ("Could not read working copy's operation", withjj workspace update-stalefailing too). GC now moves the operation your checkout, and any workspace you added withjj workspace add, last recorded onto the rewritten log, under jj's working-copy lock. While ajjcommand holds that lock, GC abandons no operations and says so (#94, ADR 0040).- A write that rewrites the commit a busy workspace sits on (
EditFiles,AbandonChange,UndoOperation) no longer makes that workspace's change divergent: edits made during the write are carried onto the new commit in the same change, and an overlap with the write is left as a conflict there (#80, ADR 0039).
Changed
reef init --agents autoalso writes the files of the agent running it, when that agent sets a documented variable in the processes it starts, though the repository has none of them yet, so an agent told only to runreef initgets the files it reads. So far that is Claude Code (CLAUDECODE=1: its shells, hooks, stdio MCP servers, and the terminals of its IDE extensions, so also scripts started from them), which getsCLAUDE.md, the skill and.mcp.json. An--agentslist is unaffected, and--checkgoes by the repository's files alone, so it can pass wherereef initrun by an agent would still add that agent's files. Unsetting the variable (env -u CLAUDECODE reef init) writes as before.- A GC, backup or quiesce refused because maintenance is running now names what is running, such as a timed GC.
reef statusis a command, no longer a usage error pointing atreef here. Prefixes that named one command now name several and are usage errors listing them:reef sandreef st(formerlystore),reef d(formerlydaemon),reef a(formerlyguide).reef sto,reef daandreef gustill work.reef herepoints a divergent workspace atreef log --workspace <id> -r 'change_id(<id>)', which lists its commits, instead ofreef ws inspect.- An operation's
kind,snapshot_kind, workspace andagentcome from itsreef.*attributes when it has them;import git headat registration is nowOTHERrather thanEXTERNAL. - The daemon's API is
reef.api.v1, in the newreef-apicrate: typed enums instead of strings, every list paginated, every write returning the jj operation it recorded, errors asgoogle.rpc.Statuswith areef.api.v1.ErrorDetailthat stock gRPC tooling decodes, and every RPC Phase 2 implements declared up front (ADR 0029).reef.daemon.v0is gone exceptDaemon/Status, which a 0.1reef daemon statusandstopstill use. - A daemon that cannot serve a request as asked says so: exit code 9, kind
unsupported. The CLI's JSON error object carries the daemon'sreasonwhen there is one, such asAPI_VERSION_MISMATCH. reef ws listasks the daemon for workspaces newest first, a page at a time.reefcontains no daemon and links no jj-lib (ADR 0033):reef daemon runrunsreef-daemon, andreef config showandreef restorerunreef-daemon config showandreef-daemon restore. Autostart no longer falls back toreef daemon run.reef-daemonmust be besidereefor onPATH; without it, a command that needs it fails with exit 3 and the install line. The release tarballs,install.shand Homebrew ship both binaries; from source, install both crates (cargo install --git https://github.com/reef-vcs/reef reef-vcs reef-daemon, and the same forcargo binstall).- Commands other than
reef config showread onlydaemon.socket,daemon.pid_fileandrepo.auto_registerfromconfig.toml, so an unrelated bad key no longer stops them; the daemon still refuses to start on it, andreef config shownames it. reef-vcsre-exportsreef-apiandreef-clientinstead ofreef-libandreef-daemon, and itswatchmanfeature is gone.ws create --change/--new-on,ws move, andstore build --revtake a jj revset naming one commit, resolved in jj's order: tags, bookmarks, commit id prefixes, then change id prefixes. A bookmark whose name is also an id prefix (cafe) now names the bookmark, and hex text that prefixes both a commit id and a change id names the commit. A divergent change id or a conflicted bookmark is nowinvalid_argument(exit 2, reasonAMBIGUOUS_REVISION, with the commits in the hint) instead ofconflict(exit 5).- Operation subscriptions are served from an in-memory broker (ADR 0038): the daemon reads each new operation once per followed repository, however many subscribe, and reads the log for a subscriber only behind the ring. Frames are unchanged.
reef-daemonraises its open-file limit at start (to 10,240 on macOS, 65,536 elsewhere, within the hard limit) and logs it, so hundreds of clients can connect to a daemon started from a shell whose limit is 256. At the limit it backs off accepting for 100 ms and warns, instead of spinning a core in the accept loop.
Upgrading from 0.1
reef no longer contains the daemon: install reef-daemon with it (cargo install and cargo binstall take both crate names; the tarballs and the Homebrew formula carry both).
A 0.1 daemon keeps running after the binaries are replaced. The first reef command restarts it when it has no active workspaces. Otherwise the command fails with exit code 9 and next set to reef daemon stop: stop it once its ws run sessions end, and the next command starts the new one. A 0.1 reef against the new daemon is told to upgrade (exit 5); its daemon status and daemon stop keep working.
A checkout where jj fails after a 0.1 reef ws gc is repaired by the next reef ws gc when its files match its commit. Otherwise that GC removes the stranded operation, and jj workspace update-stale there recovers it with a recovery commit.
The daemon now collects garbage by itself, first 10 minutes after the new daemon starts; set [gc] interval = "0s" to keep it manual. That first run also repairs a checkout a 0.1 reef ws gc broke, as described above.