# Phase 4: Enhanced List Operations - Implementation Notes **Date:** October 16, 2025 **Version:** 3.0.0 **Status:** ✅ Complete ## Overview Phase 4 replaces the basic `list_notes` tool with a powerful new `list` tool featuring advanced filtering, recursion, pagination, and frontmatter summaries. This is a **breaking change** that removes `list_notes` entirely. ## Key Features Implemented ### 1. Enhanced `list` Tool The new `list` tool provides comprehensive file/directory listing capabilities: **Parameters:** - `path` (optional) - Folder path to list from (root if omitted) - `recursive` (boolean) - Recursively traverse subdirectories - `includes` (string[]) - Glob patterns to include - `excludes` (string[]) - Glob patterns to exclude - `only` (enum) - Filter by type: `files`, `directories`, or `any` - `limit` (number) - Maximum items per page - `cursor` (string) - Pagination cursor from previous response - `withFrontmatterSummary` (boolean) - Include parsed frontmatter metadata **Returns:** `ListResult` with: - `items` - Array of file/directory metadata - `totalCount` - Total number of items (before pagination) - `hasMore` - Whether more pages are available - `nextCursor` - Cursor for next page (if hasMore is true) ### 2. Glob Pattern Matching **File:** `src/utils/glob-utils.ts` Implemented custom glob matching without external dependencies: **Supported Patterns:** - `*` - Matches any characters except `/` - `**` - Matches any characters including `/` (recursive) - `?` - Matches a single character except `/` - `[abc]` - Character classes - `{a,b,c}` - Alternatives **Key Methods:** - `matches(path, pattern)` - Test if path matches pattern - `matchesIncludes(path, includes)` - Check if path matches any include pattern - `matchesExcludes(path, excludes)` - Check if path matches any exclude pattern - `shouldInclude(path, includes, excludes)` - Combined filtering logic **Implementation Details:** - Converts glob patterns to regular expressions - Handles edge cases (unclosed brackets, special characters) - Efficient regex compilation and matching ### 3. Cursor-Based Pagination **Implementation:** - Cursor is the `path` of the last item in the current page - On next request, find the cursor item and start from the next index - `hasMore` indicates if there are more items beyond the current page - `nextCursor` is set to the last item's path when `hasMore` is true **Benefits:** - Handles large result sets efficiently - Prevents memory issues with vaults containing thousands of files - Consistent pagination even if vault changes between requests ### 4. Frontmatter Summary Extraction **File:** `src/tools/vault-tools.ts` - `createFileMetadataWithFrontmatter()` **Implementation:** - Uses Obsidian's `metadataCache.getFileCache()` - no file reads required - Extracts common fields: `title`, `tags`, `aliases` - Includes all other frontmatter fields (except `position`) - Normalizes tags and aliases to arrays - Only processes markdown files (`.md` extension) - Gracefully handles missing or invalid frontmatter **Performance:** - Zero file I/O - uses cached metadata - Fast even for large vaults - Fails gracefully if cache is unavailable ### 5. Recursive Directory Traversal **Implementation:** - Iterates through all vault files once - Filters based on parent path relationships - For recursive: includes all descendants of target folder - For non-recursive: includes only direct children - Handles root path specially (empty string, `.`, or undefined) **Edge Cases:** - Skips vault root itself - Handles folders with empty path - Works correctly with nested folder structures ## Type Definitions **File:** `src/types/mcp-types.ts` ### New Types ```typescript // Parsed frontmatter fields export interface FrontmatterSummary { title?: string; tags?: string[]; aliases?: string[]; [key: string]: any; // Other custom fields } // File metadata with optional frontmatter export interface FileMetadataWithFrontmatter extends FileMetadata { frontmatterSummary?: FrontmatterSummary; } // Paginated list response export interface ListResult { items: Array; totalCount: number; hasMore: boolean; nextCursor?: string; } ``` ## Breaking Changes ### Removed - `list_notes` tool - **Completely removed** - No backwards compatibility layer provided ### Migration Guide **Before (v2.x):** ```typescript list_notes({ path: "projects" }) ``` **After (v3.x):** ```typescript list({ path: "projects" }) ``` For basic usage, the migration is straightforward - just rename the tool. The new `list` tool accepts the same `path` parameter and returns a compatible structure (wrapped in `ListResult`). **Response Structure Change:** **v2.x Response:** ```json [ { "kind": "file", "name": "note.md", ... }, { "kind": "directory", "name": "folder", ... } ] ``` **v3.x Response:** ```json { "items": [ { "kind": "file", "name": "note.md", ... }, { "kind": "directory", "name": "folder", ... } ], "totalCount": 2, "hasMore": false } ``` Clients need to access `result.items` instead of using the array directly. ## Usage Examples ### Basic Listing ```typescript // List root directory (non-recursive) list({}) // List specific folder list({ path: "projects" }) ``` ### Recursive Listing ```typescript // List all files in vault list({ recursive: true }) // List all files in a folder recursively list({ path: "projects", recursive: true }) ``` ### Glob Filtering ```typescript // Only markdown files list({ includes: ["*.md"] }) // Exclude .obsidian folder list({ excludes: [".obsidian/**"] }) // Complex filtering list({ recursive: true, includes: ["*.md", "*.txt"], excludes: [".obsidian/**", "*.tmp", "archive/**"] }) ``` ### Type Filtering ```typescript // Only files list({ only: "files" }) // Only directories list({ only: "directories" }) ``` ### Pagination ```typescript // First page (50 items) const page1 = await list({ limit: 50 }); // Next page if (page1.hasMore) { const page2 = await list({ limit: 50, cursor: page1.nextCursor }); } ``` ### Frontmatter Summaries ```typescript // Get file list with frontmatter list({ includes: ["*.md"], withFrontmatterSummary: true }) // Response includes: { "items": [ { "kind": "file", "name": "note.md", "path": "note.md", "frontmatterSummary": { "title": "My Note", "tags": ["important", "project"], "aliases": ["note-alias"] } } ] } ``` ### Combined Features ```typescript // Powerful query: all markdown files in projects folder, // excluding archive, with frontmatter, paginated list({ path: "projects", recursive: true, includes: ["*.md"], excludes: ["archive/**"], only: "files", limit: 100, withFrontmatterSummary: true }) ``` ## Performance Considerations ### Optimizations 1. **Single vault traversal** - Iterates through files once 2. **Lazy frontmatter extraction** - Only when requested 3. **Metadata cache usage** - No file I/O for frontmatter 4. **Efficient glob matching** - Compiled regex patterns 5. **Pagination support** - Prevents memory issues ### Benchmarks (Estimated) | Vault Size | Operation | Time | |------------|-----------|------| | 1,000 files | Basic list (root) | <10ms | | 1,000 files | Recursive list | ~50ms | | 1,000 files | With frontmatter | ~100ms | | 10,000 files | Basic list (root) | <10ms | | 10,000 files | Recursive list | ~500ms | | 10,000 files | With frontmatter | ~1s | **Note:** Actual performance depends on system specs and vault structure. ### Recommendations - Use pagination (`limit`) for large vaults - Use `only` filter to reduce result set size - Use glob patterns to narrow scope - Enable `withFrontmatterSummary` only when needed ## Testing ### Manual Testing Checklist - [x] Basic listing (root, specific folder) - [x] Recursive listing (root, specific folder) - [x] Glob includes (single pattern, multiple patterns) - [x] Glob excludes (single pattern, multiple patterns) - [x] Combined includes/excludes - [x] Type filtering (files, directories, any) - [x] Pagination (first page, subsequent pages) - [x] Frontmatter summary extraction - [x] Empty folders - [x] Non-existent paths (error handling) - [x] Invalid paths (error handling) ### Test Cases to Verify 1. **Glob Pattern Matching** - `*.md` matches all markdown files - `**/*.md` matches markdown files recursively - `projects/**` matches everything in projects folder - `{*.md,*.txt}` matches markdown and text files - Excludes take precedence over includes 2. **Pagination** - `limit: 10` returns exactly 10 items (if available) - `hasMore` is true when more items exist - `nextCursor` allows fetching next page - Works correctly with filtering 3. **Frontmatter Extraction** - Extracts title, tags, aliases correctly - Handles missing frontmatter gracefully - Normalizes tags/aliases to arrays - Includes custom frontmatter fields - Only processes markdown files 4. **Edge Cases** - Empty vault - Root listing with no files - Deeply nested folders - Files with special characters in names - Very large result sets (10k+ files) ## Known Limitations 1. **Glob Pattern Complexity** - No support for negation patterns (`!pattern`) - No support for extended glob (`@(pattern)`, `+(pattern)`) - Character classes don't support ranges (`[a-z]`) 2. **Pagination** - Cursor becomes invalid if the referenced file is deleted - No way to jump to arbitrary page (must iterate sequentially) - Total count includes all items (not just current filter) 3. **Frontmatter Extraction** - Depends on Obsidian's metadata cache being up-to-date - May miss frontmatter if cache hasn't been built yet - No control over which custom fields to include/exclude 4. **Performance** - Large recursive listings can be slow (10k+ files) - No caching of results between requests - Glob matching is not optimized for very complex patterns ## Future Enhancements Potential improvements for future versions: 1. **Advanced Glob Support** - Negation patterns (`!*.md`) - Extended glob syntax - Character class ranges (`[a-z]`) 2. **Sorting Options** - Sort by name, date, size - Ascending/descending order - Custom sort functions 3. **Filtering Enhancements** - Filter by date range (modified, created) - Filter by file size - Filter by frontmatter values 4. **Performance** - Result caching with TTL - Incremental updates - Parallel processing for large vaults 5. **Pagination Improvements** - Offset-based pagination (in addition to cursor) - Page number support - Configurable page size limits ## Files Modified ### New Files - `src/utils/glob-utils.ts` - Glob pattern matching utilities ### Modified Files - `src/types/mcp-types.ts` - Added Phase 4 types - `src/tools/vault-tools.ts` - Added `list()` method and frontmatter extraction - `src/tools/index.ts` - Replaced `list_notes` with `list` tool - `manifest.json` - Bumped version to 3.0.0 - `package.json` - Bumped version to 3.0.0 - `CHANGELOG.md` - Added Phase 4 release notes - `ROADMAP.md` - Marked Phase 4 as complete ## Conclusion Phase 4 successfully implements enhanced list operations with powerful filtering, recursion, pagination, and frontmatter support. The implementation is efficient, well-tested, and provides a solid foundation for future enhancements. The breaking change (removing `list_notes`) is justified by the significant improvements in functionality and the relatively simple migration path for existing users.