Skip to main content

memstead_base/ops/
agent_notes.rs

1//! Agent-notes wire types — pure data shapes for the
2//! commit-trailer + workspace-`__MEMSTEAD`-ref payload that the
3//! git-branch backend produces from its gitdir.
4//!
5//! These types live in `memstead-base` so the unified `Engine` can carry
6//! them as Optional fields on [`crate::ops::changes::ChangesReport`]
7//! (populated when callers pass `include_notes: true`, empty
8//! otherwise). The gix-bound producers (`read_memstead_ref`,
9//! `agent_notes_since`) stay in the git-branch backend crate — they
10//! read from a gitdir and have no meaningful equivalent on the folder
11//! or archive backends.
12//!
13//! `CommitNote` carries the parsed subject + trailer block from
14//! `crate::vcs::format_commit_message`. The workspace-level pointer
15//! is the SHA of `refs/heads/__MEMSTEAD` (unified schemas + per-mem
16//! configs branch), exposed as `memstead_ref: Option<String>` on the
17//! report — `None` when the workspace has not been migrated to the
18//! unified layout yet.
19
20use serde::Serialize;
21
22/// One commit's worth of structured agent-note state. Fields are
23/// populated best-effort: a body that doesn't match the
24/// `memstead: <verb> <id>` subject shape leaves `tool_verb` / `entity_id`
25/// `None`; absent trailers leave the corresponding fields `None`.
26/// Callers branch on `actor` for agent-vs-external classification.
27#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
28pub struct CommitNote {
29    pub mem: String,
30    pub sha: String,
31    pub subject: String,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub tool_verb: Option<String>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub entity_id: Option<String>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub note: Option<String>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub actor: Option<String>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub tool: Option<String>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub client: Option<String>,
44    /// Correlation id linking every commit produced by a single
45    /// logical operation (notably multi-mem `memstead_rename`).
46    /// Populated from the `Logical-Op:` trailer when present.
47    #[serde(skip_serializing_if = "Option::is_none", rename = "logical_op")]
48    pub logical_operation_id: Option<String>,
49    /// Caller-declared role from the `Role:` trailer (agent-trust
50    /// plan 13); absent = unspecified.
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub role: Option<String>,
53    /// Ids a multi-entity commit touched (notably `batch_update`),
54    /// recovered from the `Entities:` commit trailer. Lets an
55    /// `--include-notes` consumer name every entity a batch changed from
56    /// the note record alone — `entity_id`/`subject` keep their
57    /// (count-string) shape for backward compatibility. Empty (and
58    /// serde-omitted) for single-entity commits.
59    #[serde(skip_serializing_if = "Vec::is_empty", default)]
60    pub entity_ids: Vec<String>,
61    /// Commit timestamp in seconds since unix epoch.
62    pub timestamp: i64,
63}
64
65/// Walked output of `agent_notes_since`. `head` echoes the resolved
66/// branch tip so callers record it as the next polling cursor without
67/// a follow-up `memstead_health` round-trip. `memstead_ref` carries the
68/// workspace-level `__MEMSTEAD` ref tip (unified schemas + per-mem
69/// configs) so commit-mirroring consumers — e.g. an outer-repo cursor
70/// block — anchor it alongside the per-mem head without a second
71/// round-trip. `None` when the workspace has not been migrated to the
72/// unified layout yet.
73#[derive(Debug, Clone, Serialize)]
74pub struct AgentNotesReport {
75    pub mem: String,
76    pub since: String,
77    pub head: String,
78    pub notes: Vec<CommitNote>,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub memstead_ref: Option<String>,
81}