From 30b305bc22006c08c7ea2f6a7bc3b267dfe59f94 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Thu, 30 Jul 2026 10:05:00 +0200 Subject: [PATCH] fix: better validate messages --- src/dev_brain.rs | 51 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/dev_brain.rs b/src/dev_brain.rs index 2cce9f3..f313325 100644 --- a/src/dev_brain.rs +++ b/src/dev_brain.rs @@ -32,11 +32,11 @@ const TOPIC_DIRS: [&str; 7] = [ static VAULT_LOCK: RwLock<()> = RwLock::new(()); pub(crate) const PROMPT: &str = r#"# Dev Brain -Dev Brain is a managed Obsidian wiki, not a project directory or generic memory. Call dev_brain_info to get both its real folder and the separate registered project folders. Use the Dev Brain folder only for wiki maintenance; use a registered project folder for cited source files and Git commands. Do not invent a .brain path or use bash for wiki maintenance. Read schema.md before maintaining pages and append material changes to log.md. Call dev_brain_validate after edits; it rebuilds index.md and skills.md and reports broken links as repairable warnings without discarding content. Fix warnings with ordinary edits or by creating the missing page. The system prompt lists verified skills by name, description, and Markdown path. When a task matches a skill, read that complete skill file before acting and follow its instructions."#; +Dev Brain is a managed Obsidian wiki, not a project directory or generic memory. Call dev_brain_info to get both its real folder and the separate registered project folders. Use the Dev Brain folder only for wiki maintenance; use a registered project folder for cited source files and Git commands. Do not invent a .brain path or use bash for wiki maintenance. Read schema.md before maintaining pages and append material changes to log.md. Call dev_brain_validate after edits; it rebuilds index.md and skills.md and reports broken links as repairable warnings without discarding content. Validation is semantic freshness work, not revision bookkeeping: for each reported drifted source, re-read that file, check whether its changes alter the page's documented findings, update the page when needed, then update only that source record to the newest commit containing the current file content. For a large revision-backed source, use `git diff -- path` to focus on what changed before reading the necessary current context. Never copy the repository's overall HEAD into every source revision. Repeat for every drifted source, then call dev_brain_validate again. Fix warnings with ordinary edits or by creating the missing page. The system prompt lists verified skills by name, description, and Markdown path. When a task matches a skill, read that complete skill file before acting and follow its instructions."#; pub(crate) const TOOL_SCHEMAS: &str = r#"{"type":"function","function":{"name":"dev_brain_info","description":"Return the separate Dev Brain wiki folder and registered project source folders. Use ordinary file tools on the returned paths.","parameters":{"type":"object","properties":{}}}} {"type":"function","function":{"name":"dev_brain_search","description":"Search the validated Dev Brain index with freshness and project evidence. Use ordinary search for literal or regex file search.","parameters":{"type":"object","properties":{"query":{"type":"string"},"limit":{"type":"number"},"authoritative":{"type":"boolean"}},"required":["query"]}}} -{"type":"function","function":{"name":"dev_brain_validate","description":"Validate managed pages after ordinary file edits, deterministically rebuild index.md and skills.md, refresh search, and report repairable link warnings.","parameters":{"type":"object","properties":{}}}}"#; +{"type":"function","function":{"name":"dev_brain_validate","description":"Validate managed pages after ordinary file edits, report every drifted project source for semantic reinspection and per-source revision updates, deterministically rebuild index.md and skills.md, refresh search, and report repairable link warnings.","parameters":{"type":"object","properties":{}}}}"#; const DEFAULT_PURPOSE: &str = "# Dev Brain purpose\n\n\ Dev Brain compiles durable, source-backed knowledge from the registered projects.\n\n\ @@ -81,7 +81,7 @@ sources: --- ``` -`project` names a registered project, not this Dev Brain vault; `dev_brain_info` lists the exact registered names and folders. Each source `path` is relative to that registered project's folder; never resolve it inside the vault. Cite only files that support the page's claims, not every dirty file in the project. Each source has exactly one evidence version: `revision` or a lowercase SHA-256 `hash`. A revision is a commit whose version of that source file exactly matches the current clean file, preferably the latest commit returned by `git log -1 --format=%H -- path`; validation compares that file at the cited revision, not the repository's current HEAD. Use a hash when that specific file differs from HEAD, is untracked, or its registered project is not Git. +`project` names a registered project, not this Dev Brain vault; `dev_brain_info` lists the exact registered names and folders. Each source `path` is relative to that registered project's folder; never resolve it inside the vault. Cite only files that support the page's claims, not every dirty file in the project. Each source has exactly one evidence version: `revision` or a lowercase SHA-256 `hash`. A revision is a commit whose version of that source file exactly matches the current clean file, preferably the latest commit returned by `git log -1 --format=%H -- path`; validation compares that file at the cited revision, not the repository's current HEAD. Every source therefore has its own revision; never stamp all sources with the repository's overall HEAD. Use a hash when that specific file differs from HEAD, is untracked, or its registered project is not Git. ## Skills @@ -113,7 +113,7 @@ Start with `index.md` or ranked search, then follow links and backlinks. A `veri ## Refresh and semantic validation -When evidence changes, re-read the affected code, documentation, and tests. Update, split, merge, or retire dependent pages, keep `index.md` exact, and append a material update entry to `log.md` with source revisions. Only mark a page `verified` after its claims have been checked against its current evidence. Unsupported conclusions stay `needs-review`. +When validation reports drifted sources, handle each listed file independently: re-read it, compare its current behavior with the page's documented findings, update, split, merge, or retire affected knowledge, then update only that source's revision or hash. For a large revision-backed file, `git diff -- path` shows the change from the recorded evidence to the current working-tree version; use it to focus the review, then read enough current context to validate the affected findings. Repeat for every listed file and re-run validation. Keep `index.md` exact, and append a material update entry to `log.md` with source revisions. Only mark a page `verified` after its claims have been checked against its current evidence. Unsupported conclusions stay `needs-review`. ## Publication @@ -949,19 +949,24 @@ fn load_and_validate_pages( frontmatter.project )); } - let fresh = frontmatter + let drifted = frontmatter .sources .iter() - .map(|source| validate_source(source, projects)) + .map(|source| { + validate_source(source, projects) + .map(|fresh| (!fresh).then(|| format!("{}:{}", source.project, source.path))) + }) .collect::, _>>()? .into_iter() - .all(|fresh| fresh); - if publishing && frontmatter.status == "verified" && !fresh { + .flatten() + .collect::>(); + if publishing && frontmatter.status == "verified" && !drifted.is_empty() { return Err(format!( - "{relative} cannot be published as verified because its evidence has drifted." + "{relative} cannot be published as verified because its evidence has drifted:\n- {}\nRe-read each listed source and check whether its changes alter the page's documented findings. For a large revision-backed source, use `git diff -- path` to focus on what changed, then inspect the necessary current context. Update the knowledge when needed, then update only that source's revision to its newest commit containing the current file content (not the repository's overall HEAD), or its hash when required. Re-run dev_brain_validate after every listed source is current.", + drifted.join("\n- ") )); } - let status = if frontmatter.status == "verified" && !fresh { + let status = if frontmatter.status == "verified" && !drifted.is_empty() { "stale".into() } else { frontmatter.status @@ -1907,6 +1912,32 @@ mod tests { ); } + #[test] + fn validation_names_every_drifted_source_and_requires_semantic_refresh() { + let fixture = Fixture::new(); + fs::write(fixture.project.join("second.rs"), "second\n").unwrap(); + let source_hash = source_hash(&fixture); + let second_hash = hash_file(&fixture.project.join("second.rs")).unwrap(); + let topic = fixture + .topic("verified", &source_hash, "Source-backed findings.") + .replace( + &format!(" hash: {source_hash}\n"), + &format!( + " hash: {source_hash}\n - project: Fixture\n path: second.rs\n hash: {second_hash}\n" + ), + ); + let mut brain = fixture.brain(); + write_topic(&fixture, &mut brain, &topic).unwrap(); + + fs::write(fixture.project.join("source.rs"), "changed\n").unwrap(); + fs::write(fixture.project.join("second.rs"), "also changed\n").unwrap(); + let error = brain.validate().unwrap_err(); + assert!(error.contains("- Fixture:source.rs\n- Fixture:second.rs")); + assert!(error.contains("check whether its changes alter the page's documented findings")); + assert!(error.contains("git diff -- path")); + assert!(error.contains("not the repository's overall HEAD")); + } + #[test] fn source_versions_follow_that_file_instead_of_the_whole_worktree() { let fixture = Fixture::new();