v0.4.1 : Phase 1 complete (17/17) - dashboard TUI (#33), favoris/notes/tags (#39), profils d'environnement (#40), completions dynamiques + man pages (#41), packaging officiel + CI (#42), self-update Gitea, state v3, 212 tests
This commit is contained in:
+53
-3
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const STATE_VERSION: u32 = 2;
|
||||
pub const STATE_VERSION: u32 = 3;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StateFile {
|
||||
@@ -21,6 +21,23 @@ pub struct StateFile {
|
||||
/// RFC 3339 timestamp of the last start/run of any agent.
|
||||
#[serde(default)]
|
||||
pub last_used: Option<String>,
|
||||
/// Personal annotations per agent: favorite, note, tags (issue #39).
|
||||
#[serde(default)]
|
||||
pub annotations: BTreeMap<String, Annotation>,
|
||||
}
|
||||
|
||||
/// Personal annotations attached to an agent by the user (issue #39).
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Annotation {
|
||||
/// Marked as a favorite (star in list/status).
|
||||
#[serde(default)]
|
||||
pub favorite: bool,
|
||||
/// Free-form note shown in 'am info'.
|
||||
#[serde(default)]
|
||||
pub note: Option<String>,
|
||||
/// Personal tags, filterable with 'am search --tag'.
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for StateFile {
|
||||
@@ -30,6 +47,7 @@ impl Default for StateFile {
|
||||
installed: BTreeMap::new(),
|
||||
sessions_count: 0,
|
||||
last_used: None,
|
||||
annotations: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +180,38 @@ impl StateStore {
|
||||
sf.sessions_count = sf.sessions_count.saturating_add(1);
|
||||
self.save(&sf)
|
||||
}
|
||||
|
||||
/// Every personal annotation, keyed by agent name (issue #39).
|
||||
pub fn annotations(&self) -> Result<BTreeMap<String, Annotation>> {
|
||||
Ok(self.load()?.annotations)
|
||||
}
|
||||
|
||||
/// The annotation block of one agent (defaults to empty).
|
||||
pub fn annotation(&self, name: &str) -> Result<Annotation> {
|
||||
Ok(self
|
||||
.load()?
|
||||
.annotations
|
||||
.get(name)
|
||||
.cloned()
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Replace the annotation block of one agent.
|
||||
pub fn set_annotation(&self, name: &str, ann: &Annotation) -> Result<()> {
|
||||
let mut sf = self.load()?;
|
||||
sf.annotations.insert(name.to_string(), ann.clone());
|
||||
self.save(&sf)
|
||||
}
|
||||
|
||||
/// Remove the annotation block of one agent (returns true when present).
|
||||
pub fn remove_annotation(&self, name: &str) -> Result<bool> {
|
||||
let mut sf = self.load()?;
|
||||
let removed = sf.annotations.remove(name).is_some();
|
||||
if removed {
|
||||
self.save(&sf)?;
|
||||
}
|
||||
Ok(removed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -221,13 +271,13 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
let sf = st.load().unwrap();
|
||||
assert_eq!(sf.version, 2);
|
||||
assert_eq!(sf.version, 3);
|
||||
assert_eq!(sf.sessions_count, 0);
|
||||
assert_eq!(sf.last_used, None);
|
||||
assert!(sf.installed.contains_key("claude-code"));
|
||||
// The migration is persisted on first contact.
|
||||
let raw = std::fs::read_to_string(st.path()).unwrap();
|
||||
assert!(raw.contains(r#""version": 2"#), "migrated file rewritten: {raw}");
|
||||
assert!(raw.contains(r#""version": 3"#), "migrated file rewritten: {raw}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user