3470 lines
135 KiB
Plaintext
Raw Permalink Normal View History

2026-03-13 00:21:04 +00:00
#!/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.
2026-03-29 00:19:56 +00:00
export def nickel-import-path [root: string]: nothing -> string {
2026-03-13 00:21:04 +00:00
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]
# 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" }
2026-03-29 00:19:56 +00:00
let project_flags = (scan-project-flags $root)
2026-03-13 00:21:04 +00:00
let just_modules = (scan-just-modules $root)
2026-03-29 00:19:56 +00:00
let just_recipes = (scan-just-recipes $root)
2026-03-13 00:21:04 +00:00
let ontoref_commands = (scan-ontoref-commands)
let modes = (scan-reflection-modes $root)
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
let mode_dags = (scan-reflection-mode-dags $root)
2026-03-13 00:21:04 +00:00
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 [])
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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)
2026-03-13 00:21:04 +00:00
let data = {
2026-03-29 00:19:56 +00:00
project_flags: $project_flags,
2026-03-13 00:21:04 +00:00
just_modules: $just_modules,
2026-03-29 00:19:56 +00:00
just_recipes: $just_recipes,
2026-03-13 00:21:04 +00:00
ontoref_commands: $ontoref_commands,
reflection_modes: $modes,
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
mode_dags: $mode_dags,
2026-03-13 00:21:04 +00:00
claude_capabilities: $claude,
ci_tools: $ci_tools,
manifest_modes: $manifest_modes,
manifest_capabilities: $manifest_capabilities,
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
backlog: $backlog,
adrs: $adrs,
api_routes: $api_routes,
feature_flags: $feature_flags,
2026-03-13 00:21:04 +00:00
}
emit-output $data $f { || render-capabilities-text $data $a $root }
}
2026-03-29 00:19:56 +00:00
# ── 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 []),
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
guards: ($mode.guards? | default []),
2026-03-29 00:19:56 +00:00
steps: $steps,
postconditions: ($mode.postconditions? | default []),
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
converge: ($mode.converge? | default null),
2026-03-29 00:19:56 +00:00
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)" }
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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)"
}
}
2026-03-29 00:19:56 +00:00
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)"
}
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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)..."
}
}
2026-03-29 00:19:56 +00:00
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 ""
}
}
2026-03-13 00:21:04 +00:00
# ── 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" [
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
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
2026-03-13 00:21:04 +00:00
--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
}
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
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)
2026-03-13 00:21:04 +00:00
let data = {
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
source: ($target | first),
impacts: $all_impacts,
include_external: $include_external,
2026-03-13 00:21:04 +00:00
}
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" [
2026-03-13 00:21:04 +00:00
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
2026-03-13 00:21:04 +00:00
]: 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),
}})
2026-03-13 00:21:04 +00:00
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 ""
2026-03-13 00:21:04 +00:00
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")
}
2026-03-13 00:21:04 +00:00
}
# ── 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"
}
2026-03-13 00:21:04 +00:00
# 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)
2026-03-13 00:21:04 +00:00
$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 []),
2026-03-13 00:21:04 +00:00
}
}
# ── 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)"
}
}
2026-03-13 00:21:04 +00:00
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)`" }
}
2026-03-13 00:21:04 +00:00
print ""
}
# ── Interactive loop ──────────────────────────────────────────────────────────
def find-interactive-loop [
matches: list<record>,
all_nodes: list<record>,
edges: list<record>,
root: string,
term: string,
clip: bool,
2026-03-13 00:21:04 +00:00
] {
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
2026-03-13 00:21:04 +00:00
# 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) }
2026-03-13 00:21:04 +00:00
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)
}
2026-03-13 00:21:04 +00:00
}
}
}
}
# ── 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)
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
# //! 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 []))
2026-03-13 00:21:04 +00:00
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,
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
crate_doc: $crate_doc,
2026-03-13 00:21:04 +00:00
}
emit-output $data $f { || render-feature-detail-text $data $root }
}
}
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
# ── 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 [])
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
# 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 {
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
let r = (do { ^curl -sf --max-time 2 $"($daemon_url)/api/catalog" } | complete)
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
if $r.exit_code == 0 {
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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
}
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
} else {
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
[]
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
}
} else {
[]
}
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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.",
}
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
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,
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
authoring_conventions: $authoring_conventions,
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
}
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 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)"
}
}
}
}
}
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
# 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 {})
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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 })
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
{
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 {
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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) {
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
print $" features : ($cr.features | str join ', ')"
}
if ($cr.depends_on | is-not-empty) {
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
print $" deps : ($cr.depends_on | each { |d| $'(ansi cyan)($d)(ansi reset)' } | str join ', ')"
}
print ""
}
}
}
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
# ── 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 show = (do { ^git -C $root show $"HEAD:($rel_path)" } | complete)
if $show.exit_code != 0 { return {} }
let mk = (do { ^mktemp } | complete)
if $mk.exit_code != 0 { return {} }
let tmp = ($mk.stdout | str trim)
$show.stdout | 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 ""
}
}
2026-03-13 00:21:04 +00:00
# ── 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 { "" }
2026-03-13 00:21:04 +00:00
let description = if ($manifest | is-not-empty) { $manifest.description? | 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),
}
}
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
}
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
def 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 }),
}
}
2026-03-13 00:21:04 +00:00
# ── 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
}
2026-03-29 00:19:56 +00:00
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" }
}
2026-03-13 00:21:04 +00:00
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 { "" }
2026-03-29 00:19:56 +00:00
{ name: $name, category: (categorize-recipe $name), description: $desc }
2026-03-13 00:21:04 +00:00
}
}
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",
--- feat: API catalog surface, protocol v2 tooling, MCP expansion, on+re update ## Summary Session 2026-03-23. Closes the loop between handler code and discoverability across all three surfaces (browser, CLI, MCP agent) via compile-time inventory registration. Adds protocol v2 update tooling, extends MCP from 21 to 29 tools, and brings the self-description up to date. ## API Catalog Surface (#[onto_api] proc-macro) - crates/ontoref-derive: new proc-macro crate; `#[onto_api(method, path, description, auth, actors, params, tags)]` emits `inventory::submit!(ApiRouteEntry{...})` at link time - crates/ontoref-daemon/src/api_catalog.rs: `catalog()` — pure fn over `inventory::iter::<ApiRouteEntry>()`, zero runtime allocation - GET /api/catalog: returns full annotated HTTP surface as JSON - templates/pages/api_catalog.html: new page with client-side filtering by method, auth, path/description; detail panel per route (params table, feature flag); linked from dashboard card and nav - UI nav: "API" link (</> icon) added to mobile dropdown and desktop bar - inventory = "0.3" added to workspace.dependencies (MIT, zero transitive deps) ## Protocol Update Mode - reflection/modes/update_ontoref.ncl: 9-step DAG (5 detect parallel, 2 update idempotent, 2 validate, 1 report) — brings any project from protocol v1 to v2 by adding manifest.ncl and connections.ncl if absent, scanning ADRs for deprecated check_hint, validating with nickel export - reflection/templates/update-ontology-prompt.md: 8-phase reusable prompt for agent-driven ontology enrichment (infrastructure → audit → core.ncl → state.ncl → manifest.ncl → connections.ncl → ADR migration → validation) ## CLI — describe group extensions - reflection/bin/ontoref.nu: `describe diff [--fmt] [--file]` and `describe api [--actor] [--tag] [--auth] [--fmt]` registered as canonical subcommands with log-action; aliases `df` and `da` added; QUICK REFERENCE and ALIASES sections updated ## MCP — two new tools (21 → 29 total) - ontoref_api_catalog: filters catalog() output by actor/tag/auth; returns { routes, total } — no HTTP roundtrip, calls inventory directly - ontoref_file_versions: reads ProjectContext.file_versions DashMap per slug; returns BTreeMap<filename, u64> reload counters - insert_mcp_ctx: audited and updated from 15 to 28 entries in 6 groups - HelpTool JSON: 8 new entries (validate_adrs, validate, impact, guides, bookmark_list, bookmark_add, api_catalog, file_versions) - ServerHandler::get_info instructions updated to mention new tools ## Web UI — dashboard additions - Dashboard: "API Catalog" card (9th); "Ontology File Versions" section showing per-file reload counters from file_versions DashMap - dashboard_mp: builds BTreeMap<String, u64> from ctx.file_versions and injects into Tera context ## on+re update - .ontology/core.ncl: describe-query-layer and adopt-ontoref-tooling descriptions updated; ontoref-daemon updated ("11 pages", "29 tools", API catalog, per-file versioning, #[onto_api]); new node api-catalog-surface (Yang/Practice) with 3 edges; artifact_paths extended across 3 nodes - .ontology/state.ncl: protocol-maturity blocker updated (protocol v2 complete); self-description-coverage catalyst updated with session 2026-03-23 additions - ADR-007: "API Surface Discoverability via #[onto_api] Proc-Macro" — Accepted ## Documentation - README.md: crates table updated (11 pages, 29 MCP tools, ontoref-derive row); MCP representative table expanded; API Catalog, Semantic Diff, Per-File Versioning paragraphs added; update_ontoref onboarding section added - CHANGELOG.md: [Unreleased] section with 4 change groups - assets/web/src/index.html: tool counts 19→29 (EN+ES), page counts 12→11 (EN+ES), daemon description paragraph updated with API catalog + #[onto_api]
2026-03-23 00:58:27 +01:00
"describe guides", "describe diff", "describe api",
"describe state", "describe workspace",
2026-03-13 00:21:04 +00:00
]
}
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
}
2026-03-29 00:19:56 +00:00
def git-remote-slug [root: string]: nothing -> string {
let r = do { ^git -C $root remote get-url origin } | complete
if $r.exit_code != 0 { return "" }
let url = ($r.stdout | str trim)
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 = ($"($root)/.git" | path exists)
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
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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
}
2026-03-13 00:21:04 +00:00
# ── 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 {}
}
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
}
2026-03-13 00:21:04 +00:00
# ── 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)"
}
# 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 render-capabilities-text [data: record, actor: string, root: string]: nothing -> nothing {
print ""
print "CAPABILITIES"
print "══════════════════════════════════════════════════════════════════"
2026-03-29 00:19:56 +00:00
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 ', ')"
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
let bp = ($data.backlog?.pending? | default 0)
if $bp > 0 {
print $" Backlog pending: ($bp)"
2026-03-29 00:19:56 +00:00
}
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)"
}
}
}
2026-03-13 00:21:04 +00:00
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 ', ')"
}
}
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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)"
}
}
}
2026-03-13 00:21:04 +00:00
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 ""
}
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
# 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")
}
""
}
2026-03-13 00:21:04 +00:00
# ── 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 ""
}
feat: mode guards, convergence, manifest coverage, doc authoring pattern ## Mode guards and convergence loops (ADR-011) - `Guard` and `Converge` types added to `reflection/schema.ncl` and `reflection/defaults.ncl`. Guards run pre-flight checks (Block/Warn); converge loops iterate until a condition is met (RetryFailed/RetryAll). - `sync-ontology.ncl`: 3 guards + converge (zero-drift condition, max 2 iter). - `coder-workflow.ncl`: guard (coder-dir-exists) + `novelty-check` step. - Rust types in `ontoref-reflection/src/mode.rs`; executor in `executor.rs` evaluates guards before steps and convergence loop after. - `adrs/adr-011-mode-guards-and-convergence.ncl` added. ## Manifest capability completeness - `.ontology/manifest.ncl`: 3 → 19 declared capabilities covering the full action surface (daemon API, modes, Task Composer, QA, bookmarks, etc.). - `sync.nu`: `audit-manifest-coverage` + `sync manifest-check` command. - `validate-project.ncl`: 6th category `manifest-cov`. - Pre-commit hook `manifest-coverage` added. - Migrations `0010-manifest-capability-completeness`, `0011-manifest-coverage-hooks`. ## Rust doc authoring pattern — canonical `///` convention - `#[onto_api]`: `description = "..."` optional when `///` doc comment exists above handler — first line used as fallback. `#[derive(OntologyNode)]` same. - `ontoref-daemon/src/api.rs`: 42 handlers migrated to `///` doc comments; `description = "..."` removed from all `#[onto_api]` blocks. - `sync diff --docs --fail-on-drift`: exits 1 on crate `//!` drift; used by new `docs-drift` pre-commit hook. `docs-links` hook checks rustdoc broken links. - `generator.nu`: mdBook `crates/` chapter — per-crate page from `//!` doc, coverage badge, feature flags, implementing practice nodes. - `.claude/CLAUDE.md`: `### Documentation Authoring (Rust)` section added. - Migration `0012-rust-doc-authoring-pattern`. ## OntologyNode derive fixes - `#[derive(OntologyNode)]`: `name` and `paths` attributes supported; `///` doc fallback for `description`; `artifact_paths` correctly populated. - `Core::from_value` calls `merge_contributors()` behind `#[cfg(feature = "derive")]`. ## Bug fixes - `sync.nu` drift check: exact crate path match (not `str starts-with`); first-path-only rule; split on `. ` not `.` to avoid `.ontology/` truncation. - `find-unclaimed-artifacts`: fixed absolute vs relative path comparison. - Rustdoc broken intra-doc links fixed across all three crates. - `ci-docs` recipe now sets `RUSTDOCFLAGS` and actually fails on errors. mode guards/converge, manifest coverage validation, 19 capabilities (ADR-011) Extend the mode schema with Guard (pre-flight Block/Warn checks) and Converge (RetryFailed/RetryAll post-execution loops) — protocol pushes back on invalid state and iterates until convergence. ADR-011 records the decision to extend modes rather than create a separate action subsystem. Manifest expanded from 3 to 19 capabilities covering the full action surface (compose, plans, backlog graduation, notifications, coder pipeline, forms, templates, drift, quick actions, migrations, config, onboarding). New audit-manifest-coverage validator + pre-commit hook + SessionStart hook ensure agents always see complete project self-description. Bug fix: find-unclaimed-artifacts absolute vs relative path comparison — 19 phantom MISSING items resolved. Health 43% → 100%. Anti-slop: coder novelty-check step (Jaccard overlap against published+QA) inserted between triage and publish in coder-workflow. Justfile restructured into 5 modules (build/test/dev/ci/assets). Migrations 0010-0011 propagate requirements to consumer projects.
2026-03-30 19:08:25 +01:00
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 ""
}
2026-03-13 00:21:04 +00:00
}
# ── 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 ""
}
}