OXYGENHUB
◆ v0.18.0 oxygen-vscode / docs / spec.md
22.7 KiB · Markdown · last changed Withdraw tags, verify the transparency log, shallow clones, keep a side or both sides of a conflict, commit and amend by paths, merge remote bookmarks (closes #11) 6 days ago renderedsource blame history raw

Oxygen for Visual Studio Code

StatusDraft 0.2 (milestones 0.1 to 0.6 implemented)
ScopeA VS Code extension giving Oxygen repositories the same integrated experience the built-in Git extension gives Git, plus what the Oxygen model makes possible
Depends onoxy (reference implementation, spec Draft 0.8a) with the machine interface of §4

1. Goals

  1. Parity with the built-in Git extension. Everything a VS Code user does with Git through the UI must be doable with Oxygen through the UI: see what changed, diff it, commit some or all of it, switch bookmarks, sync with a remote, see history, resolve conflicts, and get gutter and explorer decorations, without opening a terminal.
  2. Expose what Oxygen does better. Exact blame, first-class conflicts with a resolution flow, undo of any operation, bisect, subrepositories, and the trust policy get first-class UI rather than being hidden behind the Git vocabulary.
  3. Never lie about state. The extension shows what oxy reports and nothing it infers; every mutation goes through oxy, so the operation log stays the single source of truth and oxy undo works on what the extension did.
  4. Fast enough to run on every keystroke pause. Status refresh after a file save must complete within the manifest stat-cache budget (tens of milliseconds on a warm repository, spec §12.4).

Non-goals for 1.0: a web UI, code review, issue tracking (those belong to oxyhub), a Git compatibility mode (the built-in Git extension already covers Git repositories), and remote development on a machine without oxy.

2. How it compares

Built-in Git extensionOxygen extensionNotes
Source Control view with Changes / StagedSource Control view with Changes and Conflicts groupsNo staging area; partial commits select paths or hunks at commit time (§5.3)
Commit box, Ctrl+EnterSameoxy commit -m; amend from the menu
Quick diff gutterSameBase content via oxy cat (§4)
File decorations (M, A, D, U, C)M, A, D, R (renamed), X (mode), ‼ (conflict markers), ✔ (resolved)From oxy status --json
Branch picker in the status barBookmark picker: switch, create, merge (a remote's bookmark too)oxy bookmark, oxy merge REMOTE/BOOKMARK
Sync (fetch, pull, push)Same, plus per-bookmark pushCounts from oxy bookmark list --json
Timeline view (file history)Timeline: changes that touched the file, from oxy log --json --pathExact, follows renames
Merge conflict editor (Accept current / incoming / both)Conflict editor over oxy markers with the same CodeLens actions, plus "Mark resolved" that runs oxy resolve, "Keep a Side" for binary files and subrepo pins, and "Keep Both Sides" for lines added at the same placeMarkers name the contributing changes
StashWorkspaces (oxy workspace add)Stash has no equivalent; a second workspace is the honest answer
Git graph (via extensions)History view: heads and dependencies as a graphoxy log --json carries dependencies
Blame (via GitLens)Inline blame toggle and a Blame viewoxy blame --json
Undo last commitOperation log view: any operation undone with one clickoxy op log --json, oxy undo
SubmodulesSubrepos node in the SCM view with update and statusoxy submodule status --json
—Bisect paneloxy bisect
—Trust panel: signature status per change, policy summary, the hub's transparency logoxy verify --json, oxy policy show --json, oxy tlog verify --json
Git output channelOxygen output channel with every command run

3. Architecture

  • Language and build. TypeScript, bundled with esbuild, targeting the current VS Code engine. Tests with @vscode/test-electron against a fixture repository created with oxy in a temporary directory.
  • Process model. The extension spawns oxy per operation with --plain --json (§4) and parses its output; it never reads .oxy/ itself. Long operations (clone, push, fetch, bisect run) stream progress to a VS Code progress notification and can be cancelled by killing the process. One operation mutates a repository at a time; reads may run concurrently.
  • Repository model. One Repository object per workspace folder containing (or nested under) an oxy repository, found with oxy root (§4). It holds the last status --json, bookmark list --json, and prompt results, and refreshes on: a file watcher over the workspace (debounced 250 ms, ignoring .oxy/), a watcher on .oxy/ops/head (any operation, including ones from a terminal), window focus, and after every command the extension runs itself.
  • Binary discovery. oxygen.path setting, else oxy on PATH, else ~/.local/bin/oxy. The extension checks oxy --version at activation and shows a one-click "Install or locate oxy" message if it is missing or older than the minimum it supports.
  • URIs. oxy: URIs serve file content from a state: oxy:/<path>?change=<hash>&repo=<root>; change may be @base (the working copy's base), a ChangeHash, a ChangeId prefix, or a bookmark. A TextDocumentContentProvider answers them with oxy cat.
  • Activation. On workspaceContains:.oxy and on the oxygen.* commands.

4. Machine interface required from oxy

The extension depends on stable JSON outputs. These are additions to the reference implementation, tracked as phase 3 in oxygen/docs/plan.md:

CommandOutput
oxy rootThe repository root and workspace id for the current directory, or exit 1
oxy status --jsonExists: paths with status, conflicts
oxy log --json [-n N] [--path P] [--bookmark B]Changes with hash, ChangeId, author, date, message, deps, heads flag, signature status
oxy show --json CHANGEThe change's metadata plus per-file summaries
oxy diff --json [CHANGE]Per file: kind of change and hunks with line ranges and text, for the working copy or a change
oxy cat PATH [-r CHANGE]Raw content of a file at a state (default: the working copy's base)
oxy hunks --jsonNumbered hunks of the working copy, as commit -i offers them
oxy commit --hunks 1,4,5Non-interactive hunk selection
oxy bookmark list --jsonName, heads, current, ahead, behind
oxy op log --jsonOperations with id, time, command, and whether undo can reach them
oxy blame --jsonExists
oxy resolve --jsonPer conflicted path: resolved, markers, unresolved
oxy submodule status --jsonPath, pin, actual, dirty, url
oxy bisect status --jsonThe BisectStep
oxy verify --json [CHANGE]Signature status per change
oxy policy show --jsonRoots, rules, revoked, expiry
oxy remote list --json, fetch --json, pull --json, push --jsonNames, urls, results
oxy promptExists; used for the status bar
oxy discard PATH..Restore paths to the working copy's base (new; the extension's "Discard changes")
oxy commit -m M PATH.., oxy amend PATH..Commit or amend only these paths, a conflict's resolution included; the rest stays uncommitted (oxy 0.5.0)
oxy bookmark list --all --jsonAlso remotes' bookmarks with no local one, named REMOTE/NAME; oxy merge REMOTE/NAME merges one as last fetched (oxygen#117)
oxy resolve PATH..Settle a binary file's or subrepo pin's replace conflict with what the working copy holds there (oxygen#140)
oxy resolve --both [--reverse] [PATH..]Keep every side of each adjacent insertion, earliest change first; reports what keeps its markers and why (oxygen#121, oxy 0.8.0)
oxy tag delete NAME -r REASONWithdraw a tag: a signed withdrawal kept beside its creation; the name is never reused
oxy tlog verify --json [HUB]The hub's transparency log checked: checkpoint size, root, witnesses' cosignatures, this repository's entries
oxy clone --depth 0 URL DIRA shallow clone from the remote's checkpoint: the latest state only
oxy --version --jsonVersion and protocol version

Every JSON command MUST print one JSON document on stdout and nothing else, use exit status 0 for success, and put human-readable errors on stderr with a non-zero status. Ids are hex strings. Times are milliseconds since the epoch.

5. User interface

5.1 Source Control view

  • Groups: Conflicts (only when present, listed first), Changes. Each entry shows the path, a status letter, and a tooltip with the status word; clicking opens the diff editor (working file against oxy: base content; for an added file the file itself; for a removed file the base content).
  • Title actions: Commit (with message box), Refresh, Fetch, Pull, Push, More… (Amend, Undo last operation, Open history, Open operation log).
  • Entry actions: Open file, Open changes, Discard changes (confirm), Commit only this path, Amend selected paths.
  • Multi-select commit: selecting entries and choosing "Commit selected" runs oxy commit PATH..; "Amend Selected Paths" runs oxy amend PATH.., folding only those into the head the working copy sits on.
  • The view's badge is the number of changed paths; the badge turns red with the conflict count while conflicts exist.

5.2 Diff editor and quick diff

  • Quick diff decorations in the gutter come from the base content served over oxy:.
  • The diff editor title is path (base ↔ working copy); for history diffs, path (change A ↔ change B).
  • "Commit this hunk" and "Discard this hunk" CodeLens actions in the diff editor call oxy commit --hunks and oxy discard --hunks (the latter is a phase 3 addition to oxy).

5.3 Commit

  • The message box supports multi-line messages; Ctrl+Enter commits everything, the dropdown offers Commit selected paths, Commit hunks… (opens a quick pick of hunks from oxy hunks --json), Amend, Amend selected paths (oxy amend PATH..), Amend into… (quick pick of changes), and Commit and push.
  • Empty message: the extension opens an editor for the message, like Git's.
  • A pre-commit hook veto shows its stderr in a notification with "Show output".

5.4 Status bar

  • Left: ⚛ main 658990aa ✎ from oxy prompt --glyphs, coloured by state; click opens the bookmark picker.
  • Next to it: ↑2 ↓1 sync counts; click runs Sync (fetch then pull then push, each confirmed on first use).
  • A spinner while any command runs; conflicts show ‼ 2 in the warning colour; click opens the Conflicts group.

5.5 Bookmarks

  • Picker: current bookmark marked, each with heads count and ahead/behind, plus Create bookmark…, Merge bookmark into current…, Delete bookmark…. Switching with uncommitted changes offers Discard or Cancel (never silently).
  • Merge bookmark… lists the other local bookmarks, then remotes' bookmarks that have no local one (oxy bookmark list --all --json, shown as origin/develop, "as last fetched"); picking one runs oxy merge with that name.
  • Tags… lists tags to check out, plus Create tag… and Withdraw a tag…. Withdraw Tag… asks for the tag and a reason, confirms in a modal that the withdrawal is signed with your key, kept beside the tag's creation and logged (pushed to remotes that have the tag), and that the name is never used again, then runs oxy tag delete NAME -r REASON.
  • Explorer context menu on a folder: "Open in new workspace" runs oxy workspace add.

5.6 History view

  • A webview showing the bookmark's changes as a graph: nodes are changes, edges are dependencies, heads highlighted, the working copy at the top. Multiple heads render side by side with no fake merge nodes.
  • Clicking a change opens its summary (message, author, files, signature); actions: checkout, describe, abandon, squash into…, rebase onto…, tag, blame this version of a file, open diff against parent.
  • Filters: bookmark, author, path, text.

5.7 Conflicts

  • Files with markers open in the editor with CodeLens over each region: Accept side 1, Accept side 2, Accept both, Compare sides. Side labels are the contributing changes' short ids and subjects, as the markers carry them.
  • Tree conflicts (.oxy-conflict-N candidates) appear under Conflicts with actions "Keep this candidate" (renames it into place and removes the rest).
  • A binary file's or subrepo pin's conflict (a replace conflict, no markers to edit) shows Keep a Side…. For a file it offers each side, labelled with the side's change, plus the version on disk: keeping a side writes that change's version of the file (oxy cat PATH -r CHANGE), removes the .oxy-conflict-N candidates, and runs oxy resolve PATH, which settles the conflict with what the working copy holds. For a subrepo pin it offers the pin the nested repository holds now, or opening the subrepo to move it first (oxygen#140).
  • A text conflict, and the Conflicts group, offer Keep Both Sides (Lines Added at the Same Place)…: oxy resolve --both [PATH..], earliest change first or (--reverse) latest first. Where the sides changed the same lines the markers stay, and oxy's reason is shown (oxygen#121; needs oxy 0.8.0).
  • "Mark resolved" runs oxy resolve --json and reports what remains; the commit action reads "Commit resolution" while conflicts are resolved but not committed.
  • Commit Resolution of Selected Paths, on entries of the Conflicts group, commits only those paths' resolution (oxy commit -m M PATH..) while other conflicts stay for later; it asks first if a chosen path is not resolved yet.

5.8 Blame

  • Command "Toggle inline blame" decorates each line's end with changeid · author · age; hovering shows the full message with a link to the change. A "Blame" side view lists the same per line. Uses oxy blame --json on the base content and adjusts for unsaved edits by dimming lines that differ from base.

5.9 Operation log

  • A tree view listing operations newest first with time and command; each has Undo to here. The item corresponding to the current view is marked. Snapshot operations are hidden by default.

5.10 Subrepositories

  • A Subrepos node in the SCM view when .oxymodules exists or subrepo nodes are tracked: each entry shows path, pin, and state (at pin, differs, dirty, missing) with actions Update, Update all (--init), Open folder.

5.11 Bisect

  • Panel with Start, Good/Bad marking of the state under test, Run with a task or command, Skip, Reset, and a progress line ("7 candidates, about 3 steps"). The culprit opens in the history view.

5.12 Trust

  • Each change in the history view shows a signature badge (signed, sealed, unsigned, invalid, revoked key). A Trust section in the SCM view summarises the policy (roots, rules, expiry) and flags an expired policy or an unverified remote.
  • The Trust view's last line is the hub's transparency log. It is checked on request, not on refresh, since it asks the hub: Verify Transparency Log (the view's title button, the line itself, or the command) runs oxy tlog verify --json against the checkout's remote and shows "verified up to checkpoint N" with the log's origin and this repository's entry count, then one line per witness that cosigned the checkpoint (or a warning that none you trust did), or the error when the check fails.

5.13 Remotes and sync

  • Remote picker for fetch/pull/push when more than one remote exists; results shown as notifications (rejected pushes explain why and offer Pull). Tokens are read from the repository configuration; the extension never stores secrets itself.
  • Clone Repository… asks for the URL and the parent folder, then offers Full history or Shallow (latest state only, fast for big repositories), which runs oxy clone --depth 0 URL DIR.

5.14 Settings

SettingDefaultMeaning
oxygen.path""Path to the oxy binary
oxygen.autoRefreshtrueWatch the workspace and refresh
oxygen.autoFetchfalseFetch every oxygen.autoFetchPeriod seconds (default 180)
oxygen.inlineBlamefalseShow inline blame by default
oxygen.showSnapshotOperationsfalseList snapshot operations in the operation log
oxygen.confirmSynctrueConfirm before pushing
oxygen.decorations.enabledtrueExplorer and tab decorations

5.15 Commands (all prefixed Oxygen:)

Commit, Commit Selected Paths, Commit Resolution of Selected Paths, Commit Hunks…, Amend, Amend Selected Paths, Amend Into…, Discard Changes, Refresh, Fetch, Pull, Push, Push Bookmark…, Sync, Switch Bookmark…, Create Bookmark…, Merge Bookmark…, Delete Bookmark…, Open History, Open Operation Log, Undo Last Operation, Toggle Inline Blame, Show Blame, Resolve Conflicts, Mark Resolved, Keep a Side…, Keep Both Sides (Lines Added at the Same Place)…, Checkout Change…, Describe Change…, Abandon Change…, Squash Into…, Rebase Onto…, Tags…, Create Tag…, Withdraw Tag…, Add Workspace…, Update Subrepos, Bisect Start/Good/Bad/Skip/Run/Reset, Verify Signatures, Verify Transparency Log, Show Policy, Open Output, Clone Repository…, Initialize Repository.

Keybindings match the Git extension where a counterpart exists (for example Ctrl+Enter in the commit box).

6. Behaviour rules

  • Refresh is cheap and debounced; a refresh never blocks the UI thread and never runs two status calls at once per repository.
  • Errors from oxy appear verbatim in a notification with "Show output"; the output channel logs every command, its working directory, duration, and exit status.
  • Nothing destructive without a confirmation: discard, delete bookmark, abandon, --discard switches, and force push all confirm, with a "Don't ask again" option per action.
  • Multi-root workspaces: one repository model per folder; the SCM view shows a provider per repository, as Git does.
  • Files outside the sparse filter are shown greyed with a tooltip.
  • Large repositories: history and blame are paged (-n) and fetched on demand; decorations use status --json only.

7. Repository layout and tooling

oxygen-vscode/
  package.json          contributes: commands, views, menus, keybindings, settings, scm
  src/
    extension.ts        activation, repository discovery
    oxy.ts              process wrapper and JSON parsing (one function per §4 command)
    model/              Repository, status, bookmarks, operations
    scm/                SourceControl provider and resource groups
    views/              history (webview), operation log, blame, subrepos, bisect, trust
    editor/             oxy: content provider, quick diff, conflict CodeLens, inline blame
    commands/           one file per command group
  assets/images/        oxygen.svg (the extension icon), history webview assets
  test/                 integration tests against a fixture repository
  docs/spec.md          this document

Build with npm run build (esbuild), test with npm test, package with vsce package, publish to the Marketplace under the oxygen publisher once 1.0 is reached. CI runs lint, build, and tests on Linux, macOS, and Windows with a pinned oxy release.

8. Milestones

  1. 0.1 Read-only: activation, binary discovery, SCM view with Changes and Conflicts, diff editor over oxy:, quick diff, decorations, status bar. Requires oxy root, cat, status --json (exists), prompt.
  2. 0.2 Write: commit (all, paths, hunks, amend), discard, refresh on operations, undo, operation log view. Requires hunks --json, commit --hunks, discard, op log --json.
  3. 0.3 Bookmarks and sync: picker, create/switch/merge/delete, fetch/pull/push with results, remotes. Requires bookmark list --json, sync --json outputs.
  4. 0.4 History and blame: history webview, timeline, inline blame, show. Requires log --json, show --json, diff --json.
  5. 0.5 Conflicts: conflict editor CodeLens, tree conflict candidates, resolve flow. Requires resolve --json.
  6. 0.6 The rest: subrepos, bisect, trust, tags, workspaces, clone and init.
  7. 1.0: tests on three platforms, Marketplace listing, documentation.

8a. Implementation notes (2026-09-27)

  • The Timeline API is still proposed in VS Code, so file history is a command with a quick pick and diffs between versions rather than a Timeline provider.
  • oxy gained tag list --json and workspace list --json for §4, and two bugs the extension's tests found were fixed in oxy: a chosen addition hunk now carries its content, and an unsigned repository keeps a tag's message.
  • Extensions are per VS Code profile; the install command must name the profile a workspace uses.

8b. Implementation notes (2026-10-29): oxy 0.5 to 0.9

  • Brought up to oxy 0.9 (oxygen-vscode#11): tag withdrawal, the transparency log in the Trust view, shallow clone, keep-a-side and keep-both-sides settling, commit and amend by paths, and merging a remote's bookmark, each through the commands in §4.
  • The CLI names a withdrawal oxy tag delete NAME -r REASON (help: "Withdraw a tag"); there is no tag withdraw subcommand.
  • oxy resolve PATH settles a replace conflict with whatever the working copy holds; there is no per-side flag, so the extension writes the chosen side's version first. oxy status --json does not say which text conflicts are adjacent insertions (only the human output does), so Keep Both Sides is offered on every text conflict and reports oxy's refusals.
  • oxy resolve --both needs oxy 0.8.0; the tests that use it are skipped on older oxy.
  • bookmark list --all --json marks no field for a remote's bookmark; the extension recognises one by its REMOTE/ name prefix, remote field and null ahead/behind counts.

9a. Exported API (0.7.0)

activate returns an OxygenAPI (src/api.ts): the repositories as handles (root, status, bookmarks, current bookmark, remotes(), json(args), refresh()), onDidChangeStatus, onDidChangeRepositories, getRepository(path), and registerRemoteProvider. A remote provider gives an id and label, matches(remoteUrl) and urlFor(remoteUrl, target) for repository, bookmark, change, file (with line), proposals and issues targets. This extension uses the providers for its Open on Remote commands and sets the oxygen.hasRemoteProvider context; oxygenhub-vscode registers one for hubs and consumes the handles instead of running oxy itself, mirroring how the GitHub Pull Requests extension builds on the built-in Git extension.

9. Open questions

  1. Whether the extension should ship oxy binaries for the three platforms or only locate one; shipping simplifies onboarding but ties releases together.
  2. Whether inline blame should run on every edit (as GitLens does) or only on save; the stat cache makes per-save cheap, per-edit needs a content-aware diff against base in the extension.
  3. Whether a long-lived oxy daemon (oxy serve-ide) is warranted for very large repositories, where spawning a process per refresh would dominate.