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 content hash differs from the stored entry Ignores non-`.md` files and `_manifest.json`.
(&self)
| 445 | /// |
| 446 | /// Ignores non-`.md` files and `_manifest.json`. |
| 447 | pub fn vault_scan_working_copy(&self) -> Result<Vec<VaultFileChange>, RepositoryError> { |
| 448 | let vault_dir = self.vault_dir(); |
| 449 | if !vault_dir.exists() { |
| 450 | return Ok(Vec::new()); |
| 451 | } |
| 452 | |
| 453 | let mut changes = Vec::new(); |
| 454 | |
| 455 | // Walk the vault directory |
| 456 | for entry in walkdir::WalkDir::new(&vault_dir) |
| 457 | .into_iter() |
| 458 | .filter_map(|e| e.ok()) |
| 459 | { |
| 460 | let path = entry.path(); |
| 461 | |
| 462 | // Skip directories |
| 463 | if path.is_dir() { |
| 464 | continue; |
| 465 | } |
| 466 | |
| 467 | // Skip non-markdown files |
| 468 | let ext = path.extension().and_then(|e| e.to_str()).unwrap_or(""); |
| 469 | if ext != "md" { |
| 470 | continue; |
| 471 | } |
| 472 | |
| 473 | // Skip manifest |
| 474 | if path.file_name().and_then(|n| n.to_str()) == Some("_manifest.json") { |
| 475 | continue; |
| 476 | } |
| 477 | |
| 478 | // Get vault-relative path |
| 479 | let rel_path = |
| 480 | path.strip_prefix(&vault_dir) |
| 481 | .map_err(|_| RepositoryError::InvalidOperation { |
| 482 | message: format!("path not under vault: {}", path.display()), |
| 483 | })?; |
| 484 | let rel_path_str = rel_path.to_string_lossy().to_string(); |
| 485 | // Normalize to forward slashes (for Windows compat) |
| 486 | let rel_path_str = rel_path_str.replace('\\', "/"); |
| 487 | |
| 488 | // Read file content |
| 489 | let file_content = std::fs::read_to_string(path)?; |
| 490 | |
| 491 | // Parse into frontmatter + body |
| 492 | let (frontmatter_json, body) = parse_markdown_frontmatter(&file_content); |
| 493 | |
| 494 | // Compute hash of the body (not frontmatter) |
| 495 | let body_bytes = body.as_bytes(); |
| 496 | let new_hash = Hash::of(body_bytes); |
| 497 | |
| 498 | // Check if this is new or changed |
| 499 | let existing = self.vault_retrieve(&rel_path_str)?; |
| 500 | let change_type = match &existing { |
| 501 | Some(entry) if Hash::from_bytes(entry.content_hash) == new_hash => { |
| 502 | continue; // Unchanged, skip |
| 503 | } |
| 504 | Some(_) => VaultChangeType::Modified, |