basic-memory 0.2.12__py3-none-any.whl → 0.16.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of basic-memory might be problematic. Click here for more details.
- basic_memory/__init__.py +5 -1
- basic_memory/alembic/alembic.ini +119 -0
- basic_memory/alembic/env.py +27 -3
- basic_memory/alembic/migrations.py +4 -9
- basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
- basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +108 -0
- basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +104 -0
- basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py +49 -0
- basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
- basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
- basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +100 -0
- basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
- basic_memory/api/app.py +63 -31
- basic_memory/api/routers/__init__.py +4 -1
- basic_memory/api/routers/directory_router.py +84 -0
- basic_memory/api/routers/importer_router.py +152 -0
- basic_memory/api/routers/knowledge_router.py +165 -28
- basic_memory/api/routers/management_router.py +80 -0
- basic_memory/api/routers/memory_router.py +28 -67
- basic_memory/api/routers/project_router.py +406 -0
- basic_memory/api/routers/prompt_router.py +260 -0
- basic_memory/api/routers/resource_router.py +219 -14
- basic_memory/api/routers/search_router.py +21 -13
- basic_memory/api/routers/utils.py +130 -0
- basic_memory/api/template_loader.py +292 -0
- basic_memory/cli/app.py +52 -1
- basic_memory/cli/auth.py +277 -0
- basic_memory/cli/commands/__init__.py +13 -2
- basic_memory/cli/commands/cloud/__init__.py +6 -0
- basic_memory/cli/commands/cloud/api_client.py +112 -0
- basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
- basic_memory/cli/commands/cloud/cloud_utils.py +101 -0
- basic_memory/cli/commands/cloud/core_commands.py +195 -0
- basic_memory/cli/commands/cloud/rclone_commands.py +301 -0
- basic_memory/cli/commands/cloud/rclone_config.py +110 -0
- basic_memory/cli/commands/cloud/rclone_installer.py +249 -0
- basic_memory/cli/commands/cloud/upload.py +233 -0
- basic_memory/cli/commands/cloud/upload_command.py +124 -0
- basic_memory/cli/commands/command_utils.py +51 -0
- basic_memory/cli/commands/db.py +26 -7
- basic_memory/cli/commands/import_chatgpt.py +83 -0
- basic_memory/cli/commands/import_claude_conversations.py +86 -0
- basic_memory/cli/commands/import_claude_projects.py +85 -0
- basic_memory/cli/commands/import_memory_json.py +35 -92
- basic_memory/cli/commands/mcp.py +84 -10
- basic_memory/cli/commands/project.py +876 -0
- basic_memory/cli/commands/status.py +47 -30
- basic_memory/cli/commands/tool.py +341 -0
- basic_memory/cli/main.py +13 -6
- basic_memory/config.py +481 -22
- basic_memory/db.py +192 -32
- basic_memory/deps.py +252 -22
- basic_memory/file_utils.py +113 -58
- basic_memory/ignore_utils.py +297 -0
- basic_memory/importers/__init__.py +27 -0
- basic_memory/importers/base.py +79 -0
- basic_memory/importers/chatgpt_importer.py +232 -0
- basic_memory/importers/claude_conversations_importer.py +177 -0
- basic_memory/importers/claude_projects_importer.py +148 -0
- basic_memory/importers/memory_json_importer.py +108 -0
- basic_memory/importers/utils.py +58 -0
- basic_memory/markdown/entity_parser.py +143 -23
- basic_memory/markdown/markdown_processor.py +3 -3
- basic_memory/markdown/plugins.py +39 -21
- basic_memory/markdown/schemas.py +1 -1
- basic_memory/markdown/utils.py +28 -13
- basic_memory/mcp/async_client.py +134 -4
- basic_memory/mcp/project_context.py +141 -0
- basic_memory/mcp/prompts/__init__.py +19 -0
- basic_memory/mcp/prompts/ai_assistant_guide.py +70 -0
- basic_memory/mcp/prompts/continue_conversation.py +62 -0
- basic_memory/mcp/prompts/recent_activity.py +188 -0
- basic_memory/mcp/prompts/search.py +57 -0
- basic_memory/mcp/prompts/utils.py +162 -0
- basic_memory/mcp/resources/ai_assistant_guide.md +283 -0
- basic_memory/mcp/resources/project_info.py +71 -0
- basic_memory/mcp/server.py +7 -13
- basic_memory/mcp/tools/__init__.py +33 -21
- basic_memory/mcp/tools/build_context.py +120 -0
- basic_memory/mcp/tools/canvas.py +130 -0
- basic_memory/mcp/tools/chatgpt_tools.py +187 -0
- basic_memory/mcp/tools/delete_note.py +225 -0
- basic_memory/mcp/tools/edit_note.py +320 -0
- basic_memory/mcp/tools/list_directory.py +167 -0
- basic_memory/mcp/tools/move_note.py +545 -0
- basic_memory/mcp/tools/project_management.py +200 -0
- basic_memory/mcp/tools/read_content.py +271 -0
- basic_memory/mcp/tools/read_note.py +255 -0
- basic_memory/mcp/tools/recent_activity.py +534 -0
- basic_memory/mcp/tools/search.py +369 -14
- basic_memory/mcp/tools/utils.py +374 -16
- basic_memory/mcp/tools/view_note.py +77 -0
- basic_memory/mcp/tools/write_note.py +207 -0
- basic_memory/models/__init__.py +3 -2
- basic_memory/models/knowledge.py +67 -15
- basic_memory/models/project.py +87 -0
- basic_memory/models/search.py +10 -6
- basic_memory/repository/__init__.py +2 -0
- basic_memory/repository/entity_repository.py +229 -7
- basic_memory/repository/observation_repository.py +35 -3
- basic_memory/repository/project_info_repository.py +10 -0
- basic_memory/repository/project_repository.py +103 -0
- basic_memory/repository/relation_repository.py +21 -2
- basic_memory/repository/repository.py +147 -29
- basic_memory/repository/search_repository.py +437 -59
- basic_memory/schemas/__init__.py +22 -9
- basic_memory/schemas/base.py +97 -8
- basic_memory/schemas/cloud.py +50 -0
- basic_memory/schemas/directory.py +30 -0
- basic_memory/schemas/importer.py +35 -0
- basic_memory/schemas/memory.py +188 -23
- basic_memory/schemas/project_info.py +211 -0
- basic_memory/schemas/prompt.py +90 -0
- basic_memory/schemas/request.py +57 -3
- basic_memory/schemas/response.py +9 -1
- basic_memory/schemas/search.py +33 -35
- basic_memory/schemas/sync_report.py +72 -0
- basic_memory/services/__init__.py +2 -1
- basic_memory/services/context_service.py +251 -106
- basic_memory/services/directory_service.py +295 -0
- basic_memory/services/entity_service.py +595 -60
- basic_memory/services/exceptions.py +21 -0
- basic_memory/services/file_service.py +284 -30
- basic_memory/services/initialization.py +191 -0
- basic_memory/services/link_resolver.py +50 -56
- basic_memory/services/project_service.py +863 -0
- basic_memory/services/search_service.py +172 -34
- basic_memory/sync/__init__.py +3 -2
- basic_memory/sync/background_sync.py +26 -0
- basic_memory/sync/sync_service.py +1176 -96
- basic_memory/sync/watch_service.py +412 -135
- basic_memory/templates/prompts/continue_conversation.hbs +110 -0
- basic_memory/templates/prompts/search.hbs +101 -0
- basic_memory/utils.py +388 -28
- basic_memory-0.16.1.dist-info/METADATA +493 -0
- basic_memory-0.16.1.dist-info/RECORD +148 -0
- {basic_memory-0.2.12.dist-info → basic_memory-0.16.1.dist-info}/entry_points.txt +1 -0
- basic_memory/alembic/README +0 -1
- basic_memory/cli/commands/sync.py +0 -203
- basic_memory/mcp/tools/knowledge.py +0 -56
- basic_memory/mcp/tools/memory.py +0 -151
- basic_memory/mcp/tools/notes.py +0 -122
- basic_memory/schemas/discovery.py +0 -28
- basic_memory/sync/file_change_scanner.py +0 -158
- basic_memory/sync/utils.py +0 -34
- basic_memory-0.2.12.dist-info/METADATA +0 -291
- basic_memory-0.2.12.dist-info/RECORD +0 -78
- {basic_memory-0.2.12.dist-info → basic_memory-0.16.1.dist-info}/WHEEL +0 -0
- {basic_memory-0.2.12.dist-info → basic_memory-0.16.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"""Service for resolving markdown links to permalinks."""
|
|
2
2
|
|
|
3
|
-
from typing import Optional, Tuple
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from loguru import logger
|
|
6
6
|
|
|
7
|
-
from basic_memory.repository.entity_repository import EntityRepository
|
|
8
|
-
from basic_memory.repository.search_repository import SearchIndexRow
|
|
9
|
-
from basic_memory.services.search_service import SearchService
|
|
10
7
|
from basic_memory.models import Entity
|
|
8
|
+
from basic_memory.repository.entity_repository import EntityRepository
|
|
11
9
|
from basic_memory.schemas.search import SearchQuery, SearchItemType
|
|
10
|
+
from basic_memory.services.search_service import SearchService
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class LinkResolver:
|
|
@@ -17,8 +16,9 @@ class LinkResolver:
|
|
|
17
16
|
Uses a combination of exact matching and search-based resolution:
|
|
18
17
|
1. Try exact permalink match (fastest)
|
|
19
18
|
2. Try exact title match
|
|
20
|
-
3.
|
|
21
|
-
4.
|
|
19
|
+
3. Try exact file path match
|
|
20
|
+
4. Try file path with .md extension (for folder/title patterns)
|
|
21
|
+
5. Fall back to search for fuzzy matching
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
def __init__(self, entity_repository: EntityRepository, search_service: SearchService):
|
|
@@ -26,9 +26,17 @@ class LinkResolver:
|
|
|
26
26
|
self.entity_repository = entity_repository
|
|
27
27
|
self.search_service = search_service
|
|
28
28
|
|
|
29
|
-
async def resolve_link(
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
async def resolve_link(
|
|
30
|
+
self, link_text: str, use_search: bool = True, strict: bool = False
|
|
31
|
+
) -> Optional[Entity]:
|
|
32
|
+
"""Resolve a markdown link to a permalink.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
link_text: The link text to resolve
|
|
36
|
+
use_search: Whether to use search-based fuzzy matching as fallback
|
|
37
|
+
strict: If True, only exact matches are allowed (no fuzzy search fallback)
|
|
38
|
+
"""
|
|
39
|
+
logger.trace(f"Resolving link: {link_text}")
|
|
32
40
|
|
|
33
41
|
# Clean link text and extract any alias
|
|
34
42
|
clean_text, alias = self._normalize_link_text(link_text)
|
|
@@ -40,26 +48,47 @@ class LinkResolver:
|
|
|
40
48
|
return entity
|
|
41
49
|
|
|
42
50
|
# 2. Try exact title match
|
|
43
|
-
|
|
44
|
-
if
|
|
51
|
+
found = await self.entity_repository.get_by_title(clean_text)
|
|
52
|
+
if found:
|
|
53
|
+
# Return first match if there are duplicates (consistent behavior)
|
|
54
|
+
entity = found[0]
|
|
45
55
|
logger.debug(f"Found title match: {entity.title}")
|
|
46
56
|
return entity
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
# 3. Try file path
|
|
59
|
+
found_path = await self.entity_repository.get_by_file_path(clean_text)
|
|
60
|
+
if found_path:
|
|
61
|
+
logger.debug(f"Found entity with path: {found_path.file_path}")
|
|
62
|
+
return found_path
|
|
63
|
+
|
|
64
|
+
# 4. Try file path with .md extension if not already present
|
|
65
|
+
if not clean_text.endswith(".md") and "/" in clean_text:
|
|
66
|
+
file_path_with_md = f"{clean_text}.md"
|
|
67
|
+
found_path_md = await self.entity_repository.get_by_file_path(file_path_with_md)
|
|
68
|
+
if found_path_md:
|
|
69
|
+
logger.debug(f"Found entity with path (with .md): {found_path_md.file_path}")
|
|
70
|
+
return found_path_md
|
|
71
|
+
|
|
72
|
+
# In strict mode, don't try fuzzy search - return None if no exact match found
|
|
73
|
+
if strict:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
# 5. Fall back to search for fuzzy matching (only if not in strict mode)
|
|
77
|
+
if use_search and "*" not in clean_text:
|
|
50
78
|
results = await self.search_service.search(
|
|
51
|
-
query=SearchQuery(
|
|
79
|
+
query=SearchQuery(text=clean_text, entity_types=[SearchItemType.ENTITY]),
|
|
52
80
|
)
|
|
53
81
|
|
|
54
82
|
if results:
|
|
55
83
|
# Look for best match
|
|
56
|
-
best_match =
|
|
57
|
-
logger.
|
|
84
|
+
best_match = min(results, key=lambda x: x.score) # pyright: ignore
|
|
85
|
+
logger.trace(
|
|
58
86
|
f"Selected best match from {len(results)} results: {best_match.permalink}"
|
|
59
87
|
)
|
|
60
|
-
|
|
88
|
+
if best_match.permalink:
|
|
89
|
+
return await self.entity_repository.get_by_permalink(best_match.permalink)
|
|
61
90
|
|
|
62
|
-
|
|
91
|
+
# if we couldn't find anything then return None
|
|
63
92
|
return None
|
|
64
93
|
|
|
65
94
|
def _normalize_link_text(self, link_text: str) -> Tuple[str, Optional[str]]:
|
|
@@ -84,43 +113,8 @@ class LinkResolver:
|
|
|
84
113
|
text, alias = text.split("|", 1)
|
|
85
114
|
text = text.strip()
|
|
86
115
|
alias = alias.strip()
|
|
116
|
+
else:
|
|
117
|
+
# Strip whitespace from text even if no alias
|
|
118
|
+
text = text.strip()
|
|
87
119
|
|
|
88
120
|
return text, alias
|
|
89
|
-
|
|
90
|
-
def _select_best_match(self, search_text: str, results: List[SearchIndexRow]) -> Entity:
|
|
91
|
-
"""Select best match from search results.
|
|
92
|
-
|
|
93
|
-
Uses multiple criteria:
|
|
94
|
-
1. Word matches in title field
|
|
95
|
-
2. Word matches in path
|
|
96
|
-
3. Overall search score
|
|
97
|
-
"""
|
|
98
|
-
# Get search terms for matching
|
|
99
|
-
terms = search_text.lower().split()
|
|
100
|
-
|
|
101
|
-
# Score each result
|
|
102
|
-
scored_results = []
|
|
103
|
-
for result in results:
|
|
104
|
-
# Start with base score (lower is better)
|
|
105
|
-
score = result.score
|
|
106
|
-
assert score is not None
|
|
107
|
-
|
|
108
|
-
# Parse path components
|
|
109
|
-
path_parts = result.permalink.lower().split("/")
|
|
110
|
-
last_part = path_parts[-1] if path_parts else ""
|
|
111
|
-
|
|
112
|
-
# Title word match boosts
|
|
113
|
-
term_matches = [term for term in terms if term in last_part]
|
|
114
|
-
if term_matches:
|
|
115
|
-
score *= 0.5 # Boost for each matching term
|
|
116
|
-
|
|
117
|
-
# Exact title match is best
|
|
118
|
-
if last_part == search_text.lower():
|
|
119
|
-
score *= 0.2
|
|
120
|
-
|
|
121
|
-
scored_results.append((score, result))
|
|
122
|
-
|
|
123
|
-
# Sort by score (lowest first) and return best
|
|
124
|
-
scored_results.sort(key=lambda x: x[0], reverse=True)
|
|
125
|
-
|
|
126
|
-
return scored_results[0][1]
|