feat(embedding,vision): candle embeddings with offline fallback, on-demand clipboard vision capture, and concurrency audit
This commit is contained in:
1 parent
5bd8b1587a
commit
e4a0fe72df
47 files changed
+6292
-3503
No files matched your search
+250
-57
@@ -49,6 +49,11 @@ impl McpTool for LogDecisionHandler {
|
||||
status: "accepted".to_string(),
|
||||
supersedes: req.supersedes,
|
||||
timestamp: crate::handlers::utils::now_secs(),
|
||||
namespace: req.namespace.unwrap_or_else(|| crate::models::default_namespace()),
|
||||
repo_name: req.repo_name,
|
||||
alternatives_considered: req.alternatives_considered.unwrap_or_default(),
|
||||
affected_components: req.affected_components.unwrap_or_default(),
|
||||
author: req.author,
|
||||
};
|
||||
|
||||
adrs_to_index.push(a.clone());
|
||||
@@ -201,7 +206,11 @@ impl McpTool for LogErrorFixHandler {
|
||||
git_commit: req.git_commit,
|
||||
git_branch: req.git_branch,
|
||||
embedding,
|
||||
})
|
||||
..Default::default()
|
||||
});
|
||||
if fixes.len() > 300 {
|
||||
fixes.remove(0);
|
||||
}
|
||||
});
|
||||
state.record_activity("error_fix", &format!("Fixed error: {}", req.signature), Some(&solution));
|
||||
Ok(format!("Logged error fix for {}: {}", req.signature, solution))
|
||||
@@ -238,8 +247,13 @@ impl McpTool for SearchErrorFixesHandler {
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let mut score = 0.0;
|
||||
if st.contains(&f.signature) || f.signature.contains(st) {
|
||||
let st_lower = st.to_lowercase();
|
||||
let sig_lower = f.signature.to_lowercase();
|
||||
let sol_lower = f.solution.to_lowercase();
|
||||
if st_lower.contains(&sig_lower) || sig_lower.contains(&st_lower) {
|
||||
score += 0.8;
|
||||
} else if st_lower.contains(&sol_lower) || sol_lower.contains(&st_lower) {
|
||||
score += 0.5;
|
||||
}
|
||||
if let Some(emb) = &f.embedding {
|
||||
if !query_emb.is_empty() {
|
||||
@@ -343,16 +357,65 @@ impl McpTool for LogCodeChangeHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
let change_kind = match req.change_kind.as_deref().map(|s| s.to_lowercase()).as_deref() {
|
||||
Some("added") | Some("create") | Some("created") | Some("new") => crate::models::ChangeKind::Added,
|
||||
Some("deleted") | Some("remove") | Some("removed") => crate::models::ChangeKind::Deleted,
|
||||
Some("renamed") | Some("move") | Some("moved") => crate::models::ChangeKind::Renamed,
|
||||
_ => crate::models::ChangeKind::Modified,
|
||||
};
|
||||
|
||||
let namespace = req
|
||||
.namespace
|
||||
.filter(|ns| !ns.trim().is_empty())
|
||||
.or_else(|| req.repo_name.clone().filter(|rn| !rn.trim().is_empty()))
|
||||
.unwrap_or_else(crate::models::default_namespace);
|
||||
|
||||
let symbols = req.symbol_references.clone().unwrap_or_default();
|
||||
let line_range = req.line_range.clone();
|
||||
|
||||
let effective_rev = req.revision.clone().or_else(|| req.git_commit.clone());
|
||||
let effective_branch = req.branch.clone().or_else(|| req.git_branch.clone());
|
||||
let detected_vcs = if let Some(vcs) = req.vcs_type.clone() {
|
||||
Some(vcs)
|
||||
} else if let Some(ref rev) = effective_rev {
|
||||
if rev.starts_with('r') && rev[1..].chars().all(|c| c.is_ascii_digit()) {
|
||||
Some("svn".to_string())
|
||||
} else if req.repo_url.as_deref().map(|u| u.contains("/svn/")).unwrap_or(false) {
|
||||
Some("svn".to_string())
|
||||
} else {
|
||||
Some("git".to_string())
|
||||
}
|
||||
} else if let Some(ref br) = effective_branch {
|
||||
if br.eq_ignore_ascii_case("trunk") || br.starts_with("branches/") || br.starts_with("tags/") {
|
||||
Some("svn".to_string())
|
||||
} else {
|
||||
Some("git".to_string())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
state.code.ledger.modify(|ledger| {
|
||||
ledger.push(CodeChange {
|
||||
timestamp: crate::handlers::utils::now_secs(),
|
||||
file_path: req.file_path.clone(),
|
||||
description: description.clone(),
|
||||
git_commit: req.git_commit,
|
||||
git_branch: req.git_branch,
|
||||
git_commit: effective_rev.clone(),
|
||||
git_branch: effective_branch.clone(),
|
||||
repo_name: req.repo_name,
|
||||
repo_url: req.repo_url,
|
||||
namespace: namespace.clone(),
|
||||
change_kind,
|
||||
symbols,
|
||||
line_range,
|
||||
author: req.author,
|
||||
session_id: req.session_id,
|
||||
vcs_type: detected_vcs,
|
||||
revision: effective_rev,
|
||||
branch: effective_branch,
|
||||
repository_root: req.repository_root,
|
||||
});
|
||||
if ledger.len() > 1000 {
|
||||
if ledger.len() > 500 {
|
||||
ledger.remove(0);
|
||||
}
|
||||
});
|
||||
@@ -376,15 +439,44 @@ impl McpTool for QueryRecentChangesHandler {
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let data = state
|
||||
.code
|
||||
.ledger
|
||||
.read_with(|l| Ok::<String, crate::error::AppError>(serde_json::to_string(l)?))?;
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: QueryRecentChangesTool = serde_json::from_value(args).unwrap_or(QueryRecentChangesTool {
|
||||
namespace: None,
|
||||
repo_name: None,
|
||||
vcs_type: None,
|
||||
limit: None,
|
||||
});
|
||||
let limit = req.limit.unwrap_or(50);
|
||||
let data = state.code.ledger.read_with(|l| {
|
||||
let filtered: Vec<_> = l
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|c| {
|
||||
if let Some(ns) = &req.namespace {
|
||||
if !c.namespace.eq_ignore_ascii_case(ns) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(repo) = &req.repo_name {
|
||||
if c.repo_name.as_ref().map(|rn| !rn.eq_ignore_ascii_case(repo)).unwrap_or(true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(vcs) = &req.vcs_type {
|
||||
if !c.effective_vcs().eq_ignore_ascii_case(vcs) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
.take(limit)
|
||||
.cloned()
|
||||
.collect();
|
||||
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?)
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LearnPreferenceHandler;
|
||||
|
||||
#[async_trait]
|
||||
@@ -409,6 +501,7 @@ impl McpTool for LearnPreferenceHandler {
|
||||
key: req.key.clone(),
|
||||
value: req.value,
|
||||
updated_at: crate::handlers::utils::now_secs(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -474,6 +567,12 @@ impl McpTool for LogTechDebtHandler {
|
||||
git_commit: req.git_commit,
|
||||
git_branch: req.git_branch,
|
||||
embedding,
|
||||
repo_name: req.repo_name,
|
||||
severity: req.severity,
|
||||
file_path: req.file_path,
|
||||
line_range: req.line_range,
|
||||
workaround: req.workaround,
|
||||
effort_estimate: req.effort_estimate,
|
||||
})
|
||||
});
|
||||
Ok("Tech debt logged".to_string())
|
||||
@@ -605,7 +704,11 @@ impl McpTool for DecisionsHandler {
|
||||
"context": context,
|
||||
"decision": decision,
|
||||
"consequences": consequences,
|
||||
"namespace": ns
|
||||
"namespace": ns,
|
||||
"repo_name": req.repo_name,
|
||||
"author": req.author,
|
||||
"affected_components": req.affected_components,
|
||||
"alternatives_considered": req.alternatives_considered,
|
||||
}),
|
||||
state,
|
||||
).await
|
||||
@@ -655,7 +758,12 @@ impl McpTool for TechDebtHandler {
|
||||
"git_branch": req.git_branch,
|
||||
"symbol_references": req.symbol_references,
|
||||
"line_range": req.line_range,
|
||||
"namespace": ns
|
||||
"namespace": ns,
|
||||
"repo_name": req.repo_name,
|
||||
"severity": req.severity,
|
||||
"file_path": req.file_path,
|
||||
"workaround": req.workaround,
|
||||
"effort_estimate": req.effort_estimate,
|
||||
}),
|
||||
state,
|
||||
).await
|
||||
@@ -699,23 +807,12 @@ impl McpTool for OmniSearchHandler {
|
||||
.search(&req.query, req.namespace.as_deref())
|
||||
.unwrap_or_default();
|
||||
|
||||
let q = req.query.clone();
|
||||
let query_emb = crate::embedding::generate_embedding_async(q.clone())
|
||||
let vector_matches = state
|
||||
.search()
|
||||
.semantic_search(&req.query, req.namespace.as_deref(), limit * 2)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let vector_matches = if !query_emb.is_empty() {
|
||||
if let Some(vdb) = &*state.vector_db.read().await {
|
||||
vdb.search(query_emb.clone(), (limit * 2) as u64)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Reciprocal Rank Fusion (RRF) algorithm
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
@@ -754,8 +851,8 @@ impl McpTool for OmniSearchHandler {
|
||||
let item = MatchItem {
|
||||
id: v_match.id.clone(),
|
||||
doc_type: v_match.doc_type,
|
||||
title: v_match.id,
|
||||
body: v_match.text,
|
||||
title: v_match.title,
|
||||
body: v_match.body,
|
||||
};
|
||||
rrf_scores.insert(item_id, (score, item));
|
||||
}
|
||||
@@ -770,29 +867,38 @@ impl McpTool for OmniSearchHandler {
|
||||
let kg_json = state.read_graph(|full| {
|
||||
let mut kg_results = serde_json::Map::new();
|
||||
let mut count = 0;
|
||||
|
||||
// Build pre-indexed adjacency map: O(R) once instead of O(E * R)
|
||||
let mut adj_map: std::collections::HashMap<&str, Vec<(&str, &str, &str)>> =
|
||||
std::collections::HashMap::new();
|
||||
for rel in &full.relations {
|
||||
adj_map
|
||||
.entry(rel.from.as_str())
|
||||
.or_default()
|
||||
.push((rel.to.as_str(), rel.relation_type.as_str(), "outgoing"));
|
||||
adj_map
|
||||
.entry(rel.to.as_str())
|
||||
.or_default()
|
||||
.push((rel.from.as_str(), rel.relation_type.as_str(), "incoming"));
|
||||
}
|
||||
|
||||
for res in &matches {
|
||||
if res.doc_type == "entity"
|
||||
&& let Some(e) = full.entities.get(&res.id)
|
||||
{
|
||||
if count >= limit {
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
|
||||
// 1-hop relation expansion for GraphRAG
|
||||
// 1-hop relation expansion for GraphRAG via pre-indexed adjacency
|
||||
let mut connected_rels = Vec::new();
|
||||
for rel in &full.relations {
|
||||
if rel.from == res.id {
|
||||
if let Some(rels) = adj_map.get(res.id.as_str()) {
|
||||
for (target, rel_type, direction) in rels {
|
||||
connected_rels.push(serde_json::json!({
|
||||
"target": rel.to,
|
||||
"relation": rel.relation_type,
|
||||
"direction": "outgoing"
|
||||
}));
|
||||
} else if rel.to == res.id {
|
||||
connected_rels.push(serde_json::json!({
|
||||
"target": rel.from,
|
||||
"relation": rel.relation_type,
|
||||
"direction": "incoming"
|
||||
"target": target,
|
||||
"relation": rel_type,
|
||||
"direction": direction
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -851,6 +957,11 @@ impl McpTool for OmniSearchHandler {
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let q = req.query.clone();
|
||||
let query_emb = crate::embedding::generate_embedding_async(req.query.clone())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let snippets_json = state.code.snippets.read_with(|all_snippets| {
|
||||
let mut scored: Vec<_> = all_snippets
|
||||
.iter()
|
||||
@@ -907,7 +1018,6 @@ impl McpTool for OmniSearchHandler {
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let q = req.query;
|
||||
let tech_debts_json = state.code.tech_debts.read_with(|debts| {
|
||||
let mut scored: Vec<_> = debts
|
||||
.iter()
|
||||
@@ -995,7 +1105,7 @@ impl McpTool for OmniSearchHandler {
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let report = serde_json::json!({
|
||||
let mut report = serde_json::json!({
|
||||
"knowledge_graph": kg_json,
|
||||
"tasks": tasks_json,
|
||||
"snippets": snippets_json,
|
||||
@@ -1004,15 +1114,52 @@ impl McpTool for OmniSearchHandler {
|
||||
"handoff_memos": memos_json,
|
||||
"error_fixes": error_fixes_json
|
||||
});
|
||||
let mut out_str = report.to_string();
|
||||
if let Some(max_tok) = req.max_tokens {
|
||||
let max_chars = max_tok * 4;
|
||||
let mut out_str = report.to_string();
|
||||
if out_str.len() > max_chars {
|
||||
out_str.truncate(max_chars);
|
||||
out_str.push_str("... [TRUNCATED_TO_MAX_TOKENS]");
|
||||
let prune_keys = [
|
||||
"error_fixes",
|
||||
"tech_debts",
|
||||
"snippets",
|
||||
"adrs",
|
||||
"handoff_memos",
|
||||
"knowledge_graph",
|
||||
"tasks",
|
||||
];
|
||||
let mut pruned = false;
|
||||
for key in prune_keys {
|
||||
while out_str.len() > max_chars {
|
||||
let popped = if let Some(arr) = report.get_mut(key).and_then(|v| v.as_array_mut()) {
|
||||
if arr.len() > 1 {
|
||||
arr.pop();
|
||||
pruned = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if popped {
|
||||
out_str = report.to_string();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if out_str.len() <= max_chars {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if pruned && let Some(obj) = report.as_object_mut() {
|
||||
obj.insert(
|
||||
"_truncated_to_max_tokens".to_string(),
|
||||
serde_json::Value::Bool(true),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out_str)
|
||||
Ok(report.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1101,8 +1248,13 @@ impl McpTool for SuggestErrorFixHandler {
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let mut score = 0.0;
|
||||
if req.stack_trace.contains(&f.signature) || f.signature.contains(&req.stack_trace) {
|
||||
let st_lower = req.stack_trace.to_lowercase();
|
||||
let sig_lower = f.signature.to_lowercase();
|
||||
let sol_lower = f.solution.to_lowercase();
|
||||
if st_lower.contains(&sig_lower) || sig_lower.contains(&st_lower) {
|
||||
score += 0.8;
|
||||
} else if st_lower.contains(&sol_lower) || sol_lower.contains(&st_lower) {
|
||||
score += 0.5;
|
||||
}
|
||||
if let Some(emb) = &f.embedding {
|
||||
if !query_emb.is_empty() {
|
||||
@@ -1178,6 +1330,7 @@ impl McpTool for ManageCheckpointHandler {
|
||||
timestamp: crate::handlers::utils::now_secs(),
|
||||
description: desc.clone(),
|
||||
namespace: ns,
|
||||
..Default::default()
|
||||
};
|
||||
state.project.snapshots.modify(|snaps| snaps.push(snapshot));
|
||||
}
|
||||
@@ -1381,22 +1534,31 @@ impl McpTool for QueryLineageHandler {
|
||||
"decision": adr.decision
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let changes = state.code.ledger.read_with(|c| c.clone());
|
||||
for change in changes {
|
||||
let file_match = change.file_path.to_lowercase().contains(&q);
|
||||
let desc_match = change.description.to_lowercase().contains(&q);
|
||||
if file_match || desc_match {
|
||||
let repo_match = change.repo_name.as_ref().map(|r| r.to_lowercase().contains(&q)).unwrap_or(false);
|
||||
let symbol_match = change.symbols.iter().any(|s| s.to_lowercase().contains(&q));
|
||||
let ns_match = change.namespace.to_lowercase().contains(&q);
|
||||
if file_match || desc_match || repo_match || symbol_match || ns_match {
|
||||
timeline.push(serde_json::json!({
|
||||
"timestamp": change.timestamp,
|
||||
"type": "CodeChange",
|
||||
"file": change.file_path,
|
||||
"description": change.description,
|
||||
"commit": change.git_commit
|
||||
"commit": change.git_commit,
|
||||
"branch": change.git_branch,
|
||||
"repo_name": change.repo_name,
|
||||
"namespace": change.namespace,
|
||||
"change_kind": change.change_kind,
|
||||
"symbols": change.symbols,
|
||||
"line_range": change.line_range,
|
||||
"author": change.author,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fixes = state.code.error_fixes.read_with(|f| f.clone());
|
||||
for fix in fixes {
|
||||
@@ -1509,6 +1671,7 @@ impl McpTool for LogHypothesisHandler {
|
||||
status: req.status.unwrap_or_else(|| "unverified".to_string()),
|
||||
evidence: req.evidence,
|
||||
timestamp,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
state.code.hypotheses.modify(|h| h.push(record));
|
||||
@@ -1576,10 +1739,16 @@ impl McpTool for GetPreflightContextHandler {
|
||||
let pinned = state.project.pinned_files.read_with(|p| p.clone());
|
||||
let tech_debts = state.code.tech_debts.read_with(|d| d.clone());
|
||||
let hypotheses = state.code.hypotheses.read_with(|h| h.clone());
|
||||
let recent_commands = state.telemetry.terminal_history.read_with(|h| {
|
||||
h.iter().take(5).cloned().collect::<Vec<_>>()
|
||||
});
|
||||
let recent_activities = state.telemetry.recent_activities.read_with(|a| {
|
||||
a.iter().take(5).cloned().collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let active_tasks: Vec<_> = tasks
|
||||
.into_iter()
|
||||
.filter(|t| t.status == "in_progress" || t.status == "open")
|
||||
.filter(|t| !matches!(t.status.as_str(), "completed" | "done" | "cancelled"))
|
||||
.map(|t| serde_json::json!({
|
||||
"id": t.id,
|
||||
"title": t.title,
|
||||
@@ -1590,7 +1759,12 @@ impl McpTool for GetPreflightContextHandler {
|
||||
|
||||
let branch_pinned: Vec<_> = pinned
|
||||
.into_iter()
|
||||
.filter(|p| p.namespace == req.namespace && (req.git_branch.is_none() || p.git_branch == req.git_branch))
|
||||
.filter(|p| {
|
||||
p.namespace == req.namespace
|
||||
&& (req.git_branch.is_none()
|
||||
|| p.git_branch.is_none()
|
||||
|| p.git_branch == req.git_branch)
|
||||
})
|
||||
.map(|p| p.file_path)
|
||||
.collect();
|
||||
|
||||
@@ -1617,7 +1791,9 @@ impl McpTool for GetPreflightContextHandler {
|
||||
"active_tasks": active_tasks,
|
||||
"pinned_files": branch_pinned,
|
||||
"top_open_tech_debts": open_tech_debts,
|
||||
"active_hypotheses": active_hypotheses
|
||||
"active_hypotheses": active_hypotheses,
|
||||
"recent_terminal_commands": recent_commands,
|
||||
"recent_activities": recent_activities
|
||||
});
|
||||
|
||||
Ok(serde_json::to_string_pretty(&preflight)?)
|
||||
@@ -1652,6 +1828,7 @@ impl McpTool for BroadcastAgentSignalHandler {
|
||||
payload: req.payload,
|
||||
timestamp,
|
||||
ttl_seconds: req.ttl_seconds,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
state.telemetry.agent_signals.modify(|s| {
|
||||
@@ -1747,7 +1924,11 @@ impl McpTool for AutoSessionCheckpointHandler {
|
||||
let hypotheses = state.code.hypotheses.read_with(|h| h.clone());
|
||||
let ledger = state.code.ledger.read_with(|l| l.clone());
|
||||
|
||||
let active_tasks: Vec<_> = tasks.iter().filter(|t| t.status == "in_progress" || t.status == "open").map(|t| t.title.as_str()).collect();
|
||||
let active_tasks: Vec<_> = tasks
|
||||
.iter()
|
||||
.filter(|t| !matches!(t.status.as_str(), "completed" | "done" | "cancelled"))
|
||||
.map(|t| t.title.as_str())
|
||||
.collect();
|
||||
let unverified_hyp: Vec<_> = hypotheses.iter().filter(|h| h.status == "unverified").map(|h| h.hypothesis.as_str()).collect();
|
||||
let recent_changes: Vec<_> = ledger.iter().rev().take(5).map(|c| c.file_path.as_str()).collect();
|
||||
|
||||
@@ -1765,6 +1946,7 @@ impl McpTool for AutoSessionCheckpointHandler {
|
||||
expires_at: None,
|
||||
namespace: req.namespace,
|
||||
timestamp,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
state.telemetry.handoff_memos.modify(|m| {
|
||||
@@ -2036,6 +2218,7 @@ mod tests {
|
||||
expires_at: None,
|
||||
dependencies: vec![],
|
||||
acceptance_criteria: vec![],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
{
|
||||
@@ -2232,6 +2415,7 @@ mod tests {
|
||||
expires_at: None,
|
||||
dependencies: vec![],
|
||||
acceptance_criteria: vec![],
|
||||
..Default::default()
|
||||
};
|
||||
state.project.tasks.modify(|t| t.push(task));
|
||||
|
||||
@@ -2408,6 +2592,7 @@ mod tests {
|
||||
tags: vec!["rust".to_string(), "mcp".to_string()],
|
||||
updated_at: 0,
|
||||
embedding: None,
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2467,6 +2652,7 @@ mod tests {
|
||||
expires_at: None,
|
||||
dependencies: vec![],
|
||||
acceptance_criteria: vec![],
|
||||
..Default::default()
|
||||
};
|
||||
let task2 = crate::models::Task {
|
||||
id: "t-2".to_string(),
|
||||
@@ -2480,6 +2666,7 @@ mod tests {
|
||||
expires_at: None,
|
||||
dependencies: vec!["t-1".to_string()],
|
||||
acceptance_criteria: vec![],
|
||||
..Default::default()
|
||||
};
|
||||
state.project.tasks.modify(|t| {
|
||||
t.push(task1);
|
||||
@@ -2607,6 +2794,7 @@ mod tests {
|
||||
observations: vec!["Obs 1".to_string()],
|
||||
namespace: "global".to_string(),
|
||||
git_branch: None,
|
||||
..Default::default()
|
||||
});
|
||||
g.entities.insert("Ent2".to_string(), crate::models::Entity {
|
||||
name: "Ent2".to_string(),
|
||||
@@ -2614,12 +2802,14 @@ mod tests {
|
||||
observations: vec!["Obs 2".to_string()],
|
||||
namespace: "global".to_string(),
|
||||
git_branch: None,
|
||||
..Default::default()
|
||||
});
|
||||
g.relations.push(crate::models::Relation {
|
||||
from: "Ent1".to_string(),
|
||||
to: "Ent2".to_string(),
|
||||
relation_type: "uses".to_string(),
|
||||
namespace: "global".to_string(),
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2693,6 +2883,7 @@ mod tests {
|
||||
timestamp: 0,
|
||||
description: "Test snap".to_string(),
|
||||
namespace: "global".to_string(),
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
let rest_snap = mg_chk
|
||||
@@ -2737,6 +2928,7 @@ mod tests {
|
||||
dependencies: vec!["NON-EXISTENT-TASK".to_string()],
|
||||
description: "Blocked task desc".to_string(),
|
||||
expires_at: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
||||
@@ -2756,6 +2948,7 @@ mod tests {
|
||||
timestamp: 0,
|
||||
namespace: "global".to_string(),
|
||||
git_branch: Some("main".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user