feat: Phase 7 - Waypoint Support

- Add get_folder_waypoint tool to extract waypoint blocks from folder notes
- Add is_folder_note tool to detect folder notes by basename or waypoint markers
- Implement waypoint edit protection in update_note to prevent corruption
- Create waypoint-utils.ts with helper functions for waypoint operations
- Add FolderWaypointResult and FolderNoteResult types
- Update ROADMAP.md and CHANGELOG.md with Phase 7 completion
- All manual tests passing
This commit is contained in:
2025-10-17 00:16:14 -04:00
parent e6cdd6d90a
commit 4e399e00f8
7 changed files with 489 additions and 26 deletions

View File

@@ -1,9 +1,10 @@
import { App, TFile, TFolder } from 'obsidian';
import { CallToolResult, FileMetadata, DirectoryMetadata, VaultInfo, SearchResult, SearchMatch, StatResult, ExistsResult, ListResult, FileMetadataWithFrontmatter, FrontmatterSummary, WaypointSearchResult } from '../types/mcp-types';
import { CallToolResult, FileMetadata, DirectoryMetadata, VaultInfo, SearchResult, SearchMatch, StatResult, ExistsResult, ListResult, FileMetadataWithFrontmatter, FrontmatterSummary, WaypointSearchResult, FolderWaypointResult, FolderNoteResult } from '../types/mcp-types';
import { PathUtils } from '../utils/path-utils';
import { ErrorMessages } from '../utils/error-messages';
import { GlobUtils } from '../utils/glob-utils';
import { SearchUtils } from '../utils/search-utils';
import { WaypointUtils } from '../utils/waypoint-utils';
export class VaultTools {
constructor(private app: App) {}
@@ -596,4 +597,96 @@ export class VaultTools {
};
}
}
async getFolderWaypoint(path: string): Promise<CallToolResult> {
try {
// Normalize and validate path
const normalizedPath = PathUtils.normalizePath(path);
// Resolve file
const file = PathUtils.resolveFile(this.app, normalizedPath);
if (!file) {
return {
content: [{
type: "text",
text: ErrorMessages.fileNotFound(normalizedPath)
}],
isError: true
};
}
// Read file content
const content = await this.app.vault.read(file);
// Extract waypoint block
const waypointBlock = WaypointUtils.extractWaypointBlock(content);
const result: FolderWaypointResult = {
path: file.path,
hasWaypoint: waypointBlock.hasWaypoint,
waypointRange: waypointBlock.waypointRange,
links: waypointBlock.links,
rawContent: waypointBlock.rawContent
};
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Get folder waypoint error: ${(error as Error).message}`
}],
isError: true
};
}
}
async isFolderNote(path: string): Promise<CallToolResult> {
try {
// Normalize and validate path
const normalizedPath = PathUtils.normalizePath(path);
// Resolve file
const file = PathUtils.resolveFile(this.app, normalizedPath);
if (!file) {
return {
content: [{
type: "text",
text: ErrorMessages.fileNotFound(normalizedPath)
}],
isError: true
};
}
// Check if it's a folder note
const folderNoteInfo = await WaypointUtils.isFolderNote(this.app, file);
const result: FolderNoteResult = {
path: file.path,
isFolderNote: folderNoteInfo.isFolderNote,
reason: folderNoteInfo.reason,
folderPath: folderNoteInfo.folderPath
};
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Is folder note error: ${(error as Error).message}`
}],
isError: true
};
}
}
}