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
release / linux (push) Failing after 23s
release / windows (push) Canceled after 0s
release / macos (push) Canceled after 0s

This commit is contained in:
2026-08-17 10:50:13 -04:00
parent 4f4cd034b2
commit 56732d672b
93 changed files with 9412 additions and 1270 deletions
+53 -3
View File
@@ -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]