337 lines
18 KiB
Text
337 lines
18 KiB
Text
|
|
#!/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 }
|
|||
|
|
}
|
|||
|
|
}
|