basic-memory 0.13.0b5__tar.gz → 0.13.0b6__tar.gz

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.

Files changed (313) hide show
  1. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/workflows/release.yml +4 -10
  2. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/PKG-INFO +1 -1
  3. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/RELEASE_NOTES_v0.13.0.md +1 -0
  4. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/AI Assistant Guide.md +7 -0
  5. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/__init__.py +2 -1
  6. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/project.py +1 -1
  7. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/tool.py +6 -6
  8. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/prompts/recent_activity.py +1 -1
  9. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/read_note.py +2 -2
  10. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/view_note.py +1 -1
  11. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/repository/search_repository.py +17 -12
  12. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/entity_service.py +14 -1
  13. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/project_service.py +24 -3
  14. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/sync/sync_service.py +36 -11
  15. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/static/ai_assistant_guide.md +7 -0
  16. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_project_info.py +2 -2
  17. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_prompts.py +9 -9
  18. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_resource_project_info.py +2 -2
  19. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_resources.py +1 -1
  20. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_build_context.py +7 -7
  21. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_canvas.py +6 -6
  22. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_edit_note.py +33 -31
  23. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_list_directory.py +17 -17
  24. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_move_note.py +46 -46
  25. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_read_note.py +17 -17
  26. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_recent_activity.py +10 -10
  27. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_resource.py +10 -10
  28. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_search.py +18 -18
  29. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_sync_status.py +7 -7
  30. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_view_note.py +30 -26
  31. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_write_note.py +21 -21
  32. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/repository/test_search_repository.py +34 -0
  33. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_entity_service.py +113 -0
  34. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_project_service.py +132 -0
  35. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/sync/test_sync_service.py +222 -0
  36. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/uv.lock +3 -3
  37. basic_memory-0.13.0b5/TESTING.md +0 -337
  38. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/check-health.md +0 -0
  39. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/commands.md +0 -0
  40. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/lint-fix.md +0 -0
  41. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/release/beta.md +0 -0
  42. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/release/changelog.md +0 -0
  43. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/release/release-check.md +0 -0
  44. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/release/release.md +0 -0
  45. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/test-coverage.md +0 -0
  46. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.claude/commands/test-live.md +0 -0
  47. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.env.oauth.example +0 -0
  48. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  49. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  50. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  51. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  52. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/dependabot.yml +0 -0
  53. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/workflows/claude.yml +0 -0
  54. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/workflows/dev-release.yml +0 -0
  55. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/workflows/pr-title.yml +0 -0
  56. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.github/workflows/test.yml +0 -0
  57. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.gitignore +0 -0
  58. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.mcp.json +0 -0
  59. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/.python-version +0 -0
  60. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/AUTH.md +0 -0
  61. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/CHANGELOG.md +0 -0
  62. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/CITATION.cff +0 -0
  63. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/CLA.md +0 -0
  64. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/CLAUDE.md +0 -0
  65. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/CODE_OF_CONDUCT.md +0 -0
  66. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/CONTRIBUTING.md +0 -0
  67. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/Dockerfile +0 -0
  68. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/LICENSE +0 -0
  69. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/README.md +0 -0
  70. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/SECURITY.md +0 -0
  71. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/basic-memory.md +0 -0
  72. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/CLI Reference.md +0 -0
  73. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/Canvas.md +0 -0
  74. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/Claude.ai Integration.md +0 -0
  75. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/Getting Started with Basic Memory.md +0 -0
  76. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/Knowledge Format.md +0 -0
  77. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/OAuth Authentication Guide.md +0 -0
  78. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/Obsidian Integration.md +0 -0
  79. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/Supabase OAuth Setup.md +0 -0
  80. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/Technical Information.md +0 -0
  81. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/User Guide.md +0 -0
  82. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/Welcome to Basic memory.md +0 -0
  83. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/Canvas.png +0 -0
  84. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/Claude-Obsidian-Demo.mp4 +0 -0
  85. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/Prompt.png +0 -0
  86. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/disk-ai-logo-400x400.png +0 -0
  87. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/disk-ai-logo.png +0 -0
  88. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/prompt 1.png +0 -0
  89. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/prompt2.png +0 -0
  90. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/prompt3.png +0 -0
  91. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/attachments/prompt4.png +0 -0
  92. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/docs/publish.js +0 -0
  93. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/Coffee Notes/Brewing Equipment.md +0 -0
  94. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/Coffee Notes/Coffee Bean Origins.md +0 -0
  95. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/Coffee Notes/Coffee Brewing Methods.md +0 -0
  96. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/Coffee Notes/Coffee Flavor Map.md +0 -0
  97. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/Coffee Notes/Coffee Knowledge Base.md +0 -0
  98. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/Coffee Notes/Flavor Extraction.md +0 -0
  99. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/Coffee Notes/Perfect Pour Over Coffee Method.canvas +0 -0
  100. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/Coffee Notes/Tasting Notes.md +0 -0
  101. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/examples/testing/Test Note Creation - Basic Functionality.md +0 -0
  102. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/justfile +0 -0
  103. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/llms-install.md +0 -0
  104. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/memory.json +0 -0
  105. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/pyproject.toml +0 -0
  106. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/scripts/install.sh +0 -0
  107. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/smithery.yaml +0 -0
  108. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/alembic.ini +0 -0
  109. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/env.py +0 -0
  110. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/migrations.py +0 -0
  111. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/script.py.mako +0 -0
  112. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
  113. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +0 -0
  114. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +0 -0
  115. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +0 -0
  116. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +0 -0
  117. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +0 -0
  118. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/__init__.py +0 -0
  119. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/app.py +0 -0
  120. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/__init__.py +0 -0
  121. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/directory_router.py +0 -0
  122. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/importer_router.py +0 -0
  123. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/knowledge_router.py +0 -0
  124. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/management_router.py +0 -0
  125. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/memory_router.py +0 -0
  126. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/project_router.py +0 -0
  127. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/prompt_router.py +0 -0
  128. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/resource_router.py +0 -0
  129. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/search_router.py +0 -0
  130. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/routers/utils.py +0 -0
  131. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/api/template_loader.py +0 -0
  132. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/__init__.py +0 -0
  133. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/app.py +0 -0
  134. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/__init__.py +0 -0
  135. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/auth.py +0 -0
  136. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/db.py +0 -0
  137. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/import_chatgpt.py +0 -0
  138. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/import_claude_conversations.py +0 -0
  139. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/import_claude_projects.py +0 -0
  140. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/import_memory_json.py +0 -0
  141. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/mcp.py +0 -0
  142. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/status.py +0 -0
  143. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/commands/sync.py +0 -0
  144. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/cli/main.py +0 -0
  145. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/config.py +0 -0
  146. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/db.py +0 -0
  147. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/deps.py +0 -0
  148. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/file_utils.py +0 -0
  149. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/importers/__init__.py +0 -0
  150. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/importers/base.py +0 -0
  151. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/importers/chatgpt_importer.py +0 -0
  152. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/importers/claude_conversations_importer.py +0 -0
  153. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/importers/claude_projects_importer.py +0 -0
  154. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/importers/memory_json_importer.py +0 -0
  155. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/importers/utils.py +0 -0
  156. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/markdown/__init__.py +0 -0
  157. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/markdown/entity_parser.py +0 -0
  158. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/markdown/markdown_processor.py +0 -0
  159. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/markdown/plugins.py +0 -0
  160. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/markdown/schemas.py +0 -0
  161. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/markdown/utils.py +0 -0
  162. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/__init__.py +0 -0
  163. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/async_client.py +0 -0
  164. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/auth_provider.py +0 -0
  165. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/external_auth_provider.py +0 -0
  166. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/project_session.py +0 -0
  167. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/prompts/__init__.py +0 -0
  168. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/prompts/ai_assistant_guide.py +0 -0
  169. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/prompts/continue_conversation.py +0 -0
  170. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/prompts/search.py +0 -0
  171. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/prompts/sync_status.py +0 -0
  172. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/prompts/utils.py +0 -0
  173. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/resources/ai_assistant_guide.md +0 -0
  174. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/resources/project_info.py +0 -0
  175. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/server.py +0 -0
  176. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/supabase_auth_provider.py +0 -0
  177. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/__init__.py +0 -0
  178. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/build_context.py +0 -0
  179. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/canvas.py +0 -0
  180. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/delete_note.py +0 -0
  181. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/edit_note.py +0 -0
  182. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/list_directory.py +0 -0
  183. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/move_note.py +0 -0
  184. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/project_management.py +0 -0
  185. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/read_content.py +0 -0
  186. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/recent_activity.py +0 -0
  187. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/search.py +0 -0
  188. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/sync_status.py +0 -0
  189. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/utils.py +0 -0
  190. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/mcp/tools/write_note.py +0 -0
  191. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/models/__init__.py +0 -0
  192. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/models/base.py +0 -0
  193. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/models/knowledge.py +0 -0
  194. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/models/project.py +0 -0
  195. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/models/search.py +0 -0
  196. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/repository/__init__.py +0 -0
  197. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/repository/entity_repository.py +0 -0
  198. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/repository/observation_repository.py +0 -0
  199. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/repository/project_info_repository.py +0 -0
  200. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/repository/project_repository.py +0 -0
  201. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/repository/relation_repository.py +0 -0
  202. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/repository/repository.py +0 -0
  203. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/__init__.py +0 -0
  204. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/base.py +0 -0
  205. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/delete.py +0 -0
  206. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/directory.py +0 -0
  207. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/importer.py +0 -0
  208. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/memory.py +0 -0
  209. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/project_info.py +0 -0
  210. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/prompt.py +0 -0
  211. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/request.py +0 -0
  212. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/response.py +0 -0
  213. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/schemas/search.py +0 -0
  214. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/__init__.py +0 -0
  215. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/context_service.py +0 -0
  216. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/directory_service.py +0 -0
  217. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/exceptions.py +0 -0
  218. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/file_service.py +0 -0
  219. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/initialization.py +0 -0
  220. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/link_resolver.py +0 -0
  221. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/migration_service.py +0 -0
  222. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/search_service.py +0 -0
  223. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/service.py +0 -0
  224. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/services/sync_status_service.py +0 -0
  225. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/sync/__init__.py +0 -0
  226. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/sync/background_sync.py +0 -0
  227. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/sync/watch_service.py +0 -0
  228. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/templates/prompts/continue_conversation.hbs +0 -0
  229. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/templates/prompts/search.hbs +0 -0
  230. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/src/basic_memory/utils.py +0 -0
  231. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/static/json_canvas_spec_1_0.md +0 -0
  232. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/conftest.py +0 -0
  233. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_build_context_validation.py +0 -0
  234. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_delete_note_integration.py +0 -0
  235. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_edit_note_integration.py +0 -0
  236. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_list_directory_integration.py +0 -0
  237. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_move_note_integration.py +0 -0
  238. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_project_management_integration.py +0 -0
  239. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_read_content_integration.py +0 -0
  240. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_read_note_integration.py +0 -0
  241. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_search_integration.py +0 -0
  242. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/test-int/mcp/test_write_note_integration.py +0 -0
  243. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/Non-MarkdownFileSupport.pdf +0 -0
  244. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/Screenshot.png +0 -0
  245. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/__init__.py +0 -0
  246. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/conftest.py +0 -0
  247. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_continue_conversation_template.py +0 -0
  248. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_directory_router.py +0 -0
  249. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_importer_router.py +0 -0
  250. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_knowledge_router.py +0 -0
  251. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_management_router.py +0 -0
  252. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_memory_router.py +0 -0
  253. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_project_router.py +0 -0
  254. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_project_router_operations.py +0 -0
  255. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_prompt_router.py +0 -0
  256. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_resource_router.py +0 -0
  257. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_search_router.py +0 -0
  258. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_search_template.py +0 -0
  259. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_template_loader.py +0 -0
  260. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/api/test_template_loader_helpers.py +0 -0
  261. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/conftest.py +0 -0
  262. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_auth_commands.py +0 -0
  263. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_cli_tools.py +0 -0
  264. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_import_chatgpt.py +0 -0
  265. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_import_claude_conversations.py +0 -0
  266. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_import_claude_projects.py +0 -0
  267. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_import_memory_json.py +0 -0
  268. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_project_commands.py +0 -0
  269. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_status.py +0 -0
  270. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_sync.py +0 -0
  271. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/cli/test_version.py +0 -0
  272. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/conftest.py +0 -0
  273. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/importers/test_importer_base.py +0 -0
  274. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/importers/test_importer_utils.py +0 -0
  275. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/markdown/__init__.py +0 -0
  276. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/markdown/test_entity_parser.py +0 -0
  277. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/markdown/test_markdown_plugins.py +0 -0
  278. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/markdown/test_markdown_processor.py +0 -0
  279. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/markdown/test_observation_edge_cases.py +0 -0
  280. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/markdown/test_parser_edge_cases.py +0 -0
  281. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/markdown/test_relation_edge_cases.py +0 -0
  282. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/markdown/test_task_detection.py +0 -0
  283. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/conftest.py +0 -0
  284. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_auth_provider.py +0 -0
  285. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_server.py +0 -0
  286. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_delete_note.py +0 -0
  287. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/mcp/test_tool_utils.py +0 -0
  288. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/repository/test_entity_repository.py +0 -0
  289. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/repository/test_observation_repository.py +0 -0
  290. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/repository/test_project_info_repository.py +0 -0
  291. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/repository/test_project_repository.py +0 -0
  292. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/repository/test_relation_repository.py +0 -0
  293. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/repository/test_repository.py +0 -0
  294. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/schemas/test_memory_url.py +0 -0
  295. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/schemas/test_memory_url_validation.py +0 -0
  296. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/schemas/test_schemas.py +0 -0
  297. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/schemas/test_search.py +0 -0
  298. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_context_service.py +0 -0
  299. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_directory_service.py +0 -0
  300. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_file_service.py +0 -0
  301. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_initialization.py +0 -0
  302. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_link_resolver.py +0 -0
  303. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_project_service_operations.py +0 -0
  304. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_search_service.py +0 -0
  305. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/services/test_sync_status_service.py +0 -0
  306. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/sync/test_sync_wikilink_issue.py +0 -0
  307. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/sync/test_tmp_files.py +0 -0
  308. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/sync/test_watch_service.py +0 -0
  309. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/sync/test_watch_service_edge_cases.py +0 -0
  310. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/utils/test_file_utils.py +0 -0
  311. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/utils/test_parse_tags.py +0 -0
  312. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/utils/test_permalink_formatting.py +0 -0
  313. {basic_memory-0.13.0b5 → basic_memory-0.13.0b6}/tests/utils/test_utf8_handling.py +0 -0
@@ -32,17 +32,11 @@ jobs:
32
32
  uv sync
33
33
  uv build
34
34
 
35
- - name: Verify version matches tag
35
+ - name: Verify build succeeded
36
36
  run: |
37
- # Get version from built package
38
- PACKAGE_VERSION=$(uv run python -c "import basic_memory; print(basic_memory.__version__)")
39
- TAG_VERSION=${GITHUB_REF_NAME#v} # Remove 'v' prefix from tag
40
- echo "Package version: $PACKAGE_VERSION"
41
- echo "Tag version: $TAG_VERSION"
42
- if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then
43
- echo "Version mismatch! Package: $PACKAGE_VERSION, Tag: $TAG_VERSION"
44
- exit 1
45
- fi
37
+ # Verify that build artifacts exist
38
+ ls -la dist/
39
+ echo "Build completed successfully"
46
40
 
47
41
  - name: Create GitHub Release
48
42
  uses: softprops/action-gh-release@v2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-memory
3
- Version: 0.13.0b5
3
+ Version: 0.13.0b6
4
4
  Summary: Local-first knowledge management combining Zettelkasten with knowledge graphs
5
5
  Project-URL: Homepage, https://github.com/basicmachines-co/basic-memory
6
6
  Project-URL: Repository, https://github.com/basicmachines-co/basic-memory
@@ -16,6 +16,7 @@ Basic Memory v0.13.0 is a **major release** that transforms Basic Memory into a
16
16
  **Key v0.13.0 Accomplishments:**
17
17
  - ✅ **Complete Project Management System** - Project switching and project-specific operations
18
18
  - ✅ **Advanced Note Editing** - Incremental editing with append, prepend, find/replace, and section operations
19
+ - ✅ **View Notes as Artifacts in Claude Desktop/Web** - Use the view_note tool to view a note as an artifact
19
20
  - ✅ **File Management System** - Full move operations with database consistency and rollback protection
20
21
  - ✅ **Enhanced Search Capabilities** - Frontmatter tags now searchable, improved content discoverability
21
22
  - ✅ **Unified Database Architecture** - Single app-level database for better performance and project management
@@ -77,6 +77,13 @@ read_note("specs/search-design") # By path
77
77
  read_note("memory://specs/search") # By memory URL
78
78
  ```
79
79
 
80
+ **Viewing notes as formatted artifacts (Claude Desktop):**
81
+ ```
82
+ view_note("Search Design") # Creates readable artifact
83
+ view_note("specs/search-design") # By permalink
84
+ view_note("memory://specs/search") # By memory URL
85
+ ```
86
+
80
87
  **Incremental editing** (v0.13.0):
81
88
  ```
82
89
  edit_note(
@@ -1,3 +1,4 @@
1
1
  """basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
2
2
 
3
- __version__ = "0.13.0b5"
3
+ # API version for FastAPI - independent of package version
4
+ __version__ = "v0"
@@ -174,7 +174,7 @@ def display_project_info(
174
174
  """Display detailed information and statistics about the current project."""
175
175
  try:
176
176
  # Get project info
177
- info = asyncio.run(project_info())
177
+ info = asyncio.run(project_info.fn()) # type: ignore # pyright: ignore [reportAttributeAccessIssue]
178
178
 
179
179
  if json_output:
180
180
  # Convert to JSON and print
@@ -90,7 +90,7 @@ def write_note(
90
90
  typer.echo("Empty content provided. Please provide non-empty content.", err=True)
91
91
  raise typer.Exit(1)
92
92
 
93
- note = asyncio.run(mcp_write_note(title, content, folder, tags))
93
+ note = asyncio.run(mcp_write_note.fn(title, content, folder, tags))
94
94
  rprint(note)
95
95
  except Exception as e: # pragma: no cover
96
96
  if not isinstance(e, typer.Exit):
@@ -103,7 +103,7 @@ def write_note(
103
103
  def read_note(identifier: str, page: int = 1, page_size: int = 10):
104
104
  """Read a markdown note from the knowledge base."""
105
105
  try:
106
- note = asyncio.run(mcp_read_note(identifier, page, page_size))
106
+ note = asyncio.run(mcp_read_note.fn(identifier, page, page_size))
107
107
  rprint(note)
108
108
  except Exception as e: # pragma: no cover
109
109
  if not isinstance(e, typer.Exit):
@@ -124,7 +124,7 @@ def build_context(
124
124
  """Get context needed to continue a discussion."""
125
125
  try:
126
126
  context = asyncio.run(
127
- mcp_build_context(
127
+ mcp_build_context.fn(
128
128
  url=url,
129
129
  depth=depth,
130
130
  timeframe=timeframe,
@@ -157,7 +157,7 @@ def recent_activity(
157
157
  """Get recent activity across the knowledge base."""
158
158
  try:
159
159
  context = asyncio.run(
160
- mcp_recent_activity(
160
+ mcp_recent_activity.fn(
161
161
  type=type, # pyright: ignore [reportArgumentType]
162
162
  depth=depth,
163
163
  timeframe=timeframe,
@@ -210,7 +210,7 @@ def search_notes(
210
210
  search_type = "text" if search_type is None else search_type
211
211
 
212
212
  results = asyncio.run(
213
- mcp_search(
213
+ mcp_search.fn(
214
214
  query,
215
215
  search_type=search_type,
216
216
  page=page,
@@ -241,7 +241,7 @@ def continue_conversation(
241
241
  """Prompt to continue a previous conversation or work session."""
242
242
  try:
243
243
  # Prompt functions return formatted strings directly
244
- session = asyncio.run(mcp_continue_conversation(topic=topic, timeframe=timeframe))
244
+ session = asyncio.run(mcp_continue_conversation.fn(topic=topic, timeframe=timeframe)) # type: ignore
245
245
  rprint(session)
246
246
  except Exception as e: # pragma: no cover
247
247
  if not isinstance(e, typer.Exit):
@@ -38,7 +38,7 @@ async def recent_activity_prompt(
38
38
  """
39
39
  logger.info(f"Getting recent activity, timeframe: {timeframe}")
40
40
 
41
- recent = await recent_activity(timeframe=timeframe, type=[SearchItemType.ENTITY])
41
+ recent = await recent_activity.fn(timeframe=timeframe, type=[SearchItemType.ENTITY])
42
42
 
43
43
  # Extract primary results from the hierarchical structure
44
44
  primary_results = []
@@ -81,7 +81,7 @@ async def read_note(
81
81
 
82
82
  # Fallback 1: Try title search via API
83
83
  logger.info(f"Search title for: {identifier}")
84
- title_results = await search_notes(query=identifier, search_type="title", project=project)
84
+ title_results = await search_notes.fn(query=identifier, search_type="title", project=project)
85
85
 
86
86
  if title_results and title_results.results:
87
87
  result = title_results.results[0] # Get the first/best match
@@ -105,7 +105,7 @@ async def read_note(
105
105
 
106
106
  # Fallback 2: Text search as a last resort
107
107
  logger.info(f"Title search failed, trying text search for: {identifier}")
108
- text_results = await search_notes(query=identifier, search_type="text", project=project)
108
+ text_results = await search_notes.fn(query=identifier, search_type="text", project=project)
109
109
 
110
110
  # We didn't find a direct match, construct a helpful error message
111
111
  if not text_results or not text_results.results:
@@ -37,7 +37,7 @@ async def view_note(
37
37
  logger.info(f"Viewing note: {identifier}")
38
38
 
39
39
  # Call the existing read_note logic
40
- content = await read_note(identifier, page, page_size, project)
40
+ content = await read_note.fn(identifier, page, page_size, project)
41
41
 
42
42
  # Check if this is an error message (note not found)
43
43
  if "# Note Not Found:" in content:
@@ -237,19 +237,24 @@ class SearchRepository:
237
237
 
238
238
  # Handle text search for title and content
239
239
  if search_text:
240
- # Check for explicit boolean operators - only detect them in proper boolean contexts
241
- has_boolean = any(op in f" {search_text} " for op in [" AND ", " OR ", " NOT "])
242
-
243
- if has_boolean:
244
- # If boolean operators are present, use the raw query
245
- # No need to prepare it, FTS5 will understand the operators
246
- params["text"] = search_text
247
- conditions.append("(title MATCH :text OR content_stems MATCH :text)")
240
+ # Skip FTS for wildcard-only queries that would cause "unknown special query" errors
241
+ if search_text.strip() == "*" or search_text.strip() == "":
242
+ # For wildcard searches, don't add any text conditions - return all results
243
+ pass
248
244
  else:
249
- # Standard search with term preparation
250
- processed_text = self._prepare_search_term(search_text.strip())
251
- params["text"] = processed_text
252
- conditions.append("(title MATCH :text OR content_stems MATCH :text)")
245
+ # Check for explicit boolean operators - only detect them in proper boolean contexts
246
+ has_boolean = any(op in f" {search_text} " for op in [" AND ", " OR ", " NOT "])
247
+
248
+ if has_boolean:
249
+ # If boolean operators are present, use the raw query
250
+ # No need to prepare it, FTS5 will understand the operators
251
+ params["text"] = search_text
252
+ conditions.append("(title MATCH :text OR content_stems MATCH :text)")
253
+ else:
254
+ # Standard search with term preparation
255
+ processed_text = self._prepare_search_term(search_text.strip())
256
+ params["text"] = processed_text
257
+ conditions.append("(title MATCH :text OR content_stems MATCH :text)")
253
258
 
254
259
  # Handle title match search
255
260
  if title:
@@ -299,7 +299,20 @@ class EntityService(BaseService[EntityModel]):
299
299
  # Mark as incomplete because we still need to add relations
300
300
  model.checksum = None
301
301
  # Repository will set project_id automatically
302
- return await self.repository.add(model)
302
+ try:
303
+ return await self.repository.add(model)
304
+ except IntegrityError as e:
305
+ # Handle race condition where entity was created by another process
306
+ if "UNIQUE constraint failed: entity.file_path" in str(
307
+ e
308
+ ) or "UNIQUE constraint failed: entity.permalink" in str(e):
309
+ logger.info(
310
+ f"Entity already exists for file_path={file_path} (file_path or permalink conflict), updating instead of creating"
311
+ )
312
+ return await self.update_entity_and_observations(file_path, markdown)
313
+ else:
314
+ # Re-raise if it's a different integrity error
315
+ raise
303
316
 
304
317
  async def update_entity_and_observations(
305
318
  self, file_path: Path, markdown: EntityMarkdown
@@ -209,8 +209,29 @@ class ProjectService:
209
209
  db_projects = await self.repository.get_active_projects()
210
210
  db_projects_by_name = {p.name: p for p in db_projects}
211
211
 
212
- # Get all projects from configuration
213
- config_projects = config_manager.projects
212
+ # Get all projects from configuration and normalize names if needed
213
+ config_projects = config_manager.projects.copy()
214
+ updated_config = {}
215
+ config_updated = False
216
+
217
+ for name, path in config_projects.items():
218
+ # Generate normalized name (what the database expects)
219
+ normalized_name = generate_permalink(name)
220
+
221
+ if normalized_name != name:
222
+ logger.info(f"Normalizing project name in config: '{name}' -> '{normalized_name}'")
223
+ config_updated = True
224
+
225
+ updated_config[normalized_name] = path
226
+
227
+ # Update the configuration if any changes were made
228
+ if config_updated:
229
+ config_manager.config.projects = updated_config
230
+ config_manager.save_config(config_manager.config)
231
+ logger.info("Config updated with normalized project names")
232
+
233
+ # Use the normalized config for further processing
234
+ config_projects = updated_config
214
235
 
215
236
  # Add projects that exist in config but not in DB
216
237
  for name, path in config_projects.items():
@@ -219,7 +240,7 @@ class ProjectService:
219
240
  project_data = {
220
241
  "name": name,
221
242
  "path": path,
222
- "permalink": name.lower().replace(" ", "-"),
243
+ "permalink": generate_permalink(name),
223
244
  "is_active": True,
224
245
  # Don't set is_default here - let the enforcement logic handle it
225
246
  }
@@ -364,18 +364,43 @@ class SyncService:
364
364
  content_type = self.file_service.content_type(path)
365
365
 
366
366
  file_path = Path(path)
367
- entity = await self.entity_repository.add(
368
- Entity(
369
- entity_type="file",
370
- file_path=path,
371
- checksum=checksum,
372
- title=file_path.name,
373
- created_at=created,
374
- updated_at=modified,
375
- content_type=content_type,
367
+ try:
368
+ entity = await self.entity_repository.add(
369
+ Entity(
370
+ entity_type="file",
371
+ file_path=path,
372
+ checksum=checksum,
373
+ title=file_path.name,
374
+ created_at=created,
375
+ updated_at=modified,
376
+ content_type=content_type,
377
+ )
376
378
  )
377
- )
378
- return entity, checksum
379
+ return entity, checksum
380
+ except IntegrityError as e:
381
+ # Handle race condition where entity was created by another process
382
+ if "UNIQUE constraint failed: entity.file_path" in str(e):
383
+ logger.info(
384
+ f"Entity already exists for file_path={path}, updating instead of creating"
385
+ )
386
+ # Treat as update instead of create
387
+ entity = await self.entity_repository.get_by_file_path(path)
388
+ if entity is None: # pragma: no cover
389
+ logger.error(f"Entity not found after constraint violation, path={path}")
390
+ raise ValueError(f"Entity not found after constraint violation: {path}")
391
+
392
+ updated = await self.entity_repository.update(
393
+ entity.id, {"file_path": path, "checksum": checksum}
394
+ )
395
+
396
+ if updated is None: # pragma: no cover
397
+ logger.error(f"Failed to update entity, entity_id={entity.id}, path={path}")
398
+ raise ValueError(f"Failed to update entity with ID {entity.id}")
399
+
400
+ return updated, checksum
401
+ else:
402
+ # Re-raise if it's a different integrity error
403
+ raise
379
404
  else:
380
405
  entity = await self.entity_repository.get_by_file_path(path)
381
406
  if entity is None: # pragma: no cover
@@ -50,6 +50,13 @@ read_note("specs/search-design") # By permalink
50
50
  read_note("memory://specs/search") # By memory URL
51
51
  ```
52
52
 
53
+ **Viewing notes as formatted artifacts (Claude Desktop):**
54
+ ```
55
+ view_note("Search Design") # Creates readable artifact
56
+ view_note("specs/search-design") # By permalink
57
+ view_note("memory://specs/search") # By memory URL
58
+ ```
59
+
53
60
  **Incremental editing (v0.13.0) - REQUIRES EXACT IDENTIFIERS:**
54
61
  ```
55
62
  edit_note(
@@ -48,7 +48,7 @@ def test_info_stats():
48
48
 
49
49
  # Mock the async project_info function
50
50
  with patch(
51
- "basic_memory.cli.commands.project.project_info", new_callable=AsyncMock
51
+ "basic_memory.cli.commands.project.project_info.fn", new_callable=AsyncMock
52
52
  ) as mock_func:
53
53
  mock_func.return_value = mock_info
54
54
 
@@ -97,7 +97,7 @@ def test_info_stats_json():
97
97
 
98
98
  # Mock the async project_info function
99
99
  with patch(
100
- "basic_memory.cli.commands.project.project_info", new_callable=AsyncMock
100
+ "basic_memory.cli.commands.project.project_info.fn", new_callable=AsyncMock
101
101
  ) as mock_func:
102
102
  mock_func.return_value = mock_info
103
103
 
@@ -15,7 +15,7 @@ async def test_continue_conversation_with_topic(client, test_graph):
15
15
  # We can use the test_graph fixture which already has relevant content
16
16
 
17
17
  # Call the function with a topic that should match existing content
18
- result = await continue_conversation(topic="Root", timeframe="1w")
18
+ result = await continue_conversation.fn(topic="Root", timeframe="1w")
19
19
 
20
20
  # Check that the result contains expected content
21
21
  assert "Continuing conversation on: Root" in result
@@ -27,7 +27,7 @@ async def test_continue_conversation_with_topic(client, test_graph):
27
27
  async def test_continue_conversation_with_recent_activity(client, test_graph):
28
28
  """Test continue_conversation with no topic, using recent activity."""
29
29
  # Call the function without a topic
30
- result = await continue_conversation(timeframe="1w")
30
+ result = await continue_conversation.fn(timeframe="1w")
31
31
 
32
32
  # Check that the result contains expected content for recent activity
33
33
  assert "Continuing conversation on: Recent Activity" in result
@@ -40,7 +40,7 @@ async def test_continue_conversation_with_recent_activity(client, test_graph):
40
40
  async def test_continue_conversation_no_results(client):
41
41
  """Test continue_conversation when no results are found."""
42
42
  # Call with a non-existent topic
43
- result = await continue_conversation(topic="NonExistentTopic", timeframe="1w")
43
+ result = await continue_conversation.fn(topic="NonExistentTopic", timeframe="1w")
44
44
 
45
45
  # Check the response indicates no results found
46
46
  assert "Continuing conversation on: NonExistentTopic" in result
@@ -51,7 +51,7 @@ async def test_continue_conversation_no_results(client):
51
51
  async def test_continue_conversation_creates_structured_suggestions(client, test_graph):
52
52
  """Test that continue_conversation generates structured tool usage suggestions."""
53
53
  # Call the function with a topic that should match existing content
54
- result = await continue_conversation(topic="Root", timeframe="1w")
54
+ result = await continue_conversation.fn(topic="Root", timeframe="1w")
55
55
 
56
56
  # Verify the response includes clear tool usage instructions
57
57
  assert "start by executing one of the suggested commands" in result.lower()
@@ -69,7 +69,7 @@ async def test_continue_conversation_creates_structured_suggestions(client, test
69
69
  async def test_search_prompt_with_results(client, test_graph):
70
70
  """Test search_prompt with a query that returns results."""
71
71
  # Call the function with a query that should match existing content
72
- result = await search_prompt("Root")
72
+ result = await search_prompt.fn("Root")
73
73
 
74
74
  # Check the response contains expected content
75
75
  assert 'Search Results for: "Root"' in result
@@ -82,7 +82,7 @@ async def test_search_prompt_with_results(client, test_graph):
82
82
  async def test_search_prompt_with_timeframe(client, test_graph):
83
83
  """Test search_prompt with a timeframe."""
84
84
  # Call the function with a query and timeframe
85
- result = await search_prompt("Root", timeframe="1w")
85
+ result = await search_prompt.fn("Root", timeframe="1w")
86
86
 
87
87
  # Check the response includes timeframe information
88
88
  assert 'Search Results for: "Root" (after 7d)' in result
@@ -93,7 +93,7 @@ async def test_search_prompt_with_timeframe(client, test_graph):
93
93
  async def test_search_prompt_no_results(client):
94
94
  """Test search_prompt when no results are found."""
95
95
  # Call with a query that won't match anything
96
- result = await search_prompt("XYZ123NonExistentQuery")
96
+ result = await search_prompt.fn("XYZ123NonExistentQuery")
97
97
 
98
98
  # Check the response indicates no results found
99
99
  assert 'Search Results for: "XYZ123NonExistentQuery"' in result
@@ -149,7 +149,7 @@ def test_prompt_context_with_file_path_no_permalink():
149
149
  async def test_recent_activity_prompt(client, test_graph):
150
150
  """Test recent_activity_prompt."""
151
151
  # Call the function
152
- result = await recent_activity_prompt(timeframe="1w")
152
+ result = await recent_activity_prompt.fn(timeframe="1w")
153
153
 
154
154
  # Check the response contains expected content
155
155
  assert "Recent Activity" in result
@@ -161,7 +161,7 @@ async def test_recent_activity_prompt(client, test_graph):
161
161
  async def test_recent_activity_prompt_with_custom_timeframe(client, test_graph):
162
162
  """Test recent_activity_prompt with custom timeframe."""
163
163
  # Call the function with a custom timeframe
164
- result = await recent_activity_prompt(timeframe="1d")
164
+ result = await recent_activity_prompt.fn(timeframe="1d")
165
165
 
166
166
  # Check the response includes the custom timeframe
167
167
  assert "Recent Activity from (1d)" in result
@@ -97,7 +97,7 @@ async def test_project_info_tool():
97
97
  "basic_memory.mcp.resources.project_info.call_get", return_value=mock_response
98
98
  ) as mock_call_get:
99
99
  # Call the function
100
- result = await project_info()
100
+ result = await project_info.fn()
101
101
 
102
102
  # Verify that call_get was called with the correct URL
103
103
  mock_call_get.assert_called_once()
@@ -138,7 +138,7 @@ async def test_project_info_error_handling():
138
138
  ):
139
139
  # Verify that the exception propagates
140
140
  with pytest.raises(Exception) as excinfo:
141
- await project_info()
141
+ await project_info.fn()
142
142
 
143
143
  # Verify error message
144
144
  assert "Test error" in str(excinfo.value)
@@ -8,7 +8,7 @@ import pytest
8
8
  async def test_ai_assistant_guide_exists(app):
9
9
  """Test that the canvas spec resource exists and returns content."""
10
10
  # Call the resource function
11
- guide = ai_assistant_guide()
11
+ guide = ai_assistant_guide.fn()
12
12
 
13
13
  # Verify basic characteristics of the content
14
14
  assert guide is not None
@@ -14,7 +14,7 @@ from basic_memory.schemas.memory import (
14
14
  @pytest.mark.asyncio
15
15
  async def test_get_basic_discussion_context(client, test_graph):
16
16
  """Test getting basic discussion context."""
17
- context = await build_context(url="memory://test/root")
17
+ context = await build_context.fn(url="memory://test/root")
18
18
 
19
19
  assert isinstance(context, GraphContext)
20
20
  assert len(context.results) == 1
@@ -33,7 +33,7 @@ async def test_get_basic_discussion_context(client, test_graph):
33
33
  @pytest.mark.asyncio
34
34
  async def test_get_discussion_context_pattern(client, test_graph):
35
35
  """Test getting context with pattern matching."""
36
- context = await build_context(url="memory://test/*", depth=1)
36
+ context = await build_context.fn(url="memory://test/*", depth=1)
37
37
 
38
38
  assert isinstance(context, GraphContext)
39
39
  assert len(context.results) > 1 # Should match multiple test/* paths
@@ -45,13 +45,13 @@ async def test_get_discussion_context_pattern(client, test_graph):
45
45
  async def test_get_discussion_context_timeframe(client, test_graph):
46
46
  """Test timeframe parameter filtering."""
47
47
  # Get recent context
48
- recent_context = await build_context(
48
+ recent_context = await build_context.fn(
49
49
  url="memory://test/root",
50
50
  timeframe="1d", # Last 24 hours
51
51
  )
52
52
 
53
53
  # Get older context
54
- older_context = await build_context(
54
+ older_context = await build_context.fn(
55
55
  url="memory://test/root",
56
56
  timeframe="30d", # Last 30 days
57
57
  )
@@ -74,7 +74,7 @@ async def test_get_discussion_context_timeframe(client, test_graph):
74
74
  @pytest.mark.asyncio
75
75
  async def test_get_discussion_context_not_found(client):
76
76
  """Test handling of non-existent URIs."""
77
- context = await build_context(url="memory://test/does-not-exist")
77
+ context = await build_context.fn(url="memory://test/does-not-exist")
78
78
 
79
79
  assert isinstance(context, GraphContext)
80
80
  assert len(context.results) == 0
@@ -103,7 +103,7 @@ async def test_build_context_timeframe_formats(client, test_graph):
103
103
  # Test each valid timeframe
104
104
  for timeframe in valid_timeframes:
105
105
  try:
106
- result = await build_context(
106
+ result = await build_context.fn(
107
107
  url=test_url, timeframe=timeframe, page=1, page_size=10, max_related=10
108
108
  )
109
109
  assert result is not None
@@ -113,4 +113,4 @@ async def test_build_context_timeframe_formats(client, test_graph):
113
113
  # Test invalid timeframes should raise ValidationError
114
114
  for timeframe in invalid_timeframes:
115
115
  with pytest.raises(ToolError):
116
- await build_context(url=test_url, timeframe=timeframe)
116
+ await build_context.fn(url=test_url, timeframe=timeframe)
@@ -34,7 +34,7 @@ async def test_create_canvas(app, project_config):
34
34
  folder = "visualizations"
35
35
 
36
36
  # Execute
37
- result = await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
37
+ result = await canvas.fn(nodes=nodes, edges=edges, title=title, folder=folder)
38
38
 
39
39
  # Verify result message
40
40
  assert result
@@ -71,7 +71,7 @@ async def test_create_canvas_with_extension(app, project_config):
71
71
  folder = "visualizations"
72
72
 
73
73
  # Execute
74
- result = await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
74
+ result = await canvas.fn(nodes=nodes, edges=edges, title=title, folder=folder)
75
75
 
76
76
  # Verify
77
77
  assert "Created: visualizations/extension-test.canvas" in result
@@ -105,7 +105,7 @@ async def test_update_existing_canvas(app, project_config):
105
105
  folder = "visualizations"
106
106
 
107
107
  # Create initial canvas
108
- await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
108
+ await canvas.fn(nodes=nodes, edges=edges, title=title, folder=folder)
109
109
 
110
110
  # Verify file exists
111
111
  file_path = Path(project_config.home) / folder / f"{title}.canvas"
@@ -128,7 +128,7 @@ async def test_update_existing_canvas(app, project_config):
128
128
  ]
129
129
 
130
130
  # Execute update
131
- result = await canvas(nodes=updated_nodes, edges=updated_edges, title=title, folder=folder)
131
+ result = await canvas.fn(nodes=updated_nodes, edges=updated_edges, title=title, folder=folder)
132
132
 
133
133
  # Verify result indicates update
134
134
  assert "Updated: visualizations/update-test.canvas" in result
@@ -159,7 +159,7 @@ async def test_create_canvas_with_nested_folders(app, project_config):
159
159
  folder = "visualizations/nested/folders" # Deep path
160
160
 
161
161
  # Execute
162
- result = await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
162
+ result = await canvas.fn(nodes=nodes, edges=edges, title=title, folder=folder)
163
163
 
164
164
  # Verify
165
165
  assert "Created: visualizations/nested/folders/nested-test.canvas" in result
@@ -242,7 +242,7 @@ async def test_create_canvas_complex_content(app, project_config):
242
242
  test_file_path.write_text("# Test File\nThis is referenced by the canvas")
243
243
 
244
244
  # Execute
245
- result = await canvas(nodes=nodes, edges=edges, title=title, folder=folder)
245
+ result = await canvas.fn(nodes=nodes, edges=edges, title=title, folder=folder)
246
246
 
247
247
  # Verify
248
248
  assert "Created: visualizations/complex-test.canvas" in result