use std::path::Path;
use std::process::ExitCode;
use anyhow::Result;
use clap::{ArgMatches, Command as ClapCommand};
use serde_json::Value;
use crate::commands::describe::CommandDescriptor;
use crate::mcp::protocol::Tool;
use crate::output::OutputFormat;
use crate::paths::state::StateLayout;
pub(crate) struct HealthDiagnostic {
pub check: &'static str,
pub severity: &'static str,
pub file: String,
pub message: String,
pub details: Option<serde_json::Value>,
}
pub(crate) trait Extension {
fn name(&self) -> &'static str;
fn command_groups(&self) -> &'static [&'static str];
fn cli_command(&self) -> Option<ClapCommand> {
None
}
fn dispatch_cli(
&self,
_subcommand_name: &str,
_matches: &ArgMatches,
_output: OutputFormat,
) -> Option<Result<ExitCode>> {
None
}
fn mcp_tools(&self, _commands: &[CommandDescriptor]) -> Vec<Tool> {
Vec::new()
}
fn dispatch_mcp(&self, _tool_name: &str, _args: &Value) -> Option<Result<Value>> {
None
}
fn health_diagnostics(
&self,
_layout: &StateLayout,
_repo_root: &Path,
_locality_id: &str,
) -> Result<Vec<HealthDiagnostic>> {
Ok(Vec::new())
}
fn enrich_pod_status(
&self,
_pod_name: &str,
_locality_id: &str,
_profile: &str,
_shared_root: &Path,
) -> Option<Vec<(String, String)>> {
None
}
}
pub(crate) fn registered() -> Vec<&'static dyn Extension> {
Vec::new()
}
#[cfg(test)]
pub(crate) fn owned_command_groups() -> Vec<&'static str> {
registered()
.into_iter()
.flat_map(|extension| extension.command_groups().iter().copied())
.collect()
}
pub(crate) fn augment_clap(mut command: ClapCommand) -> ClapCommand {
for extension in registered() {
debug_assert!(!extension.name().is_empty());
debug_assert!(!extension.command_groups().is_empty());
if let Some(subcommand) = extension.cli_command() {
command = command.subcommand(subcommand);
}
}
command
}
pub(crate) fn dispatch_cli(
subcommand_name: &str,
matches: &ArgMatches,
output: OutputFormat,
) -> Option<Result<ExitCode>> {
for extension in registered() {
if let Some(result) = extension.dispatch_cli(subcommand_name, matches, output) {
return Some(result);
}
}
None
}
pub(crate) fn build_mcp_tools(commands: &[CommandDescriptor]) -> Vec<Tool> {
let mut tools = Vec::new();
for extension in registered() {
debug_assert!(!extension.name().is_empty());
debug_assert!(!extension.command_groups().is_empty());
tools.extend(extension.mcp_tools(commands));
}
tools
}
pub(crate) fn dispatch_mcp(tool_name: &str, args: &Value) -> Option<Result<Value>> {
for extension in registered() {
if let Some(report) = extension.dispatch_mcp(tool_name, args) {
return Some(report);
}
}
None
}
pub(crate) fn health_diagnostics(
layout: &StateLayout,
repo_root: &Path,
locality_id: &str,
) -> Result<Vec<HealthDiagnostic>> {
let mut all = Vec::new();
for extension in registered() {
all.extend(extension.health_diagnostics(layout, repo_root, locality_id)?);
}
Ok(all)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_extensions_are_registered_in_the_kernel() {
assert!(registered().is_empty());
assert!(owned_command_groups().is_empty());
}
#[test]
fn extension_mcp_tools_are_empty_in_the_kernel() {
let schema = crate::commands::describe::run();
let tools = build_mcp_tools(&schema.commands);
assert!(tools.is_empty());
}
#[test]
fn extension_cli_commands_are_empty_in_the_kernel() {
let command = augment_clap(clap::Command::new("ccd"));
assert!(command.get_subcommands().next().is_none());
}
}