mod adr;
mod backlog;
mod backlog_order;
mod boot;
mod boundary;
mod catalog;
mod clock;
mod commands;
mod concept_map;
mod conduct;
mod conformance;
mod contentset;
mod corpus;
mod corpus_guard;
mod coverage;
mod coverage_scan;
mod coverage_store;
mod coverage_verify;
mod coverage_view;
mod dep_seq;
mod dispatch;
mod dispatch_config;
mod doctor_checks;
mod dtoml;
mod entity;
mod estimate;
mod facet;
mod facet_write;
mod finding;
mod fsutil;
mod git;
mod globmatch;
mod governance;
mod hymns;
mod input;
mod install;
mod install_config;
mod integrity;
mod kinds;
mod knowledge;
mod lazyspec;
mod ledger;
mod lexical;
mod lifecycle;
pub(crate) mod links;
mod listing;
mod map_server;
mod mcp_server;
mod memory;
mod meta;
mod paths;
mod plan;
mod policy;
mod priority;
mod projection;
mod rec;
mod reconcile;
mod registry;
mod regression;
mod regression_run;
mod relation;
mod relation_graph;
mod relation_query;
mod requirement;
mod reserve;
mod retrieve;
mod review;
mod revision;
mod rfc;
mod risk;
mod root;
mod search;
mod slice;
mod spec;
mod standard;
mod state;
mod status;
mod supersede;
mod tag;
#[cfg(test)]
mod test_support;
mod tomlfmt;
mod tty;
mod value;
mod verify;
mod vtgate;
mod worktree;
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use clap::{Args, Parser};
use crate::listing::{Format, ListArgs};
#[derive(Parser)]
#[command(name = "doctrine", version, about = "doctrine CLI")]
struct Cli {
#[arg(long, default_value = "auto", global = true)]
color: clap::ColorChoice,
#[command(subcommand)]
command: crate::commands::cli::Command,
}
#[derive(Args, Debug)]
pub(crate) struct CommonListArgs {
#[arg(long, short = 'f')]
pub(crate) filter: Option<String>,
#[arg(long, short = 'r')]
pub(crate) regexp: Option<String>,
#[arg(long, short = 'i')]
pub(crate) case_insensitive: bool,
#[arg(long, short = 's', value_delimiter = ',')]
pub(crate) status: Vec<String>,
#[arg(long, short = 't')]
pub(crate) tag: Vec<String>,
#[arg(long, short = 'a')]
pub(crate) all: bool,
#[arg(long, value_parser = Format::from_str, default_value_t = Format::Table)]
pub(crate) format: Format,
#[arg(long)]
pub(crate) json: bool,
#[arg(long, value_delimiter = ',')]
pub(crate) columns: Option<Vec<String>>,
}
impl CommonListArgs {
pub(crate) fn into_list_args(self, color: bool) -> ListArgs {
ListArgs {
substr: self.filter,
regexp: self.regexp,
case_insensitive: self.case_insensitive,
status: self.status,
tags: self.tag,
all: self.all,
format: self.format,
json: self.json,
columns: self.columns,
render: crate::listing::RenderOpts {
color,
term_width: crate::tty::stdout_terminal_width(),
},
}
}
}
#[derive(Args, Debug, Clone)]
pub(crate) struct CommonShowArgs {
pub(crate) id: String,
#[arg(long, value_parser = Format::from_str, default_value_t = Format::Table)]
pub(crate) format: Format,
#[arg(long)]
pub(crate) json: bool,
#[arg(short = 'p', long)]
pub(crate) path: Option<PathBuf>,
}
fn main() -> anyhow::Result<()> {
match Cli::try_parse() {
Ok(cli) => {
let color = crate::tty::resolve_color(cli.color);
crate::commands::guard::worker_guard(&cli.command)?;
let Cli { command, .. } = cli;
crate::commands::cli::dispatch(command, color)
}
Err(e) => {
if matches!(
e.kind(),
clap::error::ErrorKind::DisplayHelp
| clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
| clap::error::ErrorKind::MissingSubcommand
) {
let args: Vec<String> = std::env::args().skip(1).collect();
let has_real_subcommand = args.iter().any(|a| !a.starts_with('-') && a != "help");
if !has_real_subcommand {
let color = crate::tty::stdout_color_enabled();
let term_width = crate::tty::stdout_terminal_width();
let help = if args.iter().any(|a| a == "--boot-map") {
crate::commands::cli::render_boot_map()
} else if args.iter().any(|a| a == "--commands") {
crate::commands::cli::render_commands_table(color, term_width)
} else {
crate::commands::cli::render_top_level_help(color, term_width)
};
writeln!(std::io::stdout(), "{help}")?;
return Ok(());
}
}
e.exit()
}
}
}
#[cfg(test)]
mod tests {
use {crate::Cli, clap::Parser};
#[test]
fn only_memory_alone_parses() {
let r = Cli::try_parse_from(["doctrine", "install", "--only-memory"]);
assert!(r.is_ok());
}
}
#[cfg(test)]
mod write_class_tests {
use super::*;
use crate::commands::cli::Command;
use crate::commands::guard::{WriteClass, write_class};
use crate::concept_map::ConceptMapCommand;
use crate::memory::MemoryCommand;
use crate::review::ReviewCommand;
fn cls(args: &[&str]) -> Option<&'static str> {
match write_class(&Cli::try_parse_from(args).unwrap().command) {
WriteClass::Read => None,
WriteClass::Write(v) | WriteClass::Orchestrator(v) | WriteClass::Hookmint(v) => Some(v),
WriteClass::MarkerClear => None,
}
}
#[test]
fn install_is_write() {
assert_eq!(cls(&["doctrine", "install"]), Some("install"));
}
#[test]
fn slice_split() {
assert_eq!(cls(&["doctrine", "slice", "new"]), Some("slice new"));
assert_eq!(
cls(&["doctrine", "slice", "design", "0"]),
Some("slice design")
);
assert_eq!(cls(&["doctrine", "slice", "plan", "0"]), Some("slice plan"));
assert_eq!(
cls(&["doctrine", "slice", "phases", "0"]),
Some("slice phases")
);
assert_eq!(
cls(&["doctrine", "slice", "notes", "0"]),
Some("slice notes")
);
assert_eq!(
cls(&[
"doctrine", "slice", "phase", "0", "PHASE-01", "--status", "planned"
]),
Some("slice phase")
);
assert_eq!(
cls(&["doctrine", "slice", "status", "0", "proposed"]),
Some("slice status")
);
assert_eq!(cls(&["doctrine", "slice", "list"]), None);
assert_eq!(cls(&["doctrine", "slice", "show", ""]), None);
}
#[test]
fn memory_split() {
assert_eq!(
cls(&["doctrine", "memory", "record", "T", "--type", "concept"]),
Some("memory record")
);
assert_eq!(
cls(&["doctrine", "memory", "verify", ""]),
Some("memory verify")
);
assert_eq!(cls(&["doctrine", "memory", "show", ""]), None);
assert_eq!(cls(&["doctrine", "memory", "list"]), None);
assert_eq!(cls(&["doctrine", "memory", "find"]), None);
assert_eq!(cls(&["doctrine", "memory", "retrieve"]), None);
assert_eq!(cls(&["doctrine", "memory", "resolve-links"]), None);
assert_eq!(cls(&["doctrine", "memory", "backlinks", ""]), None);
assert_eq!(cls(&["doctrine", "memory", "sync"]), Some("memory sync"));
assert_eq!(
cls(&["doctrine", "memory", "sync", "install"]),
Some("memory sync install")
);
assert_eq!(
cls(&["doctrine", "memory", "status", "", ""]),
Some("memory status")
);
assert_eq!(
cls(&["doctrine", "memory", "edit", ""]),
Some("memory edit")
);
}
#[test]
fn memory_record_new_flags_parse_and_reach_the_variant() {
let cli = Cli::try_parse_from([
"doctrine",
"memory",
"record",
"T",
"--type",
"fact",
"--lifespan",
"semantic",
"--review-by",
"2026-08-01",
"--provenance-source",
"code:src/main.rs:42",
"--trust",
"low",
"--severity",
"critical",
])
.unwrap();
let Command::Memory {
command:
MemoryCommand::Record {
lifespan,
review_by,
provenance_source,
trust,
severity,
..
},
} = cli.command
else {
panic!("expected memory record");
};
assert_eq!(lifespan, Some(memory::Lifespan::Semantic));
assert_eq!(review_by.as_deref(), Some("2026-08-01"));
assert_eq!(provenance_source.len(), 1);
assert_eq!(provenance_source[0].kind, "code");
assert_eq!(provenance_source[0].ref_, "src/main.rs:42");
assert_eq!(trust.as_deref(), Some("low"));
assert_eq!(severity.as_deref(), Some("critical"));
}
#[test]
fn memory_record_invalid_lifespan_is_rejected() {
let cli = Cli::try_parse_from([
"doctrine",
"memory",
"record",
"T",
"--type",
"fact",
"--lifespan",
"bogus",
]);
assert!(cli.is_err());
}
#[test]
fn memory_search_retrieve_lifespan_flag_parses_on_the_shared_args() {
let search =
Cli::try_parse_from(["doctrine", "memory", "search", "--lifespan", "semantic"])
.unwrap();
let Command::Memory {
command: MemoryCommand::Search { args, .. },
} = search.command
else {
panic!("expected memory search");
};
assert_eq!(args.lifespan, Some(memory::Lifespan::Semantic));
let retrieve =
Cli::try_parse_from(["doctrine", "memory", "retrieve", "--lifespan", "working"])
.unwrap();
let Command::Memory {
command: MemoryCommand::Retrieve { args, .. },
} = retrieve.command
else {
panic!("expected memory retrieve");
};
assert_eq!(args.lifespan, Some(memory::Lifespan::Working));
}
#[test]
fn memory_search_invalid_lifespan_is_rejected() {
let cli = Cli::try_parse_from(["doctrine", "memory", "search", "--lifespan", "garbage"]);
assert!(cli.is_err());
}
#[test]
fn adr_split() {
assert_eq!(cls(&["doctrine", "adr", "new"]), Some("adr new"));
assert_eq!(
cls(&["doctrine", "adr", "status", "0", "--status", "proposed"]),
Some("adr status")
);
assert_eq!(cls(&["doctrine", "adr", "list"]), None);
assert_eq!(cls(&["doctrine", "adr", "show", ""]), None);
}
#[test]
fn policy_split() {
assert_eq!(cls(&["doctrine", "policy", "new"]), Some("policy new"));
assert_eq!(
cls(&["doctrine", "policy", "status", "0", "--status", "draft"]),
Some("policy status")
);
assert_eq!(cls(&["doctrine", "policy", "list"]), None);
assert_eq!(cls(&["doctrine", "policy", "show", ""]), None);
}
#[test]
fn standard_split() {
assert_eq!(cls(&["doctrine", "standard", "new"]), Some("standard new"));
assert_eq!(
cls(&["doctrine", "standard", "status", "0", "--status", "draft"]),
Some("standard status")
);
assert_eq!(cls(&["doctrine", "standard", "list"]), None);
assert_eq!(cls(&["doctrine", "standard", "show", ""]), None);
}
#[test]
fn spec_split() {
assert_eq!(
cls(&["doctrine", "spec", "new", "product"]),
Some("spec new")
);
assert_eq!(
cls(&["doctrine", "spec", "req", "add", "", "--kind", "functional"]),
Some("spec req add")
);
assert_eq!(
cls(&["doctrine", "spec", "req", "status", "", "--to", "active"]),
Some("spec req status")
);
assert_eq!(cls(&["doctrine", "spec", "list"]), None);
assert_eq!(cls(&["doctrine", "spec", "show", ""]), None);
assert_eq!(cls(&["doctrine", "spec", "validate"]), None);
}
#[test]
fn backlog_split() {
assert_eq!(
cls(&["doctrine", "backlog", "new", "issue"]),
Some("backlog new")
);
assert_eq!(
cls(&["doctrine", "backlog", "edit", "", "--status", "open"]),
Some("backlog edit")
);
assert_eq!(cls(&["doctrine", "backlog", "list"]), None);
assert_eq!(cls(&["doctrine", "backlog", "show", ""]), None);
}
#[test]
fn knowledge_split() {
assert_eq!(
cls(&["doctrine", "knowledge", "new", "assumption"]),
Some("knowledge new")
);
assert_eq!(
cls(&["doctrine", "knowledge", "status", "", ""]),
Some("knowledge status")
);
assert_eq!(cls(&["doctrine", "knowledge", "list"]), None);
assert_eq!(cls(&["doctrine", "knowledge", "show", ""]), None);
}
#[test]
fn boot_split() {
assert_eq!(cls(&["doctrine", "boot"]), Some("boot"));
assert_eq!(cls(&["doctrine", "boot", "--check"]), Some("boot"));
assert_eq!(cls(&["doctrine", "boot", "install"]), Some("boot install"));
}
#[test]
fn worktree_is_read() {
assert_eq!(cls(&["doctrine", "worktree", "provision", "x"]), None);
assert_eq!(cls(&["doctrine", "worktree", "check-allowlist"]), None);
assert_eq!(cls(&["doctrine", "worktree", "status"]), None);
}
#[test]
fn worktree_marker_is_bespoke_class() {
let c = Cli::try_parse_from(["doctrine", "worktree", "marker", "--clear"])
.unwrap()
.command;
assert!(
matches!(write_class(&c), WriteClass::MarkerClear),
"marker --clear must be the bespoke MarkerClear class"
);
assert_eq!(cls(&["doctrine", "worktree", "marker", "--clear"]), None);
}
#[test]
fn worktree_marker_stamp_subagent_is_hookmint() {
let c = Cli::try_parse_from(["doctrine", "worktree", "marker", "--stamp-subagent"])
.unwrap()
.command;
assert!(
matches!(
write_class(&c),
WriteClass::Hookmint("marker --stamp-subagent")
),
"marker --stamp-subagent must be the Hookmint class"
);
assert_eq!(
cls(&["doctrine", "worktree", "marker", "--stamp-subagent"]),
Some("marker --stamp-subagent")
);
}
#[test]
fn worktree_fork_is_orchestrator() {
let c = Cli::try_parse_from([
"doctrine", "worktree", "fork", "--base", "B", "--branch", "wkr", "--dir", "x",
])
.unwrap()
.command;
assert!(
matches!(write_class(&c), WriteClass::Orchestrator("fork")),
"fork must be Orchestrator(\"fork\")"
);
assert_eq!(
cls(&[
"doctrine", "worktree", "fork", "--base", "B", "--branch", "wkr", "--dir", "x"
]),
Some("fork")
);
}
#[test]
fn worktree_create_fork_is_orchestrator() {
let c = Cli::try_parse_from(["doctrine", "worktree", "create-fork"])
.unwrap()
.command;
assert!(
matches!(write_class(&c), WriteClass::Orchestrator("create-fork")),
"create-fork must be Orchestrator(\"create-fork\")"
);
assert_eq!(
cls(&["doctrine", "worktree", "create-fork"]),
Some("create-fork")
);
}
#[test]
fn dispatch_sync_is_orchestrator() {
let c = Cli::try_parse_from([
"doctrine",
"dispatch",
"sync",
"--slice",
"64",
"--prepare-review",
])
.unwrap()
.command;
assert!(
matches!(write_class(&c), WriteClass::Orchestrator("dispatch-sync")),
"dispatch sync must be Orchestrator(\"dispatch-sync\")"
);
assert_eq!(
cls(&[
"doctrine",
"dispatch",
"sync",
"--slice",
"64",
"--prepare-review"
]),
Some("dispatch-sync")
);
}
#[test]
fn dispatch_sync_integrate_is_orchestrator() {
let c = Cli::try_parse_from([
"doctrine",
"dispatch",
"sync",
"--slice",
"64",
"--integrate",
])
.unwrap()
.command;
assert!(
matches!(write_class(&c), WriteClass::Orchestrator("dispatch-sync")),
"dispatch sync --integrate must be Orchestrator(\"dispatch-sync\")"
);
assert_eq!(
cls(&[
"doctrine",
"dispatch",
"sync",
"--slice",
"64",
"--integrate"
]),
Some("dispatch-sync")
);
}
#[test]
fn inspect_is_read() {
assert_eq!(cls(&["doctrine", "inspect", "SL-046"]), None);
}
#[test]
fn validate_is_read_reseat_is_write() {
assert_eq!(cls(&["doctrine", "validate"]), None);
assert_eq!(cls(&["doctrine", "reseat", "SL-001"]), Some("reseat"));
}
#[test]
fn estimate_is_write() {
assert_eq!(
cls(&["doctrine", "estimate", "set", "SL-001", "1", "3"]),
Some("estimate")
);
}
#[test]
fn value_is_write() {
assert_eq!(
cls(&["doctrine", "value", "set", "SL-001", "42"]),
Some("value")
);
}
#[test]
fn help_snapshot_top_level() {
let help = <Cli as clap::CommandFactory>::command()
.render_help()
.to_string();
assert!(help.contains("doctrine CLI"), "top-level about text");
assert!(help.contains("Usage: doctrine"), "usage line");
assert!(help.contains("Commands:"), "commands section");
assert!(help.contains(" install"), "install command present");
assert!(help.contains(" slice"), "slice command present");
assert!(help.contains(" memory"), "memory command present");
assert!(help.contains(" adr"), "adr command present");
assert!(help.contains(" spec"), "spec command present");
assert!(help.contains(" dispatch"), "dispatch command present");
assert!(help.contains(" help"), "help command always present");
assert!(help.contains("Options:"), "global options");
assert!(help.contains("--color"), "color flag in help");
}
#[test]
fn help_snapshot_slice_subcommand() {
let help = <Cli as clap::CommandFactory>::command()
.find_subcommand_mut("slice")
.unwrap()
.render_help()
.to_string();
assert!(help.contains("Create and list slices"));
assert!(help.contains("new"));
assert!(help.contains("design"));
assert!(help.contains("plan"));
assert!(help.contains("list"));
assert!(help.contains("show"));
}
#[test]
fn help_snapshot_memory_subcommand() {
let help = <Cli as clap::CommandFactory>::command()
.find_subcommand_mut("memory")
.unwrap()
.render_help()
.to_string();
assert!(help.contains("Record, show, and list memories"));
assert!(help.contains("record"));
assert!(help.contains("find"));
assert!(help.contains("retrieve"));
assert!(help.contains("list"));
}
#[test]
fn help_snapshot_adr_subcommand() {
let help = <Cli as clap::CommandFactory>::command()
.find_subcommand_mut("adr")
.unwrap()
.render_help()
.to_string();
assert!(help.contains("Create and list architecture decision records"));
assert!(help.contains("new"));
assert!(help.contains("list"));
assert!(help.contains("show"));
assert!(help.contains("status"));
}
#[test]
fn help_snapshot_spec_subcommand() {
let help = <Cli as clap::CommandFactory>::command()
.find_subcommand_mut("spec")
.unwrap()
.render_help()
.to_string();
assert!(help.contains("Create and list product / technical specifications"));
assert!(help.contains("new"));
assert!(help.contains("list"));
assert!(help.contains("show"));
assert!(help.contains("validate"));
assert!(help.contains("req"));
}
#[test]
fn commands_table_structure() {
let out = crate::commands::cli::render_commands_table(false, Some(80));
assert!(
out.contains("For arguments & options: doctrine <command> <verb> --help"),
"footer"
);
assert!(out.contains("install"), "install");
assert!(out.contains("slice"), "slice");
assert!(out.contains("review"), "review");
assert!(out.contains("list"), "list verb");
assert!(out.contains("new"), "new verb");
assert!(out.contains("show"), "show verb");
assert!(!out.contains("│ help"), "help not listed as verb");
assert!(out.contains("—"), "em-dash for leaf commands");
assert!(
out.contains("command") && out.contains("verb") && out.contains("description"),
"headers"
);
}
#[test]
fn parse_list_status_value_delimiter_equivalence() {
use crate::slice::SliceCommand;
let a =
Cli::try_parse_from(["doctrine", "slice", "list", "--status", "draft,active"]).unwrap();
let b = Cli::try_parse_from([
"doctrine", "slice", "list", "--status", "draft", "--status", "active",
])
.unwrap();
let Command::Slice {
command: SliceCommand::List { list: la, .. },
} = a.command
else {
panic!("expected SliceCommand::List");
};
let Command::Slice {
command: SliceCommand::List { list: lb, .. },
} = b.command
else {
panic!("expected SliceCommand::List");
};
assert_eq!(la.status, lb.status);
assert_eq!(la.status, ["draft", "active"]);
}
#[test]
fn parse_find_retrieve_offset_conflicts_with_page() {
let r = Cli::try_parse_from(["doctrine", "memory", "find", "--offset", "5", "--page", "2"]);
assert!(r.is_err(), "offset + page should conflict");
}
#[test]
fn parse_find_memory_type_valid() {
let r = Cli::try_parse_from(["doctrine", "memory", "find", "--type", "concept"]);
assert!(r.is_ok());
}
#[test]
fn parse_find_memory_type_invalid() {
let r = Cli::try_parse_from(["doctrine", "memory", "find", "--type", "banana"]);
assert!(r.is_err());
}
#[test]
fn parse_find_status_valid() {
let r = Cli::try_parse_from(["doctrine", "memory", "find", "--status", "active"]);
assert!(r.is_ok());
}
#[test]
fn parse_find_status_invalid() {
let r = Cli::try_parse_from(["doctrine", "memory", "find", "--status", "foobar"]);
assert!(r.is_err());
}
#[test]
fn parse_find_lifespan_valid() {
let r = Cli::try_parse_from(["doctrine", "memory", "find", "--lifespan", "semantic"]);
assert!(r.is_ok());
}
#[test]
fn parse_find_lifespan_invalid() {
let r = Cli::try_parse_from(["doctrine", "memory", "find", "--lifespan", "quantum"]);
assert!(r.is_err());
}
#[test]
fn parse_retrieve_min_trust_valid() {
let r = Cli::try_parse_from(["doctrine", "memory", "retrieve", "--min-trust", "high"]);
assert!(r.is_ok());
}
#[test]
fn parse_retrieve_min_trust_invalid() {
let r = Cli::try_parse_from(["doctrine", "memory", "retrieve", "--min-trust", "banana"]);
assert!(r.is_err());
}
#[test]
fn parse_dispatch_sync_prepare_review_parses() {
let r = Cli::try_parse_from([
"doctrine",
"dispatch",
"sync",
"--slice",
"99",
"--prepare-review",
]);
assert!(r.is_ok(), "sync with --prepare-review");
}
#[test]
fn parse_dispatch_sync_integrate_parses_without_trunk() {
let r = Cli::try_parse_from([
"doctrine",
"dispatch",
"sync",
"--slice",
"99",
"--integrate",
]);
assert!(r.is_ok(), "sync with --integrate alone (trunk is optional)");
}
#[test]
fn parse_dispatch_sync_missing_stage_errors() {
let r = Cli::try_parse_from(["doctrine", "dispatch", "sync", "--slice", "99"]);
assert!(r.is_err(), "sync without a stage selector should error");
}
#[test]
fn parse_concept_map_list_common_list_args() {
let r = Cli::try_parse_from([
"doctrine",
"concept-map",
"list",
"--filter",
"test",
"--tag",
"a,b",
"--all",
"--json",
]);
assert!(r.is_ok(), "concept-map list with CommonListArgs");
let parsed = r.unwrap();
let Command::ConceptMap {
command: ConceptMapCommand::List { .. },
} = parsed.command
else {
panic!("expected ConceptMapCommand::List");
};
}
#[test]
fn parse_review_list_common_list_args() {
let r = Cli::try_parse_from([
"doctrine",
"review",
"list",
"--status",
"open",
"--format",
"json",
"--columns",
"id,title",
]);
assert!(r.is_ok(), "review list with CommonListArgs");
let parsed = r.unwrap();
let Command::Review {
command: ReviewCommand::List { .. },
} = parsed.command
else {
panic!("expected ReviewCommand::List");
};
}
#[test]
fn parse_rec_list_common_list_args() {
let r = Cli::try_parse_from([
"doctrine",
"rec",
"list",
"--all",
"--regexp",
"test.*",
"--case-insensitive",
]);
assert!(r.is_ok(), "rec list with CommonListArgs");
let parsed = r.unwrap();
let Command::Rec {
command: crate::rec::RecCommand::List { .. },
} = parsed.command
else {
panic!("expected RecCommand::List");
};
}
#[test]
fn parse_concept_map_show_with_json_flag() {
let r = Cli::try_parse_from(["doctrine", "concept-map", "show", "1", "--json"]);
let parsed = r.unwrap();
let Command::ConceptMap {
command: ConceptMapCommand::Show { json, .. },
} = parsed.command
else {
panic!("expected ConceptMapCommand::Show");
};
assert!(json, "--json flag should set json: true");
}
}