consolidate hardcoded paths into config, refactor apply_agent

Move prompts_dir into Config (was hardcoded ~/poc/memory/prompts).
Replace hardcoded ~/.claude/memory paths in spectral.rs, graph.rs,
and main.rs with store::memory_dir() or config::get(). Replace
hardcoded ~/.claude/projects in knowledge.rs and main.rs with
config::get().projects_dir.

Extract apply_agent_file() from cmd_apply_agent() — separates
file scanning from per-file JSON parsing and link application.
This commit is contained in:
ProofOfConcept 2026-03-08 21:16:52 -04:00
parent 52523403c5
commit 2f2c84e1c0
6 changed files with 96 additions and 96 deletions

View file

@ -51,6 +51,8 @@ pub struct Config {
pub context_groups: Vec<ContextGroup>, pub context_groups: Vec<ContextGroup>,
/// Max concurrent LLM calls in the daemon. /// Max concurrent LLM calls in the daemon.
pub llm_concurrency: usize, pub llm_concurrency: usize,
/// Directory containing prompt templates for agents.
pub prompts_dir: PathBuf,
/// Separate Claude config dir for background agent work (daemon jobs). /// Separate Claude config dir for background agent work (daemon jobs).
/// If set, passed as CLAUDE_CONFIG_DIR so the daemon authenticates /// If set, passed as CLAUDE_CONFIG_DIR so the daemon authenticates
/// with different OAuth credentials than the interactive session. /// with different OAuth credentials than the interactive session.
@ -81,6 +83,7 @@ impl Default for Config {
}, },
], ],
llm_concurrency: 1, llm_concurrency: 1,
prompts_dir: home.join("poc/memory/prompts"),
agent_config_dir: None, agent_config_dir: None,
} }
} }
@ -138,6 +141,9 @@ impl Config {
if let Some(n) = cfg.get("llm_concurrency").and_then(|v| v.as_u64()) { if let Some(n) = cfg.get("llm_concurrency").and_then(|v| v.as_u64()) {
config.llm_concurrency = n.max(1) as usize; config.llm_concurrency = n.max(1) as usize;
} }
if let Some(s) = cfg.get("prompts_dir").and_then(|v| v.as_str()) {
config.prompts_dir = expand_home(s);
}
if let Some(s) = cfg.get("agent_config_dir").and_then(|v| v.as_str()) { if let Some(s) = cfg.get("agent_config_dir").and_then(|v| v.as_str()) {
config.agent_config_dir = Some(expand_home(s)); config.agent_config_dir = Some(expand_home(s));
} }

View file

@ -519,8 +519,7 @@ pub struct MetricsSnapshot {
} }
fn metrics_log_path() -> std::path::PathBuf { fn metrics_log_path() -> std::path::PathBuf {
let home = std::env::var("HOME").unwrap_or_default(); crate::store::memory_dir().join("metrics.jsonl")
std::path::PathBuf::from(home).join(".claude/memory/metrics.jsonl")
} }
/// Load previous metrics snapshots /// Load previous metrics snapshots

View file

@ -22,8 +22,7 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
fn projects_dir() -> PathBuf { fn projects_dir() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into()); crate::config::get().projects_dir.clone()
PathBuf::from(home).join(".claude/projects")
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -19,13 +19,11 @@ use poc_memory::*;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::env;
use std::process; use std::process;
/// Find the most recently modified .jsonl transcript in the Claude projects dir. /// Find the most recently modified .jsonl transcript in the Claude projects dir.
fn find_current_transcript() -> Option<String> { fn find_current_transcript() -> Option<String> {
let home = env::var("HOME").ok()?; let projects = config::get().projects_dir.clone();
let projects = std::path::Path::new(&home).join(".claude/projects");
if !projects.exists() { return None; } if !projects.exists() { return None; }
let mut newest: Option<(std::time::SystemTime, std::path::PathBuf)> = None; let mut newest: Option<(std::time::SystemTime, std::path::PathBuf)> = None;
@ -1022,10 +1020,89 @@ fn cmd_link_impact(source: &str, target: &str) -> Result<(), String> {
Ok(()) Ok(())
} }
/// Apply links from a single agent result JSON file.
/// Returns (links_applied, errors).
fn apply_agent_file(
store: &mut store::Store,
data: &serde_json::Value,
) -> (usize, usize) {
let agent_result = data.get("agent_result").or(Some(data));
let links = match agent_result.and_then(|r| r.get("links")).and_then(|l| l.as_array()) {
Some(l) => l,
None => return (0, 0),
};
let entry_text = data.get("entry_text")
.and_then(|v| v.as_str())
.unwrap_or("");
if let (Some(start), Some(end)) = (
agent_result.and_then(|r| r.get("source_start")).and_then(|v| v.as_u64()),
agent_result.and_then(|r| r.get("source_end")).and_then(|v| v.as_u64()),
) {
println!(" Source: L{}-L{}", start, end);
}
let mut applied = 0;
let mut errors = 0;
for link in links {
let target = match link.get("target").and_then(|v| v.as_str()) {
Some(t) => t,
None => continue,
};
let reason = link.get("reason").and_then(|v| v.as_str()).unwrap_or("");
if let Some(note) = target.strip_prefix("NOTE:") {
println!(" NOTE: {}{}", note, reason);
continue;
}
let resolved = match store.resolve_key(target) {
Ok(r) => r,
Err(_) => {
println!(" SKIP {} (not found in graph)", target);
continue;
}
};
let source_key = match store.find_journal_node(entry_text) {
Some(k) => k,
None => {
println!(" SKIP {} (no matching journal node)", target);
continue;
}
};
let source_uuid = match store.nodes.get(&source_key) {
Some(n) => n.uuid,
None => continue,
};
let target_uuid = match store.nodes.get(&resolved) {
Some(n) => n.uuid,
None => continue,
};
let rel = store::new_relation(
source_uuid, target_uuid,
store::RelationType::Link,
0.5,
&source_key, &resolved,
);
if let Err(e) = store.add_relation(rel) {
eprintln!(" Error adding relation: {}", e);
errors += 1;
} else {
println!(" LINK {}{} ({})", source_key, resolved, reason);
applied += 1;
}
}
(applied, errors)
}
fn cmd_apply_agent(process_all: bool) -> Result<(), String> { fn cmd_apply_agent(process_all: bool) -> Result<(), String> {
let home = env::var("HOME").unwrap_or_default(); let results_dir = store::memory_dir().join("agent-results");
let results_dir = std::path::PathBuf::from(&home)
.join(".claude/memory/agent-results");
if !results_dir.exists() { if !results_dir.exists() {
println!("No agent results directory"); println!("No agent results directory");
@ -1036,7 +1113,6 @@ fn cmd_apply_agent(process_all: bool) -> Result<(), String> {
let mut applied = 0; let mut applied = 0;
let mut errors = 0; let mut errors = 0;
// Find .json result files
let mut files: Vec<_> = std::fs::read_dir(&results_dir) let mut files: Vec<_> = std::fs::read_dir(&results_dir)
.map_err(|e| format!("read results dir: {}", e))? .map_err(|e| format!("read results dir: {}", e))?
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
@ -1064,84 +1140,11 @@ fn cmd_apply_agent(process_all: bool) -> Result<(), String> {
} }
}; };
// Check for agent_result with links
let agent_result = data.get("agent_result").or(Some(&data));
let links = match agent_result.and_then(|r| r.get("links")).and_then(|l| l.as_array()) {
Some(l) => l,
None => continue,
};
let entry_text = data.get("entry_text")
.and_then(|v| v.as_str())
.unwrap_or("");
let source_start = agent_result
.and_then(|r| r.get("source_start"))
.and_then(|v| v.as_u64());
let source_end = agent_result
.and_then(|r| r.get("source_end"))
.and_then(|v| v.as_u64());
println!("Processing {}:", path.file_name().unwrap().to_string_lossy()); println!("Processing {}:", path.file_name().unwrap().to_string_lossy());
if let (Some(start), Some(end)) = (source_start, source_end) { let (a, e) = apply_agent_file(&mut store, &data);
println!(" Source: L{}-L{}", start, end); applied += a;
} errors += e;
for link in links {
let target = match link.get("target").and_then(|v| v.as_str()) {
Some(t) => t,
None => continue,
};
let reason = link.get("reason").and_then(|v| v.as_str()).unwrap_or("");
// Skip NOTE: targets (new topics, not existing nodes)
if let Some(note) = target.strip_prefix("NOTE:") {
println!(" NOTE: {}{}", note, reason);
continue;
}
// Try to resolve the target key and link from journal entry
let resolved = match store.resolve_key(target) {
Ok(r) => r,
Err(_) => {
println!(" SKIP {} (not found in graph)", target);
continue;
}
};
let source_key = match store.find_journal_node(entry_text) {
Some(k) => k,
None => {
println!(" SKIP {} (no matching journal node)", target);
continue;
}
};
// Get UUIDs for both nodes
let source_uuid = match store.nodes.get(&source_key) {
Some(n) => n.uuid,
None => continue,
};
let target_uuid = match store.nodes.get(&resolved) {
Some(n) => n.uuid,
None => continue,
};
let rel = store::new_relation(
source_uuid, target_uuid,
store::RelationType::Link,
0.5,
&source_key, &resolved,
);
if let Err(e) = store.add_relation(rel) {
eprintln!(" Error adding relation: {}", e);
errors += 1;
} else {
println!(" LINK {}{} ({})", source_key, resolved, reason);
applied += 1;
}
}
// Move processed file to avoid re-processing
if !process_all { if !process_all {
let done_dir = util::memory_subdir("agent-results/done")?; let done_dir = util::memory_subdir("agent-results/done")?;
let dest = done_dir.join(path.file_name().unwrap()); let dest = done_dir.join(path.file_name().unwrap());

View file

@ -11,15 +11,9 @@ use super::scoring::{
replay_queue, replay_queue_with_graph, detect_interference, replay_queue, replay_queue_with_graph, detect_interference,
}; };
/// Prompt template directory
pub fn prompts_dir() -> std::path::PathBuf {
let home = std::env::var("HOME").unwrap_or_default();
std::path::PathBuf::from(home).join("poc/memory/prompts")
}
/// Load a prompt template, replacing {{PLACEHOLDER}} with data /// Load a prompt template, replacing {{PLACEHOLDER}} with data
pub fn load_prompt(name: &str, replacements: &[(&str, &str)]) -> Result<String, String> { pub fn load_prompt(name: &str, replacements: &[(&str, &str)]) -> Result<String, String> {
let path = prompts_dir().join(format!("{}.md", name)); let path = crate::config::get().prompts_dir.join(format!("{}.md", name));
let mut content = std::fs::read_to_string(&path) let mut content = std::fs::read_to_string(&path)
.map_err(|e| format!("load prompt {}: {}", path.display(), e))?; .map_err(|e| format!("load prompt {}: {}", path.display(), e))?;
for (placeholder, data) in replacements { for (placeholder, data) in replacements {

View file

@ -43,8 +43,7 @@ pub struct SpectralEmbedding {
} }
fn embedding_path() -> PathBuf { fn embedding_path() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_default(); crate::store::memory_dir().join("spectral-embedding.json")
PathBuf::from(home).join(".claude/memory/spectral-embedding.json")
} }
/// Compute spectral decomposition of the memory graph. /// Compute spectral decomposition of the memory graph.