only allow edit_note, move_note using strict identifier match

Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
phernandez
2025-06-04 23:37:29 -05:00
parent 91bfe2dc92
commit 602c55fe90
6 changed files with 199 additions and 31 deletions
+17 -11
View File
@@ -24,14 +24,14 @@ def _format_error_response(
if "Entity not found" in error_message or "entity not found" in error_message.lower():
return f"""# Edit Failed - Note Not Found
The note with identifier '{identifier}' could not be found.
The note with identifier '{identifier}' could not be found. Edit operations require an exact match (no fuzzy matching).
## Suggestions to try:
1. **Search for the note first**: Use `search_notes("{identifier.split("/")[-1]}")` to find similar notes
2. **Try different identifier formats**:
- If you used a permalink like "folder/note-title", try just the title: "{identifier.split("/")[-1].replace("-", " ").title()}"
- If you used a title, try the permalink format: "{identifier.lower().replace(" ", "-")}"
- Use `read_note()` first to verify the note exists and get the correct identifiers
1. **Search for the note first**: Use `search_notes("{identifier.split("/")[-1]}")` to find similar notes with exact identifiers
2. **Try different exact identifier formats**:
- If you used a permalink like "folder/note-title", try the exact title: "{identifier.split("/")[-1].replace("-", " ").title()}"
- If you used a title, try the exact permalink format: "{identifier.lower().replace(" ", "-")}"
- Use `read_note()` first to verify the note exists and get the exact identifier
## Alternative approach:
Use `write_note()` to create the note first, then edit it."""
@@ -142,7 +142,9 @@ async def edit_note(
It supports various operations for different editing scenarios.
Args:
identifier: The title, permalink, or memory:// URL of the note to edit
identifier: The exact title, permalink, or memory:// URL of the note to edit.
Must be an exact match - fuzzy matching is not supported for edit operations.
Use search_notes() or read_note() first to find the correct identifier if uncertain.
operation: The editing operation to perform:
- "append": Add content to the end of the note
- "prepend": Add content to the beginning of the note
@@ -179,10 +181,14 @@ async def edit_note(
# Replace subsection with more specific header
edit_note("docs/setup", "replace_section", "Updated install steps\\n", section="### Installation")
# Using different identifier formats
edit_note("Meeting Notes", "append", "\\n- Follow up on action items") # title
edit_note("docs/meeting-notes", "append", "\\n- Follow up tasks") # permalink
edit_note("docs/Meeting Notes", "append", "\\n- Next steps") # folder/title
# Using different identifier formats (must be exact matches)
edit_note("Meeting Notes", "append", "\\n- Follow up on action items") # exact title
edit_note("docs/meeting-notes", "append", "\\n- Follow up tasks") # exact permalink
edit_note("docs/Meeting Notes", "append", "\\n- Next steps") # exact folder/title
# If uncertain about identifier, search first:
# search_notes("meeting") # Find available notes
# edit_note("docs/meeting-notes-2025", "append", "content") # Use exact result
# Add new section to document
edit_note("project-plan", "replace_section", "TBD - needs research\\n", section="## Future Work")
+22 -11
View File
@@ -26,14 +26,14 @@ def _format_move_error_response(error_message: str, identifier: str, destination
return dedent(f"""
# Move Failed - Note Not Found
The note '{identifier}' could not be found for moving.
The note '{identifier}' could not be found for moving. Move operations require an exact match (no fuzzy matching).
## Suggestions to try:
1. **Search for the note first**: Use `search_notes("{search_term}")` to find it
2. **Try different identifier formats**:
- If you used a permalink like "folder/note-title", try just the title: "{title_format}"
- If you used a title, try the permalink format: "{permalink_format}"
- Use `read_note()` first to verify the note exists and get the correct identifier
1. **Search for the note first**: Use `search_notes("{search_term}")` to find it with exact identifiers
2. **Try different exact identifier formats**:
- If you used a permalink like "folder/note-title", try the exact title: "{title_format}"
- If you used a title, try the exact permalink format: "{permalink_format}"
- Use `read_note()` first to verify the note exists and get the exact identifier
3. **Check current project**: Use `get_current_project()` to verify you're in the right project
4. **List available notes**: Use `list_directory("/")` to see what notes exist
@@ -43,7 +43,7 @@ def _format_move_error_response(error_message: str, identifier: str, destination
# First, verify the note exists:
search_notes("{identifier}")
# Then use the correct identifier from search results:
# Then use the exact identifier from search results:
move_note("correct-identifier-here", "{destination_path}")
```
""").strip()
@@ -220,7 +220,9 @@ async def move_note(
"""Move a note to a new file location within the same project.
Args:
identifier: Entity identifier (title, permalink, or memory:// URL)
identifier: Exact entity identifier (title, permalink, or memory:// URL).
Must be an exact match - fuzzy matching is not supported for move operations.
Use search_notes() or read_note() first to find the correct identifier if uncertain.
destination_path: New path relative to project root (e.g., "work/meetings/2025-05-26.md")
project: Optional project name (defaults to current session project)
@@ -228,9 +230,18 @@ async def move_note(
Success message with move details
Examples:
- Move to new folder: move_note("My Note", "work/notes/my-note.md")
- Move by permalink: move_note("my-note-permalink", "archive/old-notes/my-note.md")
- Specify project: move_note("My Note", "archive/my-note.md", project="work-project")
# Move to new folder (exact title match)
move_note("My Note", "work/notes/my-note.md")
# Move by exact permalink
move_note("my-note-permalink", "archive/old-notes/my-note.md")
# Specify project with exact identifier
move_note("My Note", "archive/my-note.md", project="work-project")
# If uncertain about identifier, search first:
# search_notes("my note") # Find available notes
# move_note("docs/my-note-2025", "archive/my-note.md") # Use exact result
Note: This operation moves notes within the specified project only. Moving notes
between different projects is not currently supported.
+4 -4
View File
@@ -413,8 +413,8 @@ class EntityService(BaseService[EntityModel]):
"""
logger.debug(f"Editing entity: {identifier}, operation: {operation}")
# Find the entity using the link resolver
entity = await self.link_resolver.resolve_link(identifier)
# Find the entity using the link resolver with strict mode for destructive operations
entity = await self.link_resolver.resolve_link(identifier, strict=True)
if not entity:
raise EntityNotFoundError(f"Entity not found: {identifier}")
@@ -630,8 +630,8 @@ class EntityService(BaseService[EntityModel]):
"""
logger.debug(f"Moving entity: {identifier} to {destination_path}")
# 1. Resolve identifier to entity
entity = await self.link_resolver.resolve_link(identifier)
# 1. Resolve identifier to entity with strict mode for destructive operations
entity = await self.link_resolver.resolve_link(identifier, strict=True)
if not entity:
raise EntityNotFoundError(f"Entity not found: {identifier}")
+13 -4
View File
@@ -26,8 +26,14 @@ class LinkResolver:
self.entity_repository = entity_repository
self.search_service = search_service
async def resolve_link(self, link_text: str, use_search: bool = True) -> Optional[Entity]:
"""Resolve a markdown link to a permalink."""
async def resolve_link(self, link_text: str, use_search: bool = True, strict: bool = False) -> Optional[Entity]:
"""Resolve a markdown link to a permalink.
Args:
link_text: The link text to resolve
use_search: Whether to use search-based fuzzy matching as fallback
strict: If True, only exact matches are allowed (no fuzzy search fallback)
"""
logger.trace(f"Resolving link: {link_text}")
# Clean link text and extract any alias
@@ -60,9 +66,12 @@ class LinkResolver:
logger.debug(f"Found entity with path (with .md): {found_path_md.file_path}")
return found_path_md
# search if indicated
# In strict mode, don't try fuzzy search - return None if no exact match found
if strict:
return None
# 5. Fall back to search for fuzzy matching (only if not in strict mode)
if use_search and "*" not in clean_text:
# 5. Fall back to search for fuzzy matching on title (use text search for prefix matching)
results = await self.search_service.search(
query=SearchQuery(text=clean_text, entity_types=[SearchItemType.ENTITY]),
)