diff --git a/src/dev_brain.rs b/src/dev_brain.rs index f313325..ead205f 100644 --- a/src/dev_brain.rs +++ b/src/dev_brain.rs @@ -32,7 +32,7 @@ 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. 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."#; +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 that changed that file. For a large revision-backed source, use `git diff -- path` to focus on what changed before reading the necessary current context. Also inspect the commits affecting the file since its recorded revision. If code disappeared, do not assume its behavior was deleted: inspect the full change commits and search the current project, callers, and tests for a rename, replacement, or move to another file; update the page's source list when evidence moved. 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"]}}} @@ -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. 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. +`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 the newest commit that changed that source file, returned by `git log -1 --format=%H -- path`; validation requires that per-file revision and compares its file content with the current clean file. 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 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`. +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. Inspect the commits affecting that path since the recorded revision as well. When code was removed, inspect the full commits and search the current project, callers, and tests before concluding the behavior disappeared: it may have been renamed, replaced, or moved into another file that should replace or join the page's cited sources. 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 @@ -962,7 +962,7 @@ fn load_and_validate_pages( .collect::>(); if publishing && frontmatter.status == "verified" && !drifted.is_empty() { return Err(format!( - "{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.", + "{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 and the commits affecting that path since the recorded revision. If code disappeared, inspect the full change commits and search the current project, callers, and tests for a rename, replacement, or move to another file before concluding the behavior was removed; update the cited source list when evidence moved. Update the knowledge when needed, then set only that source's revision to `git log -1 --format=%H -- path` (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- ") )); } @@ -1281,10 +1281,11 @@ fn git_revision_matches_source( let project = root.canonicalize().ok()?; let path = project.strip_prefix(workdir).ok()?.join(relative); let commit = repository - .revparse_single(revision) - .ok()? - .peel_to_commit() + .find_commit(latest_source_revision(&repository, &path)?) .ok()?; + if !commit.id().to_string().starts_with(revision) { + return Some(false); + } let entry = commit.tree().ok()?.get_path(&path).ok()?; let blob = repository.find_blob(entry.id()).ok()?; Some(fs::read(source).ok()?.as_slice() == blob.content()) @@ -1294,6 +1295,38 @@ fn git_revision_matches_source( matches } +fn latest_source_revision(repository: &git2::Repository, path: &Path) -> Option { + let mut revisions = repository.revwalk().ok()?; + revisions.push_head().ok()?; + revisions + .set_sorting(git2::Sort::TOPOLOGICAL | git2::Sort::TIME) + .ok()?; + for revision in revisions.flatten() { + let commit = repository.find_commit(revision).ok()?; + let current = commit + .tree() + .ok()? + .get_path(path) + .ok() + .map(|entry| entry.id()); + let changed = if commit.parent_count() == 0 { + current.is_some() + } else { + commit.parents().all(|parent| { + parent + .tree() + .ok() + .and_then(|tree| tree.get_path(path).ok().map(|entry| entry.id())) + != current + }) + }; + if changed { + return Some(revision); + } + } + None +} + fn collect_managed_topics( root: &Path, directory: &Path, @@ -1935,6 +1968,7 @@ mod tests { 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("search the current project, callers, and tests")); assert!(error.contains("not the repository's overall HEAD")); } @@ -1991,7 +2025,7 @@ mod tests { index.write().unwrap(); let tree = repository.find_tree(tree_id).unwrap(); let parent = repository.head().unwrap().peel_to_commit().unwrap(); - repository + let unrelated_revision = repository .commit( Some("HEAD"), &signature, @@ -2000,10 +2034,17 @@ mod tests { &tree, &[&parent], ) - .unwrap(); + .unwrap() + .to_string(); drop(parent); drop(tree); drop(repository); + assert!(!git_revision_matches_source( + &fixture.project, + Path::new("source.rs"), + &fixture.project.join("source.rs"), + &unrelated_revision + )); assert!(brain.current_fingerprint().unwrap() == fingerprint); brain.validate().unwrap(); assert!(