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:
parent
52523403c5
commit
2f2c84e1c0
6 changed files with 96 additions and 96 deletions
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue