memoryが.insomnia配下ではなくworkspace root直下を想定していた問題の修正

This commit is contained in:
Keisuke Hirata 2026-04-28 11:53:08 +09:00
parent 341bd71dc5
commit 46e1b92ade
11 changed files with 87 additions and 70 deletions

View File

@ -322,7 +322,7 @@ mod tests {
#[test] #[test]
fn workflow_write_rejected() { fn workflow_write_rejected() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("memory/workflow/wf.md"); let path = dir.path().join(".insomnia/memory/workflow/wf.md");
let content = format!( let content = format!(
"---\nupdated_at: {now}\ndescription: x\nauto_invoke: false\nuser_invocable: true\n---\nbody", "---\nupdated_at: {now}\ndescription: x\nauto_invoke: false\nuser_invocable: true\n---\nbody",
now = iso_now() now = iso_now()
@ -352,7 +352,7 @@ mod tests {
#[test] #[test]
fn decision_with_unknown_replaced_by_errors() { fn decision_with_unknown_replaced_by_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: ghost\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: ghost\n---\nbody\n",
now = iso_now() now = iso_now()
@ -369,7 +369,7 @@ mod tests {
#[test] #[test]
fn decision_replaced_by_self_errors() { fn decision_replaced_by_self_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: foo\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: foo\n---\nbody\n",
now = iso_now() now = iso_now()
@ -387,7 +387,7 @@ mod tests {
fn decision_replaced_by_existing_ok() { fn decision_replaced_by_existing_ok() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
// Pre-create the target. // Pre-create the target.
let target = dir.path().join("memory/decisions/bar.md"); let target = dir.path().join(".insomnia/memory/decisions/bar.md");
write( write(
&target, &target,
&format!( &format!(
@ -395,7 +395,7 @@ mod tests {
now = iso_now() now = iso_now()
), ),
); );
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: bar\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: bar\n---\nbody\n",
now = iso_now() now = iso_now()
@ -407,7 +407,7 @@ mod tests {
#[test] #[test]
fn missing_required_field_errors() { fn missing_required_field_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
// Missing `status`. // Missing `status`.
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\n---\nbody\n",
@ -423,7 +423,7 @@ mod tests {
#[test] #[test]
fn knowledge_long_description_with_model_invokation_errors() { fn knowledge_long_description_with_model_invokation_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("knowledge/foo.md"); let path = dir.path().join(".insomnia/knowledge/foo.md");
let big_desc = "x".repeat(2000); let big_desc = "x".repeat(2000);
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: true\nuser_invocable: true\nlast_sources: []\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: true\nuser_invocable: true\nlast_sources: []\n---\nbody\n",
@ -441,7 +441,7 @@ mod tests {
#[test] #[test]
fn knowledge_long_description_without_model_invokation_ok() { fn knowledge_long_description_without_model_invokation_ok() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("knowledge/foo.md"); let path = dir.path().join(".insomnia/knowledge/foo.md");
let big_desc = "x".repeat(2000); let big_desc = "x".repeat(2000);
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\nbody\n", "---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\nbody\n",
@ -454,7 +454,7 @@ mod tests {
#[test] #[test]
fn summary_path_accepted() { fn summary_path_accepted() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("memory/summary.md"); let path = dir.path().join(".insomnia/memory/summary.md");
let content = format!( let content = format!(
"---\nupdated_at: {now}\n---\nsummary body\n", "---\nupdated_at: {now}\n---\nsummary body\n",
now = iso_now() now = iso_now()
@ -466,7 +466,7 @@ mod tests {
#[test] #[test]
fn create_when_existing_errors() { fn create_when_existing_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
write( write(
&path, &path,
&format!( &format!(
@ -491,7 +491,7 @@ mod tests {
fn workflow_lint_accepts_valid_record() { fn workflow_lint_accepts_valid_record() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
// Place a Knowledge record that the workflow will reference. // Place a Knowledge record that the workflow will reference.
let kn = dir.path().join("knowledge/foo.md"); let kn = dir.path().join(".insomnia/knowledge/foo.md");
write( write(
&kn, &kn,
&format!( &format!(
@ -548,14 +548,14 @@ mod tests {
// `db-pol` (1 deletion), `db-pools` (1 insertion). // `db-pol` (1 deletion), `db-pools` (1 insertion).
for slug in ["db-pol", "db-pools"] { for slug in ["db-pol", "db-pools"] {
write( write(
&dir.path().join(format!("memory/decisions/{slug}.md")), &dir.path().join(format!(".insomnia/memory/decisions/{slug}.md")),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
n = iso_now() n = iso_now()
), ),
); );
} }
let path = dir.path().join("memory/decisions/db-pool.md"); let path = dir.path().join(".insomnia/memory/decisions/db-pool.md");
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody\n",
n = iso_now() n = iso_now()
@ -577,14 +577,14 @@ mod tests {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
for slug in ["alpha", "bravo"] { for slug in ["alpha", "bravo"] {
write( write(
&dir.path().join(format!("memory/decisions/{slug}.md")), &dir.path().join(format!(".insomnia/memory/decisions/{slug}.md")),
&format!( &format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
n = iso_now() n = iso_now()
), ),
); );
} }
let path = dir.path().join("memory/decisions/charlie.md"); let path = dir.path().join(".insomnia/memory/decisions/charlie.md");
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
n = iso_now() n = iso_now()
@ -603,7 +603,7 @@ mod tests {
#[test] #[test]
fn body_size_limit_errors() { fn body_size_limit_errors() {
let (dir, linter) = workspace(); let (dir, linter) = workspace();
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
let big_body = "x".repeat(8001); let big_body = "x".repeat(8001);
let content = format!( let content = format!(
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: open\n---\n{body}", "---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: open\n---\n{body}",

View File

@ -84,7 +84,7 @@ mod tests {
model_invokation: bool, model_invokation: bool,
body: &str, body: &str,
) { ) {
let path = dir.join("knowledge").join(format!("{slug}.md")); let path = dir.join(".insomnia/knowledge").join(format!("{slug}.md"));
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nkind: policy\ndescription: \"{description}\"\nmodel_invokation: {flag}\nuser_invocable: true\nlast_sources: []\n---\n{body}", "---\ncreated_at: {n}\nupdated_at: {n}\nkind: policy\ndescription: \"{description}\"\nmodel_invokation: {flag}\nuser_invocable: true\nlast_sources: []\n---\n{body}",
n = now(), n = now(),
@ -95,7 +95,7 @@ mod tests {
fn setup() -> (TempDir, WorkspaceLayout) { fn setup() -> (TempDir, WorkspaceLayout) {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
std::fs::create_dir_all(dir.path().join("knowledge")).unwrap(); std::fs::create_dir_all(dir.path().join(".insomnia/knowledge")).unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
(dir, layout) (dir, layout)
} }
@ -141,7 +141,7 @@ mod tests {
write_knowledge(dir.path(), "good", "ok", true, ""); write_knowledge(dir.path(), "good", "ok", true, "");
// Garbage in frontmatter — must be skipped, not panic. // Garbage in frontmatter — must be skipped, not panic.
std::fs::write( std::fs::write(
dir.path().join("knowledge/bad.md"), dir.path().join(".insomnia/knowledge/bad.md"),
"---\nthis is not yaml: : :\n---\nbody\n", "---\nthis is not yaml: : :\n---\nbody\n",
) )
.unwrap(); .unwrap();
@ -155,7 +155,7 @@ mod tests {
fn non_md_files_ignored() { fn non_md_files_ignored() {
let (dir, layout) = setup(); let (dir, layout) = setup();
write_knowledge(dir.path(), "good", "ok", true, ""); write_knowledge(dir.path(), "good", "ok", true, "");
std::fs::write(dir.path().join("knowledge/note.txt"), "not markdown\n").unwrap(); std::fs::write(dir.path().join(".insomnia/knowledge/note.txt"), "not markdown\n").unwrap();
let got = collect_resident_knowledge(&layout); let got = collect_resident_knowledge(&layout);
assert_eq!(got.len(), 1); assert_eq!(got.len(), 1);

View File

@ -41,9 +41,9 @@ mod tests {
let layout = WorkspaceLayout::new(PathBuf::from("/ws")); let layout = WorkspaceLayout::new(PathBuf::from("/ws"));
let rules = deny_write_rules(&layout); let rules = deny_write_rules(&layout);
assert_eq!(rules.len(), 2); assert_eq!(rules.len(), 2);
assert_eq!(rules[0].target, PathBuf::from("/ws/memory")); assert_eq!(rules[0].target, PathBuf::from("/ws/.insomnia/memory"));
assert_eq!(rules[0].permission, Permission::Write); assert_eq!(rules[0].permission, Permission::Write);
assert!(rules[0].recursive); assert!(rules[0].recursive);
assert_eq!(rules[1].target, PathBuf::from("/ws/knowledge")); assert_eq!(rules[1].target, PathBuf::from("/ws/.insomnia/knowledge"));
} }
} }

View File

@ -174,7 +174,7 @@ mod tests {
fn setup() -> (TempDir, WorkspaceLayout, PathBuf) { fn setup() -> (TempDir, WorkspaceLayout, PathBuf) {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let initial = format!( let initial = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody body\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody body\n",

View File

@ -6,11 +6,11 @@
//! omitted, returns one entry per file (no excerpt) so the agent can //! omitted, returns one entry per file (no excerpt) so the agent can
//! enumerate what records exist without knowing what's inside them. //! enumerate what records exist without knowing what's inside them.
//! //!
//! - `MemoryQuery` walks `memory/summary.md`, `memory/decisions/`, //! - `MemoryQuery` walks `.insomnia/memory/{summary.md,decisions/,
//! `memory/requests/`. `memory/workflow/` and `memory/_staging/` //! requests/}`. `.insomnia/memory/workflow/` and
//! are excluded by construction. //! `.insomnia/memory/_staging/` are excluded by construction.
//! - `KnowledgeQuery` walks `knowledge/*.md` and supports a `kind` //! - `KnowledgeQuery` walks `.insomnia/knowledge/*.md` and supports a
//! filter against the Knowledge frontmatter's `kind` field. //! `kind` filter against the Knowledge frontmatter's `kind` field.
//! //!
//! No derived index — the file tree is the source of truth and is //! No derived index — the file tree is the source of truth and is
//! re-scanned per call. 出現順: within a file by line order, across //! re-scanned per call. 出現順: within a file by line order, across
@ -441,16 +441,16 @@ mod tests {
fn setup() -> (TempDir, WorkspaceLayout) { fn setup() -> (TempDir, WorkspaceLayout) {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let layout = WorkspaceLayout::new(dir.path().to_path_buf()); let layout = WorkspaceLayout::new(dir.path().to_path_buf());
std::fs::create_dir_all(dir.path().join("memory/decisions")).unwrap(); std::fs::create_dir_all(dir.path().join(".insomnia/memory/decisions")).unwrap();
std::fs::create_dir_all(dir.path().join("memory/requests")).unwrap(); std::fs::create_dir_all(dir.path().join(".insomnia/memory/requests")).unwrap();
std::fs::create_dir_all(dir.path().join("memory/workflow")).unwrap(); std::fs::create_dir_all(dir.path().join(".insomnia/memory/workflow")).unwrap();
std::fs::create_dir_all(dir.path().join("memory/_staging")).unwrap(); std::fs::create_dir_all(dir.path().join(".insomnia/memory/_staging")).unwrap();
std::fs::create_dir_all(dir.path().join("knowledge")).unwrap(); std::fs::create_dir_all(dir.path().join(".insomnia/knowledge")).unwrap();
(dir, layout) (dir, layout)
} }
fn write_decision(dir: &Path, slug: &str, body: &str) { fn write_decision(dir: &Path, slug: &str, body: &str) {
let path = dir.join("memory/decisions").join(format!("{slug}.md")); let path = dir.join(".insomnia/memory/decisions").join(format!("{slug}.md"));
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n{body}", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n{body}",
n = now() n = now()
@ -459,7 +459,7 @@ mod tests {
} }
fn write_knowledge(dir: &Path, slug: &str, kind: &str, description: &str, body: &str) { fn write_knowledge(dir: &Path, slug: &str, kind: &str, description: &str, body: &str) {
let path = dir.join("knowledge").join(format!("{slug}.md")); let path = dir.join(".insomnia/knowledge").join(format!("{slug}.md"));
let content = format!( let content = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nkind: {kind}\ndescription: \"{description}\"\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\n{body}", "---\ncreated_at: {n}\nupdated_at: {n}\nkind: {kind}\ndescription: \"{description}\"\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\n{body}",
n = now() n = now()
@ -514,7 +514,7 @@ mod tests {
let (dir, layout) = setup(); let (dir, layout) = setup();
write_decision(dir.path(), "alpha", "body\n"); write_decision(dir.path(), "alpha", "body\n");
write_decision(dir.path(), "beta", "body\n"); write_decision(dir.path(), "beta", "body\n");
let summary_path = dir.path().join("memory/summary.md"); let summary_path = dir.path().join(".insomnia/memory/summary.md");
std::fs::write( std::fs::write(
&summary_path, &summary_path,
format!("---\nupdated_at: {n}\n---\nhello\n", n = now()), format!("---\nupdated_at: {n}\n---\nhello\n", n = now()),
@ -534,7 +534,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn memory_query_finds_summary() { async fn memory_query_finds_summary() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let summary_path = dir.path().join("memory/summary.md"); let summary_path = dir.path().join(".insomnia/memory/summary.md");
std::fs::write( std::fs::write(
&summary_path, &summary_path,
format!("---\nupdated_at: {n}\n---\nthe needle is here\n", n = now()), format!("---\nupdated_at: {n}\n---\nthe needle is here\n", n = now()),
@ -552,9 +552,9 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn memory_query_excludes_workflow_and_staging() { async fn memory_query_excludes_workflow_and_staging() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let wf = dir.path().join("memory/workflow/wf.md"); let wf = dir.path().join(".insomnia/memory/workflow/wf.md");
std::fs::write(&wf, "needle in workflow\n").unwrap(); std::fs::write(&wf, "needle in workflow\n").unwrap();
let stg = dir.path().join("memory/_staging/abc.json"); let stg = dir.path().join(".insomnia/memory/_staging/abc.json");
std::fs::write(&stg, "needle in staging\n").unwrap(); std::fs::write(&stg, "needle in staging\n").unwrap();
let (_, tool) = memory_query_tool(layout, QueryConfig::default())(); let (_, tool) = memory_query_tool(layout, QueryConfig::default())();

View File

@ -144,7 +144,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn read_decision_by_slug() { async fn read_decision_by_slug() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, "alpha\nbeta\n").unwrap(); std::fs::write(&path, "alpha\nbeta\n").unwrap();
@ -159,7 +159,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn read_summary_without_slug() { async fn read_summary_without_slug() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join("memory/summary.md"); let path = dir.path().join(".insomnia/memory/summary.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, "summary body\n").unwrap(); std::fs::write(&path, "summary body\n").unwrap();
@ -199,7 +199,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn knowledge_path_resolution() { async fn knowledge_path_resolution() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join("knowledge/policy.md"); let path = dir.path().join(".insomnia/knowledge/policy.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, "k\n").unwrap(); std::fs::write(&path, "k\n").unwrap();

View File

@ -149,7 +149,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn write_creates_summary() { async fn write_creates_summary() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join("memory/summary.md"); let path = dir.path().join(".insomnia/memory/summary.md");
let content = format!("---\nupdated_at: {n}\n---\nbody\n", n = now()); let content = format!("---\nupdated_at: {n}\n---\nbody\n", n = now());
let (meta, tool) = write_tool(layout)(); let (meta, tool) = write_tool(layout)();
@ -187,7 +187,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn write_update_existing() { async fn write_update_existing() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let initial = format!( let initial = format!(
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nold\n", "---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nold\n",
@ -220,7 +220,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn write_does_not_persist_on_lint_failure() { async fn write_does_not_persist_on_lint_failure() {
let (dir, layout) = setup(); let (dir, layout) = setup();
let path = dir.path().join("memory/decisions/foo.md"); let path = dir.path().join(".insomnia/memory/decisions/foo.md");
let bad = "no frontmatter at all"; let bad = "no frontmatter at all";
let (_, tool) = write_tool(layout)(); let (_, tool) = write_tool(layout)();
let inp = serde_json::json!({ let inp = serde_json::json!({

View File

@ -1,20 +1,28 @@
//! Workspace-level path layout for the memory subsystem. //! Workspace-level path layout for the memory subsystem.
//! //!
//! Resolves a workspace root into the concrete directories the linter //! `WorkspaceLayout` carries the workspace root (typically the Pod's
//! and tools operate on: //! pwd). All insomnia-managed content lives under the conventional
//! `<root>/.insomnia/` subdirectory — the same place that holds
//! `manifest.toml` and `prompts/`. The memory subsystem nests its
//! trees inside it:
//! //!
//! - `<root>/memory/summary.md` //! - `<root>/.insomnia/memory/summary.md`
//! - `<root>/memory/decisions/<slug>.md` //! - `<root>/.insomnia/memory/decisions/<slug>.md`
//! - `<root>/memory/requests/<slug>.md` //! - `<root>/.insomnia/memory/requests/<slug>.md`
//! - `<root>/memory/workflow/<slug>.md` //! - `<root>/.insomnia/memory/workflow/<slug>.md`
//! - `<root>/memory/_staging/<id>.json` //! - `<root>/.insomnia/memory/_staging/<id>.json`
//! - `<root>/knowledge/<slug>.md` //! - `<root>/.insomnia/knowledge/<slug>.md`
//!
//! Configuring `[memory]` with an empty body is therefore sufficient
//! for any workspace that already uses the `.insomnia/` convention; no
//! `workspace_root` override is needed.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::error::LintError; use crate::error::LintError;
use crate::slug::Slug; use crate::slug::Slug;
const INSOMNIA_DIR: &str = ".insomnia";
const MEMORY_DIR: &str = "memory"; const MEMORY_DIR: &str = "memory";
const KNOWLEDGE_DIR: &str = "knowledge"; const KNOWLEDGE_DIR: &str = "knowledge";
const SUMMARY_FILE: &str = "summary.md"; const SUMMARY_FILE: &str = "summary.md";
@ -81,12 +89,17 @@ impl WorkspaceLayout {
&self.root &self.root
} }
/// `<root>/.insomnia/`. The base of every other memory path.
pub fn insomnia_dir(&self) -> PathBuf {
self.root.join(INSOMNIA_DIR)
}
pub fn memory_dir(&self) -> PathBuf { pub fn memory_dir(&self) -> PathBuf {
self.root.join(MEMORY_DIR) self.insomnia_dir().join(MEMORY_DIR)
} }
pub fn knowledge_dir(&self) -> PathBuf { pub fn knowledge_dir(&self) -> PathBuf {
self.root.join(KNOWLEDGE_DIR) self.insomnia_dir().join(KNOWLEDGE_DIR)
} }
pub fn summary_path(&self) -> PathBuf { pub fn summary_path(&self) -> PathBuf {
@ -126,13 +139,14 @@ impl WorkspaceLayout {
} }
/// Classify a path under the memory tree. Returns `None` if the /// Classify a path under the memory tree. Returns `None` if the
/// path is not under `memory/` or `knowledge/` of this workspace, /// path is not under `.insomnia/memory/` or `.insomnia/knowledge/`
/// or if it lives in `_staging/` (which is opaque to the linter). /// of this workspace, or if it lives in `_staging/` (which is
/// opaque to the linter).
/// ///
/// On a conventional path that's *almost* a record but malformed /// On a conventional path that's *almost* a record but malformed
/// (e.g. `decisions/Foo.md` with an invalid slug), returns /// (e.g. `.insomnia/memory/decisions/Foo.md` with an invalid slug),
/// `Err(LintError::InvalidSlug | InvalidPath)` so the caller can /// returns `Err(LintError::InvalidSlug | InvalidPath)` so the caller
/// surface it as a write violation. /// can surface it as a write violation.
pub fn classify(&self, path: &Path) -> Result<Option<ClassifiedPath>, LintError> { pub fn classify(&self, path: &Path) -> Result<Option<ClassifiedPath>, LintError> {
let memory = self.memory_dir(); let memory = self.memory_dir();
let knowledge = self.knowledge_dir(); let knowledge = self.knowledge_dir();
@ -221,7 +235,7 @@ mod tests {
#[test] #[test]
fn classifies_summary() { fn classifies_summary() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/memory/summary.md")) .classify(&PathBuf::from("/ws/.insomnia/memory/summary.md"))
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(cp.kind, RecordKind::Summary); assert_eq!(cp.kind, RecordKind::Summary);
@ -231,7 +245,7 @@ mod tests {
#[test] #[test]
fn classifies_decision_with_slug() { fn classifies_decision_with_slug() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/memory/decisions/foo-bar.md")) .classify(&PathBuf::from("/ws/.insomnia/memory/decisions/foo-bar.md"))
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(cp.kind, RecordKind::Decision); assert_eq!(cp.kind, RecordKind::Decision);
@ -241,7 +255,7 @@ mod tests {
#[test] #[test]
fn classifies_knowledge() { fn classifies_knowledge() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/knowledge/x.md")) .classify(&PathBuf::from("/ws/.insomnia/knowledge/x.md"))
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(cp.kind, RecordKind::Knowledge); assert_eq!(cp.kind, RecordKind::Knowledge);
@ -250,7 +264,7 @@ mod tests {
#[test] #[test]
fn classifies_workflow() { fn classifies_workflow() {
let cp = layout() let cp = layout()
.classify(&PathBuf::from("/ws/memory/workflow/wf.md")) .classify(&PathBuf::from("/ws/.insomnia/memory/workflow/wf.md"))
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(cp.kind, RecordKind::Workflow); assert_eq!(cp.kind, RecordKind::Workflow);
@ -260,7 +274,7 @@ mod tests {
fn staging_returns_none() { fn staging_returns_none() {
assert!( assert!(
layout() layout()
.classify(&PathBuf::from("/ws/memory/_staging/abc.json")) .classify(&PathBuf::from("/ws/.insomnia/memory/_staging/abc.json"))
.unwrap() .unwrap()
.is_none() .is_none()
); );
@ -285,7 +299,7 @@ mod tests {
#[test] #[test]
fn invalid_slug_rejected() { fn invalid_slug_rejected() {
let err = layout() let err = layout()
.classify(&PathBuf::from("/ws/memory/decisions/Foo.md")) .classify(&PathBuf::from("/ws/.insomnia/memory/decisions/Foo.md"))
.unwrap_err(); .unwrap_err();
assert!(matches!(err, LintError::InvalidSlug(_))); assert!(matches!(err, LintError::InvalidSlug(_)));
} }
@ -293,7 +307,7 @@ mod tests {
#[test] #[test]
fn nested_under_record_dir_rejected() { fn nested_under_record_dir_rejected() {
let err = layout() let err = layout()
.classify(&PathBuf::from("/ws/memory/decisions/sub/foo.md")) .classify(&PathBuf::from("/ws/.insomnia/memory/decisions/sub/foo.md"))
.unwrap_err(); .unwrap_err();
assert!(matches!(err, LintError::InvalidPath(_))); assert!(matches!(err, LintError::InvalidPath(_)));
} }
@ -301,7 +315,7 @@ mod tests {
#[test] #[test]
fn unknown_top_level_dir_rejected() { fn unknown_top_level_dir_rejected() {
let err = layout() let err = layout()
.classify(&PathBuf::from("/ws/memory/something/foo.md")) .classify(&PathBuf::from("/ws/.insomnia/memory/something/foo.md"))
.unwrap_err(); .unwrap_err();
assert!(matches!(err, LintError::InvalidPath(_))); assert!(matches!(err, LintError::InvalidPath(_)));
} }

View File

@ -14,6 +14,8 @@ Workflow`/<slug>` で呼び出される制約付き作業フロー)は別 p
### 記録対象の 4 種 ### 記録対象の 4 種
本ドキュメント以下のパスはすべて **`<workspace_root>/.insomnia/`** からの相対表記。`.insomnia/` は manifest / prompts と同じく workspace に紐付く insomnia コンテンツのルートで、memory もこの規約に従う。`workspace_root` 既定は Pod の pwd。
| 種別 | パス | 備考 | | 種別 | パス | 備考 |
| ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------- | | ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------- |
| Always-on サマリ | `memory/summary.md` | 1-5k tokens 目安 | | Always-on サマリ | `memory/summary.md` | 1-5k tokens 目安 |

View File

@ -2,3 +2,6 @@
Prefer the most specific tool for the job. When reading files you already know the path of, use the file-read tool directly instead of searching. Prefer the most specific tool for the job. When reading files you already know the path of, use the file-read tool directly instead of searching.
When searching, use grep/glob primitives rather than shell pipelines. When searching, use grep/glob primitives rather than shell pipelines.
You can run multiple tools simultaneously by calling them within a single response.
It is recommended to run tools that handle asynchronous processing, such as queries and readings, in batches.

View File

@ -1,7 +1,5 @@
You are here as an agent of the "insomnia system". You are here as an agent of the "insomnia system".
Stay precise, edit code directly when asked, and avoid speculative refactoring. Explain what you changed in one short paragraph at the end of each turn.
{% include "common/workspace" %} {% include "common/workspace" %}
{% include "common/tool-usage" %} {% include "common/tool-usage" %}