basic-memory 0.12.2__tar.gz → 0.12.3__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 (227) hide show
  1. {basic_memory-0.12.2 → basic_memory-0.12.3}/CHANGELOG.md +15 -0
  2. {basic_memory-0.12.2 → basic_memory-0.12.3}/CLAUDE.md +15 -1
  3. {basic_memory-0.12.2 → basic_memory-0.12.3}/PKG-INFO +3 -3
  4. {basic_memory-0.12.2 → basic_memory-0.12.3}/README.md +2 -2
  5. {basic_memory-0.12.2 → basic_memory-0.12.3}/pyproject.toml +1 -1
  6. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/__init__.py +1 -1
  7. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/read_note.py +2 -2
  8. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/recent_activity.py +37 -15
  9. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/utils.py +67 -17
  10. basic_memory-0.12.2/tests/mcp/test_tool_memory.py → basic_memory-0.12.3/tests/mcp/test_tool_build_context.py +1 -51
  11. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_read_note.py +1 -1
  12. basic_memory-0.12.3/tests/mcp/test_tool_recent_activity.py +110 -0
  13. basic_memory-0.12.3/tests/utils/test_permalink_formatting.py +120 -0
  14. {basic_memory-0.12.2 → basic_memory-0.12.3}/uv.lock +1 -1
  15. basic_memory-0.12.2/tests/utils/test_permalink_formatting.py +0 -68
  16. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  17. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  18. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  19. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/dependabot.yml +0 -0
  21. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/workflows/claude-code-actions.yml +0 -0
  22. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/workflows/pr-title.yml +0 -0
  23. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/workflows/release.yml +0 -0
  24. {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/workflows/test.yml +0 -0
  25. {basic_memory-0.12.2 → basic_memory-0.12.3}/.gitignore +0 -0
  26. {basic_memory-0.12.2 → basic_memory-0.12.3}/.python-version +0 -0
  27. {basic_memory-0.12.2 → basic_memory-0.12.3}/CITATION.cff +0 -0
  28. {basic_memory-0.12.2 → basic_memory-0.12.3}/CLA.md +0 -0
  29. {basic_memory-0.12.2 → basic_memory-0.12.3}/CODE_OF_CONDUCT.md +0 -0
  30. {basic_memory-0.12.2 → basic_memory-0.12.3}/CONTRIBUTING.md +0 -0
  31. {basic_memory-0.12.2 → basic_memory-0.12.3}/Dockerfile +0 -0
  32. {basic_memory-0.12.2 → basic_memory-0.12.3}/LICENSE +0 -0
  33. {basic_memory-0.12.2 → basic_memory-0.12.3}/Makefile +0 -0
  34. {basic_memory-0.12.2 → basic_memory-0.12.3}/SECURITY.md +0 -0
  35. {basic_memory-0.12.2 → basic_memory-0.12.3}/basic-memory.md +0 -0
  36. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/AI Assistant Guide.md +0 -0
  37. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/CLI Reference.md +0 -0
  38. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Canvas.md +0 -0
  39. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Getting Started with Basic Memory.md +0 -0
  40. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Knowledge Format.md +0 -0
  41. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Obsidian Integration.md +0 -0
  42. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Technical Information.md +0 -0
  43. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/User Guide.md +0 -0
  44. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Welcome to Basic memory.md +0 -0
  45. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/Canvas.png +0 -0
  46. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/Claude-Obsidian-Demo.mp4 +0 -0
  47. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/Prompt.png +0 -0
  48. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/disk-ai-logo-400x400.png +0 -0
  49. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/disk-ai-logo.png +0 -0
  50. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/prompt 1.png +0 -0
  51. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/prompt2.png +0 -0
  52. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/prompt3.png +0 -0
  53. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/prompt4.png +0 -0
  54. {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/publish.js +0 -0
  55. {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Brewing Equipment.md +0 -0
  56. {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Bean Origins.md +0 -0
  57. {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Brewing Methods.md +0 -0
  58. {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Flavor Map.md +0 -0
  59. {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Knowledge Base.md +0 -0
  60. {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Flavor Extraction.md +0 -0
  61. {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Perfect Pour Over Coffee Method.canvas +0 -0
  62. {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Tasting Notes.md +0 -0
  63. {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/Basic.icns +0 -0
  64. {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/README.md +0 -0
  65. {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/icon.svg +0 -0
  66. {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/installer.py +0 -0
  67. {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/make_icons.sh +0 -0
  68. {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/setup.py +0 -0
  69. {basic_memory-0.12.2 → basic_memory-0.12.3}/llms-install.md +0 -0
  70. {basic_memory-0.12.2 → basic_memory-0.12.3}/memory.json +0 -0
  71. {basic_memory-0.12.2 → basic_memory-0.12.3}/scripts/install.sh +0 -0
  72. {basic_memory-0.12.2 → basic_memory-0.12.3}/smithery.yaml +0 -0
  73. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/alembic.ini +0 -0
  74. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/env.py +0 -0
  75. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/migrations.py +0 -0
  76. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/script.py.mako +0 -0
  77. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
  78. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +0 -0
  79. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +0 -0
  80. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +0 -0
  81. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/__init__.py +0 -0
  82. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/app.py +0 -0
  83. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/__init__.py +0 -0
  84. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/knowledge_router.py +0 -0
  85. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/memory_router.py +0 -0
  86. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/project_info_router.py +0 -0
  87. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/resource_router.py +0 -0
  88. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/search_router.py +0 -0
  89. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/__init__.py +0 -0
  90. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/app.py +0 -0
  91. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/__init__.py +0 -0
  92. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/db.py +0 -0
  93. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_chatgpt.py +0 -0
  94. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_claude_conversations.py +0 -0
  95. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_claude_projects.py +0 -0
  96. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_memory_json.py +0 -0
  97. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/mcp.py +0 -0
  98. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/project.py +0 -0
  99. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/status.py +0 -0
  100. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/sync.py +0 -0
  101. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/tool.py +0 -0
  102. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/main.py +0 -0
  103. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/config.py +0 -0
  104. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/db.py +0 -0
  105. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/deps.py +0 -0
  106. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/file_utils.py +0 -0
  107. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/__init__.py +0 -0
  108. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/entity_parser.py +0 -0
  109. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/markdown_processor.py +0 -0
  110. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/plugins.py +0 -0
  111. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/schemas.py +0 -0
  112. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/utils.py +0 -0
  113. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/__init__.py +0 -0
  114. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/async_client.py +0 -0
  115. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/main.py +0 -0
  116. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/__init__.py +0 -0
  117. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/ai_assistant_guide.py +0 -0
  118. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/continue_conversation.py +0 -0
  119. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/recent_activity.py +0 -0
  120. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/search.py +0 -0
  121. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/utils.py +0 -0
  122. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/resources/ai_assistant_guide.md +0 -0
  123. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/server.py +0 -0
  124. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/__init__.py +0 -0
  125. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/build_context.py +0 -0
  126. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/canvas.py +0 -0
  127. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/delete_note.py +0 -0
  128. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/project_info.py +0 -0
  129. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/read_content.py +0 -0
  130. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/search.py +0 -0
  131. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/utils.py +0 -0
  132. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/write_note.py +0 -0
  133. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/models/__init__.py +0 -0
  134. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/models/base.py +0 -0
  135. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/models/knowledge.py +0 -0
  136. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/models/search.py +0 -0
  137. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/__init__.py +0 -0
  138. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/entity_repository.py +0 -0
  139. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/observation_repository.py +0 -0
  140. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/project_info_repository.py +0 -0
  141. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/relation_repository.py +0 -0
  142. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/repository.py +0 -0
  143. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/search_repository.py +0 -0
  144. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/__init__.py +0 -0
  145. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/base.py +0 -0
  146. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/delete.py +0 -0
  147. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/memory.py +0 -0
  148. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/project_info.py +0 -0
  149. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/request.py +0 -0
  150. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/response.py +0 -0
  151. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/search.py +0 -0
  152. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/__init__.py +0 -0
  153. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/context_service.py +0 -0
  154. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/entity_service.py +0 -0
  155. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/exceptions.py +0 -0
  156. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/file_service.py +0 -0
  157. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/initialization.py +0 -0
  158. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/link_resolver.py +0 -0
  159. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/search_service.py +0 -0
  160. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/service.py +0 -0
  161. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/sync/__init__.py +0 -0
  162. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/sync/sync_service.py +0 -0
  163. {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/sync/watch_service.py +0 -0
  164. {basic_memory-0.12.2 → basic_memory-0.12.3}/static/ai_assistant_guide.md +0 -0
  165. {basic_memory-0.12.2 → basic_memory-0.12.3}/static/json_canvas_spec_1_0.md +0 -0
  166. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/Non-MarkdownFileSupport.pdf +0 -0
  167. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/Screenshot.png +0 -0
  168. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/__init__.py +0 -0
  169. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/conftest.py +0 -0
  170. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_knowledge_router.py +0 -0
  171. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_memory_router.py +0 -0
  172. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_project_info_router.py +0 -0
  173. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_resource_router.py +0 -0
  174. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_search_router.py +0 -0
  175. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/conftest.py +0 -0
  176. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_cli_tools.py +0 -0
  177. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_import_chatgpt.py +0 -0
  178. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_import_claude_conversations.py +0 -0
  179. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_import_claude_projects.py +0 -0
  180. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_import_memory_json.py +0 -0
  181. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_project_commands.py +0 -0
  182. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_project_info.py +0 -0
  183. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_status.py +0 -0
  184. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_sync.py +0 -0
  185. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_version.py +0 -0
  186. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/conftest.py +0 -0
  187. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/edit_file_test.py +0 -0
  188. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/__init__.py +0 -0
  189. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_entity_parser.py +0 -0
  190. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_markdown_plugins.py +0 -0
  191. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_markdown_processor.py +0 -0
  192. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_observation_edge_cases.py +0 -0
  193. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_parser_edge_cases.py +0 -0
  194. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_relation_edge_cases.py +0 -0
  195. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_task_detection.py +0 -0
  196. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/conftest.py +0 -0
  197. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_prompts.py +0 -0
  198. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_resources.py +0 -0
  199. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_canvas.py +0 -0
  200. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_project_info.py +0 -0
  201. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_resource.py +0 -0
  202. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_search.py +0 -0
  203. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_utils.py +0 -0
  204. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_write_note.py +0 -0
  205. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/repository/test_entity_repository.py +0 -0
  206. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/repository/test_observation_repository.py +0 -0
  207. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/repository/test_relation_repository.py +0 -0
  208. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/repository/test_repository.py +0 -0
  209. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/schemas/test_memory_url.py +0 -0
  210. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/schemas/test_schemas.py +0 -0
  211. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/schemas/test_search.py +0 -0
  212. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_context_service.py +0 -0
  213. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_entity_service.py +0 -0
  214. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_file_service.py +0 -0
  215. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_initialization.py +0 -0
  216. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_link_resolver.py +0 -0
  217. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_search_service.py +0 -0
  218. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_sync_service.py +0 -0
  219. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_sync_wikilink_issue.py +0 -0
  220. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_tmp_files.py +0 -0
  221. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_watch_service.py +0 -0
  222. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_watch_service_edge_cases.py +0 -0
  223. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/test_basic_memory.py +0 -0
  224. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/test_config.py +0 -0
  225. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/utils/test_file_utils.py +0 -0
  226. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/utils/test_parse_tags.py +0 -0
  227. {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/utils/test_utf8_handling.py +0 -0
@@ -1,6 +1,21 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.12.3 (2025-04-17)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Add extra logic for permalink generation with mixed Latin unicode and Chinese characters
9
+ ([`73ea91f`](https://github.com/basicmachines-co/basic-memory/commit/73ea91fe0d1f7ab89b99a1b691d59fe608b7fcbb))
10
+
11
+ Signed-off-by: phernandez <paul@basicmachines.co>
12
+
13
+ - Modify recent_activity args to be strings instead of enums
14
+ ([`3c1cc34`](https://github.com/basicmachines-co/basic-memory/commit/3c1cc346df519e703fae6412d43a92c7232c6226))
15
+
16
+ Signed-off-by: phernandez <paul@basicmachines.co>
17
+
18
+
4
19
  ## v0.12.2 (2025-04-08)
5
20
 
6
21
  ### Bug Fixes
@@ -172,4 +172,18 @@ With GitHub integration, the development workflow includes:
172
172
  4. **Documentation maintenance** - Claude can keep documentation updated as the code evolves
173
173
 
174
174
  With this integration, the AI assistant is a full-fledged team member rather than just a tool for generating code
175
- snippets.
175
+ snippets.
176
+
177
+
178
+ ### Basic Memory Pro
179
+
180
+ Basic Memory Pro is a desktop GUI application that wraps the basic-memory CLI/MCP tools:
181
+
182
+ - Built with Tauri (Rust), React (TypeScript), and a Python FastAPI sidecar
183
+ - Provides visual knowledge graph exploration and project management
184
+ - Uses the same core codebase but adds a desktop-friendly interface
185
+ - Project configuration is shared between CLI and Pro versions
186
+ - Multiple project support with visual switching interface
187
+
188
+ local repo: /Users/phernandez/dev/basicmachines/basic-memory-pro
189
+ github: https://github.com/basicmachines-co/basic-memory-pro
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-memory
3
- Version: 0.12.2
3
+ Version: 0.12.3
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
@@ -367,9 +367,9 @@ config:
367
367
  "command": "uvx",
368
368
  "args": [
369
369
  "basic-memory",
370
- "mcp",
371
370
  "--project",
372
- "your-project-name"
371
+ "your-project-name",
372
+ "mcp"
373
373
  ]
374
374
  }
375
375
  }
@@ -333,9 +333,9 @@ config:
333
333
  "command": "uvx",
334
334
  "args": [
335
335
  "basic-memory",
336
- "mcp",
337
336
  "--project",
338
- "your-project-name"
337
+ "your-project-name",
338
+ "mcp"
339
339
  ]
340
340
  }
341
341
  }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "basic-memory"
3
- version = "0.12.2"
3
+ version = "0.12.3"
4
4
  description = "Local-first knowledge management combining Zettelkasten with knowledge graphs"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12.1"
@@ -1,3 +1,3 @@
1
1
  """basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
2
2
 
3
- __version__ = "0.12.2"
3
+ __version__ = "0.12.3"
@@ -111,7 +111,7 @@ def format_not_found_message(identifier: str) -> str:
111
111
  ## Search Instead
112
112
  Try searching for related content:
113
113
  ```
114
- search(query="{identifier}")
114
+ search_notes(query="{identifier}")
115
115
  ```
116
116
 
117
117
  ## Recent Activity
@@ -172,7 +172,7 @@ def format_related_results(identifier: str, results) -> str:
172
172
  ## Search For More Results
173
173
  To see more related content:
174
174
  ```
175
- search(query="{identifier}")
175
+ search_notes(query="{identifier}")
176
176
  ```
177
177
 
178
178
  ## Create New Note
@@ -1,6 +1,6 @@
1
1
  """Recent activity tool for Basic Memory MCP server."""
2
2
 
3
- from typing import Optional, List
3
+ from typing import List, Union
4
4
 
5
5
  from loguru import logger
6
6
 
@@ -14,7 +14,7 @@ from basic_memory.schemas.search import SearchItemType
14
14
 
15
15
  @mcp.tool(
16
16
  description="""Get recent activity from across the knowledge base.
17
-
17
+
18
18
  Timeframe supports natural language formats like:
19
19
  - "2 days ago"
20
20
  - "last week"
@@ -25,9 +25,9 @@ from basic_memory.schemas.search import SearchItemType
25
25
  """,
26
26
  )
27
27
  async def recent_activity(
28
- type: Optional[List[SearchItemType]] = None,
29
- depth: Optional[int] = 1,
30
- timeframe: Optional[TimeFrame] = "7d",
28
+ type: Union[str, List[str]] = "",
29
+ depth: int = 1,
30
+ timeframe: TimeFrame = "7d",
31
31
  page: int = 1,
32
32
  page_size: int = 10,
33
33
  max_related: int = 10,
@@ -35,11 +35,14 @@ async def recent_activity(
35
35
  """Get recent activity across the knowledge base.
36
36
 
37
37
  Args:
38
- type: Filter by content type(s). Valid options:
39
- - ["entity"] for knowledge entities
40
- - ["relation"] for connections between entities
41
- - ["observation"] for notes and observations
38
+ type: Filter by content type(s). Can be a string or list of strings.
39
+ Valid options:
40
+ - "entity" or ["entity"] for knowledge entities
41
+ - "relation" or ["relation"] for connections between entities
42
+ - "observation" or ["observation"] for notes and observations
42
43
  Multiple types can be combined: ["entity", "relation"]
44
+ Case-insensitive: "ENTITY" and "entity" are treated the same.
45
+ Default is an empty string, which returns all types.
43
46
  depth: How many relation hops to traverse (1-3 recommended)
44
47
  timeframe: Time window to search. Supports natural language:
45
48
  - Relative: "2 days ago", "last week", "yesterday"
@@ -59,14 +62,17 @@ async def recent_activity(
59
62
  # Get all entities for the last 10 days (default)
60
63
  recent_activity()
61
64
 
62
- # Get all entities from yesterday
65
+ # Get all entities from yesterday (string format)
66
+ recent_activity(type="entity", timeframe="yesterday")
67
+
68
+ # Get all entities from yesterday (list format)
63
69
  recent_activity(type=["entity"], timeframe="yesterday")
64
70
 
65
71
  # Get recent relations and observations
66
72
  recent_activity(type=["relation", "observation"], timeframe="today")
67
73
 
68
74
  # Look back further with more context
69
- recent_activity(type=["entity"], depth=2, timeframe="2 weeks ago")
75
+ recent_activity(type="entity", depth=2, timeframe="2 weeks ago")
70
76
 
71
77
  Notes:
72
78
  - Higher depth values (>3) may impact performance with large result sets
@@ -86,11 +92,27 @@ async def recent_activity(
86
92
  if timeframe:
87
93
  params["timeframe"] = timeframe # pyright: ignore
88
94
 
89
- # send enum values if we have an enum, else send string value
95
+ # Validate and convert type parameter
90
96
  if type:
91
- params["type"] = [ # pyright: ignore
92
- type.value if isinstance(type, SearchItemType) else type for type in type
93
- ]
97
+ # Convert single string to list
98
+ if isinstance(type, str):
99
+ type_list = [type]
100
+ else:
101
+ type_list = type
102
+
103
+ # Validate each type against SearchItemType enum
104
+ validated_types = []
105
+ for t in type_list:
106
+ try:
107
+ # Try to convert string to enum
108
+ if isinstance(t, str):
109
+ validated_types.append(SearchItemType(t.lower()))
110
+ except ValueError:
111
+ valid_types = [t.value for t in SearchItemType]
112
+ raise ValueError(f"Invalid type: {t}. Valid types are: {valid_types}")
113
+
114
+ # Add validated types to params
115
+ params["type"] = [t.value for t in validated_types] # pyright: ignore
94
116
 
95
117
  response = await call_get(
96
118
  client,
@@ -5,11 +5,11 @@ import os
5
5
  import logging
6
6
  import re
7
7
  import sys
8
+ import unicodedata
8
9
  from pathlib import Path
9
- from typing import Optional, Protocol, Union, runtime_checkable, List
10
+ from typing import Optional, Protocol, Union, runtime_checkable, List, Any
10
11
 
11
12
  from loguru import logger
12
- from unidecode import unidecode
13
13
 
14
14
 
15
15
  @runtime_checkable
@@ -27,23 +27,23 @@ FilePath = Union[Path, str]
27
27
  logging.getLogger("opentelemetry.sdk.metrics._internal.instrument").setLevel(logging.ERROR)
28
28
 
29
29
 
30
- def generate_permalink(file_path: Union[Path, str, PathLike]) -> str:
31
- """Generate a stable permalink from a file path.
32
-
33
- Args:
34
- file_path: Original file path (str, Path, or PathLike)
30
+ def generate_permalink(file_path: Union[Path, str, Any]) -> str:
31
+ """
32
+ Generate a permalink from a file path.
35
33
 
36
34
  Returns:
37
35
  Normalized permalink that matches validation rules. Converts spaces and underscores
38
- to hyphens for consistency.
36
+ to hyphens for consistency. Preserves non-ASCII characters like Chinese.
39
37
 
40
38
  Examples:
41
39
  >>> generate_permalink("docs/My Feature.md")
42
40
  'docs/my-feature'
43
- >>> generate_permalink("specs/API (v2).md")
41
+ >>> generate_permalink("specs/API_v2.md")
44
42
  'specs/api-v2'
45
43
  >>> generate_permalink("design/unified_model_refactor.md")
46
44
  'design/unified-model-refactor'
45
+ >>> generate_permalink("中文/测试文档.md")
46
+ '中文/测试文档'
47
47
  """
48
48
  # Convert Path to string if needed
49
49
  path_str = str(file_path)
@@ -51,24 +51,74 @@ def generate_permalink(file_path: Union[Path, str, PathLike]) -> str:
51
51
  # Remove extension
52
52
  base = os.path.splitext(path_str)[0]
53
53
 
54
- # Transliterate unicode to ascii
55
- ascii_text = unidecode(base)
54
+ # Create a transliteration mapping for specific characters
55
+ transliteration_map = {
56
+ "ø": "o", # Handle Søren -> soren
57
+ "å": "a", # Handle Kierkegård -> kierkegard
58
+ "ü": "u", # Handle Müller -> muller
59
+ "é": "e", # Handle Café -> cafe
60
+ "è": "e", # Handle Mère -> mere
61
+ "ê": "e", # Handle Fête -> fete
62
+ "à": "a", # Handle À la mode -> a la mode
63
+ "ç": "c", # Handle Façade -> facade
64
+ "ñ": "n", # Handle Niño -> nino
65
+ "ö": "o", # Handle Björk -> bjork
66
+ "ä": "a", # Handle Häagen -> haagen
67
+ # Add more mappings as needed
68
+ }
69
+
70
+ # Process character by character, transliterating Latin characters with diacritics
71
+ result = ""
72
+ for char in base:
73
+ # Direct mapping for known characters
74
+ if char.lower() in transliteration_map:
75
+ result += transliteration_map[char.lower()]
76
+ # General case using Unicode normalization
77
+ elif unicodedata.category(char).startswith("L") and ord(char) > 127:
78
+ # Decompose the character (e.g., ü -> u + combining diaeresis)
79
+ decomposed = unicodedata.normalize("NFD", char)
80
+ # If decomposition produced multiple characters and first one is ASCII
81
+ if len(decomposed) > 1 and ord(decomposed[0]) < 128:
82
+ # Keep only the base character
83
+ result += decomposed[0].lower()
84
+ else:
85
+ # For non-Latin scripts like Chinese, preserve the character
86
+ result += char
87
+ else:
88
+ # Add the character as is
89
+ result += char
90
+
91
+ # Handle special punctuation cases for apostrophes
92
+ result = result.replace("'", "")
56
93
 
57
94
  # Insert dash between camelCase
58
- ascii_text = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", ascii_text)
95
+ # This regex finds boundaries between lowercase and uppercase letters
96
+ result = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", result)
97
+
98
+ # Insert dash between Chinese and Latin character boundaries
99
+ # This is needed for cases like "中文English" -> "中文-english"
100
+ result = re.sub(r"([\u4e00-\u9fff])([a-zA-Z])", r"\1-\2", result)
101
+ result = re.sub(r"([a-zA-Z])([\u4e00-\u9fff])", r"\1-\2", result)
59
102
 
60
- # Convert to lowercase
61
- lower_text = ascii_text.lower()
103
+ # Convert ASCII letters to lowercase, preserve non-ASCII characters
104
+ lower_text = "".join(c.lower() if c.isascii() and c.isalpha() else c for c in result)
62
105
 
63
- # replace underscores with hyphens
106
+ # Replace underscores with hyphens
64
107
  text_with_hyphens = lower_text.replace("_", "-")
65
108
 
66
- # Replace remaining invalid chars with hyphens
67
- clean_text = re.sub(r"[^a-z0-9/\-]", "-", text_with_hyphens)
109
+ # Replace spaces and unsafe ASCII characters with hyphens, but preserve non-ASCII characters
110
+ # Include common Chinese character ranges and other non-ASCII characters
111
+ clean_text = re.sub(
112
+ r"[^a-z0-9\u4e00-\u9fff\u3000-\u303f\u3400-\u4dbf/\-]", "-", text_with_hyphens
113
+ )
68
114
 
69
115
  # Collapse multiple hyphens
70
116
  clean_text = re.sub(r"-+", "-", clean_text)
71
117
 
118
+ # Remove hyphens between adjacent Chinese characters only
119
+ # This handles cases like "你好-世界" -> "你好世界"
120
+ clean_text = re.sub(r"([\u4e00-\u9fff])-([\u4e00-\u9fff])", r"\1\2", clean_text)
121
+
72
122
  # Clean each path segment
73
123
  segments = clean_text.split("/")
74
124
  clean_segments = [s.strip("-") for s in segments]
@@ -5,12 +5,9 @@ from datetime import datetime
5
5
 
6
6
  from mcp.server.fastmcp.exceptions import ToolError
7
7
 
8
- from basic_memory.mcp.tools import build_context, recent_activity
8
+ from basic_memory.mcp.tools import build_context
9
9
  from basic_memory.schemas.memory import (
10
10
  GraphContext,
11
- EntitySummary,
12
- ObservationSummary,
13
- RelationSummary,
14
11
  )
15
12
 
16
13
 
@@ -83,53 +80,6 @@ invalid_timeframes = [
83
80
  ]
84
81
 
85
82
 
86
- @pytest.mark.asyncio
87
- async def test_recent_activity_timeframe_formats(client, test_graph):
88
- """Test that recent_activity accepts various timeframe formats."""
89
- # Test each valid timeframe
90
- for timeframe in valid_timeframes:
91
- try:
92
- result = await recent_activity(
93
- type=["entity"], timeframe=timeframe, page=1, page_size=10, max_related=10
94
- )
95
- assert result is not None
96
- except Exception as e:
97
- pytest.fail(f"Failed with valid timeframe '{timeframe}': {str(e)}")
98
-
99
- # Test invalid timeframes should raise ValidationError
100
- for timeframe in invalid_timeframes:
101
- with pytest.raises(ToolError):
102
- await recent_activity(timeframe=timeframe)
103
-
104
-
105
- @pytest.mark.asyncio
106
- async def test_recent_activity_type_filters(client, test_graph):
107
- """Test that recent_activity correctly filters by types."""
108
- # Test single type
109
- result = await recent_activity(type=["entity"])
110
- assert result is not None
111
- assert all(isinstance(r, EntitySummary) for r in result.primary_results)
112
-
113
- # Test multiple types
114
- result = await recent_activity(type=["entity", "observation"])
115
- assert result is not None
116
- assert all(
117
- isinstance(r, EntitySummary) or isinstance(r, ObservationSummary)
118
- for r in result.primary_results
119
- )
120
-
121
- # Test all types
122
- result = await recent_activity(type=["entity", "observation", "relation"])
123
- assert result is not None
124
- # Results can be any type
125
- assert all(
126
- isinstance(r, EntitySummary)
127
- or isinstance(r, ObservationSummary)
128
- or isinstance(r, RelationSummary)
129
- for r in result.primary_results
130
- )
131
-
132
-
133
83
  @pytest.mark.asyncio
134
84
  async def test_build_context_timeframe_formats(client, test_graph):
135
85
  """Test that build_context accepts various timeframe formats."""
@@ -267,7 +267,7 @@ async def test_read_note_text_search_fallback(mock_call_get, mock_search):
267
267
  assert "Related Note 1" in result
268
268
  assert "Related Note 2" in result
269
269
  assert 'read_note("notes/related-note-1")' in result
270
- assert "search(query=" in result
270
+ assert "search_notes(query=" in result
271
271
  assert "write_note(" in result
272
272
 
273
273
 
@@ -0,0 +1,110 @@
1
+ """Tests for discussion context MCP tool."""
2
+
3
+ import pytest
4
+
5
+ from mcp.server.fastmcp.exceptions import ToolError
6
+
7
+ from basic_memory.mcp.tools import recent_activity
8
+ from basic_memory.schemas.memory import (
9
+ EntitySummary,
10
+ ObservationSummary,
11
+ RelationSummary,
12
+ )
13
+ from basic_memory.schemas.search import SearchItemType
14
+
15
+ # Test data for different timeframe formats
16
+ valid_timeframes = [
17
+ "7d", # Standard format
18
+ "yesterday", # Natural language
19
+ "0d", # Zero duration
20
+ ]
21
+
22
+ invalid_timeframes = [
23
+ "invalid", # Nonsense string
24
+ "tomorrow", # Future date
25
+ ]
26
+
27
+
28
+ @pytest.mark.asyncio
29
+ async def test_recent_activity_timeframe_formats(client, test_graph):
30
+ """Test that recent_activity accepts various timeframe formats."""
31
+ # Test each valid timeframe
32
+ for timeframe in valid_timeframes:
33
+ try:
34
+ result = await recent_activity(
35
+ type=["entity"], timeframe=timeframe, page=1, page_size=10, max_related=10
36
+ )
37
+ assert result is not None
38
+ except Exception as e:
39
+ pytest.fail(f"Failed with valid timeframe '{timeframe}': {str(e)}")
40
+
41
+ # Test invalid timeframes should raise ValidationError
42
+ for timeframe in invalid_timeframes:
43
+ with pytest.raises(ToolError):
44
+ await recent_activity(timeframe=timeframe)
45
+
46
+
47
+ @pytest.mark.asyncio
48
+ async def test_recent_activity_type_filters(client, test_graph):
49
+ """Test that recent_activity correctly filters by types."""
50
+
51
+ # Test single string type
52
+ result = await recent_activity(type=SearchItemType.ENTITY)
53
+ assert result is not None
54
+ assert all(isinstance(r, EntitySummary) for r in result.primary_results)
55
+
56
+ # Test single string type
57
+ result = await recent_activity(type="entity")
58
+ assert result is not None
59
+ assert all(isinstance(r, EntitySummary) for r in result.primary_results)
60
+
61
+ # Test single type
62
+ result = await recent_activity(type=["entity"])
63
+ assert result is not None
64
+ assert all(isinstance(r, EntitySummary) for r in result.primary_results)
65
+
66
+ # Test multiple types
67
+ result = await recent_activity(type=["entity", "observation"])
68
+ assert result is not None
69
+ assert all(
70
+ isinstance(r, EntitySummary) or isinstance(r, ObservationSummary)
71
+ for r in result.primary_results
72
+ )
73
+
74
+ # Test multiple types
75
+ result = await recent_activity(type=[SearchItemType.ENTITY, SearchItemType.OBSERVATION])
76
+ assert result is not None
77
+ assert all(
78
+ isinstance(r, EntitySummary) or isinstance(r, ObservationSummary)
79
+ for r in result.primary_results
80
+ )
81
+
82
+ # Test all types
83
+ result = await recent_activity(type=["entity", "observation", "relation"])
84
+ assert result is not None
85
+ # Results can be any type
86
+ assert all(
87
+ isinstance(r, EntitySummary)
88
+ or isinstance(r, ObservationSummary)
89
+ or isinstance(r, RelationSummary)
90
+ for r in result.primary_results
91
+ )
92
+
93
+
94
+ @pytest.mark.asyncio
95
+ async def test_recent_activity_type_invalid(client, test_graph):
96
+ """Test that recent_activity correctly filters by types."""
97
+
98
+ # Test single invalid string type
99
+ with pytest.raises(ValueError) as e:
100
+ await recent_activity(type="note")
101
+ assert (
102
+ str(e.value) == "Invalid type: note. Valid types are: ['entity', 'observation', 'relation']"
103
+ )
104
+
105
+ # Test invalid string array type
106
+ with pytest.raises(ValueError) as e:
107
+ await recent_activity(type=["note"])
108
+ assert (
109
+ str(e.value) == "Invalid type: note. Valid types are: ['entity', 'observation', 'relation']"
110
+ )
@@ -0,0 +1,120 @@
1
+ """Test permalink formatting during sync."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from basic_memory.config import ProjectConfig
8
+ from basic_memory.services import EntityService
9
+ from basic_memory.sync.sync_service import SyncService
10
+ from basic_memory.utils import generate_permalink
11
+
12
+
13
+ async def create_test_file(path: Path, content: str = "test content") -> None:
14
+ """Create a test file with given content."""
15
+ path.parent.mkdir(parents=True, exist_ok=True)
16
+ path.write_text(content)
17
+
18
+
19
+ @pytest.mark.asyncio
20
+ async def test_permalink_formatting(
21
+ sync_service: SyncService, test_config: ProjectConfig, entity_service: EntityService
22
+ ):
23
+ """Test that permalinks are properly formatted during sync.
24
+
25
+ This ensures:
26
+ - Underscores are converted to hyphens
27
+ - Spaces are converted to hyphens
28
+ - Mixed case is lowercased
29
+ - Directory structure is preserved
30
+ - Multiple directories work correctly
31
+ """
32
+ project_dir = test_config.home
33
+
34
+ # Test cases with different filename formats
35
+ test_cases = [
36
+ # filename -> expected permalink
37
+ ("my_awesome_feature.md", "my-awesome-feature"),
38
+ ("MIXED_CASE_NAME.md", "mixed-case-name"),
39
+ ("spaces and_underscores.md", "spaces-and-underscores"),
40
+ ("design/model_refactor.md", "design/model-refactor"),
41
+ (
42
+ "test/multiple_word_directory/feature_name.md",
43
+ "test/multiple-word-directory/feature-name",
44
+ ),
45
+ ]
46
+
47
+ # Create test files
48
+ for filename, _ in test_cases:
49
+ content = """
50
+ ---
51
+ type: knowledge
52
+ created: 2024-01-01
53
+ modified: 2024-01-01
54
+ ---
55
+ # Test File
56
+
57
+ Testing permalink generation.
58
+ """
59
+ await create_test_file(project_dir / filename, content)
60
+
61
+ # Run sync
62
+ await sync_service.sync(test_config.home)
63
+
64
+ # Verify permalinks
65
+ for filename, expected_permalink in test_cases:
66
+ entity = await entity_service.repository.get_by_file_path(filename)
67
+ assert entity.permalink == expected_permalink, (
68
+ f"File {filename} should have permalink {expected_permalink}"
69
+ )
70
+
71
+
72
+ @pytest.mark.parametrize(
73
+ "input_path, expected",
74
+ [
75
+ ("test/Über File.md", "test/uber-file"),
76
+ ("docs/résumé.md", "docs/resume"),
77
+ ("notes/Déjà vu.md", "notes/deja-vu"),
78
+ ("papers/Jürgen's Findings.md", "papers/jurgens-findings"),
79
+ ("archive/François Müller.md", "archive/francois-muller"),
80
+ ("research/Søren Kierkegård.md", "research/soren-kierkegard"),
81
+ ("articles/El Niño.md", "articles/el-nino"),
82
+ ],
83
+ )
84
+ def test_latin_accents_transliteration(input_path, expected):
85
+ """Test that Latin letters with accents are properly transliterated."""
86
+ assert generate_permalink(input_path) == expected
87
+
88
+
89
+ @pytest.mark.parametrize(
90
+ "input_path, expected",
91
+ [
92
+ ("中文/测试文档.md", "中文/测试文档"),
93
+ ("notes/北京市.md", "notes/北京市"),
94
+ ("research/上海简介.md", "research/上海简介"),
95
+ ("docs/中文 English Mixed.md", "docs/中文-english-mixed"),
96
+ ("articles/东京Tokyo混合.md", "articles/东京-tokyo-混合"),
97
+ ("papers/汉字_underscore_test.md", "papers/汉字-underscore-test"),
98
+ ("projects/中文CamelCase测试.md", "projects/中文-camel-case-测试"),
99
+ ],
100
+ )
101
+ def test_chinese_character_preservation(input_path, expected):
102
+ """Test that Chinese characters are preserved in permalinks."""
103
+ assert generate_permalink(input_path) == expected
104
+
105
+
106
+ @pytest.mark.parametrize(
107
+ "input_path, expected",
108
+ [
109
+ ("mixed/北京Café.md", "mixed/北京-cafe"),
110
+ ("notes/东京Tōkyō.md", "notes/东京-tokyo"),
111
+ ("research/München中文.md", "research/munchen-中文"),
112
+ ("docs/Über测试.md", "docs/uber-测试"),
113
+ ("complex/北京Beijing上海Shanghai.md", "complex/北京-beijing-上海-shanghai"),
114
+ ("special/中文!@#$%^&*()_+.md", "special/中文"),
115
+ ("punctuation/你好,世界!.md", "punctuation/你好世界"),
116
+ ],
117
+ )
118
+ def test_mixed_character_sets(input_path, expected):
119
+ """Test handling of mixed character sets and edge cases."""
120
+ assert generate_permalink(input_path) == expected
@@ -71,7 +71,7 @@ wheels = [
71
71
 
72
72
  [[package]]
73
73
  name = "basic-memory"
74
- version = "0.12.1"
74
+ version = "0.12.2"
75
75
  source = { editable = "." }
76
76
  dependencies = [
77
77
  { name = "aiosqlite" },