config-driven context loading, consolidate hooks, add docs

Move the hardcoded context priority groups from cmd_load_context()
into the config file as [context.NAME] sections. Add journal_days
and journal_max settings. The config parser handles section headers
with ordered group preservation.

Consolidate load-memory.sh into the memory-search binary — it now
handles both session-start context loading (first prompt) and ambient
search (subsequent prompts), eliminating the shell script.

Update install_hook() to reference ~/.cargo/bin/memory-search and
remove the old load-memory.sh entry from settings.json.

Add end-user documentation (doc/README.md) covering installation,
configuration, all commands, hook mechanics, and notes for AI
assistants using the system.

Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-03-05 15:54:44 -05:00
parent a8aaadb0ad
commit 90d60894ed
5 changed files with 336 additions and 78 deletions

View file

@ -8,6 +8,12 @@ use std::sync::OnceLock;
static CONFIG: OnceLock<Config> = OnceLock::new();
#[derive(Debug, Clone)]
pub struct ContextGroup {
pub label: String,
pub keys: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct Config {
/// Display name for the human user in transcripts/prompts.
@ -20,6 +26,12 @@ pub struct Config {
pub projects_dir: PathBuf,
/// Core node keys that should never be decayed/deleted.
pub core_nodes: Vec<String>,
/// How many days of journal to include in load-context.
pub journal_days: u32,
/// Max journal entries to include in load-context.
pub journal_max: usize,
/// Ordered context groups for session-start loading.
pub context_groups: Vec<ContextGroup>,
}
impl Default for Config {
@ -31,6 +43,11 @@ impl Default for Config {
data_dir: home.join(".claude/memory"),
projects_dir: home.join(".claude/projects"),
core_nodes: vec!["identity.md".to_string()],
journal_days: 7,
journal_max: 20,
context_groups: vec![
ContextGroup { label: "identity".into(), keys: vec!["identity.md".into()] },
],
}
}
}
@ -50,16 +67,56 @@ impl Config {
return config;
};
// Simple TOML parser — we only need flat key = "value" pairs.
// Simple TOML parser: flat key=value pairs + [context.NAME] sections.
let mut context_groups: Vec<ContextGroup> = Vec::new();
let mut current_section: Option<String> = None;
let mut current_label: Option<String> = None;
let mut current_keys: Vec<String> = Vec::new();
let mut saw_context = false;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
// Section header: [context.NAME]
if line.starts_with('[') && line.ends_with(']') {
// Flush previous context section
if let Some(name) = current_section.take() {
let label = current_label.take()
.unwrap_or_else(|| name.replace('_', " "));
context_groups.push(ContextGroup { label, keys: std::mem::take(&mut current_keys) });
}
let section = &line[1..line.len()-1];
if let Some(name) = section.strip_prefix("context.") {
current_section = Some(name.to_string());
saw_context = true;
}
continue;
}
let Some((key, value)) = line.split_once('=') else { continue };
let key = key.trim();
let value = value.trim().trim_matches('"');
// Inside a [context.X] section
if current_section.is_some() {
match key {
"keys" => {
current_keys = value.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
}
"label" => current_label = Some(value.to_string()),
_ => {}
}
continue;
}
// Top-level keys
match key {
"user_name" => config.user_name = value.to_string(),
"assistant_name" => config.assistant_name = value.to_string(),
@ -71,10 +128,27 @@ impl Config {
.filter(|s| !s.is_empty())
.collect();
}
"journal_days" => {
if let Ok(d) = value.parse() { config.journal_days = d; }
}
"journal_max" => {
if let Ok(m) = value.parse() { config.journal_max = m; }
}
_ => {}
}
}
// Flush final section
if let Some(name) = current_section.take() {
let label = current_label.take()
.unwrap_or_else(|| name.replace('_', " "));
context_groups.push(ContextGroup { label, keys: current_keys });
}
if saw_context {
config.context_groups = context_groups;
}
config
}
}