basic-memory 0.12.2__tar.gz → 0.12.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of basic-memory might be problematic. Click here for more details.
- {basic_memory-0.12.2 → basic_memory-0.12.3}/CHANGELOG.md +15 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/CLAUDE.md +15 -1
- {basic_memory-0.12.2 → basic_memory-0.12.3}/PKG-INFO +3 -3
- {basic_memory-0.12.2 → basic_memory-0.12.3}/README.md +2 -2
- {basic_memory-0.12.2 → basic_memory-0.12.3}/pyproject.toml +1 -1
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/__init__.py +1 -1
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/read_note.py +2 -2
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/recent_activity.py +37 -15
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/utils.py +67 -17
- basic_memory-0.12.2/tests/mcp/test_tool_memory.py → basic_memory-0.12.3/tests/mcp/test_tool_build_context.py +1 -51
- {basic_memory-0.12.2 → 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.3/tests/utils/test_permalink_formatting.py +120 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/uv.lock +1 -1
- basic_memory-0.12.2/tests/utils/test_permalink_formatting.py +0 -68
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/dependabot.yml +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/workflows/claude-code-actions.yml +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/workflows/pr-title.yml +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/workflows/release.yml +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.github/workflows/test.yml +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.gitignore +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/.python-version +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/CITATION.cff +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/CLA.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/CODE_OF_CONDUCT.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/CONTRIBUTING.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/Dockerfile +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/LICENSE +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/Makefile +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/SECURITY.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/basic-memory.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/AI Assistant Guide.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/CLI Reference.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Canvas.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Getting Started with Basic Memory.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Knowledge Format.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Obsidian Integration.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Technical Information.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/User Guide.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/Welcome to Basic memory.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/Canvas.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/Claude-Obsidian-Demo.mp4 +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/Prompt.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/disk-ai-logo-400x400.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/disk-ai-logo.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/prompt 1.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/prompt2.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/prompt3.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/attachments/prompt4.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/docs/publish.js +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Brewing Equipment.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Bean Origins.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Brewing Methods.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Flavor Map.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Coffee Knowledge Base.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Flavor Extraction.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Perfect Pour Over Coffee Method.canvas +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/examples/Coffee Notes/Tasting Notes.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/Basic.icns +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/README.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/icon.svg +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/installer.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/make_icons.sh +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/installer/setup.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/llms-install.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/memory.json +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/scripts/install.sh +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/smithery.yaml +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/alembic.ini +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/env.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/migrations.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/script.py.mako +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/app.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/knowledge_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/memory_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/project_info_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/resource_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/api/routers/search_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/app.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/db.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_chatgpt.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_claude_conversations.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_claude_projects.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/import_memory_json.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/mcp.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/project.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/status.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/sync.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/commands/tool.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/cli/main.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/config.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/db.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/deps.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/file_utils.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/entity_parser.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/markdown_processor.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/plugins.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/schemas.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/markdown/utils.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/async_client.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/main.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/ai_assistant_guide.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/continue_conversation.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/recent_activity.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/search.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/prompts/utils.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/resources/ai_assistant_guide.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/server.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/build_context.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/canvas.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/delete_note.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/project_info.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/read_content.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/search.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/utils.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/mcp/tools/write_note.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/models/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/models/base.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/models/knowledge.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/models/search.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/entity_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/observation_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/project_info_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/relation_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/repository/search_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/base.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/delete.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/memory.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/project_info.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/request.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/response.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/schemas/search.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/context_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/entity_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/exceptions.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/file_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/initialization.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/link_resolver.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/search_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/services/service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/sync/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/sync/sync_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/src/basic_memory/sync/watch_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/static/ai_assistant_guide.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/static/json_canvas_spec_1_0.md +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/Non-MarkdownFileSupport.pdf +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/Screenshot.png +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/conftest.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_knowledge_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_memory_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_project_info_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_resource_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/api/test_search_router.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/conftest.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_cli_tools.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_import_chatgpt.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_import_claude_conversations.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_import_claude_projects.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_import_memory_json.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_project_commands.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_project_info.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_status.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_sync.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/cli/test_version.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/conftest.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/edit_file_test.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/__init__.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_entity_parser.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_markdown_plugins.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_markdown_processor.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_observation_edge_cases.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_parser_edge_cases.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_relation_edge_cases.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/markdown/test_task_detection.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/conftest.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_prompts.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_resources.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_canvas.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_project_info.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_resource.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_search.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_utils.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/mcp/test_tool_write_note.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/repository/test_entity_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/repository/test_observation_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/repository/test_relation_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/repository/test_repository.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/schemas/test_memory_url.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/schemas/test_schemas.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/schemas/test_search.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_context_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_entity_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_file_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_initialization.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_link_resolver.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/services/test_search_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_sync_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_sync_wikilink_issue.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_tmp_files.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_watch_service.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/sync/test_watch_service_edge_cases.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/test_basic_memory.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/test_config.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/utils/test_file_utils.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/utils/test_parse_tags.py +0 -0
- {basic_memory-0.12.2 → basic_memory-0.12.3}/tests/utils/test_utf8_handling.py +0 -0
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.12.3 (2025-04-17)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Add extra logic for permalink generation with mixed Latin unicode and Chinese characters
|
|
9
|
+
([`73ea91f`](https://github.com/basicmachines-co/basic-memory/commit/73ea91fe0d1f7ab89b99a1b691d59fe608b7fcbb))
|
|
10
|
+
|
|
11
|
+
Signed-off-by: phernandez <paul@basicmachines.co>
|
|
12
|
+
|
|
13
|
+
- Modify recent_activity args to be strings instead of enums
|
|
14
|
+
([`3c1cc34`](https://github.com/basicmachines-co/basic-memory/commit/3c1cc346df519e703fae6412d43a92c7232c6226))
|
|
15
|
+
|
|
16
|
+
Signed-off-by: phernandez <paul@basicmachines.co>
|
|
17
|
+
|
|
18
|
+
|
|
4
19
|
## v0.12.2 (2025-04-08)
|
|
5
20
|
|
|
6
21
|
### Bug Fixes
|
|
@@ -172,4 +172,18 @@ With GitHub integration, the development workflow includes:
|
|
|
172
172
|
4. **Documentation maintenance** - Claude can keep documentation updated as the code evolves
|
|
173
173
|
|
|
174
174
|
With this integration, the AI assistant is a full-fledged team member rather than just a tool for generating code
|
|
175
|
-
snippets.
|
|
175
|
+
snippets.
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
### Basic Memory Pro
|
|
179
|
+
|
|
180
|
+
Basic Memory Pro is a desktop GUI application that wraps the basic-memory CLI/MCP tools:
|
|
181
|
+
|
|
182
|
+
- Built with Tauri (Rust), React (TypeScript), and a Python FastAPI sidecar
|
|
183
|
+
- Provides visual knowledge graph exploration and project management
|
|
184
|
+
- Uses the same core codebase but adds a desktop-friendly interface
|
|
185
|
+
- Project configuration is shared between CLI and Pro versions
|
|
186
|
+
- Multiple project support with visual switching interface
|
|
187
|
+
|
|
188
|
+
local repo: /Users/phernandez/dev/basicmachines/basic-memory-pro
|
|
189
|
+
github: https://github.com/basicmachines-co/basic-memory-pro
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: basic-memory
|
|
3
|
-
Version: 0.12.
|
|
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
|
}
|
|
@@ -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,
|
|
@@ -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]
|
|
@@ -5,12 +5,9 @@ from datetime import datetime
|
|
|
5
5
|
|
|
6
6
|
from mcp.server.fastmcp.exceptions import ToolError
|
|
7
7
|
|
|
8
|
-
from basic_memory.mcp.tools import build_context
|
|
8
|
+
from basic_memory.mcp.tools import build_context
|
|
9
9
|
from basic_memory.schemas.memory import (
|
|
10
10
|
GraphContext,
|
|
11
|
-
EntitySummary,
|
|
12
|
-
ObservationSummary,
|
|
13
|
-
RelationSummary,
|
|
14
11
|
)
|
|
15
12
|
|
|
16
13
|
|
|
@@ -83,53 +80,6 @@ invalid_timeframes = [
|
|
|
83
80
|
]
|
|
84
81
|
|
|
85
82
|
|
|
86
|
-
@pytest.mark.asyncio
|
|
87
|
-
async def test_recent_activity_timeframe_formats(client, test_graph):
|
|
88
|
-
"""Test that recent_activity accepts various timeframe formats."""
|
|
89
|
-
# Test each valid timeframe
|
|
90
|
-
for timeframe in valid_timeframes:
|
|
91
|
-
try:
|
|
92
|
-
result = await recent_activity(
|
|
93
|
-
type=["entity"], timeframe=timeframe, page=1, page_size=10, max_related=10
|
|
94
|
-
)
|
|
95
|
-
assert result is not None
|
|
96
|
-
except Exception as e:
|
|
97
|
-
pytest.fail(f"Failed with valid timeframe '{timeframe}': {str(e)}")
|
|
98
|
-
|
|
99
|
-
# Test invalid timeframes should raise ValidationError
|
|
100
|
-
for timeframe in invalid_timeframes:
|
|
101
|
-
with pytest.raises(ToolError):
|
|
102
|
-
await recent_activity(timeframe=timeframe)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@pytest.mark.asyncio
|
|
106
|
-
async def test_recent_activity_type_filters(client, test_graph):
|
|
107
|
-
"""Test that recent_activity correctly filters by types."""
|
|
108
|
-
# Test single type
|
|
109
|
-
result = await recent_activity(type=["entity"])
|
|
110
|
-
assert result is not None
|
|
111
|
-
assert all(isinstance(r, EntitySummary) for r in result.primary_results)
|
|
112
|
-
|
|
113
|
-
# Test multiple types
|
|
114
|
-
result = await recent_activity(type=["entity", "observation"])
|
|
115
|
-
assert result is not None
|
|
116
|
-
assert all(
|
|
117
|
-
isinstance(r, EntitySummary) or isinstance(r, ObservationSummary)
|
|
118
|
-
for r in result.primary_results
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# Test all types
|
|
122
|
-
result = await recent_activity(type=["entity", "observation", "relation"])
|
|
123
|
-
assert result is not None
|
|
124
|
-
# Results can be any type
|
|
125
|
-
assert all(
|
|
126
|
-
isinstance(r, EntitySummary)
|
|
127
|
-
or isinstance(r, ObservationSummary)
|
|
128
|
-
or isinstance(r, RelationSummary)
|
|
129
|
-
for r in result.primary_results
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
83
|
@pytest.mark.asyncio
|
|
134
84
|
async def test_build_context_timeframe_formats(client, test_graph):
|
|
135
85
|
"""Test that build_context accepts various timeframe formats."""
|
|
@@ -267,7 +267,7 @@ async def test_read_note_text_search_fallback(mock_call_get, mock_search):
|
|
|
267
267
|
assert "Related Note 1" in result
|
|
268
268
|
assert "Related Note 2" in result
|
|
269
269
|
assert 'read_note("notes/related-note-1")' in result
|
|
270
|
-
assert "
|
|
270
|
+
assert "search_notes(query=" in result
|
|
271
271
|
assert "write_note(" in result
|
|
272
272
|
|
|
273
273
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Tests for discussion context MCP tool."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from mcp.server.fastmcp.exceptions import ToolError
|
|
6
|
+
|
|
7
|
+
from basic_memory.mcp.tools import recent_activity
|
|
8
|
+
from basic_memory.schemas.memory import (
|
|
9
|
+
EntitySummary,
|
|
10
|
+
ObservationSummary,
|
|
11
|
+
RelationSummary,
|
|
12
|
+
)
|
|
13
|
+
from basic_memory.schemas.search import SearchItemType
|
|
14
|
+
|
|
15
|
+
# Test data for different timeframe formats
|
|
16
|
+
valid_timeframes = [
|
|
17
|
+
"7d", # Standard format
|
|
18
|
+
"yesterday", # Natural language
|
|
19
|
+
"0d", # Zero duration
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
invalid_timeframes = [
|
|
23
|
+
"invalid", # Nonsense string
|
|
24
|
+
"tomorrow", # Future date
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.asyncio
|
|
29
|
+
async def test_recent_activity_timeframe_formats(client, test_graph):
|
|
30
|
+
"""Test that recent_activity accepts various timeframe formats."""
|
|
31
|
+
# Test each valid timeframe
|
|
32
|
+
for timeframe in valid_timeframes:
|
|
33
|
+
try:
|
|
34
|
+
result = await recent_activity(
|
|
35
|
+
type=["entity"], timeframe=timeframe, page=1, page_size=10, max_related=10
|
|
36
|
+
)
|
|
37
|
+
assert result is not None
|
|
38
|
+
except Exception as e:
|
|
39
|
+
pytest.fail(f"Failed with valid timeframe '{timeframe}': {str(e)}")
|
|
40
|
+
|
|
41
|
+
# Test invalid timeframes should raise ValidationError
|
|
42
|
+
for timeframe in invalid_timeframes:
|
|
43
|
+
with pytest.raises(ToolError):
|
|
44
|
+
await recent_activity(timeframe=timeframe)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_recent_activity_type_filters(client, test_graph):
|
|
49
|
+
"""Test that recent_activity correctly filters by types."""
|
|
50
|
+
|
|
51
|
+
# Test single string type
|
|
52
|
+
result = await recent_activity(type=SearchItemType.ENTITY)
|
|
53
|
+
assert result is not None
|
|
54
|
+
assert all(isinstance(r, EntitySummary) for r in result.primary_results)
|
|
55
|
+
|
|
56
|
+
# Test single string type
|
|
57
|
+
result = await recent_activity(type="entity")
|
|
58
|
+
assert result is not None
|
|
59
|
+
assert all(isinstance(r, EntitySummary) for r in result.primary_results)
|
|
60
|
+
|
|
61
|
+
# Test single type
|
|
62
|
+
result = await recent_activity(type=["entity"])
|
|
63
|
+
assert result is not None
|
|
64
|
+
assert all(isinstance(r, EntitySummary) for r in result.primary_results)
|
|
65
|
+
|
|
66
|
+
# Test multiple types
|
|
67
|
+
result = await recent_activity(type=["entity", "observation"])
|
|
68
|
+
assert result is not None
|
|
69
|
+
assert all(
|
|
70
|
+
isinstance(r, EntitySummary) or isinstance(r, ObservationSummary)
|
|
71
|
+
for r in result.primary_results
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Test multiple types
|
|
75
|
+
result = await recent_activity(type=[SearchItemType.ENTITY, SearchItemType.OBSERVATION])
|
|
76
|
+
assert result is not None
|
|
77
|
+
assert all(
|
|
78
|
+
isinstance(r, EntitySummary) or isinstance(r, ObservationSummary)
|
|
79
|
+
for r in result.primary_results
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Test all types
|
|
83
|
+
result = await recent_activity(type=["entity", "observation", "relation"])
|
|
84
|
+
assert result is not None
|
|
85
|
+
# Results can be any type
|
|
86
|
+
assert all(
|
|
87
|
+
isinstance(r, EntitySummary)
|
|
88
|
+
or isinstance(r, ObservationSummary)
|
|
89
|
+
or isinstance(r, RelationSummary)
|
|
90
|
+
for r in result.primary_results
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.mark.asyncio
|
|
95
|
+
async def test_recent_activity_type_invalid(client, test_graph):
|
|
96
|
+
"""Test that recent_activity correctly filters by types."""
|
|
97
|
+
|
|
98
|
+
# Test single invalid string type
|
|
99
|
+
with pytest.raises(ValueError) as e:
|
|
100
|
+
await recent_activity(type="note")
|
|
101
|
+
assert (
|
|
102
|
+
str(e.value) == "Invalid type: note. Valid types are: ['entity', 'observation', 'relation']"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Test invalid string array type
|
|
106
|
+
with pytest.raises(ValueError) as e:
|
|
107
|
+
await recent_activity(type=["note"])
|
|
108
|
+
assert (
|
|
109
|
+
str(e.value) == "Invalid type: note. Valid types are: ['entity', 'observation', 'relation']"
|
|
110
|
+
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Test permalink formatting during sync."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from basic_memory.config import ProjectConfig
|
|
8
|
+
from basic_memory.services import EntityService
|
|
9
|
+
from basic_memory.sync.sync_service import SyncService
|
|
10
|
+
from basic_memory.utils import generate_permalink
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def create_test_file(path: Path, content: str = "test content") -> None:
|
|
14
|
+
"""Create a test file with given content."""
|
|
15
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
path.write_text(content)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.asyncio
|
|
20
|
+
async def test_permalink_formatting(
|
|
21
|
+
sync_service: SyncService, test_config: ProjectConfig, entity_service: EntityService
|
|
22
|
+
):
|
|
23
|
+
"""Test that permalinks are properly formatted during sync.
|
|
24
|
+
|
|
25
|
+
This ensures:
|
|
26
|
+
- Underscores are converted to hyphens
|
|
27
|
+
- Spaces are converted to hyphens
|
|
28
|
+
- Mixed case is lowercased
|
|
29
|
+
- Directory structure is preserved
|
|
30
|
+
- Multiple directories work correctly
|
|
31
|
+
"""
|
|
32
|
+
project_dir = test_config.home
|
|
33
|
+
|
|
34
|
+
# Test cases with different filename formats
|
|
35
|
+
test_cases = [
|
|
36
|
+
# filename -> expected permalink
|
|
37
|
+
("my_awesome_feature.md", "my-awesome-feature"),
|
|
38
|
+
("MIXED_CASE_NAME.md", "mixed-case-name"),
|
|
39
|
+
("spaces and_underscores.md", "spaces-and-underscores"),
|
|
40
|
+
("design/model_refactor.md", "design/model-refactor"),
|
|
41
|
+
(
|
|
42
|
+
"test/multiple_word_directory/feature_name.md",
|
|
43
|
+
"test/multiple-word-directory/feature-name",
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Create test files
|
|
48
|
+
for filename, _ in test_cases:
|
|
49
|
+
content = """
|
|
50
|
+
---
|
|
51
|
+
type: knowledge
|
|
52
|
+
created: 2024-01-01
|
|
53
|
+
modified: 2024-01-01
|
|
54
|
+
---
|
|
55
|
+
# Test File
|
|
56
|
+
|
|
57
|
+
Testing permalink generation.
|
|
58
|
+
"""
|
|
59
|
+
await create_test_file(project_dir / filename, content)
|
|
60
|
+
|
|
61
|
+
# Run sync
|
|
62
|
+
await sync_service.sync(test_config.home)
|
|
63
|
+
|
|
64
|
+
# Verify permalinks
|
|
65
|
+
for filename, expected_permalink in test_cases:
|
|
66
|
+
entity = await entity_service.repository.get_by_file_path(filename)
|
|
67
|
+
assert entity.permalink == expected_permalink, (
|
|
68
|
+
f"File {filename} should have permalink {expected_permalink}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.mark.parametrize(
|
|
73
|
+
"input_path, expected",
|
|
74
|
+
[
|
|
75
|
+
("test/Über File.md", "test/uber-file"),
|
|
76
|
+
("docs/résumé.md", "docs/resume"),
|
|
77
|
+
("notes/Déjà vu.md", "notes/deja-vu"),
|
|
78
|
+
("papers/Jürgen's Findings.md", "papers/jurgens-findings"),
|
|
79
|
+
("archive/François Müller.md", "archive/francois-muller"),
|
|
80
|
+
("research/Søren Kierkegård.md", "research/soren-kierkegard"),
|
|
81
|
+
("articles/El Niño.md", "articles/el-nino"),
|
|
82
|
+
],
|
|
83
|
+
)
|
|
84
|
+
def test_latin_accents_transliteration(input_path, expected):
|
|
85
|
+
"""Test that Latin letters with accents are properly transliterated."""
|
|
86
|
+
assert generate_permalink(input_path) == expected
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.mark.parametrize(
|
|
90
|
+
"input_path, expected",
|
|
91
|
+
[
|
|
92
|
+
("中文/测试文档.md", "中文/测试文档"),
|
|
93
|
+
("notes/北京市.md", "notes/北京市"),
|
|
94
|
+
("research/上海简介.md", "research/上海简介"),
|
|
95
|
+
("docs/中文 English Mixed.md", "docs/中文-english-mixed"),
|
|
96
|
+
("articles/东京Tokyo混合.md", "articles/东京-tokyo-混合"),
|
|
97
|
+
("papers/汉字_underscore_test.md", "papers/汉字-underscore-test"),
|
|
98
|
+
("projects/中文CamelCase测试.md", "projects/中文-camel-case-测试"),
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
def test_chinese_character_preservation(input_path, expected):
|
|
102
|
+
"""Test that Chinese characters are preserved in permalinks."""
|
|
103
|
+
assert generate_permalink(input_path) == expected
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize(
|
|
107
|
+
"input_path, expected",
|
|
108
|
+
[
|
|
109
|
+
("mixed/北京Café.md", "mixed/北京-cafe"),
|
|
110
|
+
("notes/东京Tōkyō.md", "notes/东京-tokyo"),
|
|
111
|
+
("research/München中文.md", "research/munchen-中文"),
|
|
112
|
+
("docs/Über测试.md", "docs/uber-测试"),
|
|
113
|
+
("complex/北京Beijing上海Shanghai.md", "complex/北京-beijing-上海-shanghai"),
|
|
114
|
+
("special/中文!@#$%^&*()_+.md", "special/中文"),
|
|
115
|
+
("punctuation/你好,世界!.md", "punctuation/你好世界"),
|
|
116
|
+
],
|
|
117
|
+
)
|
|
118
|
+
def test_mixed_character_sets(input_path, expected):
|
|
119
|
+
"""Test handling of mixed character sets and edge cases."""
|
|
120
|
+
assert generate_permalink(input_path) == expected
|