basic-memory 0.4.3__tar.gz → 0.5.0__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 (146) hide show
  1. {basic_memory-0.4.3 → basic_memory-0.5.0}/CHANGELOG.md +11 -0
  2. {basic_memory-0.4.3 → basic_memory-0.5.0}/PKG-INFO +1 -1
  3. {basic_memory-0.4.3 → basic_memory-0.5.0}/pyproject.toml +1 -1
  4. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/__init__.py +1 -1
  5. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/api/routers/resource_router.py +1 -1
  6. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/tools/notes.py +41 -23
  7. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/api/test_resource_router.py +0 -1
  8. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/mcp/test_tool_knowledge.py +11 -10
  9. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/mcp/test_tool_notes.py +129 -66
  10. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/mcp/test_tool_search.py +3 -2
  11. {basic_memory-0.4.3 → basic_memory-0.5.0}/uv.lock +1 -1
  12. basic_memory-0.4.3/tests/mcp/test_tool_get_entity.py +0 -45
  13. {basic_memory-0.4.3 → basic_memory-0.5.0}/.github/workflows/pr-title.yml +0 -0
  14. {basic_memory-0.4.3 → basic_memory-0.5.0}/.github/workflows/release.yml +0 -0
  15. {basic_memory-0.4.3 → basic_memory-0.5.0}/.github/workflows/test.yml +0 -0
  16. {basic_memory-0.4.3 → basic_memory-0.5.0}/.gitignore +0 -0
  17. {basic_memory-0.4.3 → basic_memory-0.5.0}/.python-version +0 -0
  18. {basic_memory-0.4.3 → basic_memory-0.5.0}/CITATION.cff +0 -0
  19. {basic_memory-0.4.3 → basic_memory-0.5.0}/CODE_OF_CONDUCT.md +0 -0
  20. {basic_memory-0.4.3 → basic_memory-0.5.0}/CONTRIBUTING.md +0 -0
  21. {basic_memory-0.4.3 → basic_memory-0.5.0}/LICENSE +0 -0
  22. {basic_memory-0.4.3 → basic_memory-0.5.0}/Makefile +0 -0
  23. {basic_memory-0.4.3 → basic_memory-0.5.0}/README.md +0 -0
  24. {basic_memory-0.4.3 → basic_memory-0.5.0}/basic-memory.md +0 -0
  25. {basic_memory-0.4.3 → basic_memory-0.5.0}/installer/Basic.icns +0 -0
  26. {basic_memory-0.4.3 → basic_memory-0.5.0}/installer/README.md +0 -0
  27. {basic_memory-0.4.3 → basic_memory-0.5.0}/installer/icon.svg +0 -0
  28. {basic_memory-0.4.3 → basic_memory-0.5.0}/installer/installer.py +0 -0
  29. {basic_memory-0.4.3 → basic_memory-0.5.0}/installer/make_icons.sh +0 -0
  30. {basic_memory-0.4.3 → basic_memory-0.5.0}/installer/setup.py +0 -0
  31. {basic_memory-0.4.3 → basic_memory-0.5.0}/memory.json +0 -0
  32. {basic_memory-0.4.3 → basic_memory-0.5.0}/scripts/install.sh +0 -0
  33. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/alembic/README +0 -0
  34. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/alembic/env.py +0 -0
  35. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/alembic/migrations.py +0 -0
  36. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/alembic/script.py.mako +0 -0
  37. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
  38. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/api/__init__.py +0 -0
  39. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/api/app.py +0 -0
  40. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/api/routers/__init__.py +0 -0
  41. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/api/routers/knowledge_router.py +0 -0
  42. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/api/routers/memory_router.py +0 -0
  43. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/api/routers/search_router.py +0 -0
  44. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/__init__.py +0 -0
  45. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/app.py +0 -0
  46. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/__init__.py +0 -0
  47. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/db.py +0 -0
  48. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/import_chatgpt.py +0 -0
  49. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/import_claude_conversations.py +0 -0
  50. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/import_claude_projects.py +0 -0
  51. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/import_memory_json.py +0 -0
  52. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/mcp.py +0 -0
  53. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/status.py +0 -0
  54. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/commands/sync.py +0 -0
  55. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/cli/main.py +0 -0
  56. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/config.py +0 -0
  57. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/db.py +0 -0
  58. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/deps.py +0 -0
  59. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/file_utils.py +0 -0
  60. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/markdown/__init__.py +0 -0
  61. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/markdown/entity_parser.py +0 -0
  62. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/markdown/markdown_processor.py +0 -0
  63. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/markdown/plugins.py +0 -0
  64. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/markdown/schemas.py +0 -0
  65. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/markdown/utils.py +0 -0
  66. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/__init__.py +0 -0
  67. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/async_client.py +0 -0
  68. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/server.py +0 -0
  69. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/tools/__init__.py +0 -0
  70. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/tools/knowledge.py +0 -0
  71. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/tools/memory.py +0 -0
  72. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/tools/search.py +0 -0
  73. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/mcp/tools/utils.py +0 -0
  74. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/models/__init__.py +0 -0
  75. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/models/base.py +0 -0
  76. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/models/knowledge.py +0 -0
  77. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/models/search.py +0 -0
  78. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/repository/__init__.py +0 -0
  79. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/repository/entity_repository.py +0 -0
  80. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/repository/observation_repository.py +0 -0
  81. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/repository/relation_repository.py +0 -0
  82. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/repository/repository.py +0 -0
  83. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/repository/search_repository.py +0 -0
  84. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/schemas/__init__.py +0 -0
  85. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/schemas/base.py +0 -0
  86. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/schemas/delete.py +0 -0
  87. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/schemas/discovery.py +0 -0
  88. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/schemas/memory.py +0 -0
  89. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/schemas/request.py +0 -0
  90. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/schemas/response.py +0 -0
  91. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/schemas/search.py +0 -0
  92. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/services/__init__.py +0 -0
  93. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/services/context_service.py +0 -0
  94. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/services/entity_service.py +0 -0
  95. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/services/exceptions.py +0 -0
  96. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/services/file_service.py +0 -0
  97. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/services/link_resolver.py +0 -0
  98. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/services/search_service.py +0 -0
  99. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/services/service.py +0 -0
  100. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/sync/__init__.py +0 -0
  101. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/sync/file_change_scanner.py +0 -0
  102. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/sync/sync_service.py +0 -0
  103. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/sync/utils.py +0 -0
  104. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/sync/watch_service.py +0 -0
  105. {basic_memory-0.4.3 → basic_memory-0.5.0}/src/basic_memory/utils.py +0 -0
  106. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/api/conftest.py +0 -0
  107. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/api/test_knowledge_router.py +0 -0
  108. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/api/test_memory_router.py +0 -0
  109. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/api/test_search_router.py +0 -0
  110. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/cli/test_import_chatgpt.py +0 -0
  111. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/cli/test_import_claude_conversations.py +0 -0
  112. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/cli/test_import_claude_projects.py +0 -0
  113. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/cli/test_import_memory_json.py +0 -0
  114. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/cli/test_status.py +0 -0
  115. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/cli/test_sync.py +0 -0
  116. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/conftest.py +0 -0
  117. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/edit_file_test.py +0 -0
  118. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/markdown/__init__.py +0 -0
  119. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/markdown/test_entity_parser.py +0 -0
  120. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/markdown/test_markdown_plugins.py +0 -0
  121. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/markdown/test_markdown_processor.py +0 -0
  122. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/markdown/test_observation_edge_cases.py +0 -0
  123. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/markdown/test_parser_edge_cases.py +0 -0
  124. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/markdown/test_relation_edge_cases.py +0 -0
  125. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/markdown/test_task_detection.py +0 -0
  126. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/mcp/conftest.py +0 -0
  127. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/mcp/test_tool_memory.py +0 -0
  128. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/mcp/test_tool_utils.py +0 -0
  129. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/repository/test_entity_repository.py +0 -0
  130. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/repository/test_observation_repository.py +0 -0
  131. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/repository/test_relation_repository.py +0 -0
  132. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/repository/test_repository.py +0 -0
  133. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/schemas/test_memory_url.py +0 -0
  134. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/schemas/test_schemas.py +0 -0
  135. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/schemas/test_search.py +0 -0
  136. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/services/test_context_service.py +0 -0
  137. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/services/test_entity_service.py +0 -0
  138. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/services/test_file_service.py +0 -0
  139. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/services/test_link_resolver.py +0 -0
  140. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/services/test_search_service.py +0 -0
  141. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/sync/test_file_change_scanner.py +0 -0
  142. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/sync/test_sync_service.py +0 -0
  143. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/sync/test_watch_service.py +0 -0
  144. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/test_basic_memory.py +0 -0
  145. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/utils/test_file_utils.py +0 -0
  146. {basic_memory-0.4.3 → basic_memory-0.5.0}/tests/utils/test_permalink_formatting.py +0 -0
@@ -1,6 +1,17 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.5.0 (2025-02-18)
5
+
6
+ ### Features
7
+
8
+ - Return semantic info in markdown after write_note
9
+ ([#11](https://github.com/basicmachines-co/basic-memory/pull/11),
10
+ [`0689e7a`](https://github.com/basicmachines-co/basic-memory/commit/0689e7a730497827bf4e16156ae402ddc5949077))
11
+
12
+ Co-authored-by: phernandez <phernandez@basicmachines.co>
13
+
14
+
4
15
  ## v0.4.3 (2025-02-18)
5
16
 
6
17
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-memory
3
- Version: 0.4.3
3
+ Version: 0.5.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "basic-memory"
3
- version = "0.4.3"
3
+ version = "0.5.0"
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.4.3"
3
+ __version__ = "0.5.0"
@@ -31,7 +31,7 @@ def get_entity_ids(item: SearchIndexRow) -> list[int]:
31
31
  from_entity = item.from_id
32
32
  to_entity = item.to_id # pyright: ignore [reportReturnType]
33
33
  return [from_entity, to_entity] if to_entity else [from_entity] # pyright: ignore [reportReturnType]
34
- case _:
34
+ case _: # pragma: no cover
35
35
  raise ValueError(f"Unexpected type: {item.type}")
36
36
 
37
37
 
@@ -17,15 +17,14 @@ from basic_memory.schemas.memory import memory_url_path
17
17
 
18
18
 
19
19
  @mcp.tool(
20
- description="Create or update a markdown note. Returns the permalink for referencing.",
20
+ description="Create or update a markdown note. Returns a markdown formatted summary of the semantic content.",
21
21
  )
22
22
  async def write_note(
23
23
  title: str,
24
24
  content: str,
25
25
  folder: str,
26
26
  tags: Optional[List[str]] = None,
27
- verbose: bool = False,
28
- ) -> EntityResponse | str:
27
+ ) -> str:
29
28
  """Write a markdown note to the knowledge base.
30
29
 
31
30
  The content can include semantic observations and relations using markdown syntax.
@@ -53,14 +52,16 @@ async def write_note(
53
52
  content: Markdown content for the note, can include observations and relations
54
53
  folder: the folder where the file should be saved
55
54
  tags: Optional list of tags to categorize the note
56
- verbose: If True, returns full EntityResponse with semantic info
57
55
 
58
56
  Returns:
59
- If verbose=False: Permalink that can be used to reference the note
60
- If verbose=True: EntityResponse with full semantic details
57
+ A markdown formatted summary of the semantic content, including:
58
+ - Creation/update status
59
+ - File path and checksum
60
+ - Observation counts by category
61
+ - Relation counts (resolved/unresolved)
62
+ - Tags if present
61
63
 
62
64
  Examples:
63
- # Note with both explicit and inline relations
64
65
  write_note(
65
66
  title="Search Implementation",
66
67
  content="# Search Component\\n\\n"
@@ -73,20 +74,6 @@ async def write_note(
73
74
  "- depends_on [[Database Schema]]",
74
75
  folder="docs/components"
75
76
  )
76
-
77
- # Note with tags
78
- write_note(
79
- title="Error Handling Design",
80
- content="# Error Handling\\n\\n"
81
- "This design builds on [[Reliability Design]].\\n\\n"
82
- "## Approach\\n"
83
- "- [design] Use error codes #architecture\\n"
84
- "- [tech] Implement retry logic #implementation\\n\\n"
85
- "## Relations\\n"
86
- "- extends [[Base Error Handling]]",
87
- folder="docs/design",
88
- tags=["architecture", "reliability"]
89
- )
90
77
  """
91
78
  logger.info(f"Writing note folder:'{folder}' title: '{title}'")
92
79
 
@@ -101,12 +88,43 @@ async def write_note(
101
88
  entity_metadata=metadata,
102
89
  )
103
90
 
104
- # Use existing knowledge tool
91
+ # Create or update via knowledge API
105
92
  logger.info(f"Creating {entity.permalink}")
106
93
  url = f"/knowledge/entities/{entity.permalink}"
107
94
  response = await call_put(client, url, json=entity.model_dump())
108
95
  result = EntityResponse.model_validate(response.json())
109
- return result if verbose else result.permalink
96
+
97
+ # Format semantic summary based on status code
98
+ action = "Created" if response.status_code == 201 else "Updated"
99
+ assert result.checksum is not None
100
+ summary = [
101
+ f"# {action} {result.file_path} ({result.checksum[:8]})",
102
+ f"permalink: {result.permalink}",
103
+ ]
104
+
105
+ if result.observations:
106
+ categories = {}
107
+ for obs in result.observations:
108
+ categories[obs.category] = categories.get(obs.category, 0) + 1
109
+
110
+ summary.append("\n## Observations")
111
+ for category, count in sorted(categories.items()):
112
+ summary.append(f"- {category}: {count}")
113
+
114
+ if result.relations:
115
+ unresolved = sum(1 for r in result.relations if not r.to_id)
116
+ resolved = len(result.relations) - unresolved
117
+
118
+ summary.append("\n## Relations")
119
+ summary.append(f"- Resolved: {resolved}")
120
+ if unresolved:
121
+ summary.append(f"- Unresolved: {unresolved}")
122
+ summary.append("\nUnresolved relations will be retried on next sync.")
123
+
124
+ if tags:
125
+ summary.append(f"\n## Tags\n- {', '.join(tags)}")
126
+
127
+ return "\n".join(summary)
110
128
 
111
129
 
112
130
  @mcp.tool(description="Read note content by title, permalink, relation, or pattern")
@@ -158,7 +158,6 @@ async def test_get_resource_entities(client, test_config, entity_repository):
158
158
  entity2 = EntityResponse(**entity_response)
159
159
 
160
160
  assert len(entity2.relations) == 1
161
- relation = entity2.relations[0]
162
161
 
163
162
  # Test getting the content via the relation
164
163
  response = await client.get("/resource/test/*")
@@ -13,7 +13,7 @@ from basic_memory.schemas.delete import DeleteEntitiesRequest
13
13
  async def test_get_single_entity(client):
14
14
  """Test retrieving a single entity."""
15
15
  # First create an entity
16
- permalink = await notes.write_note(
16
+ result = await notes.write_note(
17
17
  title="Test Note",
18
18
  folder="test",
19
19
  content="""
@@ -22,9 +22,10 @@ async def test_get_single_entity(client):
22
22
  """,
23
23
  tags=["test", "documentation"],
24
24
  )
25
+ assert result
25
26
 
26
27
  # Get the entity
27
- entity = await get_entity(permalink)
28
+ entity = await get_entity("test/test-note")
28
29
 
29
30
  # Verify entity details
30
31
  assert entity.title == "Test Note"
@@ -36,40 +37,40 @@ async def test_get_single_entity(client):
36
37
  async def test_get_multiple_entities(client):
37
38
  """Test retrieving multiple entities."""
38
39
  # Create two test entities
39
- permalink1 = await notes.write_note(
40
+ await notes.write_note(
40
41
  title="Test Note 1",
41
42
  folder="test",
42
43
  content="# Test 1",
43
44
  )
44
- permalink2 = await notes.write_note(
45
+ await notes.write_note(
45
46
  title="Test Note 2",
46
47
  folder="test",
47
48
  content="# Test 2",
48
49
  )
49
50
 
50
51
  # Get both entities
51
- request = GetEntitiesRequest(permalinks=[permalink1, permalink2])
52
+ request = GetEntitiesRequest(permalinks=["test/test-note-1", "test/test-note-2"])
52
53
  response = await get_entities(request)
53
54
 
54
55
  # Verify we got both entities
55
56
  assert len(response.entities) == 2
56
57
  permalinks = {e.permalink for e in response.entities}
57
- assert permalink1 in permalinks
58
- assert permalink2 in permalinks
58
+ assert "test/test-note-1" in permalinks
59
+ assert "test/test-note-2" in permalinks
59
60
 
60
61
 
61
62
  @pytest.mark.asyncio
62
63
  async def test_delete_entities(client):
63
64
  """Test deleting entities."""
64
65
  # Create a test entity
65
- permalink = await notes.write_note(
66
+ await notes.write_note(
66
67
  title="Test Note",
67
68
  folder="test",
68
69
  content="# Test Note to Delete",
69
70
  )
70
71
 
71
72
  # Delete the entity
72
- request = DeleteEntitiesRequest(permalinks=[permalink])
73
+ request = DeleteEntitiesRequest(permalinks=["test/test-note"])
73
74
  response = await delete_entities(request)
74
75
 
75
76
  # Verify deletion
@@ -77,7 +78,7 @@ async def test_delete_entities(client):
77
78
 
78
79
  # Verify entity no longer exists
79
80
  with pytest.raises(ToolError):
80
- await get_entity(permalink)
81
+ await get_entity("test/test-note")
81
82
 
82
83
 
83
84
  @pytest.mark.asyncio
@@ -1,10 +1,11 @@
1
1
  """Tests for note tools that exercise the full stack with SQLite."""
2
2
 
3
+ from textwrap import dedent
4
+
3
5
  import pytest
4
6
  from mcp.server.fastmcp.exceptions import ToolError
5
7
 
6
8
  from basic_memory.mcp.tools import notes
7
- from basic_memory.schemas import EntityResponse
8
9
 
9
10
 
10
11
  @pytest.mark.asyncio
@@ -17,31 +18,41 @@ async def test_write_note(app):
17
18
  - Handle tags correctly
18
19
  - Return valid permalink
19
20
  """
20
- permalink = await notes.write_note(
21
+ result = await notes.write_note(
21
22
  title="Test Note",
22
23
  folder="test",
23
24
  content="# Test\nThis is a test note",
24
25
  tags=["test", "documentation"],
25
26
  )
26
27
 
27
- assert permalink # Got a valid permalink
28
+ assert result
29
+ assert (
30
+ dedent("""
31
+ # Created test/Test Note.md (159f2168)
32
+ permalink: test/test-note
33
+
34
+ ## Tags
35
+ - test, documentation
36
+ """).strip()
37
+ in result
38
+ )
28
39
 
29
40
  # Try reading it back via permalink
30
- content = await notes.read_note(permalink)
41
+ content = await notes.read_note("test/test-note")
31
42
  assert (
32
- """
33
- ---
34
- title: Test Note
35
- type: note
36
- permalink: test/test-note
37
- tags:
38
- - '#test'
39
- - '#documentation'
40
- ---
41
-
42
- # Test
43
- This is a test note
44
- """.strip()
43
+ dedent("""
44
+ ---
45
+ title: Test Note
46
+ type: note
47
+ permalink: test/test-note
48
+ tags:
49
+ - '#test'
50
+ - '#documentation'
51
+ ---
52
+
53
+ # Test
54
+ This is a test note
55
+ """).strip()
45
56
  in content
46
57
  )
47
58
 
@@ -49,20 +60,28 @@ This is a test note
49
60
  @pytest.mark.asyncio
50
61
  async def test_write_note_no_tags(app):
51
62
  """Test creating a note without tags."""
52
- permalink = await notes.write_note(title="Simple Note", folder="test", content="Just some text")
63
+ result = await notes.write_note(title="Simple Note", folder="test", content="Just some text")
53
64
 
65
+ assert result
66
+ assert (
67
+ dedent("""
68
+ # Created test/Simple Note.md (9a1ff079)
69
+ permalink: test/simple-note
70
+ """).strip()
71
+ in result
72
+ )
54
73
  # Should be able to read it back
55
- content = await notes.read_note(permalink)
74
+ content = await notes.read_note("test/simple-note")
56
75
  assert (
57
- """
58
- --
59
- title: Simple Note
60
- type: note
61
- permalink: test/simple-note
62
- ---
63
-
64
- Just some text
65
- """.strip()
76
+ dedent("""
77
+ --
78
+ title: Simple Note
79
+ type: note
80
+ permalink: test/simple-note
81
+ ---
82
+
83
+ Just some text
84
+ """).strip()
66
85
  in content
67
86
  )
68
87
 
@@ -84,24 +103,44 @@ async def test_write_note_update_existing(app):
84
103
  - Handle tags correctly
85
104
  - Return valid permalink
86
105
  """
87
- permalink = await notes.write_note(
106
+ result = await notes.write_note(
88
107
  title="Test Note",
89
108
  folder="test",
90
109
  content="# Test\nThis is a test note",
91
110
  tags=["test", "documentation"],
92
111
  )
93
112
 
94
- assert permalink # Got a valid permalink
113
+ assert result # Got a valid permalink
114
+ assert (
115
+ dedent("""
116
+ # Created test/Test Note.md (159f2168)
117
+ permalink: test/test-note
118
+
119
+ ## Tags
120
+ - test, documentation
121
+ """).strip()
122
+ in result
123
+ )
95
124
 
96
- permalink = await notes.write_note(
125
+ result = await notes.write_note(
97
126
  title="Test Note",
98
127
  folder="test",
99
128
  content="# Test\nThis is an updated note",
100
129
  tags=["test", "documentation"],
101
130
  )
131
+ assert (
132
+ dedent("""
133
+ # Updated test/Test Note.md (131b5662)
134
+ permalink: test/test-note
135
+
136
+ ## Tags
137
+ - test, documentation
138
+ """).strip()
139
+ in result
140
+ )
102
141
 
103
142
  # Try reading it back
104
- content = await notes.read_note(permalink)
143
+ content = await notes.read_note("test/test-note")
105
144
  assert (
106
145
  """
107
146
  ---
@@ -135,10 +174,18 @@ async def test_read_note_by_title(app):
135
174
  async def test_note_unicode_content(app):
136
175
  """Test handling of unicode content in notes."""
137
176
  content = "# Test 🚀\nThis note has emoji 🎉 and unicode ♠♣♥♦"
138
- permalink = await notes.write_note(title="Unicode Test", folder="test", content=content)
177
+ result = await notes.write_note(title="Unicode Test", folder="test", content=content)
178
+
179
+ assert (
180
+ dedent("""
181
+ # Created test/Unicode Test.md (272389cd)
182
+ permalink: test/unicode-test
183
+ """).strip()
184
+ in result
185
+ )
139
186
 
140
187
  # Read back should preserve unicode
141
- result = await notes.read_note(permalink)
188
+ result = await notes.read_note("test/unicode-test")
142
189
  assert content in result
143
190
 
144
191
 
@@ -147,20 +194,32 @@ async def test_multiple_notes(app):
147
194
  """Test creating and managing multiple notes."""
148
195
  # Create several notes
149
196
  notes_data = [
150
- ("Note 1", "test", "Content 1", ["tag1"]),
151
- ("Note 2", "test", "Content 2", ["tag1", "tag2"]),
152
- ("Note 3", "test", "Content 3", []),
197
+ ("test/note-1", "Note 1", "test", "Content 1", ["tag1"]),
198
+ ("test/note-2", "Note 2", "test", "Content 2", ["tag1", "tag2"]),
199
+ ("test/note-3", "Note 3", "test", "Content 3", []),
153
200
  ]
154
201
 
155
- permalinks = []
156
- for title, folder, content, tags in notes_data:
157
- permalink = await notes.write_note(title=title, folder=folder, content=content, tags=tags)
158
- permalinks.append(permalink)
202
+ for _, title, folder, content, tags in notes_data:
203
+ await notes.write_note(title=title, folder=folder, content=content, tags=tags)
159
204
 
160
205
  # Should be able to read each one
161
- for i, permalink in enumerate(permalinks):
162
- content = await notes.read_note(permalink)
163
- assert f"Content {i + 1}" in content
206
+ for permalink, title, folder, content, _ in notes_data:
207
+ note = await notes.read_note(permalink)
208
+ assert content in note
209
+
210
+ # read multiple notes at once
211
+
212
+ result = await notes.read_note("test/*")
213
+
214
+ # note we can't compare times
215
+ assert "--- memory://test/note-1" in result
216
+ assert "Content 1" in result
217
+
218
+ assert "--- memory://test/note-2" in result
219
+ assert "Content 2" in result
220
+
221
+ assert "--- memory://test/note-3" in result
222
+ assert "Content 3" in result
164
223
 
165
224
 
166
225
  @pytest.mark.asyncio
@@ -172,16 +231,16 @@ async def test_delete_note_existing(app):
172
231
  - Return valid permalink
173
232
  - Delete the note
174
233
  """
175
- permalink = await notes.write_note(
234
+ result = await notes.write_note(
176
235
  title="Test Note",
177
236
  folder="test",
178
237
  content="# Test\nThis is a test note",
179
238
  tags=["test", "documentation"],
180
239
  )
181
240
 
182
- assert permalink # Got a valid permalink
241
+ assert result
183
242
 
184
- deleted = await notes.delete_note(permalink)
243
+ deleted = await notes.delete_note("test/test-note")
185
244
  assert deleted is True
186
245
 
187
246
 
@@ -207,7 +266,7 @@ async def test_write_note_verbose(app):
207
266
  - Handle tags correctly
208
267
  - Return valid permalink
209
268
  """
210
- entity = await notes.write_note(
269
+ result = await notes.write_note(
211
270
  title="Test Note",
212
271
  folder="test",
213
272
  content="""
@@ -218,24 +277,27 @@ async def test_write_note_verbose(app):
218
277
 
219
278
  """,
220
279
  tags=["test", "documentation"],
221
- verbose=True,
222
280
  )
223
281
 
224
- assert isinstance(entity, EntityResponse)
225
-
226
- assert entity.title == "Test Note"
227
- assert entity.file_path == "test/Test Note.md"
228
- assert entity.entity_type == "note"
229
- assert entity.permalink == "test/test-note"
230
-
231
- assert len(entity.observations) == 1
232
- assert entity.observations[0].content == "First observation"
233
-
234
- assert len(entity.relations) == 1
235
- assert entity.relations[0].relation_type == "relates to"
236
- assert entity.relations[0].from_id == "test/test-note"
237
- assert entity.relations[0].to_id is None
238
- assert entity.relations[0].to_name == "Knowledge"
282
+ assert (
283
+ dedent("""
284
+ # Created test/Test Note.md (06873a7a)
285
+ permalink: test/test-note
286
+
287
+ ## Observations
288
+ - note: 1
289
+
290
+ ## Relations
291
+ - Resolved: 0
292
+ - Unresolved: 1
293
+
294
+ Unresolved relations will be retried on next sync.
295
+
296
+ ## Tags
297
+ - test, documentation
298
+ """).strip()
299
+ in result
300
+ )
239
301
 
240
302
 
241
303
  @pytest.mark.asyncio
@@ -248,13 +310,14 @@ async def test_read_note_memory_url(app):
248
310
  - Return the note content
249
311
  """
250
312
  # First create a note
251
- permalink = await notes.write_note(
313
+ result = await notes.write_note(
252
314
  title="Memory URL Test",
253
315
  folder="test",
254
316
  content="Testing memory:// URL handling",
255
317
  )
318
+ assert result
256
319
 
257
320
  # Should be able to read it with a memory:// URL
258
- memory_url = f"memory://{permalink}"
321
+ memory_url = "memory://test/memory-url-test"
259
322
  content = await notes.read_note(memory_url)
260
323
  assert "Testing memory:// URL handling" in content
@@ -12,12 +12,13 @@ from basic_memory.schemas.search import SearchQuery, SearchItemType
12
12
  async def test_search_basic(client):
13
13
  """Test basic search functionality."""
14
14
  # Create a test note
15
- permalink = await notes.write_note(
15
+ result = await notes.write_note(
16
16
  title="Test Search Note",
17
17
  folder="test",
18
18
  content="# Test\nThis is a searchable test note",
19
19
  tags=["test", "search"],
20
20
  )
21
+ assert result
21
22
 
22
23
  # Search for it
23
24
  query = SearchQuery(text="searchable")
@@ -25,7 +26,7 @@ async def test_search_basic(client):
25
26
 
26
27
  # Verify results
27
28
  assert len(response.results) > 0
28
- assert any(r.permalink == permalink for r in response.results)
29
+ assert any(r.permalink == "test/test-search-note" for r in response.results)
29
30
 
30
31
 
31
32
  @pytest.mark.asyncio
@@ -70,7 +70,7 @@ wheels = [
70
70
 
71
71
  [[package]]
72
72
  name = "basic-memory"
73
- version = "0.4.2"
73
+ version = "0.4.3"
74
74
  source = { editable = "." }
75
75
  dependencies = [
76
76
  { name = "aiosqlite" },
@@ -1,45 +0,0 @@
1
- """Tests for get_entity MCP tool."""
2
-
3
- import pytest
4
- from mcp.server.fastmcp.exceptions import ToolError
5
-
6
- from basic_memory.mcp.tools import notes
7
- from basic_memory.mcp.tools.knowledge import get_entity
8
-
9
-
10
- @pytest.mark.asyncio
11
- async def test_get_basic_entity(client):
12
- """Test retrieving a basic entity."""
13
- # First create an entity
14
- permalink = await notes.write_note(
15
- title="Test Note",
16
- folder="test",
17
- content="""
18
- # Test\nThis is a test note
19
- - [note] First observation
20
- """,
21
- tags=["test", "documentation"],
22
- )
23
-
24
- assert permalink # Got a valid permalink
25
-
26
- # Get the entity without content
27
- entity = await get_entity(permalink)
28
-
29
- # Verify entity details
30
- assert entity.file_path == "test/Test Note.md"
31
- assert entity.entity_type == "note"
32
- assert entity.permalink == "test/test-note"
33
-
34
- # Check observations
35
- assert len(entity.observations) == 1
36
- obs = entity.observations[0]
37
- assert obs.content == "First observation"
38
- assert obs.category == "note"
39
-
40
-
41
- @pytest.mark.asyncio
42
- async def test_get_nonexistent_entity(client):
43
- """Test attempting to get a non-existent entity."""
44
- with pytest.raises(ToolError):
45
- await get_entity("test/nonexistent")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes