Scan the vault working copy for changed files. Walks `.vault/` and compares each markdown file against its stored entry in redb. Returns a list of paths that are new or modified. Files are identified as changed if: - They don't exist in redb (new file) - Their body hash or user frontmatter differs from the stored entry Ignores non-`.md` files and `_manifest.json`.
(&self)
| 552 | /// |
| 553 | /// Ignores non-`.md` files and `_manifest.json`. |
| 554 | pub fn vault_scan_working_copy(&self) -> Result<Vec<VaultFileChange>, RepositoryError> { |
| 555 | let vault_dir = self.vault_dir(); |
| 556 | if !vault_dir.exists() { |
| 557 | return Ok(Vec::new()); |
| 558 | } |
| 559 | |
| 560 | let mut changes = Vec::new(); |
| 561 | |
| 562 | // Walk the vault directory |
| 563 | for entry in walkdir::WalkDir::new(&vault_dir) |
| 564 | .into_iter() |
| 565 | .filter_map(|e| e.ok()) |
| 566 | { |
| 567 | let path = entry.path(); |
| 568 | |
| 569 | // Skip directories |
| 570 | if path.is_dir() { |
| 571 | continue; |
| 572 | } |
| 573 | |
| 574 | // Skip non-markdown files |
| 575 | let ext = path.extension().and_then(|e| e.to_str()).unwrap_or(""); |
| 576 | if ext != "md" { |
| 577 | continue; |
| 578 | } |
| 579 | |
| 580 | // Skip manifest |
| 581 | if path.file_name().and_then(|n| n.to_str()) == Some("_manifest.json") { |
| 582 | continue; |
| 583 | } |
| 584 | |
| 585 | // Get vault-relative path |
| 586 | let rel_path = |
| 587 | path.strip_prefix(&vault_dir) |
| 588 | .map_err(|_| RepositoryError::InvalidOperation { |
| 589 | message: format!("path not under vault: {}", path.display()), |
| 590 | })?; |
| 591 | let rel_path_str = rel_path.to_string_lossy().to_string(); |
| 592 | // Normalize to forward slashes (for Windows compat) |
| 593 | let rel_path_str = rel_path_str.replace('\\', "/"); |
| 594 | |
| 595 | // Read file content |
| 596 | let file_content = std::fs::read_to_string(path)?; |
| 597 | |
| 598 | // Parse into frontmatter + body |
| 599 | let (frontmatter_json, body) = parse_markdown_frontmatter(&file_content); |
| 600 | |
| 601 | // Body hashes remain useful for content/embedding caches, but |
| 602 | // frontmatter is also knowledge: status, labels, identity, and |
| 603 | // descriptions affect retrieval and KG extraction. |
| 604 | let body_bytes = body.as_bytes(); |
| 605 | let new_hash = Hash::of(body_bytes); |
| 606 | |
| 607 | // Check if this is new or changed |
| 608 | let existing = self.vault_retrieve(&rel_path_str)?; |
| 609 | let change_type = match &existing { |
| 610 | Some(entry) |
| 611 | if Hash::from_bytes(entry.content_hash) == new_hash |