@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.
Files changed (148) hide show
  1. package/ANALYSIS_LADDER.md +231 -0
  2. package/CHANGELOG.md +124 -0
  3. package/INTEGRATIONS.md +242 -0
  4. package/LICENSE +21 -0
  5. package/MEMORY.md +253 -0
  6. package/NIXOS.md +357 -0
  7. package/QUICKSTART.md +157 -0
  8. package/README.md +295 -0
  9. package/RELEASE.md +190 -0
  10. package/ValidationChecklist.md +598 -0
  11. package/docs/demo.md +338 -0
  12. package/docs/llm-integration.md +300 -0
  13. package/docs/parallel-execution-plan.md +160 -0
  14. package/flake.nix +228 -0
  15. package/integrations/README.md +242 -0
  16. package/integrations/demo-steps.sh +64 -0
  17. package/integrations/nvim-runebook.lua +140 -0
  18. package/integrations/tmux-status.sh +51 -0
  19. package/integrations/vim-runebook.vim +77 -0
  20. package/integrations/wezterm-status-simple.lua +48 -0
  21. package/integrations/wezterm-status.lua +76 -0
  22. package/nixos-module.nix +156 -0
  23. package/package.json +76 -0
  24. package/packages/design-dojo/index.js +4 -0
  25. package/packages/design-dojo/package.json +20 -0
  26. package/packages/design-dojo/tokens.css +69 -0
  27. package/playwright.config.ts +16 -0
  28. package/scripts/check-versions.cjs +62 -0
  29. package/scripts/demo.sh +220 -0
  30. package/shell.nix +31 -0
  31. package/src/app.html +13 -0
  32. package/src/cli/index.ts +1050 -0
  33. package/src/lib/agent/analysis-pipeline.ts +347 -0
  34. package/src/lib/agent/analysis-service.ts +171 -0
  35. package/src/lib/agent/analysis.ts +159 -0
  36. package/src/lib/agent/analyzers/heuristic.ts +289 -0
  37. package/src/lib/agent/analyzers/index.ts +7 -0
  38. package/src/lib/agent/analyzers/llm.ts +204 -0
  39. package/src/lib/agent/analyzers/local-search.ts +215 -0
  40. package/src/lib/agent/capture.ts +123 -0
  41. package/src/lib/agent/index.ts +244 -0
  42. package/src/lib/agent/integration.ts +81 -0
  43. package/src/lib/agent/llm/providers/base.ts +99 -0
  44. package/src/lib/agent/llm/providers/index.ts +60 -0
  45. package/src/lib/agent/llm/providers/mock.ts +67 -0
  46. package/src/lib/agent/llm/providers/ollama.ts +151 -0
  47. package/src/lib/agent/llm/providers/openai.ts +153 -0
  48. package/src/lib/agent/llm/sanitizer.ts +170 -0
  49. package/src/lib/agent/llm/types.ts +118 -0
  50. package/src/lib/agent/memory.ts +363 -0
  51. package/src/lib/agent/node-status.ts +56 -0
  52. package/src/lib/agent/node-suggestions.ts +64 -0
  53. package/src/lib/agent/status.ts +80 -0
  54. package/src/lib/agent/suggestions.ts +169 -0
  55. package/src/lib/components/Canvas.svelte +124 -0
  56. package/src/lib/components/ConnectionLine.svelte +46 -0
  57. package/src/lib/components/DisplayNode.svelte +167 -0
  58. package/src/lib/components/InputNode.svelte +158 -0
  59. package/src/lib/components/TerminalNode.svelte +237 -0
  60. package/src/lib/components/Toolbar.svelte +359 -0
  61. package/src/lib/components/TransformNode.svelte +327 -0
  62. package/src/lib/core/index.ts +31 -0
  63. package/src/lib/core/observer.ts +278 -0
  64. package/src/lib/core/redaction.ts +158 -0
  65. package/src/lib/core/shell-adapters/base.ts +325 -0
  66. package/src/lib/core/shell-adapters/bash.ts +110 -0
  67. package/src/lib/core/shell-adapters/index.ts +62 -0
  68. package/src/lib/core/shell-adapters/zsh.ts +105 -0
  69. package/src/lib/core/storage.ts +360 -0
  70. package/src/lib/core/types.ts +176 -0
  71. package/src/lib/design-dojo/Box.svelte +47 -0
  72. package/src/lib/design-dojo/Button.svelte +75 -0
  73. package/src/lib/design-dojo/Input.svelte +65 -0
  74. package/src/lib/design-dojo/List.svelte +38 -0
  75. package/src/lib/design-dojo/Select.svelte +48 -0
  76. package/src/lib/design-dojo/SplitPane.svelte +43 -0
  77. package/src/lib/design-dojo/StatusBar.svelte +61 -0
  78. package/src/lib/design-dojo/Table.svelte +47 -0
  79. package/src/lib/design-dojo/Text.svelte +36 -0
  80. package/src/lib/design-dojo/Toggle.svelte +48 -0
  81. package/src/lib/design-dojo/index.ts +10 -0
  82. package/src/lib/stores/canvas-praxis.ts +268 -0
  83. package/src/lib/stores/canvas.ts +58 -0
  84. package/src/lib/types/agent.ts +78 -0
  85. package/src/lib/types/canvas.ts +71 -0
  86. package/src/lib/utils/storage.ts +326 -0
  87. package/src/lib/utils/yaml-loader.ts +52 -0
  88. package/src/routes/+layout.svelte +5 -0
  89. package/src/routes/+layout.ts +5 -0
  90. package/src/routes/+page.svelte +32 -0
  91. package/src-tauri/Cargo.lock +5735 -0
  92. package/src-tauri/Cargo.toml +38 -0
  93. package/src-tauri/build.rs +3 -0
  94. package/src-tauri/capabilities/default.json +10 -0
  95. package/src-tauri/icons/128x128.png +0 -0
  96. package/src-tauri/icons/128x128@2x.png +0 -0
  97. package/src-tauri/icons/32x32.png +0 -0
  98. package/src-tauri/icons/Square107x107Logo.png +0 -0
  99. package/src-tauri/icons/Square142x142Logo.png +0 -0
  100. package/src-tauri/icons/Square150x150Logo.png +0 -0
  101. package/src-tauri/icons/Square284x284Logo.png +0 -0
  102. package/src-tauri/icons/Square30x30Logo.png +0 -0
  103. package/src-tauri/icons/Square310x310Logo.png +0 -0
  104. package/src-tauri/icons/Square44x44Logo.png +0 -0
  105. package/src-tauri/icons/Square71x71Logo.png +0 -0
  106. package/src-tauri/icons/Square89x89Logo.png +0 -0
  107. package/src-tauri/icons/StoreLogo.png +0 -0
  108. package/src-tauri/icons/icon.icns +0 -0
  109. package/src-tauri/icons/icon.ico +0 -0
  110. package/src-tauri/icons/icon.png +0 -0
  111. package/src-tauri/src/agents/agent1.rs +66 -0
  112. package/src-tauri/src/agents/agent2.rs +80 -0
  113. package/src-tauri/src/agents/agent3.rs +73 -0
  114. package/src-tauri/src/agents/agent4.rs +66 -0
  115. package/src-tauri/src/agents/agent5.rs +68 -0
  116. package/src-tauri/src/agents/agent6.rs +75 -0
  117. package/src-tauri/src/agents/base.rs +52 -0
  118. package/src-tauri/src/agents/mod.rs +17 -0
  119. package/src-tauri/src/core/coordination.rs +117 -0
  120. package/src-tauri/src/core/mod.rs +12 -0
  121. package/src-tauri/src/core/ownership.rs +61 -0
  122. package/src-tauri/src/core/types.rs +132 -0
  123. package/src-tauri/src/execution/mod.rs +5 -0
  124. package/src-tauri/src/execution/runner.rs +143 -0
  125. package/src-tauri/src/lib.rs +161 -0
  126. package/src-tauri/src/main.rs +6 -0
  127. package/src-tauri/src/memory/api.rs +422 -0
  128. package/src-tauri/src/memory/client.rs +156 -0
  129. package/src-tauri/src/memory/encryption.rs +79 -0
  130. package/src-tauri/src/memory/migration.rs +110 -0
  131. package/src-tauri/src/memory/mod.rs +28 -0
  132. package/src-tauri/src/memory/schema.rs +275 -0
  133. package/src-tauri/src/memory/tests.rs +192 -0
  134. package/src-tauri/src/orchestrator/coordinator.rs +232 -0
  135. package/src-tauri/src/orchestrator/mod.rs +13 -0
  136. package/src-tauri/src/orchestrator/planner.rs +304 -0
  137. package/src-tauri/tauri.conf.json +35 -0
  138. package/static/examples/date-time-example.yaml +147 -0
  139. package/static/examples/hello-world.yaml +74 -0
  140. package/static/examples/transform-example.yaml +157 -0
  141. package/static/favicon.png +0 -0
  142. package/static/svelte.svg +1 -0
  143. package/static/tauri.svg +6 -0
  144. package/static/vite.svg +1 -0
  145. package/svelte.config.js +18 -0
  146. package/tsconfig.json +19 -0
  147. package/vite.config.js +45 -0
  148. 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
+ }