VCS Abstraction Layer — Uniform jj/git API via vcs.nu
Accepted
Context
ontoref modules that interact with version control (opmode.nu, git-event.nu, jjw.nu, init-repo.nu) historically hardcoded ^git subcommands. As jj adoption grows among contributors and orchestration projects (vapora), hardcoded git calls produce silent failures: jj repos have .jj/ but may lack a .git/ HEAD in expected locations, and jj semantics differ (the working copy is always a commit, @- is the parent, `jj file show` replaces `git show HEAD:path`). The dual-VCS problem had two layers: (1) detection — which VCS is active in a given project root, and (2) semantics — same logical operation (show last committed state, restore a file, get remote URL) expressed differently per VCS. Spreading detection logic across modules produces duplication and makes future VCS additions (e.g. Pijul, Sapling) a multi-file change.
Decision
Introduce reflection/modules/vcs.nu as the single VCS abstraction layer. It exports: detect (returns 'jj' | 'git' | 'none' via filesystem check — .jj/ presence), is-repo, show-committed (jj: `jj file show -r @-`; git: `git show HEAD:path`), restore-file (jj: `jj restore --from @-`; git: `git checkout --`), remote-url (jj: `jj git remote list`; git: `git remote get-url origin`), current-branch (jj: `jj log -r @ --no-graph -T bookmarks`; git: `git branch --show-current`), uncommitted-files (jj: `jj diff --summary -r @`; git: `git status --porcelain`), commit-count (jj: `jj log --no-graph -T '' | lines | length`; git: `git rev-list --count HEAD`). All ontoref modules must import vcs.nu and call these exports — direct ^git or ^jj subprocess calls inside modules are prohibited. jj and rad are not listed as requirements in ontoref's manifest: they are opt-in tools whose requirements belong in orchestration projects that depend on them.
Constraints
- Hard All VCS subprocess calls in reflection/modules/ and reflection/bin/ must go through vcs.nu exports — no direct ^git or ^jj calls outside vcs.nu itself
- Hard jj and rad must not appear as required = true entries in .ontology/manifest.ncl requirements[]
Alternatives considered
- Env var ONTOREF_VCS to select backend — rejected: Creates mutable state that can desync from the actual repo state. A repo cloned fresh has no env var set; a contributor switching between git and jj repos would need to update the env var manually. Filesystem detection is always correct without configuration.
- Per-module inline detection (duplicate detect logic in each file) — rejected: Already the de-facto state before vcs.nu. Duplicated detection means any change to jj semantics (e.g. a jj CLI flag change) requires hunting every module. The abstraction cost is one import line per module.
- Wrap the entire CLI in a shim that translates git commands to jj — rejected: Shim-layer translation is fragile — git and jj command surfaces are not isomorphic (jj has no git stash equivalent; jj describe vs git commit -m). The operations ontoref needs are a small, well-defined set; a typed Nu module is a cleaner contract than a command-translation shim.