@plures/runebook 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ANALYSIS_LADDER.md +231 -0
- package/CHANGELOG.md +124 -0
- package/INTEGRATIONS.md +242 -0
- package/LICENSE +21 -0
- package/MEMORY.md +253 -0
- package/NIXOS.md +357 -0
- package/QUICKSTART.md +157 -0
- package/README.md +295 -0
- package/RELEASE.md +190 -0
- package/ValidationChecklist.md +598 -0
- package/docs/demo.md +338 -0
- package/docs/llm-integration.md +300 -0
- package/docs/parallel-execution-plan.md +160 -0
- package/flake.nix +228 -0
- package/integrations/README.md +242 -0
- package/integrations/demo-steps.sh +64 -0
- package/integrations/nvim-runebook.lua +140 -0
- package/integrations/tmux-status.sh +51 -0
- package/integrations/vim-runebook.vim +77 -0
- package/integrations/wezterm-status-simple.lua +48 -0
- package/integrations/wezterm-status.lua +76 -0
- package/nixos-module.nix +156 -0
- package/package.json +76 -0
- package/packages/design-dojo/index.js +4 -0
- package/packages/design-dojo/package.json +20 -0
- package/packages/design-dojo/tokens.css +69 -0
- package/playwright.config.ts +16 -0
- package/scripts/check-versions.cjs +62 -0
- package/scripts/demo.sh +220 -0
- package/shell.nix +31 -0
- package/src/app.html +13 -0
- package/src/cli/index.ts +1050 -0
- package/src/lib/agent/analysis-pipeline.ts +347 -0
- package/src/lib/agent/analysis-service.ts +171 -0
- package/src/lib/agent/analysis.ts +159 -0
- package/src/lib/agent/analyzers/heuristic.ts +289 -0
- package/src/lib/agent/analyzers/index.ts +7 -0
- package/src/lib/agent/analyzers/llm.ts +204 -0
- package/src/lib/agent/analyzers/local-search.ts +215 -0
- package/src/lib/agent/capture.ts +123 -0
- package/src/lib/agent/index.ts +244 -0
- package/src/lib/agent/integration.ts +81 -0
- package/src/lib/agent/llm/providers/base.ts +99 -0
- package/src/lib/agent/llm/providers/index.ts +60 -0
- package/src/lib/agent/llm/providers/mock.ts +67 -0
- package/src/lib/agent/llm/providers/ollama.ts +151 -0
- package/src/lib/agent/llm/providers/openai.ts +153 -0
- package/src/lib/agent/llm/sanitizer.ts +170 -0
- package/src/lib/agent/llm/types.ts +118 -0
- package/src/lib/agent/memory.ts +363 -0
- package/src/lib/agent/node-status.ts +56 -0
- package/src/lib/agent/node-suggestions.ts +64 -0
- package/src/lib/agent/status.ts +80 -0
- package/src/lib/agent/suggestions.ts +169 -0
- package/src/lib/components/Canvas.svelte +124 -0
- package/src/lib/components/ConnectionLine.svelte +46 -0
- package/src/lib/components/DisplayNode.svelte +167 -0
- package/src/lib/components/InputNode.svelte +158 -0
- package/src/lib/components/TerminalNode.svelte +237 -0
- package/src/lib/components/Toolbar.svelte +359 -0
- package/src/lib/components/TransformNode.svelte +327 -0
- package/src/lib/core/index.ts +31 -0
- package/src/lib/core/observer.ts +278 -0
- package/src/lib/core/redaction.ts +158 -0
- package/src/lib/core/shell-adapters/base.ts +325 -0
- package/src/lib/core/shell-adapters/bash.ts +110 -0
- package/src/lib/core/shell-adapters/index.ts +62 -0
- package/src/lib/core/shell-adapters/zsh.ts +105 -0
- package/src/lib/core/storage.ts +360 -0
- package/src/lib/core/types.ts +176 -0
- package/src/lib/design-dojo/Box.svelte +47 -0
- package/src/lib/design-dojo/Button.svelte +75 -0
- package/src/lib/design-dojo/Input.svelte +65 -0
- package/src/lib/design-dojo/List.svelte +38 -0
- package/src/lib/design-dojo/Select.svelte +48 -0
- package/src/lib/design-dojo/SplitPane.svelte +43 -0
- package/src/lib/design-dojo/StatusBar.svelte +61 -0
- package/src/lib/design-dojo/Table.svelte +47 -0
- package/src/lib/design-dojo/Text.svelte +36 -0
- package/src/lib/design-dojo/Toggle.svelte +48 -0
- package/src/lib/design-dojo/index.ts +10 -0
- package/src/lib/stores/canvas-praxis.ts +268 -0
- package/src/lib/stores/canvas.ts +58 -0
- package/src/lib/types/agent.ts +78 -0
- package/src/lib/types/canvas.ts +71 -0
- package/src/lib/utils/storage.ts +326 -0
- package/src/lib/utils/yaml-loader.ts +52 -0
- package/src/routes/+layout.svelte +5 -0
- package/src/routes/+layout.ts +5 -0
- package/src/routes/+page.svelte +32 -0
- package/src-tauri/Cargo.lock +5735 -0
- package/src-tauri/Cargo.toml +38 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +10 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/agents/agent1.rs +66 -0
- package/src-tauri/src/agents/agent2.rs +80 -0
- package/src-tauri/src/agents/agent3.rs +73 -0
- package/src-tauri/src/agents/agent4.rs +66 -0
- package/src-tauri/src/agents/agent5.rs +68 -0
- package/src-tauri/src/agents/agent6.rs +75 -0
- package/src-tauri/src/agents/base.rs +52 -0
- package/src-tauri/src/agents/mod.rs +17 -0
- package/src-tauri/src/core/coordination.rs +117 -0
- package/src-tauri/src/core/mod.rs +12 -0
- package/src-tauri/src/core/ownership.rs +61 -0
- package/src-tauri/src/core/types.rs +132 -0
- package/src-tauri/src/execution/mod.rs +5 -0
- package/src-tauri/src/execution/runner.rs +143 -0
- package/src-tauri/src/lib.rs +161 -0
- package/src-tauri/src/main.rs +6 -0
- package/src-tauri/src/memory/api.rs +422 -0
- package/src-tauri/src/memory/client.rs +156 -0
- package/src-tauri/src/memory/encryption.rs +79 -0
- package/src-tauri/src/memory/migration.rs +110 -0
- package/src-tauri/src/memory/mod.rs +28 -0
- package/src-tauri/src/memory/schema.rs +275 -0
- package/src-tauri/src/memory/tests.rs +192 -0
- package/src-tauri/src/orchestrator/coordinator.rs +232 -0
- package/src-tauri/src/orchestrator/mod.rs +13 -0
- package/src-tauri/src/orchestrator/planner.rs +304 -0
- package/src-tauri/tauri.conf.json +35 -0
- package/static/examples/date-time-example.yaml +147 -0
- package/static/examples/hello-world.yaml +74 -0
- package/static/examples/transform-example.yaml +157 -0
- package/static/favicon.png +0 -0
- package/static/svelte.svg +1 -0
- package/static/tauri.svg +6 -0
- package/static/vite.svg +1 -0
- package/svelte.config.js +18 -0
- package/tsconfig.json +19 -0
- package/vite.config.js +45 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Migration and versioning mechanism for schema evolution
|
|
2
|
+
|
|
3
|
+
use crate::memory::api::MemoryStore;
|
|
4
|
+
use anyhow::{Context, Result};
|
|
5
|
+
use serde_json::Value;
|
|
6
|
+
|
|
7
|
+
const SCHEMA_VERSION_KEY: &str = "memory:schema:version";
|
|
8
|
+
const CURRENT_SCHEMA_VERSION: u32 = 1;
|
|
9
|
+
|
|
10
|
+
/// Run all pending migrations
|
|
11
|
+
pub async fn run_migrations(store: &MemoryStore) -> Result<()> {
|
|
12
|
+
let current_version = get_current_version(store).await?;
|
|
13
|
+
|
|
14
|
+
if current_version < CURRENT_SCHEMA_VERSION {
|
|
15
|
+
// Run migrations sequentially
|
|
16
|
+
for version in (current_version + 1)..=CURRENT_SCHEMA_VERSION {
|
|
17
|
+
migrate_to_version(store, version)
|
|
18
|
+
.await
|
|
19
|
+
.with_context(|| format!("Failed to migrate to version {}", version))?;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Update version
|
|
23
|
+
set_version(store, CURRENT_SCHEMA_VERSION).await?;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Ok(())
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async fn get_current_version(store: &MemoryStore) -> Result<u32> {
|
|
30
|
+
let client = &store.client;
|
|
31
|
+
|
|
32
|
+
match client.get(SCHEMA_VERSION_KEY).await? {
|
|
33
|
+
Some(value) => {
|
|
34
|
+
if let Some(version) = value.as_u64() {
|
|
35
|
+
Ok(version as u32)
|
|
36
|
+
} else {
|
|
37
|
+
Ok(0)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
None => Ok(0),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async fn set_version(store: &MemoryStore, version: u32) -> Result<()> {
|
|
45
|
+
let client = &store.client;
|
|
46
|
+
let value = serde_json::json!(version);
|
|
47
|
+
client.put(SCHEMA_VERSION_KEY, &value).await?;
|
|
48
|
+
Ok(())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async fn migrate_to_version(store: &MemoryStore, version: u32) -> Result<()> {
|
|
52
|
+
match version {
|
|
53
|
+
1 => {
|
|
54
|
+
// Initial schema version - no migration needed
|
|
55
|
+
// This is where we would migrate from version 0 to 1
|
|
56
|
+
Ok(())
|
|
57
|
+
}
|
|
58
|
+
_ => {
|
|
59
|
+
anyhow::bail!("Unknown migration version: {}", version);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Get migration status
|
|
65
|
+
pub async fn get_migration_status(store: &MemoryStore) -> Result<MigrationStatus> {
|
|
66
|
+
let current_version = get_current_version(store).await?;
|
|
67
|
+
|
|
68
|
+
Ok(MigrationStatus {
|
|
69
|
+
current_version,
|
|
70
|
+
target_version: CURRENT_SCHEMA_VERSION,
|
|
71
|
+
is_up_to_date: current_version >= CURRENT_SCHEMA_VERSION,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[derive(Debug, Clone)]
|
|
76
|
+
pub struct MigrationStatus {
|
|
77
|
+
pub current_version: u32,
|
|
78
|
+
pub target_version: u32,
|
|
79
|
+
pub is_up_to_date: bool,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Future migration examples:
|
|
83
|
+
//
|
|
84
|
+
// async fn migrate_to_version_2(store: &MemoryStore) -> Result<()> {
|
|
85
|
+
// // Example: Add a new field to all sessions
|
|
86
|
+
// let keys = store.client.list("memory:session:").await?;
|
|
87
|
+
// for key in keys {
|
|
88
|
+
// if let Some(mut value) = store.client.get(&key).await? {
|
|
89
|
+
// // Add new field
|
|
90
|
+
// value["new_field"] = serde_json::json!("default_value");
|
|
91
|
+
// store.client.put(&key, &value).await?;
|
|
92
|
+
// }
|
|
93
|
+
// }
|
|
94
|
+
// Ok(())
|
|
95
|
+
// }
|
|
96
|
+
//
|
|
97
|
+
// async fn migrate_to_version_3(store: &MemoryStore) -> Result<()> {
|
|
98
|
+
// // Example: Rename a field across all commands
|
|
99
|
+
// let keys = store.client.list("memory:command:").await?;
|
|
100
|
+
// for key in keys {
|
|
101
|
+
// if let Some(mut value) = store.client.get(&key).await? {
|
|
102
|
+
// if let Some(old_value) = value.get("old_field_name").cloned() {
|
|
103
|
+
// value["new_field_name"] = old_value;
|
|
104
|
+
// value.as_object_mut().unwrap().remove("old_field_name");
|
|
105
|
+
// store.client.put(&key, &value).await?;
|
|
106
|
+
// }
|
|
107
|
+
// }
|
|
108
|
+
// }
|
|
109
|
+
// Ok(())
|
|
110
|
+
// }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// PluresDB cognitive memory storage module
|
|
2
|
+
// Local-first "cognitive memory" for terminal events, commands, outputs, errors, insights, and suggestions
|
|
3
|
+
|
|
4
|
+
pub mod api;
|
|
5
|
+
pub mod client;
|
|
6
|
+
pub mod encryption;
|
|
7
|
+
pub mod migration;
|
|
8
|
+
pub mod schema;
|
|
9
|
+
|
|
10
|
+
#[cfg(test)]
|
|
11
|
+
mod tests;
|
|
12
|
+
|
|
13
|
+
pub use api::MemoryStore;
|
|
14
|
+
pub use client::PluresDBClient;
|
|
15
|
+
pub use schema::*;
|
|
16
|
+
|
|
17
|
+
use anyhow::Result;
|
|
18
|
+
|
|
19
|
+
/// Initialize the memory store with PluresDB connection
|
|
20
|
+
pub async fn init_memory_store(host: &str, port: u16, data_dir: &str) -> Result<MemoryStore> {
|
|
21
|
+
let client = PluresDBClient::new(host, port, data_dir)?;
|
|
22
|
+
let store = MemoryStore::new(client).await?;
|
|
23
|
+
|
|
24
|
+
// Run migrations
|
|
25
|
+
migration::run_migrations(&store).await?;
|
|
26
|
+
|
|
27
|
+
Ok(store)
|
|
28
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Schema definitions for cognitive memory storage
|
|
2
|
+
// Defines tables/collections: sessions, commands, outputs, errors, insights, suggestions, provenance
|
|
3
|
+
|
|
4
|
+
use chrono::{DateTime, Utc};
|
|
5
|
+
use serde::{Deserialize, Serialize};
|
|
6
|
+
use uuid::Uuid;
|
|
7
|
+
|
|
8
|
+
/// Session metadata - represents a terminal session
|
|
9
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
10
|
+
pub struct Session {
|
|
11
|
+
pub id: String,
|
|
12
|
+
pub started_at: DateTime<Utc>,
|
|
13
|
+
pub ended_at: Option<DateTime<Utc>>,
|
|
14
|
+
pub shell_type: String, // bash, zsh, nushell, etc.
|
|
15
|
+
pub initial_cwd: String,
|
|
16
|
+
pub hostname: Option<String>,
|
|
17
|
+
pub user: Option<String>,
|
|
18
|
+
pub metadata: serde_json::Value, // Additional session metadata
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Normalized command record
|
|
22
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
23
|
+
pub struct Command {
|
|
24
|
+
pub id: String,
|
|
25
|
+
pub session_id: String,
|
|
26
|
+
pub command: String, // Normalized command (e.g., "git" not "/usr/bin/git")
|
|
27
|
+
pub args: Vec<String>,
|
|
28
|
+
pub env_summary: serde_json::Value, // Sanitized environment variables
|
|
29
|
+
pub cwd: String,
|
|
30
|
+
pub started_at: DateTime<Utc>,
|
|
31
|
+
pub ended_at: Option<DateTime<Utc>>,
|
|
32
|
+
pub exit_code: Option<i32>,
|
|
33
|
+
pub success: bool,
|
|
34
|
+
pub duration_ms: Option<u64>,
|
|
35
|
+
pub pid: Option<u32>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Output chunk - stdout/stderr output, optionally compressed
|
|
39
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
40
|
+
pub struct Output {
|
|
41
|
+
pub id: String,
|
|
42
|
+
pub command_id: String,
|
|
43
|
+
pub stream_type: String, // "stdout" or "stderr"
|
|
44
|
+
pub chunk_index: u32,
|
|
45
|
+
pub content: Vec<u8>, // Raw bytes (may be compressed)
|
|
46
|
+
pub compressed: bool, // Whether content is gzip-compressed
|
|
47
|
+
pub size_bytes: u64, // Uncompressed size
|
|
48
|
+
pub timestamp: DateTime<Utc>,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Classified error record
|
|
52
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
53
|
+
pub struct Error {
|
|
54
|
+
pub id: String,
|
|
55
|
+
pub command_id: String,
|
|
56
|
+
pub session_id: String,
|
|
57
|
+
pub error_type: String, // "exit_code", "stderr", "timeout", "permission", etc.
|
|
58
|
+
pub severity: String, // "low", "medium", "high", "critical"
|
|
59
|
+
pub message: String,
|
|
60
|
+
pub stderr_snippet: Option<String>, // First 500 chars of stderr
|
|
61
|
+
pub exit_code: Option<i32>,
|
|
62
|
+
pub timestamp: DateTime<Utc>,
|
|
63
|
+
pub context: serde_json::Value, // Additional error context
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// AI/heuristic annotation/insight
|
|
67
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
68
|
+
pub struct Insight {
|
|
69
|
+
pub id: String,
|
|
70
|
+
pub command_id: Option<String>, // Optional: linked to specific command
|
|
71
|
+
pub session_id: Option<String>, // Optional: linked to specific session
|
|
72
|
+
pub insight_type: String, // "pattern", "optimization", "warning", "tip", "correlation"
|
|
73
|
+
pub title: String,
|
|
74
|
+
pub description: String,
|
|
75
|
+
pub confidence: f64, // 0.0 to 1.0
|
|
76
|
+
pub source: String, // "heuristic", "ai", "rule", etc.
|
|
77
|
+
pub generated_at: DateTime<Utc>,
|
|
78
|
+
pub metadata: serde_json::Value,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Ranked suggestion
|
|
82
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
83
|
+
pub struct Suggestion {
|
|
84
|
+
pub id: String,
|
|
85
|
+
pub suggestion_type: String, // "command", "optimization", "shortcut", "warning", "tip"
|
|
86
|
+
pub priority: String, // "low", "medium", "high"
|
|
87
|
+
pub rank: f64, // Ranking score (higher = more relevant)
|
|
88
|
+
pub title: String,
|
|
89
|
+
pub description: String,
|
|
90
|
+
pub command: Option<String>, // Suggested command
|
|
91
|
+
pub args: Option<Vec<String>>, // Suggested arguments
|
|
92
|
+
pub context: serde_json::Value,
|
|
93
|
+
pub created_at: DateTime<Utc>,
|
|
94
|
+
pub dismissed: bool,
|
|
95
|
+
pub applied: bool,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Provenance information - tracks source, confidence, model/tool used
|
|
99
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
100
|
+
pub struct Provenance {
|
|
101
|
+
pub id: String,
|
|
102
|
+
pub entity_type: String, // "command", "output", "error", "insight", "suggestion"
|
|
103
|
+
pub entity_id: String,
|
|
104
|
+
pub source: String, // "terminal", "ai", "heuristic", "user", etc.
|
|
105
|
+
pub confidence: Option<f64>, // 0.0 to 1.0
|
|
106
|
+
pub model: Option<String>, // AI model name if applicable
|
|
107
|
+
pub tool: Option<String>, // Tool/function name if applicable
|
|
108
|
+
pub created_at: DateTime<Utc>,
|
|
109
|
+
pub metadata: serde_json::Value,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Event wrapper for append_event API
|
|
113
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
114
|
+
pub struct MemoryEvent {
|
|
115
|
+
pub id: String,
|
|
116
|
+
pub event_type: String, // "command", "output", "error", "session_start", "session_end"
|
|
117
|
+
pub timestamp: DateTime<Utc>,
|
|
118
|
+
pub session_id: String,
|
|
119
|
+
pub data: serde_json::Value, // Event-specific data
|
|
120
|
+
pub provenance: Option<Provenance>,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Context window for analysis
|
|
124
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
125
|
+
pub struct ContextWindow {
|
|
126
|
+
pub session_id: String,
|
|
127
|
+
pub start_time: DateTime<Utc>,
|
|
128
|
+
pub end_time: DateTime<Utc>,
|
|
129
|
+
pub commands: Vec<Command>,
|
|
130
|
+
pub outputs: Vec<Output>,
|
|
131
|
+
pub errors: Vec<Error>,
|
|
132
|
+
pub insights: Vec<Insight>,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
impl Session {
|
|
136
|
+
pub fn new(shell_type: String, initial_cwd: String) -> Self {
|
|
137
|
+
Self {
|
|
138
|
+
id: Uuid::new_v4().to_string(),
|
|
139
|
+
started_at: Utc::now(),
|
|
140
|
+
ended_at: None,
|
|
141
|
+
shell_type,
|
|
142
|
+
initial_cwd,
|
|
143
|
+
hostname: None,
|
|
144
|
+
user: None,
|
|
145
|
+
metadata: serde_json::json!({}),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
impl Command {
|
|
151
|
+
pub fn new(session_id: String, command: String, args: Vec<String>, cwd: String) -> Self {
|
|
152
|
+
Self {
|
|
153
|
+
id: Uuid::new_v4().to_string(),
|
|
154
|
+
session_id,
|
|
155
|
+
command,
|
|
156
|
+
args,
|
|
157
|
+
env_summary: serde_json::json!({}),
|
|
158
|
+
cwd,
|
|
159
|
+
started_at: Utc::now(),
|
|
160
|
+
ended_at: None,
|
|
161
|
+
exit_code: None,
|
|
162
|
+
success: false,
|
|
163
|
+
duration_ms: None,
|
|
164
|
+
pid: None,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
impl Output {
|
|
170
|
+
pub fn new(
|
|
171
|
+
command_id: String,
|
|
172
|
+
stream_type: String,
|
|
173
|
+
chunk_index: u32,
|
|
174
|
+
content: Vec<u8>,
|
|
175
|
+
) -> Self {
|
|
176
|
+
let size_bytes = content.len() as u64;
|
|
177
|
+
Self {
|
|
178
|
+
id: Uuid::new_v4().to_string(),
|
|
179
|
+
command_id,
|
|
180
|
+
stream_type,
|
|
181
|
+
chunk_index,
|
|
182
|
+
content,
|
|
183
|
+
compressed: false,
|
|
184
|
+
size_bytes,
|
|
185
|
+
timestamp: Utc::now(),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
impl Error {
|
|
191
|
+
pub fn new(
|
|
192
|
+
command_id: String,
|
|
193
|
+
session_id: String,
|
|
194
|
+
error_type: String,
|
|
195
|
+
severity: String,
|
|
196
|
+
message: String,
|
|
197
|
+
) -> Self {
|
|
198
|
+
Self {
|
|
199
|
+
id: Uuid::new_v4().to_string(),
|
|
200
|
+
command_id,
|
|
201
|
+
session_id,
|
|
202
|
+
error_type,
|
|
203
|
+
severity,
|
|
204
|
+
message,
|
|
205
|
+
stderr_snippet: None,
|
|
206
|
+
exit_code: None,
|
|
207
|
+
timestamp: Utc::now(),
|
|
208
|
+
context: serde_json::json!({}),
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
impl Insight {
|
|
214
|
+
pub fn new(
|
|
215
|
+
insight_type: String,
|
|
216
|
+
title: String,
|
|
217
|
+
description: String,
|
|
218
|
+
confidence: f64,
|
|
219
|
+
source: String,
|
|
220
|
+
) -> Self {
|
|
221
|
+
Self {
|
|
222
|
+
id: Uuid::new_v4().to_string(),
|
|
223
|
+
command_id: None,
|
|
224
|
+
session_id: None,
|
|
225
|
+
insight_type,
|
|
226
|
+
title,
|
|
227
|
+
description,
|
|
228
|
+
confidence,
|
|
229
|
+
source,
|
|
230
|
+
generated_at: Utc::now(),
|
|
231
|
+
metadata: serde_json::json!({}),
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
impl Suggestion {
|
|
237
|
+
pub fn new(
|
|
238
|
+
suggestion_type: String,
|
|
239
|
+
priority: String,
|
|
240
|
+
rank: f64,
|
|
241
|
+
title: String,
|
|
242
|
+
description: String,
|
|
243
|
+
) -> Self {
|
|
244
|
+
Self {
|
|
245
|
+
id: Uuid::new_v4().to_string(),
|
|
246
|
+
suggestion_type,
|
|
247
|
+
priority,
|
|
248
|
+
rank,
|
|
249
|
+
title,
|
|
250
|
+
description,
|
|
251
|
+
command: None,
|
|
252
|
+
args: None,
|
|
253
|
+
context: serde_json::json!({}),
|
|
254
|
+
created_at: Utc::now(),
|
|
255
|
+
dismissed: false,
|
|
256
|
+
applied: false,
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
impl Provenance {
|
|
262
|
+
pub fn new(entity_type: String, entity_id: String, source: String) -> Self {
|
|
263
|
+
Self {
|
|
264
|
+
id: Uuid::new_v4().to_string(),
|
|
265
|
+
entity_type,
|
|
266
|
+
entity_id,
|
|
267
|
+
source,
|
|
268
|
+
confidence: None,
|
|
269
|
+
model: None,
|
|
270
|
+
tool: None,
|
|
271
|
+
created_at: Utc::now(),
|
|
272
|
+
metadata: serde_json::json!({}),
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Tests for cognitive memory storage
|
|
2
|
+
// Property tests and integration tests
|
|
3
|
+
|
|
4
|
+
#[cfg(test)]
|
|
5
|
+
mod tests {
|
|
6
|
+
use crate::memory::api::MemoryStore;
|
|
7
|
+
use crate::memory::client::PluresDBClient;
|
|
8
|
+
use crate::memory::migration;
|
|
9
|
+
use crate::memory::schema::*;
|
|
10
|
+
use crate::memory::*;
|
|
11
|
+
use chrono::Duration as ChronoDuration;
|
|
12
|
+
use chrono::Utc;
|
|
13
|
+
|
|
14
|
+
// Integration test: store events then query
|
|
15
|
+
#[tokio::test]
|
|
16
|
+
async fn test_store_and_query_events() {
|
|
17
|
+
// This test requires a PluresDB server running
|
|
18
|
+
// Skip if server is not available
|
|
19
|
+
let client = match PluresDBClient::new("localhost", 34567, "./test-pluresdb-data") {
|
|
20
|
+
Ok(c) => c,
|
|
21
|
+
Err(_) => {
|
|
22
|
+
eprintln!("Skipping test: PluresDB server not available");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Check if server is available
|
|
28
|
+
if !client.health_check().await.unwrap_or(false) {
|
|
29
|
+
eprintln!("Skipping test: PluresDB server not responding");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let store = MemoryStore::new(client).await.unwrap();
|
|
34
|
+
|
|
35
|
+
// Create a test session
|
|
36
|
+
let session = Session::new("bash".to_string(), "/tmp".to_string());
|
|
37
|
+
let session_key = format!("memory:session:{}", session.id);
|
|
38
|
+
store
|
|
39
|
+
.client
|
|
40
|
+
.put(&session_key, &serde_json::to_value(&session).unwrap())
|
|
41
|
+
.await
|
|
42
|
+
.unwrap();
|
|
43
|
+
|
|
44
|
+
// Create a test command
|
|
45
|
+
let mut command = Command::new(
|
|
46
|
+
session.id.clone(),
|
|
47
|
+
"echo".to_string(),
|
|
48
|
+
vec!["hello".to_string()],
|
|
49
|
+
"/tmp".to_string(),
|
|
50
|
+
);
|
|
51
|
+
command.exit_code = Some(0);
|
|
52
|
+
command.success = true;
|
|
53
|
+
command.ended_at = Some(Utc::now());
|
|
54
|
+
command.duration_ms = Some(100);
|
|
55
|
+
|
|
56
|
+
store.store_command(command.clone()).await.unwrap();
|
|
57
|
+
|
|
58
|
+
// Create a test error
|
|
59
|
+
let error = Error::new(
|
|
60
|
+
command.id.clone(),
|
|
61
|
+
session.id.clone(),
|
|
62
|
+
"exit_code".to_string(),
|
|
63
|
+
"low".to_string(),
|
|
64
|
+
"Test error".to_string(),
|
|
65
|
+
);
|
|
66
|
+
store.store_error(error.clone()).await.unwrap();
|
|
67
|
+
|
|
68
|
+
// Query recent errors
|
|
69
|
+
let errors = store
|
|
70
|
+
.query_recent_errors(Some(10), None, None)
|
|
71
|
+
.await
|
|
72
|
+
.unwrap();
|
|
73
|
+
assert!(errors.len() > 0);
|
|
74
|
+
assert!(errors.iter().any(|e| e.id == error.id));
|
|
75
|
+
|
|
76
|
+
// Get context window
|
|
77
|
+
let context = store
|
|
78
|
+
.get_context(&session.id, ChronoDuration::hours(1))
|
|
79
|
+
.await
|
|
80
|
+
.unwrap();
|
|
81
|
+
assert_eq!(context.session_id, session.id);
|
|
82
|
+
assert!(context.commands.len() > 0);
|
|
83
|
+
assert!(context.errors.len() > 0);
|
|
84
|
+
|
|
85
|
+
// List sessions
|
|
86
|
+
let sessions = store.list_sessions().await.unwrap();
|
|
87
|
+
assert!(sessions.iter().any(|s| s.id == session.id));
|
|
88
|
+
|
|
89
|
+
// Cleanup
|
|
90
|
+
store.wipe_all().await.unwrap();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Property test: schema roundtrip
|
|
94
|
+
#[tokio::test]
|
|
95
|
+
async fn test_schema_roundtrip() {
|
|
96
|
+
let client = match PluresDBClient::new("localhost", 34567, "./test-pluresdb-data") {
|
|
97
|
+
Ok(c) => c,
|
|
98
|
+
Err(_) => {
|
|
99
|
+
eprintln!("Skipping test: PluresDB server not available");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if !client.health_check().await.unwrap_or(false) {
|
|
105
|
+
eprintln!("Skipping test: PluresDB server not responding");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let store = MemoryStore::new(client).await.unwrap();
|
|
110
|
+
|
|
111
|
+
// Test Session roundtrip
|
|
112
|
+
let session = Session::new("zsh".to_string(), "/home/user".to_string());
|
|
113
|
+
let session_key = format!("memory:session:{}", session.id);
|
|
114
|
+
store
|
|
115
|
+
.client
|
|
116
|
+
.put(&session_key, &serde_json::to_value(&session).unwrap())
|
|
117
|
+
.await
|
|
118
|
+
.unwrap();
|
|
119
|
+
|
|
120
|
+
let retrieved_value = store.client.get(&session_key).await.unwrap().unwrap();
|
|
121
|
+
let retrieved: Session = serde_json::from_value(retrieved_value).unwrap();
|
|
122
|
+
assert_eq!(session.id, retrieved.id);
|
|
123
|
+
assert_eq!(session.shell_type, retrieved.shell_type);
|
|
124
|
+
assert_eq!(session.initial_cwd, retrieved.initial_cwd);
|
|
125
|
+
|
|
126
|
+
// Test Command roundtrip
|
|
127
|
+
let command = Command::new(
|
|
128
|
+
session.id.clone(),
|
|
129
|
+
"ls".to_string(),
|
|
130
|
+
vec!["-la".to_string()],
|
|
131
|
+
"/home/user".to_string(),
|
|
132
|
+
);
|
|
133
|
+
let command_key = format!("memory:command:{}", command.id);
|
|
134
|
+
store
|
|
135
|
+
.client
|
|
136
|
+
.put(&command_key, &serde_json::to_value(&command).unwrap())
|
|
137
|
+
.await
|
|
138
|
+
.unwrap();
|
|
139
|
+
|
|
140
|
+
let retrieved_value = store.client.get(&command_key).await.unwrap().unwrap();
|
|
141
|
+
let retrieved: Command = serde_json::from_value(retrieved_value).unwrap();
|
|
142
|
+
assert_eq!(command.id, retrieved.id);
|
|
143
|
+
assert_eq!(command.command, retrieved.command);
|
|
144
|
+
assert_eq!(command.args, retrieved.args);
|
|
145
|
+
|
|
146
|
+
// Test Suggestion roundtrip
|
|
147
|
+
let suggestion = Suggestion::new(
|
|
148
|
+
"tip".to_string(),
|
|
149
|
+
"medium".to_string(),
|
|
150
|
+
0.8,
|
|
151
|
+
"Test Tip".to_string(),
|
|
152
|
+
"This is a test suggestion".to_string(),
|
|
153
|
+
);
|
|
154
|
+
store.persist_suggestion(suggestion.clone()).await.unwrap();
|
|
155
|
+
|
|
156
|
+
let suggestions = store.get_suggestions(None, None).await.unwrap();
|
|
157
|
+
assert!(suggestions.iter().any(|s| s.id == suggestion.id));
|
|
158
|
+
|
|
159
|
+
// Cleanup
|
|
160
|
+
store.wipe_all().await.unwrap();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Test migration system
|
|
164
|
+
#[tokio::test]
|
|
165
|
+
async fn test_migrations() {
|
|
166
|
+
let client = match PluresDBClient::new("localhost", 34567, "./test-pluresdb-data") {
|
|
167
|
+
Ok(c) => c,
|
|
168
|
+
Err(_) => {
|
|
169
|
+
eprintln!("Skipping test: PluresDB server not available");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if !client.health_check().await.unwrap_or(false) {
|
|
175
|
+
eprintln!("Skipping test: PluresDB server not responding");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let store = MemoryStore::new(client).await.unwrap();
|
|
180
|
+
|
|
181
|
+
// Run migrations
|
|
182
|
+
migration::run_migrations(&store).await.unwrap();
|
|
183
|
+
|
|
184
|
+
// Check migration status
|
|
185
|
+
let status = migration::get_migration_status(&store).await.unwrap();
|
|
186
|
+
assert!(status.is_up_to_date);
|
|
187
|
+
assert_eq!(status.target_version, status.current_version);
|
|
188
|
+
|
|
189
|
+
// Cleanup
|
|
190
|
+
store.wipe_all().await.unwrap();
|
|
191
|
+
}
|
|
192
|
+
}
|