Skip to content

Commit 98dc641

Browse files
Add lifecycle and integration actions
1 parent e8a9441 commit 98dc641

6 files changed

Lines changed: 359 additions & 120 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use std::path::PathBuf;
2+
3+
use tree_ring_memory_core::{
4+
collect_dox_memories, collect_revolve_memories, DoxSyncReport, DoxSyncRequest,
5+
RevolveSyncReport, RevolveSyncRequest,
6+
};
7+
use tree_ring_memory_sqlite::SQLiteMemoryStore;
8+
9+
use super::ActionResult;
10+
11+
#[derive(Debug, Clone, PartialEq, Eq)]
12+
pub struct DoxSyncActionRequest {
13+
pub source_root: PathBuf,
14+
pub project: Option<String>,
15+
pub dry_run: bool,
16+
}
17+
18+
#[derive(Debug, Clone, PartialEq)]
19+
pub struct DoxSyncActionReport {
20+
pub report: DoxSyncReport,
21+
pub dry_run: bool,
22+
}
23+
24+
#[derive(Debug, Clone, PartialEq, Eq)]
25+
pub struct RevolveSyncActionRequest {
26+
pub source_root: PathBuf,
27+
pub project: Option<String>,
28+
pub dry_run: bool,
29+
}
30+
31+
#[derive(Debug, Clone, PartialEq)]
32+
pub struct RevolveSyncActionReport {
33+
pub report: RevolveSyncReport,
34+
pub dry_run: bool,
35+
}
36+
37+
pub fn sync_dox(
38+
store: Option<&mut SQLiteMemoryStore>,
39+
request: DoxSyncActionRequest,
40+
) -> ActionResult<DoxSyncActionReport> {
41+
let mut dox_request = DoxSyncRequest::new(request.source_root);
42+
dox_request.project = request.project;
43+
let report = collect_dox_memories(&dox_request).map_err(|err| err.to_string())?;
44+
if !request.dry_run {
45+
let store = store.ok_or_else(|| {
46+
"DOX sync action requires an open writable store when dry_run=false".to_string()
47+
})?;
48+
store
49+
.put_many(&report.events)
50+
.map_err(|err| err.to_string())?;
51+
}
52+
Ok(DoxSyncActionReport {
53+
report,
54+
dry_run: request.dry_run,
55+
})
56+
}
57+
58+
pub fn sync_revolve(
59+
store: Option<&mut SQLiteMemoryStore>,
60+
request: RevolveSyncActionRequest,
61+
) -> ActionResult<RevolveSyncActionReport> {
62+
let mut revolve_request = RevolveSyncRequest::new(request.source_root);
63+
revolve_request.project = request.project;
64+
let report = collect_revolve_memories(&revolve_request).map_err(|err| err.to_string())?;
65+
if !request.dry_run {
66+
let store = store.ok_or_else(|| {
67+
"Revolve sync action requires an open writable store when dry_run=false".to_string()
68+
})?;
69+
store
70+
.put_many(&report.events)
71+
.map_err(|err| err.to_string())?;
72+
}
73+
Ok(RevolveSyncActionReport {
74+
report,
75+
dry_run: request.dry_run,
76+
})
77+
}
78+
79+
#[cfg(test)]
80+
mod tests {
81+
use std::fs;
82+
use tempfile::tempdir;
83+
84+
use super::*;
85+
86+
#[test]
87+
fn dox_action_dry_run_does_not_write_events() {
88+
let dir = tempdir().unwrap();
89+
fs::write(dir.path().join("AGENTS.md"), "# Rules\n\nAlways run tests.").unwrap();
90+
91+
let report = sync_dox(
92+
None,
93+
DoxSyncActionRequest {
94+
source_root: dir.path().to_path_buf(),
95+
project: Some("tree-ring".to_string()),
96+
dry_run: true,
97+
},
98+
)
99+
.unwrap();
100+
101+
assert_eq!(report.report.memory_count, 1);
102+
assert!(!dir.path().join("memory.sqlite").exists());
103+
}
104+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use std::path::PathBuf;
2+
3+
use crate::integrations::{scan_integrations, IntegrationScanReport};
4+
5+
#[derive(Debug, Clone, PartialEq, Eq)]
6+
pub struct IntegrationScanRequest {
7+
pub source_root: PathBuf,
8+
}
9+
10+
#[derive(Debug, Clone, PartialEq)]
11+
pub struct IntegrationScanActionReport {
12+
pub report: IntegrationScanReport,
13+
}
14+
15+
pub fn scan(request: IntegrationScanRequest) -> IntegrationScanActionReport {
16+
IntegrationScanActionReport {
17+
report: scan_integrations(&request.source_root),
18+
}
19+
}
20+
21+
#[cfg(test)]
22+
mod tests {
23+
use std::fs;
24+
use tempfile::tempdir;
25+
26+
use super::*;
27+
28+
#[test]
29+
fn integration_action_scans_project_markers() {
30+
let dir = tempdir().unwrap();
31+
fs::write(dir.path().join("AGENTS.md"), "# Rules").unwrap();
32+
33+
let report = scan(IntegrationScanRequest {
34+
source_root: dir.path().to_path_buf(),
35+
});
36+
37+
assert!(report.report.detected_count > 0);
38+
}
39+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::path::Path;
2+
3+
use tree_ring_memory_core::{
4+
consolidate_memories, plan_maintenance, ConsolidationPeriod, ConsolidationReport,
5+
ConsolidationRequest, MaintenanceReport, MaintenanceRequest,
6+
};
7+
use tree_ring_memory_sqlite::SQLiteMemoryStore;
8+
9+
use super::ActionResult;
10+
11+
#[derive(Debug, Clone, PartialEq, Eq)]
12+
pub struct ConsolidateActionRequest {
13+
pub period_type: String,
14+
pub period_key: Option<String>,
15+
pub project: Option<String>,
16+
pub dry_run: bool,
17+
pub force: bool,
18+
}
19+
20+
#[derive(Debug, Clone, PartialEq, Eq)]
21+
pub struct MaintainActionRequest {
22+
pub project: Option<String>,
23+
pub include_superseded: bool,
24+
pub apply_expired: bool,
25+
pub apply_secret_redactions: bool,
26+
pub repair_fts: bool,
27+
}
28+
29+
pub fn consolidation_request(
30+
request: ConsolidateActionRequest,
31+
) -> ActionResult<ConsolidationRequest> {
32+
Ok(ConsolidationRequest {
33+
period_type: ConsolidationPeriod::parse(&request.period_type)
34+
.map_err(|err| err.to_string())?,
35+
period_key: request.period_key,
36+
project: request.project,
37+
dry_run: request.dry_run,
38+
force: request.force,
39+
})
40+
}
41+
42+
pub fn consolidate(
43+
store: &mut SQLiteMemoryStore,
44+
request: ConsolidateActionRequest,
45+
) -> ActionResult<ConsolidationReport> {
46+
let request = consolidation_request(request)?;
47+
store.consolidate(&request).map_err(|err| err.to_string())
48+
}
49+
50+
pub fn consolidate_dry_run_from_path(
51+
db_path: &Path,
52+
request: ConsolidateActionRequest,
53+
) -> ActionResult<ConsolidationReport> {
54+
let request = consolidation_request(request)?;
55+
if db_path.exists() {
56+
let store = SQLiteMemoryStore::open_read_only(db_path).map_err(|err| err.to_string())?;
57+
let events = store.list_all(false).map_err(|err| err.to_string())?;
58+
consolidate_memories(&events, &request).map_err(|err| err.to_string())
59+
} else {
60+
consolidate_memories(&[], &request).map_err(|err| err.to_string())
61+
}
62+
}
63+
64+
pub fn maintenance_request(request: MaintainActionRequest) -> MaintenanceRequest {
65+
MaintenanceRequest {
66+
dry_run: !(request.apply_expired || request.apply_secret_redactions || request.repair_fts),
67+
apply_expired: request.apply_expired,
68+
apply_secret_redactions: request.apply_secret_redactions,
69+
repair_fts: request.repair_fts,
70+
include_superseded: request.include_superseded,
71+
project: request.project,
72+
}
73+
}
74+
75+
pub fn maintain(
76+
db_path: &Path,
77+
store: Option<&mut SQLiteMemoryStore>,
78+
request: MaintainActionRequest,
79+
) -> ActionResult<MaintenanceReport> {
80+
let request = maintenance_request(request);
81+
if request.dry_run && !db_path.exists() {
82+
return Ok(plan_maintenance(&[], &request));
83+
}
84+
if let Some(store) = store {
85+
return store.maintain(&request).map_err(|err| err.to_string());
86+
}
87+
let mut store = SQLiteMemoryStore::open_read_only(db_path).map_err(|err| err.to_string())?;
88+
store.maintain(&request).map_err(|err| err.to_string())
89+
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use tempfile::tempdir;
94+
95+
use super::*;
96+
97+
#[test]
98+
fn maintenance_action_missing_store_is_non_mutating_dry_run() {
99+
let dir = tempdir().unwrap();
100+
let db_path = dir.path().join(".tree-ring/memory.sqlite");
101+
102+
let report = maintain(
103+
&db_path,
104+
None,
105+
MaintainActionRequest {
106+
project: None,
107+
include_superseded: false,
108+
apply_expired: false,
109+
apply_secret_redactions: false,
110+
repair_fts: false,
111+
},
112+
)
113+
.unwrap();
114+
115+
assert_eq!(report.memory_count, 0);
116+
assert!(!db_path.exists());
117+
}
118+
}

crates/tree-ring-memory-cli/src/actions/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
pub mod adapters;
12
pub mod audit;
23
pub mod export_import;
4+
pub mod integrations;
5+
pub mod lifecycle;
36
pub mod recall;
47
pub mod remember;
58

0 commit comments

Comments
 (0)