Refactor: Dismantle MemoryState into semantic domain sub-structs

This commit is contained in:
Riz Ashraf committed 2026-09-30 21:13:03 +01:00
1 parent 0e866f2465
commit a34554b7ff
13 files changed
+186 -158

No files matched your search

+2 -2
View File
@@ -38,7 +38,7 @@ pub async fn gate_verify_handler(
) -> Result<impl IntoResponse, AppError> {
let mut found = None;
let mut to_remove = None;
app_state.handler.state.gates.modify(|gates| {
app_state.handler.state.env.gates.modify(|gates| {
if let Some(idx) = gates.iter().position(|g| {
g.action == q.action
&& g.target == q.target
@@ -96,7 +96,7 @@ pub async fn gate_set_handler(
reason: body.reason.clone(),
timestamp: crate::handlers::utils::now_secs(),
};
app_state.handler.state.gates.modify(|gates| {
app_state.handler.state.env.gates.modify(|gates| {
gates.retain(|g| !(g.action == record.action && g.target == record.target));
gates.push(record);
});
+29 -29
View File
@@ -80,7 +80,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
post({
let state_clone = app_state.handler.state.clone();
move |axum::extract::Path(id): axum::extract::Path<String>| async move {
state_clone.tasks.modify(|tasks| {
state_clone.project.tasks.modify(|tasks| {
for t in tasks.iter_mut() {
if t.id == id {
t.status = "completed".to_string();
@@ -97,7 +97,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let tasks_json = state_clone.tasks.read_with(|t| serde_json::to_string(t).unwrap_or_else(|_| "[]".to_string()));
let tasks_json = state_clone.project.tasks.read_with(|t| serde_json::to_string(t).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], tasks_json)
}
}),
@@ -107,7 +107,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let sticky_json = state_clone.sticky.read_with(|s| serde_json::to_string(s).unwrap_or_else(|_| "[]".to_string()));
let sticky_json = state_clone.code.sticky.read_with(|s| serde_json::to_string(s).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], sticky_json)
}
}),
@@ -145,7 +145,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let activities_json = state_clone.recent_activities.read_with(|a| serde_json::to_string(a).unwrap_or_else(|_| "[]".to_string()));
let activities_json = state_clone.telemetry.recent_activities.read_with(|a| serde_json::to_string(a).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], activities_json)
}
}),
@@ -155,7 +155,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let json = state_clone.tech_debts.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
let json = state_clone.code.tech_debts.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], json)
}
}),
@@ -165,7 +165,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let json = state_clone.adrs.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
let json = state_clone.code.adrs.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], json)
}
}),
@@ -175,7 +175,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let json = state_clone.context_workspaces.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
let json = state_clone.project.context_workspaces.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], json)
}
}),
@@ -185,7 +185,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let json = state_clone.handoff_memos.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
let json = state_clone.telemetry.handoff_memos.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], json)
}
}),
@@ -195,7 +195,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let json = state_clone.milestones.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
let json = state_clone.project.milestones.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], json)
}
}),
@@ -205,7 +205,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let json = state_clone.snippets.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
let json = state_clone.code.snippets.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], json)
}
}),
@@ -215,7 +215,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let json = state_clone.pr_checklists.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
let json = state_clone.project.pr_checklists.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], json)
}
}),
@@ -225,7 +225,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
get({
let state_clone = app_state.handler.state.clone();
move || async move {
let json = state_clone.error_fixes.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
let json = state_clone.code.error_fixes.read_with(|items| serde_json::to_string(items).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], json)
}
}),
@@ -236,24 +236,24 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
let state_clone = app_state.handler.state.clone();
move || async move {
let (entities, relations) = state_clone.read_graph(|g| (g.entities.len(), g.relations.len()));
let tasks = state_clone.tasks.read_with(|items| items.len());
let snippets = state_clone.snippets.read_with(|items| items.len());
let tech_debts = state_clone.tech_debts.read_with(|items| items.len());
let adrs = state_clone.adrs.read_with(|items| items.len());
let tasks = state_clone.project.tasks.read_with(|items| items.len());
let snippets = state_clone.code.snippets.read_with(|items| items.len());
let tech_debts = state_clone.code.tech_debts.read_with(|items| items.len());
let adrs = state_clone.code.adrs.read_with(|items| items.len());
let ledger = state_clone.ledger.read_with(|items| items.len());
let sticky = state_clone.sticky.read_with(|items| items.len());
let error_fixes = state_clone.error_fixes.read_with(|items| items.len());
let pinned_files = state_clone.pinned_files.read_with(|items| items.len());
let session_summaries = state_clone.session_summaries.read_with(|items| items.len());
let handoff_memos = state_clone.handoff_memos.read_with(|items| items.len());
let env_fingerprints = state_clone.env_fingerprints.read_with(|items| items.len());
let env_requirements = state_clone.env_requirements.read_with(|items| items.len());
let milestones = state_clone.milestones.read_with(|items| items.len());
let environments = state_clone.environments.read_with(|items| items.len());
let pr_checklists = state_clone.pr_checklists.read_with(|items| items.len());
let gates = state_clone.gates.read_with(|items| items.len());
let context_workspaces = state_clone.context_workspaces.read_with(|items| items.len());
let ledger = state_clone.code.ledger.read_with(|items| items.len());
let sticky = state_clone.code.sticky.read_with(|items| items.len());
let error_fixes = state_clone.code.error_fixes.read_with(|items| items.len());
let pinned_files = state_clone.project.pinned_files.read_with(|items| items.len());
let session_summaries = state_clone.telemetry.session_summaries.read_with(|items| items.len());
let handoff_memos = state_clone.telemetry.handoff_memos.read_with(|items| items.len());
let env_fingerprints = state_clone.env.env_fingerprints.read_with(|items| items.len());
let env_requirements = state_clone.env.env_requirements.read_with(|items| items.len());
let milestones = state_clone.project.milestones.read_with(|items| items.len());
let environments = state_clone.env.environments.read_with(|items| items.len());
let pr_checklists = state_clone.project.pr_checklists.read_with(|items| items.len());
let gates = state_clone.env.gates.read_with(|items| items.len());
let context_workspaces = state_clone.project.context_workspaces.read_with(|items| items.len());
axum::Json(serde_json::json!({
"entities": entities,
+2 -2
View File
@@ -79,7 +79,7 @@ use crate::models::TerminalHistory;
pub async fn get_terminal_history_handler(
State(state): State<Arc<AppState>>,
) -> impl axum::response::IntoResponse {
let history_json = state.handler.state.terminal_history.read_with(|h| serde_json::to_string(h).unwrap_or_else(|_| "[]".to_string()));
let history_json = state.handler.state.telemetry.terminal_history.read_with(|h| serde_json::to_string(h).unwrap_or_else(|_| "[]".to_string()));
([(axum::http::header::CONTENT_TYPE, "application/json")], history_json)
}
@@ -87,7 +87,7 @@ pub async fn terminal_telemetry_handler(
State(state): State<Arc<AppState>>,
axum::Json(payload): axum::Json<TerminalHistory>,
) -> impl axum::response::IntoResponse {
state.handler.state.terminal_history.modify(|history| {
state.handler.state.telemetry.terminal_history.modify(|history| {
history.push_front(payload.clone());
if history.len() > 100 {
history.pop_back();
+1 -1
View File
@@ -33,7 +33,7 @@ pub fn spawn_watcher(state: Arc<MemoryState>) {
expires_at: None,
};
state.sticky.modify(|notes| {
state.code.sticky.modify(|notes| {
notes.push(note.clone());
});
+6 -6
View File
@@ -23,7 +23,7 @@ impl McpTool for UpdateEnvFingerprintHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: UpdateEnvFingerprintTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
state.env_fingerprints.modify(|fps| {
state.env.env_fingerprints.modify(|fps| {
fps.insert(
req.namespace.clone(),
crate::models::EnvFingerprint {
@@ -58,7 +58,7 @@ impl McpTool for ReadEnvFingerprintHandler {
let req: ReadEnvFingerprintTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state
.env_fingerprints
.env.env_fingerprints
.read_with(|fps| fps.get(&req.namespace).cloned());
if let Some(fp) = data {
let data = Ok::<String, crate::error::AppError>(serde_json::to_string(&fp)?)?;
@@ -86,7 +86,7 @@ impl McpTool for LogEnvRequirementHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: LogEnvRequirementTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.env_requirements.modify(|reqs| {
state.env.env_requirements.modify(|reqs| {
reqs.retain(|r| !(r.namespace == req.namespace && r.key == req.key));
reqs.push(crate::models::EnvRequirement {
namespace: req.namespace,
@@ -117,7 +117,7 @@ impl McpTool for RegisterEnvironmentHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: RegisterEnvironmentTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
state.environments.modify(|envs| {
state.env.environments.modify(|envs| {
envs.retain(|e| !(e.namespace == req.namespace && e.name == req.name));
envs.push(crate::models::EnvironmentDetail {
namespace: req.namespace,
@@ -150,7 +150,7 @@ impl McpTool for GetEnvironmentDetailsHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: GetEnvironmentDetailsTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.environments.read_with(|envs| {
let data = state.env.environments.read_with(|envs| {
let filtered: Vec<_> = envs
.iter()
.filter(|e| e.namespace == req.namespace)
@@ -197,7 +197,7 @@ mod tests {
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
// Ensure namespace is present in test setup
state.environments.modify(|e| {
state.env.environments.modify(|e| {
e.push(crate::models::EnvironmentDetail {
namespace: "global".to_string(),
name: "test".to_string(),
+24 -24
View File
@@ -24,7 +24,7 @@ impl McpTool for LogDecisionHandler {
let idx = state.get_search_index();
let mut final_id = String::new();
state.adrs.modify(|adrs| {
state.code.adrs.modify(|adrs| {
if let Some(superseded_id) = &req.supersedes {
for old_adr in adrs.iter_mut() {
if old_adr.id == *superseded_id {
@@ -70,7 +70,7 @@ impl McpTool for QueryDecisionsHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: QueryDecisionsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.adrs.read_with(|adrs| {
let data = state.code.adrs.read_with(|adrs| {
let filtered: Vec<_> = adrs
.iter()
.filter(|a| {
@@ -108,7 +108,7 @@ impl McpTool for DeleteDecisionHandler {
let req: crate::tools::DeleteDecisionTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut found = false;
state.adrs.modify(|adrs| {
state.code.adrs.modify(|adrs| {
if let Some(pos) = adrs.iter().position(|a| a.id == req.id) {
adrs.remove(pos);
found = true;
@@ -140,7 +140,7 @@ impl McpTool for LogErrorFixHandler {
let req: LogErrorFixTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let text_to_embed = format!("Signature: {}\nSolution: {}", req.signature, req.solution);
let embedding = crate::embedding::generate_embedding_async(text_to_embed).await.ok();
state.error_fixes.modify(|fixes| {
state.code.error_fixes.modify(|fixes| {
fixes.push(crate::models::ErrorFix {
signature: req.signature,
solution: req.solution,
@@ -172,7 +172,7 @@ impl McpTool for SearchErrorFixesHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: SearchErrorFixesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let q = req.query;
let data = state.error_fixes.read_with(|fixes| {
let data = state.code.error_fixes.read_with(|fixes| {
let filtered: Vec<_> = fixes
.iter()
.filter(|f| {
@@ -200,7 +200,7 @@ impl McpTool for LogCodeChangeHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: LogCodeChangeTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.ledger.modify(|ledger| {
state.code.ledger.modify(|ledger| {
ledger.push(CodeChange {
timestamp: crate::handlers::utils::now_secs(),
file_path: req.file_path,
@@ -230,7 +230,7 @@ impl McpTool for QueryRecentChangesHandler {
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let data = state
.ledger
.code.ledger
.read_with(|l| Ok::<String, crate::error::AppError>(serde_json::to_string(l)?))?;
Ok(data)
}
@@ -250,7 +250,7 @@ impl McpTool for LearnPreferenceHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: LearnPreferenceTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.prefs.modify(|prefs| {
state.env.prefs.modify(|prefs| {
prefs.insert(
req.key.clone(),
crate::models::Preference {
@@ -278,7 +278,7 @@ impl McpTool for ReadPreferencesHandler {
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
state
.prefs
.env.prefs
.read_with(|prefs| Ok::<String, crate::error::AppError>(serde_json::to_string(prefs)?))
}
}
@@ -299,7 +299,7 @@ impl McpTool for LogTechDebtHandler {
let req: LogTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let text_to_embed = format!("Description: {}\nIdeal Solution: {}", req.description, req.ideal_solution);
let embedding = crate::embedding::generate_embedding_async(text_to_embed).await.ok();
state.tech_debts.modify(|debts| {
state.code.tech_debts.modify(|debts| {
debts.push(crate::models::TechDebt {
id: uuid::Uuid::new_v4().to_string(),
namespace: req.namespace,
@@ -334,7 +334,7 @@ impl McpTool for ResolveTechDebtHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ResolveTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut found = false;
state.tech_debts.modify(|debts| {
state.code.tech_debts.modify(|debts| {
for d in debts.iter_mut() {
if d.id == req.id {
d.is_resolved = true;
@@ -365,7 +365,7 @@ impl McpTool for ListTechDebtHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ListTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.tech_debts.read_with(|debts| {
let data = state.code.tech_debts.read_with(|debts| {
let filtered: Vec<_> = debts
.iter()
.filter(|d| {
@@ -451,7 +451,7 @@ impl McpTool for OmniSearchHandler {
}
}
let tasks_json = state.tasks.read_with(|all_tasks| {
let tasks_json = state.project.tasks.read_with(|all_tasks| {
let filtered: Vec<_> = all_tasks
.iter()
.filter(|t| matched_tasks.contains(t.id.as_str()))
@@ -470,7 +470,7 @@ impl McpTool for OmniSearchHandler {
serde_json::to_value(&filtered).map_err(|e| e.to_string())
})?;
let snippets_json = state.snippets.read_with(|all_snippets| {
let snippets_json = state.code.snippets.read_with(|all_snippets| {
let mut scored: Vec<_> = all_snippets.iter().map(|s| {
let mut score = 0.0;
if matched_snippets.contains(s.name.as_str()) || contains_ignore_ascii_case(&s.name, &q) || contains_ignore_ascii_case(&s.description, &q) {
@@ -495,7 +495,7 @@ impl McpTool for OmniSearchHandler {
serde_json::to_value(&filtered).map_err(|e| e.to_string())
})?;
let adrs_json = state.adrs.read_with(|all_adrs| {
let adrs_json = state.code.adrs.read_with(|all_adrs| {
let filtered: Vec<_> = all_adrs
.iter()
.filter(|a| matched_adrs.contains(a.id.as_str()))
@@ -516,7 +516,7 @@ impl McpTool for OmniSearchHandler {
})?;
let q = req.query;
let tech_debts_json = state.tech_debts.read_with(|debts| {
let tech_debts_json = state.code.tech_debts.read_with(|debts| {
let mut scored: Vec<_> = debts.iter().map(|d| {
if req.namespace.as_ref().is_some_and(|ns| d.namespace != *ns) {
return (d, 0.0);
@@ -545,7 +545,7 @@ impl McpTool for OmniSearchHandler {
serde_json::to_value(&filtered).map_err(|e| e.to_string())
})?;
let memos_json = state.handoff_memos.read_with(|memos| {
let memos_json = state.telemetry.handoff_memos.read_with(|memos| {
let filtered: Vec<_> = memos
.iter()
.filter(|m| {
@@ -566,7 +566,7 @@ impl McpTool for OmniSearchHandler {
serde_json::to_value(&filtered).map_err(|e| e.to_string())
})?;
let error_fixes_json = state.error_fixes.read_with(|fixes| {
let error_fixes_json = state.code.error_fixes.read_with(|fixes| {
let mut scored: Vec<_> = fixes.iter().map(|f| {
let mut score = 0.0;
if contains_ignore_ascii_case(&f.signature, &q) || contains_ignore_ascii_case(&f.solution, &q) {
@@ -614,27 +614,27 @@ impl McpTool for GetProjectHealthHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: GetProjectHealthTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let active_tasks = state
.tasks
.project.tasks
.read_with(|tasks| tasks.iter().filter(|t| t.status != "done").count());
let unresolved_debt = state.tech_debts.read_with(|debts| {
let unresolved_debt = state.code.tech_debts.read_with(|debts| {
debts
.iter()
.filter(|d| d.namespace == req.namespace && !d.is_resolved)
.count()
});
let unread_memos = state.handoff_memos.read_with(|memos| {
let unread_memos = state.telemetry.handoff_memos.read_with(|memos| {
memos
.iter()
.filter(|m| m.namespace == req.namespace)
.count()
});
let active_milestones = state.milestones.read_with(|milestones| {
let active_milestones = state.project.milestones.read_with(|milestones| {
milestones
.iter()
.filter(|m| m.namespace == req.namespace && m.status != "done")
.count()
});
let remaining_checklists = state.pr_checklists.read_with(|checklists| {
let remaining_checklists = state.project.pr_checklists.read_with(|checklists| {
checklists
.iter()
.filter(|c| c.namespace == req.namespace)
@@ -821,7 +821,7 @@ mod tests {
};
{
state.tasks.modify(|t| {
state.project.tasks.modify(|t| {
t.push(task.clone());
});
}
+11 -11
View File
@@ -21,7 +21,7 @@ impl McpTool for AddStickyNoteHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: AddStickyNoteTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.sticky.modify(|notes| {
state.code.sticky.modify(|notes| {
notes.push(StickyNote {
timestamp: crate::handlers::utils::now_secs(),
content: req.content,
@@ -49,7 +49,7 @@ impl McpTool for ReadStickyNotesHandler {
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let data = state
.sticky
.code.sticky
.read_with(|s| Ok::<String, crate::error::AppError>(serde_json::to_string(s)?))?;
Ok(data)
}
@@ -73,7 +73,7 @@ impl McpTool for DeleteStickyNoteHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: DeleteStickyNoteTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut success = false;
state.sticky.modify(|notes| {
state.code.sticky.modify(|notes| {
if req.index > 0 && req.index <= notes.len() {
notes.remove(req.index - 1);
success = true;
@@ -103,7 +103,7 @@ impl McpTool for ClearStickyNotesHandler {
}
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
state.sticky.modify(|notes| {
state.code.sticky.modify(|notes| {
notes.clear();
});
Ok("All sticky notes cleared.".to_string())
@@ -127,7 +127,7 @@ impl McpTool for LeaveHandoffMemoHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: LeaveHandoffMemoTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.handoff_memos.modify(|memos| {
state.telemetry.handoff_memos.modify(|memos| {
memos.push(crate::models::HandoffMemo {
id: uuid::Uuid::new_v4().to_string(),
author: "agy".to_string(),
@@ -158,7 +158,7 @@ impl McpTool for ReadHandoffMemosHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ReadHandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.handoff_memos.read_with(|items| {
let data = state.telemetry.handoff_memos.read_with(|items| {
let filtered: Vec<_> = items
.iter()
.filter(|i| {
@@ -194,7 +194,7 @@ impl McpTool for ClearHandoffMemosHandler {
let req: ClearHandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let ids: HashSet<_> = req.ids.into_iter().collect();
state
.handoff_memos
.telemetry.handoff_memos
.modify(|memos| memos.retain(|m| !ids.contains(&m.id)));
Ok("Handoff memos cleared".to_string())
}
@@ -217,7 +217,7 @@ impl McpTool for AddSessionSummaryHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: AddSessionSummaryTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.session_summaries.modify(|summaries| {
state.telemetry.session_summaries.modify(|summaries| {
summaries.push(crate::models::SessionSummary {
summary: req.summary,
namespace: req.namespace,
@@ -249,9 +249,9 @@ impl McpTool for GenerateStandupReportHandler {
serde_json::from_value(args).map_err(|e| e.to_string())?;
let cutoff = crate::handlers::utils::now_secs().saturating_sub(req.hours_lookback * 3600);
let report_str = state.tasks.read_with(|items| {
state.ledger.read_with(|changes| {
state.session_summaries.read_with(|summaries| {
let report_str = state.project.tasks.read_with(|items| {
state.code.ledger.read_with(|changes| {
state.telemetry.session_summaries.read_with(|summaries| {
let filtered_tasks: Vec<_> = items.iter().filter(|t| t.updated_at >= cutoff).collect();
let filtered_changes: Vec<_> = changes.iter().filter(|c| c.timestamp >= cutoff).collect();
let filtered_summaries: Vec<_> = summaries.iter().filter(|s| s.namespace == req.namespace && s.timestamp >= cutoff).collect();
+10 -10
View File
@@ -40,7 +40,7 @@ impl McpTool for AddTaskHandler {
};
let idx = state.get_search_index();
drop(idx.index_task(&task));
state.tasks.modify(|tasks| {
state.project.tasks.modify(|tasks| {
tasks.push(task);
});
Ok(format!("Task added with ID: {}", task_id).to_string())
@@ -63,7 +63,7 @@ impl McpTool for DeleteTaskHandler {
let req: DeleteTaskTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut deleted_count = 0;
let mut actually_deleted = Vec::new();
state.tasks.modify(|tasks| {
state.project.tasks.modify(|tasks| {
let initial_len = tasks.len();
// Build index-based children map
@@ -141,7 +141,7 @@ impl McpTool for UpdateTaskStatusHandler {
let mut blocker_details = String::new();
let target_status = req.status.to_lowercase();
state.tasks.modify(|tasks| {
state.project.tasks.modify(|tasks| {
// Find target task
let target_idx = tasks
.iter()
@@ -279,7 +279,7 @@ impl McpTool for ListActiveTasksHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ListActiveTasksTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.tasks.read_with(|tasks| {
let data = state.project.tasks.read_with(|tasks| {
let filtered: Vec<_> = tasks
.iter()
.filter(|t| {
@@ -319,7 +319,7 @@ impl McpTool for SetAcceptanceCriteriaHandler {
let req: SetAcceptanceCriteriaTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut success = false;
state.tasks.modify(|tasks| {
state.project.tasks.modify(|tasks| {
if let Some(task) = tasks.iter_mut().rev().find(|t| t.title == req.task_title) {
task.acceptance_criteria = req
.criteria
@@ -362,7 +362,7 @@ impl McpTool for VerifyAcceptanceCriteriaHandler {
serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut success = false;
let mut already_met = false;
state.tasks.modify(|tasks| {
state.project.tasks.modify(|tasks| {
if let Some(task) = tasks.iter_mut().find(|t| t.id == req.task_id)
&& let Some(ac) = task
.acceptance_criteria
@@ -405,7 +405,7 @@ impl McpTool for AddMilestoneHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: AddMilestoneTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.milestones.modify(|ms| {
state.project.milestones.modify(|ms| {
ms.push(crate::models::Milestone {
id: uuid::Uuid::new_v4().to_string(),
title: req.title,
@@ -433,7 +433,7 @@ impl McpTool for UpdateMilestoneHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: UpdateMilestoneTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut found = false;
state.milestones.modify(|ms| {
state.project.milestones.modify(|ms| {
for m in ms.iter_mut() {
if m.id == req.id {
m.status = req.status.clone();
@@ -464,7 +464,7 @@ impl McpTool for ListMilestonesHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ListMilestonesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.milestones.read_with(|items| {
let data = state.project.milestones.read_with(|items| {
let filtered: Vec<_> = items
.iter()
.filter(|i| {
@@ -559,7 +559,7 @@ mod tests {
assert!(res1.contains("Milestone added"));
// Fetch milestone ID from state directly to update
let ms_id = state.milestones.read_with(|ms| ms[0].id.clone());
let ms_id = state.project.milestones.read_with(|ms| ms[0].id.clone());
// Update Milestone
let update_ms = UpdateMilestoneHandler;
+15 -15
View File
@@ -20,7 +20,7 @@ impl McpTool for PinFileHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: PinFileTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.pinned_files.modify(|pinned| {
state.project.pinned_files.modify(|pinned| {
pinned.retain(|p| p.namespace != req.namespace || p.file_path != req.file_path);
pinned.push(crate::models::PinnedFile {
namespace: req.namespace,
@@ -47,7 +47,7 @@ impl McpTool for UnpinFileHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: UnpinFileTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state.pinned_files.modify(|pinned| {
state.project.pinned_files.modify(|pinned| {
pinned.retain(|p| p.namespace != req.namespace || p.file_path != req.file_path)
});
Ok("File unpinned".to_string())
@@ -71,7 +71,7 @@ impl McpTool for ListPinnedFilesHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ListPinnedFilesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.pinned_files.read_with(|pinned| {
let data = state.project.pinned_files.read_with(|pinned| {
let filtered: Vec<_> = pinned
.iter()
.filter(|p| {
@@ -124,7 +124,7 @@ impl McpTool for StoreSnippetHandler {
let idx = state.get_search_index();
drop(idx.index_snippet(&snippet));
state.snippets.modify(|snippets| {
state.code.snippets.modify(|snippets| {
snippets.retain(|s| s.name != req_name);
snippets.push(snippet);
});
@@ -148,7 +148,7 @@ impl McpTool for SearchSnippetsHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: SearchSnippetsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let query = req.query;
let data = state.snippets.read_with(|snippets| {
let data = state.code.snippets.read_with(|snippets| {
let results: Vec<_> = snippets
.iter()
.filter(|s| {
@@ -178,7 +178,7 @@ impl McpTool for DeleteSnippetHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: DeleteSnippetTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut deleted = false;
state.snippets.modify(|snippets| {
state.code.snippets.modify(|snippets| {
let orig = snippets.len();
snippets.retain(|s| s.name != req.name);
deleted = snippets.len() < orig;
@@ -211,7 +211,7 @@ impl McpTool for SaveContextWorkspaceHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: SaveContextWorkspaceTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
state.context_workspaces.modify(|ws| {
state.project.context_workspaces.modify(|ws| {
ws.retain(|w| w.namespace != req.namespace || w.name != req.name);
ws.push(crate::models::ContextWorkspace {
namespace: req.namespace,
@@ -243,7 +243,7 @@ impl McpTool for LoadContextWorkspaceHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: LoadContextWorkspaceTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.context_workspaces.read_with(|ws| {
let data = state.project.context_workspaces.read_with(|ws| {
let filtered: Vec<_> = ws
.iter()
.filter(|w| w.namespace == req.namespace && w.name == req.name)
@@ -272,7 +272,7 @@ impl McpTool for ListContextWorkspacesHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ListContextWorkspacesTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.context_workspaces.read_with(|ws| {
let data = state.project.context_workspaces.read_with(|ws| {
let filtered: Vec<_> = ws
.iter()
.filter(|w| req.namespace.as_ref().is_none_or(|ns| &w.namespace == ns))
@@ -303,7 +303,7 @@ impl McpTool for DeleteContextWorkspaceHandler {
serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut found = false;
state.context_workspaces.modify(|ws| {
state.project.context_workspaces.modify(|ws| {
if let Some(pos) = ws
.iter()
.position(|w| w.namespace == req.namespace && w.name == req.name)
@@ -339,7 +339,7 @@ impl McpTool for AddPrChecklistItemHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: AddPrChecklistItemTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
state.pr_checklists.modify(|items| {
state.project.pr_checklists.modify(|items| {
items.push(crate::models::PrChecklistItem {
namespace: req.namespace,
id: uuid::Uuid::new_v4().to_string(),
@@ -364,7 +364,7 @@ impl McpTool for GetPrChecklistHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: GetPrChecklistTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.pr_checklists.read_with(|items| {
let data = state.project.pr_checklists.read_with(|items| {
let filtered: Vec<_> = items
.iter()
.filter(|i| i.namespace == req.namespace)
@@ -393,7 +393,7 @@ impl McpTool for ClearPrChecklistHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ClearPrChecklistTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
state
.pr_checklists
.project.pr_checklists
.modify(|items| items.retain(|i| i.namespace != req.namespace));
Ok("PR checklist cleared".to_string())
}
@@ -625,14 +625,14 @@ impl McpTool for SemanticCodeSearchHandler {
let mut texts_to_embed = Vec::new();
let mut metadata = Vec::new();
let snippets = state.snippets.read_with(|snips| snips.clone());
let snippets = state.code.snippets.read_with(|snips| snips.clone());
for snippet in snippets {
let combined = format!("{} {} {}", snippet.name, snippet.description, snippet.code);
texts_to_embed.push(combined);
metadata.push((snippet.name, snippet.description));
}
let sticky = state.sticky.read_with(|s| s.clone());
let sticky = state.code.sticky.read_with(|s| s.clone());
for note in sticky {
texts_to_embed.push(note.content.clone());
metadata.push(("StickyNote".to_string(), note.content.chars().take(200).collect::<String>()));
+1 -1
View File
@@ -81,7 +81,7 @@ pub async fn start_background_indexer(state: Arc<MemoryState>) {
embedding,
};
state.snippets.modify(|snippets| {
state.code.snippets.modify(|snippets| {
// Prevent duplicates if already indexed
if !snippets.iter().any(|s| s.name == snippet.name) {
snippets.push(snippet.clone());
+7 -7
View File
@@ -103,16 +103,16 @@ async fn ttl_sweeper_worker(state: Arc<MemoryState>) {
.unwrap_or_default()
.as_secs();
state.tasks.modify(|tasks| {
state.project.tasks.modify(|tasks| {
tasks.retain(|t| t.expires_at.is_none_or(|exp| exp > now));
});
state.sticky.modify(|notes| {
state.code.sticky.modify(|notes| {
notes.retain(|n| n.expires_at.is_none_or(|exp| exp > now));
});
state.handoff_memos.modify(|memos| {
state.telemetry.handoff_memos.modify(|memos| {
memos.retain(|m| m.expires_at.is_none_or(|exp| exp > now));
});
state.session_summaries.modify(|summaries| {
state.telemetry.session_summaries.modify(|summaries| {
summaries.retain(|s| s.expires_at.is_none_or(|exp| exp > now));
});
}
@@ -144,7 +144,7 @@ async fn condense_graph_worker(state: Arc<MemoryState>) {
// Condense sticky notes
let mut condensed_sticky_content = String::new();
state.sticky.modify(|notes| {
state.code.sticky.modify(|notes| {
if notes.len() > threshold {
notes.sort_by_key(|n| n.timestamp);
let to_remove = notes.len() - (threshold / 2);
@@ -174,7 +174,7 @@ async fn condense_graph_worker(state: Arc<MemoryState>) {
// Condense snippets
let mut condensed_snippet_content = String::new();
state.snippets.modify(|snippets| {
state.code.snippets.modify(|snippets| {
if snippets.len() > threshold {
snippets.sort_by_key(|s| s.updated_at);
let to_remove = snippets.len() - (threshold / 2);
@@ -260,7 +260,7 @@ async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::E
if let Ok((len, _addr)) = socket.recv_from(&mut buf).await
&& let Ok(payload) = serde_json::from_slice::<crate::models::TerminalHistory>(&buf[..len])
{
udp_state.handler.state.terminal_history.modify(|history| {
udp_state.handler.state.telemetry.terminal_history.modify(|history| {
history.push_front(payload.clone());
if history.len() > 100 {
history.pop_back();
+4 -4
View File
@@ -101,7 +101,7 @@ impl McpResource for TasksActiveResource {
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
let state_clone = Arc::clone(&state);
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
let tasks = state_clone.tasks.cache.read().unwrap();
let tasks = state_clone.project.tasks.cache.read().unwrap();
let data: Vec<_> = tasks
.iter()
.filter(|t| t.status != "completed" && t.status != "done")
@@ -190,7 +190,7 @@ impl MemoryHandler {
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
let state_clone = Arc::clone(&state);
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
let items = state_clone.terminal_history.cache.read().unwrap();
let items = state_clone.telemetry.terminal_history.cache.read().unwrap();
Ok(serde_json::to_string_pretty(&*items)?)
})
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
@@ -211,7 +211,7 @@ impl MemoryHandler {
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
let state_clone = Arc::clone(&state);
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
let items = state_clone.pinned_files.cache.read().unwrap();
let items = state_clone.project.pinned_files.cache.read().unwrap();
Ok(serde_json::to_string_pretty(&*items)?)
})
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
@@ -233,7 +233,7 @@ impl MemoryHandler {
async fn read(&self, state: Arc<MemoryState>) -> crate::error::Result<String> {
let state_clone = Arc::clone(&state);
tokio::task::spawn_blocking(move || -> crate::error::Result<String> {
let items = state_clone.milestones.cache.read().unwrap();
let items = state_clone.project.milestones.cache.read().unwrap();
Ok(serde_json::to_string_pretty(&*items)?)
})
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?
+74 -46
View File
@@ -13,32 +13,50 @@ pub struct GenericEvent {
pub payload: serde_json::Value,
}
pub struct ProjectStores {
pub tasks: Store<Vec<Task>>,
pub milestones: Store<Vec<Milestone>>,
pub pr_checklists: Store<Vec<PrChecklistItem>>,
pub context_workspaces: Store<Vec<ContextWorkspace>>,
pub pinned_files: Store<Vec<PinnedFile>>,
}
pub struct CodeStores {
pub ledger: Store<Vec<CodeChange>>,
pub snippets: Store<Vec<Snippet>>,
pub adrs: Store<Vec<Adr>>,
pub error_fixes: Store<Vec<ErrorFix>>,
pub tech_debts: Store<Vec<TechDebt>>,
pub sticky: Store<Vec<StickyNote>>,
}
pub struct EnvironmentStores {
pub env_fingerprints: Store<HashMap<String, EnvFingerprint>>,
pub env_requirements: Store<Vec<EnvRequirement>>,
pub environments: Store<Vec<EnvironmentDetail>>,
pub gates: Store<Vec<GateRecord>>,
pub prefs: Store<HashMap<String, Preference>>,
}
pub struct TelemetryStores {
pub session_summaries: Store<Vec<SessionSummary>>,
pub handoff_memos: Store<Vec<HandoffMemo>>,
pub recent_activities: Store<std::collections::VecDeque<serde_json::Value>>,
pub terminal_history: Store<std::collections::VecDeque<TerminalHistory>>,
}
pub struct MemoryState {
pub base_dir: PathBuf,
pub clipboard_watch_mode: tokio::sync::RwLock<bool>,
pub graph: Store<KnowledgeGraph>,
pub search_index: RwLock<MemoryIndex>,
pub vector_db: tokio::sync::RwLock<Option<VectorDB>>,
pub ledger: Store<Vec<CodeChange>>,
pub sticky: Store<Vec<StickyNote>>,
pub tasks: Store<Vec<Task>>,
pub snippets: Store<Vec<Snippet>>,
pub adrs: Store<Vec<Adr>>,
pub prefs: Store<HashMap<String, Preference>>,
pub error_fixes: Store<Vec<ErrorFix>>,
pub pinned_files: Store<Vec<PinnedFile>>,
pub session_summaries: Store<Vec<SessionSummary>>,
pub handoff_memos: Store<Vec<HandoffMemo>>,
pub env_fingerprints: Store<HashMap<String, EnvFingerprint>>,
pub env_requirements: Store<Vec<EnvRequirement>>,
pub milestones: Store<Vec<Milestone>>,
pub environments: Store<Vec<EnvironmentDetail>>,
pub pr_checklists: Store<Vec<PrChecklistItem>>,
pub tech_debts: Store<Vec<TechDebt>>,
pub gates: Store<Vec<GateRecord>>,
pub context_workspaces: Store<Vec<ContextWorkspace>>,
pub recent_activities: Store<std::collections::VecDeque<serde_json::Value>>,
pub terminal_history: Store<std::collections::VecDeque<TerminalHistory>>,
pub project: ProjectStores,
pub code: CodeStores,
pub env: EnvironmentStores,
pub telemetry: TelemetryStores,
pub activity_tx: tokio::sync::broadcast::Sender<String>,
pub event_bus_tx: tokio::sync::broadcast::Sender<GenericEvent>,
}
@@ -66,26 +84,36 @@ impl MemoryState {
}
}),
vector_db: tokio::sync::RwLock::new(None),
ledger: Store::new("audit_ledger", db.clone()),
sticky: Store::new("sticky_notes", db.clone()),
tasks: Store::new("tasks", db.clone()),
snippets: Store::new("snippets", db.clone()),
adrs: Store::new("adrs", db.clone()),
prefs: Store::new("preferences", db.clone()),
error_fixes: Store::new("error_fixes", db.clone()),
pinned_files: Store::new("pinned_files", db.clone()),
session_summaries: Store::new("session_summaries", db.clone()),
handoff_memos: Store::new("handoff_memos", db.clone()),
env_fingerprints: Store::new("env_fingerprints", db.clone()),
env_requirements: Store::new("env_requirements", db.clone()),
milestones: Store::new("milestones", db.clone()),
environments: Store::new("environments", db.clone()),
pr_checklists: Store::new("pr_checklists", db.clone()),
tech_debts: Store::new("tech_debts", db.clone()),
gates: Store::new("gates", db.clone()),
context_workspaces: Store::new("context_workspaces", db.clone()),
recent_activities: Store::new("recent_activities", db.clone()),
terminal_history: Store::new("terminal_history", db.clone()),
project: ProjectStores {
tasks: Store::new("tasks", db.clone()),
milestones: Store::new("milestones", db.clone()),
pr_checklists: Store::new("pr_checklists", db.clone()),
context_workspaces: Store::new("context_workspaces", db.clone()),
pinned_files: Store::new("pinned_files", db.clone()),
},
code: CodeStores {
ledger: Store::new("audit_ledger", db.clone()),
snippets: Store::new("snippets", db.clone()),
adrs: Store::new("adrs", db.clone()),
error_fixes: Store::new("error_fixes", db.clone()),
tech_debts: Store::new("tech_debts", db.clone()),
sticky: Store::new("sticky_notes", db.clone()),
},
env: EnvironmentStores {
env_fingerprints: Store::new("env_fingerprints", db.clone()),
env_requirements: Store::new("env_requirements", db.clone()),
environments: Store::new("environments", db.clone()),
gates: Store::new("gates", db.clone()),
prefs: Store::new("preferences", db.clone()),
},
telemetry: TelemetryStores {
session_summaries: Store::new("session_summaries", db.clone()),
handoff_memos: Store::new("handoff_memos", db.clone()),
recent_activities: Store::new("recent_activities", db.clone()),
terminal_history: Store::new("terminal_history", db.clone()),
},
activity_tx: tokio::sync::broadcast::channel(100).0,
event_bus_tx: tokio::sync::broadcast::channel(1000).0,
}
@@ -102,7 +130,7 @@ impl MemoryState {
"message": message
});
self.recent_activities.modify(|activities| {
self.telemetry.recent_activities.modify(|activities| {
activities.push_back(item.clone());
if activities.len() > 100 {
activities.pop_front();
@@ -143,9 +171,9 @@ impl MemoryState {
let entities: Vec<_> = self
.graph
.read_with(|g| g.entities.values().cloned().collect());
let tasks = self.tasks.read_with(|t| t.clone());
let snippets = self.snippets.read_with(|s| s.clone());
let adrs = self.adrs.read_with(|a| a.clone());
let tasks = self.project.tasks.read_with(|t| t.clone());
let snippets = self.code.snippets.read_with(|s| s.clone());
let adrs = self.code.adrs.read_with(|a| a.clone());
tracing::info!(
"rebuild_index: found {} entities, {} tasks",
@@ -195,7 +223,7 @@ mod tests {
assert_eq!(state.base_dir, dir.path());
// Write a test value
state.tasks.modify(|tasks| {
state.project.tasks.modify(|tasks| {
tasks.push(Task {
id: "123".to_string(),
title: "Test Task".to_string(),
@@ -212,7 +240,7 @@ mod tests {
});
// Ensure it is saved
state.tasks.read_with(|tasks| {
state.project.tasks.read_with(|tasks| {
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].id, "123");
});