#![cfg_attr(not(test), warn(clippy::unwrap_used))]
use std::io::{self, BufRead, IsTerminal, Write as _};
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::Result;
use clap::{Parser, Subcommand};
use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, ContentArrangement, Table};
use slugify::slugify;
use cli::proxy::StoreProxy;
use mati_core::health::quality;
use mati_core::store::{
Category, ConfidenceScore, QualityScore, QualityTier, Record, RecordLifecycle, RecordSource,
RecordVersion, StalenessScore, Store,
};
mod cli;
#[derive(Parser)]
#[command(
name = "mati",
version,
about = "Engineering knowledge that survives turnover",
long_about = "mati is a persistent, queryable knowledge store for codebases.\n\
Exposed to agents via MCP stdio, with Claude and Codex integration paths.\n\n\
Core workflow:\n \
mati init build project memory\n \
mati explain <file> file briefing before editing\n \
mati diff <range> pre-merge check against knowledge store\n \
mati status project memory dashboard"
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
#[command(visible_alias = "i")]
Init(cli::init::InitArgs),
#[command(visible_alias = "e")]
Explain(cli::explain::ExplainArgs),
#[command(visible_alias = "d")]
Diff(cli::diff::DiffArgs),
#[command(visible_alias = "s")]
Status(cli::status::StatusArgs),
#[command(visible_alias = "g")]
Gotcha(cli::gotcha::GotchaArgs),
Show(cli::show::ShowArgs),
Ls(cli::show::LsArgs),
History(cli::show::HistoryArgs),
Gaps(cli::gaps::GapsArgs),
Clusters(cli::clusters::ClustersArgs),
Stats(cli::stats::StatsArgs),
Enrich(cli::enrich::EnrichArgs),
Note {
text: String,
},
Suggest(cli::suggest::SuggestArgs),
Search(cli::search::SearchArgs),
Export(cli::show::ExportArgs),
Import(cli::show::ImportArgs),
#[command(visible_alias = "c")]
Config(cli::config::ConfigArgs),
Completion {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
Review(cli::review::ReviewArgs),
Stale(cli::stale::StaleArgs),
Repair(cli::repair::RepairArgs),
Check,
Doctor(cli::doctor::DoctorArgs),
Eval(cli::eval::EvalArgs),
Policy(cli::policy::PolicyArgs),
#[command(name = "verify-chain")]
VerifyChain(cli::verify_chain::VerifyChainArgs),
QualityCheck,
Improve {
key: String,
},
Hooks(cli::init::HooksArgs),
Sandbox(cli::sandbox::SandboxArgs),
Daemon(cli::daemon::DaemonArgs),
Supervisor(cli::supervisor::SupervisorArgs),
Ping {
#[arg(long)]
daemon_only: bool,
},
Serve {
#[arg(long)]
path: Option<std::path::PathBuf>,
},
#[command(hide = true, name = "hook-decide")]
HookDecide(cli::hook_decide::HookDecideArgs),
#[command(hide = true)]
DocCapture {
path: String,
},
#[command(hide = true)]
Get { key: String },
#[command(hide = true)]
LogMiss { key: String },
#[command(hide = true)]
LogHit { key: String },
#[command(hide = true)]
LogComplianceMiss { key: String },
#[command(hide = true)]
LogComplianceHit { key: String },
#[command(hide = true)]
LogCodexShellMiss { key: String },
#[command(hide = true)]
LogBootstrap { key: String },
#[command(hide = true)]
LogPromptNudge { key: String },
#[command(hide = true)]
SessionCheckConsulted { key: String },
#[command(hide = true)]
SessionCheckConsultedRecent {
key: String,
#[arg(long, default_value_t = mati_core::store::session::CONSULTED_RECENT_TTL_SECS)]
ttl_secs: u64,
},
#[command(hide = true)]
SessionFlush,
#[command(hide = true)]
SessionHarvest,
#[command(hide = true)]
SessionClearConsults,
#[command(hide = true, name = "subagent-context")]
SubagentContext,
#[command(hide = true)]
EditHook {
path: String,
},
#[command(hide = true)]
Reparse {
path: String,
},
#[command(name = "verify-evidence")]
VerifyEvidence(cli::verify_evidence::VerifyEvidenceArgs),
#[command(name = "extract-signals")]
ExtractSignals(cli::extract_signals::ExtractSignalsArgs),
#[command(hide = true, name = "prompt-context")]
PromptContext {
files: Vec<String>,
},
}
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn")),
)
.init();
let cli = Cli::parse();
lower_worker_thread_qos();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.on_thread_start(lower_worker_thread_qos)
.build()?
.block_on(async_main(cli))
}
fn lower_worker_thread_qos() {
#[cfg(target_os = "macos")]
{
extern "C" {
fn pthread_set_qos_class_self_np(
qos_class: libc::c_uint,
relative_priority: libc::c_int,
) -> libc::c_int;
}
const QOS_CLASS_USER_INITIATED: libc::c_uint = 0x19;
let ret = unsafe { pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0) };
debug_assert_eq!(ret, 0, "pthread_set_qos_class_self_np failed: errno={ret}");
}
}
async fn async_main(cli: Cli) -> Result<()> {
match cli.command {
Commands::Init(args) => cli::init::run(args).await,
Commands::Config(args) => cli::config::run(args).await,
Commands::Enrich(args) => cli::enrich::run(args).await,
Commands::Status(args) => cli::status::run(args).await,
Commands::Stats(args) => cli::stats::run(args).await,
Commands::Gaps(args) => cli::gaps::run(args).await,
Commands::Clusters(args) => cli::clusters::run(args).await,
Commands::Show(args) => cli::show::run_show(args).await,
Commands::Ls(args) => cli::show::run_ls(args).await,
Commands::History(args) => cli::show::run_history(args).await,
Commands::Gotcha(args) => cli::gotcha::run(args).await,
Commands::QualityCheck => run_quality_check().await,
Commands::Improve { key } => run_improve(&key).await,
Commands::Note { text } => run_note(&text).await,
Commands::Suggest(args) => cli::suggest::run(args).await,
Commands::Search(args) => cli::search::run(args).await,
Commands::Completion { shell } => {
use clap::CommandFactory;
clap_complete::generate(shell, &mut Cli::command(), "mati", &mut std::io::stdout());
Ok(())
}
Commands::Export(args) => cli::show::run_export(args).await,
Commands::Import(args) => cli::show::run_import(args).await,
Commands::Review(args) => cli::review::run(args).await,
Commands::Explain(args) => cli::explain::run(args).await,
Commands::Diff(args) => cli::diff::run(args).await,
Commands::Stale(args) => cli::stale::run(args).await,
Commands::Repair(args) => cli::repair::run(args).await,
Commands::Check => cli::check::run().await,
Commands::Doctor(args) => cli::doctor::run(args).await,
Commands::Eval(args) => cli::eval::run(args).await,
Commands::Policy(args) => cli::policy::run(args).await,
Commands::VerifyChain(args) => cli::verify_chain::run(args).await,
Commands::Hooks(args) => cli::init::run_hooks(args),
Commands::Sandbox(args) => cli::sandbox::run(args).await,
Commands::Daemon(args) => match args.command {
cli::daemon::DaemonCommand::Start => cli::daemon::run_daemon_start().await,
cli::daemon::DaemonCommand::Stop(args) => cli::daemon::run_daemon_stop(args).await,
cli::daemon::DaemonCommand::Status => cli::daemon::run_daemon_status().await,
},
Commands::Supervisor(args) => cli::supervisor::run(args).await,
Commands::Ping { daemon_only } => {
let cwd = std::env::current_dir()?;
let root = cli::daemon::mati_root_for(&cwd)?;
match cli::daemon::daemon_result(&root, "ping", serde_json::json!({})).await {
cli::daemon::DaemonResult::Ok(resp) => {
if resp.get("ok").and_then(|v| v.as_bool()).unwrap_or(false) {
println!("mati ok");
return Ok(());
}
anyhow::bail!("mati daemon reachable but ping returned an error");
}
cli::daemon::DaemonResult::Unresponsive => {
anyhow::bail!("mati daemon unresponsive");
}
cli::daemon::DaemonResult::NotRunning | cli::daemon::DaemonResult::StaleSocket => {
if daemon_only {
std::process::exit(1);
}
if !root.join("knowledge.db").exists() {
anyhow::bail!(
"no daemon running and no mati store initialized for this directory.\n\
Run `mati init` to set up, or `mati daemon start` to bring up a daemon."
);
}
}
}
let store = Store::open(&cwd).await?;
let latency_us = store.ping().await?;
println!("mati ok {latency_us}µs");
Ok(())
}
Commands::Serve { path } => {
let root = match path {
Some(p) => std::fs::canonicalize(&p)?,
None => std::env::current_dir()?,
};
mati_core::mcp::serve(&root).await
}
Commands::HookDecide(args) => cli::hook_decide::run(args).await,
Commands::Get { key } => cli::hooks::run_get(&key).await,
Commands::LogMiss { key } => cli::hooks::run_log_miss(&key).await,
Commands::LogHit { key } => cli::hooks::run_log_hit(&key).await,
Commands::LogComplianceMiss { key } => cli::hooks::run_log_compliance_miss(&key).await,
Commands::LogComplianceHit { key } => cli::hooks::run_log_compliance_hit(&key).await,
Commands::LogCodexShellMiss { key } => cli::hooks::run_log_codex_shell_miss(&key).await,
Commands::LogBootstrap { key } => cli::hooks::run_log_bootstrap(&key).await,
Commands::LogPromptNudge { key } => cli::hooks::run_log_prompt_nudge(&key).await,
Commands::SessionCheckConsulted { key } => {
cli::hooks::run_session_check_consulted(&key).await
}
Commands::SessionCheckConsultedRecent { key, ttl_secs } => {
cli::hooks::run_session_check_consulted_recent(&key, ttl_secs).await
}
Commands::SessionFlush => cli::hooks::run_session_flush().await,
Commands::SessionHarvest => cli::hooks::run_session_harvest().await,
Commands::SessionClearConsults => cli::hooks::run_session_clear_consults().await,
Commands::SubagentContext => cli::subagent_context::run_subagent_context().await,
Commands::DocCapture { path } => cli::hooks::run_doc_capture(&path).await,
Commands::EditHook { path } => cli::hooks::run_edit_hook(&path).await,
Commands::Reparse { path } => cli::reparse::run(&path).await,
Commands::VerifyEvidence(args) => cli::verify_evidence::run(args).await,
Commands::ExtractSignals(args) => cli::extract_signals::run(args).await,
Commands::PromptContext { files } => cli::hooks::run_prompt_context(&files).await,
}
}
async fn run_note(text: &str) -> Result<()> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let slug = slugify!(text, max_length = 30);
let key = format!("dev_note:{slug}-{now}");
let device_id = mati_core::store::stable_device_id();
let mut record = Record {
key: key.clone(),
value: text.to_string(),
category: Category::DevNote,
priority: mati_core::store::Priority::Normal,
tags: vec![],
created_at: now,
updated_at: now,
ref_url: None,
staleness: StalenessScore::fresh(),
lifecycle: RecordLifecycle::Active,
version: RecordVersion {
device_id,
logical_clock: 1,
wall_clock: now,
},
quality: QualityScore::layer0_default(),
access_count: 0,
last_accessed: 0,
source: RecordSource::DeveloperManual,
confidence: ConfidenceScore::for_new_record(&RecordSource::DeveloperManual),
gap_analysis_score: 0.0,
payload: None,
};
let score = quality::analyze(&record);
record.quality = score.clone();
let cwd = std::env::current_dir()?;
let proxy = StoreProxy::open(&cwd).await?;
proxy.put(&key, &record).await?;
println!("Created {key} (quality: {:.2})", score.value);
Ok(())
}
async fn run_quality_check() -> Result<()> {
let cwd = std::env::current_dir()?;
let proxy = StoreProxy::open(&cwd).await?;
let mut all: Vec<Record> = Vec::new();
for prefix in &["file:", "gotcha:", "decision:", "dev_note:"] {
all.extend(
proxy
.scan_prefix(prefix)
.await?
.into_iter()
.filter(|r| matches!(r.lifecycle, RecordLifecycle::Active)),
);
}
if all.is_empty() {
println!("No knowledge records found. Run `mati init` first.");
return Ok(());
}
for r in &mut all {
let score = quality::analyze(r);
r.quality = score;
}
let tiers = [
QualityTier::Suppressed,
QualityTier::Poor,
QualityTier::Acceptable,
QualityTier::Good,
QualityTier::Excellent,
];
for tier in &tiers {
let tier_records: Vec<&Record> = all.iter().filter(|r| r.quality.tier == *tier).collect();
if tier_records.is_empty() {
continue;
}
let tier_label = match tier {
QualityTier::Suppressed => "Suppressed (< 0.2)",
QualityTier::Poor => "Poor (0.2 – 0.4)",
QualityTier::Acceptable => "Acceptable (0.4 – 0.7)",
QualityTier::Good => "Good (0.7 – 0.9)",
QualityTier::Excellent => "Excellent (>= 0.9)",
};
println!("\n{tier_label} ({} records)", tier_records.len());
let mut table = Table::new();
table
.load_preset(UTF8_FULL_CONDENSED)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_header(vec![
Cell::new("Key"),
Cell::new("Score"),
Cell::new("Signals"),
]);
for r in &tier_records {
let sigs: Vec<&str> = r
.quality
.signals
.iter()
.map(|s| cli::show::signal_label(s))
.collect();
table.add_row(vec![
Cell::new(&r.key),
Cell::new(format!("{:.2}", r.quality.value)),
Cell::new(sigs.join(", ")),
]);
}
println!("{table}");
if *tier == QualityTier::Suppressed || *tier == QualityTier::Poor {
if let Some(first) = tier_records.first() {
let hints = quality::generate_improvement_hints(&first.quality);
if !hints.is_empty() {
println!(" Hints:");
for hint in hints {
println!(" - {hint}");
}
}
}
}
}
println!();
Ok(())
}
async fn run_improve(key: &str) -> Result<()> {
let cwd = std::env::current_dir()?;
let proxy = StoreProxy::open(&cwd).await?;
let use_color = io::stderr().is_terminal();
let mut record = match proxy.get(key).await? {
Some(r) => r,
None => anyhow::bail!("no record found for key '{key}'"),
};
let score = quality::analyze(&record);
println!("Current quality: {:.2} ({:?})", score.value, score.tier);
println!("Current value:\n {}\n", record.value);
let hints = quality::generate_improvement_hints(&score);
if !hints.is_empty() {
println!("Improvement hints:");
for hint in &hints {
println!(" - {hint}");
}
println!();
}
let stdin = io::stdin();
let mut lines = stdin.lock().lines();
eprint_prompt("New value (empty to keep current): ", use_color);
let new_value = read_line(&mut lines)?;
if !new_value.is_empty() {
record.value = new_value;
}
let new_score = quality::analyze(&record);
if quality::below_quality_gate(&new_score) {
quality::print_quality_gate_error(&new_score, use_color);
anyhow::bail!(
"record rejected by quality gate (score {:.2})",
new_score.value
);
}
if new_score.value < 0.4 {
quality::print_quality_caveat(&new_score, use_color);
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
record.quality = new_score.clone();
record.updated_at = now;
record.version.logical_clock += 1;
record.version.wall_clock = now;
proxy.put(key, &record).await?;
println!(
"Updated {key} (quality: {:.2} -> {:.2})",
score.value, new_score.value
);
Ok(())
}
fn eprint_prompt(msg: &str, use_color: bool) {
if use_color {
eprint!("{}{}{}", cli::colors::BLUE, msg, cli::colors::RESET);
} else {
eprint!("{msg}");
}
let _ = io::stderr().flush();
}
fn read_line(lines: &mut io::Lines<io::StdinLock<'_>>) -> Result<String> {
match lines.next() {
Some(Ok(line)) => Ok(line.trim().to_string()),
Some(Err(e)) => Err(e.into()),
None => Ok(String::new()),
}
}
#[cfg(test)]
mod qos_tests {
#[cfg(target_os = "macos")]
use super::lower_worker_thread_qos;
#[cfg(target_os = "macos")]
#[test]
fn worker_thread_qos_is_user_initiated() {
extern "C" {
fn qos_class_self() -> libc::c_uint;
}
const QOS_CLASS_USER_INITIATED: libc::c_uint = 0x19;
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.on_thread_start(lower_worker_thread_qos)
.build()
.unwrap();
let actual = rt.block_on(async {
tokio::task::spawn(async move { unsafe { qos_class_self() } })
.await
.unwrap()
});
assert_eq!(
actual, QOS_CLASS_USER_INITIATED,
"worker thread QoS should be USER_INITIATED (0x19), got {actual:#x}"
);
}
}