use clap::{Args, Subcommand};
use cortex_reflect::{
extract_deterministic_candidates, AcceptedMemory, PrincipleCandidateBatch,
PrincipleExtractionWindow,
};
use crate::cmd::open_default_store;
use crate::exit::Exit;
#[derive(Debug, Subcommand)]
pub enum PrinciplesSub {
Extract(ExtractArgs),
}
#[derive(Debug, Args)]
pub struct ExtractArgs {
#[arg(long, value_name = "WINDOW")]
pub window: String,
#[arg(long, value_name = "MODEL")]
pub model: String,
}
pub fn run(sub: PrinciplesSub) -> Exit {
match sub {
PrinciplesSub::Extract(args) => extract(args),
}
}
fn extract(args: ExtractArgs) -> Exit {
if args.window.trim().is_empty() {
eprintln!("cortex principles extract: --window must not be empty");
return Exit::Usage;
}
if args.model.trim().is_empty() {
eprintln!("cortex principles extract: --model must not be empty");
return Exit::Usage;
}
if args.model != "replay" {
eprintln!(
"cortex principles extract: unsupported --model `{}`. Only `replay` is wired for store-backed Phase 2 extraction.",
args.model
);
return Exit::Usage;
}
let pool = match open_default_store("principles extract") {
Ok(pool) => pool,
Err(exit) => return exit,
};
let repo = cortex_store::repo::MemoryRepo::new(&pool);
let memories = match repo.list_by_status("active") {
Ok(memories) => memories,
Err(err) => {
eprintln!("cortex principles extract: failed to read active memories: {err}");
return Exit::Internal;
}
};
let window = PrincipleExtractionWindow::new(
memories
.into_iter()
.map(|memory| AcceptedMemory {
id: memory.id,
claim: memory.claim,
domains: string_array(&memory.domains_json),
applies_when: string_array(&memory.applies_when_json),
does_not_apply_when: string_array(&memory.does_not_apply_when_json),
})
.collect(),
);
let batch = PrincipleCandidateBatch {
candidate_principles: extract_deterministic_candidates(&window),
};
match serde_json::to_string_pretty(&batch) {
Ok(serialized) => {
println!("{serialized}");
Exit::Ok
}
Err(err) => {
eprintln!("cortex principles extract: failed to serialize candidates: {err}");
Exit::Internal
}
}
}
fn string_array(value: &serde_json::Value) -> Vec<String> {
value
.as_array()
.into_iter()
.flatten()
.filter_map(|value| value.as_str().map(ToString::to_string))
.collect()
}