memstead_mcp/read_mems.rs
1//! Batch-install helper for the `--read-mem` CLI flag.
2//!
3//! Wraps [`memstead_git_branch::mem_cache::install_to_cache`] +
4//! [`memstead_git_branch::mem_cache::register_cached_archive`] in a
5//! small loop so the binary entry point stays thin and integration
6//! tests can drive the behavior without spawning the MCP server. Each
7//! archive lands in the global cache and registers as a
8//! **workspace-level read-only mount** — the same model `memstead
9//! install` produces; no writable mem's config is touched.
10
11use std::path::{Path, PathBuf};
12
13use memstead_git_branch::mem_cache::{self, CacheInstallOutcome, InstallError, MountRegistration};
14
15/// Outcome of processing a single `--read-mem` argument.
16///
17/// Owning enum instead of `Result` so callers can iterate the full batch
18/// and decide per entry how loudly to surface it — the binary warn-logs,
19/// tests inspect structure.
20#[derive(Debug)]
21pub enum ReadMemResult {
22 /// Validator accepted the archive; it is cached and mounted
23 /// (either or both may have been no-ops for already-present
24 /// content).
25 Installed {
26 archive: PathBuf,
27 outcome: CacheInstallOutcome,
28 mount: MountRegistration,
29 },
30 /// Validation, cache I/O, or mount registration failed. The error
31 /// `Display` preserves path + reason, so a warn log over the
32 /// value is actionable without unwrapping the variant.
33 Failed { archive: PathBuf, error: String },
34}
35
36/// Install every `--read-mem` archive as a workspace-level read-only
37/// mount, one by one, collecting per-archive outcomes.
38///
39/// **Warn-and-continue semantics.** A malformed archive does not abort
40/// the batch — the caller receives a `Failed` entry and keeps going.
41/// The write mem stays useful on its own; tearing the server down
42/// over one bad `--read-mem` is worse DX than a visible warning plus
43/// a running server.
44///
45/// Relative `archive` paths resolve against `cwd`. The caller persists
46/// the mount state once after the batch (`engine.persist_state()`)
47/// when any entry reports a `Registered` / `Refreshed` mount.
48pub fn install_read_mems(
49 engine: &mut memstead_base::Engine,
50 archives: &[PathBuf],
51 cwd: &Path,
52) -> Vec<ReadMemResult> {
53 let writable: Vec<String> = engine
54 .mem_router()
55 .writable_mems()
56 .iter()
57 .map(|n| n.to_string())
58 .collect();
59
60 archives
61 .iter()
62 .map(|archive| {
63 let archive = if archive.is_absolute() {
64 archive.clone()
65 } else {
66 cwd.join(archive)
67 };
68 let writable_refs: Vec<&str> = writable.iter().map(String::as_str).collect();
69 let outcome = match mem_cache::install_to_cache(&archive, &writable_refs) {
70 Ok(o) => o,
71 Err(e) => {
72 return ReadMemResult::Failed {
73 archive,
74 error: e.to_string(),
75 };
76 }
77 };
78 match mem_cache::register_cached_archive(engine, &outcome, "--read-mem") {
79 Ok(mount) => ReadMemResult::Installed {
80 archive,
81 outcome,
82 mount,
83 },
84 Err(e) => ReadMemResult::Failed {
85 archive,
86 error: e.to_string(),
87 },
88 }
89 })
90 .collect()
91}
92
93/// Re-exported for callers that log validation failures specifically.
94pub type ReadMemInstallError = InstallError;