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

View file

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

View file

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

View file

@ -19,13 +19,11 @@ use poc_memory::*;
use clap::{Parser, Subcommand};
use std::env;
use std::process;
/// Find the most recently modified .jsonl transcript in the Claude projects dir.
fn find_current_transcript() -> Option<String> {
let home = env::var("HOME").ok()?;
let projects = std::path::Path::new(&home).join(".claude/projects");
let projects = config::get().projects_dir.clone();
if !projects.exists() { return 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(())
}
/// 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> {
let home = env::var("HOME").unwrap_or_default();
let results_dir = std::path::PathBuf::from(&home)
.join(".claude/memory/agent-results");
let results_dir = store::memory_dir().join("agent-results");
if !results_dir.exists() {
println!("No agent results directory");
@ -1036,7 +1113,6 @@ fn cmd_apply_agent(process_all: bool) -> Result<(), String> {
let mut applied = 0;
let mut errors = 0;
// Find .json result files
let mut files: Vec<_> = std::fs::read_dir(&results_dir)
.map_err(|e| format!("read results dir: {}", e))?
.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());
if let (Some(start), Some(end)) = (source_start, source_end) {
println!(" Source: L{}-L{}", start, end);
}
let (a, e) = apply_agent_file(&mut store, &data);
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 {
let done_dir = util::memory_subdir("agent-results/done")?;
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,
};
/// 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
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)
.map_err(|e| format!("load prompt {}: {}", path.display(), e))?;
for (placeholder, data) in replacements {

View file

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