#!/usr/bin/env nu
# reflection/modules/describe.nu — project self-knowledge query layer.
# Aggregates from ontology, ADRs, modes, manifest, justfiles, .claude, CI config
# and renders answers from the perspective of a specific actor.
#
# The entry point for anyone (human, agent, CI) arriving at a project cold.

def project-root []: nothing -> string {
  let pr = ($env.ONTOREF_PROJECT_ROOT? | default "")
  if ($pr | is-not-empty) and ($pr != $env.ONTOREF_ROOT) { $pr } else { $env.ONTOREF_ROOT }
}

def actor-default []: nothing -> string {
  $env.ONTOREF_ACTOR? | default "developer"
}

# Build NICKEL_IMPORT_PATH for a given project root.
# Includes project-local ontology, onref symlinked schemas, ADR defaults,
# and the existing NICKEL_IMPORT_PATH from the environment.
export def nickel-import-path [root: string]: nothing -> string {
  let entries = [
    $"($root)/.ontology"
    $"($root)/adrs"
    $"($root)/.ontoref/ontology/schemas"
    $"($root)/.ontoref/adrs"
    $"($root)/.onref"
    $root
    $"($env.ONTOREF_ROOT)/ontology"
    $"($env.ONTOREF_ROOT)/ontology/schemas"
    $"($env.ONTOREF_ROOT)/adrs"
    $env.ONTOREF_ROOT
  ]
  let valid = ($entries | where { |p| $p | path exists } | uniq)
  let existing = ($env.NICKEL_IMPORT_PATH? | default "")
  if ($existing | is-not-empty) {
    ($valid | append $existing) | str join ":"
  } else {
    $valid | str join ":"
  }
}

use ../modules/store.nu [daemon-export-safe]
use ./vcs.nu

# Centralized output dispatcher for all describe commands.
# Handles text (via render callback), json, yaml, toml, table.
def emit-output [data: record, fmt: string, renderer: closure]: nothing -> nothing {
  match $fmt {
    "json" => { print ($data | to json) },
    "yaml" => { print ($data | to yaml) },
    "toml" => {
      print "# TOML cannot represent nested arrays of records. Falling back to JSON."
      print ($data | to json)
    },
    "table" => { print ($data | table --expand) },
    _ => { do $renderer },
  }
}

# ── describe project ────────────────────────────────────────────────────────────
# "What IS this project? What does it believe? What does it protect?"

export def "describe project" [
  --fmt: string = "",     # Output format: text* | json | yaml | toml | table
  --actor: string = "",   # Perspective: developer | agent | ci | auditor
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let identity = (collect-identity $root)
  let axioms = (collect-axioms $root)
  let tensions = (collect-tensions $root)
  let practices = (collect-practices $root)
  let gates = (collect-gates $root)
  let adrs = (collect-adr-summary $root)
  let dimensions = (collect-dimensions $root)

  let data = {
    identity: $identity,
    axioms: $axioms,
    tensions: $tensions,
    practices: $practices,
    gates: $gates,
    adrs: $adrs,
    dimensions: $dimensions,
  }

  emit-output $data $f { || render-project-text $data $a $root }
}

# ── describe capabilities ───────────────────────────────────────────────────────
# "What can I DO here? What commands, modes, recipes, tools exist?"

export def "describe capabilities" [
  --fmt: string = "",
  --actor: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let project_flags = (scan-project-flags $root)
  let just_modules = (scan-just-modules $root)
  let just_recipes = (scan-just-recipes $root)
  let ontoref_commands = (scan-ontoref-commands)
  let modes = (scan-reflection-modes $root)
  let mode_dags = (scan-reflection-mode-dags $root)
  let claude = (scan-claude-capabilities $root)
  let ci_tools = (scan-ci-tools $root)
  let manifest_modes = (scan-manifest-modes $root)
  let manifest = (load-manifest-safe $root)
  let manifest_capabilities = ($manifest.capabilities? | default [])
  let backlog = (load-backlog-items $root)
  let adrs = (collect-adr-summary $root)
  let api_routes = (load-api-catalog-static $root $a)
  let feature_flags = (collect-cargo-features $root)
  let domain_extension = (resolve-domain-extension $root)

  let data = {
    project_flags: $project_flags,
    just_modules: $just_modules,
    just_recipes: $just_recipes,
    ontoref_commands: $ontoref_commands,
    reflection_modes: $modes,
    mode_dags: $mode_dags,
    claude_capabilities: $claude,
    ci_tools: $ci_tools,
    manifest_modes: $manifest_modes,
    manifest_capabilities: $manifest_capabilities,
    backlog: $backlog,
    adrs: $adrs,
    api_routes: $api_routes,
    feature_flags: $feature_flags,
    domain_extension: $domain_extension,
  }

  emit-output $data $f { || render-capabilities-text $data $a $root }
}

# ── describe mode ────────────────────────────────────────────────────────────────
# "What steps does this mode define? In what order? What does each step do?"

export def "describe mode" [
  name?: string,                  # Mode ID (without .ncl extension). Omit to list all.
  --fmt: string = "",             # Output format: text* | json | yaml | table
  --actor: string = "",           # Perspective: developer | agent | ci
  --with-capabilities,            # Annotate each step with applicable flag (requires capabilities scan)
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  # List mode — no name given
  if ($name | is-empty) {
    let modes = (scan-reflection-modes $root)
    let data = { modes: $modes }
    emit-output $data $f {||
      print ""
      print "AVAILABLE MODES"
      print "══════════════════════════════════════════════════════════════════"
      for m in $modes {
        let src = if $m.source == "project" { " [project]" } else { "" }
        print $"  ($m.id)($src)  — ($m.steps) steps"
        if ($m.trigger | is-not-empty) { print $"    ($m.trigger)" }
      }
      print ""
      print $"Run: ontoref describe mode <name>"
    }
    return
  }

  # Locate mode file — project-local takes precedence over ontoref
  let project_file = $"($root)/reflection/modes/($name).ncl"
  let ontoref_file = $"($env.ONTOREF_ROOT)/reflection/modes/($name).ncl"
  let mode_root = if ($project_file | path exists) { $root } else { $env.ONTOREF_ROOT }
  let mode_file = if ($project_file | path exists) { $project_file } else { $ontoref_file }

  if not ($mode_file | path exists) {
    print $"(ansi red)Mode '($name)' not found.(ansi reset)"
    print $"  Searched: ($project_file)"
    print $"            ($ontoref_file)"
    return
  }

  let ip = (nickel-import-path $mode_root)
  let mode = (daemon-export-safe $mode_file --import-path $ip)
  if $mode == null {
    print $"(ansi red)Failed to export mode '($name)' — check NCL syntax.(ansi reset)"
    return
  }

  # Optionally annotate steps with capability flags
  let flags = if $with_capabilities { (scan-project-flags $root) } else { {} }
  let steps = ($mode.steps? | default [] | each { |s|
    if ($flags | is-not-empty) {
      $s | insert "_applicable" true   # placeholder — extended in T3 schema with `needs`
    } else { $s }
  })

  let data = {
    id:             ($mode.id? | default $name),
    trigger:        ($mode.trigger? | default ""),
    preconditions:  ($mode.preconditions? | default []),
    guards:         ($mode.guards? | default []),
    steps:          $steps,
    postconditions: ($mode.postconditions? | default []),
    converge:       ($mode.converge? | default null),
    source:         (if ($project_file | path exists) { "project" } else { "ontoref" }),
    file:           $mode_file,
  }

  emit-output $data $f {||
    print ""
    print $"MODE: ($data.id)  [($data.source)]"
    print "══════════════════════════════════════════════════════════════════"
    if ($data.trigger | is-not-empty) {
      print $"  ($data.trigger)"
    }

    if ($data.preconditions | is-not-empty) {
      print ""
      print "  PRECONDITIONS"
      for p in $data.preconditions { print $"    · ($p)" }
    }

    if ($data.guards | is-not-empty) {
      print ""
      print "  GUARDS (pre-flight checks)"
      for g in $data.guards {
        let sev = ($g.severity? | default "Block")
        let marker = if $sev == "Block" { "⊘" } else { "⚠" }
        print $"    ($marker) ($g.id) [($sev)]: ($g.reason)"
      }
    }

    print ""
    print "  STEPS"
    print "  ──────────────────────────────────────────────────────────────"
    for s in $data.steps {
      let deps = if ($s.depends_on? | default [] | is-not-empty) {
        let dep_ids = ($s.depends_on | each { |d|
          let kind = ($d.kind? | default "Always")
          if $kind != "Always" { $"($d.step)[($kind)]" } else { $d.step }
        })
        $"  after: ($dep_ids | str join ', ')"
      } else { "" }
      let actor_tag = match ($s.actor? | default "Both") {
        "Human" => " [human]",
        "Agent" => " [agent]",
        _       => "",
      }
      let err = ($s.on_error?.strategy? | default "Stop")
      print $"  ($s.id)($actor_tag)  on_error=($err)($deps)"
      print $"    ($s.action? | default '')"
      if ($s.cmd? | default "" | is-not-empty) {
        print $"    $ ($s.cmd)"
      }
      if ($s.verify? | default "" | is-not-empty) {
        print $"    verify: ($s.verify)"
      }
    }

    if ($data.converge != null) {
      print ""
      print "  CONVERGENCE"
      let max = ($data.converge.max_iterations? | default 3)
      let strat = ($data.converge.strategy? | default "RetryFailed")
      print $"    Iterate until condition met — max ($max) iterations, strategy: ($strat)"
      if ($data.converge.condition? | default "" | is-not-empty) {
        print $"    $ ($data.converge.condition | str substring 0..120)..."
      }
    }

    if ($data.postconditions | is-not-empty) {
      print ""
      print "  POSTCONDITIONS"
      for p in $data.postconditions { print $"    · ($p)" }
    }
    print ""
  }
}

# ── describe requirements ────────────────────────────────────────────────────────
# "What does this project need to run? What are the prod/dev prerequisites?"

export def "describe requirements" [
  --fmt: string = "",
  --actor: string = "",
  --environment: string = "",  # filter by environment: production | development | both
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let manifest = (load-manifest-safe $root)
  let all_reqs = ($manifest.requirements? | default [])
  let critical = ($manifest.critical_deps? | default [])

  let requirements = if ($environment | is-not-empty) {
    $all_reqs | where { |r| ($r.env? | default "Both") == ($environment | str capitalize) }
  } else {
    $all_reqs
  }

  let data = {
    requirements: $requirements,
    critical_deps: $critical,
  }

  emit-output $data $f {||
    print ""
    print "REQUIREMENTS"
    print "══════════════════════════════════════════════════════════════════"
    if ($requirements | is-not-empty) {
      let prod = ($requirements | where { |r| ($r.env? | default "Both") in ["Production", "Both"] })
      let dev  = ($requirements | where { |r| ($r.env? | default "Both") in ["Development", "Both"] })

      if ($prod | is-not-empty) {
        print ""
        print "PRODUCTION"
        print "──────────────────────────────────────────────────────────────────"
        for r in $prod {
          let req_flag = if ($r.required? | default true) { "(required)" } else { "(optional)" }
          let ver = if ($r.version? | default "" | is-not-empty) { $" >= ($r.version)" } else { "" }
          print $"  ($r.name)($ver) [($r.kind? | default '')] ($req_flag)"
          if ($r.impact? | default "" | is-not-empty) { print $"    Impact: ($r.impact)" }
          if ($r.provision? | default "" | is-not-empty) { print $"    Provision: ($r.provision)" }
        }
      }

      if ($dev | is-not-empty) {
        print ""
        print "DEVELOPMENT"
        print "──────────────────────────────────────────────────────────────────"
        for r in $dev {
          let req_flag = if ($r.required? | default true) { "(required)" } else { "(optional)" }
          let ver = if ($r.version? | default "" | is-not-empty) { $" >= ($r.version)" } else { "" }
          print $"  ($r.name)($ver) [($r.kind? | default '')] ($req_flag)"
          if ($r.impact? | default "" | is-not-empty) { print $"    Impact: ($r.impact)" }
          if ($r.provision? | default "" | is-not-empty) { print $"    Provision: ($r.provision)" }
        }
      }
    } else {
      print "  (no requirements declared in manifest)"
    }

    if ($critical | is-not-empty) {
      print ""
      print "CRITICAL DEPENDENCIES"
      print "──────────────────────────────────────────────────────────────────"
      for d in $critical {
        print $"  ($d.name) [($d.ref? | default '')]"
        print $"    Used for: ($d.used_for? | default '')"
        print $"    Failure: ($d.failure_impact? | default '')"
        if ($d.mitigation? | default "" | is-not-empty) {
          print $"    Mitigation: ($d.mitigation)"
        }
      }
    }
    print ""
  }
}

# ── describe constraints ────────────────────────────────────────────────────────
# "What can I NOT do? What are the Hard rules?"

export def "describe constraints" [
  --fmt: string = "",
  --actor: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let axioms = (collect-axioms $root)
  let hard_constraints = (collect-hard-constraints $root)
  let gates = (collect-gates $root)

  let data = {
    invariants: $axioms,
    hard_constraints: $hard_constraints,
    active_gates: $gates,
  }

  emit-output $data $f { || render-constraints-text $data $a }
}

# ── describe tools ──────────────────────────────────────────────────────────────
# "What dev/CI tools does this project use? How do I call them?"

export def "describe tools" [
  --fmt: string = "",
  --actor: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let ci_tools = (scan-ci-tools $root)
  let just_recipes = (scan-just-recipes $root)
  let dev_tools = (scan-dev-tools $root)

  let data = {
    ci_tools: $ci_tools,
    just_recipes: $just_recipes,
    dev_tools: $dev_tools,
  }

  emit-output $data $f { || render-tools-text $data $a $root }
}

# ── describe impact ─────────────────────────────────────────────────────────────
# "If I change X, what else is affected?"

export def "describe impact" [
  node_id: string,                    # Ontology node id to trace
  --depth: int = 2,                   # How many edge hops to follow
  --include-external,                 # Follow connections.ncl to external projects via daemon
  --fmt: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let f = if ($fmt | is-not-empty) { $fmt } else { "text" }
  let ontology = (load-ontology-safe $root)

  if ($ontology | is-empty) {
    print "  No .ontology/core.ncl found."
    return
  }

  let nodes = ($ontology.nodes? | default [])
  let edges = ($ontology.edges? | default [])

  let target = ($nodes | where id == $node_id)
  if ($target | is-empty) {
    let available = ($nodes | get id | str join ", ")
    print $"  Node '($node_id)' not found. Available: ($available)"
    return
  }

  let local_impacts = (trace-impacts $node_id $edges $nodes $depth)

  # When --include-external, query the daemon for cross-project entries
  let external_impacts = if $include_external {
    let daemon_url = ($env.ONTOREF_DAEMON_URL? | default "http://127.0.0.1:7891")
    let result = do {
      http get $"($daemon_url)/graph/impact?node=($node_id)&depth=($depth)&include_external=true"
    } | complete
    if $result.exit_code == 0 {
      let resp = ($result.stdout | from json)
      $resp.impacts? | default [] | each { |e|
        {
          id:          $e.node_id,
          name:        ($e.node_name? | default $e.node_id),
          level:       "external",
          description: $"[$($e.slug)] via ($e.via)",
          depth:       $e.depth,
          direction:   $e.direction,
          external:    true,
        }
      }
    } else {
      []
    }
  } else {
    []
  }

  let all_impacts = ($local_impacts | append $external_impacts)

  let data = {
    source:           ($target | first),
    impacts:          $all_impacts,
    include_external: $include_external,
  }

  emit-output $data $f { || render-impact-text $data }
}

# ── describe why ────────────────────────────────────────────────────────────────
# "Why does this decision/constraint/practice exist?"

export def "describe why" [
  id: string,              # Node id, ADR id, or constraint id
  --fmt: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let f = if ($fmt | is-not-empty) { $fmt } else { "text" }

  let ontology = (load-ontology-safe $root)
  let adr_data = (load-all-adrs $root)

  # Search in ontology nodes
  let node_match = if ($ontology | is-not-empty) {
    $ontology.nodes? | default [] | where id == $id
  } else { [] }

  # Search in ADRs
  let adr_match = ($adr_data | where { |a| $a.id == $id or $a.id == $"adr-($id)" })

  let data = {
    node: (if ($node_match | is-not-empty) { $node_match | first } else { null }),
    adr: (if ($adr_match | is-not-empty) { $adr_match | first } else { null }),
    edges_from: (if ($ontology | is-not-empty) {
      $ontology.edges? | default [] | where from == $id
    } else { [] }),
    edges_to: (if ($ontology | is-not-empty) {
      $ontology.edges? | default [] | where to == $id
    } else { [] }),
  }

  emit-output $data $f { || render-why-text $data $id }
}

# ── describe find ──────────────────────────────────────────────────────────────
# HOWTO-oriented search: What is it, Why it exists, How to use it, Where to look.
# Extracts doc comments from Rust source, finds examples/tests, shows related nodes.
# Human: interactive selector loop. Agent: structured JSON.

export def "describe search" [
  term: string,            # Search term (case-insensitive substring match)
  --level: string = "",    # Filter by level: Axiom | Tension | Practice | Project
  --fmt: string = "",
  --clip,                  # Copy selected result to clipboard after rendering
]: nothing -> nothing {
  let root = (project-root)
  let actor = (actor-default)
  let raw_fmt = if ($fmt | is-not-empty) { $fmt } else if $actor == "agent" { "json" } else { "text" }
  let f = match $raw_fmt {
    "j"  => "json",
    "y"  => "yaml",
    "t"  => "toml",
    "m"  => "md",
    _    => $raw_fmt,
  }

  let ontology = (load-ontology-safe $root)
  if ($ontology | is-empty) {
    print "  No .ontology/core.ncl found."
    return
  }

  let nodes = ($ontology.nodes? | default [])
  let edges = ($ontology.edges? | default [])
  let term_lower = ($term | str downcase)

  mut matches = ($nodes | where { |n|
    let id_match = ($n.id | str downcase | str contains $term_lower)
    let label_match = ($n.name? | default "" | str downcase | str contains $term_lower)
    let desc_match = ($n.description? | default "" | str downcase | str contains $term_lower)
    $id_match or $label_match or $desc_match
  })

  if ($level | is-not-empty) {
    $matches = ($matches | where { |n| ($n.level? | default "") == $level })
  }

  if ($matches | is-empty) {
    print $"  No nodes matching '($term)'."
    if ($level | is-not-empty) { print $"  (ansi dark_gray)Level filter: ($level)(ansi reset)" }
    return
  }

  if $f == "json" or $f == "yaml" or $f == "toml" {
    # Use $matches directly — no daemon/build-howto needed for structured output.
    let results = ($matches | each { |n| {
      id:          $n.id,
      name:        ($n.name? | default ""),
      level:       ($n.level? | default ""),
      description: ($n.description? | default ""),
      pole:        ($n.pole? | default ""),
      invariant:   ($n.invariant? | default false),
      edges_from:  ($edges | where from == $n.id | select kind to),
      edges_to:    ($edges | where to   == $n.id | select kind from),
    }})
    let payload = { term: $term, count: ($results | length), results: $results }
    match $f {
      "json" => { print ($payload | to json) },
      "yaml" => { print ($payload | to yaml) },
      "toml" => { print ({ find: $payload } | to toml) },
    }
    return
  }

  if $f == "md" {
    let results = ($matches | each { |n| build-howto $n $nodes $edges $root })
    for r in $results { render-howto-md $r }
    return
  }

  if ($matches | length) == 1 {
    let node = ($matches | first)
    render-howto $node $nodes $edges $root
    if $clip {
      let h = (build-howto $node $nodes $edges $root)
      clip-text (howto-to-md-string $h)
    }
    return
  }

  # No TTY (subprocess, pipe, CI): print summary list without interactive selector.
  let is_tty = (do { ^test -t 0 } | complete | get exit_code) == 0
  if not $is_tty {
    print ""
    print $"  (ansi white_bold)Search:(ansi reset) '($term)'  ($matches | length) results"
    print ""
    for m in $matches {
      let level_str = ($m.level? | default "" | fill -w 9)
      let name_str  = ($m.name? | default $m.id)
      let desc_str  = ($m.description? | default "")
      print $"  (ansi cyan)($level_str)(ansi reset) (ansi white_bold)($m.id)(ansi reset)  ($name_str)"
      if ($desc_str | is-not-empty) {
        print $"             (ansi dark_gray)($desc_str)(ansi reset)"
      }
    }
    print ""
    return
  }

  find-interactive-loop $matches $nodes $edges $root $term $clip
}

# Backward-compatible alias — delegates to describe search.
export def "describe find" [
  term: string,
  --level: string = "",
  --fmt: string = "",
  --clip,
]: nothing -> nothing {
  describe search $term --level $level --fmt $fmt --clip=$clip
}

# Load entries from a qa.ncl file path. Returns empty list on missing file or export failure.
def qa-load-entries [qa_path: string]: nothing -> list {
  if not ($qa_path | path exists) { return [] }
  let r = (do { ^nickel export --format json $qa_path } | complete)
  if $r.exit_code != 0 { return [] }
  ($r.stdout | from json | get entries? | default [])
}

# Word-overlap score: count of query words present in the combined entry text.
def qa-score-entry [words: list, entry: record]: nothing -> int {
  let text = ($"($entry.question? | default '') ($entry.answer? | default '') ($entry.tags? | default [] | str join ' ')" | str downcase)
  $words | each { |w| if ($text | str contains $w) { 1 } else { 0 } } | math sum
}

# Search Q&A entries in reflection/qa.ncl with word-overlap scoring.
# Falls back to describe search when no QA hits are found.
export def "qa search" [
  term: string,                    # Natural-language query
  --global (-g),                   # Also search ONTOREF_ROOT qa.ncl
  --no-fallback,                   # Do not fall back to ontology search
  --fmt: string = "",
  --clip,                          # Copy output to clipboard after rendering
]: nothing -> nothing {
  let root  = (project-root)
  let actor = (actor-default)
  let f     = if ($fmt | is-not-empty) { $fmt } else if $actor == "agent" { "json" } else { "text" }
  let words = ($term | str downcase | split words | where { |w| ($w | str length) > 2 })

  let project_entries = (qa-load-entries $"($root)/reflection/qa.ncl")
    | each { |e| $e | insert scope "project" }

  mut entries = $project_entries

  if $global {
    let global_root = $env.ONTOREF_ROOT
    if $global_root != $root {
      let global_entries = (qa-load-entries $"($global_root)/reflection/qa.ncl")
        | each { |e| $e | insert scope "global" }
      $entries = ($entries | append $global_entries)
    }
  }

  let scored = ($entries
    | each { |e| $e | insert _score (qa-score-entry $words $e) }
    | where { |e| $e._score > 0 }
    | sort-by _score --reverse
  )

  if ($scored | is-empty) {
    if not $no_fallback {
      print $"  (ansi dark_gray)No QA entries matching '($term)' — searching ontology…(ansi reset)"
      describe search $term --fmt $fmt --clip=$clip
    } else {
      print $"  No QA entries matching '($term)'."
    }
    return
  }

  if $f == "json" {
    let out = ($scored | reject _score | to json)
    print $out
    if $clip { clip-text $out }
    return
  }

  mut clip_lines: list<string> = []
  for e in $scored {
    let scope_tag = $"(ansi dark_gray)[($e.scope)](ansi reset)"
    let id_tag    = $"(ansi cyan)($e.id)(ansi reset)"
    print $"($scope_tag) ($id_tag)  (ansi white_bold)($e.question)(ansi reset)"
    if ($e.answer? | default "" | is-not-empty) {
      print $"  ($e.answer)"
    }
    print ""
    if $clip {
      $clip_lines = ($clip_lines | append $"[($e.scope)] ($e.id)  ($e.question)")
      if ($e.answer? | default "" | is-not-empty) {
        $clip_lines = ($clip_lines | append $"  ($e.answer)")
      }
      $clip_lines = ($clip_lines | append "")
    }
  }
  if $clip and ($clip_lines | is-not-empty) {
    clip-text ($clip_lines | str join "\n")
  }
}

# ── HOWTO builder ─────────────────────────────────────────────────────────────

# Extract //! or /// doc comments from a Rust file (module-level docs).
def extract-rust-docs [file_path: string]: nothing -> string {
  if not ($file_path | path exists) { return "" }
  let lines = (open $file_path --raw | lines)
  mut doc_lines = []
  mut in_docs = true
  for line in $lines {
    if not $in_docs { break }
    let trimmed = ($line | str trim)
    if ($trimmed | str starts-with "//!") {
      let content = ($trimmed | str replace "//! " "" | str replace "//!" "")
      $doc_lines = ($doc_lines | append $content)
    } else if ($trimmed | str starts-with "///") {
      let content = ($trimmed | str replace "/// " "" | str replace "///" "")
      $doc_lines = ($doc_lines | append $content)
    } else if ($trimmed | is-empty) and ($doc_lines | is-not-empty) {
      $doc_lines = ($doc_lines | append "")
    } else if ($doc_lines | is-not-empty) {
      $in_docs = false
    }
  }
  $doc_lines | str join "\n" | str trim
}

# Find examples related to a crate.
def find-examples [root: string, crate_name: string]: nothing -> list<record> {
  let examples_dir = $"($root)/crates/($crate_name)/examples"
  if not ($examples_dir | path exists) { return [] }
  glob $"($examples_dir)/*.rs" | each { |e|
    let name = ($e | path basename | str replace ".rs" "")
    let docs = (extract-rust-docs $e)
    let short = if ($docs | is-not-empty) {
      $docs | lines | first
    } else { "" }
    { name: $name, cmd: $"cargo run -p ($crate_name) --example ($name)", description: $short }
  }
}

# Find test files that reference a module/artifact.
def find-tests [root: string, artifact_path: string]: nothing -> list<record> {
  # Derive crate name and module name from artifact path.
  let parts = ($artifact_path | split row "/")
  if ($parts | length) < 2 { return [] }
  if ($parts | first) != "crates" { return [] }
  let crate_name = ($parts | get 1)
  let tests_dir = $"($root)/crates/($crate_name)/tests"
  if not ($tests_dir | path exists) { return [] }

  # Search for test files whose name relates to the artifact.
  let module_name = ($artifact_path | path basename | str replace ".rs" "")
  let test_files = (glob $"($tests_dir)/*.rs")
  $test_files | each { |tf|
    let test_name = ($tf | path basename | str replace ".rs" "")
    let content = (open $tf --raw)
    let module_lower = ($module_name | str downcase)
    let test_lower = ($test_name | str downcase)
    if ($test_lower | str contains $module_lower) or ($content | str downcase | str contains $module_lower) {
      let docs = (extract-rust-docs $tf)
      let short = if ($docs | is-not-empty) { $docs | lines | first } else { "" }
      { name: $test_name, cmd: $"cargo test -p ($crate_name) --test ($test_name)", description: $short }
    } else { null }
  } | compact
}

# Copy text to system clipboard (pbcopy / xclip / wl-copy).
def clip-text [text: string]: nothing -> nothing {
  if (which pbcopy | is-not-empty) {
    $text | ^pbcopy
    print --stderr "  ✓ Copied to clipboard"
  } else if (which xclip | is-not-empty) {
    $text | ^xclip -selection clipboard
    print --stderr "  ✓ Copied to clipboard"
  } else if (which "wl-copy" | is-not-empty) {
    $text | ^wl-copy
    print --stderr "  ✓ Copied to clipboard"
  } else {
    print --stderr "  No clipboard tool found (install pbcopy, xclip, or wl-copy)"
  }
}

# Build a plain markdown string from a howto record (mirrors render-howto-md).
def howto-to-md-string [h: record]: nothing -> string {
  mut lines: list<string> = []
  let inv = if $h.invariant { " **invariant**" } else { "" }
  $lines = ($lines | append $"# ($h.id)($inv)")
  $lines = ($lines | append "")
  $lines = ($lines | append $"**Level**: ($h.level)  **Name**: ($h.name)")
  $lines = ($lines | append "")
  $lines = ($lines | append "## What")
  $lines = ($lines | append "")
  $lines = ($lines | append $h.what)
  if ($h.what_docs | is-not-empty) {
    $lines = ($lines | append "")
    $lines = ($lines | append $h.what_docs)
  }
  if ($h.source | is-not-empty) {
    $lines = ($lines | append "")
    $lines = ($lines | append "## Source")
    $lines = ($lines | append "")
    for s in $h.source {
      if ($s.modules? | is-not-empty) {
        $lines = ($lines | append $"- `($s.path)/`")
        let mods = ($s.modules | each { |m| $m | str replace ".rs" "" } | where { |m| $m != "mod" })
        if ($mods | is-not-empty) {
          let mod_str = ($mods | each { |m| $"`($m)`" } | str join ", ")
          $lines = ($lines | append $"  Modules: ($mod_str)")
        }
      } else {
        $lines = ($lines | append $"- `($s.path)`")
      }
    }
  }
  if ($h.examples | is-not-empty) {
    $lines = ($lines | append "")
    $lines = ($lines | append "## Examples")
    $lines = ($lines | append "")
    for ex in $h.examples {
      $lines = ($lines | append "```sh")
      $lines = ($lines | append $ex.cmd)
      $lines = ($lines | append "```")
      if ($ex.description | is-not-empty) { $lines = ($lines | append $ex.description) }
      $lines = ($lines | append "")
    }
  }
  if ($h.tests | is-not-empty) {
    $lines = ($lines | append "")
    $lines = ($lines | append "## Tests")
    $lines = ($lines | append "")
    for t in $h.tests {
      $lines = ($lines | append "```sh")
      $lines = ($lines | append $t.cmd)
      $lines = ($lines | append "```")
      if ($t.description | is-not-empty) { $lines = ($lines | append $t.description) }
      $lines = ($lines | append "")
    }
  }
  if ($h.related_to | is-not-empty) {
    $lines = ($lines | append "")
    $lines = ($lines | append "## Related")
    $lines = ($lines | append "")
    for r in $h.related_to { $lines = ($lines | append $"- → `($r.id)` ($r.name)") }
  }
  if ($h.used_by | is-not-empty) {
    $lines = ($lines | append "")
    $lines = ($lines | append "## Used by")
    $lines = ($lines | append "")
    for u in $h.used_by { $lines = ($lines | append $"- ← `($u.id)` ($u.name)") }
  }
  if ($h.adrs | is-not-empty) {
    $lines = ($lines | append "")
    $lines = ($lines | append "## Validated by")
    $lines = ($lines | append "")
    for adr in $h.adrs { $lines = ($lines | append $"- `($adr)`") }
  }
  $lines = ($lines | append "")
  $lines | str join "\n"
}

# Build full HOWTO record for a node.
def build-howto [
  n: record,
  all_nodes: list<record>,
  edges: list<record>,
  root: string,
]: nothing -> record {
  let artifacts = ($n.artifact_paths? | default [])

  # WHAT: doc comments from the artifact entry point.
  mut what_docs = ""
  mut source_files = []
  for a in $artifacts {
    let full = $"($root)/($a)"
    if ($full | path exists) {
      if ($full | path type) == "dir" {
        # Directory artifact: read mod.rs or lib.rs.
        let mod_rs = $"($full)/mod.rs"
        let lib_rs = $"($full)/lib.rs"
        let entry = if ($mod_rs | path exists) { $mod_rs } else if ($lib_rs | path exists) { $lib_rs } else { "" }
        if ($entry | is-not-empty) {
          let docs = (extract-rust-docs $entry)
          if ($docs | is-not-empty) { $what_docs = $docs }
          $source_files = ($source_files | append { path: $a, entry: ($entry | path basename) })
        }
        # List public source files in the directory.
        let rs_files = (glob ($full | path join "*.rs") | each { |f| $f | path basename } | sort)
        $source_files = ($source_files | append { path: $a, modules: $rs_files })
      } else if ($full | str ends-with ".rs") {
        let docs = (extract-rust-docs $full)
        if ($docs | is-not-empty) and ($what_docs | is-empty) { $what_docs = $docs }
        $source_files = ($source_files | append { path: $a })
      } else {
        $source_files = ($source_files | append { path: $a })
      }
    }
  }

  # HOW: examples and tests.
  mut examples = []
  mut tests = []
  for a in $artifacts {
    if ($a | str starts-with "crates/") {
      let crate_name = ($a | split row "/" | get 1)
      let found_examples = (find-examples $root $crate_name)
      $examples = ($examples | append $found_examples)
      let found_tests = (find-tests $root $a)
      $tests = ($tests | append $found_tests)
    }
  }
  $examples = ($examples | uniq-by name)
  $tests = ($tests | uniq-by name)

  # RELATED: connected nodes (compact — just id + name for context, not structural dump).
  let related = ($edges | where from == $n.id | each { |e|
    let t = ($all_nodes | where id == $e.to)
    { id: $e.to, name: (if ($t | is-not-empty) { ($t | first).name? | default $e.to } else { $e.to }), relation: $e.kind }
  })
  let used_by = ($edges | where to == $n.id | each { |e|
    let s = ($all_nodes | where id == $e.from)
    { id: $e.from, name: (if ($s | is-not-empty) { ($s | first).name? | default $e.from } else { $e.from }), relation: $e.kind }
  })

  {
    id: $n.id,
    name: ($n.name? | default ""),
    level: ($n.level? | default ""),
    invariant: ($n.invariant? | default false),
    what: ($n.description? | default ""),
    what_docs: $what_docs,
    source: $source_files,
    examples: $examples,
    tests: $tests,
    related_to: $related,
    used_by: $used_by,
    adrs: ($n.adrs? | default []),
  }
}

# ── Render HOWTO (human) ──────────────────────────────────────────────────────

def render-howto [
  n: record,
  all_nodes: list<record>,
  edges: list<record>,
  root: string,
] {
  let h = (build-howto $n $all_nodes $edges $root)
  let level_val_color = match $h.level {
    "Axiom"   => (ansi red),
    "Tension"  => (ansi yellow),
    "Practice" => (ansi green),
    _          => (ansi cyan),
  }
  let inv = if $h.invariant { $" (ansi red_bold)invariant(ansi reset)" } else { "" }

  print ""
  print $"  (ansi white_bold)($h.id)(ansi reset)($inv)"
  print $"  ($level_val_color)($h.level)(ansi reset)  ($h.name)"

  # WHAT — ontology description + source doc comments.
  print ""
  print $"  (ansi white_bold)What(ansi reset)"
  print $"  ($h.what)"
  if ($h.what_docs | is-not-empty) {
    print ""
    let doc_lines = ($h.what_docs | lines)
    for line in $doc_lines {
      print $"  (ansi dark_gray)($line)(ansi reset)"
    }
  }

  # SOURCE — where the code lives, public modules.
  if ($h.source | is-not-empty) {
    print ""
    print $"  (ansi white_bold)Source(ansi reset)"
    for s in $h.source {
      if ($s.modules? | is-not-empty) {
        print $"  (ansi cyan)($s.path)/(ansi reset)"
        let mods = ($s.modules | each { |m| $m | str replace ".rs" "" } | where { |m| $m != "mod" })
        if ($mods | is-not-empty) {
          let mod_str = ($mods | str join "  ")
          print $"  (ansi dark_gray)($mod_str)(ansi reset)"
        }
      } else {
        print $"  (ansi cyan)($s.path)(ansi reset)"
      }
    }
  }

  # HOW — examples to run.
  if ($h.examples | is-not-empty) {
    print ""
    print $"  (ansi white_bold)Examples(ansi reset)"
    for ex in $h.examples {
      print $"  (ansi green)(ansi reset) ($ex.cmd)"
      if ($ex.description | is-not-empty) {
        print $"    (ansi dark_gray)($ex.description)(ansi reset)"
      }
    }
  }

  # HOW — tests to run.
  if ($h.tests | is-not-empty) {
    print ""
    print $"  (ansi white_bold)Tests(ansi reset)"
    for t in $h.tests {
      print $"  (ansi green)(ansi reset) ($t.cmd)"
      if ($t.description | is-not-empty) {
        print $"    (ansi dark_gray)($t.description)(ansi reset)"
      }
    }
  }

  # RELATED — connected nodes (compact, for context).
  if ($h.related_to | is-not-empty) {
    print ""
    print $"  (ansi white_bold)Related(ansi reset)"
    for r in $h.related_to {
      print $"  (ansi green)→(ansi reset) (ansi cyan)($r.id)(ansi reset)  ($r.name)"
    }
  }
  if ($h.used_by | is-not-empty) {
    print ""
    print $"  (ansi white_bold)Used by(ansi reset)"
    for u in $h.used_by {
      print $"  (ansi yellow)←(ansi reset) (ansi cyan)($u.id)(ansi reset)  ($u.name)"
    }
  }
  if ($h.adrs | is-not-empty) {
    print ""
    print $"  (ansi white_bold)Validated by(ansi reset)"
    for adr in $h.adrs {
      print $"  (ansi magenta)◆(ansi reset) (ansi cyan)($adr)(ansi reset)"
    }
  }

  print ""
}

# ── Render HOWTO (markdown) ─────────────────────────────────────────────────

def render-howto-md [h: record] {
  let inv = if $h.invariant { " **invariant**" } else { "" }
  print $"# ($h.id)($inv)"
  print ""
  print $"**Level**: ($h.level)  **Name**: ($h.name)"
  print ""
  print "## What"
  print ""
  print $h.what
  if ($h.what_docs | is-not-empty) {
    print ""
    print $h.what_docs
  }
  if ($h.source | is-not-empty) {
    print ""
    print "## Source"
    print ""
    for s in $h.source {
      if ($s.modules? | is-not-empty) {
        print $"- `($s.path)/`"
        let mods = ($s.modules | each { |m| $m | str replace ".rs" "" } | where { |m| $m != "mod" })
        if ($mods | is-not-empty) {
          let mod_str = ($mods | each { |m| $"`($m)`" } | str join ", ")
          print $"  Modules: ($mod_str)"
        }
      } else {
        print $"- `($s.path)`"
      }
    }
  }
  if ($h.examples | is-not-empty) {
    print ""
    print "## Examples"
    print ""
    for ex in $h.examples {
      print "```sh"
      print $ex.cmd
      print "```"
      if ($ex.description | is-not-empty) { print $ex.description }
      print ""
    }
  }
  if ($h.tests | is-not-empty) {
    print ""
    print "## Tests"
    print ""
    for t in $h.tests {
      print "```sh"
      print $t.cmd
      print "```"
      if ($t.description | is-not-empty) { print $t.description }
      print ""
    }
  }
  if ($h.related_to | is-not-empty) {
    print ""
    print "## Related"
    print ""
    for r in $h.related_to { print $"- → `($r.id)` ($r.name)" }
  }
  if ($h.used_by | is-not-empty) {
    print ""
    print "## Used by"
    print ""
    for u in $h.used_by { print $"- ← `($u.id)` ($u.name)" }
  }
  if ($h.adrs | is-not-empty) {
    print ""
    print "## Validated by"
    print ""
    for adr in $h.adrs { print $"- `($adr)`" }
  }
  print ""
}

# ── Interactive loop ──────────────────────────────────────────────────────────

def find-interactive-loop [
  matches: list<record>,
  all_nodes: list<record>,
  edges: list<record>,
  root: string,
  term: string,
  clip: bool,
] {
  let match_count = ($matches | length)
  print ""
  print $"  (ansi white_bold)Search:(ansi reset) '($term)'  ($match_count) results"
  print ""

  let labels = ($matches | each { |n|
    let level_val_tag = ($n.level? | default "?" | fill -w 9)
    $"($level_val_tag) ($n.id)  —  ($n.name? | default '')"
  })
  let quit_label = "← quit"
  let selector_items = ($labels | append $quit_label)

  loop {
    let pick = ($selector_items | input list $"  (ansi cyan_bold)Select:(ansi reset) ")
    if ($pick | is-empty) or ($pick == $quit_label) { break }

    let picked_parts = ($pick | split row "  —  ")
    let picked_prefix = ($picked_parts | first | str trim)
    let picked_id = ($picked_prefix | split row " " | last)

    let node_matches = ($matches | where id == $picked_id)
    if ($node_matches | is-empty) { continue }

    let selected_node = ($node_matches | first)
    render-howto $selected_node $all_nodes $edges $root

    # Offer to jump to a related node, back to results, or quit.
    let h = (build-howto $selected_node $all_nodes $edges $root)
    if $clip { clip-text (howto-to-md-string $h) }
    let conn_ids = ($h.related_to | get id) | append ($h.used_by | get id) | uniq
    if ($conn_ids | is-not-empty) {
      let jump_items = ($conn_ids | append "← back" | append "← quit")
      let jump = ($jump_items | input list $"  (ansi cyan_bold)Jump to:(ansi reset) ")
      if ($jump | is-empty) or ($jump == "← quit") { break }
      if $jump == "← back" { continue }

      let jumped = ($all_nodes | where id == $jump)
      if ($jumped | is-not-empty) {
        let jumped_node = ($jumped | first)
        render-howto $jumped_node $all_nodes $edges $root
        if $clip {
          let jh = (build-howto $jumped_node $all_nodes $edges $root)
          clip-text (howto-to-md-string $jh)
        }
      }
    }
  }
}

# ── describe features ──────────────────────────────────────────────────────────
# "What concrete capabilities does this project have?"
# No args → list all features. With id → detailed view of one feature.

export def "describe features" [
  id?: string,             # Feature id to detail (optional — omit for list)
  --fmt: string = "",
  --actor: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let ontology = (load-ontology-safe $root)
  if ($ontology | is-empty) {
    print "  No .ontology/core.ncl found."
    return
  }

  let nodes = ($ontology.nodes? | default [])
  let edges = ($ontology.edges? | default [])

  # Features = all non-Axiom, non-Tension nodes (Practice, etc.)
  let features = ($nodes | where { |n|
    let level_val = ($n.level? | default "")
    $level_val != "Axiom" and $level_val != "Tension"
  })

  # Cargo features (compile-time toggles)
  let cargo_features = (collect-cargo-features $root)

  if ($id | is-empty) or ($id == "") {
    # List mode
    let data = {
      features: ($features | each { |n| {
        id: $n.id,
        name: ($n.name? | default $n.id),
        level: ($n.level? | default ""),
        description: ($n.description? | default ""),
        artifacts: ($n.artifact_paths? | default [] | length),
      }}),
      cargo_features: $cargo_features,
    }
    emit-output $data $f { || render-features-list-text $data $root }
  } else {
    # Detail mode
    let target = ($features | where { |n| $n.id == $id })
    if ($target | is-empty) {
      let available = ($features | get id | str join ", ")
      print $"  Feature '($id)' not found. Available: ($available)"
      return
    }
    let node = ($target | first)

    # Verify artifact paths
    let artifacts = ($node.artifact_paths? | default [] | each { |p|
      let full = $"($root)/($p)"
      { path: $p, exists: ($full | path exists) }
    })

    # Edges: dependencies (outgoing) and dependents (incoming)
    let outgoing = ($edges | where from == $id | each { |e|
      let target_node = ($nodes | where id == $e.to)
      let label = if ($target_node | is-not-empty) { ($target_node | first).name? | default $e.to } else { $e.to }
      { id: $e.to, name: $label, relation: ($e.kind? | default ""), direction: "depends_on" }
    })
    let incoming = ($edges | where to == $id | each { |e|
      let source_node = ($nodes | where id == $e.from)
      let label = if ($source_node | is-not-empty) { ($source_node | first).name? | default $e.from } else { $e.from }
      { id: $e.from, name: $label, relation: ($e.kind? | default ""), direction: "depended_by" }
    })

    # Related state dimensions
    let dims = (collect-dimensions $root)
    let related_dims = ($dims | where { |d|
      ($d.id | str contains $id) or ($id | str contains $d.id)
    })

    # Related ADR constraints
    let hard_constraints = (collect-hard-constraints $root)
    let related_constraints = ($hard_constraints | where { |c|
      ($c.check_hint? | default "" | str contains $id) or ($c.adr_id? | default "" | str contains $id)
    })

    # Cargo deps for the crate if feature maps to one
    let crate_deps = (collect-crate-deps $root $id)

    # //! crate doc — present only when this node's primary artifact is a crate directory.
    let crate_doc = (find-crate-doc-for-node $root ($node.artifact_paths? | default []))

    let data = {
      id: $node.id,
      name: ($node.name? | default $node.id),
      level: ($node.level? | default ""),
      pole: ($node.pole? | default ""),
      description: ($node.description? | default ""),
      invariant: ($node.invariant? | default false),
      artifacts: $artifacts,
      depends_on: $outgoing,
      depended_by: $incoming,
      dimensions: $related_dims,
      constraints: $related_constraints,
      crate_deps: $crate_deps,
      crate_doc: $crate_doc,
    }

    emit-output $data $f { || render-feature-detail-text $data $root }
  }
}

# ── describe guides ─────────────────────────────────────────────────────────────
# "Give me everything an actor needs to operate correctly in this project."
# Single deterministic JSON output: identity, axioms, practices, constraints,
# gate_state, dimensions, available_modes, actor_policy, language_guides,
# content_assets, templates, connections.

export def "describe guides" [
  --actor: string = "",   # Actor context: developer | agent | ci | admin
  --fmt: string = "",     # Output format: json | yaml | text (default json for agent, text otherwise)
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "json" }

  let identity   = (collect-identity $root)
  let axioms     = (collect-axioms $root)
  let practices  = (collect-practices $root)
  let gates      = (collect-gates $root)
  let dimensions = (collect-dimensions $root)
  let adrs       = (collect-adr-summary $root)
  let modes      = (scan-reflection-modes $root)
  let claude     = (scan-claude-capabilities $root)
  let manifest   = (load-manifest-safe $root)
  let conns      = (collect-connections $root)

  let constraints = (collect-constraint-summary $root)
  let actor_policy = (derive-actor-policy $gates $a)

  let content_assets        = ($manifest.content_assets? | default [])
  let templates             = ($manifest.templates? | default [])
  let manifest_capabilities = ($manifest.capabilities? | default [])
  let manifest_requirements = ($manifest.requirements? | default [])
  let manifest_critical_deps = ($manifest.critical_deps? | default [])

  # Fetch API surface from daemon; empty list if daemon is not reachable.
  let daemon_url = ($env.ONTOREF_DAEMON_URL? | default "http://127.0.0.1:7891")
  let api_surface = do {
    let r = (do { ^curl -sf --max-time 2 $"($daemon_url)/api/catalog" } | complete)
    if $r.exit_code == 0 {
      let resp = (do { $r.stdout | from json } | complete)
      if $resp.exit_code == 0 {
        let all = ($resp.stdout.routes? | default [])
        if ($a | is-not-empty) {
          $all | where { |route| $route.actors | any { |act| $act == $a } }
        } else {
          $all
        }
      } else {
        []
      }
    } else {
      []
    }
  }

  let authoring_conventions = {
    migration_rule: "Any change to templates/, reflection/schemas/*.ncl, .claude/CLAUDE.md, or consumer-facing reflection/modes/ that consumers need to adopt MUST be accompanied by a new migration in reflection/migrations/. Migrations are the sole propagation mechanism — without one the change never reaches consumer projects.",
    migration_triggers: [
      "templates/ — always, templates are installed verbatim into consumer projects",
      "reflection/schemas/*.ncl — if adds required fields consumers must populate",
      ".claude/CLAUDE.md — if the section should exist in all consumer CLAUDE.md files",
      "reflection/modes/*.ncl — if the mode is part of the adoption surface",
      "adrs/ — only if the ADR introduces a constraint consumers must satisfy",
    ],
    migration_not_needed: [
      "crates/ — Rust implementation, not copied to consumers",
      "reflection/modules/ — runtime behavior delivered via installed binary",
      "internal refactors that do not affect the consumer-visible protocol surface",
    ],
    migration_authoring: "ontoref migrate list | last — check last id, increment. Schema: { id, slug, description, check: {tag, ...}, instructions }. check.tag: FileExists | Grep | NuCmd. NuCmd must be valid Nushell — no bash operators.",
  }

  # Domain ontology layer — cross-project relationships from manifest
  let d_orig = ($manifest.domain_origin?   | default {})
  let d_prov = ($manifest.domain_provides? | default {})
  let has_orig_g = ($d_orig | is-not-empty) and ($d_orig.id? | is-not-empty)
  let has_prov_g = ($d_prov | is-not-empty) and ($d_prov.id? | is-not-empty)
  let impls_for_guides = if $has_prov_g {
    find-domain-implementations ($d_prov.id? | default "")
  } else { [] }
  let orig_path_g = ($d_orig.path? | default "")
  let fw_constraints_g = if $has_orig_g and ($orig_path_g | is-not-empty) {
    load-framework-hard-constraints $orig_path_g
  } else if $has_prov_g {
    load-framework-hard-constraints $root
  } else { [] }
  let integrated_g = ($d_orig.integrated? | default true)
  let contract_kind_g = ($d_prov.kind? | default ($d_orig.kind? | default "Ncl") | into string)
  let advisory_g: string = if $has_orig_g and not $integrated_g {
    $"EXTERNAL domain contract \(($contract_kind_g)\): .ontology/ is independent of the code-level contract. Verify all changes against framework_constraints before modifying the implementation layer."
  } else if $has_orig_g and $integrated_g {
    "INTEGRATED domain: this project's .ontology/ IS the domain implementation."
  } else if $has_prov_g {
    "FRAMEWORK: changes to the contract surface may affect registered implementations."
  } else { "" }
  let domain_layer = {
    role:                  (if $has_prov_g { "framework" } else if $has_orig_g { "implementation" } else { "none" }),
    domain_provides:       $d_prov,
    domain_origin:         $d_orig,
    implementations:       $impls_for_guides,
    framework_constraints: $fw_constraints_g,
    agent_advisory:        $advisory_g,
  }

  let data = {
    identity:       $identity,
    axioms:         $axioms,
    practices:      $practices,
    constraints:    $constraints,
    gate_state:     $gates,
    dimensions:     $dimensions,
    adrs:           $adrs,
    available_modes: $modes,
    actor_policy:   $actor_policy,
    language_guides: $claude,
    content_assets:   $content_assets,
    templates:        $templates,
    connections:      $conns,
    api_surface:      $api_surface,
    capabilities:     $manifest_capabilities,
    requirements:     $manifest_requirements,
    critical_deps:    $manifest_critical_deps,
    authoring_conventions: $authoring_conventions,
    domain_layer:     $domain_layer,
  }

  emit-output $data $f {||
    print $"=== Project Guides: ($identity.name) [actor: ($a)] ==="
    print ""
    print $"Identity:   ($identity.name) / ($identity.kind)"
    print $"Axioms:     ($axioms | length)"
    print $"Practices:  ($practices | length)"
    print $"Modes:      ($modes | length)"
    print $"Gates:      ($gates | length) active"
    print $"Connections: ($conns | length)"
    print $"API surface: ($api_surface | length) endpoints visible to actor"
    print ""
    print "Actor policy:"
    print ($actor_policy | table)
    print ""
    print "Constraint summary:"
    print ($constraints | table)
  }
}

# ── describe api ────────────────────────────────────────────────────────────────
# "What HTTP endpoints does the daemon expose? How do I call them?"
# Queries GET /api/catalog from the daemon and renders the full surface.

export def "describe api" [
  --actor: string = "",     # Filter to endpoints whose actors include this role
  --tag: string = "",       # Filter by tag (e.g. "graph", "describe", "auth")
  --auth: string = "",      # Filter by auth level: none | viewer | admin
  --fmt: string = "",       # Output format: text* | json
]: nothing -> nothing {
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }
  let daemon_url = ($env.ONTOREF_DAEMON_URL? | default "http://127.0.0.1:7891")

  let result = (do { http get $"($daemon_url)/api/catalog" } | complete)
  if $result.exit_code != 0 {
    print $"  (ansi red)Daemon unreachable at ($daemon_url) — is it running?(ansi reset)"
    return
  }

  let resp = ($result.stdout | from json)
  mut routes = ($resp.routes? | default [])

  if ($actor | is-not-empty) {
    $routes = ($routes | where { |r| $r.actors | any { |act| $act == $actor } })
  }
  if ($tag | is-not-empty) {
    $routes = ($routes | where { |r| $r.tags | any { |t| $t == $tag } })
  }
  if ($auth | is-not-empty) {
    $routes = ($routes | where auth == $auth)
  }

  let data = { count: ($routes | length), routes: $routes }

  emit-output $data $f { || render-api-text $data }
}

def render-api-text [data: record]: nothing -> nothing {
  print $"(ansi white_bold)Daemon API surface(ansi reset)  ($data.count) endpoints"
  print ""

  # Group by first tag for readable sectioning
  let grouped = ($data.routes | group-by { |r| if ($r.tags | is-empty) { "other" } else { $r.tags | first } })

  for section in ($grouped | transpose key value | sort-by key) {
    print $"(ansi cyan_bold)── ($section.key | str upcase) ──────────────────────────────────────(ansi reset)"
    for r in $section.value {
      let auth_badge = match $r.auth {
        "none"   => $"(ansi dark_gray)[open](ansi reset)",
        "viewer" => $"(ansi yellow)[viewer](ansi reset)",
        "admin"  => $"(ansi red)[admin](ansi reset)",
        _        => $"(ansi dark_gray)[?](ansi reset)"
      }
      let actors_str = ($r.actors | str join ", ")
      let feat = if ($r.feature | is-not-empty) { $"  (ansi dark_gray)feature:($r.feature)(ansi reset)" } else { "" }
      print $"  (ansi white_bold)($r.method)(ansi reset) (ansi green)($r.path)(ansi reset)  ($auth_badge)($feat)"
      print $"    ($r.description)"
      if ($r.actors | is-not-empty) {
        print $"    (ansi dark_gray)actors: ($actors_str)(ansi reset)"
      }
      if ($r.params | is-not-empty) {
        for p in $r.params {
          let con = $"(ansi dark_gray)($p.constraint)(ansi reset)"
          print $"    (ansi dark_gray)· ($p.name) [($p.kind)] ($con) — ($p.description)(ansi reset)"
        }
      }
      print ""
    }
  }
}

# ── describe domain ─────────────────────────────────────────────────────────────
# "What domain ontology relationships does this project participate in?"
# For framework projects: shows declared contract + all registered implementations.
# For implementation projects: shows the origin framework + navigation hints.
# Scans ~/.config/ontoref/projects.ncl to resolve cross-project relationships.

export def "describe domain" [
  --fmt: string = "",     # Output format: text* | json
  --actor: string = "",
]: nothing -> nothing {
  let root  = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let manifest = (load-manifest-safe $root)
  let d_orig = ($manifest.domain_origin?   | default {})
  let d_prov = ($manifest.domain_provides? | default {})
  let has_orig = ($d_orig | is-not-empty) and ($d_orig.id? | is-not-empty)
  let has_prov = ($d_prov | is-not-empty) and ($d_prov.id? | is-not-empty)

  if not $has_orig and not $has_prov {
    print $"  (ansi yellow)No domain_origin or domain_provides declared in .ontology/manifest.ncl(ansi reset)"
    return
  }

  let impls = if $has_prov {
    find-domain-implementations ($d_prov.id? | default "")
  } else { [] }

  let orig_path = ($d_orig.path? | default "")

  let origin_project: record = if $has_orig and ($orig_path | is-not-empty) and ($orig_path | path exists) {
    let om = (load-manifest-safe $orig_path)
    {
      root: $orig_path,
      name: ($om.name? | default ($d_orig.name? | default "")),
      slug: ($om.slug? | default ($orig_path | path basename)),
    }
  } else { {} }

  # Load framework hard constraints — essential for agents working on implementations.
  # For framework projects: load from self. For implementations: load from origin.
  let fw_constraints = if $has_orig and ($orig_path | is-not-empty) {
    load-framework-hard-constraints $orig_path
  } else if $has_prov {
    load-framework-hard-constraints $root
  } else { [] }

  # Contract advisory for external (integrated = false) domain implementations.
  # Agents need an explicit signal that .ontology/ is independent of the code contract.
  let integrated = ($d_orig.integrated? | default true)
  let contract_kind = ($d_prov.kind? | default ($d_orig.kind? | default "Ncl") | into string)
  let agent_advisory: string = if $has_orig and not $integrated {
    $"This project implements an EXTERNAL domain contract \(($contract_kind)\). The .ontology/ describes the project independently — it does NOT capture the code-level contract. Before modifying the implementation layer, cross-reference constraints from the framework ADRs listed in framework_constraints."
  } else if $has_orig and $integrated {
    "This project's .ontology/ IS the domain implementation (integrated = true). Ontology and implementation are the same layer."
  } else if $has_prov {
    "This project defines a domain contract. Changes to the contract surface may affect registered implementations."
  } else { "" }

  let data = {
    role:                (if $has_prov { "framework" } else { "implementation" }),
    domain_provides:     $d_prov,
    domain_origin:       $d_orig,
    origin_project:      $origin_project,
    implementations:     $impls,
    framework_constraints: $fw_constraints,
    agent_advisory:      $agent_advisory,
  }

  emit-output $data $f { || render-domain-text $data $root }
}

def render-domain-text [data: record, root: string]: nothing -> nothing {
  let sep = "──────────────────────────────────────────────────────────────────"
  print ""
  print "DOMAIN ONTOLOGY LAYER"
  print "══════════════════════════════════════════════════════════════════"

  # Agent advisory — always first when present
  let advisory = ($data.agent_advisory? | default "")
  if ($advisory | is-not-empty) {
    print $"(ansi yellow_bold)AGENT ADVISORY(ansi reset)"
    print $sep
    print $"  ($advisory)"
    print ""
  }

  if $data.role == "framework" {
    let prov    = $data.domain_provides
    let prov_id = ($prov.id? | default "")
    let prov_nm = ($prov.name? | default "")
    let prov_k  = ($prov.kind? | default "Ncl" | into string)
    print $"  Role:     (ansi cyan_bold)framework / provider(ansi reset)"
    print $"  Domain:   (ansi white_bold)($prov_id)(ansi reset)  ($prov_nm)  [($prov_k)]"
    let schema = ($prov.schema_path? | default "")
    if ($schema | is-not-empty) { print $"  Schemas:  ($schema)  ← contract surface for implementations" }
    let impl_kind = ($prov.impl_repo_kind? | default "")
    if ($impl_kind | is-not-empty) { print $"  impl_for: ($impl_kind) repo_kind" }
    let desc = ($prov.description? | default "")
    if ($desc | is-not-empty) { print $"  ($desc)" }

    print ""
    if ($data.implementations | is-empty) {
      print $"  (ansi dark_gray)No registered implementations found in ~/.config/ontoref/projects.ncl(ansi reset)"
    } else {
      print $"KNOWN IMPLEMENTATIONS  ($data.implementations | length) registered"
      print $sep
      for impl in $data.implementations {
        let integ = if $impl.integrated { "integrated" } else { $"(ansi yellow)external(ansi reset) — .ontology/ is independent of code contract" }
        print $"  (ansi green)($impl.slug)(ansi reset)  ($impl.root)"
        if ($impl.extends | is-not-empty) {
          print $"    extends:  ($impl.extends | str join ', ')"
        }
        print $"    type:     ($integ)"
        print $"    navigate: ONTOREF_PROJECT_ROOT=($impl.root) ore describe project"
      }
    }

  } else {
    let orig      = $data.domain_origin
    let orig_id   = ($orig.id? | default "")
    let orig_nm   = ($orig.name? | default "")
    let integrated = ($orig.integrated? | default true)
    let integ_str = if $integrated { "integrated" } else { $"(ansi yellow)external(ansi reset)" }
    let orig_path = ($orig.path? | default "")
    let ext       = ($orig.extends? | default [])

    print $"  Role:     (ansi cyan_bold)implementation(ansi reset)"
    print $"  Domain:   (ansi white_bold)($orig_id)(ansi reset)  ($orig_nm)"
    print $"  Type:     ($integ_str)"
    if ($ext | is-not-empty) {
      print $"  Extends:  ($ext | str join ', ')"
    }

    print ""
    print $"ORIGIN FRAMEWORK"
    print $sep
    if ($data.origin_project | is-empty) {
      if ($orig_path | is-not-empty) {
        print $"  (ansi yellow)Path declared but not reachable:(ansi reset) ($orig_path)"
      } else {
        print $"  (ansi dark_gray)No path declared — cannot resolve origin project(ansi reset)"
      }
    } else {
      let op = $data.origin_project
      print $"  (ansi green)($op.slug)(ansi reset)  ($op.root)"
      print $"    navigate: ONTOREF_PROJECT_ROOT=($op.root) ore describe project"
      print $"    navigate: ONTOREF_PROJECT_ROOT=($op.root) ore describe capabilities"
      print $"    navigate: ONTOREF_PROJECT_ROOT=($op.root) ore describe domain"
    }
  }

  # Framework hard constraints — shown for both roles
  let fc = ($data.framework_constraints? | default [])
  if ($fc | is-not-empty) {
    print ""
    let src_label = if $data.role == "framework" { "HARD CONSTRAINTS (own ADRs)" } else { "FRAMEWORK HARD CONSTRAINTS (inherited)" }
    print $src_label
    print $sep
    for c in $fc {
      print $"  [($c.adr_id)] ($c.adr_title)"
      print $"    ($c.description)"
      if ($c.check_hint | is-not-empty) {
        print $"    Check: ($c.check_hint)"
      }
    }
  }

  print ""
}

# ── describe state ──────────────────────────────────────────────────────────────
# "What FSM dimensions exist and where are they currently?"
# Reads .ontology/state.ncl and prints each dimension with current/desired state,
# horizon, and whether the desired state has been reached.

export def "describe state" [
  id?: string,         # Dimension id to detail with transitions (omit for list)
  --fmt: string = "",  # Output format: text* | json
  --actor: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let dims = (collect-dimensions $root)
  if ($dims | is-empty) {
    print "  No .ontology/state.ncl found or no dimensions declared."
    return
  }

  if ($id | is-empty) or ($id == "") {
    let data = { dimensions: $dims }
    emit-output $data $f { ||
      print $"(ansi white_bold)FSM Dimensions(ansi reset)  ($dims | length) total"
      print ""
      for d in $dims {
        let reached = $d.reached? | default false
        let status = if $reached {
          $"(ansi green)✓ reached(ansi reset)"
        } else {
          $"(ansi yellow)→ in progress(ansi reset)"
        }
        print $"  (ansi white_bold)($d.id)(ansi reset)  ($status)"
        print $"    (ansi cyan)($d.name)(ansi reset)  horizon: ($d.horizon)"
        print $"    current: (ansi white)($d.current_state)(ansi reset)  desired: (ansi dark_gray)($d.desired_state)(ansi reset)"
        print ""
      }
    }
  } else {
    let ip = (nickel-import-path $root)
    let state = (daemon-export-safe $"($root)/.ontology/state.ncl" --import-path $ip)
    if $state == null {
      print "  Failed to export .ontology/state.ncl"
      return
    }
    let target = ($state.dimensions? | default [] | where id == $id)
    if ($target | is-empty) {
      let avail = ($dims | get id | str join ", ")
      print $"  Dimension '($id)' not found. Available: ($avail)"
      return
    }
    let dim = ($target | first)
    let transitions = ($dim.transitions? | default [])
    let data = {
      id: $dim.id,
      name: $dim.name,
      description: ($dim.description? | default ""),
      current_state: $dim.current_state,
      desired_state: $dim.desired_state,
      horizon: ($dim.horizon? | default ""),
      reached: ($dim.current_state == $dim.desired_state),
      coupled_with: ($dim.coupled_with? | default []),
      transitions: ($transitions | each { |t| {
        from: $t.from,
        to: $t.to,
        condition: ($t.condition? | default ""),
        catalyst: ($t.catalyst? | default ""),
        blocker: ($t.blocker? | default ""),
      }}),
    }
    emit-output $data $f { ||
      let reached = $data.reached
      print $"(ansi white_bold)($data.id)(ansi reset)"
      print $"  (ansi cyan)($data.name)(ansi reset)  horizon: ($data.horizon)"
      print $"  ($data.description)"
      print ""
      print $"  current : (ansi white_bold)($data.current_state)(ansi reset)"
      let status_badge = if $reached { $"(ansi green)✓ reached(ansi reset)" } else { $"(ansi yellow)in progress(ansi reset)" }
      print $"  desired : ($data.desired_state)  ($status_badge)"
      if ($data.coupled_with | is-not-empty) {
        print $"  coupled : ($data.coupled_with | str join ', ')"
      }
      if ($data.transitions | is-not-empty) {
        print ""
        print $"  (ansi white_bold)Transitions(ansi reset)"
        for t in $data.transitions {
          let marker = if $t.from == $data.current_state { $"(ansi green)▶(ansi reset)" } else { " " }
          print $"  ($marker) ($t.from) → ($t.to)"
          if ($t.condition | is-not-empty) { print $"      condition : ($t.condition)" }
          if ($t.catalyst | is-not-empty)  { print $"      catalyst  : ($t.catalyst)" }
          if ($t.blocker | is-not-empty) and ($t.blocker != "none") {
            print $"      (ansi yellow)blocker   : ($t.blocker)(ansi reset)"
          }
        }
      }
    }
  }
}

# Scan src/**/*.rs for `pub` items and check for preceding `///` doc comments.
# Returns { total: int, documented: int, percent: int }.
def crate-doc-coverage [crate_full_path: string]: nothing -> record {
  let src_dir = $"($crate_full_path)/src"
  if not ($src_dir | path exists) { return { total: 0, documented: 0, percent: 0 } }

  let rs_files = (glob $"($src_dir)/**/*.rs")
  if ($rs_files | is-empty) { return { total: 0, documented: 0, percent: 0 } }

  mut total = 0
  mut documented = 0

  for rs in $rs_files {
    let lines = (open $rs | lines)
    let indexed = ($lines | enumerate)
    for row in $indexed {
      let trimmed = ($row.item | str trim)
      # Match `pub fn`, `pub struct`, `pub enum`, `pub trait`, `pub type`, `pub const`, `pub mod`.
      let is_pub = (
        ($trimmed | str starts-with "pub fn ") or
        ($trimmed | str starts-with "pub struct ") or
        ($trimmed | str starts-with "pub enum ") or
        ($trimmed | str starts-with "pub trait ") or
        ($trimmed | str starts-with "pub type ") or
        ($trimmed | str starts-with "pub const ") or
        ($trimmed | str starts-with "pub mod ") or
        ($trimmed | str starts-with "pub async fn ") or
        ($trimmed =~ '^\s*pub\s*\(crate\)')
      )
      if not $is_pub { continue }
      $total = ($total + 1)
      # Look at the previous non-empty line.
      if $row.index > 0 {
        let prev = ($indexed | where { |r| $r.index < $row.index } | reverse | where { |r| ($r.item | str trim | is-not-empty) } | if ($in | is-not-empty) { first } else { null })
        if ($prev != null) {
          let prev_trim = ($prev.item | str trim)
          if ($prev_trim | str starts-with "///") or ($prev_trim | str starts-with "#[") {
            # Check further back for /// if immediately preceded by an attribute
            if ($prev_trim | str starts-with "#[") {
              let before_attr = ($indexed | where { |r| $r.index < $prev.index } | reverse | where { |r| ($r.item | str trim | is-not-empty) } | if ($in | is-not-empty) { first } else { null })
              if ($before_attr != null) and (($before_attr.item | str trim) | str starts-with "///") {
                $documented = ($documented + 1)
              }
            } else {
              $documented = ($documented + 1)
            }
          }
        }
      }
    }
  }

  let pct = if $total > 0 { ($documented * 100 / $total | math round) } else { 100 }
  { total: $total, documented: $documented, percent: $pct }
}

# ── describe workspace ───────────────────────────────────────────────────────────
# "What crates are in this workspace and how do they depend on each other?"
# Reads workspace Cargo.toml + member manifests. Shows only intra-workspace deps —
# external crate dependencies are omitted to keep the output focused.

export def "describe workspace" [
  --fmt: string = "",  # Output format: text* | json
  --actor: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let ws_toml = $"($root)/Cargo.toml"
  if not ($ws_toml | path exists) {
    print "  No Cargo.toml found at project root — not a Rust workspace."
    return
  }

  let ws = (open $ws_toml)
  let member_globs = ($ws | get -o workspace.members | default [])
  if ($member_globs | is-empty) {
    print "  No workspace.members declared in Cargo.toml."
    return
  }

  # Collect all member crate names + features.
  mut crates = []
  for mg in $member_globs {
    let expanded = (glob $"($root)/($mg)/Cargo.toml")
    for ct in $expanded {
      let c = (open $ct)
      let name = ($c | get -o package.name | default ($ct | path dirname | path basename))
      let features = ($c | get -o features | default {} | columns)
      let all_deps = ($c | get -o dependencies | default {})
      let crate_path = ($ct | path dirname | path relative-to $root)
      let crate_doc = (find-crate-doc-for-node $root [$crate_path])
      let doc_coverage = (crate-doc-coverage $"($root)/($crate_path)")
      $crates = ($crates | append [{
        name: $name,
        features: $features,
        all_deps: $all_deps,
        path: $crate_path,
        crate_doc: $crate_doc,
        doc_coverage: $doc_coverage,
      }])
    }
  }

  let crate_names = ($crates | get name)

  # Build intra-workspace dep edges.
  let crates_with_ws_deps = ($crates | each { |cr|
    let dep_names = (try { $cr.all_deps | columns } catch { [] })
    let ws_deps = ($dep_names | where { |d| $d in $crate_names })
    {
      name:         $cr.name,
      features:     $cr.features,
      path:         $cr.path,
      depends_on:   $ws_deps,
      crate_doc:    $cr.crate_doc,
      doc_coverage: $cr.doc_coverage,
    }
  })

  let data = { crates: $crates_with_ws_deps }

  emit-output $data $f { ||
    print $"(ansi white_bold)Workspace Crates(ansi reset)  ($crates_with_ws_deps | length) members"
    print ""
    for cr in $crates_with_ws_deps {
      let cov = $cr.doc_coverage
      let pct = $cov.percent
      let cov_color = if $pct >= 80 { ansi green } else if $pct >= 50 { ansi yellow } else { ansi red }
      let doc_tag = if ($cr.crate_doc | is-not-empty) { " ✓ //! doc" } else { " ✗ no //! doc" }
      print $"  (ansi white_bold)($cr.name)(ansi reset)  (ansi dark_gray)($cr.path)(ansi reset)(ansi cyan)($doc_tag)(ansi reset)"
      print $"    doc coverage : ($cov_color)($pct)%(ansi reset)  ($cov.documented)/($cov.total) public items"
      if ($cr.features | is-not-empty) {
        print $"    features     : ($cr.features | str join ', ')"
      }
      if ($cr.depends_on | is-not-empty) {
        print $"    deps         : ($cr.depends_on | each { |d| $'(ansi cyan)($d)(ansi reset)' } | str join ', ')"
      }
      print ""
    }
  }
}

# ── describe diff ───────────────────────────────────────────────────────────────
# "What changed in the ontology since the last commit?"
# Compares the current working-tree core.ncl against the HEAD-committed version.
# Outputs structured added/removed/changed diffs for nodes and edges.

export def "describe diff" [
  --fmt: string = "",   # Output format: text* | json
  --file: string = "",  # Ontology file to diff (relative to project root, default .ontology/core.ncl)
]: nothing -> nothing {
  let root = (project-root)
  let f = if ($fmt | is-not-empty) { $fmt } else { "text" }
  let rel = if ($file | is-not-empty) { $file } else { ".ontology/core.ncl" }

  let current = (load-ontology-safe $root)
  let committed = (diff-export-committed $rel $root)

  let curr_nodes = ($current.nodes? | default [] | each { |n| { id: $n.id, name: ($n.name? | default ""), description: ($n.description? | default ""), level: ($n.level? | default ""), pole: ($n.pole? | default ""), invariant: ($n.invariant? | default false) } })
  let comm_nodes = ($committed.nodes? | default [] | each { |n| { id: $n.id, name: ($n.name? | default ""), description: ($n.description? | default ""), level: ($n.level? | default ""), pole: ($n.pole? | default ""), invariant: ($n.invariant? | default false) } })

  let curr_ids = ($curr_nodes | get id)
  let comm_ids = ($comm_nodes | get id)

  let nodes_added   = ($curr_nodes | where { |n| not ($comm_ids | any { |id| $id == $n.id }) })
  let nodes_removed = ($comm_nodes | where { |n| not ($curr_ids | any { |id| $id == $n.id }) })

  # Nodes present in both — compare field by field.
  let both_ids = ($curr_ids | where { |id| $comm_ids | any { |cid| $cid == $id } })
  let nodes_changed = ($both_ids | each { |id|
    let curr = ($curr_nodes | where id == $id | first)
    let prev = ($comm_nodes | where id == $id | first)
    if ($curr.name != $prev.name or $curr.description != $prev.description or $curr.level != $prev.level or $curr.pole != $prev.pole or $curr.invariant != $prev.invariant) {
      { id: $id, before: $prev, after: $curr }
    } else {
      null
    }
  } | compact)

  let curr_edges = ($current.edges? | default [] | each { |e|
    let ef = ($e.from? | default "")
    let et = ($e.to?   | default "")
    let ek = ($e.kind? | default "")
    { key: $"($ef)->($et)[($ek)]", from: $ef, to: $et, kind: $ek }
  })
  let comm_edges = ($committed.edges? | default [] | each { |e|
    let ef = ($e.from? | default "")
    let et = ($e.to?   | default "")
    let ek = ($e.kind? | default "")
    { key: $"($ef)->($et)[($ek)]", from: $ef, to: $et, kind: $ek }
  })

  let curr_ekeys = ($curr_edges | get key)
  let comm_ekeys = ($comm_edges | get key)

  let edges_added   = ($curr_edges | where { |e| not ($comm_ekeys | any { |k| $k == $e.key }) })
  let edges_removed = ($comm_edges | where { |e| not ($curr_ekeys | any { |k| $k == $e.key }) })

  let data = {
    file:          $rel,
    nodes_added:   $nodes_added,
    nodes_removed: $nodes_removed,
    nodes_changed: $nodes_changed,
    edges_added:   $edges_added,
    edges_removed: $edges_removed,
    summary: {
      nodes_added:   ($nodes_added | length),
      nodes_removed: ($nodes_removed | length),
      nodes_changed: ($nodes_changed | length),
      edges_added:   ($edges_added | length),
      edges_removed: ($edges_removed | length),
    },
  }

  emit-output $data $f { || render-diff-text $data }
}

def diff-export-committed [rel_path: string, root: string]: nothing -> record {
  let ip = (nickel-import-path $root)
  let committed = (vcs show-committed $"($root)/($rel_path)")
  if ($committed | is-empty) { return {} }
  let mk = (do { ^mktemp } | complete)
  if $mk.exit_code != 0 { return {} }
  let tmp = ($mk.stdout | str trim)
  $committed | save --force $tmp
  let r = (do { ^nickel export --format json --import-path $ip $tmp } | complete)
  do { ^rm -f $tmp } | complete | ignore
  if $r.exit_code != 0 { return {} }
  $r.stdout | from json
}

def render-diff-text [data: record]: nothing -> nothing {
  let s = $data.summary
  let total = ($s.nodes_added + $s.nodes_removed + $s.nodes_changed + $s.edges_added + $s.edges_removed)

  print $"(ansi white_bold)Ontology diff vs HEAD:(ansi reset) ($data.file)"
  print ""

  if $total == 0 {
    print $"  (ansi dark_gray)No changes — working tree matches HEAD.(ansi reset)"
    return
  }

  if $s.nodes_added > 0 {
    print $"(ansi green_bold)Nodes added ($s.nodes_added):(ansi reset)"
    for n in $data.nodes_added {
      print $"  + (ansi green)($n.id)(ansi reset)  [($n.level)]  ($n.name)"
    }
    print ""
  }

  if $s.nodes_removed > 0 {
    print $"(ansi red_bold)Nodes removed ($s.nodes_removed):(ansi reset)"
    for n in $data.nodes_removed {
      print $"  - (ansi red)($n.id)(ansi reset)  [($n.level)]  ($n.name)"
    }
    print ""
  }

  if $s.nodes_changed > 0 {
    print $"(ansi yellow_bold)Nodes changed ($s.nodes_changed):(ansi reset)"
    for c in $data.nodes_changed {
      print $"  ~ (ansi yellow)($c.id)(ansi reset)"
      if $c.before.name != $c.after.name {
        print $"    name:        (ansi dark_gray)($c.before.name)(ansi reset) → ($c.after.name)"
      }
      if $c.before.description != $c.after.description {
        let prev = ($c.before.description | str substring 0..60)
        let curr = ($c.after.description | str substring 0..60)
        print $"    description: (ansi dark_gray)($prev)…(ansi reset) → ($curr)…"
      }
      if $c.before.level != $c.after.level {
        print $"    level:       (ansi dark_gray)($c.before.level)(ansi reset) → ($c.after.level)"
      }
      if $c.before.pole != $c.after.pole {
        print $"    pole:        (ansi dark_gray)($c.before.pole)(ansi reset) → ($c.after.pole)"
      }
      if $c.before.invariant != $c.after.invariant {
        print $"    invariant:   (ansi dark_gray)($c.before.invariant)(ansi reset) → ($c.after.invariant)"
      }
    }
    print ""
  }

  if $s.edges_added > 0 {
    print $"(ansi cyan_bold)Edges added ($s.edges_added):(ansi reset)"
    for e in $data.edges_added {
      print $"  + (ansi cyan)($e.from)(ansi reset) →[($e.kind)]→ (ansi cyan)($e.to)(ansi reset)"
    }
    print ""
  }

  if $s.edges_removed > 0 {
    print $"(ansi magenta_bold)Edges removed ($s.edges_removed):(ansi reset)"
    for e in $data.edges_removed {
      print $"  - (ansi magenta)($e.from)(ansi reset) →[($e.kind)]→ (ansi magenta)($e.to)(ansi reset)"
    }
    print ""
  }
}

# ── Collectors ──────────────────────────────────────────────────────────────────

def collect-identity [root: string]: nothing -> record {
  # From Cargo.toml or manifest
  let cargo = $"($root)/Cargo.toml"
  let name = if ($cargo | path exists) {
    let cargo_data = (open $cargo)
    if ($cargo_data | get -o package.name | is-not-empty) {
      $cargo_data | get package.name
    } else if ($cargo_data | get -o workspace | is-not-empty) {
      $root | path basename
    } else {
      $root | path basename
    }
  } else {
    $root | path basename
  }

  let manifest = (load-manifest-safe $root)
  let kind = if ($manifest | is-not-empty) { $manifest.repo_kind? | default "" } else { "" }
  let description = if ($manifest | is-not-empty) { $manifest.description? | default "" } else { "" }
  let domain_origin   = if ($manifest | is-not-empty) { $manifest.domain_origin?   | default {} } else { {} }
  let domain_provides = if ($manifest | is-not-empty) { $manifest.domain_provides? | default {} } else { {} }

  {
    name: $name,
    kind: $kind,
    description: $description,
    root: $root,
    has_ontology: ($"($root)/.ontology/core.ncl" | path exists),
    has_adrs: ((glob $"($root)/adrs/adr-*.ncl" | length) > 0),
    has_reflection: ($"($root)/reflection" | path exists),
    has_manifest: ($"($root)/.ontology/manifest.ncl" | path exists),
    has_coder: ($"($root)/.coder" | path exists),
    domain_origin:   $domain_origin,
    domain_provides: $domain_provides,
  }
}

def collect-axioms [root: string]: nothing -> list<record> {
  let ontology = (load-ontology-safe $root)
  if ($ontology | is-empty) { return [] }
  $ontology.nodes? | default [] | where { |n|
    ($n.invariant? | default false) == true
  } | each { |n| { id: $n.id, name: $n.name, description: $n.description } }
}

def collect-tensions [root: string]: nothing -> list<record> {
  let ontology = (load-ontology-safe $root)
  if ($ontology | is-empty) { return [] }
  $ontology.nodes? | default [] | where { |n|
    ($n.level? | default "") == "Tension"
  } | each { |n| { id: $n.id, name: $n.name, description: $n.description } }
}

def collect-practices [root: string]: nothing -> list<record> {
  let ontology = (load-ontology-safe $root)
  if ($ontology | is-empty) { return [] }
  $ontology.nodes? | default [] | where { |n|
    ($n.level? | default "") == "Practice"
  } | each { |n| {
    id: $n.id,
    name: $n.name,
    description: $n.description,
    artifact_paths: ($n.artifact_paths? | default []),
  }}
}

def collect-gates [root: string]: nothing -> list<record> {
  let gate_file = $"($root)/.ontology/gate.ncl"
  if not ($gate_file | path exists) { return [] }
  let ip = (nickel-import-path $root)
  let gate = (daemon-export-safe $gate_file --import-path $ip)
  if $gate == null { return [] }
  $gate.membranes? | default [] | where { |m| ($m.active? | default false) == true }
  | each { |m| {
    id: $m.id,
    name: $m.name,
    permeability: ($m.permeability? | default ""),
    accepts: ($m.accepts? | default []),
  }}
}

def collect-dimensions [root: string]: nothing -> list<record> {
  let state_file = $"($root)/.ontology/state.ncl"
  if not ($state_file | path exists) { return [] }
  let ip = (nickel-import-path $root)
  let state = (daemon-export-safe $state_file --import-path $ip)
  if $state == null { return [] }
  $state.dimensions? | default [] | each { |d| {
    id: $d.id,
    name: $d.name,
    current_state: $d.current_state,
    desired_state: $d.desired_state,
    horizon: ($d.horizon? | default ""),
    reached: ($d.current_state == $d.desired_state),
  }}
}

def collect-adr-summary [root: string]: nothing -> list<record> {
  let files = (glob $"($root)/adrs/adr-*.ncl")
  let ip = (nickel-import-path $root)
  $files | each { |f|
    let adr = (daemon-export-safe $f --import-path $ip)
    if $adr != null {
      {
        id: ($adr.id? | default ""),
        title: ($adr.title? | default ""),
        status: ($adr.status? | default ""),
        constraint_count: ($adr.constraints? | default [] | length),
      }
    } else { null }
  } | compact
}

def collect-hard-constraints [root: string]: nothing -> list<record> {
  let files = (glob $"($root)/adrs/adr-*.ncl")
  let ip = (nickel-import-path $root)
  $files | each { |f|
    let adr = (daemon-export-safe $f --import-path $ip)
    if $adr != null {
      if ($adr.status? | default "") == "Accepted" {
        let constraints = ($adr.constraints? | default [])
        $constraints | where { |c| ($c.severity? | default "") == "Hard" }
        | each { |c| {
          adr_id: ($adr.id? | default ""),
          check_hint: ($c.check_hint? | default ""),
          description: ($c.description? | default ""),
        }}
      } else { [] }
    } else { [] }
  } | flatten
}

def collect-constraint-summary [root: string]: nothing -> list<record> {
  let files = (glob $"($root)/adrs/adr-*.ncl")
  let ip = (nickel-import-path $root)
  $files | each { |f|
    let adr = (daemon-export-safe $f --import-path $ip)
    if $adr != null {
      if ($adr.status? | default "") == "Accepted" {
        let constraints = ($adr.constraints? | default [])
        $constraints | each { |c| {
          adr_id:      ($adr.id? | default ""),
          severity:    ($c.severity? | default ""),
          description: ($c.description? | default ""),
          check_tag:   ($c.check?.tag? | default ($c.check_hint? | default "")),
        }}
      } else { [] }
    } else { [] }
  } | flatten
}

def collect-connections [root: string]: nothing -> list<record> {
  let conn_file = $"($root)/.ontology/connections.ncl"
  if not ($conn_file | path exists) { return [] }
  let ip = (nickel-import-path $root)
  let conn = (daemon-export-safe $conn_file --import-path $ip)
  if $conn == null { return [] }
  $conn.connections? | default []
}

# Derive what an actor is allowed to do based on the active gate membranes.
# Permeability: Open → full access; Controlled/Locked → restricted; Closed → read-only.
def derive-actor-policy [gates: list<record>, actor: string]: nothing -> record {
  let is_agent = ($actor == "agent")
  let is_ci    = ($actor == "ci")

  # Find the most restrictive membrane that constrains the actor.
  let permeabilities = ($gates | get -o permeability | compact | uniq)

  let most_restrictive = if ($permeabilities | any { |p| $p == "Closed" }) {
    "Closed"
  } else if ($permeabilities | any { |p| $p == "Locked" }) {
    "Locked"
  } else if ($permeabilities | any { |p| $p == "Controlled" }) {
    "Controlled"
  } else {
    "Open"
  }

  let base_open = ($most_restrictive == "Open")
  let base_controlled = ($most_restrictive == "Controlled" or $most_restrictive == "Open")

  {
    actor:              $actor,
    gate_permeability:  $most_restrictive,
    can_read_ontology:  true,
    can_read_adrs:      true,
    can_read_manifest:  true,
    can_run_modes:      (if $is_agent { $base_controlled } else { true }),
    can_modify_adrs:    (if ($is_agent or $is_ci) { $base_open } else { $base_controlled }),
    can_modify_ontology: (if ($is_agent or $is_ci) { $base_open } else { $base_controlled }),
    can_push_sync:      (if $is_agent { false } else { $base_controlled }),
  }
}

# ── Scanners ────────────────────────────────────────────────────────────────────

def scan-just-modules [root: string]: nothing -> list<record> {
  let justfile = $"($root)/justfile"
  if not ($justfile | path exists) { return [{ status: "no_justfile" }] }
  let content = (open $justfile --raw)
  let mod_lines = ($content | lines | where { |l| ($l | str starts-with "mod ") or ($l | str starts-with "mod? ") })
  let import_lines = ($content | lines | where { |l| ($l | str starts-with "import ") or ($l | str starts-with "import? ") })

  mut modules = []
  for line in $mod_lines {
    let parts = ($line | split row " " | where { |p| ($p | is-not-empty) })
    let name = if ($parts | length) >= 2 { $parts | get 1 | str replace "?" "" } else { "unknown" }
    $modules = ($modules | append { type: "mod", name: $name, line: $line })
  }
  for line in $import_lines {
    let parts = ($line | split row "'" | where { |p| ($p | is-not-empty) })
    let path = if ($parts | length) >= 2 { $parts | get 1 | str trim } else { "unknown" }
    let name = ($path | path basename | str replace ".just" "")
    $modules = ($modules | append { type: "import", name: $name, path: $path })
  }

  # Also check justfiles/ directory
  let justfiles_dir = $"($root)/justfiles"
  if ($justfiles_dir | path exists) {
    let files = (glob $"($justfiles_dir)/*.just")
    for f in $files {
      let name = ($f | path basename | str replace ".just" "")
      let already = ($modules | where { |m| $m.name == $name })
      if ($already | is-empty) {
        $modules = ($modules | append { type: "file_only", name: $name, path: ($f | str replace $root ".") })
      }
    }
  }

  $modules
}

def categorize-recipe [name: string]: nothing -> string {
  if ($name | str starts-with "ci") { "ci" } else if ($name | str starts-with "build") { "build" } else if ($name | str starts-with "test") or ($name == "test") { "test" } else if ($name | str starts-with "doc") { "docs" } else if ($name | str starts-with "deploy") { "deploy" } else if ($name | str starts-with "nickel") { "nickel" } else if ($name | str starts-with "install") or ($name | str starts-with "release") or ($name | str starts-with "package") or ($name | str starts-with "dist") { "distro" } else if ($name in ["fmt", "format", "lint", "watch", "dev", "setup", "setup-hooks", "clean"]) or ($name | str starts-with "fmt") or ($name | str starts-with "lint") or ($name | str starts-with "watch") { "dev" } else { "other" }
}

def scan-just-recipes [root: string]: nothing -> list<record> {
  let result = do { ^just --list --unsorted --justfile $"($root)/justfile" } | complete
  if $result.exit_code != 0 { return [] }
  $result.stdout | lines | where { |l| ($l | str trim | is-not-empty) and not ($l | str starts-with "Available") }
  | each { |l|
    let trimmed = ($l | str trim)
    let parts = ($trimmed | split row " # ")
    let name = ($parts | first | str trim)
    let desc = if ($parts | length) > 1 { $parts | skip 1 | str join " # " | str trim } else { "" }
    { name: $name, category: (categorize-recipe $name), description: $desc }
  }
}

def scan-ontoref-commands []: nothing -> list<string> {
  [
    "check", "form list", "form run",
    "mode list", "mode show", "mode run", "mode select",
    "adr list", "adr validate", "adr show", "adr accept",
    "constraint", "register",
    "backlog roadmap", "backlog list", "backlog add", "backlog done",
    "config show", "config verify", "config audit", "config apply",
    "sync scan", "sync diff", "sync propose", "sync apply", "sync audit",
    "coder init", "coder record", "coder log", "coder export", "coder triage",
    "manifest mode", "manifest publish", "manifest layers", "manifest consumers",
    "describe project", "describe capabilities", "describe constraints",
    "describe tools", "describe features", "describe impact", "describe why",
    "describe guides", "describe diff", "describe api",
    "describe state", "describe workspace", "describe domain",
  ]
}

def scan-reflection-modes [root: string]: nothing -> list<record> {
  let ontoref_modes = (glob $"($env.ONTOREF_ROOT)/reflection/modes/*.ncl")
  let project_modes = if $root != $env.ONTOREF_ROOT {
    glob $"($root)/reflection/modes/*.ncl"
  } else { [] }
  let all_modes = ($ontoref_modes | append $project_modes | uniq)

  $all_modes | each { |f|
    let mode_root = if ($f | str starts-with $root) and ($root != $env.ONTOREF_ROOT) { $root } else { $env.ONTOREF_ROOT }
    let ip = (nickel-import-path $mode_root)
    let m = (daemon-export-safe $f --import-path $ip)
    if $m != null {
      {
        id: ($m.id? | default ""),
        trigger: ($m.trigger? | default ""),
        steps: ($m.steps? | default [] | length),
        source: (if ($f | str starts-with $root) and ($root != $env.ONTOREF_ROOT) { "project" } else { "ontoref" }),
      }
    } else { null }
  } | compact
}

def scan-claude-capabilities [root: string]: nothing -> record {
  let claude_dir = $"($root)/.claude"
  if not ($claude_dir | path exists) {
    return { present: false }
  }

  let has_claude_md = ($"($claude_dir)/CLAUDE.md" | path exists)
  let guidelines = (glob $"($claude_dir)/guidelines/*/" | each { |d| $d | path basename })
  let commands = (glob $"($claude_dir)/commands/*.md" | length)
  let skills = (glob $"($claude_dir)/skills/*/" | each { |d| $d | path basename })
  let hooks = (glob $"($claude_dir)/hooks/*" | length)
  let agents = (glob $"($claude_dir)/agents/*.md" | each { |f| $f | path basename | str replace ".md" "" })
  let profiles = (glob $"($claude_dir)/profiles/*/" | each { |d| $d | path basename })
  let stacks = (glob $"($claude_dir)/stacks/*/" | each { |d| $d | path basename })

  {
    present: true,
    has_claude_md: $has_claude_md,
    guidelines: $guidelines,
    commands_count: $commands,
    skills: $skills,
    hooks_count: $hooks,
    agents: $agents,
    profiles: $profiles,
    stacks: $stacks,
  }
}

def scan-ci-tools [root: string]: nothing -> list<record> {
  let config_path = $"($root)/.typedialog/ci/config.ncl"
  if not ($config_path | path exists) { return [] }
  let ip = (nickel-import-path $root)
  let config = (daemon-export-safe $config_path --import-path $ip)
  if $config == null { return [] }
  let tools = ($config.ci?.tools? | default {})
  $tools | transpose name cfg | each { |t| {
    name: $t.name,
    enabled: ($t.cfg.enabled? | default false),
    install_method: ($t.cfg.install_method? | default "unknown"),
  }}
}

def scan-manifest-modes [root: string]: nothing -> list<record> {
  let manifest = (load-manifest-safe $root)
  if ($manifest | is-empty) { return [] }
  $manifest.operational_modes? | default [] | each { |m| {
    id: ($m.id? | default ""),
    description: ($m.description? | default ""),
    audit_level: ($m.audit_level? | default ""),
  }}
}

def scan-dev-tools [root: string]: nothing -> list<record> {
  mut tools = []
  if ($"($root)/bacon.toml" | path exists) {
    $tools = ($tools | append { tool: "bacon", config: "bacon.toml", purpose: "File watcher with diagnostics export" })
  }
  if ($"($root)/.cargo/config.toml" | path exists) {
    $tools = ($tools | append { tool: "cargo", config: ".cargo/config.toml", purpose: "Build system with custom aliases" })
  }
  if ($"($root)/.pre-commit-config.yaml" | path exists) {
    $tools = ($tools | append { tool: "pre-commit", config: ".pre-commit-config.yaml", purpose: "Pre-commit hook runner" })
  }
  if ($"($root)/deny.toml" | path exists) or ($"($root)/.cargo/deny.toml" | path exists) {
    $tools = ($tools | append { tool: "cargo-deny", config: "deny.toml", purpose: "License and advisory checker" })
  }
  if ($"($root)/rustfmt.toml" | path exists) or ($"($root)/.rustfmt.toml" | path exists) {
    $tools = ($tools | append { tool: "rustfmt", config: "rustfmt.toml", purpose: "Code formatter" })
  }
  if ($"($root)/clippy.toml" | path exists) or ($"($root)/.clippy.toml" | path exists) {
    $tools = ($tools | append { tool: "clippy", config: "clippy.toml", purpose: "Linter" })
  }
  $tools
}

def git-remote-slug [root: string]: nothing -> string {
  let url = (vcs remote-url)
  if ($url | is-empty) { return "" }
  if ($url | str contains "@") {
    # git@host:owner/repo.git
    $url | split row ":" | last | str replace -r '\.git$' "" | str trim
  } else {
    # https://host/owner/repo.git
    let parts = ($url | split row "/" | last 2)
    ($parts | str join "/") | str replace -r '\.git$' ""
  }
}

def scan-project-flags [root: string]: nothing -> record {
  let cargo_toml = $"($root)/Cargo.toml"
  let has_rust = ($cargo_toml | path exists)

  let crates = if $has_rust {
    let cargo = (open $cargo_toml)
    let members = ($cargo | get -o workspace.members | default [])
    if ($members | is-not-empty) {
      $members | each { |m|
        glob $"($root)/($m)/Cargo.toml"
      } | flatten | each { |f|
        let c = (open $f)
        $c | get -o package.name | default ($f | path dirname | path basename)
      }
    } else {
      let name = ($cargo | get -o package.name | default ($root | path basename))
      [$name]
    }
  } else { [] }

  let config_file = $"($root)/.ontoref/config.ncl"
  let has_nats = if ($config_file | path exists) {
    let ip = (nickel-import-path $root)
    let cfg = (daemon-export-safe $config_file --import-path $ip)
    if $cfg != null { $cfg.nats_events?.enabled? | default false } else { false }
  } else { false }

  let has_git = (vcs is-repo)
  let git_slug = if $has_git { git-remote-slug $root } else { "" }
  let has_git_remote = ($git_slug | is-not-empty)

  let open_prs = if $has_git_remote and (which gh | is-not-empty) {
    let r = do { ^gh pr list --repo $git_slug --state open --json number --jq length } | complete
    if $r.exit_code == 0 { $r.stdout | str trim | into int } else { 0 }
  } else { 0 }

  {
    has_rust: $has_rust,
    has_ui: (($"($root)/templates" | path exists) or ($"($root)/assets" | path exists)),
    has_mdbook: ($"($root)/docs/SUMMARY.md" | path exists),
    has_nats: $has_nats,
    has_precommit: ($"($root)/.pre-commit-config.yaml" | path exists),
    has_backlog: ($"($root)/reflection/backlog.ncl" | path exists),
    has_git_remote: $has_git_remote,
    git_slug: $git_slug,
    open_prs: $open_prs,
    crates: $crates,
  }
}

def count-backlog-pending [root: string]: nothing -> int {
  let file = $"($root)/reflection/backlog.ncl"
  if not ($file | path exists) { return 0 }
  let ip = (nickel-import-path $root)
  let backlog = (daemon-export-safe $file --import-path $ip)
  if $backlog == null { return 0 }
  ($backlog.items? | default [])
  | where { |i| ($i.status? | default "open") not-in ["done", "graduated"] }
  | length
}

def load-backlog-items [root: string]: nothing -> record {
  let file = $"($root)/reflection/backlog.ncl"
  if not ($file | path exists) { return { pending: 0, items: [] } }
  let ip = (nickel-import-path $root)
  let backlog = (daemon-export-safe $file --import-path $ip)
  if $backlog == null { return { pending: 0, items: [] } }
  let pending = (($backlog.items? | default [])
    | where { |i| ($i.status? | default "open") not-in ["done", "graduated"] })
  {
    pending: ($pending | length),
    items: ($pending | each { |i| {
      id:       ($i.id? | default ""),
      title:    ($i.title? | default ""),
      kind:     ($i.kind? | default ""),
      priority: ($i.priority? | default ""),
      status:   ($i.status? | default "open"),
    }}),
  }
}

def load-api-catalog-static [root: string, actor: string]: nothing -> list<record> {
  let catalog = $"($root)/artifacts/api-catalog-ontoref-daemon.ncl"
  if not ($catalog | path exists) { return [] }
  let ip = (nickel-import-path $root)
  let data = (daemon-export-safe $catalog --import-path $ip)
  if $data == null { return [] }
  let all = ($data.routes? | default [])
  let actor_cap = ($actor | str capitalize)
  if ($actor | is-not-empty) {
    $all | where { |r| $r.actors | any { |a| $a == $actor_cap } }
  } else {
    $all
  }
}

def scan-reflection-mode-dags [root: string]: nothing -> list<record> {
  let ontoref_modes = (glob $"($env.ONTOREF_ROOT)/reflection/modes/*.ncl")
  let project_modes = if $root != $env.ONTOREF_ROOT {
    glob $"($root)/reflection/modes/*.ncl"
  } else { [] }
  let all_modes = ($ontoref_modes | append $project_modes | uniq)

  $all_modes | each { |f|
    let mode_root = if ($f | str starts-with $root) and ($root != $env.ONTOREF_ROOT) { $root } else { $env.ONTOREF_ROOT }
    let ip = (nickel-import-path $mode_root)
    let m = (daemon-export-safe $f --import-path $ip)
    if $m != null {
      {
        id:      ($m.id? | default ""),
        trigger: ($m.trigger? | default ""),
        source:  (if ($f | str starts-with $root) and ($root != $env.ONTOREF_ROOT) { "project" } else { "ontoref" }),
        preconditions:  ($m.preconditions? | default []),
        guards:  ($m.guards? | default [] | each { |g| {
          id:       ($g.id? | default ""),
          reason:   ($g.reason? | default ""),
          severity: ($g.severity? | default "Block"),
        }}),
        steps:   ($m.steps? | default [] | each { |s| {
          id:         ($s.id? | default ""),
          actor:      ($s.actor? | default ""),
          action:     ($s.action? | default ""),
          cmd:        ($s.cmd? | default ""),
          on_error:   ($s.on_error?.strategy? | default "Stop"),
          verify:     ($s.verify? | default ""),
          note:       ($s.note? | default ""),
          depends_on: ($s.depends_on? | default [] | each { |d| $d.step? | default "" }),
        }}),
        postconditions: ($m.postconditions? | default []),
        converge: (if ($m.converge? | default null) != null {
          {
            max_iterations: ($m.converge.max_iterations? | default 3),
            strategy:       ($m.converge.strategy? | default "RetryFailed"),
          }
        } else { null }),
      }
    } else { null }
  } | compact
}

# ── Feature collectors ────────────────────────────────────────────────────────

def collect-cargo-features [root: string]: nothing -> list<record> {
  let cargo_toml = $"($root)/Cargo.toml"
  if not ($cargo_toml | path exists) { return [] }
  let cargo = (open $cargo_toml)
  let members = ($cargo | get -o workspace.members | default [])

  if ($members | is-empty) {
    let features = ($cargo | get -o features | default {})
    if ($features | columns | is-empty) { return [] }
    $features | transpose name deps | each { |f| {
      crate: ($cargo | get -o package.name | default ($root | path basename)),
      feature: $f.name,
      enables: ($f.deps | str join ", "),
    }}
  } else {
    mut result = []
    for member in $members {
      let expanded = (glob $"($root)/($member)/Cargo.toml")
      for ct in $expanded {
        let c = (open $ct)
        let cname = ($c | get -o package.name | default ($ct | path dirname | path basename))
        let features = ($c | get -o features | default {})
        if ($features | columns | is-not-empty) {
          let feats = ($features | transpose name deps | each { |f| {
            crate: $cname,
            feature: $f.name,
            enables: ($f.deps | str join ", "),
          }})
          $result = ($result | append $feats)
        }
      }
    }
    $result
  }
}

def collect-crate-deps [root: string, feature_id: string]: nothing -> list<record> {
  let cargo_toml = $"($root)/Cargo.toml"
  if not ($cargo_toml | path exists) { return [] }
  let cargo = (open $cargo_toml)
  let members = ($cargo | get -o workspace.members | default [])

  # Try to find a crate whose name matches or contains the feature_id
  let id_parts = ($feature_id | split row "-")

  mut found_deps = []
  let tomls = if ($members | is-empty) {
    [$cargo_toml]
  } else {
    mut paths = []
    for member in $members {
      let expanded = (glob $"($root)/($member)/Cargo.toml")
      $paths = ($paths | append $expanded)
    }
    $paths
  }

  for ct in $tomls {
    let c = (open $ct)
    let cname = ($c | get -o package.name | default ($ct | path dirname | path basename))
    let cname_parts = ($cname | split row "-")
    # Match if feature_id contains crate name or vice versa
    let is_match = ($cname | str contains $feature_id) or ($feature_id | str contains $cname) or (($id_parts | where { |p| $p in $cname_parts }) | is-not-empty)
    if $is_match {
      let deps = ($c | get -o dependencies | default {})
      $found_deps = ($found_deps | append ($deps | transpose name spec | each { |d|
        let version = if ($d.spec | describe | str starts-with "string") {
          $d.spec
        } else {
          $d.spec | get -o version | default ""
        }
        { crate: $cname, dep: $d.name, version: $version }
      }))
    }
  }
  $found_deps
}

# ── Loaders ─────────────────────────────────────────────────────────────────────

def load-ontology-safe [root: string]: nothing -> record {
  let core = $"($root)/.ontology/core.ncl"
  if not ($core | path exists) { return {} }
  let ip = (nickel-import-path $root)
  daemon-export-safe $core --import-path $ip | default {}
}

def load-manifest-safe [root: string]: nothing -> record {
  let manifest = $"($root)/.ontology/manifest.ncl"
  if not ($manifest | path exists) { return {} }
  let ip = (nickel-import-path $root)
  daemon-export-safe $manifest --import-path $ip | default {}
}

# Scan ~/.config/ontoref/projects.ncl and return projects whose
# manifest.domain_origin.id matches domain_id.
# Text-parses project.ncl files to extract roots (no nickel subprocess).
# Only invokes nickel for projects whose raw manifest text contains the id.
def find-domain-implementations [domain_id: string]: nothing -> list<record> {
  if ($domain_id | is-empty) { return [] }
  let cfg = $"($env.HOME)/.config/ontoref/projects.ncl"
  if not ($cfg | path exists) { return [] }

  let proj_paths = (
    open --raw $cfg
    | lines
    | each { |l| $l | str trim }
    | where { |l| $l | str contains 'import "' }
    | each { |l|
        let m = ($l | parse --regex 'import\s+"(?P<p>[^"]+)"')
        if ($m | is-empty) { null } else { $m | get p | first }
      }
    | compact
  )

  $proj_paths | each { |proj_ncl|
    if not ($proj_ncl | path exists) { return null }
    # Fast text extraction of root = "..." from project.ncl
    let root_line = (
      open --raw $proj_ncl
      | lines
      | where { |l| $l | str trim | str starts-with "root " }
      | first
      | default ""
    )
    if ($root_line | is-empty) { return null }
    let root_m = ($root_line | parse --regex '"(?P<r>[^"]+)"')
    if ($root_m | is-empty) { return null }
    let root = ($root_m | get r | first)
    if not ($root | path exists) { return null }

    let manifest_path = $"($root)/.ontology/manifest.ncl"
    if not ($manifest_path | path exists) { return null }

    # Quick text pre-filter — avoids nickel for unrelated projects
    let raw = (open --raw $manifest_path)
    if not ($raw | str contains $"\"($domain_id)\"") { return null }

    let ip = (nickel-import-path $root)
    let mdata = (daemon-export-safe $manifest_path --import-path $ip | default {})
    let orig_id = ($mdata.domain_origin? | default {} | get id? | default "")
    if $orig_id != $domain_id { return null }

    let orig = ($mdata.domain_origin? | default {})
    {
      root:          $root,
      slug:          ($mdata.slug? | default ($root | path basename)),
      name:          ($mdata.name? | default ($root | path basename)),
      integrated:    ($orig.integrated? | default true),
      extends:       ($orig.extends?    | default []),
    }
  } | compact
}

# Load hard constraints from Accepted ADRs at framework_root.
# Returns list of {adr_id, title, description, check_hint} for agent consumption.
def load-framework-hard-constraints [framework_root: string]: nothing -> list<record> {
  if ($framework_root | is-empty) or not ($framework_root | path exists) { return [] }
  let files = (glob $"($framework_root)/adrs/adr-*.ncl")
  if ($files | is-empty) { return [] }
  let ip = (nickel-import-path $framework_root)
  $files | each { |f|
    let adr = (daemon-export-safe $f --import-path $ip)
    if $adr == null { return null }
    if ($adr.status? | default "") != "Accepted" { return null }
    let hard = ($adr.constraints? | default [] | where { |c| ($c.severity? | default "") == "Hard" })
    if ($hard | is-empty) { return null }
    $hard | each { |c| {
      adr_id:      ($adr.id? | default ""),
      adr_title:   ($adr.title? | default ""),
      description: ($c.description? | default ($c.claim? | default "")),
      check_hint:  ($c.check_hint? | default ($c.id? | default "")),
    }}
  } | compact | flatten
}

def load-all-adrs [root: string]: nothing -> list<record> {
  let files = (glob $"($root)/adrs/adr-*.ncl")
  let ip = (nickel-import-path $root)
  $files | each { |f|
    daemon-export-safe $f --import-path $ip
  } | compact
}

def list-ontology-extensions [root: string]: nothing -> list<string> {
  let dir = $"($root)/.ontology"
  let core = ["core.ncl", "state.ncl", "gate.ncl"]
  glob ($dir | path join "*.ncl")
  | each { |f| $f | path basename }
  | where { |f| $f not-in $core }
  | each { |f| $f | str replace ".ncl" "" }
  | sort
}

def load-ontology-extension [root: string, stem: string]: nothing -> any {
  let file = $"($root)/.ontology/($stem).ncl"
  if not ($file | path exists) { return null }
  let ip = (nickel-import-path $root)
  daemon-export-safe $file --import-path $ip
}

# ── Impact tracer ───────────────────────────────────────────────────────────────

def trace-impacts [
  node_id: string,
  edges: list<record>,
  nodes: list<record>,
  max_depth: int,
]: nothing -> list<record> {
  # Depth-1: direct neighbors
  let depth1 = (find-neighbors $node_id $edges $nodes [$node_id] 1)

  if $max_depth < 2 or ($depth1 | is-empty) {
    return ($depth1 | sort-by depth id)
  }

  # Depth-2: neighbors of depth-1 nodes
  let visited_after_1 = ([$node_id] | append ($depth1 | get id))
  let depth2 = ($depth1 | each { |d1|
    find-neighbors $d1.id $edges $nodes $visited_after_1 2
  } | flatten)

  if $max_depth < 3 or ($depth2 | is-empty) {
    return ($depth1 | append $depth2 | sort-by depth id)
  }

  # Depth-3: neighbors of depth-2 nodes
  let visited_after_2 = ($visited_after_1 | append ($depth2 | get id))
  let depth3 = ($depth2 | each { |d2|
    find-neighbors $d2.id $edges $nodes $visited_after_2 3
  } | flatten)

  $depth1 | append $depth2 | append $depth3 | sort-by depth id
}

def find-neighbors [
  current: string,
  edges: list<record>,
  nodes: list<record>,
  visited: list<string>,
  depth: int,
]: nothing -> list<record> {
  let outgoing = ($edges | where from == $current)
  let incoming = ($edges | where to == $current)
  let candidates = ($outgoing | each { |e| { target: $e.to, edge_type: $e.kind, direction: "outgoing" } })
    | append ($incoming | each { |e| { target: $e.from, edge_type: $e.kind, direction: "incoming" } })

  $candidates | where { |n| not ($n.target in $visited) }
  | each { |n|
    let target_node = ($nodes | where id == $n.target)
    let label = if ($target_node | is-not-empty) { ($target_node | first).name } else { $n.target }
    {
      id: $n.target,
      name: $label,
      edge_type: $n.edge_type,
      direction: $n.direction,
      depth: $depth,
    }
  }
}

# ── Renderers ───────────────────────────────────────────────────────────────────

def render-project-text [data: record, actor: string, root: string]: nothing -> nothing {
  let name = $data.identity.name
  print ""
  print $"($name)"
  print "══════════════════════════════════════════════════════════════════"

  if ($data.identity.description | is-not-empty) {
    print $data.identity.description
    print ""
  }
  if ($data.identity.kind | is-not-empty) {
    print $"Kind: ($data.identity.kind)"
  }

  # Domain ontology layer
  let d_orig = ($data.identity.domain_origin? | default {})
  let d_prov = ($data.identity.domain_provides? | default {})
  if ($d_orig | is-not-empty) and ($d_orig.id? | is-not-empty) {
    let orig_id    = ($d_orig.id? | default "")
    let orig_name  = ($d_orig.name? | default "")
    let orig_integ = if ($d_orig.integrated? | default true) { "integrated" } else { "external" }
    print $"Domain: implements ($orig_id) — ($orig_name) [$orig_integ]"
    let orig_path = ($d_orig.path? | default "")
    if ($orig_path | is-not-empty) { print $"  origin: ($orig_path)" }
    let orig_ext = ($d_orig.extends? | default [])
    if ($orig_ext | is-not-empty) { print $"  extends: ($orig_ext | str join ', ')" }
  }
  if ($d_prov | is-not-empty) and ($d_prov.id? | is-not-empty) {
    let prov_id   = ($d_prov.id? | default "")
    let prov_name = ($d_prov.name? | default "")
    let prov_kind = ($d_prov.kind? | default "Ncl" | into string)
    print $"Domain: provides ($prov_id) — ($prov_name) [$prov_kind]"
    let prov_schema = ($d_prov.schema_path? | default "")
    if ($prov_schema | is-not-empty) { print $"  schemas: ($prov_schema)" }
    let prov_impl = ($d_prov.impl_repo_kind? | default "")
    if ($prov_impl | is-not-empty) { print $"  impl repo_kind: ($prov_impl)" }
    print "  → ore describe domain  (list implementations)"
  }
  if ($d_orig | is-not-empty) and ($d_orig.id? | is-not-empty) {
    let orig_path = ($d_orig.path? | default "")
    if ($orig_path | is-not-empty) {
      print $"  → ONTOREF_PROJECT_ROOT=($orig_path) ore describe domain  \(framework details\)"
    }
  }

  # Systems present
  let systems = [
    (if $data.identity.has_ontology { "ontology" } else { null }),
    (if $data.identity.has_adrs { "ADRs" } else { null }),
    (if $data.identity.has_reflection { "reflection" } else { null }),
    (if $data.identity.has_manifest { "manifest" } else { null }),
    (if $data.identity.has_coder { ".coder" } else { null }),
  ] | compact
  print $"Systems: ($systems | str join ', ')"
  print ""

  if ($data.axioms | is-not-empty) {
    print "INVARIANTS (non-negotiable)"
    print "──────────────────────────────────────────────────────────────────"
    for ax in $data.axioms {
      print $"  ■ ($ax.name)"
      print $"    ($ax.description)"
    }
    print ""
  }

  if $actor == "auditor" or $actor == "developer" {
    if ($data.tensions | is-not-empty) {
      print "TENSIONS (active trade-offs)"
      print "──────────────────────────────────────────────────────────────────"
      for t in $data.tensions {
        print $"  ⇄ ($t.name)"
        print $"    ($t.description)"
      }
      print ""
    }
  }

  if ($data.dimensions | is-not-empty) {
    print "STATE DIMENSIONS"
    print "──────────────────────────────────────────────────────────────────"
    for d in $data.dimensions {
      let marker = if $d.reached { "●" } else { "○" }
      print $"  ($marker) ($d.name): ($d.current_state) → ($d.desired_state)"
    }
    print ""
  }

  if ($data.gates | is-not-empty) {
    print "ACTIVE GATES (protected boundaries)"
    print "──────────────────────────────────────────────────────────────────"
    for g in $data.gates {
      print $"  ⊘ ($g.name) [permeability: ($g.permeability)]"
      if ($g.accepts | is-not-empty) {
        print $"    Accepts: ($g.accepts | str join ', ')"
      }
    }
    print ""
  }

  if ($data.adrs | is-not-empty) {
    print "ARCHITECTURAL DECISIONS"
    print "──────────────────────────────────────────────────────────────────"
    for adr in $data.adrs {
      let status_marker = match $adr.status { "Accepted" => "✓", "Proposed" => "?", _ => "×" }
      print $"  ($status_marker) ($adr.id): ($adr.title) [($adr.constraint_count) constraints]"
    }
    print ""
  }

  if ($data.practices | is-not-empty) and ($actor != "ci") {
    print "PRACTICES & SYSTEMS"
    print "──────────────────────────────────────────────────────────────────"
    for p in $data.practices {
      print $"  ◆ ($p.name)"
      if ($p.artifact_paths | is-not-empty) {
        print $"    Artifacts: ($p.artifact_paths | str join ', ')"
      }
    }
    print ""
  }
}

def resolve-domain-extension [root: string]: nothing -> record {
  let ontoref_root = ($env.ONTOREF_ROOT? | default "")
  if ($ontoref_root | is-empty) { return {} }
  let manifest_path = $"($root)/.ontology/manifest.ncl"
  if not ($manifest_path | path exists) { return {} }
  let rk_raw = (do { ^nickel export $manifest_path } | complete)
  if $rk_raw.exit_code != 0 { return {} }
  let repo_kind = ($rk_raw.stdout | from json | get repo_kind? | default "")
  if ($repo_kind | is-empty) { return {} }
  let domains_dir = $"($ontoref_root)/domains"
  if not ($domains_dir | path exists) { return {} }
  let matched = (
    ls $domains_dir
    | where type == "dir"
    | get name
    | each { |d|
        let rk = $"($d)/repo_kinds.txt"
        if ($rk | path exists) and ((open --raw $rk | lines | any { |l| ($l | str trim) == $repo_kind })) {
          $d | path basename
        } else { null }
      }
    | compact
    | if ($in | is-empty) { null } else { $in | first }
  )
  if ($matched | is-empty) { return {} }
  let domain_ncl = $"($domains_dir)/($matched)/domain.ncl"
  if not ($domain_ncl | path exists) { return {} }
  let raw = (do { ^nickel export $domain_ncl } | complete)
  if $raw.exit_code != 0 { return {} }
  $raw.stdout | from json | insert _domain_id $matched
}

def render-capabilities-text [data: record, actor: string, root: string]: nothing -> nothing {
  print ""
  print "CAPABILITIES"
  print "══════════════════════════════════════════════════════════════════"

  let flags = ($data.project_flags? | default {})
  if ($flags | is-not-empty) {
    print ""
    print "PROJECT FLAGS"
    print "──────────────────────────────────────────────────────────────────"
    let flag_pairs = [
      ["has_rust", "Rust"],
      ["has_ui", "UI (templates/assets)"],
      ["has_mdbook", "mdBook (docs/SUMMARY.md)"],
      ["has_nats", "NATS events"],
      ["has_precommit", "pre-commit hooks"],
      ["has_backlog", "backlog (reflection/backlog.ncl)"],
      ["has_git_remote", "git remote"],
    ]
    for pair in $flag_pairs {
      let key = ($pair | first)
      let label = ($pair | last)
      let val = ($flags | get -o $key | default false)
      let mark = if $val { "✓" } else { "○" }
      print $"  ($mark) ($label)"
    }
    let crates = ($flags.crates? | default [])
    if ($crates | is-not-empty) {
      print $"  Crates: ($crates | str join ', ')"
    }
    let bp = ($data.backlog?.pending? | default 0)
    if $bp > 0 {
      print $"  Backlog pending: ($bp)"
    }
    let open_prs = ($flags.open_prs? | default 0)
    if $open_prs > 0 {
      print $"  Open PRs: ($open_prs)  [($flags.git_slug? | default '')]"
    }
  }

  if ($data.just_recipes? | default [] | is-not-empty) {
    print ""
    print "JUST RECIPES (by category)"
    print "──────────────────────────────────────────────────────────────────"
    let by_cat = ($data.just_recipes | group-by category)
    for cat in ($by_cat | columns | sort) {
      let recipes = ($by_cat | get $cat)
      print $"  [($cat)]"
      for r in $recipes {
        let desc = if ($r.description | is-not-empty) { $"  — ($r.description)" } else { "" }
        print $"    ($r.name)($desc)"
      }
    }
  }

  if ($data.just_modules | is-not-empty) {
    print ""
    print "JUST MODULES"
    print "──────────────────────────────────────────────────────────────────"
    for m in $data.just_modules {
      if ($m.status? | default "") == "no_justfile" {
        print "  (no root justfile found)"
      } else {
        let prefix = if ($m.type? | default "") == "mod" { "mod" } else if ($m.type? | default "") == "import" { "import" } else { "file" }
        print $"  [($prefix)] ($m.name)"
      }
    }
  }

  if ($data.manifest_modes | is-not-empty) {
    print ""
    print "PROJECT MODES (manifest)"
    print "──────────────────────────────────────────────────────────────────"
    for m in $data.manifest_modes {
      print $"  ./onref manifest mode ($m.id)"
      if ($m.description | is-not-empty) { print $"    ($m.description)" }
    }
  }

  if ($data.reflection_modes | is-not-empty) {
    print ""
    print "OPERATIONAL MODES (reflection)"
    print "──────────────────────────────────────────────────────────────────"
    for m in $data.reflection_modes {
      let src = if $m.source == "project" { " [local]" } else { "" }
      print $"  ./onref mode ($m.id)($src)"
      if ($m.trigger | is-not-empty) { print $"    ($m.trigger)" }
    }
  }

  if $actor == "agent" {
    print ""
    print "ONTOREF COMMANDS"
    print "──────────────────────────────────────────────────────────────────"
    for cmd in $data.ontoref_commands {
      print $"  ./onref ($cmd)"
    }
  }

  if ($data.claude_capabilities.present? | default false) {
    let cc = $data.claude_capabilities
    print ""
    print ".CLAUDE CAPABILITIES"
    print "──────────────────────────────────────────────────────────────────"
    print $"  CLAUDE.md: (if $cc.has_claude_md { 'yes' } else { 'no' })"
    if ($cc.guidelines | is-not-empty) {
      print $"  Guidelines: ($cc.guidelines | str join ', ')"
    }
    print $"  Commands: ($cc.commands_count)"
    if ($cc.skills | is-not-empty) {
      print $"  Skills: ($cc.skills | str join ', ')"
    }
    if ($cc.agents | is-not-empty) {
      print $"  Agents: ($cc.agents | str join ', ')"
    }
    print $"  Hooks: ($cc.hooks_count)"
    if ($cc.profiles | is-not-empty) {
      print $"  Profiles: ($cc.profiles | str join ', ')"
    }
  }

  if ($data.ci_tools | is-not-empty) and ($actor == "ci" or $actor == "agent") {
    print ""
    print "CI TOOLS"
    print "──────────────────────────────────────────────────────────────────"
    for t in ($data.ci_tools | where enabled == true) {
      print $"  ✓ ($t.name) [($t.install_method)]"
    }
    let disabled = ($data.ci_tools | where enabled == false)
    if ($disabled | is-not-empty) {
      for t in $disabled {
        print $"  ○ ($t.name) [disabled]"
      }
    }
  }

  if ($data.manifest_capabilities? | default [] | is-not-empty) {
    print ""
    print "PROJECT CAPABILITIES (manifest)"
    print "──────────────────────────────────────────────────────────────────"
    for c in ($data.manifest_capabilities? | default []) {
      print $"  ($c.name): ($c.summary)"
      if ($c.artifacts? | default [] | is-not-empty) {
        print $"    Artifacts: ($c.artifacts | str join ', ')"
      }
    }
  }

  if ($data.adrs? | default [] | is-not-empty) {
    print ""
    print "ARCHITECTURAL DECISIONS (ADRs)"
    print "──────────────────────────────────────────────────────────────────"
    for adr in ($data.adrs? | default []) {
      let marker = match ($adr.status? | default "") { "Accepted" => "✓", "Proposed" => "?", _ => "×" }
      let cc = ($adr.constraint_count? | default 0)
      print $"  ($marker) ($adr.id): ($adr.title)  [($cc) constraints]"
    }
  }

  if ($data.backlog?.items? | default [] | is-not-empty) {
    print ""
    print "BACKLOG (open items)"
    print "──────────────────────────────────────────────────────────────────"
    for item in ($data.backlog.items | sort-by priority) {
      let kind_marker = if ($item.kind? | default "") == "Bug" { "⚠" } else { "·" }
      print $"  ($kind_marker) [($item.priority? | default '?')] ($item.id): ($item.title)"
    }
  }

  if ($data.feature_flags? | default [] | is-not-empty) {
    print ""
    print "FEATURE FLAGS (Cargo)"
    print "──────────────────────────────────────────────────────────────────"
    for ff in ($data.feature_flags? | default []) {
      print $"  ($ff.crate)/($ff.feature)  → ($ff.enables)"
    }
  }

  if ($data.api_routes? | default [] | is-not-empty) {
    print ""
    print $"API ROUTES \(actor: ($actor)\)"
    print "──────────────────────────────────────────────────────────────────"
    for r in ($data.api_routes? | default []) {
      let auth = ($r.auth? | default "")
      print $"  ($r.method) ($r.path)  [auth: ($auth)]"
      if ($r.description? | default "" | is-not-empty) {
        print $"    ($r.description)"
      }
    }
  }

  let dom = ($data.domain_extension? | default {})
  if ($dom | is-not-empty) and ($dom.name? | is-not-empty) {
    let domain_id = ($dom._domain_id? | default "")
    let cmd_caller = ($env.ONTOREF_CALLER? | default "ontoref")
    print ""
    print $"DOMAIN EXTENSION: ($dom.name)"
    print "──────────────────────────────────────────────────────────────────"
    for c in ($dom.commands? | default []) {
      print $"  ($cmd_caller) ($domain_id) ($c.id)  — ($c.description)"
    }
    if ($dom.pages? | default [] | is-not-empty) {
      print ""
      for p in ($dom.pages? | default []) {
        print $"  ($p.route)  — ($p.title)"
      }
    }
  }

  # Domain ontology layer (manifest domain_origin / domain_provides)
  let manifest = (load-manifest-safe $root)
  let d_orig = ($manifest.domain_origin?   | default {})
  let d_prov = ($manifest.domain_provides? | default {})
  let has_orig  = ($d_orig | is-not-empty) and ($d_orig.id? | is-not-empty)
  let has_prov  = ($d_prov | is-not-empty) and ($d_prov.id? | is-not-empty)
  let has_domain_layer = $has_orig or $has_prov
  if $has_domain_layer {
    print ""
    print "DOMAIN ONTOLOGY LAYER"
    print "──────────────────────────────────────────────────────────────────"
    if ($d_orig | is-not-empty) and ($d_orig.id? | is-not-empty) {
      let orig_id    = ($d_orig.id? | default "")
      let orig_name  = ($d_orig.name? | default "")
      let orig_integ = if ($d_orig.integrated? | default true) { "integrated" } else { "external" }
      print $"  origin:   ($orig_id)  ($orig_name)  [($orig_integ)]"
      let orig_path = ($d_orig.path? | default "")
      if ($orig_path | is-not-empty) { print $"  path:     ($orig_path)" }
      let orig_ext = ($d_orig.extends? | default [])
      if ($orig_ext | is-not-empty) { print $"  extends:  ($orig_ext | str join ', ')" }
    }
    if ($d_orig | is-not-empty) and ($d_orig.id? | is-not-empty) {
      let orig_path = ($d_orig.path? | default "")
      if ($orig_path | is-not-empty) {
        print $"  navigate: ONTOREF_PROJECT_ROOT=($orig_path) ore describe domain"
      }
    }
    if ($d_prov | is-not-empty) and ($d_prov.id? | is-not-empty) {
      let prov_id   = ($d_prov.id? | default "")
      let prov_name = ($d_prov.name? | default "")
      let prov_kind = ($d_prov.kind? | default "Ncl" | into string)
      print $"  provides: ($prov_id)  ($prov_name)  [($prov_kind)]"
      let prov_schema = ($d_prov.schema_path? | default "")
      if ($prov_schema | is-not-empty) { print $"  schemas:  ($prov_schema)" }
      let prov_impl = ($d_prov.impl_repo_kind? | default "")
      if ($prov_impl | is-not-empty) { print $"  impl_for: ($prov_impl)" }
      let prov_desc = ($d_prov.description? | default "")
      if ($prov_desc | is-not-empty) { print $"  ($prov_desc)" }
      print $"  navigate: ore describe domain  (list implementations)"
    }
  }

  print ""
}

def render-constraints-text [data: record, actor: string]: nothing -> nothing {
  print ""
  print "CONSTRAINTS"
  print "══════════════════════════════════════════════════════════════════"

  if ($data.invariants | is-not-empty) {
    print ""
    print "INVARIANTS (cannot be violated)"
    print "──────────────────────────────────────────────────────────────────"
    for ax in $data.invariants {
      print $"  ■ ($ax.name)"
      print $"    ($ax.description)"
    }
  }

  if ($data.hard_constraints | is-not-empty) {
    print ""
    print "HARD CONSTRAINTS (from Accepted ADRs)"
    print "──────────────────────────────────────────────────────────────────"
    for c in $data.hard_constraints {
      print $"  [($c.adr_id)] ($c.description)"
      if ($c.check_hint | is-not-empty) {
        print $"    Check: ($c.check_hint)"
      }
    }
  }

  if ($data.active_gates | is-not-empty) {
    print ""
    print "ACTIVE GATES"
    print "──────────────────────────────────────────────────────────────────"
    for g in $data.active_gates {
      print $"  ⊘ ($g.name) [($g.permeability)]"
      if ($g.accepts | is-not-empty) {
        print $"    Only accepts: ($g.accepts | str join ', ')"
      }
    }
  }
  print ""
}

def render-tools-text [data: record, actor: string, root: string]: nothing -> nothing {
  print ""
  print "TOOLS"
  print "══════════════════════════════════════════════════════════════════"

  if ($data.dev_tools | is-not-empty) {
    print ""
    print "DEV TOOLS (detected from config files)"
    print "──────────────────────────────────────────────────────────────────"
    for t in $data.dev_tools {
      print $"  ($t.tool) — ($t.purpose)"
      print $"    Config: ($t.config)"
    }
  }

  if ($data.ci_tools | is-not-empty) {
    print ""
    print "CI TOOLS (from .typedialog/ci/config.ncl)"
    print "──────────────────────────────────────────────────────────────────"
    for t in ($data.ci_tools | where enabled == true) {
      print $"  ✓ ($t.name) via ($t.install_method)"
    }
  }

  if ($data.just_recipes | is-not-empty) {
    print ""
    let relevant = match $actor {
      "ci" => { $data.just_recipes | where { |r| ($r.name | str starts-with "ci") or ($r.name | str contains "::ci") } },
      "developer" => { $data.just_recipes | where { |r| ($r.name | str starts-with "dev") or ($r.name | str starts-with "build") or ($r.name | str starts-with "test") or ($r.name | str contains "::dev") or ($r.name | str contains "::build") or ($r.name | str contains "::test") } },
      _ => { $data.just_recipes },
    }

    if ($relevant | is-not-empty) {
      let actor_label = $"JUST RECIPES [($actor)]"
      print $actor_label
      print "──────────────────────────────────────────────────────────────────"
      for r in $relevant {
        if ($r.description | is-not-empty) {
          print $"  just ($r.name)  # ($r.description)"
        } else {
          print $"  just ($r.name)"
        }
      }
    } else {
      print "JUST RECIPES (all)"
      print "──────────────────────────────────────────────────────────────────"
      for r in $data.just_recipes {
        print $"  just ($r.name)"
      }
    }
  }
  print ""
}

def render-impact-text [data: record]: nothing -> nothing {
  let src = $data.source
  print ""
  print $"IMPACT ANALYSIS: ($src.name? | default $src.id)"
  print "══════════════════════════════════════════════════════════════════"
  if ($src.description? | is-not-empty) {
    print $src.description
  }
  if ($src.artifact_paths? | default [] | is-not-empty) {
    print $"Artifacts: ($src.artifact_paths | str join ', ')"
  }
  print ""

  if ($data.impacts | is-not-empty) {
    print "AFFECTED NODES"
    print "──────────────────────────────────────────────────────────────────"
    for i in $data.impacts {
      let arrow = if $i.direction == "outgoing" { "→" } else { "←" }
      let indent = (0..<($i.depth) | each { "  " } | str join "")
      print $"($indent)($arrow) ($i.name) [($i.edge_type)] depth=($i.depth)"
    }
  } else {
    print "  No connected nodes found."
  }
  print ""
}

def render-why-text [data: record, id: string]: nothing -> nothing {
  print ""
  print $"WHY: ($id)"
  print "══════════════════════════════════════════════════════════════════"

  if ($data.node != null) {
    let n = $data.node
    print ""
    print $"Ontology node: ($n.name? | default $n.id)"
    print $"Level: ($n.level? | default 'unknown')"
    print $"Pole: ($n.pole? | default 'unknown')"
    print $"Invariant: ($n.invariant? | default false)"
    print ""
    print ($n.description? | default "")
    print ""

    if ($n.artifact_paths? | default [] | is-not-empty) {
      print $"Artifacts: ($n.artifact_paths | str join ', ')"
      print ""
    }
  }

  if ($data.adr != null) {
    let a = $data.adr
    print "DECISION (ADR)"
    print "──────────────────────────────────────────────────────────────────"
    print $"  ($a.id? | default ''): ($a.titulo? | default ($a.title? | default ''))"
    print $"  Status: ($a.status? | default '')"
    if ($a.contexto? | is-not-empty) { print $"  Context: ($a.contexto)" }
    if ($a.decision? | is-not-empty) { print $"  Decision: ($a.decision)" }
    if ($a.rationale? | is-not-empty) { print $"  Rationale: ($a.rationale)" }
    print ""

    let constraints = ($a.constraints? | default [])
    if ($constraints | is-not-empty) {
      print "  Constraints:"
      for c in $constraints {
        let sev = ($c.severity? | default "")
        let desc = ($c.description? | default "")
        print $"    [($sev)] ($desc)"
        if ($c.check_hint? | is-not-empty) {
          print $"      Check: ($c.check_hint)"
        }
      }
    }
    print ""
  }

  if ($data.edges_from | is-not-empty) {
    print "CONNECTIONS (outgoing)"
    print "──────────────────────────────────────────────────────────────────"
    for e in $data.edges_from {
      print $"  → ($e.to) [($e.kind)]"
    }
    print ""
  }

  if ($data.edges_to | is-not-empty) {
    print "CONNECTIONS (incoming)"
    print "──────────────────────────────────────────────────────────────────"
    for e in $data.edges_to {
      print $"  ← ($e.from) [($e.kind)]"
    }
    print ""
  }

  if ($data.node == null) and ($data.adr == null) {
    print $"  No ontology node or ADR found with id '($id)'."
    print ""
  }
}

# Returns the //! crate-level doc for the first artifact_path that is a crate directory.
# Empty string if no such path exists or no //! lines are present.
def find-crate-doc-for-node [root: string, paths: list<string>]: nothing -> string {
  for p in $paths {
    let p_norm = ($p | str replace --regex '/$' "")
    let full = $"($root)/($p_norm)"
    if not ($"($full)/Cargo.toml" | path exists) { continue }
    # It is a crate directory. Extract //! lines from src/lib.rs or src/main.rs.
    let entry = (
      [$"($full)/src/lib.rs", $"($full)/src/main.rs"]
      | where { |f| $f | path exists }
    )
    if ($entry | is-empty) { return "" }
    let lines = (open ($entry | first) | lines)
    # Skip #![...] inner attributes and blank lines, then collect //! block.
    let past_attrs = (
      $lines
      | skip while { |l|
          let t = ($l | str trim)
          ($t | is-empty) or ($t | str starts-with "#!")
        }
    )
    let doc_lines = (
      $past_attrs
      | take while { |l|
          let t = ($l | str trim)
          ($t | is-empty) or ($t | str starts-with "//!")
        }
      | where { |l| $l | str trim | str starts-with "//!" }
      | each { |l| $l | str trim | str replace --regex '^//! ?' "" }
    )
    return ($doc_lines | str join "\n")
  }
  ""
}

# ── Feature renderers ────────────────────────────────────────────────────────

def render-features-list-text [data: record, root: string]: nothing -> nothing {
  let name = ($root | path basename)
  print ""
  print $"FEATURES — ($name)"
  print "══════════════════════════════════════════════════════════════════"

  if ($data.features | is-not-empty) {
    print ""
    for f in $data.features {
      let art_count = $f.artifacts
      let art_label = if $art_count > 0 { $"  ($art_count) artifacts" } else { "" }
      let level_val_label = $"[($f.level)]"
      print $"  ◆ ($f.id)  ($level_val_label)($art_label)"
      print $"    ($f.name)"
      if ($f.description | is-not-empty) {
        # Truncate to first sentence for list view
        let first_sentence = ($f.description | split row "." | first)
        let desc = if ($first_sentence | str length) > 100 {
          $"($first_sentence | str substring 0..99)…"
        } else {
          $first_sentence
        }
        print $"    ($desc)"
      }
    }
  } else {
    print "  No ontology features found."
  }

  if ($data.cargo_features | is-not-empty) {
    print ""
    print "CARGO FEATURES (compile-time)"
    print "──────────────────────────────────────────────────────────────────"
    let grouped = ($data.cargo_features | group-by crate)
    for group in ($grouped | transpose crate_name feats) {
      print $"  ($group.crate_name)"
      for cf in $group.feats {
        let enables = if ($cf.enables | is-not-empty) { $" → ($cf.enables)" } else { "" }
        print $"    · ($cf.feature)($enables)"
      }
    }
  }

  print ""
  print $"  Use 'describe features <id>' for details on a specific feature."
  print ""
}

def render-feature-detail-text [data: record, root: string]: nothing -> nothing {
  print ""
  print $"($data.name)"
  print "══════════════════════════════════════════════════════════════════"
  print $"  id:         ($data.id)"
  print $"  level:      ($data.level)"
  print $"  pole:       ($data.pole)"
  let inv_label = if $data.invariant { "yes" } else { "no" }
  print $"  invariant:  ($inv_label)"
  print ""

  if ($data.description | is-not-empty) {
    print $data.description
    print ""
  }

  if ($data.artifacts | is-not-empty) {
    print "ARTIFACTS"
    print "──────────────────────────────────────────────────────────────────"
    for a in $data.artifacts {
      let marker = if $a.exists { "✓" } else { "✗" }
      print $"  ($marker) ($a.path)"
    }
    print ""
  }

  if ($data.depends_on | is-not-empty) {
    print "DEPENDS ON (outgoing edges)"
    print "──────────────────────────────────────────────────────────────────"
    for d in $data.depends_on {
      print $"  → ($d.name) [($d.relation)]"
    }
    print ""
  }

  if ($data.depended_by | is-not-empty) {
    print "DEPENDED BY (incoming edges)"
    print "──────────────────────────────────────────────────────────────────"
    for d in $data.depended_by {
      print $"  ← ($d.name) [($d.relation)]"
    }
    print ""
  }

  if ($data.dimensions | is-not-empty) {
    print "RELATED STATE DIMENSIONS"
    print "──────────────────────────────────────────────────────────────────"
    for d in $data.dimensions {
      let marker = if $d.reached { "●" } else { "○" }
      print $"  ($marker) ($d.id): ($d.current_state) → ($d.desired_state)"
    }
    print ""
  }

  if ($data.constraints | is-not-empty) {
    print "RELATED CONSTRAINTS"
    print "──────────────────────────────────────────────────────────────────"
    for c in $data.constraints {
      print $"  [($c.adr_id)] ($c.description)"
      if ($c.check_hint | is-not-empty) {
        print $"    Check: ($c.check_hint)"
      }
    }
    print ""
  }

  if ($data.crate_deps | is-not-empty) {
    print "CRATE DEPENDENCIES"
    print "──────────────────────────────────────────────────────────────────"
    let grouped = ($data.crate_deps | group-by crate)
    for group in ($grouped | transpose crate_name deps) {
      print $"  ($group.crate_name)"
      for d in $group.deps {
        let ver = if ($d.version | is-not-empty) { $" ($d.version)" } else { "" }
        print $"    ($d.dep)($ver)"
      }
    }
    print ""
  }

  let crate_doc = ($data.crate_doc? | default "")
  if ($crate_doc | is-not-empty) {
    print "IMPLEMENTATION VIEW  \(crate //! doc\)"
    print "──────────────────────────────────────────────────────────────────"
    for line in ($crate_doc | lines) {
      print $"  ($line)"
    }
    print ""
  }
}

# ── describe connections ──────────────────────────────────────────────────────
# "How does this project connect to other projects?"

export def "describe connections" [
  --fmt: string = "",
  --actor: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let conn_path = $"($root)/.ontology/connections.ncl"

  if not ($conn_path | path exists) {
    if $f == "json" {
      print ({"upstream": [], "downstream": [], "peers": []} | to json)
    } else {
      print "No connections.ncl found in .ontology/"
    }
    return
  }

  let data = (daemon-export-safe $conn_path)

  if $data == null {
    if $f == "json" {
      print ({"upstream": [], "downstream": [], "peers": [], "error": "export failed"} | to json)
    } else {
      print "Failed to export connections.ncl"
    }
    return
  }

  emit-output $data $f {||
    let up   = ($data | get upstream   | default [])
    let down = ($data | get downstream | default [])
    let peer = ($data | get peers      | default [])

    print ""
    print $"(ansi white_bold)PROJECT CONNECTIONS(ansi reset)"
    print $"(ansi dark_gray)─────────────────────────────────(ansi reset)"

    if ($up | is-not-empty) {
      print $"\n(ansi cyan_bold)Upstream(ansi reset) (consumes from)"
      for c in $up {
        print $"  → (ansi white)($c.project)(ansi reset)  (ansi dark_gray)[($c.kind)](ansi reset)  ($c.note? | default '')"
      }
    }
    if ($down | is-not-empty) {
      print $"\n(ansi cyan_bold)Downstream(ansi reset) (consumed by)"
      for c in $down {
        print $"  → (ansi white)($c.project)(ansi reset)  (ansi dark_gray)[($c.kind)](ansi reset)  ($c.note? | default '')"
      }
    }
    if ($peer | is-not-empty) {
      print $"\n(ansi cyan_bold)Peers(ansi reset) (co-developed or sibling)"
      for c in $peer {
        print $"  → (ansi white)($c.project)(ansi reset)  (ansi dark_gray)[($c.kind)](ansi reset)  ($c.note? | default '')"
      }
    }
    if ($up | is-empty) and ($down | is-empty) and ($peer | is-empty) {
      print "  (no connections declared)"
    }
    print ""
  }
}

# Coerce any NCL value to a plain string safe for a GFM table cell.
# Uses `to json` throughout — accepts any input type including nothing.
def md-cell []: any -> any {
  let value = $in
  let t = ($value | describe)
  if ($t | str starts-with "table") or ($t | str starts-with "list") {
    $value | to json | str replace -ar '^\[|\]$' '' | str replace -a '"' '' | str trim
  } else if ($t | str starts-with "record") {
    $value | to json
  } else if $t == "nothing" {
    ""
  } else {
    $value | to json | str replace -ar '^"|"$' ''
  }
}

# Render one value as a markdown section body (no heading).
def render-val-md [val: any]: nothing -> any {
  if $val == null { return "" }
  let t = ($val | describe)
  if ($t | str starts-with "table") {
    # Render each record as vertical key: value block, separated by ---
    let cols = ($val | columns)
    $val | each { |row|
      $cols | each { |c|
        let v = ($row | get --optional $c)
        let cell = if $v == null { "" } else { $v | md-cell }
        $"**($c)**: ($cell)  "
      } | str join "\n"
    } | str join "\n\n---\n\n"
  } else if ($t | str starts-with "list") {
    if ($val | is-empty) {
      "_empty_"
    } else {
      # split row returns list<string> which each can accept; avoids each on any-typed val
      $val | to json | str replace -ar '^\[|\]$' '' | str replace -a '"' '' | str trim
      | split row ", " | each { |item| $"- ($item | str trim)" } | str join "\n"
    }
  } else if ($t | str starts-with "record") {
    $val | columns | each { |c|
      let raw = ($val | get $c)
      let v = if $raw == null { "" } else { $raw | md-cell }
      $"- **($c)**: ($v)"
    } | str join "\n"
  } else {
    $val | to json | str replace -ar '^"|"$' ''
  }
}

# Try to render a section via a Tera template at {root}/layouts/{stem}/{section}.tera.
# Returns the rendered string if the template exists, null otherwise.
def render-section-tera [root: string, stem: string, section: string, val: any]: nothing -> any {
  let tmpl = $"($root)/layouts/($stem)/($section).tera"
  if not ($tmpl | path exists) { return null }
  let t = ($val | describe)
  let ctx = if ($t | str starts-with "table") or ($t | str starts-with "list") {
    {items: $val}
  } else {
    $val
  }
  $ctx | tera-render $tmpl
}

# Render an arbitrary extension record as Markdown, using Tera templates when available.
def render-extension-md [data: record, stem: string, root: string]: nothing -> any {
  let sections = ($data | columns | each { |key|
    let val = ($data | get $key)
    let body = (
      render-section-tera $root $stem $key $val
      | default (render-val-md $val)
    )
    $"\n## ($key)\n\n($body)\n"
  })
  ([$"# ($stem)"] | append $sections | str join "\n")
}

# List and optionally dump ontology extension files (.ontology/*.ncl beyond core/state/gate)
export def "describe extensions" [
  --fmt: string = "",
  --actor: string = "",
  --dump: string = "",  # stem to dump (e.g. career, personal); omit to list
  --clip,               # copy output to clipboard (dump only)
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "json" } else { "text" }

  let exts = (list-ontology-extensions $root)

  if ($dump | is-not-empty) {
    let data = (load-ontology-extension $root $dump)
    if $data == null {
      if $f == "json" {
        print ({"error": $"extension '($dump)' not found"} | to json)
      } else {
        print $"Extension '($dump).ncl' not found in .ontology/"
      }
      return
    }

    let is_rec = ($data | describe | str starts-with "record")
    let wrapped = if $is_rec { $data } else { {value: $data} }

    match $f {
      "md" => {
        let md = (render-extension-md $wrapped $dump $root)
        if $clip { $md | clip } else { print $md }
      },
      "json" => { print ($wrapped | to json) },
      "yaml" => { print ($wrapped | to yaml) },
      _ => {
        emit-output $wrapped $f {||
          print ""
          print $"(ansi white_bold)EXTENSION: ($dump)(ansi reset)"
          print $"(ansi dark_gray)─────────────────────────────────(ansi reset)"
          for key in ($wrapped | columns) {
            let val = ($wrapped | get $key)
            let t = ($val | describe)
            print $"\n(ansi cyan_bold)($key)(ansi reset)"
            if ($t | str starts-with "list") {
              if ($val | is-empty) {
                print "  (empty)"
              } else if (($val | first | describe) | str starts-with "record") {
                print ($val | table)
              } else {
                for item in $val { print $"  · ($item)" }
              }
            } else if ($t | str starts-with "record") {
              print ($val | table)
            } else {
              print $"  ($val)"
            }
          }
          print ""
        }
      }
    }
    return
  }

  let payload = {extensions: $exts}
  emit-output $payload $f {||
    print ""
    print $"(ansi white_bold)ONTOLOGY EXTENSIONS(ansi reset)"
    print $"(ansi dark_gray)─────────────────────────────────(ansi reset)"
    if ($exts | is-empty) {
      print "  (no extensions — only core/state/gate declared)"
    } else {
      for stem in $exts {
        print $"  (ansi cyan)◆(ansi reset) ($stem).ncl"
      }
      print ""
      print $"(ansi dark_gray)Use --dump <stem> to inspect a specific extension(ansi reset)"
    }
    print ""
  }
}
