memory: resolve repo memory by memory marker
This commit is contained in:
parent
afd683ac06
commit
9ed6613a94
|
|
@ -165,17 +165,19 @@ pub struct WebFetchConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Memory subsystem configuration. Presence in the manifest enables
|
/// Memory subsystem configuration. Presence in the manifest enables
|
||||||
/// memory; the workspace root defaults to the Pod's pwd unless an
|
/// memory; `workspace_root` pins the memory workspace explicitly. When it
|
||||||
/// explicit override is given.
|
/// is absent, memory resolution searches upward from the Pod's pwd for a
|
||||||
|
/// `.yoi/memory` marker rather than treating `.yoi` project records alone
|
||||||
|
/// as a memory root.
|
||||||
///
|
///
|
||||||
/// All fields are `Option`; defaults are applied at the consumer
|
/// All fields are `Option`; defaults are applied at the consumer
|
||||||
/// (`.unwrap_or(defaults::...)`). This keeps cascade `merge` simple
|
/// (`.unwrap_or(defaults::...)`). This keeps cascade `merge` simple
|
||||||
/// (`upper.x.or(self.x)`) without a separate partial/resolved split.
|
/// (`upper.x.or(self.x)`) without a separate partial/resolved split.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct MemoryConfig {
|
pub struct MemoryConfig {
|
||||||
/// Override for the workspace root. When `None`, the Pod's pwd
|
/// Override for the memory workspace root. When `None`, consumers resolve
|
||||||
/// (resolved at construction time) is used. When set, must be an
|
/// the root from their default path and ancestor `.yoi/memory` markers.
|
||||||
/// absolute path.
|
/// When set, must be an absolute path.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub workspace_root: Option<PathBuf>,
|
pub workspace_root: Option<PathBuf>,
|
||||||
/// Maximum number of records returned by `MemoryQuery` /
|
/// Maximum number of records returned by `MemoryQuery` /
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
//! Workspace-level path layout for the memory subsystem.
|
//! Workspace-level path layout for the memory subsystem.
|
||||||
//!
|
//!
|
||||||
//! `WorkspaceLayout` carries the workspace root (typically the Pod's
|
//! `WorkspaceLayout` carries the root used by the memory subsystem.
|
||||||
//! pwd). All yoi-managed content lives under the conventional
|
//! All yoi-managed memory content lives under the conventional
|
||||||
//! `<root>/.yoi/` subdirectory — the same place that holds
|
//! `<root>/.yoi/` subdirectory — alongside workspace project records
|
||||||
//! `profiles.toml`, `prompts/`, workflow, knowledge, and generated
|
//! such as workflow and generated durable memory. The trees inside it:
|
||||||
//! memory. The trees inside it:
|
|
||||||
//!
|
//!
|
||||||
//! - `<root>/.yoi/workflow/<slug>.md`
|
//! - `<root>/.yoi/workflow/<slug>.md`
|
||||||
//! - `<root>/.yoi/knowledge/<slug>.md`
|
//! - `<root>/.yoi/knowledge/<slug>.md`
|
||||||
|
|
@ -18,9 +17,9 @@
|
||||||
//! Workflows are human-managed and live one level up under
|
//! Workflows are human-managed and live one level up under
|
||||||
//! `.yoi/workflow/`.
|
//! `.yoi/workflow/`.
|
||||||
//!
|
//!
|
||||||
//! Configuring `[memory]` with an empty body is therefore sufficient
|
//! `memory.workspace_root` pins this root explicitly. Without an explicit
|
||||||
//! for any workspace that already uses the `.yoi/` convention; no
|
//! root, resolution searches upward from the Pod pwd for a `.yoi/memory`
|
||||||
//! `workspace_root` override is needed.
|
//! marker; `.yoi` project records alone are not a memory marker.
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
|
@ -82,17 +81,26 @@ impl WorkspaceLayout {
|
||||||
Self { root: root.into() }
|
Self { root: root.into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a layout from a `MemoryConfig`, falling back to
|
/// Resolve a layout from a `MemoryConfig`.
|
||||||
/// `default_root` (typically the Pod's pwd) when the manifest does
|
///
|
||||||
/// not pin `workspace_root` explicitly. Single source of truth for
|
/// An explicit `memory.workspace_root` is honored exactly. Without an
|
||||||
/// the `workspace_root.unwrap_or(pwd)` convention used across the
|
/// explicit root, resolution searches `default_root` and its ancestors for
|
||||||
/// codebase (controller wiring, scope-deny build, system-prompt
|
/// the nearest `.yoi/memory` directory. This keeps child worktrees that
|
||||||
/// resident-injection).
|
/// contain `.yoi` project records such as tickets or workflows from
|
||||||
|
/// becoming independent memory roots merely because they contain `.yoi`.
|
||||||
|
///
|
||||||
|
/// If no memory marker exists, this falls back to `default_root` because
|
||||||
|
/// existing call sites require a concrete layout. That fallback is a
|
||||||
|
/// no-marker compatibility path, not a `.yoi` marker interpretation; it
|
||||||
|
/// must not be used as evidence that `.yoi` alone enables repo-local
|
||||||
|
/// memory.
|
||||||
pub fn resolve(cfg: &manifest::MemoryConfig, default_root: &Path) -> Self {
|
pub fn resolve(cfg: &manifest::MemoryConfig, default_root: &Path) -> Self {
|
||||||
let root = cfg
|
if let Some(root) = &cfg.workspace_root {
|
||||||
.workspace_root
|
return Self::new(root.clone());
|
||||||
.clone()
|
}
|
||||||
.unwrap_or_else(|| default_root.to_path_buf());
|
|
||||||
|
let root =
|
||||||
|
find_memory_marker_root(default_root).unwrap_or_else(|| default_root.to_path_buf());
|
||||||
Self::new(root)
|
Self::new(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,6 +233,13 @@ impl WorkspaceLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_memory_marker_root(default_root: &Path) -> Option<PathBuf> {
|
||||||
|
default_root
|
||||||
|
.ancestors()
|
||||||
|
.find(|ancestor| ancestor.join(YOI_DIR).join(MEMORY_DIR).is_dir())
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
}
|
||||||
|
|
||||||
fn classify_kinded_md(
|
fn classify_kinded_md(
|
||||||
rel: &Path,
|
rel: &Path,
|
||||||
kind: RecordKind,
|
kind: RecordKind,
|
||||||
|
|
@ -257,6 +272,7 @@ fn classify_kinded_md(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn layout() -> WorkspaceLayout {
|
fn layout() -> WorkspaceLayout {
|
||||||
WorkspaceLayout::new(PathBuf::from("/ws"))
|
WorkspaceLayout::new(PathBuf::from("/ws"))
|
||||||
|
|
@ -379,9 +395,45 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_falls_back_to_default_when_workspace_root_missing() {
|
fn resolve_selects_nearest_ancestor_memory_marker_when_workspace_root_missing() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let workspace = tmp.path().join("workspace");
|
||||||
|
let child = workspace.join(".worktree/child");
|
||||||
|
std::fs::create_dir_all(workspace.join(".yoi/memory")).unwrap();
|
||||||
|
std::fs::create_dir_all(&child).unwrap();
|
||||||
|
|
||||||
let cfg = manifest::MemoryConfig::default();
|
let cfg = manifest::MemoryConfig::default();
|
||||||
let layout = WorkspaceLayout::resolve(&cfg, Path::new("/fallback"));
|
let layout = WorkspaceLayout::resolve(&cfg, &child);
|
||||||
assert_eq!(layout.root(), Path::new("/fallback"));
|
assert_eq!(layout.root(), workspace.as_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_ignores_child_project_records_without_memory_marker() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let workspace = tmp.path().join("workspace");
|
||||||
|
let child = workspace.join(".worktree/child");
|
||||||
|
std::fs::create_dir_all(workspace.join(".yoi/memory")).unwrap();
|
||||||
|
std::fs::create_dir_all(child.join(".yoi/tickets")).unwrap();
|
||||||
|
std::fs::create_dir_all(child.join(".yoi/workflow")).unwrap();
|
||||||
|
|
||||||
|
let cfg = manifest::MemoryConfig::default();
|
||||||
|
let layout = WorkspaceLayout::resolve(&cfg, &child);
|
||||||
|
assert_eq!(layout.root(), workspace.as_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn yoi_project_records_alone_do_not_define_memory_marker_root() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let workspace = tmp.path().join("workspace");
|
||||||
|
let child = workspace.join("child");
|
||||||
|
std::fs::create_dir_all(workspace.join(".yoi/tickets")).unwrap();
|
||||||
|
std::fs::create_dir_all(workspace.join(".yoi/workflow")).unwrap();
|
||||||
|
std::fs::create_dir_all(&child).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(find_memory_marker_root(&child), None);
|
||||||
|
|
||||||
|
let cfg = manifest::MemoryConfig::default();
|
||||||
|
let layout = WorkspaceLayout::resolve(&cfg, &child);
|
||||||
|
assert_eq!(layout.root(), child.as_path());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user