ontoref/reflection/bin/ontoref.nu

1443 lines
71 KiB
Plaintext
Raw Normal View History

2026-03-13 00:21:04 +00:00
#!/usr/bin/env nu
# ontoref dispatcher — all operations routed from here.
# Invoked by ontoref (alias: onref) after Nushell version is verified and ONTOREF_ACTOR is set.
#
# Business logic lives in reflection/modules/ (domain) and reflection/nulib/ (UI).
# This file only defines `def "main X"` subcommands as thin routing shims.
use ../modules/env.nu *
use ../modules/adr.nu *
use ../modules/forms.nu *
use ../modules/prereqs.nu *
use ../modules/register.nu *
use ../modules/backlog.nu *
use ../modules/config.nu *
use ../modules/sync.nu *
use ../modules/coder.nu *
use ../modules/manifest.nu *
use ../modules/describe.nu *
use ../modules/store.nu *
use ../modules/services.nu *
use ../modules/nats.nu *
use ../modules/opmode.nu *
2026-03-13 00:21:04 +00:00
use ../nulib/fmt.nu *
use ../nulib/shared.nu *
use ../nulib/help.nu [help-group]
use ../nulib/interactive.nu [missing-target, run-interactive]
use ../nulib/dashboard.nu [run-overview, run-health, run-status]
use ../nulib/modes.nu [list-modes, show-mode, run-modes-interactive, run-mode]
use ../nulib/logger.nu [log-action, log-record, log-show-config, log-query, log-follow]
# ── Helpers ───────────────────────────────────────────────────────────────────
def pick-mode []: nothing -> string {
let modes = (list-modes)
if ($modes | is-empty) { print " No modes found."; return "" }
# Check if stdin is a TTY via external test command.
let is_tty = (do { ^test -t 0 } | complete | get exit_code) == 0
if not $is_tty {
print ""
for m in $modes {
print $" ($m.id) (ansi dark_gray)($m.trigger)(ansi reset)"
}
print ""
print $" (ansi dark_gray)Usage: onref run <mode-id>(ansi reset)"
return ""
}
let ids = ($modes | each { |m| $"($m.id) (ansi dark_gray)($m.trigger)(ansi reset)" })
let picked = ($ids | input list $"(ansi cyan_bold)Run mode:(ansi reset) ")
if ($picked | is-empty) { return "" }
$picked | split row " " | first
}
# ── Entry ─────────────────────────────────────────────────────────────────────
def "main" [shortcut?: string] {
match ($shortcut | default "") {
"ru" => { main run },
"f" => { print "Usage: onref f <term>"; },
"h" | "help" | "-help" | "--help" => { main help },
"" => { show-usage-brief },
_ => { print $"Unknown command: ($shortcut). Run: onref help" },
}
}
def show-usage-brief [] {
let caller = ($env.ONTOREF_CALLER? | default "ontoref")
2026-03-13 00:21:04 +00:00
print $"\nUsage: ($caller) [command] [options]\n"
print $"Use '($caller) help' for available commands\n"
}
feat: config surface, NCL contracts, override-layer mutation, on+re update Config surface — per-project config introspection, coherence verification, and audited mutation without destroying NCL structure (ADR-008): - crates/ontoref-daemon/src/config.rs — typed DaemonNclConfig (parse-at-boundary pattern); all section structs derive ConfigFields + config_section(id, ncl_file) emitting inventory::submit!(ConfigFieldsEntry{...}) at link time - crates/ontoref-derive/src/lib.rs — #[derive(ConfigFields)] proc-macro; serde rename support; serde_rename_of() helper extracted to fix excessive_nesting - crates/ontoref-daemon/src/main.rs — 3-tuple bootstrap block (nickel_import_path, loaded_ncl_config: Option<DaemonNclConfig>, stdin_raw); apply_ui_config takes &UiConfig; NATS call site typed; resolve_asset_dir cfg(feature = "ui") - crates/ontoref-daemon/src/api.rs — config GET/PUT endpoints, quickref, coherence, cross-project comparison; index_section_fields() extracted (excessive_nesting) - crates/ontoref-daemon/src/config_coherence.rs — multi-consumer coherence; merge_meta_into_section() extracted; and() replaces unnecessary and_then NCL contracts for ontoref's own config: - .ontoref/contracts.ncl — LogConfig (LogLevel, LogRotation, PositiveInt) and DaemonConfig (Port, optional overrides); std.contract.from_validator throughout - .ontoref/config.ncl — log | C.LogConfig applied - .ontology/manifest.ncl — contracts_path, log/daemon contract refs, daemon section with DaemonRuntimeConfig consumer and 7 declared fields Protocol: - adrs/adr-008-ncl-first-config-validation-and-override-layer.ncl — NCL contracts as single validation gate; Rust structs are contract-trusted; override-layer mutation writes {section}.overrides.ncl + _overrides_meta, never touches source on+re update: - .ontology/core.ncl — config-surface node (28 practices); adr-lifecycle extended to adr-007 + adr-008; 6 new edges (ManifestsIn daemon, DependsOn ontology-crate, Complements api-catalog-surface/dag-formalized/self-describing/adopt-ontoref) - .ontology/state.ncl — protocol-maturity blocker and self-description-coverage catalyst updated for session 2026-03-26 - README.md / CHANGELOG.md updated
2026-03-26 20:20:22 +00:00
def "main help" [...args: string] {
# The bash wrapper rewrites `ore config show --help` → `main help config show`.
# When multiple tokens arrive, show the specific subcommand's Nushell help.
let group = ($args | first | default "")
2026-03-13 00:21:04 +00:00
if ($group | is-not-empty) {
feat: config surface, NCL contracts, override-layer mutation, on+re update Config surface — per-project config introspection, coherence verification, and audited mutation without destroying NCL structure (ADR-008): - crates/ontoref-daemon/src/config.rs — typed DaemonNclConfig (parse-at-boundary pattern); all section structs derive ConfigFields + config_section(id, ncl_file) emitting inventory::submit!(ConfigFieldsEntry{...}) at link time - crates/ontoref-derive/src/lib.rs — #[derive(ConfigFields)] proc-macro; serde rename support; serde_rename_of() helper extracted to fix excessive_nesting - crates/ontoref-daemon/src/main.rs — 3-tuple bootstrap block (nickel_import_path, loaded_ncl_config: Option<DaemonNclConfig>, stdin_raw); apply_ui_config takes &UiConfig; NATS call site typed; resolve_asset_dir cfg(feature = "ui") - crates/ontoref-daemon/src/api.rs — config GET/PUT endpoints, quickref, coherence, cross-project comparison; index_section_fields() extracted (excessive_nesting) - crates/ontoref-daemon/src/config_coherence.rs — multi-consumer coherence; merge_meta_into_section() extracted; and() replaces unnecessary and_then NCL contracts for ontoref's own config: - .ontoref/contracts.ncl — LogConfig (LogLevel, LogRotation, PositiveInt) and DaemonConfig (Port, optional overrides); std.contract.from_validator throughout - .ontoref/config.ncl — log | C.LogConfig applied - .ontology/manifest.ncl — contracts_path, log/daemon contract refs, daemon section with DaemonRuntimeConfig consumer and 7 declared fields Protocol: - adrs/adr-008-ncl-first-config-validation-and-override-layer.ncl — NCL contracts as single validation gate; Rust structs are contract-trusted; override-layer mutation writes {section}.overrides.ncl + _overrides_meta, never touches source on+re update: - .ontology/core.ncl — config-surface node (28 practices); adr-lifecycle extended to adr-007 + adr-008; 6 new edges (ManifestsIn daemon, DependsOn ontology-crate, Complements api-catalog-surface/dag-formalized/self-describing/adopt-ontoref) - .ontology/state.ncl — protocol-maturity blocker and self-description-coverage catalyst updated for session 2026-03-26 - README.md / CHANGELOG.md updated
2026-03-26 20:20:22 +00:00
if ($args | length) > 1 {
let subcmd = ($args | str join " ")
let cmd_name = $"main ($subcmd)"
let found = (scope commands | where name == $cmd_name | length) > 0
if $found {
let found_cmd = (scope commands | where name == $cmd_name | first)
let sig = ($found_cmd.signatures | values | first)
let pos = ($sig | where parameter_type == 'positional')
let named = ($sig | where parameter_type == 'named')
let pos_str = ($pos | get parameter_name | str join " ")
print $"Usage: ($cmd_name) [flags] ($pos_str)\n"
if ($found_cmd.description | is-not-empty) { print $found_cmd.description }
if ($found_cmd.extra_description | is-not-empty) { print $"\n($found_cmd.extra_description)" }
if ($named | length) > 0 {
print "\nFlags:"
for p in $named {
let flag = if ($p.short_flag | is-not-empty) {
$"-($p.short_flag), --($p.parameter_name)"
} else {
$" --($p.parameter_name)"
}
print $" ($flag) ($p.description)"
}
}
if ($pos | length) > 0 {
print "\nParameters:"
for p in $pos {
print $" ($p.parameter_name) <($p.syntax_shape)> ($p.description)"
}
}
return
}
# Unknown sub-path: fall through to group help.
}
2026-03-13 00:21:04 +00:00
help-group $group
return
}
let actor = ($env.ONTOREF_ACTOR? | default "developer")
let cmd = ($env.ONTOREF_CALLER? | default "ontoref")
2026-03-13 00:21:04 +00:00
let brief = adrs-brief
let adr_status = $"($brief.accepted)A/($brief.superseded)S/($brief.proposed)P"
print ""
fmt-header "ontoref — ecosystem patterns and tooling"
fmt-sep
print $"(ansi white_bold)Actor(ansi reset): (ansi cyan)($actor)(ansi reset) | (ansi white_bold)Root(ansi reset): (ansi cyan)($env.ONTOREF_ROOT)(ansi reset)"
print ""
fmt-section "COMMAND GROUPS"
print ""
fmt-cmd $"($cmd) help check" "prerequisites and environment checks"
fmt-cmd $"($cmd) help form" "interactive forms (new_adr, register, etc.)"
fmt-cmd $"($cmd) help mode" "operational modes (inspect + execute)"
fmt-cmd $"($cmd) help adr" $"ADR management (fmt-badge $"($adr_status)")"
fmt-cmd $"($cmd) help register" "record changes → CHANGELOG + ADR + ontology"
fmt-cmd $"($cmd) help backlog" "roadmap, items, promotions"
fmt-cmd $"($cmd) help config" "sealed config profiles, drift, rollback"
fmt-cmd $"($cmd) help sync" "ontology↔code sync, drift detection, proposals"
fmt-cmd $"($cmd) help coder" ".coder/ process memory: record, log, triage, publish"
fmt-cmd $"($cmd) help manifest" "operational modes, publication services, layers"
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
fmt-cmd $"($cmd) help describe" "project self-knowledge: what, how, why, impact, diff, api surface"
fmt-cmd $"($cmd) help search" "ontology search + bookmarks (NCL-persisted)"
fmt-cmd $"($cmd) help qa" "Q&A knowledge base: query, add, list"
2026-03-13 00:21:04 +00:00
fmt-cmd $"($cmd) help log" "action audit trail, follow, filter"
print ""
fmt-section "QUICK REFERENCE"
print ""
fmt-cmd $"($cmd) init" "run actor-configured init mode (from actor_init in config)"
fmt-cmd $"($cmd) run <mode-id>" "execute a mode (shortcut for mode run)"
fmt-cmd $"($cmd) s <term>" "search ontology nodes, ADRs, modes (--fmt <fmt> --clip)"
fmt-cmd $"($cmd) q <term>" "query QA entries (word-overlap score, ontology fallback) (--fmt --clip)"
fmt-cmd $"($cmd) qs <term>" "QA-first then ontology | sq: ontology-first then QA"
2026-03-13 00:21:04 +00:00
fmt-cmd $"($cmd) about" "project identity and summary"
fmt-cmd $"($cmd) diagram" "terminal box diagram of project architecture"
fmt-cmd $"($cmd) overview" "single-screen project snapshot: identity, crates, health"
fmt-cmd $"($cmd) health" "quick health bar (use --full for deep audit)"
fmt-cmd $"($cmd) status" "project dashboard: health, state, recent activity"
fmt-cmd $"($cmd) nats" "NATS event system (status, listen, emit)"
fmt-cmd $"($cmd) services" "daemon lifecycle (start, stop, restart, status, health)"
fmt-cmd $"($cmd) check" "run prerequisite checks"
fmt-cmd $"($cmd) adr list" "list ADRs with status"
fmt-cmd $"($cmd) constraint" "active Hard constraints"
fmt-cmd $"($cmd) backlog roadmap" "state dimensions + open items"
fmt-cmd $"($cmd) config audit" "verify all profiles"
fmt-cmd $"($cmd) log --tail 10 -t" "last 10 actions with timestamps"
fmt-cmd $"($cmd) setup" "onboard project: detect mode, install git hooks, initial sync"
fmt-cmd $"($cmd) mode-status" "show operational mode (local/daemon) — read-only"
fmt-cmd $"($cmd) mode-detect" "detect mode + trigger transition if changed"
fmt-cmd $"($cmd) store sync-push" "push ontology to daemon DB (projection rebuild)"
fmt-cmd $"($cmd) config-edit" "edit ~/.config/ontoref/config.ncl via browser form (typedialog roundtrip)"
fmt-cmd $"($cmd) config-setup" "validate config.ncl schema and probe external services"
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
fmt-cmd $"($cmd) describe diff [--file]" "semantic diff of ontology vs HEAD (nodes/edges added/removed/changed)"
fmt-cmd $"($cmd) describe api [--actor] [--tag]" "annotated API surface grouped by tag (requires daemon)"
fmt-cmd $"($cmd) run update_ontoref" "bring project up to current protocol version (adds manifest.ncl, connections.ncl)"
2026-03-13 00:21:04 +00:00
print ""
fmt-section "ALIASES"
print ""
print $" (ansi cyan)ad(ansi reset) → adr (ansi cyan)d(ansi reset) → describe (ansi cyan)ck(ansi reset) → check (ansi cyan)con(ansi reset) → constraint"
print $" (ansi cyan)rg(ansi reset) → register (ansi cyan)bkl(ansi reset) → backlog (ansi cyan)cfg(ansi reset) → config (ansi cyan)cod(ansi reset) → coder"
print $" (ansi cyan)mf(ansi reset) → manifest (ansi cyan)dg(ansi reset) → diagram (ansi cyan)md(ansi reset) → mode (ansi cyan)st(ansi reset) → status"
print $" (ansi cyan)fm(ansi reset) → form (ansi cyan)s(ansi reset) → search (ansi cyan)ru(ansi reset) → run \(mode\) (ansi cyan)sv(ansi reset) → services"
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
print $" (ansi cyan)nv(ansi reset) → nats (ansi cyan)q(ansi reset) → qa query (ansi cyan)f(ansi reset) → search \(alias\) (ansi cyan)df(ansi reset) → describe diff"
print $" (ansi cyan)da(ansi reset) → describe api"
2026-03-13 00:21:04 +00:00
print ""
print $" (ansi dark_gray)Tip: any group accepts(ansi reset) (ansi cyan)h(ansi reset) (ansi dark_gray)for help,(ansi reset) (ansi cyan)?(ansi reset) (ansi dark_gray)for interactive selector, or bare for picker(ansi reset)"
print $" (ansi dark_gray)Any command:(ansi reset) (ansi cyan)--fmt|-f(ansi reset) (ansi dark_gray)text*|json|yaml|toml|md(ansi reset) · (ansi cyan)--clip(ansi reset) (ansi dark_gray)copy output to clipboard(ansi reset)"
2026-03-13 00:21:04 +00:00
print ""
}
# ── Check / Forms ─────────────────────────────────────────────────────────────
def "main check" [--context: string = "", --form: string = "", --severity: string = "", --json] {
log-action "check" "read"
if $json {
prereqs check --context $context --form $form --severity $severity --json
} else {
prereqs check --context $context --form $form --severity $severity
}
}
def "main form" [action?: string] { missing-target "form" $action }
def "main form help" [] { help-group "form" }
def "main form run" [name: string, --backend: string = "cli"] {
log-action $"form run ($name)" "interactive"
form run $name --backend $backend
}
def "main form list" [] {
log-action "form list" "read"
print ""
for f in (forms list) {
print $" ($f.name)"
print $" ($f.description)"
print $" Agent: nickel-export reflection/forms/($f.name).ncl | get elements | where type != \"section_header\""
print ""
}
}
def "main form ls" [] { log-action "form list" "read"; forms list }
# ── Modes ─────────────────────────────────────────────────────────────────────
def "main mode" [action?: string] { missing-target "mode" $action }
def "main mode help" [] { help-group "mode" }
def "main mode list" [--fmt (-f): string = ""] {
let modes = (list-modes)
log-action "mode list" "read"
let f = (resolve-fmt $fmt [text table json yaml toml])
match $f {
"json" => { print ($modes | to json) },
"yaml" => { print ($modes | to yaml) },
"toml" => { print ({ modes: $modes } | to toml) },
"table" => { print ($modes | table --expand) },
_ => {
print ""
for m in $modes {
print $" ($m.id)"
print $" Trigger: ($m.trigger)"
if $m.steps > 0 { print $" Steps: ($m.steps)" }
print ""
}
},
}
}
def "main mode select" [] {
let modes = (list-modes)
run-modes-interactive $modes
}
def "main mode show" [id: string, --fmt (-f): string = ""] {
log-action $"mode show ($id)" "read"
let actor = ($env.ONTOREF_ACTOR? | default "developer")
let f = if ($fmt | is-not-empty) { $fmt } else if $actor == "agent" { "json" } else { "md" }
show-mode $id $f
}
def "main mode run" [id: string, --dry-run (-n), --yes (-y)] {
log-action $"mode run ($id)" "write"
opmode preflight "service" # modes may use daemon; degrade gracefully if unavailable
2026-03-13 00:21:04 +00:00
run-mode $id --dry-run=$dry_run --yes=$yes
}
# ── ADR ───────────────────────────────────────────────────────────────────────
def "main adr" [action?: string] { missing-target "adr" $action }
def "main adr help" [] { help-group "adr" }
def "main adr list" [--fmt (-f): string = ""] {
log-action "adr list" "read"
let f = (resolve-fmt $fmt [table md json yaml toml])
adr list --fmt $f
}
def "main adr validate" [] { log-action "adr validate" "read"; adr validate }
def "main adr accept" [id: string] { log-action $"adr accept ($id)" "write"; adr accept $id }
def "main adr show" [id?: string, --interactive (-i), --fmt (-f): string = ""] {
log-action $"adr show ($id | default '?')" "read"
let f = (resolve-fmt $fmt [md table json yaml toml])
if $interactive { adr show --interactive --fmt $f } else { adr show $id --fmt $f }
}
def "main adr l" [--fmt (-f): string = ""] { main adr list --fmt $fmt }
def "main adr v" [] { main adr validate }
def "main adr s" [id?: string, --fmt (-f): string = ""] { main adr show $id --fmt $fmt }
# ── Constraints / Register ───────────────────────────────────────────────────
def "main constraint" [--fmt (-f): string = ""] {
log-action "constraint" "read"
let f = (resolve-fmt $fmt [table md json yaml toml])
constraints --fmt $f
}
def "main register" [--backend: string = "cli"] { log-action "register" "write"; register run --backend $backend }
# ── Backlog ───────────────────────────────────────────────────────────────────
def "main backlog" [action?: string] { missing-target "backlog" $action }
def "main backlog help" [] { help-group "backlog" }
def "main backlog roadmap" [] { log-action "backlog roadmap" "read"; backlog roadmap }
def "main backlog list" [--status: string = "", --kind: string = "", --fmt (-f): string = ""] {
log-action "backlog list" "read"
let f = (resolve-fmt $fmt [table md json yaml toml])
backlog list --status $status --kind $kind --fmt $f
}
def "main backlog show" [id: string] { log-action $"backlog show ($id)" "read"; backlog show $id }
def "main backlog add" [title: string, --kind: string = "Todo", --priority: string = "Medium", --detail: string = "", --dim: string = "", --adr: string = "", --mode: string = ""] {
log-action $"backlog add ($title)" "write"
backlog add $title --kind $kind --priority $priority --detail $detail --dim $dim --adr $adr --mode $mode
}
def "main backlog done" [id: string] { log-action $"backlog done ($id)" "write"; backlog done $id }
def "main backlog cancel" [id: string] { log-action $"backlog cancel ($id)" "write"; backlog cancel $id }
def "main backlog promote" [id: string] { log-action $"backlog promote ($id)" "write"; backlog promote $id }
# ── Config ────────────────────────────────────────────────────────────────────
def "main config" [action?: string] { missing-target "config" $action }
def "main config help" [] { help-group "config" }
def "main config show" [profile: string, --fmt (-f): string = ""] {
log-action $"config show ($profile)" "read"
let f = (resolve-fmt $fmt [table json yaml toml])
config show $profile --fmt $f
}
def "main config history" [profile: string, --fmt (-f): string = ""] {
log-action $"config history ($profile)" "read"
let f = (resolve-fmt $fmt [table json yaml toml])
config history $profile --fmt $f
}
def "main config diff" [profile: string, from_id: string, to_id: string] { log-action $"config diff ($profile)" "read"; config diff $profile $from_id $to_id }
def "main config verify" [profile: string] { log-action $"config verify ($profile)" "read"; config verify $profile }
def "main config audit" [] { log-action "config audit" "read"; config audit }
def "main config apply" [profile: string, --adr: string = "", --pr: string = "", --bug: string = "", --note: string = ""] {
log-action $"config apply ($profile)" "write"
config apply $profile --adr $adr --pr $pr --bug $bug --note $note
}
def "main config rollback" [profile: string, to_id: string, --adr: string = "", --note: string = ""] {
log-action $"config rollback ($profile) ($to_id)" "write"
config rollback $profile $to_id --adr $adr --note $note
}
# ── Sync ──────────────────────────────────────────────────────────────────────
def "main sync" [action?: string] { missing-target "sync" $action }
def "main sync help" [] { help-group "sync" }
def "main sync scan" [--level: string = "auto"] {
log-action "sync scan" "read"
opmode preflight "local"
sync scan --level $level
}
def "main sync diff" [--quick] {
log-action "sync diff" "read"
opmode preflight "local"
if $quick { sync diff --quick } else { sync diff }
}
def "main sync propose" [] {
log-action "sync propose" "read"
opmode preflight "local"
sync propose
}
def "main sync apply" [] {
log-action "sync apply" "write"
opmode preflight "local" # sync apply writes only to local files — no service needed
sync apply
}
2026-03-13 00:21:04 +00:00
def "main sync state" [] { log-action "sync state" "read"; sync state }
def "main sync audit" [--fmt (-f): string = "", --strict, --quick] {
log-action "sync audit" "read"
let f = (resolve-fmt $fmt [text json])
sync audit --fmt $f --strict=$strict --quick=$quick
}
def "main sync watch" [] { log-action "sync watch" "read"; sync watch }
# ── Coder ─────────────────────────────────────────────────────────────────────
def "main coder" [action?: string] { missing-target "coder" $action }
def "main coder help" [] { help-group "coder" }
def "main coder authors" [] { log-action "coder authors" "read"; coder authors }
def "main coder init" [author: string, --actor: string = "Human", --model: string = ""] {
log-action $"coder init ($author)" "write"
coder init $author --actor $actor --model $model
}
def "main coder record" [
author: string, content: string,
--kind (-k): string = "info", --category (-c): string = "", --title (-t): string,
--tags: list<string> = [], --relates_to: list<string> = [],
--trigger: string = "", --files_touched: list<string> = [],
--domain (-d): string = "", --reusable (-r),
] {
log-action $"coder record ($author) ($title)" "write"
coder record $author $content --kind $kind --category $category --title $title --tags $tags --relates_to $relates_to --trigger $trigger --files_touched $files_touched --domain $domain --reusable=$reusable
}
def "main coder log" [--author (-a): string = "", --category (-c): string = "", --tag (-t): string = "", --kind (-k): string = "", --domain (-d): string = "", --limit (-l): int = 0] {
log-action "coder log" "read"
coder log --author $author --category $category --tag $tag --kind $kind --domain $domain --limit $limit
}
def "main coder export" [--author (-a): string = "", --category (-c): string = "", --format (-f): string = "json"] {
log-action "coder export" "read"
let f = (resolve-fmt $format [json jsonl csv])
coder export --author $author --category $category --format $f
}
def "main coder triage" [author: string, --dry-run (-n), --interactive (-i)] {
log-action $"coder triage ($author)" "write"
coder triage $author --dry-run=$dry_run --interactive=$interactive
}
def "main coder ls" [author: string = "", --category (-c): string = ""] { log-action "coder ls" "read"; coder ls $author --category $category }
def "main coder search" [pattern: string, --author (-a): string = ""] { log-action $"coder search ($pattern)" "read"; coder search $pattern --author $author }
def "main coder publish" [author: string, category: string, --dry-run (-n), --all (-a)] {
log-action $"coder publish ($author) ($category)" "write"
coder publish $author $category --dry-run=$dry_run --all=$all
}
def "main coder graduate" [source_category: string, --target (-t): string = "reflection/knowledge", --dry-run (-n)] {
log-action $"coder graduate ($source_category)" "write"
coder graduate $source_category --target $target --dry-run=$dry_run
}
# ── Manifest ──────────────────────────────────────────────────────────────────
def "main manifest" [action?: string] { missing-target "manifest" $action }
def "main manifest help" [] { help-group "manifest" }
def "main manifest mode" [id: string, --dry-run (-n)] { log-action $"manifest mode ($id)" "read"; manifest mode $id --dry-run=$dry_run }
def "main manifest mode list" [--fmt (-f): string = "table"] {
log-action "manifest mode list" "read"
let f = (resolve-fmt $fmt [table json yaml toml])
manifest mode list --fmt $f
}
def "main manifest publish" [id: string, --dry-run (-n), --yes (-y)] {
log-action $"manifest publish ($id)" "write"
manifest publish $id --dry-run=$dry_run --yes=$yes
}
def "main manifest publish list" [--fmt (-f): string = "table"] {
log-action "manifest publish list" "read"
let f = (resolve-fmt $fmt [table json yaml toml])
manifest publish list --fmt $f
}
def "main manifest layers" [--mode (-m): string = ""] { log-action "manifest layers" "read"; manifest layers --mode $mode }
def "main manifest consumers" [--fmt (-f): string = "table"] {
log-action "manifest consumers" "read"
let f = (resolve-fmt $fmt [table json yaml toml])
manifest consumers --fmt $f
}
# ── Describe ──────────────────────────────────────────────────────────────────
def "main describe" [action?: string] { missing-target "describe" $action }
def "main describe help" [] { help-group "describe" }
def "main describe project" [--fmt (-f): string = "", --actor: string = ""] {
log-action "describe project" "read"
let f = (resolve-fmt $fmt [text table json yaml toml]); describe project --fmt $f --actor $actor
}
def "main describe capabilities" [--fmt (-f): string = "", --actor: string = ""] {
log-action "describe capabilities" "read"
let f = (resolve-fmt $fmt [text table json yaml toml]); describe capabilities --fmt $f --actor $actor
}
def "main describe constraints" [--fmt (-f): string = "", --actor: string = ""] {
log-action "describe constraints" "read"
let f = (resolve-fmt $fmt [text table json yaml toml]); describe constraints --fmt $f --actor $actor
}
def "main describe tools" [--fmt (-f): string = "", --actor: string = ""] {
log-action "describe tools" "read"
let f = (resolve-fmt $fmt [text table json yaml toml]); describe tools --fmt $f --actor $actor
}
def "main describe impact" [node_id: string, --depth: int = 2, --fmt (-f): string = ""] {
log-action $"describe impact ($node_id)" "read"
let f = (resolve-fmt $fmt [text table json yaml toml]); describe impact $node_id --depth $depth --fmt $f
}
def "main describe why" [id: string, --fmt (-f): string = ""] {
log-action $"describe why ($id)" "read"
let f = (resolve-fmt $fmt [text table json yaml toml]); describe why $id --fmt $f
}
def "main describe search" [...words: string, --level: string = "", --fmt (-f): string = "", --clip] {
let term = ($words | str join ' ')
log-action $"describe search ($term)" "read"
describe search $term --level $level --fmt $fmt --clip=$clip
}
def "main describe find" [...words: string, --level: string = "", --fmt (-f): string = "", --clip] {
let term = ($words | str join ' ')
main describe search $term --level $level --fmt $fmt --clip=$clip
2026-03-13 00:21:04 +00:00
}
def "main describe features" [id?: string, --fmt (-f): string = "", --actor: string = ""] {
log-action $"describe features ($id | default '')" "read"
let f = (resolve-fmt $fmt [text table json yaml toml])
if ($id | is-empty) or ($id == "") { describe features --fmt $f --actor $actor } else { describe features $id --fmt $f --actor $actor }
}
def "main describe connections" [--fmt (-f): string = "", --actor: string = ""] {
log-action "describe connections" "read"
let f = (resolve-fmt $fmt [text json])
describe connections --fmt $f --actor $actor
}
def "main describe extensions" [--fmt (-f): string = "", --actor: string = "", --dump: string = "", --clip] {
log-action "describe extensions" "read"
let f = (resolve-fmt $fmt [text json md])
describe extensions --fmt $f --actor $actor --dump $dump --clip=$clip
}
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
def "main describe diff" [--fmt (-f): string = "", --file: string = ""] {
log-action "describe diff" "read"
let f = (resolve-fmt $fmt [text json])
describe diff --fmt $f --file $file
}
def "main describe api" [--actor: string = "", --tag: string = "", --auth: string = "", --fmt (-f): string = ""] {
log-action "describe api" "read"
let f = (resolve-fmt $fmt [text json])
describe api --actor $actor --tag $tag --auth $auth --fmt $f
}
2026-03-13 00:21:04 +00:00
# ── Diagram ───────────────────────────────────────────────────────────────────
def "main diagram" [] {
log-action "diagram" "read"
let root = (project-root)
let project_diagram = $"($root)/assets/main-diagram.md"
let onref_diagram = $"($env.ONTOREF_ROOT)/assets/main-diagram.md"
let diagram_file = if ($project_diagram | path exists) {
$project_diagram
} else if ($onref_diagram | path exists) {
$onref_diagram
} else {
print " Diagram not found"
return
}
let content = (open $diagram_file --raw)
let stripped = ($content | lines | where { |l| not ($l | str starts-with "```") } | str join "\n")
print $stripped
}
# ── About ─────────────────────────────────────────────────────────────────────
def "main about" [] {
log-action "about" "read"
let root = (project-root)
let name = ($root | path basename)
let ascii_file = $"($root)/assets/($name)_ascii.txt"
if ($ascii_file | path exists) {
let ascii = (open $ascii_file --raw)
print ""
print $ascii
}
let project_about = $"($root)/assets/about.md"
let onref_about = $"($env.ONTOREF_ROOT)/assets/about.md"
let about_file = if ($project_about | path exists) {
$project_about
} else if ($onref_about | path exists) {
$onref_about
} else {
print " About not found"
return
}
let content = (open $about_file --raw)
print $content
}
# ── Overview / Health / Status ────────────────────────────────────────────────
def "main overview" [--fmt (-f): string = ""] {
log-action "overview" "read"
let f = (resolve-fmt $fmt [text table json yaml toml])
run-overview $f
}
def "main health" [--fmt (-f): string = "", --full] {
log-action "health" "read"
let f = (resolve-fmt $fmt [text table json yaml toml])
run-health $f $full
}
def "main status" [--fmt (-f): string = ""] {
log-action "status" "read"
let f = (resolve-fmt $fmt [text table json yaml toml])
run-status $f
}
# ── Log ──────────────────────────────────────────────────────────────────────
def "main log" [
action?: string,
--follow (-f),
--latest (-l),
--since: string = "",
--until: string = "",
--tail: int = -1,
--timestamps (-t),
--level: string = "",
--actor: string = "",
--query (-q): list<string> = [],
--fmt: string = "text",
] {
let act = ($action | default "")
if $act == "h" or $act == "help" {
help-group "log"
return
}
if $act == "config" {
log-show-config
return
}
if $follow {
log-follow --timestamps=$timestamps --query $query
return
}
log-query --tail_n $tail --since $since --until $until --latest=$latest --timestamps=$timestamps --level $level --actor $actor --query $query --fmt $fmt
}
def "main log record" [
action: string,
--level (-l): string = "write",
--author (-a): string = "",
--actor: string = "",
] {
log-record $action --level $level --author $author --actor $actor
}
# ── Aliases ───────────────────────────────────────────────────────────────────
# All aliases delegate to canonical commands → single log-action call site.
# ad=adr, d=describe, ck=check, con=constraint, rg=register, f=find, ru=run,
# bkl=backlog, cfg=config, cod=coder, mf=manifest, dg=diagram, md=mode, fm=form, st=status, h=help
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
# df=describe diff, da=describe api
2026-03-13 00:21:04 +00:00
def "main ad" [action?: string] { main adr $action }
def "main ad help" [] { help-group "adr" }
def "main ad list" [--fmt (-f): string = ""] { main adr list --fmt $fmt }
def "main ad l" [--fmt (-f): string = ""] { main adr list --fmt $fmt }
def "main ad validate" [] { main adr validate }
def "main ad accept" [id: string] { main adr accept $id }
def "main ad a" [id: string] { main adr accept $id }
def "main ad show" [id?: string, --interactive (-i), --fmt (-f): string = ""] { main adr show $id --interactive=$interactive --fmt $fmt }
def "main ad s" [id?: string, --interactive (-i), --fmt (-f): string = ""] { main adr show $id --interactive=$interactive --fmt $fmt }
def "main ck" [--context: string = "", --form: string = "", --severity: string = "", --json] { main check --context $context --form $form --severity $severity --json=$json }
def "main con" [--fmt (-f): string = ""] { main constraint --fmt $fmt }
def "main rg" [--backend: string = "cli"] { main register --backend $backend }
def "main d" [action?: string] { main describe $action }
def "main d help" [] { help-group "describe" }
def "main d project" [--fmt (-f): string = "", --actor: string = ""] { main describe project --fmt $fmt --actor $actor }
def "main d p" [--fmt (-f): string = "", --actor: string = ""] { main describe project --fmt $fmt --actor $actor }
def "main d capabilities" [--fmt (-f): string = "", --actor: string = ""] { main describe capabilities --fmt $fmt --actor $actor }
def "main d cap" [--fmt (-f): string = "", --actor: string = ""] { main describe capabilities --fmt $fmt --actor $actor }
def "main d constraints" [--fmt (-f): string = "", --actor: string = ""] { main describe constraints --fmt $fmt --actor $actor }
def "main d con" [--fmt (-f): string = "", --actor: string = ""] { main describe constraints --fmt $fmt --actor $actor }
def "main d tools" [--fmt (-f): string = "", --actor: string = ""] { main describe tools --fmt $fmt --actor $actor }
def "main d t" [--fmt (-f): string = "", --actor: string = ""] { main describe tools --fmt $fmt --actor $actor }
def "main d tls" [--fmt (-f): string = "", --actor: string = ""] { main describe tools --fmt $fmt --actor $actor }
def "main d search" [...words: string, --level: string = "", --fmt (-f): string = "", --clip] { main describe search ...($words) --level $level --fmt $fmt --clip=$clip }
def "main d s" [...words: string, --level: string = "", --fmt (-f): string = "", --clip] { main describe search ...($words) --level $level --fmt $fmt --clip=$clip }
def "main d find" [...words: string, --level: string = "", --fmt (-f): string = "", --clip] { main describe search ...($words) --level $level --fmt $fmt --clip=$clip }
def "main d fi" [...words: string, --level: string = "", --fmt (-f): string = "", --clip] { main describe search ...($words) --level $level --fmt $fmt --clip=$clip }
2026-03-13 00:21:04 +00:00
def "main d features" [id?: string, --fmt (-f): string = "", --actor: string = ""] { main describe features $id --fmt $fmt --actor $actor }
def "main d fea" [id?: string, --fmt (-f): string = "", --actor: string = ""] { main describe features $id --fmt $fmt --actor $actor }
def "main d f" [id?: string, --fmt (-f): string = "", --actor: string = ""] { main describe features $id --fmt $fmt --actor $actor }
def "main d impact" [node_id: string, --depth: int = 2, --fmt (-f): string = ""] { main describe impact $node_id --depth $depth --fmt $fmt }
def "main d i" [node_id: string, --depth: int = 2, --fmt (-f): string = ""] { main describe impact $node_id --depth $depth --fmt $fmt }
def "main d imp" [node_id: string, --depth: int = 2, --fmt (-f): string = ""] { main describe impact $node_id --depth $depth --fmt $fmt }
def "main d why" [id: string, --fmt (-f): string = ""] { main describe why $id --fmt $fmt }
def "main d w" [id: string, --fmt (-f): string = ""] { main describe why $id --fmt $fmt }
def "main d connections" [--fmt (-f): string = "", --actor: string = ""] { main describe connections --fmt $fmt --actor $actor }
def "main d conn" [--fmt (-f): string = "", --actor: string = ""] { main describe connections --fmt $fmt --actor $actor }
def "main d extensions" [--fmt (-f): string = "", --actor: string = "", --dump: string = "", --clip] { main describe extensions --fmt $fmt --actor $actor --dump $dump --clip=$clip }
def "main d ext" [--fmt (-f): string = "", --actor: string = "", --dump: string = "", --clip] { main describe extensions --fmt $fmt --actor $actor --dump $dump --clip=$clip }
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
def "main d diff" [--fmt (-f): string = "", --file: string = ""] { main describe diff --fmt $fmt --file $file }
def "main d api" [--actor: string = "", --tag: string = "", --auth: string = "", --fmt (-f): string = ""] { main describe api --actor $actor --tag $tag --auth $auth --fmt $fmt }
def "main df" [--fmt (-f): string = "", --file: string = ""] { main describe diff --fmt $fmt --file $file }
def "main da" [--actor: string = "", --tag: string = "", --auth: string = "", --fmt (-f): string = ""] { main describe api --actor $actor --tag $tag --auth $auth --fmt $fmt }
2026-03-13 00:21:04 +00:00
def "main bkl" [action?: string] { main backlog $action }
def "main bkl help" [] { help-group "backlog" }
def "main bkl roadmap" [] { main backlog roadmap }
def "main bkl r" [] { main backlog roadmap }
def "main bkl list" [--status: string = "", --kind: string = "", --fmt (-f): string = ""] { main backlog list --status $status --kind $kind --fmt $fmt }
def "main bkl l" [--status: string = "", --kind: string = "", --fmt (-f): string = ""] { main backlog list --status $status --kind $kind --fmt $fmt }
def "main bkl show" [id: string] { main backlog show $id }
def "main bkl add" [title: string, --kind: string = "Todo", --priority: string = "Medium", --detail: string = "", --dim: string = "", --adr: string = "", --mode: string = ""] {
main backlog add $title --kind $kind --priority $priority --detail $detail --dim $dim --adr $adr --mode $mode
}
def "main bkl done" [id: string] { main backlog done $id }
def "main bkl cancel" [id: string] { main backlog cancel $id }
def "main bkl promote" [id: string] { main backlog promote $id }
def "main bkl p" [id: string] { main backlog promote $id }
def "main cfg" [action?: string] { main config $action }
def "main cfg help" [] { help-group "config" }
def "main cfg show" [profile: string, --fmt (-f): string = ""] { main config show $profile --fmt $fmt }
def "main cfg history" [profile: string, --fmt (-f): string = ""] { main config history $profile --fmt $fmt }
def "main cfg diff" [profile: string, from_id: string, to_id: string] { main config diff $profile $from_id $to_id }
def "main cfg verify" [profile: string] { main config verify $profile }
def "main cfg audit" [] { main config audit }
def "main cfg apply" [profile: string, --adr: string = "", --pr: string = "", --bug: string = "", --note: string = ""] { main config apply $profile --adr $adr --pr $pr --bug $bug --note $note }
def "main cfg rollback" [profile: string, to_id: string, --adr: string = "", --note: string = ""] { main config rollback $profile $to_id --adr $adr --note $note }
def "main cod" [action?: string] { main coder $action }
def "main cod help" [] { help-group "coder" }
def "main cod authors" [] { main coder authors }
def "main cod init" [author: string, --actor: string = "Human", --model: string = ""] { main coder init $author --actor $actor --model $model }
def "main cod record" [
author: string, content: string,
--kind (-k): string = "info", --category (-c): string = "", --title (-t): string,
--tags: list<string> = [], --relates_to: list<string> = [],
--trigger: string = "", --files_touched: list<string> = [],
--domain (-d): string = "", --reusable (-r),
] { main coder record $author $content --kind $kind --category $category --title $title --tags $tags --relates_to $relates_to --trigger $trigger --files_touched $files_touched --domain $domain --reusable=$reusable }
def "main cod log" [--author (-a): string = "", --category (-c): string = "", --tag (-t): string = "", --kind (-k): string = "", --domain (-d): string = "", --limit (-l): int = 0] {
main coder log --author $author --category $category --tag $tag --kind $kind --domain $domain --limit $limit
}
def "main cod export" [--author (-a): string = "", --category (-c): string = "", --format (-f): string = "json"] { main coder export --author $author --category $category --format $format }
def "main cod triage" [author: string, --dry-run (-n), --interactive (-i)] { main coder triage $author --dry-run=$dry_run --interactive=$interactive }
def "main cod ls" [author: string = "", --category (-c): string = ""] { main coder ls $author --category $category }
def "main cod search" [pattern: string, --author (-a): string = ""] { main coder search $pattern --author $author }
def "main cod publish" [author: string, category: string, --dry-run (-n), --all (-a)] { main coder publish $author $category --dry-run=$dry_run --all=$all }
def "main cod graduate" [source_category: string, --target (-t): string = "reflection/knowledge", --dry-run (-n)] { main coder graduate $source_category --target $target --dry-run=$dry_run }
def "main mf" [action?: string] { main manifest $action }
def "main mf help" [] { help-group "manifest" }
def "main mf mode" [id: string, --dry-run (-n)] { main manifest mode $id --dry-run=$dry_run }
def "main mf mode list" [--fmt (-f): string = "table"] { main manifest mode list --fmt $fmt }
def "main mf publish" [id: string, --dry-run (-n), --yes (-y)] { main manifest publish $id --dry-run=$dry_run --yes=$yes }
def "main mf publish list" [--fmt (-f): string = "table"] { main manifest publish list --fmt $fmt }
def "main mf layers" [--mode (-m): string = ""] { main manifest layers --mode $mode }
def "main mf consumers" [--fmt (-f): string = "table"] { main manifest consumers --fmt $fmt }
def "main md" [action?: string] { main mode $action }
def "main md help" [] { main mode help }
def "main md list" [--fmt (-f): string = ""] { main mode list --fmt $fmt }
def "main md l" [--fmt (-f): string = ""] { main mode list --fmt $fmt }
def "main md show" [id: string, --fmt (-f): string = ""] { main mode show $id --fmt $fmt }
def "main md s" [id: string, --fmt (-f): string = ""] { main mode show $id --fmt $fmt }
def "main md run" [id: string, --dry-run (-n), --yes (-y)] { main mode run $id --dry-run=$dry_run --yes=$yes }
def "main md select" [] { main mode select }
def "main fm" [action?: string] { main form $action }
def "main fm help" [] { main form help }
def "main fm list" [] { main form list }
def "main fm l" [] { main form list }
def "main fm run" [name: string, --backend: string = "cli"] { main form run $name --backend $backend }
def "main st" [--fmt (-f): string = ""] { main status --fmt $fmt }
def "main run" [id?: string, --dry-run (-n), --yes (-y)] {
let act = ($id | default "")
if $act == "" or $act == "?" or $act == "select" {
let mode_id = (pick-mode)
if ($mode_id | is-empty) { return }
main mode run $mode_id --dry-run=$dry_run --yes=$yes
return
}
if $act == "l" or $act == "list" { main mode list; return }
if $act == "h" or $act == "help" { help-group "mode"; return }
main mode run $act --dry-run=$dry_run --yes=$yes
}
def "main ru" [id?: string, --dry-run (-n), --yes (-y)] { main run $id --dry-run=$dry_run --yes=$yes }
# Search ontology nodes, ADRs and modes. Interactive picker in TTY; list in non-TTY/pipe.
# Supports --fmt and --clip (handled by the bash wrapper for all commands universally).
def "main search" [
...words: string, # search term (multi-word, no quotes needed)
--level: string = "", # filter by level: Axiom | Tension | Practice | Project
--fmt (-f): string = "", # output format: text* | json (j) | yaml (y) | toml (t) | md (m)
--clip, # copy selected result to clipboard
] {
let term = ($words | str join ' ')
log-action $"search ($term)" "read"
describe search $term --level $level --fmt $fmt --clip=$clip
}
# Alias for search.
def "main s" [
...words: string, # search term (multi-word, no quotes needed)
--level: string = "", # filter by level: Axiom | Tension | Practice | Project
--fmt (-f): string = "", # output format: text* | json (j) | yaml (y) | toml (t) | md (m)
--clip, # copy selected result to clipboard
] { main search ...($words) --level $level --fmt $fmt --clip=$clip }
# Alias for search (legacy).
def "main find" [
...words: string, # search term (multi-word, no quotes needed)
--level: string = "", # filter by level: Axiom | Tension | Practice | Project
--fmt (-f): string = "", # output format: text* | json (j) | yaml (y) | toml (t) | md (m)
--clip, # copy selected result to clipboard
] { main search ...($words) --level $level --fmt $fmt --clip=$clip }
# Alias for search (legacy).
def "main f" [
...words: string, # search term (multi-word, no quotes needed)
--level: string = "", # filter by level: Axiom | Tension | Practice | Project
--fmt (-f): string = "", # output format: text* | json (j) | yaml (y) | toml (t) | md (m)
--clip, # copy selected result to clipboard
] { main search ...($words) --level $level --fmt $fmt --clip=$clip }
# Search QA entries with word-overlap scoring; falls back to ontology if no QA hit.
def "main q" [
...words: string, # query term (multi-word, no quotes needed)
--global (-g), # also search ONTOREF_ROOT global qa.ncl
--no-fallback, # QA only — skip ontology fallback when no QA hit
--fmt (-f): string = "", # output format: text* | json (j) | yaml (y) | toml (t) | md (m)
--clip, # copy output to clipboard
] {
let term = ($words | str join ' ')
log-action $"q ($term)" "read"
qa search $term --global=$global --no-fallback=$no_fallback --fmt $fmt --clip=$clip
}
# QA-first search with ontology fallback.
def "main qs" [
...words: string, # query term (multi-word, no quotes needed)
--global (-g), # also search ONTOREF_ROOT global qa.ncl
--fmt (-f): string = "", # output format: text* | json (j) | yaml (y) | toml (t) | md (m)
--clip, # copy output to clipboard
] {
let term = ($words | str join ' ')
log-action $"qs ($term)" "read"
qa search $term --global=$global --fmt $fmt --clip=$clip
}
# Ontology search + QA results appended.
def "main sq" [
...words: string, # query term (multi-word, no quotes needed)
--level: string = "", # filter ontology by level: Axiom | Tension | Practice | Project
--fmt (-f): string = "", # output format: text* | json (j) | yaml (y) | toml (t) | md (m)
--clip, # copy output to clipboard
] {
let term = ($words | str join ' ')
log-action $"sq ($term)" "read"
describe search $term --level $level --fmt $fmt --clip=$clip
qa search $term --no-fallback --fmt $fmt --clip=$clip
}
2026-03-13 00:21:04 +00:00
def "main dg" [] { main diagram }
def "main h" [group?: string] { main help $group }
# ── Store ─────────────────────────────────────────────────────────────────────
def "main store" [action?: string] {
match ($action | default "") {
"sync-push" => {
log-action "store sync-push" "write"
opmode preflight "committed"
store sync-push
},
_ => { print " Available: store sync-push" },
}
}
2026-03-13 00:21:04 +00:00
# ── NATS Events ───────────────────────────────────────────────────────────────
def "main nats" [action?: string]: nothing -> nothing {
match ($action | default "") {
"" => { nats-status },
"status" => { nats-status },
"listen" => { nats-listen },
"emit" => { print "Usage: onref nats emit <event> [--project X]"; },
_ => { print $"Unknown action: ($action). Use: status, listen, emit" }
}
}
def "main nv" [action?: string]: nothing -> nothing { main nats $action }
# ── Services ──────────────────────────────────────────────────────────────────
def "main services" [action?: string, id?: string] {
services $action $id
}
def "main sv" [action?: string, id?: string] { main services $action $id }
# ── Setup (onboarding) ────────────────────────────────────────────────────────
# Onboard this project: create missing pieces, skip what already exists.
# Idempotent — safe to re-run at any time.
def "main setup" [
--kind: string = "Service", # repo_kind: Service | Library | DevWorkspace | PublishedCrate | AgentResource | Mixed
--parent: list<string> = [], # paths to framework/parent projects this project consumes
--gen-keys: list<string> = [], # generate auth keys; format: "role:label" e.g. ["admin:dev", "viewer:ci"]
] {
log-action "setup" "write"
let ontoref_root = $env.ONTOREF_ROOT # install data dir — templates and install/ scripts live here
let cwd = ($env.PWD | path expand)
let valid_kinds = ["Service" "Library" "DevWorkspace" "PublishedCrate" "AgentResource" "Mixed"]
if not ($valid_kinds | any { |k| $k == $kind }) {
error make { msg: $"invalid --kind '($kind)'. Valid values: ($valid_kinds | str join ', ')" }
}
print ""
print $" (ansi white_bold)ontoref setup(ansi reset) ($cwd) (ansi dark_gray)kind: ($kind)(ansi reset)"
if not ($parent | is-empty) {
print $" (ansi dark_gray)parents: ($parent | str join ', ')(ansi reset)"
}
print ""
let slug = ($cwd | path basename)
# ── 1. project.ncl ──────────────────────────────────────────────────────────
let project_ncl = $"($cwd)/.ontoref/project.ncl"
if not ($project_ncl | path exists) {
let template = $"($ontoref_root)/templates/project.ncl"
if not ($template | path exists) {
error make { msg: $"template not found: ($template) — re-run: just install-daemon" }
}
mkdir $"($cwd)/.ontoref"
open --raw $template
| str replace 'slug = "my-project"' $'slug = "($slug)"'
| str replace 'root = "/absolute/path/to/my-project"' $'root = "($cwd)"'
| str replace 'nickel_import_paths = []' $'nickel_import_paths = ["($cwd)"]'
| save -f $project_ncl
print $" (ansi green)✓(ansi reset) project.ncl created"
}
# ── 2. config.ncl ───────────────────────────────────────────────────────────
let config_ncl = $"($cwd)/.ontoref/config.ncl"
if not ($config_ncl | path exists) {
let tmpl = $"($ontoref_root)/templates/ontoref-config.ncl"
if not ($tmpl | path exists) {
print $" (ansi yellow)warn(ansi reset) ontoref-config.ncl template not found — run: just install-daemon"
} else {
let logo_candidates = [
$"($slug)-logo.svg" $"($slug)-logo.png"
$"($slug).svg" $"($slug).png"
"logo.svg" "logo.png"
]
let logo_file = (
$logo_candidates
| where { |f| ($"($cwd)/assets/($f)" | path exists) }
| get 0?
| default ""
)
let ui_section = if ($logo_file | is-not-empty) {
$" ui = \{\n logo = \"($logo_file)\",\n \},\n"
} else { "" }
open --raw $tmpl
| str replace --all '{{ project_name }}' $slug
| str replace '{{ ui_section }}' $ui_section
| save -f $config_ncl
if ($logo_file | is-not-empty) {
print $" (ansi green)✓(ansi reset) config.ncl created (ansi dark_gray)logo: ($logo_file)(ansi reset)"
} else {
print $" (ansi green)✓(ansi reset) config.ncl created (ansi dark_gray)(no logo found in assets/)(ansi reset)"
}
}
}
# ── 3. .ontology/ scaffolding ───────────────────────────────────────────────
let ontology_dir = $"($cwd)/.ontology"
if not ($ontology_dir | path exists) {
mkdir $ontology_dir
print $" (ansi green)✓(ansi reset) .ontology/ created"
}
for fname in ["core.ncl" "state.ncl" "gate.ncl"] {
let dst = $"($ontology_dir)/($fname)"
if not ($dst | path exists) {
let tmpl = $"($ontoref_root)/templates/ontology/($fname)"
if not ($tmpl | path exists) {
print $" (ansi yellow)warn(ansi reset) template not found: templates/ontology/($fname)"
} else {
open --raw $tmpl
| str replace --all '{{ project_name }}' $slug
| save -f $dst
print $" (ansi green)✓(ansi reset) .ontology/($fname) created"
}
}
}
let manifest_dst = $"($ontology_dir)/manifest.ncl"
if not ($manifest_dst | path exists) {
let resolved_parents = (
$parent
| each { |p|
let abs = ($p | path expand)
if not ($abs | path exists) {
print $" (ansi yellow)warn(ansi reset) parent path not found, skipping: ($abs)"
null
} else {
{ abs: $abs, slug: ($abs | path basename) }
}
}
| compact
)
let manifest_content = if ($resolved_parents | is-empty) {
$"let m = import \"manifest\" in\n\nm.make_manifest \{\n project = \"($slug)\",\n repo_kind = '($kind),\n layers = [],\n operational_modes = [],\n\}\n"
} else {
let impl_layer = $" m.make_layer \{\n id = \"implementation\",\n paths = [\".ontology/\"],\n committed = true,\n description = \"($slug) implementation layer\",\n \}"
let parent_layers = (
$resolved_parents
| each { |p|
$" m.make_layer \{\n id = \"($p.slug)-framework\",\n paths = [\"($p.abs)/.ontology/\"],\n committed = false,\n description = \"($p.slug) framework ontology\",\n \}"
}
)
let all_layers = ([$impl_layer] | append $parent_layers | str join ",\n")
let dev_mode = $" m.make_op_mode \{\n id = \"dev\",\n description = \"Standard development mode\",\n visible_layers = [\"implementation\"],\n audit_level = 'Standard,\n \}"
let browse_modes = (
$resolved_parents
| each { |p|
$" m.make_op_mode \{\n id = \"($p.slug)-browse\",\n description = \"Browse ($p.slug) capabilities available to this project\",\n visible_layers = [\"implementation\", \"($p.slug)-framework\"],\n audit_level = 'Quick,\n \}"
}
)
let all_modes = ([$dev_mode] | append $browse_modes | str join ",\n")
$"let m = import \"manifest\" in\n\nm.make_manifest \{\n project = \"($slug)\",\n repo_kind = '($kind),\n layers = [\n($all_layers),\n ],\n operational_modes = [\n($all_modes),\n ],\n\}\n"
}
$manifest_content | save -f $manifest_dst
if ($resolved_parents | is-empty) {
print $" (ansi green)✓(ansi reset) .ontology/manifest.ncl created"
} else {
let parent_slugs = ($resolved_parents | each { |p| $p.slug } | str join ", ")
print $" (ansi green)✓(ansi reset) .ontology/manifest.ncl created (ansi dark_gray)parents: ($parent_slugs)(ansi reset)"
}
}
# ── 4. adrs/ ────────────────────────────────────────────────────────────────
let adrs_dir = $"($cwd)/adrs"
if not ($adrs_dir | path exists) {
mkdir $adrs_dir
print $" (ansi green)✓(ansi reset) adrs/ created"
}
# ── 5. reflection/ ──────────────────────────────────────────────────────────
let refl_dir = $"($cwd)/reflection"
let modes_dir = $"($refl_dir)/modes"
if not ($modes_dir | path exists) {
mkdir $modes_dir
print $" (ansi green)✓(ansi reset) reflection/modes/ created"
}
let backlog_dst = $"($refl_dir)/backlog.ncl"
if not ($backlog_dst | path exists) {
"let s = import \"backlog\" in\n\n{\n items = [],\n} | s.BacklogStore\n"
| save -f $backlog_dst
print $" (ansi green)✓(ansi reset) reflection/backlog.ncl created"
}
let qa_dst = $"($refl_dir)/qa.ncl"
if not ($qa_dst | path exists) {
"let s = import \"qa\" in\n\n{\n entries = [],\n} | s.QaStore\n"
| save -f $qa_dst
print $" (ansi green)✓(ansi reset) reflection/qa.ncl created"
}
let bm_dst = $"($refl_dir)/search_bookmarks.ncl"
if not ($bm_dst | path exists) {
"let s = import \"search_bookmarks\" in\n\n{\n entries = [],\n} | s.BookmarkStore\n"
| save -f $bm_dst
print $" (ansi green)✓(ansi reset) reflection/search_bookmarks.ncl created"
}
# ── 6. Registration in projects.ncl ─────────────────────────────────────────
let projects_file = $"($env.HOME)/.config/ontoref/projects.ncl"
let already_registered = if ($projects_file | path exists) {
(open --raw $projects_file) | str contains $project_ncl
} else { false }
if not $already_registered {
let gen = $"($ontoref_root)/install/gen-projects.nu"
if ($gen | path exists) {
let r = (do { ^nu $gen --add $cwd } | complete)
if $r.exit_code == 0 {
print $" (ansi green)✓(ansi reset) registered in projects.ncl"
} else {
print $" (ansi yellow)warn(ansi reset) registration failed: ($r.stderr | str trim)"
}
} else {
print $" (ansi yellow)warn(ansi reset) gen-projects.nu not found — run: just install-daemon"
}
}
# ── 7. Git hooks ────────────────────────────────────────────────────────────
let git_hooks_dir = $"($cwd)/.git/hooks"
let hooks_src = $"($ontoref_root)/install/hooks"
if not ($git_hooks_dir | path exists) {
print $" (ansi yellow)warn(ansi reset) .git/hooks not found — skipping hook install"
} else if not ($hooks_src | path exists) {
print $" (ansi yellow)warn(ansi reset) hook templates not found — run: just install-daemon"
} else {
for hook in ["post-commit" "post-merge"] {
let src = $"($hooks_src)/($hook)"
let dst = $"($git_hooks_dir)/($hook)"
if not ($src | path exists) { continue }
let needs_install = if ($dst | path exists) {
(open --raw $src | hash sha256) != (open --raw $dst | hash sha256)
} else { true }
if $needs_install {
cp $src $dst
^chmod +x $dst
print $" (ansi green)✓(ansi reset) hook installed: ($hook)"
}
}
}
# ── 8. Detect mode + notify daemon ──────────────────────────────────────────
let state = (opmode detect)
print $" (ansi dark_gray)mode: ($state.actual)(ansi reset)"
if $state.actual == "daemon" {
notify-daemon-project-add $cwd
print $" (ansi green)✓(ansi reset) daemon notified"
} else {
print $" (ansi dark_gray) tip: start ontoref-daemon and re-run to activate sync(ansi reset)"
}
# ── 9. --gen-keys ────────────────────────────────────────────────────────────
if not ($gen_keys | is-empty) {
let project_ncl_path = $"($cwd)/.ontoref/project.ncl"
if not ($project_ncl_path | path exists) {
print $" (ansi red)✗(ansi reset) --gen-keys requires project.ncl to exist"
} else {
let ncl_content = (open --raw $project_ncl_path)
# Detect whether any real key entries already exist.
# A key entry starts with `{ role` (ignoring whitespace/comments).
let has_keys = ($ncl_content | str contains "role =")
if $has_keys {
print $" (ansi yellow)skip(ansi reset) --gen-keys: keys already present in project.ncl"
} else {
# Resolve daemon binary for hashing.
let daemon_bin = if ("ONTOREF_DAEMON" in $env) {
$env.ONTOREF_DAEMON
} else {
$"($env.HOME)/.local/bin/ontoref-daemon.bin"
}
if not ($daemon_bin | path exists) {
print $" (ansi red)✗(ansi reset) --gen-keys: daemon binary not found at ($daemon_bin)"
print $" (ansi dark_gray) set ONTOREF_DAEMON or run: just install-daemon(ansi reset)"
} else {
let valid_roles = ["admin" "viewer"]
let entries = (
$gen_keys
| each { |spec|
let parts = ($spec | split row ":" | each { str trim })
if ($parts | length) < 2 or ($parts | length) > 2 {
print $" (ansi yellow)warn(ansi reset) invalid --gen-keys entry '($spec)' — expected role:label, skipping"
null
} else {
let role = ($parts | get 0)
let label = ($parts | get 1)
if not ($valid_roles | any { |r| $r == $role }) {
print $" (ansi yellow)warn(ansi reset) unknown role '($role)' in '($spec)' — expected admin|viewer, skipping"
null
} else {
# Generate a 32-char random password (alphanumeric).
let password = (random chars --length 32)
let hash_result = (do { ^$daemon_bin --hash-password $password } | complete)
if $hash_result.exit_code != 0 {
print $" (ansi red)✗(ansi reset) failed to hash password for '($label)': ($hash_result.stderr | str trim)"
null
} else {
let hash = ($hash_result.stdout | str trim)
{ role: $role, label: $label, password: $password, hash: $hash }
}
}
}
}
| compact
)
if not ($entries | is-empty) {
# Build the keys NCL block.
let key_lines = (
$entries
| each { |e|
$" \{ role = '($e.role), hash = \"($e.hash)\", label = \"($e.label)\" \},"
}
| str join "\n"
)
# Replace empty `keys = [` ... `],` block.
let new_content = ($ncl_content | str replace --regex '(?s)keys\s*=\s*\[(\s*#[^\n]*\n)*\s*\],' $"keys = [\n($key_lines)\n ],")
$new_content | save -f $project_ncl_path
print ""
print $" (ansi white_bold)Generated keys — save these passwords now, they are NOT stored:(ansi reset)"
print ""
for e in $entries {
print $" (ansi cyan)($e.role)(ansi reset) label=(ansi white)($e.label)(ansi reset)"
print $" password=(ansi yellow)($e.password)(ansi reset)"
print ""
}
print $" (ansi green)✓(ansi reset) ($entries | length) key(s) written to project.ncl"
}
}
}
}
}
print ""
}
# Show current operational mode status (read-only, no transitions).
def "main mode-status" [] {
log-action "mode-status" "read"
opmode status
}
# Detect operational mode and trigger transition if changed. Used by git hooks.
def "main mode-detect" [] {
log-action "mode-detect" "read"
opmode detect | null
}
# ── Config ────────────────────────────────────────────────────────────────────
# Open ~/.config/ontoref/config.ncl in the browser for interactive editing (typedialog roundtrip).
def "main config-edit" [] {
log-action "config-edit" "write"
let root = (project-root)
let config_path = $"($env.HOME)/.config/ontoref/config.ncl"
let form_path = $"($root)/reflection/forms/config.ncl"
if not ($config_path | path exists) {
error make { msg: $"config not found: ($config_path)\n run: nu install/install.nu" }
}
if not ($form_path | path exists) {
error make { msg: $"form not found: ($form_path)" }
}
print $" editing ($config_path)"
print $" form ($form_path)"
print ""
let template_path = $"($root)/reflection/forms/config.ncl.j2"
^typedialog-web nickel-roundtrip --output $config_path --ncl-template $template_path --open $config_path $form_path
}
# Validate config.ncl schema and probe external services (DB, NATS).
def "main config-setup" [] {
log-action "config-setup" "read"
let root = (project-root)
^nu $"($root)/install/config-setup.nu"
}
# ── Project daemon-notification helpers (private) ─────────────────────────────
# Resolve the ontoref schemas directory for the current OS.
def ontoref-schemas-dir []: nothing -> string {
if $nu.os-info.name == "macos" {
$"($env.HOME)/Library/Application Support/ontoref/schemas"
} else {
$"($env.HOME)/.local/share/ontoref/schemas"
}
}
# Export an external project's project.ncl as a Nickel record.
# Ensures the schemas dir is in NICKEL_IMPORT_PATH so `import "ontoref-project.ncl"` resolves.
# Returns null on failure (file missing, nickel error, etc.).
def export-project-ncl [ncl_path: string]: nothing -> any {
if not ($ncl_path | path exists) { return null }
let schemas_dir = (ontoref-schemas-dir)
let existing_ip = ($env.NICKEL_IMPORT_PATH? | default "")
let import_path = if ($existing_ip | str contains $schemas_dir) {
$existing_ip
} else if ($existing_ip | is-empty) {
$schemas_dir
} else {
$"($existing_ip):($schemas_dir)"
}
let result = do { with-env { NICKEL_IMPORT_PATH: $import_path } { ^nickel export $ncl_path } } | complete
if $result.exit_code != 0 { return null }
$result.stdout | from json
}
# Notify a running daemon of a new local project registration via POST /projects.
# Silently skips when daemon is not reachable.
def notify-daemon-project-add [project_path: string] {
if not (daemon-available) { return }
let ncl_path = $"($project_path)/.ontoref/project.ncl"
let entry = (export-project-ncl $ncl_path)
if $entry == null {
print $" (ansi yellow)warn(ansi reset): daemon not notified — could not export project.ncl"
return
}
let url = $"($env.ONTOREF_DAEMON_URL? | default "http://127.0.0.1:7891")/projects"
let auth = (bearer-args)
let r = do { ^curl -sf -X POST -H "Content-Type: application/json" ...$auth -d ($entry | to json) $url } | complete
if $r.exit_code == 0 {
print $" (ansi green)daemon notified(ansi reset): project '($entry.slug)' registered live"
} else {
print $" (ansi yellow)warn(ansi reset): daemon not notified — ($r.stderr | str trim)"
}
}
# Notify a running daemon to remove a project from its live registry via DELETE /projects/{slug}.
# Silently skips when slug is empty or daemon is not reachable.
def notify-daemon-project-remove [slug: string] {
if ($slug | is-empty) or not (daemon-available) { return }
let url = $"($env.ONTOREF_DAEMON_URL? | default "http://127.0.0.1:7891")/projects/($slug)"
let auth = (bearer-args)
let r = do { ^curl -sf -X DELETE ...$auth $url } | complete
if $r.exit_code == 0 {
print $" (ansi green)daemon notified(ansi reset): project '($slug)' removed live"
} else {
print $" (ansi yellow)warn(ansi reset): daemon not notified — ($r.stderr | str trim)"
}
}
# ── Project management commands ────────────────────────────────────────────────
# Register a project with the global ontoref-daemon.
# Requires .ontoref/project.ncl in the target project root.
# Updates ~/.config/ontoref/projects.ncl and validates all existing entries.
# Notifies a running daemon via POST /projects so the change takes effect live.
def "main project-add" [
project_path: string, # absolute path to the project root
] {
log-action $"project-add ($project_path)" "write"
let ontoref_root = $env.ONTOREF_ROOT
let project_ncl = $"($project_path)/.ontoref/project.ncl"
let template = $"($ontoref_root)/templates/project.ncl"
if not ($project_ncl | path exists) {
if not ($template | path exists) {
error make { msg: $"template not found: ($template)" }
}
let abs = ($project_path | path expand)
let slug = ($abs | path basename)
mkdir $"($project_path)/.ontoref"
open --raw $template
| str replace 'slug = "my-project"' $'slug = "($slug)"'
| str replace 'root = "/absolute/path/to/my-project"' $'root = "($abs)"'
| str replace 'nickel_import_paths = []' $'nickel_import_paths = ["($abs)"]'
| save -f $project_ncl
print $" (ansi green)created(ansi reset) ($project_ncl)"
}
^nu $"($ontoref_root)/install/gen-projects.nu" --add $project_path
notify-daemon-project-add $project_path
}
# Remove a local project from the global ontoref-daemon registry.
# Updates ~/.config/ontoref/projects.ncl.
# Notifies a running daemon via DELETE /projects/{slug} so the change takes effect live.
def "main project-remove" [
project_path: string, # absolute path to the project root
] {
log-action $"project-remove ($project_path)" "write"
let ontoref_root = $env.ONTOREF_ROOT
# Read slug before gen-projects removes the entry — project.ncl may still exist on disk.
let ncl_path = $"($project_path)/.ontoref/project.ncl"
let slug = if ($ncl_path | path exists) {
let entry = (export-project-ncl $ncl_path)
$entry.slug? | default ""
} else {
""
}
^nu $"($ontoref_root)/install/gen-projects.nu" --remove $project_path
notify-daemon-project-remove $slug
}
# Register a remote project with the global ontoref-daemon.
# Remote projects push ontology via POST /sync — no local file watch.
# Updates ~/.config/ontoref/remote-projects.ncl.
def "main project-add-remote" [
remote_url: string, # git URL or HTTPS remote (e.g. git@github.com:org/repo.git)
slug: string, # unique project identifier
--check-git, # verify git remote is reachable before registering
] {
log-action $"project-add-remote ($slug)" "write"
let ontoref_root = $env.ONTOREF_ROOT
if $check_git {
let r = (do { ^git ls-remote --exit-code --heads $remote_url } | complete)
if $r.exit_code != 0 {
error make { msg: $"git remote unreachable: ($remote_url)\nUse without --check-git to register anyway." }
}
print $" (ansi green)reachable(ansi reset): ($remote_url)"
}
^nu $"($ontoref_root)/install/gen-remote-projects.nu" --add $remote_url --slug $slug
}
# Remove a remote project from the global ontoref-daemon registry.
def "main project-remove-remote" [
slug: string, # project slug to remove
] {
log-action $"project-remove-remote ($slug)" "write"
let ontoref_root = $env.ONTOREF_ROOT
^nu $"($ontoref_root)/install/gen-remote-projects.nu" --remove $slug
}
# List all registered projects (local and remote).
def "main project-list" [] {
log-action "project-list" "read"
let ontoref_root = $env.ONTOREF_ROOT
print $"(ansi white_bold)Local projects:(ansi reset)"
^nu $"($ontoref_root)/install/gen-projects.nu" --dry-run
print ""
print $"(ansi white_bold)Remote projects:(ansi reset)"
^nu $"($ontoref_root)/install/gen-remote-projects.nu" --list
}
# ── Git Hook Installation ──────────────────────────────────────────────────────
# Install ontoref git hooks (post-commit, post-merge) into the target project's .git/hooks/.
# Hooks notify the running daemon of NCL file changes with actor attribution.
# Requires ONTOREF_TOKEN to be set in the shell environment where git runs.
def "main hooks-install" [
project_path: string = ".", # absolute or relative path to the project root (default: current dir)
] {
log-action $"hooks-install ($project_path)" "write"
let ontoref_root = $env.ONTOREF_ROOT
let target = ($project_path | path expand)
let git_hooks_dir = $"($target)/.git/hooks"
if not ($git_hooks_dir | path exists) {
error make { msg: $"Not a git repository (or .git/hooks missing): ($target)" }
}
let hooks_src = $"($ontoref_root)/install/hooks"
if not ($hooks_src | path exists) {
error make { msg: $"Hook templates not found: ($hooks_src)" }
}
for hook in ["post-commit" "post-merge"] {
let src = $"($hooks_src)/($hook)"
let dst = $"($git_hooks_dir)/($hook)"
if not ($src | path exists) {
print $" (ansi yellow)skip(ansi reset): template not found: ($src)"
continue
}
cp $src $dst
^chmod +x $dst
print $" (ansi green)installed(ansi reset): ($dst)"
}
print ""
print $" Set (ansi cyan)ONTOREF_TOKEN(ansi reset) in your shell to enable attribution."
print $" Your token is returned by the daemon when your actor session is registered"
print " (POST /actors/register). Store it in your shell profile or .envrc."
}
2026-03-13 00:21:04 +00:00
# ── Init ──────────────────────────────────────────────────────────────────────
def "main init" [] {
let root = ($env.ONTOREF_PROJECT_ROOT? | default $env.ONTOREF_ROOT)
let actor = ($env.ONTOREF_ACTOR? | default "developer")
let cfg_path = $"($root)/.ontoref/config.ncl"
let init_entry = if ($cfg_path | path exists) {
let cfg = (daemon-export-safe $cfg_path)
if $cfg != null {
$cfg | get actor_init? | default [] | where { |e| $e.actor == $actor } | first | default null
} else { null }
} else { null }
if $init_entry != null and ($init_entry.auto_run? | default false) and ($init_entry.mode? | default "" | is-not-empty) {
print $" init: running '($init_entry.mode)' for actor ($actor)"
run-mode ($init_entry.mode)
} else {
print $" Actor: ($actor) — no init mode configured"
print $" Run 'onref help mode' to see available modes"
}
}