1use rmcp::schemars;
9
10#[derive(Debug, Clone, serde::Deserialize, schemars::JsonSchema)]
17#[serde(deny_unknown_fields)]
18#[serde(rename_all = "camelCase")]
19pub struct VcsConfigInput {
20 #[schemars(description = "Path to the gitdir relative to the new mem's root.")]
21 pub gitdir: String,
22 #[schemars(
23 description = "Path to the worktree relative to the new mem's root. Defaults to `\".\"` (mem root) when omitted."
24 )]
25 #[serde(default = "default_worktree")]
26 pub worktree: String,
27}
28
29fn default_worktree() -> String {
30 ".".to_string()
31}
32
33impl From<VcsConfigInput> for memstead_schema::VcsConfig {
34 fn from(v: VcsConfigInput) -> Self {
35 Self {
36 gitdir: v.gitdir,
37 worktree: v.worktree,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, serde::Deserialize, schemars::JsonSchema)]
50#[serde(deny_unknown_fields)]
51#[serde(rename_all = "snake_case")]
52pub enum RecoveryActionInput {
53 Reattach,
59 ForceOverwrite,
64 HardCleanupFirst,
69}
70
71impl From<RecoveryActionInput> for memstead_engine::RecoveryAction {
72 fn from(v: RecoveryActionInput) -> Self {
73 match v {
74 RecoveryActionInput::Reattach => memstead_engine::RecoveryAction::Reattach,
75 RecoveryActionInput::ForceOverwrite => memstead_engine::RecoveryAction::ForceOverwrite,
76 RecoveryActionInput::HardCleanupFirst => {
77 memstead_engine::RecoveryAction::HardCleanupFirst
78 }
79 }
80 }
81}
82
83#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
85#[serde(deny_unknown_fields)]
86pub struct MemCreateParams {
87 #[schemars(
88 description = "Unique name for the new mem — the full hierarchical identifier (e.g. `\"sub-mem\"` for flat layouts or `\"team/sub-mem\"` for hierarchical layouts); the value flows through verbatim. Grammar: lowercase ASCII letters, digits, hyphens; segments separated by `/`; no leading, trailing, or double slashes. Must not collide with any currently-registered mem."
89 )]
90 pub name: String,
91 #[schemars(
92 description = "Target filesystem location. Absolute path, or relative to the workspace root. Canonicalized before the allowlist check — `./a/../b` is reduced to `./b` prior to matching."
93 )]
94 pub location: String,
95 #[schemars(
96 description = "Schema pin for the new mem. Format: `name@x.y.z` — e.g. `default@1.0.0`. Resolved against the per-mem schema registry at init time."
97 )]
98 pub schema: String,
99 #[schemars(
100 description = "Optional VCS layout override. Shape: `{ \"gitdir\": \".git\", \"worktree\": \".\" }` (default isolated) or `{ \"gitdir\": \"../.git\", \"worktree\": \"..\" }` (shared-gitdir idiom). Paths are relative to the new mem's root. When absent, the engine uses the isolated default."
101 )]
102 pub vcs: Option<VcsConfigInput>,
103 #[schemars(
104 description = "Optional human-readable display title applied at creation — display text, not identity (the mem is always addressed by `name`). Same validation and storage as the CLI's `mem set-title`. Omit to leave the mem untitled."
105 )]
106 #[serde(default)]
107 pub title: Option<String>,
108 #[schemars(
109 description = "Optional one-line description applied at creation — embedded in `.mem` archive exports and surfaced on rosters. Same storage as the CLI's `mem set-description`."
110 )]
111 #[serde(default)]
112 pub description: Option<String>,
113 #[schemars(
114 description = "Optional subject block applied at creation — `{scope, method?, exclusions?}`: what the mem covers, how its content was arrived at, and what was deliberately left out. Same storage as the CLI's `mem set-subject`."
115 )]
116 #[serde(default)]
117 pub subject: Option<MemSubjectInput>,
118 #[schemars(
119 description = "Agent-authored provenance note recorded in the seed commit's body (≤280 chars). One sentence describing why this mem was created."
120 )]
121 pub note: Option<String>,
122 #[schemars(
123 description = "Explicit recovery action when on-disk storage residue is detected at the composed branch path. Three accepted values: `reattach` (adopt the residual entities, skip the seed commit), `force_overwrite` (destroy the residue — it is removed atomically, so either the residue is gone and the mem is created or nothing changed; the prior entities are gone by design), `hard_cleanup_first` (refuse with `MEM_STORAGE_RESIDUE_DETECTED`, instructing the caller to run `memstead_mem_delete` first). When omitted, the engine routes by whether the residue was left by a deliberate `memstead mem unregister`: such residue defaults to `reattach` and emits a `MEM_REATTACHED_AFTER_UNREGISTER` warning; residue from a crash refuses with `MEM_STORAGE_RESIDUE_DETECTED`. Bare create against a name with no residue ignores this field."
124 )]
125 pub recovery: Option<RecoveryActionInput>,
126 #[schemars(
127 description = "Inline the resolved schema body on the response (byte-identical to `memstead_schema(name=<resolved-schema>)` at the same verbosity). Default `false` — the response carries only `schema_ref`, `name`, `location`, and `seed_commit_sha`. Set to `true` for first-time-schema callers that want one round-trip instead of two; the schema is workspace-stable, so for the agent's second+ mem on the same schema the omitted default is the right call."
128 )]
129 #[serde(default)]
130 pub include_schema: bool,
131 #[schemars(
132 description = "Verbosity of the inlined schema body when `include_schema: true`. `\"lite\"` (default, absent) inlines the cheap cold-start skeleton (entity-type names + section keys + field shapes, relationship names + endpoints, the alias pointer; prose dropped) — the right pairing for a first-mem create that only needs to orient, and byte-identical to `memstead_schema`'s default reply. `\"full\"` inlines the complete schema — byte-identical to `memstead_schema(name=<resolved-schema>, verbosity=\"full\")`. Ignored when `include_schema` is false. Any value other than `\"full\"`/`\"lite\"` returns `INVALID_INPUT` naming the bad value."
133 )]
134 pub schema_verbosity: Option<String>,
135 #[schemars(
136 description = "Optional per-instance writing guidance, written verbatim into the new mem's config `writeGuidance` map in the seed commit. An opaque string-keyed JSON object — e.g. `{ \"phase_context\": \"early design\", \"stack\": \"Rust\" }`. The engine never interprets the keys (schema-strictness D8 — `writeGuidance` is client-owned vocabulary); a client that read the resolved schema package's `mem-template.json` fills the instance keys and passes them here. Omit (or pass `{}`) to seed no guidance."
137 )]
138 #[serde(default)]
139 pub write_guidance: std::collections::HashMap<String, serde_json::Value>,
140}
141
142#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
145#[serde(deny_unknown_fields)]
146pub struct MemSetSchemaParams {
147 #[schemars(description = "Name of the writable mem whose schema pin is being set.")]
148 pub mem: String,
149 #[schemars(
150 description = "Target schema ref, exact `name@x.y.z`. Must resolve against the loaded schema catalogue (mem-pinned, workspace, built-in); unresolvable refs refuse with SCHEMA_NOT_FOUND, malformed refs with INVALID_INPUT."
151 )]
152 pub schema: String,
153 #[schemars(
154 description = "Optional provenance note (≤280 chars). Reserved: the pin lives in workspace state today (no mem commit is produced), so the note is accepted for wire-compat and recorded once the pin-relocation cut moves the schema pin into mem config."
155 )]
156 pub note: Option<String>,
157}
158
159#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
161#[serde(deny_unknown_fields)]
162pub struct MemSetVersionParams {
163 #[schemars(description = "Name of the mem whose `version` field is being updated.")]
164 pub name: String,
165 #[schemars(
166 description = "New semver version (e.g. `0.2.0`, `1.0.0-beta.1`). Validated as semver; malformed values refuse with `INVALID_INPUT`. The version is consumed by `memstead_export --format mem` to stamp the archive filename and the `.mem` archive's published config — bump before publishing. Initial mem-create seeds `0.1.0` so this surface is the only path that needs to be invoked when an agent or operator is ready to ship."
167 )]
168 pub version: String,
169 #[schemars(
170 description = "Optional provenance note (≤280 chars) recorded on the version-bump commit body. When the workspace sets `require_notes`, omitting it rides a non-blocking `NOTE_MISSING` warning (the bump still lands)."
171 )]
172 pub note: Option<String>,
173}
174
175#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
187#[serde(deny_unknown_fields)]
188pub struct MemDeleteParams {
189 #[schemars(description = "Name of the mem to destroy.")]
190 pub name: String,
191 #[schemars(
192 description = "Agent-authored provenance note (≤280 chars). Surfaces in the outer-repo Stop-hook aggregation via the engine's trace surface; no per-mem commit is produced by delete."
193 )]
194 pub note: Option<String>,
195}
196
197#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
210#[serde(deny_unknown_fields)]
211pub struct WorkspaceGrantCrossLinkParams {
212 #[schemars(
213 description = "Source mem. The grantee — the mem permitted to author cross-mem edges into `to`."
214 )]
215 pub from: String,
216 #[schemars(
217 description = "Target mem. Pass a named mem (e.g. `\"specs\"`) to append to the named-allowlist shape, or the literal `\"*\"` to set the wildcard shape (any target). Wildcard vs. named is mutually exclusive per `from`-mem — switching between requires revoking the prior shape first; mixing surfaces `CROSS_LINK_CONFLICT`."
218 )]
219 pub to: String,
220}
221
222#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
224#[serde(deny_unknown_fields)]
225pub struct WorkspaceRevokeCrossLinkParams {
226 #[schemars(description = "Source mem. The grantee whose existing grant is being revoked.")]
227 pub from: String,
228 #[schemars(
229 description = "Target mem, or `\"*\"` to revoke the wildcard shape. When the underlying list becomes empty, the `from` key is dropped entirely."
230 )]
231 pub to: String,
232}
233
234#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
236#[serde(deny_unknown_fields)]
237pub struct WorkspaceAllowCreateParams {
238 #[schemars(
239 description = "Glob pattern matched against composed mem candidates (`<mem_path>/<name>` for hierarchical, bare `<name>` for flat). First-match-wins; lower index = higher priority."
240 )]
241 pub pattern: String,
242 #[schemars(
243 description = "Schema pins admitted by this rule. `[\"*\"]` is the any-schema escape. Each entry is a canonical `name@version` pin (e.g. `\"default@1.0.0\"`)."
244 )]
245 pub schemas: Vec<String>,
246 #[schemars(
247 description = "Existing pattern to insert before — lifts the new rule above the named pattern in the priority list. Omit to append at the end (lowest priority)."
248 )]
249 pub before: Option<String>,
250 #[schemars(
251 description = "Default cross-mem link grants for mems matching this rule. Each entry is a target-mem name (`\"specs\"`) or `\"*\"` (any). Pre-populates `[cross_mem_links]` for matching new mems so agents don't have to grant a second time."
252 )]
253 pub default_cross_links: Option<Vec<String>>,
254}
255
256#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
258#[serde(deny_unknown_fields)]
259pub struct WorkspaceRevokeCreateParams {
260 #[schemars(
261 description = "Glob pattern of the `[[mem_management.create]]` rule to drop. Matched exactly against the rule's `pattern` field."
262 )]
263 pub pattern: String,
264}
265
266#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
268#[serde(deny_unknown_fields)]
269pub struct WorkspaceAllowDeleteParams {
270 #[schemars(
271 description = "Glob pattern matched against composed mem candidates. Appended to `[[mem_management.delete]]` — the symmetric allowlist for `memstead_mem_delete`. Agent-creatable equals agent-deletable in spirit; mirror the create-side `pattern` to keep parity."
272 )]
273 pub pattern: String,
274}
275
276#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
278#[serde(deny_unknown_fields)]
279pub struct WorkspaceRevokeDeleteParams {
280 #[schemars(
281 description = "Glob pattern of the `[[mem_management.delete]]` rule to drop. Matched exactly against the rule's `pattern` field."
282 )]
283 pub pattern: String,
284}
285
286#[derive(Debug, Clone, serde::Deserialize, schemars::JsonSchema)]
289#[serde(deny_unknown_fields)]
290pub struct MemSubjectInput {
291 #[schemars(description = "What this mem covers. Required to set the block.")]
292 pub scope: String,
293 #[schemars(description = "How the mem's content was arrived at.")]
294 #[serde(default)]
295 pub method: Option<String>,
296 #[schemars(
297 description = "What was considered and deliberately left out — prose statements, order preserved."
298 )]
299 #[serde(default)]
300 pub exclusions: Option<Vec<String>>,
301}
302
303impl MemSubjectInput {
304 pub(crate) fn into_engine(self) -> memstead_schema::MemSubject {
306 memstead_schema::MemSubject {
307 scope: self.scope,
308 method: self.method,
309 exclusions: self.exclusions.unwrap_or_default(),
310 }
311 }
312}
313
314#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
324#[serde(deny_unknown_fields)]
325pub struct MemConfigureParams {
326 #[schemars(description = "Name of the mem to configure (must be a registered writable mem).")]
327 pub name: String,
328 #[schemars(
329 description = "New display title. Absent = untouched; empty string = clear (the roster falls back to the mem name)."
330 )]
331 #[serde(default)]
332 pub title: Option<String>,
333 #[schemars(
334 description = "New one-line description. Absent = untouched; empty string = clear."
335 )]
336 #[serde(default)]
337 pub description: Option<String>,
338 #[schemars(
339 description = "New subject block `{scope, method?, exclusions?}`. Absent = untouched; to clear the block as a unit pass `clear_subject: true` instead."
340 )]
341 #[serde(default)]
342 pub subject: Option<MemSubjectInput>,
343 #[schemars(
344 description = "Clear the subject block as a unit. Mutually exclusive with `subject` (both set refuses `INVALID_INPUT`)."
345 )]
346 #[serde(default)]
347 pub clear_subject: bool,
348 #[schemars(
349 description = "Optional provenance note (≤280 chars) recorded on each field's config commit."
350 )]
351 #[serde(default)]
352 pub note: Option<String>,
353}