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 {
|
2026-03-14 09:38:40 +00:00
|
|
|
let val = try {
|
|
|
|
|
input $" (ansi cyan)($arg.prompt):(ansi reset) "
|
|
|
|
|
} catch { |err|
|
|
|
|
|
if ($err.msg | str contains "interrupted") {
|
|
|
|
|
print $"\n (ansi yellow)cancelled(ansi reset)"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
error make { msg: $err.msg }
|
|
|
|
|
}
|
2026-03-13 00:21:04 +00:00
|
|
|
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."
|
|
|
|
|
}
|