Vault Walker and File Cache
Relevant Source Files
The storage subsystem relies on efficient discovery of Markdown files and a robust caching mechanism to ensure performance during large-scale vault operations. The Vault Walker provides a filtered recursive traversal of the Obsidian vault, while the File Cache implements validation logic designed to handle rapid edit cycles without sacrificing data integrity.
Vault Walker
The walkVault function in src/storage/vault-walker.ts traverses the file system and collects absolute paths to Markdown files, applying strict filtering rules:
- Markdown Only: Only files ending in
.mdare collected. - Excluded Directories: Specifically ignores
node_modulesand any custom directories configured viaWalkOptions.excludeDirs(e.g._templates,archive). - Hidden Directories: Any directory starting with a dot (
.) is skipped, effectively excluding.obsidian,.trash, and.git. - Symlinks: By default, symbolic links are skipped to prevent circular references or escaping the vault, unless explicitly enabled via
WalkOptions.followSymlinks. - Permissions: If a directory cannot be read due to permission errors (
EACCES/EPERM), it is skipped safely.
Safe Directory Creation (ensureVaultDirectory)
In addition to vault traversal, the vault walker module provides ensureVaultDirectory(vaultPath, targetPath) src/storage/vault-walker.ts#136-156. This helper is utilized by commands like palee roadmap to create target directory structures safely:
- Path Boundary Validation: Checks
path.relative(resolvedVault, targetDir)and throws an error if the path attempts directory traversal outside the vault root (..). - Symlink Escape Defense: After creating directories, validates the canonical path using
fs.realpathSyncto guarantee that no symlink targets resolve outside the vault. - Lock & Idempotency Safety: Recursively creates missing directories (
fs.mkdirSync(dir, { recursive: true })) safely.
Discovery Logic Flow
Sources: src/storage/vault-walker.ts#14-156test/storage-walker.test.ts#41-133
File Cache
The FileCache class in src/storage/cache.ts provides a deterministic in-memory store for parsed file data (such as FrontmatterResult or LoadedTopic). It is designed to minimize expensive disk I/O and SHA-256 fingerprinting operations while remaining safe against external file modifications. It contains zero test-environment bypasses (no NODE_ENV !== 'test' checks), guaranteeing identical deterministic behavior across testing and production.
The Unsettled Horizon & SHA-256 Fallback
A central feature of the cache is the UNSETTLED_HORIZON, set to 2,000 ms (2 seconds) src/storage/cache.ts#16. This constant addresses the "rapid-edit cycle" and filesystem timestamp granularity issues where files might be modified multiple times in quick succession.
- Inside Horizon (
< 2,000 mssincemtime): The cache treats recentmtimevalues as volatile. It bypasses timestamp trust and forces a file re-read and SHA-256 hash recomputation (computeFingerprint(content)). If the digest matchesentry.fingerprint,entry.mtimeandentry.lastVerifiedare updated and cached data is returned; otherwise, the entry is evicted andnullis returned. - Outside Horizon (
>= 2,000 mssincemtime):mtimeMatch: Ifstat.mtimeMs === entry.mtime, the file is settled and unchanged. PALEE executes a fastcache hit, updating entry.lastVerifiedand returningentry.datawithout reading file contents.mtimeMismatch (SHA-256 Fallback): Ifmtimehas changed outside the horizon (for example, when a file's timestamp is updated by atouchcommand, backup tool, or cloud sync without modifying note contents),FileCacheinitiates a SHA-256 fallback check. It re-reads the file and hashes its content:- If the SHA-256 hash matches
entry.fingerprint, the content is confirmed identical. The cache updatesentry.mtime = stat.mtimeMsandentry.lastVerified = now, preserving the cache entry and returningentry.data. - If the SHA-256 hash differs, the content has genuinely changed. The stale entry is deleted from the cache and
nullis returned.
- If the SHA-256 hash matches
Cache Validation Logic
When FileCache.get(filePath) is called, the following validation sequence occurs:
- Existence & Size Verification: If the file is not in the cache or
fs.statSync(filePath).sizediffers fromentry.size, the entry is immediately evicted (this.cache.delete(filePath)) andnullis returned src/storage/cache.ts#56-60. - Horizon Check: Evaluates whether
(Date.now() - mtime) < UNSETTLED_HORIZONsrc/storage/cache.ts#63-80. - Fingerprint Verification: Uses
computeFingerprintfromsrc/storage/frontmatter.tsfor mandatory verification inside the horizon and as a fallback onmtimeshifts outside the horizon src/storage/cache.ts#69-74src/storage/cache.ts#89-95.
Cache Validation Flowchart
Sources: src/storage/cache.ts#16-107test/storage-cache.test.ts#24-88
Key Methods
| Method | Description | Source |
|---|---|---|
get(filePath) | Retrieves data if valid; performs size, horizon, mtime, and SHA-256 fallback checks | src/storage/cache.ts#47-107 |
set(filePath, data, fingerprint) | Stores parsed data alongside filesystem stats (mtime, size, lastVerified) | src/storage/cache.ts#116-129 |
invalidate(filePath) | Manually evicts a specific file entry from the cache | src/storage/cache.ts#136-138 |
clear() | Flushes all entries from the in-memory cache | src/storage/cache.ts#143-145 |
Sources: src/storage/cache.ts#1-149
