basic-memory 0.12.1__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.1 → basic_memory-0.12.3}/CHANGELOG.md +26 -0
  2. {basic_memory-0.12.1 → basic_memory-0.12.3}/CLAUDE.md +15 -1
  3. {basic_memory-0.12.1 → basic_memory-0.12.3}/PKG-INFO +3 -3
  4. {basic_memory-0.12.1 → basic_memory-0.12.3}/README.md +2 -2
  5. {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/installer.py +1 -1
  6. {basic_memory-0.12.1 → basic_memory-0.12.3}/pyproject.toml +1 -1
  7. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/__init__.py +1 -1
  8. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_memory_json.py +1 -1
  9. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/entity_parser.py +1 -1
  10. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/read_note.py +2 -2
  11. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/recent_activity.py +37 -15
  12. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/sync/sync_service.py +3 -5
  13. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/utils.py +67 -17
  14. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_resource_router.py +3 -3
  15. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_import_chatgpt.py +8 -7
  16. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_import_claude_conversations.py +5 -4
  17. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_import_claude_projects.py +5 -4
  18. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_import_memory_json.py +4 -3
  19. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_markdown_processor.py +5 -5
  20. basic_memory-0.12.1/tests/mcp/test_tool_memory.py → basic_memory-0.12.3/tests/mcp/test_tool_build_context.py +1 -51
  21. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_canvas.py +4 -4
  22. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_read_note.py +1 -1
  23. basic_memory-0.12.3/tests/mcp/test_tool_recent_activity.py +110 -0
  24. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_tmp_files.py +2 -2
  25. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_watch_service.py +1 -1
  26. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/utils/test_file_utils.py +10 -10
  27. basic_memory-0.12.3/tests/utils/test_permalink_formatting.py +120 -0
  28. {basic_memory-0.12.1 → basic_memory-0.12.3}/uv.lock +1 -1
  29. basic_memory-0.12.1/tests/utils/test_permalink_formatting.py +0 -68
  30. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  31. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  32. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  33. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  34. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/dependabot.yml +0 -0
  35. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/workflows/claude-code-actions.yml +0 -0
  36. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/workflows/pr-title.yml +0 -0
  37. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/workflows/release.yml +0 -0
  38. {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/workflows/test.yml +0 -0
  39. {basic_memory-0.12.1 → basic_memory-0.12.3}/.gitignore +0 -0
  40. {basic_memory-0.12.1 → basic_memory-0.12.3}/.python-version +0 -0
  41. {basic_memory-0.12.1 → basic_memory-0.12.3}/CITATION.cff +0 -0
  42. {basic_memory-0.12.1 → basic_memory-0.12.3}/CLA.md +0 -0
  43. {basic_memory-0.12.1 → basic_memory-0.12.3}/CODE_OF_CONDUCT.md +0 -0
  44. {basic_memory-0.12.1 → basic_memory-0.12.3}/CONTRIBUTING.md +0 -0
  45. {basic_memory-0.12.1 → basic_memory-0.12.3}/Dockerfile +0 -0
  46. {basic_memory-0.12.1 → basic_memory-0.12.3}/LICENSE +0 -0
  47. {basic_memory-0.12.1 → basic_memory-0.12.3}/Makefile +0 -0
  48. {basic_memory-0.12.1 → basic_memory-0.12.3}/SECURITY.md +0 -0
  49. {basic_memory-0.12.1 → basic_memory-0.12.3}/basic-memory.md +0 -0
  50. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/AI Assistant Guide.md +0 -0
  51. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/CLI Reference.md +0 -0
  52. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Canvas.md +0 -0
  53. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Getting Started with Basic Memory.md +0 -0
  54. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Knowledge Format.md +0 -0
  55. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Obsidian Integration.md +0 -0
  56. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Technical Information.md +0 -0
  57. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/User Guide.md +0 -0
  58. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Welcome to Basic memory.md +0 -0
  59. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/Canvas.png +0 -0
  60. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/Claude-Obsidian-Demo.mp4 +0 -0
  61. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/Prompt.png +0 -0
  62. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/disk-ai-logo-400x400.png +0 -0
  63. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/disk-ai-logo.png +0 -0
  64. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/prompt 1.png +0 -0
  65. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/prompt2.png +0 -0
  66. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/prompt3.png +0 -0
  67. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/prompt4.png +0 -0
  68. {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/publish.js +0 -0
  69. {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Brewing Equipment.md +0 -0
  70. {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Bean Origins.md +0 -0
  71. {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Brewing Methods.md +0 -0
  72. {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Flavor Map.md +0 -0
  73. {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Knowledge Base.md +0 -0
  74. {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Flavor Extraction.md +0 -0
  75. {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Perfect Pour Over Coffee Method.canvas +0 -0
  76. {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Tasting Notes.md +0 -0
  77. {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/Basic.icns +0 -0
  78. {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/README.md +0 -0
  79. {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/icon.svg +0 -0
  80. {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/make_icons.sh +0 -0
  81. {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/setup.py +0 -0
  82. {basic_memory-0.12.1 → basic_memory-0.12.3}/llms-install.md +0 -0
  83. {basic_memory-0.12.1 → basic_memory-0.12.3}/memory.json +0 -0
  84. {basic_memory-0.12.1 → basic_memory-0.12.3}/scripts/install.sh +0 -0
  85. {basic_memory-0.12.1 → basic_memory-0.12.3}/smithery.yaml +0 -0
  86. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/alembic.ini +0 -0
  87. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/env.py +0 -0
  88. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/migrations.py +0 -0
  89. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/script.py.mako +0 -0
  90. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
  91. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +0 -0
  92. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +0 -0
  93. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +0 -0
  94. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/__init__.py +0 -0
  95. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/app.py +0 -0
  96. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/__init__.py +0 -0
  97. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/knowledge_router.py +0 -0
  98. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/memory_router.py +0 -0
  99. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/project_info_router.py +0 -0
  100. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/resource_router.py +0 -0
  101. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/search_router.py +0 -0
  102. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/__init__.py +0 -0
  103. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/app.py +0 -0
  104. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/__init__.py +0 -0
  105. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/db.py +0 -0
  106. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_chatgpt.py +0 -0
  107. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_claude_conversations.py +0 -0
  108. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_claude_projects.py +0 -0
  109. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/mcp.py +0 -0
  110. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/project.py +0 -0
  111. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/status.py +0 -0
  112. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/sync.py +0 -0
  113. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/tool.py +0 -0
  114. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/main.py +0 -0
  115. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/config.py +0 -0
  116. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/db.py +0 -0
  117. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/deps.py +0 -0
  118. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/file_utils.py +0 -0
  119. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/__init__.py +0 -0
  120. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/markdown_processor.py +0 -0
  121. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/plugins.py +0 -0
  122. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/schemas.py +0 -0
  123. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/utils.py +0 -0
  124. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/__init__.py +0 -0
  125. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/async_client.py +0 -0
  126. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/main.py +0 -0
  127. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/__init__.py +0 -0
  128. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/ai_assistant_guide.py +0 -0
  129. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/continue_conversation.py +0 -0
  130. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/recent_activity.py +0 -0
  131. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/search.py +0 -0
  132. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/utils.py +0 -0
  133. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/resources/ai_assistant_guide.md +0 -0
  134. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/server.py +0 -0
  135. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/__init__.py +0 -0
  136. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/build_context.py +0 -0
  137. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/canvas.py +0 -0
  138. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/delete_note.py +0 -0
  139. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/project_info.py +0 -0
  140. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/read_content.py +0 -0
  141. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/search.py +0 -0
  142. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/utils.py +0 -0
  143. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/write_note.py +0 -0
  144. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/models/__init__.py +0 -0
  145. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/models/base.py +0 -0
  146. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/models/knowledge.py +0 -0
  147. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/models/search.py +0 -0
  148. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/__init__.py +0 -0
  149. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/entity_repository.py +0 -0
  150. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/observation_repository.py +0 -0
  151. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/project_info_repository.py +0 -0
  152. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/relation_repository.py +0 -0
  153. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/repository.py +0 -0
  154. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/search_repository.py +0 -0
  155. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/__init__.py +0 -0
  156. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/base.py +0 -0
  157. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/delete.py +0 -0
  158. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/memory.py +0 -0
  159. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/project_info.py +0 -0
  160. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/request.py +0 -0
  161. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/response.py +0 -0
  162. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/search.py +0 -0
  163. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/__init__.py +0 -0
  164. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/context_service.py +0 -0
  165. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/entity_service.py +0 -0
  166. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/exceptions.py +0 -0
  167. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/file_service.py +0 -0
  168. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/initialization.py +0 -0
  169. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/link_resolver.py +0 -0
  170. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/search_service.py +0 -0
  171. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/service.py +0 -0
  172. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/sync/__init__.py +0 -0
  173. {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/sync/watch_service.py +0 -0
  174. {basic_memory-0.12.1 → basic_memory-0.12.3}/static/ai_assistant_guide.md +0 -0
  175. {basic_memory-0.12.1 → basic_memory-0.12.3}/static/json_canvas_spec_1_0.md +0 -0
  176. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/Non-MarkdownFileSupport.pdf +0 -0
  177. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/Screenshot.png +0 -0
  178. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/__init__.py +0 -0
  179. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/conftest.py +0 -0
  180. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_knowledge_router.py +0 -0
  181. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_memory_router.py +0 -0
  182. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_project_info_router.py +0 -0
  183. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_search_router.py +0 -0
  184. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/conftest.py +0 -0
  185. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_cli_tools.py +0 -0
  186. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_project_commands.py +0 -0
  187. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_project_info.py +0 -0
  188. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_status.py +0 -0
  189. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_sync.py +0 -0
  190. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_version.py +0 -0
  191. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/conftest.py +0 -0
  192. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/edit_file_test.py +0 -0
  193. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/__init__.py +0 -0
  194. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_entity_parser.py +0 -0
  195. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_markdown_plugins.py +0 -0
  196. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_observation_edge_cases.py +0 -0
  197. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_parser_edge_cases.py +0 -0
  198. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_relation_edge_cases.py +0 -0
  199. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_task_detection.py +0 -0
  200. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/conftest.py +0 -0
  201. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_prompts.py +0 -0
  202. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_resources.py +0 -0
  203. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_project_info.py +0 -0
  204. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_resource.py +0 -0
  205. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_search.py +0 -0
  206. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_utils.py +0 -0
  207. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_write_note.py +0 -0
  208. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/repository/test_entity_repository.py +0 -0
  209. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/repository/test_observation_repository.py +0 -0
  210. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/repository/test_relation_repository.py +0 -0
  211. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/repository/test_repository.py +0 -0
  212. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/schemas/test_memory_url.py +0 -0
  213. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/schemas/test_schemas.py +0 -0
  214. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/schemas/test_search.py +0 -0
  215. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_context_service.py +0 -0
  216. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_entity_service.py +0 -0
  217. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_file_service.py +0 -0
  218. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_initialization.py +0 -0
  219. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_link_resolver.py +0 -0
  220. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_search_service.py +0 -0
  221. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_sync_service.py +0 -0
  222. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_sync_wikilink_issue.py +0 -0
  223. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_watch_service_edge_cases.py +0 -0
  224. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/test_basic_memory.py +0 -0
  225. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/test_config.py +0 -0
  226. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/utils/test_parse_tags.py +0 -0
  227. {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/utils/test_utf8_handling.py +0 -0
@@ -1,6 +1,32 @@
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
+
19
+ ## v0.12.2 (2025-04-08)
20
+
21
+ ### Bug Fixes
22
+
23
+ - Utf8 for all file reads/write/open instead of default platform encoding
24
+ ([#91](https://github.com/basicmachines-co/basic-memory/pull/91),
25
+ [`2934176`](https://github.com/basicmachines-co/basic-memory/commit/29341763318408ea8f1e954a41046c4185f836c6))
26
+
27
+ Signed-off-by: phernandez <paul@basicmachines.co>
28
+
29
+
4
30
  ## v0.12.1 (2025-04-07)
5
31
 
6
32
  ### 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.1
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
  }
@@ -44,7 +44,7 @@ def update_claude_config():
44
44
 
45
45
  # Load existing config or create new
46
46
  if config_path.exists():
47
- config = json.loads(config_path.read_text())
47
+ config = json.loads(config_path.read_text(encoding="utf-8"))
48
48
  else:
49
49
  config = {"mcpServers": {}}
50
50
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "basic-memory"
3
- version = "0.12.1"
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.1"
3
+ __version__ = "0.12.3"
@@ -38,7 +38,7 @@ async def process_memory_json(
38
38
  read_task = progress.add_task("Reading memory.json...", total=None)
39
39
 
40
40
  # First pass - collect entities and relations
41
- with open(json_path) as f:
41
+ with open(json_path, encoding="utf-8") as f:
42
42
  lines = f.readlines()
43
43
  progress.update(read_task, total=len(lines))
44
44
 
@@ -104,7 +104,7 @@ class EntityParser:
104
104
  absolute_path = self.base_path / path
105
105
 
106
106
  # Parse frontmatter and content using python-frontmatter
107
- file_content = absolute_path.read_text()
107
+ file_content = absolute_path.read_text(encoding="utf-8")
108
108
  return await self.parse_file_content(absolute_path, file_content)
109
109
 
110
110
  async def parse_file_content(self, absolute_path, file_content):
@@ -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,
@@ -1,9 +1,8 @@
1
1
  """Service for syncing files between filesystem and database."""
2
2
 
3
3
  import os
4
-
5
- from dataclasses import dataclass
6
- from dataclasses import field
4
+ import time
5
+ from dataclasses import dataclass, field
7
6
  from datetime import datetime
8
7
  from pathlib import Path
9
8
  from typing import Dict, Optional, Set, Tuple
@@ -18,7 +17,6 @@ from basic_memory.models import Entity
18
17
  from basic_memory.repository import EntityRepository, RelationRepository
19
18
  from basic_memory.services import EntityService, FileService
20
19
  from basic_memory.services.search_service import SearchService
21
- import time
22
20
 
23
21
 
24
22
  @dataclass
@@ -237,7 +235,7 @@ class SyncService:
237
235
  logger.debug(f"Parsing markdown file, path: {path}, new: {new}")
238
236
 
239
237
  file_path = self.entity_parser.base_path / path
240
- file_content = file_path.read_text()
238
+ file_content = file_path.read_text(encoding="utf-8")
241
239
  file_contains_frontmatter = has_frontmatter(file_content)
242
240
 
243
241
  # entity markdown will always contain front matter, so it can be used up create/update the entity
@@ -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]
@@ -2,9 +2,9 @@
2
2
 
3
3
  import json
4
4
  from datetime import datetime, timezone
5
+ from pathlib import Path
5
6
 
6
7
  import pytest
7
- from pathlib import Path
8
8
 
9
9
  from basic_memory.schemas import EntityResponse
10
10
 
@@ -346,7 +346,7 @@ async def test_put_resource_new_file(client, test_config, entity_repository, sea
346
346
  assert full_path.exists()
347
347
 
348
348
  # Verify file content
349
- file_content = full_path.read_text()
349
+ file_content = full_path.read_text(encoding="utf-8")
350
350
  assert json.loads(file_content) == canvas_data
351
351
 
352
352
  # Verify entity was created in DB
@@ -420,7 +420,7 @@ async def test_put_resource_update_existing(client, test_config, entity_reposito
420
420
  assert response.status_code == 200
421
421
 
422
422
  # Verify file was updated
423
- updated_content = full_path.read_text()
423
+ updated_content = full_path.read_text(encoding="utf-8")
424
424
  assert json.loads(updated_content) == updated_data
425
425
 
426
426
  # Verify entity was updated
@@ -1,10 +1,11 @@
1
1
  """Tests for import_chatgpt command."""
2
2
 
3
3
  import json
4
+
4
5
  import pytest
5
6
  from typer.testing import CliRunner
6
7
 
7
- from basic_memory.cli.app import import_app, app
8
+ from basic_memory.cli.app import app, import_app
8
9
  from basic_memory.cli.commands import import_chatgpt
9
10
  from basic_memory.config import config
10
11
  from basic_memory.markdown import EntityParser, MarkdownProcessor
@@ -144,7 +145,7 @@ def sample_conversation_with_hidden():
144
145
  def sample_chatgpt_json(tmp_path, sample_conversation):
145
146
  """Create a sample ChatGPT JSON file."""
146
147
  json_file = tmp_path / "conversations.json"
147
- with open(json_file, "w") as f:
148
+ with open(json_file, "w", encoding="utf-8") as f:
148
149
  json.dump([sample_conversation], f)
149
150
  return json_file
150
151
 
@@ -167,7 +168,7 @@ async def test_process_chatgpt_json(tmp_path, sample_chatgpt_json):
167
168
  assert conv_path.exists()
168
169
 
169
170
  # Check content formatting
170
- content = conv_path.read_text()
171
+ content = conv_path.read_text(encoding="utf-8")
171
172
  assert "# Test Conversation" in content
172
173
  assert "### User" in content
173
174
  assert "Hello, this is a test message" in content
@@ -183,14 +184,14 @@ async def test_process_code_blocks(tmp_path, sample_conversation_with_code):
183
184
 
184
185
  # Create test file
185
186
  json_file = tmp_path / "code_test.json"
186
- with open(json_file, "w") as f:
187
+ with open(json_file, "w", encoding="utf-8") as f:
187
188
  json.dump([sample_conversation_with_code], f)
188
189
 
189
190
  await import_chatgpt.process_chatgpt_json(json_file, tmp_path, processor)
190
191
 
191
192
  # Check content
192
193
  conv_path = tmp_path / "20250111-code-test.md"
193
- content = conv_path.read_text()
194
+ content = conv_path.read_text(encoding="utf-8")
194
195
  assert "```python" in content
195
196
  assert "def hello():" in content
196
197
  assert "```" in content
@@ -204,7 +205,7 @@ async def test_hidden_messages(tmp_path, sample_conversation_with_hidden):
204
205
 
205
206
  # Create test file
206
207
  json_file = tmp_path / "hidden_test.json"
207
- with open(json_file, "w") as f:
208
+ with open(json_file, "w", encoding="utf-8") as f:
208
209
  json.dump([sample_conversation_with_hidden], f)
209
210
 
210
211
  results = await import_chatgpt.process_chatgpt_json(json_file, tmp_path, processor)
@@ -214,7 +215,7 @@ async def test_hidden_messages(tmp_path, sample_conversation_with_hidden):
214
215
 
215
216
  # Check content
216
217
  conv_path = tmp_path / "20250111-hidden-test.md"
217
- content = conv_path.read_text()
218
+ content = conv_path.read_text(encoding="utf-8")
218
219
  assert "Visible message" in content
219
220
  assert "Hidden message" not in content
220
221
 
@@ -1,6 +1,7 @@
1
1
  """Tests for import_claude command (chat conversations)."""
2
2
 
3
3
  import json
4
+
4
5
  import pytest
5
6
  from typer.testing import CliRunner
6
7
 
@@ -44,7 +45,7 @@ def sample_conversation():
44
45
  def sample_conversations_json(tmp_path, sample_conversation):
45
46
  """Create a sample conversations.json file."""
46
47
  json_file = tmp_path / "conversations.json"
47
- with open(json_file, "w") as f:
48
+ with open(json_file, "w", encoding="utf-8") as f:
48
49
  json.dump([sample_conversation], f)
49
50
  return json_file
50
51
 
@@ -65,7 +66,7 @@ async def test_process_chat_json(tmp_path, sample_conversations_json):
65
66
  # Check conversation file
66
67
  conv_path = tmp_path / "20250105-test-conversation.md"
67
68
  assert conv_path.exists()
68
- content = conv_path.read_text()
69
+ content = conv_path.read_text(encoding="utf-8")
69
70
 
70
71
  # Check content formatting
71
72
  assert "### Human" in content
@@ -156,7 +157,7 @@ def test_import_conversation_with_attachments(tmp_path):
156
157
  }
157
158
 
158
159
  json_file = tmp_path / "with_attachments.json"
159
- with open(json_file, "w") as f:
160
+ with open(json_file, "w", encoding="utf-8") as f:
160
161
  json.dump([conversation], f)
161
162
 
162
163
  # Set up environment
@@ -168,7 +169,7 @@ def test_import_conversation_with_attachments(tmp_path):
168
169
 
169
170
  # Check attachment formatting
170
171
  conv_path = tmp_path / "conversations/20250105-test-with-attachments.md"
171
- content = conv_path.read_text()
172
+ content = conv_path.read_text(encoding="utf-8")
172
173
  assert "**Attachment: test.txt**" in content
173
174
  assert "```" in content
174
175
  assert "Test file content" in content
@@ -1,6 +1,7 @@
1
1
  """Tests for import_claude_projects command."""
2
2
 
3
3
  import json
4
+
4
5
  import pytest
5
6
  from typer.testing import CliRunner
6
7
 
@@ -43,7 +44,7 @@ def sample_project():
43
44
  def sample_projects_json(tmp_path, sample_project):
44
45
  """Create a sample projects.json file."""
45
46
  json_file = tmp_path / "projects.json"
46
- with open(json_file, "w") as f:
47
+ with open(json_file, "w", encoding="utf-8") as f:
47
48
  json.dump([sample_project], f)
48
49
  return json_file
49
50
 
@@ -70,14 +71,14 @@ async def test_process_projects_json(tmp_path, sample_projects_json):
70
71
  # Check document files
71
72
  doc1 = project_dir / "docs/test-document.md"
72
73
  assert doc1.exists()
73
- content1 = doc1.read_text()
74
+ content1 = doc1.read_text(encoding="utf-8")
74
75
  assert "# Test Document" in content1
75
76
  assert "This is test content" in content1
76
77
 
77
78
  # Check prompt template
78
79
  prompt = project_dir / "prompt-template.md"
79
80
  assert prompt.exists()
80
- prompt_content = prompt.read_text()
81
+ prompt_content = prompt.read_text(encoding="utf-8")
81
82
  assert "# Test Prompt" in prompt_content
82
83
  assert "This is a test prompt" in prompt_content
83
84
 
@@ -160,7 +161,7 @@ def test_import_project_without_prompt(tmp_path):
160
161
  }
161
162
 
162
163
  json_file = tmp_path / "no_prompt.json"
163
- with open(json_file, "w") as f:
164
+ with open(json_file, "w", encoding="utf-8") as f:
164
165
  json.dump([project], f)
165
166
 
166
167
  # Set up environment
@@ -1,6 +1,7 @@
1
1
  """Tests for import_memory_json command."""
2
2
 
3
3
  import json
4
+
4
5
  import pytest
5
6
  from typer.testing import CliRunner
6
7
 
@@ -35,7 +36,7 @@ def sample_entities():
35
36
  def sample_json_file(tmp_path, sample_entities):
36
37
  """Create a sample memory.json file."""
37
38
  json_file = tmp_path / "memory.json"
38
- with open(json_file, "w") as f:
39
+ with open(json_file, "w", encoding="utf-8") as f:
39
40
  for entity in sample_entities:
40
41
  f.write(json.dumps(entity) + "\n")
41
42
  return json_file
@@ -55,7 +56,7 @@ async def test_process_memory_json(tmp_path, sample_json_file):
55
56
  # Check file was created
56
57
  entity_file = tmp_path / "test/test_entity.md"
57
58
  assert entity_file.exists()
58
- content = entity_file.read_text()
59
+ content = entity_file.read_text(encoding="utf-8")
59
60
  assert "Test observation 1" in content
60
61
  assert "Test observation 2" in content
61
62
  assert "test_relation [[related_entity]]" in content
@@ -120,7 +121,7 @@ def test_import_json_command_handle_old_format(tmp_path):
120
121
  ]
121
122
 
122
123
  json_file = tmp_path / "old_format.json"
123
- with open(json_file, "w") as f:
124
+ with open(json_file, "w", encoding="utf-8") as f:
124
125
  for item in old_format:
125
126
  f.write(json.dumps(item) + "\n")
126
127
 
@@ -8,10 +8,10 @@ from pathlib import Path
8
8
 
9
9
  import pytest
10
10
 
11
- from basic_memory.markdown.markdown_processor import MarkdownProcessor, DirtyFileError
11
+ from basic_memory.markdown.markdown_processor import DirtyFileError, MarkdownProcessor
12
12
  from basic_memory.markdown.schemas import (
13
- EntityMarkdown,
14
13
  EntityFrontmatter,
14
+ EntityMarkdown,
15
15
  Observation,
16
16
  Relation,
17
17
  )
@@ -41,7 +41,7 @@ async def test_write_new_minimal_file(markdown_processor: MarkdownProcessor, tmp
41
41
  await markdown_processor.write_file(path, markdown)
42
42
 
43
43
  # Read back and verify
44
- content = path.read_text()
44
+ content = path.read_text(encoding="utf-8")
45
45
  assert "---" in content # Has frontmatter
46
46
  assert "type: note" in content
47
47
  assert "permalink: test" in content
@@ -90,7 +90,7 @@ async def test_write_new_file_with_content(markdown_processor: MarkdownProcessor
90
90
  await markdown_processor.write_file(path, markdown)
91
91
 
92
92
  # Read back and verify
93
- content = path.read_text()
93
+ content = path.read_text(encoding="utf-8")
94
94
 
95
95
  # Check content preserved exactly
96
96
  assert "# Custom Title" in content
@@ -169,7 +169,7 @@ async def test_dirty_file_detection(markdown_processor: MarkdownProcessor, tmp_p
169
169
  checksum = await markdown_processor.write_file(path, initial)
170
170
 
171
171
  # Modify file directly
172
- path.write_text(path.read_text() + "\nModified!")
172
+ path.write_text(path.read_text(encoding="utf-8") + "\nModified!")
173
173
 
174
174
  # Try to update with old checksum
175
175
  update = EntityMarkdown(