Skip to content

Reef 0.2.0

Choose a tag to compare

@github-actions github-actions released this 28 Oct 20:50
· 54 commits to main since this release
b5741e8

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): SymbolService with IndexSymbols, UploadScipDocuments and ListSymbols (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_QUERY and INVALID_UPLOAD. GC collects the index as its fifth step.
  • reef symbols index, upload and list (alias sym): 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] in config.toml: enabled, background, workers, max_file_size and max_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; 0s turns it off), over every repository, 10 minutes after it starts at the earliest and a full interval after the last full run, timed or reef 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 gc prints 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.yml runs it on both runners.
  • reef ws run reports 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 mcp reports, 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 --agent or REEF_AGENT, else the workspace's owner.
  • reef presence report reports what you are doing: TEXT for an activity, --path for files you are editing, --phase and --done for progress, in the current workspace or repository; it prints the entry's id (-o text) or the entry. --id replaces an entry, and --id-file keeps 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 clear ends entries by id or --id-file.
  • reef ws renew renews a workspace's lease, the one you are in by default: what ws run does for its command, for a workspace held outside ws run. reef here suggests it for a lease in its grace period.
  • reef_client::presence: the Reporter that ws run and reef mcp use, the reporting contract that ReportPresence's comment now states, and PresenceApi.
  • Presence (ADR 0037): ReportPresence, ClearPresence and SubscribePresence. 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_LARGE and TOO_MANY_PRESENCE_ENTRIES. Client::report_presence, clear_presence and subscribe_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 watch prints presence as it changes, one JSON line per event when piped, filtered by --repo, --workspace, --by and --kind; --for 0 prints 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_reason and PresenceEndReason.
  • SubscribeOperations streams the operation log (ADR 0036): frames of operations as they are recorded, filtered like ListOperations, from now, after an operation, or from a frame's cursor, with nothing dropped. Operations Reef records arrive within 100 ms, other jj processes' within about 200 ms. Four error reasons: INVALID_CURSOR, TOO_FAR_BEHIND, OPERATION_LOG_REWRITTEN and TOO_MANY_SUBSCRIPTIONS. Client::subscribe_operations returns the stream.
  • reef op watch prints operations as they are recorded, one JSON line each when piped, with op log's filters, --after to 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_operations and the rest), returning the JSON reef -o json prints, with errors as tool results carrying the daemon's hint, next and metadata. No ADMIN RPC is a tool. Workspaces the server creates are heartbeated while it runs. wait_for_operations waits for operations matching a filter, reading SubscribeOperations.
  • reef init writes Reef's entry in .mcp.json with Claude Code's files, and with --agents mcp; it owns only mcpServers.reef and leaves the rest of the file as it was.
  • json::Edited, edit_files' result, in reef-client.
  • Commands over the VCS services (ADR 0034): reef status, log, show, diff, describe, abandon, undo, op log, op show, ws snapshot, file read and file list, from a workspace or a checkout, or with --workspace or --repo. Paths given are relative to the current directory; paths printed are repository-relative. Each prints JSON when piped (file read prints the file's bytes unless -o json), and errors from the API carry the CLI's own hint and a next built 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 metadata when there is any, such as AMBIGUOUS_REVISION's commit_ids.
  • An error reason, SNAPSHOT_FAILED (metadata workspace_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), ListOperations and GetOperation. Revisions are jj revsets with Reef's trunk(), immutable_heads(), immutable() and mutable(); 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): DescribeChange sets a commit's description; AbandonChange abandons a commit, and on one commit of a divergent change resolves the divergence, handing its descendants, bookmarks and working copies to the surviving commit; UndoOperation reverts 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 optional expected_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.agent and, for snapshots, reef.snapshot-kind attributes.
  • The reef-agent gRPC request metadata names the agent behind a write; operations record it as their agent instead of the workspace's owner. Client::with_agent sends it.
  • Files without a workspace (ADR 0032): ReadFile reads a file at a revision, 1 MiB at a time; ListFiles lists a directory, or everything below it, a page at a time; EditFiles applies 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 kind EDIT, undoable with UndoOperation. No directory is materialized; workspaces on an edited commit are snapshotted first and take the result. Edits never write through a symlink, into .git or .jj, or under a shared store's mount. Client gains read_file, read_file_to_end, list_files_page, list_files and edit_files.
  • reef --agent <name> and REEF_AGENT name the agent on every operation a command causes; ws run passes it on to its command as REEF_AGENT. An invalid name is exit 2, reason INVALID_AGENT.
  • Three error reasons: REPOSITORY_NOT_REGISTERED (a path in no registered repository), REPOSITORY_NOT_FOUND and WORKSPACE_NOT_FOUND, with the path, repository or workspace as metadata.
  • Two crates: reef-client, the thin client of the API, and reef-error, the error conventions (ADR 0033).
  • reef-daemon config show [--json] and reef-daemon restore, which reef config show and reef restore run.
  • API revision 3: FileEdit.copy_from.
  • API revision 2: OperationEffects.change_count and bookmark_count, CHANGE_EFFECT_KIND_HIDDEN, BookmarkEffect.conflicted, WorkspaceStatus.conflicted_path_count, and FileDiff's old_no_final_newline, new_no_final_newline, too_large and invalid_utf8.

Fixed

  • reef ws gc no longer leaves jj unusable in the repository's own checkout ("Could not read working copy's operation", with jj workspace update-stale failing too). GC now moves the operation your checkout, and any workspace you added with jj workspace add, last recorded onto the rewritten log, under jj's working-copy lock. While a jj command 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 auto also 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 run reef init gets 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 gets CLAUDE.md, the skill and .mcp.json. An --agents list is unaffected, and --check goes by the repository's files alone, so it can pass where reef init run 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 status is a command, no longer a usage error pointing at reef here. Prefixes that named one command now name several and are usage errors listing them: reef s and reef st (formerly store), reef d (formerly daemon), reef a (formerly guide). reef sto, reef da and reef gu still work.
  • reef here points a divergent workspace at reef log --workspace <id> -r 'change_id(<id>)', which lists its commits, instead of reef ws inspect.
  • An operation's kind, snapshot_kind, workspace and agent come from its reef.* attributes when it has them; import git head at registration is now OTHER rather than EXTERNAL.
  • The daemon's API is reef.api.v1, in the new reef-api crate: typed enums instead of strings, every list paginated, every write returning the jj operation it recorded, errors as google.rpc.Status with a reef.api.v1.ErrorDetail that stock gRPC tooling decodes, and every RPC Phase 2 implements declared up front (ADR 0029). reef.daemon.v0 is gone except Daemon/Status, which a 0.1 reef daemon status and stop still 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's reason when there is one, such as API_VERSION_MISMATCH.
  • reef ws list asks the daemon for workspaces newest first, a page at a time.
  • reef contains no daemon and links no jj-lib (ADR 0033): reef daemon run runs reef-daemon, and reef config show and reef restore run reef-daemon config show and reef-daemon restore. Autostart no longer falls back to reef daemon run. reef-daemon must be beside reef or on PATH; without it, a command that needs it fails with exit 3 and the install line. The release tarballs, install.sh and 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 for cargo binstall).
  • Commands other than reef config show read only daemon.socket, daemon.pid_file and repo.auto_register from config.toml, so an unrelated bad key no longer stops them; the daemon still refuses to start on it, and reef config show names it.
  • reef-vcs re-exports reef-api and reef-client instead of reef-lib and reef-daemon, and its watchman feature is gone.
  • ws create --change/--new-on, ws move, and store build --rev take 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 now invalid_argument (exit 2, reason AMBIGUOUS_REVISION, with the commits in the hint) instead of conflict (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-daemon raises 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.