ontoref/reflection/nulib/interactive.nu

219 lines
11 KiB
Plaintext
Raw Normal View History

2026-03-13 00:21:04 +00:00
# reflection/nulib/interactive.nu — Interactive command picker and group routing.
# Provides the "?" selector, missing-target handler, and run-group-command dispatch.
use ../modules/describe.nu *
use ../modules/config.nu *
use ../modules/sync.nu *
use ../modules/coder.nu *
use ../modules/manifest.nu *
use ../modules/backlog.nu *
use ../modules/adr.nu *
use ./help.nu [help-group]
export def group-command-info [group: string]: nothing -> list<record> {
match $group {
"describe" => [
{ name: project, desc: "philosophy, axioms, state, gates", args: [] },
{ name: capabilities, desc: "just modules, modes, commands, .claude", args: [] },
{ name: constraints, desc: "invariants, Hard constraints, gates", args: [] },
{ name: tools, desc: "dev tools, CI tools, just recipes", args: [] },
{ name: features, desc: "project features list or detail", args: [{name: "id", prompt: "feature id (empty=list all)", optional: true}] },
{ name: impact, desc: "if I change X, what's affected?", args: [{name: "node_id", prompt: "node id", optional: false}] },
{ name: why, desc: "why does X exist?", args: [{name: "id", prompt: "node/adr id", optional: false}] },
],
"backlog" => [
{ name: roadmap, desc: "state dimensions + open items by priority", args: [] },
{ name: list, desc: "open backlog items", args: [] },
{ name: show, desc: "show full backlog item", args: [{name: "id", prompt: "item id", optional: false}] },
{ name: add, desc: "create new backlog item", args: [{name: "title", prompt: "title", optional: false}] },
{ name: done, desc: "mark item as done", args: [{name: "id", prompt: "item id", optional: false}] },
{ name: cancel, desc: "cancel item", args: [{name: "id", prompt: "item id", optional: false}] },
{ name: promote, desc: "promote item priority", args: [{name: "id", prompt: "item id", optional: false}] },
],
"config" => [
{ name: show, desc: "display config profile", args: [{name: "profile", prompt: "profile name", optional: false}] },
{ name: history, desc: "config change history", args: [{name: "profile", prompt: "profile name", optional: false}] },
{ name: diff, desc: "diff between profiles or versions", args: [{name: "profile", prompt: "profile name", optional: false}, {name: "from_id", prompt: "from id", optional: false}, {name: "to_id", prompt: "to id", optional: false}] },
{ name: verify, desc: "verify profile integrity", args: [{name: "profile", prompt: "profile name", optional: false}] },
{ name: audit, desc: "full config audit", args: [] },
{ name: apply, desc: "apply config profile", args: [{name: "profile", prompt: "profile name", optional: false}] },
{ name: rollback, desc: "rollback to previous config", args: [{name: "profile", prompt: "profile name", optional: false}, {name: "to_id", prompt: "target id", optional: false}] },
],
"sync" => [
{ name: scan, desc: "analyze project artifacts", args: [] },
{ name: diff, desc: "compare scan vs ontology", args: [] },
{ name: propose, desc: "generate NCL patches for drift", args: [] },
{ name: apply, desc: "apply proposed changes with confirmation", args: [] },
{ name: state, desc: "sync state.ncl dimensions", args: [] },
{ name: audit, desc: "full ontology↔code audit", args: [] },
{ name: watch, desc: "continuous drift monitoring", args: [] },
],
"coder" => [
{ name: authors, desc: "list registered authors", args: [] },
{ name: init, desc: "initialize author workspace", args: [{name: "author", prompt: "author name", optional: false}] },
{ name: record, desc: "record structured entry", args: [{name: "author", prompt: "author name", optional: false}, {name: "title", prompt: "title", optional: false}, {name: "content", prompt: "content", optional: false}] },
{ name: log, desc: "query recorded entries", args: [] },
{ name: export, desc: "export entries (json/jsonl/csv)", args: [] },
{ name: triage, desc: "classify inbox markdown", args: [{name: "author", prompt: "author name", optional: false}] },
{ name: ls, desc: "list files in author workspace", args: [{name: "author", prompt: "author name (empty=all)", optional: true}] },
{ name: search, desc: "search across entries", args: [{name: "pattern", prompt: "search pattern", optional: false}] },
{ name: publish, desc: "promote entries to general/", args: [{name: "author", prompt: "author name", optional: false}, {name: "category", prompt: "category", optional: false}] },
{ name: graduate, desc: "copy to committed knowledge path", args: [{name: "source", prompt: "source category", optional: false}] },
],
"manifest" => [
{ name: mode, desc: "activate operational mode", args: [{name: "id", prompt: "mode id", optional: false}] },
{ name: publish, desc: "publish artifact to registry", args: [{name: "id", prompt: "service id", optional: false}] },
{ name: layers, desc: "show manifest layers", args: [] },
{ name: consumers, desc: "show consumption modes", args: [] },
],
"adr" => [
{ name: list, desc: "list all ADRs with status", args: [] },
{ name: validate, desc: "run Hard constraint checks", args: [] },
{ name: accept, desc: "Proposed → Accepted", args: [{name: "id", prompt: "adr id (e.g. adr-001)", optional: false}] },
{ name: show, desc: "show ADR detail", args: [{name: "id", prompt: "adr id (empty=interactive)", optional: true}] },
],
_ => [],
}
}
export def group-subcommands [group: string]: nothing -> list<string> {
group-command-info $group | get name | each { |n| $n | into string }
}
export def run-interactive [group: string] {
let cmds = (group-command-info $group)
if ($cmds | is-empty) {
help-group $group
return
}
let labels = ($cmds | each { |c|
let n = ($c.name | into string)
let pad_width = ([(16 - ($n | str length)), 0] | math max)
let pad = (" " | fill -w $pad_width -c ' ')
$"($n)($pad)($c.desc)"
})
let picked_label = ($labels | input list $"(ansi cyan_bold)($group):(ansi reset) ")
if ($picked_label | is-empty) { return }
let idx = ($labels | enumerate | where { |e| $e.item == $picked_label } | first | get index)
let cmd_info = ($cmds | get $idx)
let sub = ($cmd_info.name | into string)
print $" (ansi dark_gray)($cmd_info.desc)(ansi reset)"
mut collected_args: list<string> = []
for arg in $cmd_info.args {
let val = (input $" (ansi cyan)($arg.prompt):(ansi reset) ")
if ($val | is-empty) and (not $arg.optional) {
print $" (ansi yellow)required(ansi reset)"
return
}
$collected_args = ($collected_args | append $val)
}
print ""
let result = (run-group-command $group $sub $collected_args)
if ($result | describe) != "nothing" { print $result }
}
export def run-group-command [group: string, sub: string, args: list<string>] {
let a0 = ($args | get -o 0 | default "")
let a1 = ($args | get -o 1 | default "")
let a2 = ($args | get -o 2 | default "")
match $group {
"describe" => {
match $sub {
"project" => { describe project },
"capabilities" => { describe capabilities },
"constraints" => { describe constraints },
"tools" => { describe tools },
"features" => { if ($a0 | is-empty) { describe features } else { describe features $a0 } },
"impact" => { describe impact $a0 },
"why" => { describe why $a0 },
_ => {},
}
},
"backlog" => {
match $sub {
"roadmap" => { backlog roadmap },
"list" => { backlog list },
"show" => { backlog show $a0 },
"add" => { backlog add $a0 },
"done" => { backlog done $a0 },
"cancel" => { backlog cancel $a0 },
"promote" => { backlog promote $a0 },
_ => {},
}
},
"config" => {
match $sub {
"show" => { config show $a0 },
"history" => { config history $a0 },
"diff" => { config diff $a0 $a1 $a2 },
"verify" => { config verify $a0 },
"audit" => { config audit },
"apply" => { config apply $a0 },
"rollback" => { config rollback $a0 $a1 },
_ => {},
}
},
"sync" => {
match $sub {
"scan" => { sync scan },
"diff" => { sync diff },
"propose" => { sync propose },
"apply" => { sync apply },
"state" => { sync state },
"audit" => { sync audit },
"watch" => { sync watch },
_ => {},
}
},
"coder" => {
match $sub {
"authors" => { coder authors },
"init" => { coder init $a0 },
"record" => { coder record $a0 --title $a1 $a2 },
"log" => { coder log },
"export" => { coder export },
"triage" => { coder triage $a0 },
"ls" => { if ($a0 | is-empty) { coder ls } else { coder ls $a0 } },
"search" => { coder search $a0 },
"publish" => { coder publish $a0 $a1 },
"graduate" => { coder graduate $a0 },
_ => {},
}
},
"manifest" => {
match $sub {
"mode" => { manifest mode $a0 },
"publish" => { manifest publish $a0 },
"layers" => { manifest layers },
"consumers" => { manifest consumers },
_ => {},
}
},
"adr" => {
match $sub {
"list" => { adr list },
"validate" => { adr validate },
"accept" => { adr accept $a0 },
"show" => { if ($a0 | is-empty) { adr show --interactive } else { adr show $a0 } },
_ => {},
}
},
_ => {},
}
}
export def missing-target [group: string, action?: string] {
let act = ($action | default "")
if $act == "h" or $act == "help" {
help-group $group
return
}
if $act == "?" or $act == "select" or $act == "" {
run-interactive $group
return
}
let cmd = ($env.ONTOREF_CALLER? | default "./onref")
print $" (ansi yellow)($group)(ansi reset): unknown subcommand '($act)'. Run '(ansi green)($cmd) ($group) h(ansi reset)' for options."
}