const TOPIC_METHODOLOGY: &str = include_str!("../data/explain/methodology.md");
const TOPIC_DOMAIN: &str = include_str!("../data/explain/domain.md");
const TOPIC_SENSOR: &str = include_str!("../data/explain/sensor.md");
const TOPIC_HAT: &str = include_str!("../data/explain/hat.md");
const TOPIC_SCORING: &str = include_str!("../data/explain/scoring.md");
const TOPIC_FEDERATION: &str = include_str!("../data/explain/federation.md");
const TOPIC_CLI: &str = include_str!("../data/explain/cli.md");
const TOPIC_CULTURE: &str = include_str!("../data/explain/culture.md");
const TOPIC_AUTONOMY: &str = include_str!("../data/explain/autonomy.md");
const TOPIC_UI: &str = include_str!("../data/explain/ui.md");
const TOPIC_DASHBOARD_LAYOUTS: &str = include_str!("../data/explain/dashboard-layouts.md");
const TOPIC_PUBLISH_GATES: &str = include_str!("../data/explain/publish-gates.md");
const TOPIC_QUEUES: &str = include_str!("../data/explain/queues.md");
const TOPIC_SECRETS: &str = include_str!("../data/explain/secrets.md");
const TOPIC_COMMAND_POST: &str = include_str!("../data/explain/command-post.md");
pub const BUNDLED_VERSION: &str = "v3.5";
pub const CANONICAL_SOURCE: &str =
"neurogrim/crates/neurogrim-mcp/data/explain/<topic>.md";
pub fn topics() -> &'static [(&'static str, &'static str, &'static str)] {
&[
(
"methodology",
"What is LSP Brains; the overlay framing; the 5-piece model",
TOPIC_METHODOLOGY,
),
(
"domain",
"Anatomy of a domain; weight tiers; when to add one",
TOPIC_DOMAIN,
),
(
"sensor",
"Sensor authoring contract; CMDB envelope; score formula patterns",
TOPIC_SENSOR,
),
(
"hat",
"The 8 declared hats and when to wear each",
TOPIC_HAT,
),
(
"scoring",
"Unified score, confidence, trajectory, floor gates",
TOPIC_SCORING,
),
(
"federation",
"A2A peers, fractal composition, read-only siblings",
TOPIC_FEDERATION,
),
(
"cli",
"All commands grouped by purpose (introspection / authoring / execution / bookkeeping)",
TOPIC_CLI,
),
(
"culture",
"culture.yaml — five values as floor-only invariants",
TOPIC_CULTURE,
),
(
"autonomy",
"Autonomy block: action_types, levels, safety_invariants (v3.3)",
TOPIC_AUTONOMY,
),
(
"ui",
"Dashboard surface: 5 pages, SSE live updates, hat lens, theme (v3.4)",
TOPIC_UI,
),
(
"dashboard-layouts",
"Authoring per-Brain widget layouts: catalog, sizes, common patterns, edit mode (v3.4)",
TOPIC_DASHBOARD_LAYOUTS,
),
(
"publish-gates",
"v4.0 publish-gates pipeline: automated/manual/e2e gate types, manifest, runner, ledger",
TOPIC_PUBLISH_GATES,
),
(
"queues",
"v4.1 agent coordination bus: append-only JSONL, reserved namespace, SSE live updates",
TOPIC_QUEUES,
),
(
"secrets",
"v4.2 encrypted secrets: four-layer model, OS-native + encrypted-file backends, single-use proxy tokens",
TOPIC_SECRETS,
),
(
"command-post",
"v4.3 dashboard-as-primary-editing-surface: multi-page schema, edit-via-bus, CLI parity",
TOPIC_COMMAND_POST,
),
]
}
pub fn lookup(name: &str) -> Option<&'static str> {
topics().iter().find(|(k, _, _)| *k == name).map(|t| t.2)
}
pub fn topic_names() -> Vec<&'static str> {
topics().iter().map(|(k, _, _)| *k).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn topic_count_is_15() {
assert_eq!(topics().len(), 15);
}
#[test]
fn topic_names_are_unique() {
let mut names = topic_names();
names.sort();
let n = names.len();
names.dedup();
assert_eq!(n, names.len(), "duplicate topic name");
}
#[test]
fn all_topics_have_non_empty_bodies() {
for (name, _summary, body) in topics() {
assert!(
body.len() > 200,
"topic '{name}' bundled body is too short ({} bytes)",
body.len()
);
}
}
#[test]
fn all_topics_carry_version_header() {
for (name, _summary, body) in topics() {
let first = body.lines().next().unwrap_or("");
assert!(
first.starts_with("<!-- topic:") && first.contains(BUNDLED_VERSION),
"topic '{name}' is missing the bundled-version header marker; first line: {first:?}"
);
}
}
#[test]
fn methodology_topic_mentions_overlay() {
assert!(TOPIC_METHODOLOGY.contains("overlay"));
assert!(TOPIC_METHODOLOGY.contains("nervous system"));
}
#[test]
fn domain_topic_describes_weight_tiers() {
assert!(TOPIC_DOMAIN.contains("Weighted"));
assert!(TOPIC_DOMAIN.contains("Advisory"));
assert!(TOPIC_DOMAIN.contains("Stub"));
}
#[test]
fn cli_topic_lists_command_families() {
assert!(TOPIC_CLI.contains("Introspection"));
assert!(TOPIC_CLI.contains("Authoring"));
assert!(TOPIC_CLI.contains("Execution"));
assert!(TOPIC_CLI.contains("Bookkeeping"));
}
#[test]
fn lookup_finds_known_topic() {
assert!(lookup("methodology").is_some());
assert!(lookup("nonexistent").is_none());
}
#[test]
fn dashboard_layouts_topic_lists_widget_catalog_and_patterns() {
for widget_type in [
"identity",
"score-gauge",
"strongest-signals",
"top-recommendations",
"domain-card",
"markdown-note",
] {
assert!(
TOPIC_DASHBOARD_LAYOUTS.contains(widget_type),
"dashboard-layouts topic missing widget type {widget_type}"
);
}
for size in ["full", "half", "third", "quarter"] {
assert!(
TOPIC_DASHBOARD_LAYOUTS.contains(size),
"dashboard-layouts topic missing size {size}"
);
}
assert!(
TOPIC_DASHBOARD_LAYOUTS.contains("Common patterns")
|| TOPIC_DASHBOARD_LAYOUTS.contains("common patterns"),
"dashboard-layouts topic must walk through common patterns"
);
assert!(
TOPIC_DASHBOARD_LAYOUTS.contains("Edit mode") || TOPIC_DASHBOARD_LAYOUTS.contains("edit mode"),
"dashboard-layouts topic must describe edit mode"
);
}
#[test]
fn ui_topic_describes_the_five_pages_and_sse() {
assert!(TOPIC_UI.contains("Overview"));
assert!(TOPIC_UI.contains("Domains"));
assert!(TOPIC_UI.contains("Federation"));
assert!(TOPIC_UI.contains("Skills"));
assert!(
TOPIC_UI.contains("SSE") || TOPIC_UI.contains("Server-Sent Events"),
"ui topic should mention SSE / Server-Sent Events"
);
assert!(
TOPIC_UI.contains("hat") && TOPIC_UI.contains("lens"),
"ui topic should describe the hat lens picker"
);
}
}