Skip to content

[EPIC-3-1] Vault Directory Structure and Markdown Utilities (Rust) - #21

Merged
deserat merged 1 commit into
developfrom
feature/EPIC-3-1-vault-markdown-utilities
Jan 23, 2026
Merged

[EPIC-3-1] Vault Directory Structure and Markdown Utilities (Rust)#21
deserat merged 1 commit into
developfrom
feature/EPIC-3-1-vault-markdown-utilities

Conversation

@deserat

@deserat deserat commented Jan 23, 2026

Copy link
Copy Markdown
Owner

Summary

Implements complete vault filesystem utilities in Rust (sync-service) for markdown-based knowledge storage. This provides the foundation for the File Vault System (EPIC-3), enabling local markdown files with YAML frontmatter compatible with Obsidian and VSCode.

Related Issue

Closes #12 (EPIC-3-1)

Changes

New Modules (sync-service/src/vault/):

  • template.rs - ItemMetadata struct + markdown template generation
  • parser.rs - YAML frontmatter parsing using gray_matter crate
  • writer.rs - Atomic file operations using tempfile crate
  • hash.rs - SHA-256 content hashing for change detection
  • lock.rs - File locking with RAII pattern using fs2 crate

Dependencies Added:

  • gray_matter 0.2 - YAML frontmatter parsing
  • fs2 0.4 - Cross-platform file locking
  • uuid 1.0 - UUID generation
  • chrono 0.4 - Timestamps
  • tempfile 3.0 - Atomic writes

Documentation:

  • docs/vault-structure.md - Complete vault structure guide
  • docs/dev/plans/EPIC-3-1-vault-markdown-utilities.md - Implementation plan

Testing

  • Unit tests pass: 30 passed, 1 ignored (multi-process lock timeout)
  • Coverage >= 80%: 86.08% region coverage, 96.13% line coverage
  • Manual testing completed: Template generation, parsing, writing verified
  • All quality checks pass: cargo fmt, cargo clippy (no warnings)

Coverage by Module:

  • template.rs: 100%
  • parser.rs: 91.70%
  • writer.rs: 87.38%
  • hash.rs: 87.50%
  • lock.rs: 67.91% (lower due to ignored multi-process test)

Architecture Notes

Critical Design Principle: All vault filesystem operations are in Rust only. The Python api-service NEVER touches ~/noosphere-vault/ directly.

Key Features:

  • Reusable Library: Vault module can be used by both sync-service and cli
  • Atomic Operations: tempfile + rename prevents partial writes and race conditions
  • File Locking: fs2 advisory locks prevent concurrent writes between sync-service and cli
  • RAII Pattern: Automatic lock cleanup via Drop trait
  • Obsidian Compatible: Standard YAML frontmatter format
  • Unicode Support: Full UTF-8 handling throughout

Vault Structure:

~/noosphere-vault/
├── Inbox/              # Uncategorized items
├── People/             # Notes about individuals
├── Projects/           # Active and archived projects
├── Ideas/              # Creative and philosophical ideas
├── Admin/              # Tasks, meeting notes
└── .noosphere/         # Metadata (locks, cache)

Frontmatter Schema (ItemMetadata):

  • id: UUID (auto-generated)
  • title: String (required)
  • category: String (required)
  • subcategory: Option
  • tags: Vec
  • created: String (RFC3339)
  • modified: String (RFC3339)
  • state: String
  • confidence: Option

Verification Steps

cd sync-service

# Run tests
cargo test --package noosphere-sync --lib vault

# Check coverage
cargo llvm-cov --package noosphere-sync --lib

# Verify clippy
cargo clippy --package noosphere-sync -- -D warnings

# Format check
cargo fmt --check

Expected Results:

  • 30 tests pass, 1 ignored
  • Coverage >= 80%
  • No clippy warnings
  • Code formatted correctly

Checklist

  • Code follows sync-service conventions (Rust/async daemon)
  • sync-service/AGENTS.md reviewed for context
  • Documentation updated (docs/vault-structure.md)
  • No secrets or sensitive data committed
  • All tests pass with 86.08% coverage
  • Clippy passes with no warnings
  • Code formatted with cargo fmt
  • Library designed for reuse (sync-service + cli)

🤖 Generated with Claude Code

@github-actions github-actions Bot added service/sync-service File watching Rust service service/docs Documentation only labels Jan 23, 2026
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @deserat, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request establishes the foundational Rust-based utilities for the File Vault System (EPIC-3), enabling robust and compatible markdown-based knowledge storage. It introduces core modules for managing vault files, including templating, parsing, writing, hashing, and locking, ensuring data integrity and seamless integration with external markdown editors. The changes lay the groundwork for a reliable and extensible knowledge vault that can be interacted with programmatically and through third-party applications.

Highlights

  • New Rust Modules for Vault Operations: Introduced five new Rust modules (template.rs, parser.rs, writer.rs, hash.rs, lock.rs) within the sync-service/src/vault/ directory to handle core markdown file operations, metadata management, and concurrent access.
  • Dependency Integration: Added several key dependencies to Cargo.toml including gray_matter for YAML frontmatter parsing, fs2 for cross-platform file locking, uuid for unique ID generation, chrono for timestamps, and tempfile for atomic file writes.
  • Comprehensive Vault Documentation: A new docs/vault-structure.md file provides detailed documentation on the vault's directory structure, file naming conventions, YAML frontmatter specification, external tool compatibility (Obsidian, VSCode), and operational guidelines.
  • Atomic File Operations: Implemented atomic file writing using temporary files and atomic renames, preventing partial writes and ensuring data integrity during file modifications.
  • Robust File Locking Mechanism: Integrated a file locking system with an RAII pattern using the fs2 crate to manage concurrent access to vault files, preventing race conditions between the sync-service and CLI, and including a timeout mechanism.
  • Obsidian and VSCode Compatibility: Designed the markdown file structure and YAML frontmatter to be fully compatible with popular knowledge management tools like Obsidian and VSCode, facilitating external editing and seamless integration.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive set of Rust utilities for managing a markdown-based vault, which is a crucial foundation for the File Vault System. The changes are well-structured into modules for templating, parsing, writing, hashing, and file locking. The code is generally of high quality, with thorough documentation and extensive unit tests.

My review focuses on improving robustness, error handling, and ensuring the code is safe for use in an asynchronous environment. Key suggestions include:

  • Replacing unwrap() and expect() calls with proper Result propagation to prevent panics in library code.
  • Handling file system errors instead of ignoring them.
  • Addressing the use of blocking operations within an async context.
  • Enhancing validation logic for better data integrity.

Overall, this is an excellent contribution that lays a solid groundwork. The suggested changes will make the new vault utilities even more reliable and resilient.

Comment thread sync-service/src/vault/lock.rs Outdated
Comment on lines +57 to +70
pub fn new(file_path: &Path) -> Self {
let lock_dir = file_path.parent().unwrap().join(".noosphere/locks");
fs::create_dir_all(&lock_dir).ok();

let lock_file = lock_dir.join(format!(
"{}.lock",
file_path.file_name().unwrap().to_string_lossy()
));

Self {
lock_file,
_guard: None,
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The new function uses unwrap() and .ok(), which can either panic or silently ignore errors.

  1. file_path.parent().unwrap() and file_path.file_name().unwrap() will panic if the path is a root directory or ends in ...
  2. fs::create_dir_all(&lock_dir).ok() will silently ignore errors if the lock directory cannot be created (e.g., due to permissions), leading to failures later.

For a robust library function, it's better to return a Result and propagate all errors to the caller.

    pub fn new(file_path: &Path) -> Result<Self> {
        let parent_dir = file_path.parent().ok_or_else(|| {
            anyhow::anyhow!("File path '{}' has no parent directory", file_path.display())
        })?;
        let lock_dir = parent_dir.join(".noosphere/locks");
        fs::create_dir_all(&lock_dir)?;

        let file_name = file_path.file_name().ok_or_else(|| {
            anyhow::anyhow!("File path '{}' has no file name", file_path.display())
        })?;

        let lock_file = lock_dir.join(format!(
            "{}.lock",
            file_name.to_string_lossy()
        ));

        Ok(Self {
            lock_file,
            _guard: None,
        })
    }

Comment on lines +106 to +130
pub fn acquire(&mut self, timeout: Duration) -> Result<()> {
let start = SystemTime::now();

loop {
match OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&self.lock_file)
{
Ok(file) => {
// Acquire exclusive lock using fs2
file.lock_exclusive()?;
self._guard = Some(file);
return Ok(());
}
Err(_e) => {
if start.elapsed()? > timeout {
anyhow::bail!("Lock timeout: failed to acquire lock within {:?}", timeout);
}
std::thread::sleep(Duration::from_millis(100));
}
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The acquire function is synchronous and contains blocking I/O operations (OpenOptions::open, file.lock_exclusive()) and a blocking sleep (std::thread::sleep). When used within the sync-service async daemon, calling this function directly from an async task will block the worker thread, which can severely impact concurrency and responsiveness.

It's highly recommended to wrap calls to this function in tokio::task::spawn_blocking to offload the blocking work to a dedicated thread pool for blocking tasks.

Example usage from an async context:

let mut lock = FileLock::new(path)?; // Assuming new() returns Result
tokio::task::spawn_blocking(move || {
    lock.acquire(Duration::from_secs(5))
}).await??;

Comment on lines +77 to +102
pub fn create_item_template(
title: &str,
category: &str,
subcategory: Option<&str>,
tags: Vec<String>,
confidence: Option<f64>,
) -> String {
let now = Utc::now().to_rfc3339();
let metadata = ItemMetadata {
id: Uuid::new_v4(),
title: title.to_string(),
category: category.to_string(),
subcategory: subcategory.map(|s| s.to_string()),
tags,
created: now.clone(),
modified: now,
state: "uncategorized".to_string(),
confidence,
};

let yaml = serde_yaml::to_string(&metadata).expect("Failed to serialize metadata");
format!(
"---\n{}---\n\n# {}\n\n[Content goes here...]\n",
yaml, title
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The function create_item_template uses .expect() when serializing metadata to YAML, which will cause a panic if serialization fails. For a reusable library function, it's safer to return a Result and let the caller handle the error. This would also make it consistent with write_markdown_file in writer.rs, which correctly propagates serialization errors.

pub fn create_item_template(
    title: &str,
    category: &str,
    subcategory: Option<&str>,
    tags: Vec<String>,
    confidence: Option<f64>,
) -> anyhow::Result<String> {
    let now = Utc::now().to_rfc3339();
    let metadata = ItemMetadata {
        id: Uuid::new_v4(),
        title: title.to_string(),
        category: category.to_string(),
        subcategory: subcategory.map(|s| s.to_string()),
        tags,
        created: now.clone(),
        modified: now,
        state: "uncategorized".to_string(),
        confidence,
    };

    let yaml = serde_yaml::to_string(&metadata)
        .map_err(|e| anyhow::anyhow!("Failed to serialize metadata: {}", e))?;
    Ok(format!(
        "---\n{}---\n\n# {}\n\n[Content goes here...]\n",
        yaml, title
    ))
}

Comment thread docs/vault-structure.md
Comment on lines +326 to +338
```rust
// 1. Update frontmatter
update_frontmatter("~/noosphere-vault/Ideas/project.md", |meta| {
meta.category = "Projects".to_string();
meta.subcategory = Some("Work".to_string());
})?;

// 2. Move file
fs::rename(
"~/noosphere-vault/Ideas/project.md",
"~/noosphere-vault/Projects/Work/project.md"
)?;
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The example for moving files uses ~/noosphere-vault/... paths as string literals. The ~ character is typically expanded by shells, but not by most programming languages' standard libraries, including Rust's fs module. This could be misleading for developers. It would be clearer to use a variable representing the vault's base path and construct paths from it. For example:

// Assume vault_path is a Path object pointing to the vault root
let old_path = vault_path.join("Ideas/project.md");
let new_path = vault_path.join("Projects/Work/project.md");
// ... update frontmatter ...
fs::rename(&old_path, &new_path)?;

Comment thread sync-service/src/vault/lock.rs Outdated
file.unlock()?;
}
self._guard = None;
fs::remove_file(&self.lock_file).ok();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The release function ignores a potential error from fs::remove_file by using .ok(). If removing the lock file fails (e.g., due to permissions), the stale lock file will remain, potentially causing deadlocks for other processes later. It would be more robust to propagate this error.

Suggested change
fs::remove_file(&self.lock_file).ok();
fs::remove_file(&self.lock_file)?;

Comment on lines +99 to +102
pub fn validate_frontmatter(metadata: &ItemMetadata) -> bool {
// Validate required fields are present and non-empty
!metadata.title.is_empty() && !metadata.category.is_empty() && !metadata.created.is_empty()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The validate_frontmatter function provides basic checks for the presence of required fields. For more robust validation and to catch data integrity issues earlier, you could consider adding more specific checks, such as:

  • Verifying that created is a valid RFC3339 timestamp using chrono::DateTime::parse_from_rfc3339.
  • Ensuring category is one of the predefined values (e.g., "Inbox", "People", etc.).

deserat added a commit that referenced this pull request Jan 23, 2026
…ling and async documentation

This commit addresses all 6 review feedback items from Gemini Code Assist:

**Breaking Changes (improved error handling)**:
1. FileLock::new() now returns Result<Self> instead of Self
   - Propagates errors from path validation and directory creation
   - No more unwrap() panics on invalid paths
   - Better error messages for debugging

2. create_item_template() now returns Result<String> instead of String
   - Propagates YAML serialization errors instead of panicking
   - Consistent with write_markdown_file() error handling

3. FileLock::release() now propagates remove_file errors
   - Surfaces stale lock file issues instead of hiding them
   - Drop implementation logs errors to stderr

**Non-Breaking Changes (documentation and validation)**:
4. FileLock::acquire() async documentation added
   - Warning about blocking I/O in async contexts
   - Example using tokio::spawn_blocking for async runtimes

5. validate_frontmatter() enhanced with robust validation
   - RFC3339 timestamp validation using chrono::DateTime::parse_from_rfc3339
   - Category validation against allowed list: Inbox, People, Projects, Ideas, Admin
   - Added 2 new tests for invalid timestamps and categories

6. Documentation path examples updated (vault-structure.md)
   - Replaced tilde (~) paths with proper Path construction
   - Added note about shell expansion vs Rust standard library
   - Example now uses Path::new() and join() correctly

**Test Updates**:
- Updated 7 test functions to handle Result from FileLock::new()
- Updated 2 test functions to handle Result from create_item_template()
- Added 2 new validation tests (invalid timestamp, invalid category)
- Total: 33 tests (32 passed, 1 ignored)

**Quality Metrics**:
- Coverage: 84.68% region coverage (above 80% target)
- Clippy: Clean (no warnings)
- All tests passing

Addresses: PR #21 review comments
Reviewer: Gemini Code Assist

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@deserat
deserat force-pushed the feature/EPIC-3-1-vault-markdown-utilities branch from 22a5159 to c0596ad Compare January 23, 2026 05:17
@deserat

deserat commented Jan 23, 2026

Copy link
Copy Markdown
Owner Author

Updated PR to address all 6 review feedback items from Gemini Code Assist.

Changes Made:

  • ✅ FileLock::new() error handling (returns Result)
  • ✅ create_item_template() error handling (returns Result)
  • ✅ FileLock::release() error propagation
  • ✅ FileLock::acquire() async documentation
  • ✅ validate_frontmatter() enhanced validation (RFC3339 + categories)
  • ✅ Documentation path examples corrected

Quality Checks:

  • All tests passing (33 total, 32 passed, 1 ignored)
  • Coverage: 84.68% (above 80% target)
  • Clippy clean

Latest commit: c0596ad

@deserat

deserat commented Jan 23, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is an excellent pull request that lays a solid foundation for the vault filesystem utilities. The code is well-structured into modules with clear responsibilities, following Rust best practices. The use of atomic writes, file locking with RAII, and comprehensive test coverage demonstrates great attention to detail and robustness. The documentation is also very thorough. I've found a few areas for improvement, mainly around making the file lock release idempotent and fixing a small validation logic bug. Overall, fantastic work!

Comment on lines +196 to +203
pub fn release(&mut self) -> Result<()> {
if let Some(file) = &self._guard {
file.unlock()?;
}
self._guard = None;
fs::remove_file(&self.lock_file)?;
Ok(())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of release() is not idempotent. If it's called more than once (e.g., explicitly by a user, and then implicitly by Drop), the second call to fs::remove_file will fail because the file no longer exists, causing a spurious error. This can be made more robust by ensuring the unlock and file removal logic only runs if the lock is actually held. Using self._guard.take() is an idiomatic way to achieve this.

    pub fn release(&mut self) -> Result<()> {
        if let Some(file) = self._guard.take() {
            file.unlock()?;
            fs::remove_file(&self.lock_file)?;
        }
        Ok(())
    }

Comment thread sync-service/src/vault/parser.rs Outdated
Comment thread docs/vault-structure.md
Comment on lines +307 to +314
```rust
use noosphere_sync::vault::{parse_markdown_file, update_frontmatter};

// Update category
update_frontmatter(path, |meta| {
meta.category = "Projects".to_string();
})?;
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The code example for programmatically editing files is a bit confusing as the path variable is not defined. To improve clarity for users, it would be beneficial to make this a complete, self-contained example, similar to the one for moving files.

Suggested change
```rust
use noosphere_sync::vault::{parse_markdown_file, update_frontmatter};
// Update category
update_frontmatter(path, |meta| {
meta.category = "Projects".to_string();
})?;
```
use noosphere_sync::vault::writer::update_frontmatter;
use std::path::Path;
use chrono::Utc;
let file_path = Path::new("/path/to/your/note.md");
// In a real application, ensure the file exists before updating.
// Update the category and modified timestamp.
update_frontmatter(file_path, |meta| {
meta.category = "Projects".to_string();
meta.modified = Utc::now().to_rfc3339();
})?;

This commit addresses all review feedback from Gemini Code Assist on PR #21.

**Round 1 - Error Handling & Async Documentation (6 issues)**:

Breaking Changes:
1. FileLock::new() now returns Result<Self>
   - Validates file paths have parent directory and file name
   - Propagates directory creation errors instead of panicking
   - Updated 5 test functions and all documentation examples

2. create_item_template() now returns Result<String>
   - Propagates YAML serialization errors instead of panicking
   - Consistent with write_markdown_file() error handling
   - Updated 2 test functions

3. FileLock::release() error propagation improved
   - Made idempotent using take() instead of borrow (Round 2 fix)
   - No spurious errors on double-release (explicit + Drop)

Non-Breaking Changes:
4. FileLock::acquire() async documentation added
   - Warning about blocking I/O in async contexts
   - Example showing tokio::spawn_blocking usage for async runtimes

5. validate_frontmatter() enhanced validation
   - RFC3339 timestamp validation using chrono::DateTime::parse_from_rfc3339
   - Category validation: must be Inbox, People, Projects, Ideas, or Admin
   - Added modified field empty check (Round 2 fix)
   - Added 3 new validation tests

6. Documentation path examples corrected (vault-structure.md)
   - Replaced tilde (~) paths with proper Path construction
   - Added note about shell vs Rust path expansion
   - Completed programmatic editing example (Round 2 fix)

**Round 2 - Idempotency & Validation (3 issues)**:

1. FileLock::release() made idempotent [HIGH]
   - Changed from borrow (&self._guard) to take (self._guard.take())
   - Prevents spurious errors when called multiple times
   - Unlock and file removal only happen if lock held

2. validate_frontmatter() missing modified check [HIGH]
   - Added metadata.modified.is_empty() validation
   - Documentation already stated modified is required
   - Added test_validate_frontmatter_missing_modified test

3. Documentation example completed [MEDIUM]
   - Added complete example with all imports and variable definitions
   - vault-structure.md programmatic editing section now self-contained

**Test Updates**:
- Updated 7 test functions for FileLock::new() Result
- Updated 2 test functions for create_item_template() Result
- Added 3 new validation tests (invalid timestamp, invalid category, missing modified)
- Total: 34 tests (33 passed, 1 ignored)

**Quality Metrics**:
- Coverage: 84.66% region coverage (above 80% target)
- Clippy: Clean (no warnings)
- All tests passing

Addresses: PR #21 review comments (rounds 1 & 2)
Reviewer: Gemini Code Assist

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@deserat
deserat force-pushed the feature/EPIC-3-1-vault-markdown-utilities branch from c0596ad to 0e55b63 Compare January 23, 2026 05:36
@deserat

deserat commented Jan 23, 2026

Copy link
Copy Markdown
Owner Author

Updated PR to address all 3 review feedback items from round 2 (Gemini Code Assist).

Changes Made:

Round 2 Fixes:

  1. FileLock::release() made idempotent (HIGH)

    • Changed from borrow to take() to prevent spurious errors on double-release
    • Unlock and file removal only happen if lock is actually held
  2. validate_frontmatter() missing modified check (HIGH)

    • Added metadata.modified.is_empty() validation
    • Added test_validate_frontmatter_missing_modified test
  3. Documentation example completed (MEDIUM)

    • Added complete example with imports and variable definitions in vault-structure.md

Quality Checks:

  • All tests passing (34 total, 33 passed, 1 ignored)
  • Coverage: 84.66% (above 80% target)
  • Clippy clean

Latest commit: 0e55b63

All review feedback from rounds 1 & 2 has been addressed.

@deserat
deserat merged commit 4fd0c92 into develop Jan 23, 2026
1 check passed
@deserat
deserat deleted the feature/EPIC-3-1-vault-markdown-utilities branch January 23, 2026 05:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

service/docs Documentation only service/sync-service File watching Rust service

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant