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.
- {basic_memory-0.12.1 → basic_memory-0.12.3}/CHANGELOG.md +26 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/CLAUDE.md +15 -1
- {basic_memory-0.12.1 → basic_memory-0.12.3}/PKG-INFO +3 -3
- {basic_memory-0.12.1 → basic_memory-0.12.3}/README.md +2 -2
- {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/installer.py +1 -1
- {basic_memory-0.12.1 → basic_memory-0.12.3}/pyproject.toml +1 -1
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/__init__.py +1 -1
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_memory_json.py +1 -1
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/entity_parser.py +1 -1
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/read_note.py +2 -2
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/recent_activity.py +37 -15
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/sync/sync_service.py +3 -5
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/utils.py +67 -17
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_resource_router.py +3 -3
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_import_chatgpt.py +8 -7
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_import_claude_conversations.py +5 -4
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_import_claude_projects.py +5 -4
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_import_memory_json.py +4 -3
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_markdown_processor.py +5 -5
- 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
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_canvas.py +4 -4
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_read_note.py +1 -1
- basic_memory-0.12.3/tests/mcp/test_tool_recent_activity.py +110 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_tmp_files.py +2 -2
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_watch_service.py +1 -1
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/utils/test_file_utils.py +10 -10
- basic_memory-0.12.3/tests/utils/test_permalink_formatting.py +120 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/uv.lock +1 -1
- basic_memory-0.12.1/tests/utils/test_permalink_formatting.py +0 -68
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/dependabot.yml +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/workflows/claude-code-actions.yml +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/workflows/pr-title.yml +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/workflows/release.yml +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.github/workflows/test.yml +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.gitignore +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/.python-version +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/CITATION.cff +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/CLA.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/CODE_OF_CONDUCT.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/CONTRIBUTING.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/Dockerfile +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/LICENSE +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/Makefile +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/SECURITY.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/basic-memory.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/AI Assistant Guide.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/CLI Reference.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Canvas.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Getting Started with Basic Memory.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Knowledge Format.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Obsidian Integration.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Technical Information.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/User Guide.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/Welcome to Basic memory.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/Canvas.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/Claude-Obsidian-Demo.mp4 +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/Prompt.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/disk-ai-logo-400x400.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/disk-ai-logo.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/prompt 1.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/prompt2.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/prompt3.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/attachments/prompt4.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/docs/publish.js +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Brewing Equipment.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Bean Origins.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Brewing Methods.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Flavor Map.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Knowledge Base.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Flavor Extraction.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Perfect Pour Over Coffee Method.canvas +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/examples/Coffee Notes/Tasting Notes.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/Basic.icns +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/README.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/icon.svg +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/make_icons.sh +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/installer/setup.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/llms-install.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/memory.json +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/scripts/install.sh +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/smithery.yaml +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/alembic.ini +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/env.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/migrations.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/script.py.mako +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/app.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/knowledge_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/memory_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/project_info_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/resource_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/api/routers/search_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/app.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/db.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_chatgpt.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_claude_conversations.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_claude_projects.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/mcp.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/project.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/status.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/sync.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/tool.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/main.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/config.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/db.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/deps.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/file_utils.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/markdown_processor.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/plugins.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/schemas.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/markdown/utils.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/async_client.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/main.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/ai_assistant_guide.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/continue_conversation.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/recent_activity.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/search.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/utils.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/resources/ai_assistant_guide.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/server.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/build_context.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/canvas.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/delete_note.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/project_info.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/read_content.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/search.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/utils.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/write_note.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/models/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/models/base.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/models/knowledge.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/models/search.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/entity_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/observation_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/project_info_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/relation_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/repository/search_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/base.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/delete.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/memory.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/project_info.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/request.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/response.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/schemas/search.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/context_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/entity_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/exceptions.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/file_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/initialization.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/link_resolver.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/search_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/services/service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/sync/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/sync/watch_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/static/ai_assistant_guide.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/static/json_canvas_spec_1_0.md +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/Non-MarkdownFileSupport.pdf +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/Screenshot.png +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/conftest.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_knowledge_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_memory_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_project_info_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/api/test_search_router.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/conftest.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_cli_tools.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_project_commands.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_project_info.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_status.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_sync.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/cli/test_version.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/conftest.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/edit_file_test.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/__init__.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_entity_parser.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_markdown_plugins.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_observation_edge_cases.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_parser_edge_cases.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_relation_edge_cases.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/markdown/test_task_detection.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/conftest.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_prompts.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_resources.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_project_info.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_resource.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_search.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_utils.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/mcp/test_tool_write_note.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/repository/test_entity_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/repository/test_observation_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/repository/test_relation_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/repository/test_repository.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/schemas/test_memory_url.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/schemas/test_schemas.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/schemas/test_search.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_context_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_entity_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_file_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_initialization.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_link_resolver.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/services/test_search_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_sync_service.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_sync_wikilink_issue.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/sync/test_watch_service_edge_cases.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/test_basic_memory.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/test_config.py +0 -0
- {basic_memory-0.12.1 → basic_memory-0.12.3}/tests/utils/test_parse_tags.py +0 -0
- {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.
|
|
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
|
}
|
{basic_memory-0.12.1 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_memory_json.py
RENAMED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
29
|
-
depth:
|
|
30
|
-
timeframe:
|
|
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).
|
|
39
|
-
|
|
40
|
-
- ["
|
|
41
|
-
- ["
|
|
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=
|
|
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
|
-
#
|
|
95
|
+
# Validate and convert type parameter
|
|
90
96
|
if type:
|
|
91
|
-
|
|
92
|
-
|
|
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,
|
|
31
|
-
"""
|
|
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/
|
|
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
|
-
#
|
|
55
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
#
|
|
106
|
+
# Replace underscores with hyphens
|
|
64
107
|
text_with_hyphens = lower_text.replace("_", "-")
|
|
65
108
|
|
|
66
|
-
# Replace
|
|
67
|
-
|
|
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
|
|
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
|
|
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(
|