feat(embedding,vision): candle embeddings with offline fallback, on-demand clipboard vision capture, and concurrency audit

This commit is contained in:
Riz Ashraf committed 2026-10-07 06:36:09 +01:00
1 parent 5bd8b1587a
commit e4a0fe72df
47 files changed
+6292 -3503

No files matched your search

+382 -7
View File
@@ -2,24 +2,109 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ChangeKind {
Added,
Modified,
Deleted,
Renamed,
}
impl Default for ChangeKind {
fn default() -> Self {
ChangeKind::Modified
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CodeChange {
pub timestamp: u64,
pub file_path: String,
pub description: String,
#[serde(default)]
pub git_commit: Option<String>,
#[serde(default)]
pub git_branch: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub repo_url: Option<String>,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub change_kind: ChangeKind,
#[serde(default)]
pub symbols: Vec<String>,
#[serde(default)]
pub line_range: Option<String>,
#[serde(default)]
pub author: Option<String>,
#[serde(default)]
pub session_id: Option<String>,
#[serde(default)]
pub vcs_type: Option<String>,
#[serde(default)]
pub revision: Option<String>,
#[serde(default)]
pub branch: Option<String>,
#[serde(default)]
pub repository_root: Option<String>,
}
impl CodeChange {
pub fn effective_vcs(&self) -> &str {
if let Some(vcs) = &self.vcs_type {
return vcs.as_str();
}
if let Some(rev) = self.revision.as_deref().or(self.git_commit.as_deref()) {
if rev.starts_with('r') && rev[1..].chars().all(|c| c.is_ascii_digit()) {
return "svn";
}
}
if let Some(br) = self.branch.as_deref().or(self.git_branch.as_deref()) {
if br.eq_ignore_ascii_case("trunk")
|| br.starts_with("branches/")
|| br.starts_with("tags/")
{
return "svn";
}
}
"git"
}
pub fn effective_revision(&self) -> Option<&str> {
self.revision.as_deref().or(self.git_commit.as_deref())
}
pub fn effective_branch(&self) -> Option<&str> {
self.branch.as_deref().or(self.git_branch.as_deref())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct StickyNote {
#[serde(default = "generate_uuid")]
pub id: String,
pub timestamp: u64,
pub content: String,
#[serde(default)]
pub expires_at: Option<u64>,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub category: Option<String>,
#[serde(default)]
pub author: Option<String>,
}
pub fn generate_uuid() -> String {
uuid::Uuid::new_v4().to_string()
}
pub fn default_namespace() -> String {
"global".to_string()
}
pub fn normalize_entity_type(input: &str) -> String {
let trimmed = input.trim();
if trimmed.is_empty() {
@@ -47,7 +132,9 @@ pub fn normalize_entity_type(input: &str) -> String {
"McpTool" | "Mcptool" | "Mcp" => "McpTool".to_string(),
"Databasetable" | "DatabaseTable" => "DatabaseTable".to_string(),
"Datastructure" | "DataStore" | "Datastore" => "DataStructure".to_string(),
"Architecturecomponent" | "ArchitectureComponent" | "Architecture" => "ArchitectureComponent".to_string(),
"Architecturecomponent" | "ArchitectureComponent" | "Architecture" => {
"ArchitectureComponent".to_string()
}
"Frontendcomponent" | "FrontendComponent" => "FrontendComponent".to_string(),
"Globalrule" | "GlobalRule" => "GlobalRule".to_string(),
"Securitypolicy" | "SecurityPolicy" => "SecurityPolicy".to_string(),
@@ -84,7 +171,10 @@ pub enum TaskStatus {
impl TaskStatus {
pub fn is_active(&self) -> bool {
matches!(self, TaskStatus::Active | TaskStatus::InProgress | TaskStatus::Blocked)
matches!(
self,
TaskStatus::Active | TaskStatus::InProgress | TaskStatus::Blocked
)
}
pub fn is_completed(&self) -> bool {
@@ -107,7 +197,7 @@ impl std::fmt::Display for TaskStatus {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct Entity {
pub name: String,
#[serde(alias = "entityType", alias = "type", alias = "category")]
@@ -118,8 +208,19 @@ pub struct Entity {
pub namespace: String,
#[serde(default)]
pub git_branch: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub file_path: Option<String>,
#[serde(default)]
pub created_at: Option<u64>,
#[serde(default)]
pub updated_at: Option<u64>,
#[serde(default)]
pub embedding: Option<Vec<f32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct Relation {
#[serde(alias = "source", alias = "from_node", alias = "source_node")]
pub from: String,
@@ -129,7 +230,36 @@ pub struct Relation {
pub relation_type: String,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub weight: Option<f32>,
#[serde(default)]
pub is_bidirectional: bool,
}
impl PartialEq for Relation {
fn eq(&self, other: &Self) -> bool {
self.from == other.from
&& self.to == other.to
&& self.relation_type == other.relation_type
&& self.namespace == other.namespace
&& self.repo_name == other.repo_name
}
}
impl Eq for Relation {}
impl std::hash::Hash for Relation {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.from.hash(state);
self.to.hash(state);
self.relation_type.hash(state);
self.namespace.hash(state);
self.repo_name.hash(state);
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KnowledgeGraph {
#[serde(default)]
@@ -137,11 +267,49 @@ pub struct KnowledgeGraph {
#[serde(default)]
pub relations: Vec<Relation>,
}
impl KnowledgeGraph {
/// Builds an adjacency index mapping node names to lists of outgoing/incoming neighbors and relation types.
pub fn build_adjacency_map<'a>(
&'a self,
relation_filter: Option<&'a str>,
) -> HashMap<&'a str, Vec<(&'a str, &'a str, bool)>> {
let mut adj: HashMap<&'a str, Vec<(&'a str, &'a str, bool)>> =
HashMap::with_capacity(self.relations.len() * 2);
for rel in &self.relations {
if let Some(rf) = relation_filter {
if rel.relation_type != rf {
continue;
}
}
adj.entry(rel.from.as_str()).or_default().push((
rel.to.as_str(),
rel.relation_type.as_str(),
false,
));
adj.entry(rel.to.as_str()).or_default().push((
rel.from.as_str(),
rel.relation_type.as_str(),
true,
));
}
adj
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AcceptanceCriteria {
pub id: String,
pub description: String,
pub is_met: bool,
#[serde(default)]
pub verification_command: Option<String>,
#[serde(default)]
pub verification_output: Option<String>,
#[serde(default)]
pub verified_at: Option<u64>,
#[serde(default)]
pub verified_by: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -161,6 +329,16 @@ pub struct Task {
pub dependencies: Vec<String>,
#[serde(default)]
pub acceptance_criteria: Vec<AcceptanceCriteria>,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub priority: Option<String>,
#[serde(default)]
pub assigned_agent: Option<String>,
#[serde(default)]
pub verification_command: Option<String>,
}
impl Task {
@@ -172,6 +350,7 @@ impl Task {
&& !s.eq_ignore_ascii_case("canceled")
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Snippet {
pub name: String,
@@ -183,7 +362,16 @@ pub struct Snippet {
pub tags: Vec<String>,
#[serde(default)]
pub embedding: Option<Vec<f32>>,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub origin_file: Option<String>,
#[serde(default)]
pub line_range: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Adr {
pub id: String,
@@ -196,17 +384,35 @@ pub struct Adr {
#[serde(default)]
pub supersedes: Option<String>,
pub timestamp: u64,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub alternatives_considered: Vec<String>,
#[serde(default)]
pub affected_components: Vec<String>,
#[serde(default)]
pub author: Option<String>,
}
fn default_adr_status() -> String {
"accepted".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Preference {
pub key: String,
pub value: String,
pub updated_at: u64,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub category: Option<String>,
#[serde(default)]
pub rationale: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ErrorFix {
pub signature: String,
@@ -216,14 +422,32 @@ pub struct ErrorFix {
pub git_branch: Option<String>,
#[serde(default)]
pub embedding: Option<Vec<f32>>,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub error_category: Option<String>,
#[serde(default)]
pub stack_trace: Option<String>,
#[serde(default)]
pub toolchain: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PinnedFile {
pub namespace: String,
pub file_path: String,
pub timestamp: u64,
pub git_branch: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub symbol_or_line: Option<String>,
#[serde(default)]
pub pin_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SessionSummary {
pub timestamp: u64,
@@ -231,7 +455,20 @@ pub struct SessionSummary {
pub namespace: String,
#[serde(default)]
pub expires_at: Option<u64>,
#[serde(default)]
pub session_id: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub git_branch: Option<String>,
#[serde(default)]
pub git_commit: Option<String>,
#[serde(default)]
pub tasks_completed: Vec<String>,
#[serde(default)]
pub next_steps: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct HandoffMemo {
pub id: String,
@@ -241,7 +478,20 @@ pub struct HandoffMemo {
pub expires_at: Option<u64>,
pub namespace: String,
pub timestamp: u64,
#[serde(default)]
pub session_id: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub git_branch: Option<String>,
#[serde(default)]
pub vcs_revision: Option<String>,
#[serde(default)]
pub blockers: Vec<String>,
#[serde(default)]
pub action_items: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EnvFingerprint {
pub namespace: String,
@@ -249,14 +499,24 @@ pub struct EnvFingerprint {
pub shell: String,
pub tool_versions: HashMap<String, String>,
pub updated_at: u64,
#[serde(default)]
pub repo_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EnvRequirement {
pub namespace: String,
pub key: String,
pub description: String,
pub is_secret: bool,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub default_value: Option<String>,
#[serde(default)]
pub validation_regex: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Milestone {
pub id: String,
@@ -264,7 +524,18 @@ pub struct Milestone {
pub status: String,
pub namespace: String,
pub target_date: Option<u64>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub task_ids: Vec<String>,
#[serde(default)]
pub deliverables: Vec<String>,
#[serde(default)]
pub completed_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EnvironmentDetail {
pub namespace: String,
@@ -273,13 +544,31 @@ pub struct EnvironmentDetail {
pub description: String,
pub requires_vpn: bool,
pub updated_at: u64,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub env_type: Option<String>,
#[serde(default)]
pub healthcheck_endpoint: Option<String>,
#[serde(default)]
pub ssh_host: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PrChecklistItem {
pub namespace: String,
pub id: String,
pub description: String,
#[serde(default)]
pub is_checked: bool,
#[serde(default)]
pub category: Option<String>,
#[serde(default)]
pub automated_check: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TechDebt {
pub id: String,
@@ -292,7 +581,20 @@ pub struct TechDebt {
pub git_branch: Option<String>,
#[serde(default)]
pub embedding: Option<Vec<f32>>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub severity: Option<String>,
#[serde(default)]
pub file_path: Option<String>,
#[serde(default)]
pub line_range: Option<String>,
#[serde(default)]
pub workaround: Option<String>,
#[serde(default)]
pub effort_estimate: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ContextWorkspace {
pub name: String,
@@ -300,6 +602,14 @@ pub struct ContextWorkspace {
pub pinned_files: Vec<String>,
pub active_task_ids: Vec<String>,
pub saved_at: u64,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub git_branch: Option<String>,
#[serde(default)]
pub vcs_revision: Option<String>,
#[serde(default)]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -312,15 +622,44 @@ pub struct GateRecord {
pub status: String,
pub reason: Option<String>,
pub timestamp: u64,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub gate_type: Option<String>,
#[serde(default)]
pub enforcer: Option<String>,
#[serde(default)]
pub validation_log: Option<String>,
#[serde(default)]
pub expires_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TerminalHistory {
#[serde(default = "generate_uuid")]
pub id: String,
pub command: String,
pub exit_code: i32,
pub cwd: String,
#[serde(default)]
pub os: String,
pub timestamp: u64,
#[serde(default)]
pub error_output: Option<String>,
#[serde(default)]
pub shell: Option<String>,
#[serde(default)]
pub duration_ms: Option<u64>,
#[serde(default)]
pub stdout_summary: Option<String>,
#[serde(default)]
pub status_reason: Option<String>,
#[serde(default)]
pub invoker: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default = "default_namespace")]
pub namespace: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -329,6 +668,16 @@ pub struct ActivityRecord {
pub category: String, // e.g. "code_change", "task_update", "telemetry", "error_fix"
pub summary: String, // Brief human-readable description of what is being worked on
pub details: Option<String>,
#[serde(default)]
pub session_id: Option<String>,
#[serde(default)]
pub actor: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub associated_file: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -337,6 +686,14 @@ pub struct StateSnapshot {
pub timestamp: u64,
pub description: String,
pub namespace: String,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub git_branch: Option<String>,
#[serde(default)]
pub vcs_revision: Option<String>,
#[serde(default)]
pub active_tasks_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -347,6 +704,14 @@ pub struct Hypothesis {
pub status: String,
pub evidence: Option<String>,
pub timestamp: u64,
#[serde(default = "default_namespace")]
pub namespace: String,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub validation_steps: Vec<String>,
#[serde(default)]
pub diagnostician: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -357,6 +722,12 @@ pub struct AgentSignal {
pub payload: String,
pub timestamp: u64,
pub ttl_seconds: Option<u64>,
#[serde(default)]
pub recipient: Option<String>,
#[serde(default)]
pub correlation_id: Option<String>,
#[serde(default)]
pub priority: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -368,13 +739,18 @@ pub struct TaskEvent {
pub error: Option<String>,
pub timestamp: u64,
pub session_id: Option<String>,
#[serde(default)]
pub namespace: Option<String>,
#[serde(default)]
pub repo_name: Option<String>,
#[serde(default)]
pub actor: Option<String>,
}
#[cfg(test)]
mod tests {
use crate::error::AppError;
use axum::http::StatusCode;
use axum::response::IntoResponse;
use crate::error::AppError;
#[test]
fn test_app_error_bad_request_invalid_json() {
@@ -383,4 +759,3 @@ mod tests {
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}
}