- Replace list_notes with powerful new list tool
- Add recursive directory traversal
- Implement glob pattern filtering (*, **, ?, [abc], {a,b})
- Add cursor-based pagination for large result sets
- Support frontmatter summary extraction using metadata cache
- Add type filtering (files, directories, any)
- Create GlobUtils for pattern matching
- Add new types: FrontmatterSummary, FileMetadataWithFrontmatter, ListResult
- Update version to 3.0.0 (breaking change)
- Add comprehensive documentation and changelog
- Add Phase 10: UI Notifications to roadmap
BREAKING CHANGE: list_notes tool removed, replaced with list tool.
Migration: Replace list_notes({ path }) with list({ path }).
Response structure now wrapped in ListResult object.
417 lines
11 KiB
Markdown
417 lines
11 KiB
Markdown
# 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<FileMetadataWithFrontmatter | DirectoryMetadata>;
|
|
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.
|