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}