ontoref-code/domains/personal/commands.nu
2026-07-10 01:44:59 +01:00

351 lines
18 KiB
Text
Raw 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)
}
# Resolves an ontoref section path against ADR-032 layout (canonical
# `.ontoref/<rel>`) falling back to legacy root-level layout for projects that
# have not yet applied migration 0023.
def resolve-onto-path [logical: string] {
let root = (project-root)
let canonical = $"($root)/.ontoref/($logical)"
if ($canonical | path exists) { return $canonical }
let legacy_rel = if ($logical | str starts-with "ontology/") {
$logical | str replace "ontology/" ".ontology/"
} else { $logical }
let legacy = $"($root)/($legacy_rel)"
if ($legacy | path exists) { return $legacy }
$canonical
}
# ─── Loaders ──────────────────────────────────────────────────────────────────
def load-core [] { ^nickel export (resolve-onto-path "ontology/core.ncl") | from json }
def load-state [] { ^nickel export (resolve-onto-path "ontology/state.ncl") | from json }
def load-career [] { ^nickel export (resolve-onto-path "ontology/career.ncl") | from json }
def load-personal [] { ^nickel export (resolve-onto-path "ontology/personal.ncl") | from json }
def load-backlog [] { ^nickel export (resolve-onto-path "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 = (resolve-onto-path "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 = (resolve-onto-path "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 }
}
}