ontoref/domains/personal/commands.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

336 lines
18 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env nu
# domains/personal/commands.nu — PersonalOntology domain CLI for ontoref.
# Dispatched by the ontoref bash wrapper when repo_kind = PersonalOntology:
# ore personal <command> [args]
#
# Reads data from ONTOREF_PROJECT_ROOT (set by the bash wrapper).
# Code lives in ONTOREF_ROOT/domains/personal/. Data lives in the project.
def project-root [] {
$env.ONTOREF_PROJECT_ROOT? | default (pwd | path expand)
}
# ─── Loaders ──────────────────────────────────────────────────────────────────
def load-core [] { ^nickel export $"(project-root)/.ontology/core.ncl" | from json }
def load-state [] { ^nickel export $"(project-root)/.ontology/state.ncl" | from json }
def load-career [] { ^nickel export $"(project-root)/.ontology/career.ncl" | from json }
def load-personal [] { ^nickel export $"(project-root)/.ontology/personal.ncl" | from json }
def load-backlog [] { ^nickel export $"(project-root)/reflection/backlog.ncl" | from json }
# ─── Display helpers ─────────────────────────────────────────────────────────
def render-links [links: list] {
if ($links | is-empty) { return }
print ""
print "links:"
$links | each { |lk|
let label = if ($lk.label? | default "" | is-not-empty) { $" — ($lk.label)" } else { "" }
print $" [($lk.kind)] ($lk.url)($label)"
}
}
# ─── NCL patch helpers ────────────────────────────────────────────────────────
def ncl-validate [path: string] {
^nickel export $path | from json | ignore
}
def ncl-block-field-idx [lines: list<string>, target_id: string, field: string] {
let id_idx = (
$lines | enumerate
| where { |l| $l.item | str contains $"\"($target_id)\"" }
| first | get index
)
let next_id_idx = (
$lines | enumerate | skip ($id_idx + 1)
| where { |l| ($l.item | str trim | str starts-with "id ") and ($l.item | str contains "=") and ($l.item | str contains "\"") }
| if ($in | is-empty) { [{ index: ($lines | length) }] } else { $in }
| first | get index
)
$lines | enumerate
| where { |l| $l.index > $id_idx and $l.index < $next_id_idx }
| where { |l| $l.item | str trim | str starts-with $"($field) " }
| first | get index
}
# ─── state / next / validate / audit ─────────────────────────────────────────
export def "state" [] {
load-state | get dimensions | each { |dim|
let cur = $dim.states | where id == $dim.current_state | first
{ dimension: $dim.id, current: $dim.current_state, desired: $dim.desired_state, tension: $cur.tension, horizon: $dim.horizon }
}
}
export def "next" [] {
load-state | get dimensions | each { |dim|
let active = $dim.transitions | where from == $dim.current_state
if ($active | is-empty) {
{ dimension: $dim.id, from: $dim.current_state, to: "", condition: "no active transition", blocker: "", catalyst: "", horizon: "" }
} else {
let tr = $active | first
{ dimension: $dim.id, from: $tr.from, to: $tr.to, condition: $tr.condition, blocker: $tr.blocker, catalyst: $tr.catalyst, horizon: $tr.horizon }
}
}
}
export def "validate" [decision: string] {
let core = load-core
let words = ($decision | str downcase | split row " " | where { |w| ($w | str length) > 3 })
let affected = ($core.nodes | each { |n|
let text = $"($n.id) ($n.name) ($n.description)" | str downcase
if ($words | any { |w| $text =~ $w }) { $n } else { null }
} | compact)
{
decision: $decision,
core_axioms: ($core.nodes | where { |n| $n.id in $core.core } | select id level name),
invariants_at_risk: ($affected | where invariant == true | select id level name),
tensions_touched: ($affected | where level == "Tension" | select id name),
practices_touched: ($affected | where level == "Practice" | select id name),
verdict: "manual review required — if any invariant is contradicted, justification is required",
}
}
export def "audit" [] {
let core = load-core
let nodes = $core.nodes
let edges = $core.edges
let practice_ids = ($nodes | where level == "Practice" | get id)
let tensions = ($nodes | where level == "Tension" | each { |t|
let linked = ($edges | where kind == "ManifestsIn" | where { |e| $e.from == $t.id or $e.to == $t.id }
| each { |e| let peer = if $e.from == $t.id { $e.to } else { $e.from }; if $peer in $practice_ids { $peer } else { null } } | compact)
{ tension: $t.id, name: $t.name, practice_count: ($linked | length), practices: ($linked | str join ", "), status: (if ($linked | is-empty) { "no practice" } else { "ok" }) }
})
let axioms = ($nodes | where level == "Axiom" | each { |a|
let manifests = ($edges | where from == $a.id and kind == "ManifestsIn" | each { |e| $nodes | where id == $e.to | first })
{ axiom: $a.id, name: $a.name, total_links: ($manifests | length), project_links: ($manifests | where level == "Project" | length), status: (if ($manifests | where level == "Project" | is-empty) { "no project link" } else { "ok" }) }
})
{ tensions: $tensions, axioms: $axioms, active_gap: $core.tension_without_practice }
}
# ─── projects ────────────────────────────────────────────────────────────────
export def "projects" [] {
load-core | get nodes | where level == "Project" | select id name description
}
export def "projects show" [id: string] {
let nodes = load-core | get nodes
let r = if ($id =~ '^[0-9]+$') {
let projects = ($nodes | where level == "Project")
let idx = ($id | into int)
if $idx >= ($projects | length) { print --stderr $"projects show: index out of range ($idx) — total ($projects | length)"; exit 1 }
$projects | skip $idx | first
} else {
let found = ($nodes | where id == $id and level == "Project")
if ($found | is-empty) { print --stderr $"projects show: not found — ($id)"; exit 1 }
$found | first
}
print $"id: ($r.id)"
print $"name: ($r.name)"
print $"description: ($r.description)"
if ($r.artifact_paths? | default [] | is-not-empty) {
print ""
print "artifacts:"
$r.artifact_paths | each { |p| print $" ($p)" }
}
render-links ($r.links? | default [])
}
# ─── career ───────────────────────────────────────────────────────────────────
export def "career skills" [--tier: string = ""] {
let items = load-career | get skills
let filtered = if ($tier | is-empty) { $items } else { $items | where tier == $tier }
$filtered | sort-by --reverse proficiency | select name tier proficiency years
}
export def "career talks" [--status: string = ""] {
let items = load-career | get talks
let filtered = if ($status | is-empty) { $items } else { $items | where status == $status }
$filtered | select id title event date status
}
export def "career publications" [--featured] {
let items = load-career | get publications | sort-by sort_order
let filtered = if $featured { $items | where featured == true } else { $items }
$filtered | select project_node tagline status version featured
}
export def "career positioning" [] {
load-career | get positioning | select name core_message target
}
# ─── content ──────────────────────────────────────────────────────────────────
export def "content" [--status: string = ""] {
let items = load-personal | get contents
let filtered = if ($status | is-empty) { $items } else { $items | where status == $status }
$filtered | select id title kind status
}
# ─── opportunities ────────────────────────────────────────────────────────────
export def "opportunities" [--status: string = ""] {
let items = load-personal | get opportunities
let filtered = if ($status | is-empty) { $items } else { $items | where status == $status }
$filtered | select id name kind status deadline
}
export def "opportunities show" [id: string] {
let items = load-personal | get opportunities
let r = if ($id =~ '^[0-9]+$') {
let idx = ($id | into int)
if $idx >= ($items | length) { print --stderr $"opportunities show: index out of range ($idx) — total ($items | length)"; exit 1 }
$items | skip $idx | first
} else {
let found = ($items | where id == $id)
if ($found | is-empty) { print --stderr $"opportunities show: not found — ($id)"; exit 1 }
$found | first
}
print $"id: ($r.id)"
print $"name: ($r.name)"
print $"kind: ($r.kind)"
print $"status: ($r.status)"
print $"deadline: ($r.deadline)"
render-links ($r.links? | default [])
if ($r.fit_signals | is-not-empty) { print ""; print $"fit_signals: ($r.fit_signals | str join ', ')" }
if ($r.linked_nodes | is-not-empty) { print ""; print $"nodes: ($r.linked_nodes | str join ', ')" }
print ""; print "note:"; print $" ($r.note)"
}
export def "opportunities update" [id: string, --status: string] {
let valid = ["Watching" "Evaluating" "Active" "Submitted" "Closed"]
if ($status | is-empty) { print --stderr "opportunities update: --status required"; exit 1 }
if not ($status in $valid) { print --stderr $"opportunities update: invalid status '($status)' — valid: ($valid | str join ', ')"; exit 1 }
let items = load-personal | get opportunities
if ($items | where id == $id | is-empty) { print --stderr $"opportunities update: not found — ($id)"; exit 1 }
let path = $"(project-root)/.ontology/personal.ncl"
let lines = open --raw $path | lines
let status_idx = (ncl-block-field-idx $lines $id "status")
let new_status = ($lines | get $status_idx | str replace --regex `'\w+` $"'($status)")
let patched = ($lines | enumerate | each { |l| if $l.index == $status_idx { $new_status } else { $l.item } } | str join "\n")
$patched | save --force $path
ncl-validate $path
print $"($id): status → '($status)"
}
# ─── cfp ──────────────────────────────────────────────────────────────────────
export def "cfp" [--stage: string = ""] {
let items = load-backlog | get cfp
let filtered = if ($stage | is-empty) { $items } else { $items | where stage == $stage }
$filtered | select id name stage deadline sessionize_session_id next_action
}
export def "cfp show" [id: string] {
let items = load-backlog | get cfp
let r = if ($id =~ '^[0-9]+$') {
let idx = ($id | into int)
if $idx >= ($items | length) { print --stderr $"cfp show: index out of range ($idx) — total ($items | length)"; exit 1 }
$items | skip $idx | first
} else {
let found = ($items | where id == $id)
if ($found | is-empty) { print --stderr $"cfp show: not found — ($id)"; exit 1 }
$found | first
}
print $"id: ($r.id)"
print $"name: ($r.name)"
print $"stage: ($r.stage)"
print $"deadline: ($r.deadline)"
print $"opportunity_id: ($r.opportunity_id)"
print $"sessionize_session_id: ($r.sessionize_session_id)"
print $"related_mode: ($r.related_mode)"
print $"created: ($r.created)"
print $"updated: ($r.updated)"
render-links ($r.links? | default [])
print ""; print "next_action:"; print $" ($r.next_action)"
print ""; print "note:"; print $" ($r.note)"
}
export def "cfp update" [id: string, --stage: string] {
let valid = ["Watching" "Evaluating" "Drafting" "Submitted" "Accepted" "Declined" "Delivered"]
if ($stage | is-empty) { print --stderr "cfp update: --stage required"; exit 1 }
if not ($stage in $valid) { print --stderr $"cfp update: invalid stage '($stage)' — valid: ($valid | str join ', ')"; exit 1 }
let items = load-backlog | get cfp
if ($items | where id == $id | is-empty) { print --stderr $"cfp update: not found — ($id)"; exit 1 }
let today = (date now | format date "%Y-%m-%d")
let path = $"(project-root)/reflection/backlog.ncl"
let lines = open --raw $path | lines
let stage_idx = (ncl-block-field-idx $lines $id "stage")
let updated_idx = (ncl-block-field-idx $lines $id "updated")
let new_stage = ($lines | get $stage_idx | str replace --regex `'\w+` $"'($stage)")
let new_updated = ($lines | get $updated_idx | str replace --regex `"[0-9-]+"` $"\"($today)\"")
let patched = ($lines | enumerate | each { |l|
if $l.index == $stage_idx { $new_stage } else if $l.index == $updated_idx { $new_updated } else { $l.item }
} | str join "\n")
$patched | save --force $path
ncl-validate $path
print $"($id): stage → '($stage) | updated → ($today)"
}
# ─── help + main ──────────────────────────────────────────────────────────────
def show-help [] {
print "Personal Ontology — ontoref domain extension"
print ""
print "USAGE"
print " ore personal <command> [args]"
print ""
print "COMMANDS"
print " projects Project portfolio (Project-level nodes)"
print " projects show <id|idx> Full detail of a project node"
print " state Current FSM position across all dimensions"
print " next Next valid transition per dimension"
print " validate <decision> Check against ontological invariants"
print " audit Coherence audit: gaps in coverage"
print " career skills [--tier] Skills by proficiency"
print " career talks [--status] Talks filtered by status"
print " career publications [--featured] Publication cards"
print " career positioning Positioning strategies"
print " content [--status] Content pipeline"
print " opportunities [--status] Opportunities"
print " opportunities show <id> Full opportunity detail"
print " opportunities update <id> Update opportunity status"
print " cfp [--stage] CFP pipeline"
print " cfp show <id> Full CFP detail"
print " cfp update <id> --stage <s> Update CFP stage"
}
def main [
...args: string
--tier: string = ""
--status: string = ""
--stage: string = ""
--featured
] {
if ($args | is-empty) or ($args | first) == "help" { show-help; return }
let sub = ($args | str join " ")
match $sub {
"state" => { state }
"next" => { next }
"audit" => { audit }
"projects" => { projects }
"projects show" => { print --stderr "usage: personal projects show <id|idx>"; exit 1 }
_ if ($sub | str starts-with "projects show ") => { projects show ($args | get 2) }
_ if ($sub | str starts-with "projects ") => { projects show ($args | get 1) }
_ if ($sub | str starts-with "validate ") => { validate ($args | skip 1 | str join " ") }
"career skills" => { career skills --tier $tier }
"career talks" => { career talks --status $status }
"career publications" => { if $featured { career publications --featured } else { career publications } }
"career positioning" => { career positioning }
"content" => { content --status $status }
"opportunities" => { opportunities --status $status }
"opportunities show" => { print --stderr "usage: personal opportunities show <id|idx>"; exit 1 }
_ if ($sub | str starts-with "opportunities show ") => { opportunities show ($args | get 2) }
_ if ($sub | str starts-with "opportunities update ") => { opportunities update ($args | get 2) --status $status }
_ if ($sub | str starts-with "opportunities ") => { opportunities show ($args | get 1) }
"cfp" => { cfp --stage $stage }
"cfp show" => { print --stderr "usage: personal cfp show <id|idx>"; exit 1 }
_ if ($sub | str starts-with "cfp show ") => { cfp show ($args | get 2) }
_ if ($sub | str starts-with "cfp update ") => { cfp update ($args | get 2) --stage $stage }
_ if ($sub | str starts-with "cfp ") => { cfp show ($args | get 1) }
_ => { print $"Unknown command: ($sub)"; show-help }
}
}