feat: add automatic word count and link validation to write operations
Add automatic word count and link validation to create_note, update_note, and update_sections operations to provide immediate feedback on note content quality and link integrity. Features: - Word counting excludes frontmatter and Obsidian comments, includes all other content (code blocks, inline code, headings, lists, etc.) - Link validation checks wikilinks, heading links, and embeds - Results categorized as: valid links, broken notes (note doesn't exist), and broken headings (note exists but heading missing) - Detailed broken link info includes line number and context snippet - Human-readable summary (e.g., "15 links: 12 valid, 2 broken notes, 1 broken heading") - Opt-out capability via validateLinks parameter (default: true) for performance-critical operations Implementation: - New ContentUtils.countWords() for word counting logic - Enhanced LinkUtils.validateLinks() for comprehensive link validation - Updated create_note, update_note, update_sections to return wordCount and linkValidation fields - Updated MCP tool descriptions to document new features and parameters - update_note now returns structured JSON instead of simple success message Response format changes: - create_note: added wordCount and linkValidation fields - update_note: changed to structured response with wordCount and linkValidation - update_sections: added wordCount and linkValidation fields Breaking changes: - update_note response format changed from simple message to structured JSON
This commit is contained in:
@@ -16,12 +16,15 @@ import { ErrorMessages } from '../utils/error-messages';
|
||||
import { FrontmatterUtils } from '../utils/frontmatter-utils';
|
||||
import { WaypointUtils } from '../utils/waypoint-utils';
|
||||
import { VersionUtils } from '../utils/version-utils';
|
||||
import { IVaultAdapter, IFileManagerAdapter } from '../adapters/interfaces';
|
||||
import { ContentUtils } from '../utils/content-utils';
|
||||
import { LinkUtils } from '../utils/link-utils';
|
||||
import { IVaultAdapter, IFileManagerAdapter, IMetadataCacheAdapter } from '../adapters/interfaces';
|
||||
|
||||
export class NoteTools {
|
||||
constructor(
|
||||
private vault: IVaultAdapter,
|
||||
private fileManager: IFileManagerAdapter,
|
||||
private metadata: IMetadataCacheAdapter,
|
||||
private app: App // Keep temporarily for methods not yet migrated
|
||||
) {}
|
||||
|
||||
@@ -119,10 +122,11 @@ export class NoteTools {
|
||||
}
|
||||
|
||||
async createNote(
|
||||
path: string,
|
||||
content: string,
|
||||
path: string,
|
||||
content: string,
|
||||
createParents: boolean = false,
|
||||
onConflict: ConflictStrategy = 'error'
|
||||
onConflict: ConflictStrategy = 'error',
|
||||
validateLinks: boolean = true
|
||||
): Promise<CallToolResult> {
|
||||
// Validate path
|
||||
if (!path || path.trim() === '') {
|
||||
@@ -213,7 +217,7 @@ export class NoteTools {
|
||||
// Proceed with file creation
|
||||
try {
|
||||
const file = await this.vault.create(finalPath, content);
|
||||
|
||||
|
||||
const result: CreateNoteResult = {
|
||||
success: true,
|
||||
path: file.path,
|
||||
@@ -223,6 +227,19 @@ export class NoteTools {
|
||||
originalPath: originalPath
|
||||
};
|
||||
|
||||
// Add word count
|
||||
result.wordCount = ContentUtils.countWords(content);
|
||||
|
||||
// Add link validation if requested
|
||||
if (validateLinks) {
|
||||
result.linkValidation = await LinkUtils.validateLinks(
|
||||
this.vault,
|
||||
this.metadata,
|
||||
content,
|
||||
file.path
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
||||
};
|
||||
@@ -271,7 +288,7 @@ export class NoteTools {
|
||||
}
|
||||
}
|
||||
|
||||
async updateNote(path: string, content: string): Promise<CallToolResult> {
|
||||
async updateNote(path: string, content: string, validateLinks: boolean = true): Promise<CallToolResult> {
|
||||
// Validate path
|
||||
if (!path || path.trim() === '') {
|
||||
return {
|
||||
@@ -329,8 +346,30 @@ export class NoteTools {
|
||||
}
|
||||
|
||||
await this.vault.modify(file, content);
|
||||
|
||||
// Build response with word count and link validation
|
||||
const result: any = {
|
||||
success: true,
|
||||
path: file.path,
|
||||
versionId: VersionUtils.generateVersionId(file),
|
||||
modified: file.stat.mtime
|
||||
};
|
||||
|
||||
// Add word count
|
||||
result.wordCount = ContentUtils.countWords(content);
|
||||
|
||||
// Add link validation if requested
|
||||
if (validateLinks) {
|
||||
result.linkValidation = await LinkUtils.validateLinks(
|
||||
this.vault,
|
||||
this.metadata,
|
||||
content,
|
||||
file.path
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: `Note updated successfully: ${file.path}` }]
|
||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
@@ -813,7 +852,8 @@ export class NoteTools {
|
||||
async updateSections(
|
||||
path: string,
|
||||
edits: SectionEdit[],
|
||||
ifMatch?: string
|
||||
ifMatch?: string,
|
||||
validateLinks: boolean = true
|
||||
): Promise<CallToolResult> {
|
||||
// Validate path
|
||||
if (!path || path.trim() === '') {
|
||||
@@ -917,6 +957,19 @@ export class NoteTools {
|
||||
sectionsUpdated: edits.length
|
||||
};
|
||||
|
||||
// Add word count
|
||||
result.wordCount = ContentUtils.countWords(newContent);
|
||||
|
||||
// Add link validation if requested
|
||||
if (validateLinks) {
|
||||
result.linkValidation = await LinkUtils.validateLinks(
|
||||
this.vault,
|
||||
this.metadata,
|
||||
newContent,
|
||||
file.path
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user