ontoref/reflection/modules/vcs.nu
Jesús Pérez 472952e29b
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
feat: domain extension system, VCS abstraction, personal/provisioning domains, web subpages
Domain extension system (ADR-012): bash-layer dispatch activates repo_kind-conditional CLI
  domains. install.nu copies domains/ tree; short_alias wrappers generated (personal, prov).
  ore help and describe capabilities domain-aware.

  personal domain (PersonalOntology): career skills/talks/publications/positioning, CFP
  pipeline (Watching→Delivered), opportunities lifecycle, content pipeline, Sessionize
  integration. Daemon pages: /career, /personal.

  provisioning domain (DevWorkspace/Mixed): FSM state, next transitions, connections graph,
  gates, workspace card, capabilities, backlog. Daemon page: /provisioning.

  VCS abstraction layer (ADR-013): reflection/modules/vcs.nu — uniform jj/git API via
  filesystem detection (.jj/ vs .git/). opmode.nu and git-event.nu migrated off ^git.
  reflection/bin/jjw.nu — jj + ontoref + Radicle agent workspace lifecycle. jjw-ncl-merge.nu
  registered as jj merge tool for .ontology/ NCL conflicts. init-repo.nu for new_project mode.
  jj/rad not in ontoref requirements — belong in orchestration project manifests.

  'Framework RepoKind: ontology/schemas/manifest.ncl gains 'Framework variant; ontoref
  self-identifies as framework — no domain activates for the protocol itself.

  Web presence: personal.html and provisioning.html domain subpages. index.html gains
  "Project Types — Domain Extensions" section with type cards and subpage links. Nav
  compacted (Arch/Prov labels, solid backdrop-filter background).

  on+re: vcs-abstraction (adrs: adr-013) and agent-workspace-orchestration Practice nodes;
  21 manifest capabilities; state.ncl catalysts updated.
2026-04-07 23:08:29 +01:00

117 lines
4.3 KiB
Text

# VCS abstraction — detects jj or git from filesystem, exposes uniform operations.
#
# All ontoref modules should call these functions instead of hardcoding `git`
# commands. Detection is filesystem-based (no config, no env var): checks for
# `.jj/` (jj) and `.git/` (git) in ONTOREF_PROJECT_ROOT or ONTOREF_ROOT.
use ./env.nu
# Private: resolve project root following the same convention as other modules.
def project-root []: nothing -> string {
let pr = ($env.ONTOREF_PROJECT_ROOT? | default "")
if ($pr | is-not-empty) and ($pr != $env.ONTOREF_ROOT) { $pr } else { $env.ONTOREF_ROOT }
}
# Detect which VCS is active in the project root.
# Returns: "jj" | "git" | "none"
export def detect []: nothing -> string {
let root = (project-root)
let has_jj = ($root | path join ".jj" | path exists)
let has_git = ($root | path join ".git" | path exists)
if $has_jj { "jj" } else if $has_git { "git" } else { "none" }
}
# True when any VCS is active.
export def "is-repo" []: nothing -> bool {
(detect) != "none"
}
# Get file content at last-committed state.
#
# jj model: the working copy IS a commit (@). Reading from @- gives the parent
# commit — i.e., the state before any current working-copy edits.
# `jj file show` requires jj >= 0.40.
export def "show-committed" [file_path: string]: nothing -> string {
let root = (project-root)
match (detect) {
"jj" => {
let rel = ($file_path | path relative-to $root)
do { ^jj --no-pager --repository $root file show $rel -r "@-" } | complete
| if $in.exit_code == 0 { $in.stdout } else { "" }
},
"git" => {
let rel = ($file_path | path relative-to $root)
do { ^git -C $root show $"HEAD:($rel)" } | complete
| if $in.exit_code == 0 { $in.stdout } else { "" }
},
_ => { "" }
}
}
# Revert a file to its last-committed state.
#
# jj: `jj restore --from @-` undoes working-copy edits for the given file.
# git: `git checkout -- <file>` restores from HEAD.
export def "restore-file" [file_path: string]: nothing -> nothing {
let root = (project-root)
match (detect) {
"jj" => {
let rel = ($file_path | path relative-to $root)
do { ^jj --no-pager --repository $root restore --from "@-" -- $rel } | complete | ignore
},
"git" => {
do { ^git -C $root checkout -- $file_path } | complete | ignore
},
_ => {
error make { msg: $"vcs restore-file: no VCS at ($root)" }
}
}
}
# Get the remote origin URL.
export def "remote-url" []: nothing -> string {
let root = (project-root)
match (detect) {
"jj" => {
let r = do { ^jj --no-pager --repository $root git remote list } | complete
if $r.exit_code != 0 or ($r.stdout | str trim | is-empty) { return "" }
# Format: "<name> <url>" per line — extract URL from first remote
$r.stdout | lines | where { |l| ($l | str trim | is-not-empty) }
| first | split row "\t" | last | str trim
},
"git" => {
let r = do { ^git -C $root remote get-url origin } | complete
if $r.exit_code == 0 { $r.stdout | str trim } else { "" }
},
_ => { "" }
}
}
# Get the current branch or bookmark name.
#
# jj: returns the bookmark pointing to @, or "detached" if none.
# git: returns the branch name from rev-parse.
export def "current-ref" []: nothing -> string {
let root = (project-root)
match (detect) {
"jj" => {
let r = do {
^jj --no-pager --repository $root bookmark list --pointing-to "@"
-T 'name ++ "\n"'
} | complete
if $r.exit_code != 0 { return "detached" }
let names = ($r.stdout | lines | where { |l| ($l | str trim | is-not-empty) })
if ($names | is-empty) { "detached" } else { $names | first }
},
"git" => {
let r = do { ^git -C $root rev-parse --abbrev-ref HEAD } | complete
if $r.exit_code == 0 { $r.stdout | str trim } else { "unknown" }
},
_ => { "unknown" }
}
}
# True when the project root has a VCS remote configured.
export def "has-remote" []: nothing -> bool {
(remote-url) | is-not-empty
}