mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2493022e23 | |||
| 8aec09ee0b | |||
| 116619dfdb |
@@ -1,12 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
+1
-2
@@ -46,5 +46,4 @@ ENV/
|
||||
|
||||
# obsidian docs:
|
||||
/docs/.obsidian/
|
||||
/examples/.obsidian/
|
||||
/examples/.basic-memory/
|
||||
/examples/.obsidian/
|
||||
@@ -1,76 +1,6 @@
|
||||
# CHANGELOG
|
||||
|
||||
|
||||
## v0.10.0 (2025-03-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Ai_resource_guide.md path
|
||||
([`da97353`](https://github.com/basicmachines-co/basic-memory/commit/da97353cfc3acc1ceb0eca22ac6af326f77dc199))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Ai_resource_guide.md path
|
||||
([`c4732a4`](https://github.com/basicmachines-co/basic-memory/commit/c4732a47b37dd2e404139fb283b65556c81ce7c9))
|
||||
|
||||
- Ai_resource_guide.md path
|
||||
([`2e9d673`](https://github.com/basicmachines-co/basic-memory/commit/2e9d673e54ad6a63a971db64f01fc2f4e59c2e69))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Don't sync *.tmp files on watch ([#31](https://github.com/basicmachines-co/basic-memory/pull/31),
|
||||
[`6b110b2`](https://github.com/basicmachines-co/basic-memory/commit/6b110b28dd8ba705ebfc0bcb41faf2cb993da2c3))
|
||||
|
||||
Fixes #30
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Drop search_index table on db reindex
|
||||
([`31cca6f`](https://github.com/basicmachines-co/basic-memory/commit/31cca6f913849a0ab8fc944803533e3072e9ef88))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Improve utf-8 support for file reading/writing
|
||||
([#32](https://github.com/basicmachines-co/basic-memory/pull/32),
|
||||
[`eb5e4ec`](https://github.com/basicmachines-co/basic-memory/commit/eb5e4ec6bd4d2fe757087be030d867f4ca1d38ba))
|
||||
|
||||
fixes #29
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
### Chores
|
||||
|
||||
- Remove logfire
|
||||
([`9bb8a02`](https://github.com/basicmachines-co/basic-memory/commit/9bb8a020c3425a02cb3a88f6f02adcd281bccee2))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add glama badge. Fix typos in README.md
|
||||
([#28](https://github.com/basicmachines-co/basic-memory/pull/28),
|
||||
[`9af913d`](https://github.com/basicmachines-co/basic-memory/commit/9af913da4fba7bb4908caa3f15f2db2aa03777ec))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Update CLAUDE.md with GitHub integration capabilities
|
||||
([#25](https://github.com/basicmachines-co/basic-memory/pull/25),
|
||||
[`fea2f40`](https://github.com/basicmachines-co/basic-memory/commit/fea2f40d1b54d0c533e6d7ee7ce1aa7b83ad9a47))
|
||||
|
||||
This PR updates the CLAUDE.md file to document the GitHub integration capabilities that enable
|
||||
Claude to participate directly in the development workflow.
|
||||
|
||||
### Features
|
||||
|
||||
- Add Smithery integration for easier installation
|
||||
([#24](https://github.com/basicmachines-co/basic-memory/pull/24),
|
||||
[`eb1e7b6`](https://github.com/basicmachines-co/basic-memory/commit/eb1e7b6088b0b3dead9c104ee44174b2baebf417))
|
||||
|
||||
This PR adds support for deploying Basic Memory on the Smithery platform.
|
||||
|
||||
Signed-off-by: bm-claudeai <claude@basicmachines.co>
|
||||
|
||||
|
||||
## v0.9.0 (2025-03-07)
|
||||
|
||||
### Chores
|
||||
|
||||
@@ -130,42 +130,4 @@ of using AI just for code generation, we've developed a true collaborative workf
|
||||
5. Results improve through iterative collaboration and shared understanding
|
||||
|
||||
This approach has allowed us to tackle more complex challenges and build a more robust system than either humans or AI
|
||||
could achieve independently.
|
||||
|
||||
## GitHub Integration
|
||||
|
||||
Basic Memory has taken AI-Human collaboration to the next level by integrating Claude directly into the development workflow through GitHub:
|
||||
|
||||
### GitHub MCP Tools
|
||||
|
||||
Using the GitHub Model Context Protocol server, Claude can now:
|
||||
|
||||
- **Repository Management**:
|
||||
- View repository files and structure
|
||||
- Read file contents
|
||||
- Create new branches
|
||||
- Create and update files
|
||||
|
||||
- **Issue Management**:
|
||||
- Create new issues
|
||||
- Comment on existing issues
|
||||
- Close and update issues
|
||||
- Search across issues
|
||||
|
||||
- **Pull Request Workflow**:
|
||||
- Create pull requests
|
||||
- Review code changes
|
||||
- Add comments to PRs
|
||||
|
||||
This integration enables Claude to participate as a full team member in the development process, not just as a code generation tool. Claude's GitHub account ([bm-claudeai](https://github.com/bm-claudeai)) is a member of the Basic Machines organization with direct contributor access to the codebase.
|
||||
|
||||
### Collaborative Development Process
|
||||
|
||||
With GitHub integration, the development workflow includes:
|
||||
|
||||
1. **Direct code review** - Claude can analyze PRs and provide detailed feedback
|
||||
2. **Contribution tracking** - All of Claude's contributions are properly attributed in the Git history
|
||||
3. **Branch management** - Claude can create feature branches for implementations
|
||||
4. **Documentation maintenance** - Claude can keep documentation updated as the code evolves
|
||||
|
||||
This level of integration represents a new paradigm in AI-human collaboration, where the AI assistant becomes a full-fledged team member rather than just a tool for generating code snippets.
|
||||
could achieve independently.
|
||||
+34
-38
@@ -1,7 +1,6 @@
|
||||
# Contributing to Basic Memory
|
||||
|
||||
Thank you for considering contributing to Basic Memory! This document outlines the process for contributing to the
|
||||
project and how to get started as a developer.
|
||||
Thank you for considering contributing to Basic Memory! This document outlines the process for contributing to the project and how to get started as a developer.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -66,64 +65,61 @@ project and how to get started as a developer.
|
||||
|
||||
This project is designed for collaborative development between humans and LLMs (Large Language Models):
|
||||
|
||||
1. **CLAUDE.md**: The repository includes a `CLAUDE.md` file that serves as a project guide for both humans and LLMs.
|
||||
This file contains:
|
||||
- Key project information and architectural overview
|
||||
- Development commands and workflows
|
||||
- Code style guidelines
|
||||
- Documentation standards
|
||||
1. **CLAUDE.md**: The repository includes a `CLAUDE.md` file that serves as a project guide for both humans and LLMs. This file contains:
|
||||
- Key project information and architectural overview
|
||||
- Development commands and workflows
|
||||
- Code style guidelines
|
||||
- Documentation standards
|
||||
|
||||
2. **AI-Human Collaborative Workflow**:
|
||||
- We encourage using LLMs like Claude for code generation, reviews, and documentation
|
||||
- When possible, save context in markdown files that can be referenced later
|
||||
- This enables seamless knowledge transfer between different development sessions
|
||||
- Claude can help with implementation details while you focus on architecture and design
|
||||
- We encourage using LLMs like Claude for code generation, reviews, and documentation
|
||||
- When possible, save context in markdown files that can be referenced later
|
||||
- This enables seamless knowledge transfer between different development sessions
|
||||
- Claude can help with implementation details while you focus on architecture and design
|
||||
|
||||
3. **Adding to CLAUDE.md**:
|
||||
- If you discover useful project information or common commands, consider adding them to CLAUDE.md
|
||||
- This helps all contributors (human and AI) maintain consistent knowledge of the project
|
||||
- If you discover useful project information or common commands, consider adding them to CLAUDE.md
|
||||
- This helps all contributors (human and AI) maintain consistent knowledge of the project
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. **Create a Pull Request**: Open a PR against the `main` branch with a clear title and description.
|
||||
2. **Sign the Developer Certificate of Origin (DCO)**: All contributions require signing our DCO, which certifies that
|
||||
you have the right to submit your contributions. This will be automatically checked by our CLA assistant when you
|
||||
create a PR.
|
||||
2. **Sign the Developer Certificate of Origin (DCO)**: All contributions require signing our DCO, which certifies that you have the right to submit your contributions. This will be automatically checked by our CLA assistant when you create a PR.
|
||||
3. **PR Description**: Include:
|
||||
- What the PR changes
|
||||
- Why the change is needed
|
||||
- How you tested the changes
|
||||
- Any related issues (use "Fixes #123" to automatically close issues)
|
||||
- What the PR changes
|
||||
- Why the change is needed
|
||||
- How you tested the changes
|
||||
- Any related issues (use "Fixes #123" to automatically close issues)
|
||||
4. **Code Review**: Wait for code review and address any feedback.
|
||||
5. **CI Checks**: Ensure all CI checks pass.
|
||||
6. **Merge**: Once approved, a maintainer will merge your PR.
|
||||
|
||||
## Developer Certificate of Origin
|
||||
|
||||
By contributing to this project, you agree to the [Developer Certificate of Origin (DCO)](CLA.md). This means you
|
||||
certify that:
|
||||
|
||||
By contributing to this project, you agree to the [Developer Certificate of Origin (DCO)](CLA.md). This means you certify that:
|
||||
- You have the right to submit your contributions
|
||||
- You're not knowingly submitting code with patent or copyright issues
|
||||
- Your contributions are provided under the project's license (AGPL-3.0)
|
||||
|
||||
This is a lightweight alternative to a Contributor License Agreement and helps ensure that all contributions can be
|
||||
properly incorporated into the project and potentially used in commercial applications.
|
||||
This is a lightweight alternative to a Contributor License Agreement and helps ensure that all contributions can be properly incorporated into the project and potentially used in commercial applications.
|
||||
|
||||
### Signing Your Commits
|
||||
|
||||
Sign your commit:
|
||||
You can sign your commits in one of two ways:
|
||||
|
||||
**Using the `-s` or `--signoff` flag**:
|
||||
1. **Using the `-s` or `--signoff` flag**:
|
||||
```bash
|
||||
git commit -s -m "Your commit message"
|
||||
```
|
||||
This adds a `Signed-off-by` line to your commit message, certifying that you adhere to the DCO.
|
||||
|
||||
```bash
|
||||
git commit -s -m "Your commit message"
|
||||
```
|
||||
2. **Configuring Git to automatically sign off**:
|
||||
```bash
|
||||
git config --global alias.cs 'commit -s'
|
||||
```
|
||||
Then use `git cs -m "Your commit message"` to commit with sign-off.
|
||||
|
||||
This adds a `Signed-off-by` line to your commit message, certifying that you adhere to the DCO.
|
||||
|
||||
The sign-off certifies that you have the right to submit your contribution under the project's license and verifies your
|
||||
agreement to the DCO.
|
||||
The sign-off certifies that you have the right to submit your contribution under the project's license and verifies your agreement to the DCO.
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
@@ -139,10 +135,10 @@ agreement to the DCO.
|
||||
|
||||
- **Coverage Target**: We aim for 100% test coverage for all code
|
||||
- **Test Framework**: Use pytest for unit and integration tests
|
||||
- **Mocking**: Use pytest-mock for mocking dependencies only when necessary
|
||||
- **Mocking**: Use pytest-mock for mocking dependencies
|
||||
- **Edge Cases**: Test both normal operation and edge cases
|
||||
- **Database Testing**: Use in-memory SQLite for testing database operations
|
||||
- **Fixtures**: Use async pytest fixtures for setup and teardown
|
||||
- **Fixtures**: Use pytest fixtures for setup and teardown
|
||||
|
||||
## Creating Issues
|
||||
|
||||
@@ -160,4 +156,4 @@ All contributors must follow the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## Thank You!
|
||||
|
||||
Your contributions help make Basic Memory better. We appreciate your time and effort!
|
||||
Your contributions help make Basic Memory better for everyone. We appreciate your time and effort!
|
||||
|
||||
+2
-2
@@ -8,9 +8,9 @@ COPY . .
|
||||
|
||||
# Install pip and build dependencies
|
||||
RUN pip install --upgrade pip \
|
||||
&& pip install . --no-cache-dir --ignore-installed
|
||||
&& pip install . --no-cache-dir --ignore-installed
|
||||
|
||||
# Expose port if necessary (e.g., uv might use a port, but MCP over stdio so not needed here)
|
||||
|
||||
# Use the basic-memory entrypoint to run the MCP server
|
||||
CMD ["basic-memory", "mcp"]
|
||||
CMD ["basic-memory", "mcp"]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Basic Memory
|
||||
|
||||
[](https://www.gnu.org/licenses/agpl-3.0)
|
||||
[](https://badge.fury.io/py/basic-memory)
|
||||
[](https://www.python.org/downloads/)
|
||||
@@ -5,8 +7,6 @@
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
[](https://smithery.ai/server/@basicmachines-co/basic-memory)
|
||||
|
||||
# Basic Memory
|
||||
|
||||
Basic Memory lets you build persistent knowledge through natural conversations with Large Language Models (LLMs) like
|
||||
Claude, while keeping everything in simple Markdown files on your computer. It uses the Model Context Protocol (MCP) to
|
||||
enable any compatible LLM to read and write to your local knowledge base.
|
||||
@@ -14,19 +14,26 @@ enable any compatible LLM to read and write to your local knowledge base.
|
||||
- Website: http://basicmachines.co
|
||||
- Documentation: http://memory.basicmachines.co
|
||||
|
||||
## Pick up your conversation right where you left off
|
||||

|
||||
|
||||
- AI assistants can load context from local files in a new conversation
|
||||
- Notes are saved locally as Markdown files in real time
|
||||
- No project knowledge or special prompting required
|
||||
|
||||
https://github.com/user-attachments/assets/a55d8238-8dd0-454a-be4c-8860dbbd0ddc
|
||||
Basic Memory provides persistent contextual awareness across sessions through a structured knowledge graph.
|
||||
The system enables LLMs to access and reference prior conversations, track semantic relationships between concepts, and
|
||||
incorporate human edits made directly to knowledge files.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installing via Smithery
|
||||
|
||||
To install Basic Memory for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@basicmachines-co/basic-memory):
|
||||
|
||||
```bash
|
||||
npx -y @smithery/cli install @basicmachines-co/basic-memory --client claude
|
||||
```
|
||||
|
||||
### Installing Manually
|
||||
```bash
|
||||
# Install with uv (recommended)
|
||||
uv tool install basic-memory
|
||||
uv install basic-memory
|
||||
|
||||
# Configure Claude Desktop (edit ~/Library/Application Support/Claude/claude_desktop_config.json)
|
||||
# Add this to your config:
|
||||
@@ -50,23 +57,33 @@ uv tool install basic-memory
|
||||
|
||||
You can view shared context via files in `~/basic-memory` (default directory location).
|
||||
|
||||
### Alternative Installation via Smithery
|
||||
You can also install the cli tools to sync files or manage projects.
|
||||
|
||||
You can use [Smithery](https://smithery.ai/server/@basicmachines-co/basic-memory) to automatically configure Basic
|
||||
Memory for Claude Desktop:
|
||||
```bash
|
||||
uv tool install basic-memory
|
||||
|
||||
```bash
|
||||
npx -y @smithery/cli install @basicmachines-co/basic-memory --client claude
|
||||
# create a new project in a different directory
|
||||
basic-memory project add coffee ./examples/coffee
|
||||
|
||||
# you can set the project to the default
|
||||
basic-memory project default coffee
|
||||
```
|
||||
|
||||
This installs and configures Basic Memory without requiring manual edits to the Claude Desktop configuration file. The
|
||||
Smithery server hosts the MCP server component, while your data remains stored locally as Markdown files.
|
||||
View available projects
|
||||
|
||||
### Glama.ai
|
||||
```bash
|
||||
basic-memory project list
|
||||
Basic Memory Projects
|
||||
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓
|
||||
┃ Name ┃ Path ┃ Default ┃ Active ┃
|
||||
┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━┩
|
||||
│ main │ ~/basic-memory │ ✓ │ ✓ │
|
||||
│ coffee │ ~/dev/basicmachines/basic-memory/examples/coffee │ │ │
|
||||
└────────┴──────────────────────────────────────────────────┴─────────┴────────┘
|
||||
```
|
||||
|
||||
<a href="https://glama.ai/mcp/servers/o90kttu9ym">
|
||||
<img width="380" height="200" src="https://glama.ai/mcp/servers/o90kttu9ym/badge" alt="basic-memory MCP server" />
|
||||
</a>
|
||||
Basic Memory will write notes in Markdown format. Open you project directory in your text editor to view project files
|
||||
while you have conversations with an LLM.
|
||||
|
||||
## Why Basic Memory?
|
||||
|
||||
@@ -147,13 +164,36 @@ tags:
|
||||
- affects [[Flavor Extraction]]
|
||||
```
|
||||
|
||||
The note embeds semantic content and links to other topics via simple Markdown formatting.
|
||||
The note embeds semantic content and links to other topics via simple Markdown
|
||||
formatting.
|
||||
|
||||
3. You see this file on your computer in real time in the current project directory (default `~/$HOME/basic-memory`).
|
||||
3. You see this file on your computer in real time in the `~/$HOME/basic-memory` directory:
|
||||
|
||||
- Realtime sync can be enabled via running `basic-memory sync --watch`
|
||||
```markdown
|
||||
---
|
||||
title: Coffee Brewing Methods
|
||||
permalink: coffee-brewing-methods
|
||||
type: note
|
||||
---
|
||||
|
||||
4. In a chat with the LLM, you can reference a topic:
|
||||
# Coffee Brewing Methods
|
||||
|
||||
## Observations
|
||||
|
||||
- [method] Pour over provides more clarity and highlights subtle flavors
|
||||
- [technique] Water temperature at 205°F (96°C) extracts optimal compounds
|
||||
- [principle] Freshly ground beans preserve aromatics and flavor
|
||||
- [preference] Medium-light roasts work best for pour over # Added by you
|
||||
|
||||
## Relations
|
||||
|
||||
- relates_to [[Coffee Bean Origins]]
|
||||
- requires [[Proper Grinding Technique]]
|
||||
- affects [[Flavor Extraction]]
|
||||
- pairs_with [[Breakfast Pastries]] # Added by you
|
||||
```
|
||||
|
||||
4. In a new chat with the LLM, you can reference this knowledge:
|
||||
|
||||
```
|
||||
Look at `coffee-brewing-methods` for context about pour over coffee
|
||||
@@ -173,14 +213,15 @@ Following relation 'requires [[Proper Grinding Technique]]':
|
||||
- Impact of consistent particle size on extraction
|
||||
```
|
||||
|
||||
Each related document can lead to more context, building a rich semantic understanding of your knowledge base.
|
||||
Each related document can lead to more context, building a rich semantic understanding of your knowledge base. All of
|
||||
this context comes from standard Markdown files that both humans and LLMs can read and write.
|
||||
|
||||
This creates a two-way flow where:
|
||||
Every time the LLM writes notes,they are saved in local Markdown files that you can:
|
||||
|
||||
- Humans write and edit Markdown files
|
||||
- LLMs read and write through the MCP protocol
|
||||
- Sync keeps everything consistent
|
||||
- All knowledge stays in local files.
|
||||
- Edit in any text editor
|
||||
- Version via git
|
||||
- Back up normally
|
||||
- Share when you want to
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
@@ -216,7 +257,7 @@ permalink: <a uri slug>
|
||||
|
||||
Observations are facts about a topic.
|
||||
They can be added by creating a Markdown list with a special format that can reference a `category`, `tags` using a
|
||||
"#" character, and an optional `context`.
|
||||
"#" charactor, and an optional `context`.
|
||||
|
||||
Observation Markdown format:
|
||||
|
||||
@@ -260,6 +301,73 @@ Examples of relations:
|
||||
- documented_in [[Coffee Journal]]
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
|
||||
Here's a complete example of a note with frontmatter, observations, and relations:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Pour Over Coffee Method
|
||||
type: note
|
||||
permalink: pour-over-coffee-method
|
||||
tags:
|
||||
- brewing
|
||||
- coffee
|
||||
- techniques
|
||||
---
|
||||
|
||||
# Pour Over Coffee Method
|
||||
|
||||
This note documents the pour over brewing method and my experiences with it.
|
||||
|
||||
## Overview
|
||||
|
||||
The pour over method involves pouring hot water through coffee grounds in a filter. The water drains through the coffee
|
||||
and filter into a carafe or cup.
|
||||
|
||||
## Observations
|
||||
|
||||
- [equipment] Hario V60 dripper produces clean, bright cup #gear
|
||||
- [technique] Pour in concentric circles to ensure even extraction
|
||||
- [ratio] 1:16 coffee-to-water ratio works best for balanced flavor
|
||||
- [timing] Total brew time should be 2:30-3:00 minutes for medium roast
|
||||
- [temperature] Water at 205°F (96°C) extracts optimal flavor compounds
|
||||
- [grind] Medium-fine grind similar to table salt texture
|
||||
- [tip] 30-45 second bloom with double the coffee weight in water
|
||||
- [result] Produces a cleaner cup with more distinct flavor notes than immersion methods
|
||||
|
||||
## Relations
|
||||
|
||||
- complements [[Light Roast Beans]]
|
||||
- requires [[Gooseneck Kettle]]
|
||||
- contrasts_with [[French Press Method]]
|
||||
- pairs_with [[Breakfast Pastries]]
|
||||
- documented_in [[Brewing Journal]]
|
||||
- inspired_by [[Japanese Brewing Techniques]]
|
||||
- affects [[Flavor Extraction]]
|
||||
- part_of [[Morning Ritual]]
|
||||
```
|
||||
|
||||
Basic Memory will parse the Markdown and derive the semantic relationships in the content. When you run
|
||||
`basic-memory sync`:
|
||||
|
||||
1. New and changed files are detected
|
||||
2. Markdown patterns become semantic knowledge:
|
||||
|
||||
- `[tech]` becomes a categorized observation
|
||||
- `[[WikiLink]]` creates a relation in the knowledge graph
|
||||
- Tags and metadata are indexed for search
|
||||
|
||||
3. A SQLite database maintains these relationships for fast querying
|
||||
4. MCP-compatible LLMs can access this knowledge via memory:// URLs
|
||||
|
||||
This creates a two-way flow where:
|
||||
|
||||
- Humans write and edit Markdown files
|
||||
- LLMs read and write through the MCP protocol
|
||||
- Sync keeps everything consistent
|
||||
- All knowledge stays in local files.
|
||||
|
||||
## Using with Claude Desktop
|
||||
|
||||
Basic Memory is built using the MCP (Model Context Protocol) and works with the Claude desktop app (https://claude.ai/):
|
||||
@@ -333,20 +441,263 @@ canvas(nodes, edges, title, folder) - Generate knowledge visualizations
|
||||
"What have I been working on in the past week?"
|
||||
```
|
||||
|
||||
## Futher info
|
||||
## Multiple Projects
|
||||
|
||||
See the [Documentation](https://memory.basicmachines.co/) for more info, including:
|
||||
Basic Memory supports managing multiple separate knowledge bases through projects. This feature allows you to maintain
|
||||
separate knowledge graphs for different purposes (e.g., personal notes, work projects, research topics).
|
||||
|
||||
- [Complete User Guide](https://memory.basicmachines.co/docs/user-guide)
|
||||
- [CLI tools](https://memory.basicmachines.co/docs/cli-reference)
|
||||
- [Managing multiple Projects](https://memory.basicmachines.co/docs/cli-reference#project)
|
||||
- [Importing data from OpenAI/Claude Projects](https://memory.basicmachines.co/docs/cli-reference#import)
|
||||
### Managing Projects
|
||||
|
||||
```bash
|
||||
# List all configured projects
|
||||
basic-memory project list
|
||||
|
||||
# Add a new project
|
||||
basic-memory project add work ~/work-basic-memory
|
||||
|
||||
# Set the default project
|
||||
basic-memory project default work
|
||||
|
||||
# Remove a project (doesn't delete files)
|
||||
basic-memory project remove personal
|
||||
|
||||
# Show current project
|
||||
basic-memory project current
|
||||
```
|
||||
|
||||
### Using Projects in Commands
|
||||
|
||||
All commands support the `--project` flag to specify which project to use:
|
||||
|
||||
```bash
|
||||
# Sync a specific project
|
||||
basic-memory --project=work sync
|
||||
|
||||
# Run MCP server for a specific project
|
||||
basic-memory --project=personal mcp
|
||||
```
|
||||
|
||||
You can also set the `BASIC_MEMORY_PROJECT` environment variable:
|
||||
|
||||
```bash
|
||||
BASIC_MEMORY_PROJECT=work basic-memory sync
|
||||
```
|
||||
|
||||
### Project Isolation
|
||||
|
||||
Each project maintains:
|
||||
|
||||
- Its own collection of markdown files in the specified directory
|
||||
- A separate SQLite database for that project
|
||||
- Complete knowledge graph isolation from other projects
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Basic Memory is built on some key ideas:
|
||||
|
||||
- Your knowledge should stay in files you control
|
||||
- Both humans and AI should use natural formats
|
||||
- Simple text patterns can capture rich meaning
|
||||
- Local-first doesn't mean feature-poor
|
||||
- Knowledge should persist across conversations
|
||||
- AI assistants should build on past context
|
||||
- File formats should be human-readable and editable
|
||||
- Semantic structure should emerge from natural patterns
|
||||
- Knowledge graphs should be both AI and human navigable
|
||||
- Systems should augment human memory, not replace it
|
||||
|
||||
## Importing Existing Data
|
||||
|
||||
Basic Memory provides CLI commands to import data from various sources, converting them into the structured Markdown
|
||||
format:
|
||||
|
||||
### Claude.ai
|
||||
|
||||
First, request an export of your data from your Claude account. The data will be emailed to you in several files,
|
||||
including
|
||||
`conversations.json` and `projects.json`.
|
||||
|
||||
Import Claude.ai conversation data
|
||||
|
||||
```bash
|
||||
basic-memory import claude conversations
|
||||
```
|
||||
|
||||
The conversations will be turned into Markdown files and placed in the "conversations" folder by default (this can be
|
||||
changed with the --folder arg).
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
Importing chats from conversations.json...writing to .../basic-memory
|
||||
Reading chat data... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
╭────────────────────────────╮
|
||||
│ Import complete! │
|
||||
│ │
|
||||
│ Imported 307 conversations │
|
||||
│ Containing 7769 messages │
|
||||
╰────────────────────────────╯
|
||||
```
|
||||
|
||||
Next, you can run the `sync` command to import the data into basic-memory
|
||||
|
||||
```bash
|
||||
basic-memory sync
|
||||
```
|
||||
|
||||
You can also import project data from Claude.ai
|
||||
|
||||
```bash
|
||||
➜ basic-memory import claude projects
|
||||
Importing projects from projects.json...writing to .../basic-memory/projects
|
||||
Reading project data... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
╭────────────────────────────────╮
|
||||
│ Import complete! │
|
||||
│ │
|
||||
│ Imported 101 project documents │
|
||||
│ Imported 32 prompt templates │
|
||||
╰────────────────────────────────╯
|
||||
|
||||
Run 'basic-memory sync' to index the new files.
|
||||
```
|
||||
|
||||
### OpenAI ChatGPT
|
||||
|
||||
```bash
|
||||
➜ basic-memory import chatgpt
|
||||
Importing chats from conversations.json...writing to .../basic-memory/conversations
|
||||
|
||||
Reading chat data... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
╭────────────────────────────╮
|
||||
│ Import complete! │
|
||||
│ │
|
||||
│ Imported 198 conversations │
|
||||
│ Containing 11777 messages │
|
||||
╰────────────────────────────╯
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Knowledge Graph Memory Server
|
||||
|
||||
From the MCP Server: https://github.com/modelcontextprotocol/servers/tree/main/src/memory
|
||||
|
||||
```bash
|
||||
➜ basic-memory import memory-json
|
||||
Importing from memory.json...writing to .../basic-memory
|
||||
Reading memory.json... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
Creating entities... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
╭──────────────────────╮
|
||||
│ Import complete! │
|
||||
│ │
|
||||
│ Created 126 entities │
|
||||
│ Added 252 relations │
|
||||
╰──────────────────────╯
|
||||
```
|
||||
|
||||
## Working with Your Knowledge Base
|
||||
|
||||
Once you've built up a knowledge base, you can interact with it in several ways:
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
Basic Memory provides a powerful CLI for managing your knowledge:
|
||||
|
||||
```bash
|
||||
# See all available commands
|
||||
basic-memory --help
|
||||
|
||||
# Check the status of your knowledge sync
|
||||
basic-memory status
|
||||
|
||||
# Access specific tool functionality directly
|
||||
basic-memory tools
|
||||
|
||||
# Start a continuous sync process
|
||||
basic-memory sync --watch
|
||||
```
|
||||
|
||||
### Obsidian Integration
|
||||
|
||||
Basic Memory works seamlessly with [Obsidian](https://obsidian.md/), a popular knowledge management app:
|
||||
|
||||
1. Point Obsidian to your Basic Memory directory
|
||||
2. Use standard Obsidian features like backlinks and graph view
|
||||
3. See your knowledge graph visually
|
||||
4. Use the canvas visualization generated by Basic Memory
|
||||
|
||||
### File Organization
|
||||
|
||||
Basic Memory is flexible about how you organize your files:
|
||||
|
||||
- Group by topic in folders
|
||||
- Use a flat structure with descriptive filenames
|
||||
- Add custom metadata in frontmatter
|
||||
- Tag files for better searchability
|
||||
|
||||
The system will build the semantic knowledge graph regardless of your file organization preference.
|
||||
|
||||
## Using stdin with Basic Memory's `write_note` Tool
|
||||
|
||||
The `write-note` tool supports reading content from standard input (stdin), allowing for more flexible workflows when
|
||||
creating or updating notes in your Basic Memory knowledge base.
|
||||
|
||||
### Use Cases
|
||||
|
||||
This feature is particularly useful for:
|
||||
|
||||
1. **Piping output from other commands** directly into Basic Memory notes
|
||||
2. **Creating notes with multi-line content** without having to escape quotes or special characters
|
||||
3. **Integrating with AI assistants** like Claude Code that can generate content and pipe it to Basic Memory
|
||||
4. **Processing text data** from files or other sources
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Method 1: Using a Pipe
|
||||
|
||||
You can pipe content from another command into `write_note`:
|
||||
|
||||
```bash
|
||||
# Pipe output of a command into a new note
|
||||
echo "# My Note\n\nThis is a test note" | basic-memory tools write-note --title "Test Note" --folder "notes"
|
||||
|
||||
# Pipe output of a file into a new note
|
||||
cat README.md | basic-memory tools write-note --title "Project README" --folder "documentation"
|
||||
|
||||
# Process text through other tools before saving as a note
|
||||
cat data.txt | grep "important" | basic-memory tools write-note --title "Important Data" --folder "data"
|
||||
```
|
||||
|
||||
### Method 2: Using Heredoc Syntax
|
||||
|
||||
For multi-line content, you can use heredoc syntax:
|
||||
|
||||
```bash
|
||||
# Create a note with heredoc
|
||||
cat << EOF | basic-memory tools write_note --title "Project Ideas" --folder "projects"
|
||||
# Project Ideas for Q2
|
||||
|
||||
## AI Integration
|
||||
- Improve recommendation engine
|
||||
- Add semantic search to product catalog
|
||||
|
||||
## Infrastructure
|
||||
- Migrate to Kubernetes
|
||||
- Implement CI/CD pipeline
|
||||
EOF
|
||||
```
|
||||
|
||||
### Method 3: Input Redirection
|
||||
|
||||
You can redirect input from a file:
|
||||
|
||||
```bash
|
||||
# Create a note from file content
|
||||
basic-memory tools write-note --title "Meeting Notes" --folder "meetings" < meeting_notes.md
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
AGPL-3.0
|
||||
|
||||
Contributions are welcome. See the [Contributing](CONTRIBUTING.md) guide for info about setting up the project locally
|
||||
and submitting PRs.
|
||||
|
||||
Built with ♥️ by Basic Machines
|
||||
Built with ♥️ by Basic Machines
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.x.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
If you find a vulnerability, please contact hello@basicmachines.co
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"file-explorer": true,
|
||||
"global-search": true,
|
||||
"switcher": true,
|
||||
"graph": true,
|
||||
"backlink": true,
|
||||
"canvas": true,
|
||||
"outgoing-link": true,
|
||||
"tag-pane": true,
|
||||
"properties": false,
|
||||
"page-preview": true,
|
||||
"daily-notes": true,
|
||||
"templates": true,
|
||||
"note-composer": true,
|
||||
"command-palette": true,
|
||||
"slash-command": false,
|
||||
"editor-status": true,
|
||||
"bookmarks": true,
|
||||
"markdown-importer": false,
|
||||
"zk-prefixer": false,
|
||||
"random-note": false,
|
||||
"outline": true,
|
||||
"word-count": true,
|
||||
"slides": false,
|
||||
"audio-recorder": false,
|
||||
"workspaces": false,
|
||||
"file-recovery": true,
|
||||
"publish": true,
|
||||
"sync": true,
|
||||
"webviewer": false
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"siteId": "947ee055a8c6f1a57efa4afa09791e62",
|
||||
"host": "publish-01.obsidian.md",
|
||||
"included": [],
|
||||
"excluded": []
|
||||
}
|
||||
@@ -9,16 +9,15 @@ permalink: docs/getting-started
|
||||
This guide will help you install Basic Memory, configure it with Claude Desktop, and create your first knowledge notes
|
||||
through conversations.
|
||||
|
||||
Basic Memory uses the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) to connect with LLMs.
|
||||
It can be used with any service that supports the MCP, but Claude Desktop works especially well.
|
||||
Basic Memory uses the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) to connect with LLMs. It can be used with any service that supports the MCP, but Claude Desktop works especially well.
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Install Basic Memory
|
||||
|
||||
```bash
|
||||
# Install with uv (recommended).
|
||||
uv tool install basic-memory
|
||||
# Install with uv (recommended)
|
||||
uv install basic-memory
|
||||
|
||||
# Or with pip
|
||||
pip install basic-memory
|
||||
@@ -26,9 +25,6 @@ pip install basic-memory
|
||||
|
||||
> **Important**: You need to install Basic Memory using one of the commands above to use the command line tools.
|
||||
|
||||
Using `uv tool install` will install the basic-memory package in a standalone virtual environment.
|
||||
See the [UV docs](https://docs.astral.sh/uv/concepts/tools/) for more info.
|
||||
|
||||
### 2. Configure Claude Desktop
|
||||
|
||||
Claude Desktop often has trouble finding executables in your user path. Follow these steps for a reliable setup:
|
||||
@@ -36,11 +32,9 @@ Claude Desktop often has trouble finding executables in your user path. Follow t
|
||||
#### Step 1: Find the absolute path to uvx
|
||||
|
||||
Open Terminal and run:
|
||||
|
||||
```bash
|
||||
which uvx
|
||||
```
|
||||
|
||||
This will show you the full path (e.g., `/Users/yourusername/.cargo/bin/uvx`).
|
||||
|
||||
#### Step 2: Edit Claude Desktop Configuration
|
||||
@@ -113,7 +107,6 @@ If Claude cannot find Basic Memory tools:
|
||||
#### Permission Issues
|
||||
|
||||
If you encounter permission errors:
|
||||
|
||||
1. Check that Basic Memory has access to create files in your home directory
|
||||
2. Ensure Claude Desktop has permission to execute the uvx command
|
||||
|
||||
|
||||
@@ -24,13 +24,7 @@ Basic Memory connects you and AI assistants through shared knowledge:
|
||||
Both you and AI assistants like Claude can read from and write to the same knowledge base, creating a continuous
|
||||
learning environment where each conversation builds upon previous ones.
|
||||
|
||||
## Pick up your conversation right where you left off
|
||||
|
||||
- AI assistants can load context from local files in a new conversation
|
||||
- Notes are saved locally as Markdown files in real time
|
||||
- No project knowledge or special prompting required
|
||||
|
||||
![[Claude-Obsidian-Demo.mp4]]
|
||||
![[Obsidian-CoffeeKnowledgeBase-examples-overlays.gif]]
|
||||
|
||||
Basic Memory uses:
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "basic-memory"
|
||||
version = "0.10.0"
|
||||
version = "0.9.0"
|
||||
description = "Local-first knowledge management combining Zettelkasten with knowledge graphs"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12.1"
|
||||
|
||||
+4
-2
@@ -7,9 +7,11 @@ startCommand:
|
||||
type: object
|
||||
properties: {}
|
||||
description: No configuration required. This MCP server runs using the default command.
|
||||
commandFunction: |-
|
||||
commandFunction:
|
||||
# A JS function that produces the CLI command based on the given config to start the MCP on stdio.
|
||||
|-
|
||||
(config) => ({
|
||||
command: 'basic-memory',
|
||||
args: ['mcp']
|
||||
})
|
||||
exampleConfig: {}
|
||||
exampleConfig: {}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
|
||||
|
||||
__version__ = "0.10.0"
|
||||
__version__ = "0.9.0"
|
||||
@@ -3,6 +3,9 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter
|
||||
from sqlalchemy import text
|
||||
|
||||
from basic_memory.config import config, config_manager
|
||||
from basic_memory.deps import (
|
||||
ProjectInfoRepositoryDep,
|
||||
@@ -15,8 +18,6 @@ from basic_memory.schemas import (
|
||||
SystemStatus,
|
||||
)
|
||||
from basic_memory.sync.watch_service import WATCH_STATUS_JSON
|
||||
from fastapi import APIRouter
|
||||
from sqlalchemy import text
|
||||
|
||||
router = APIRouter(prefix="/stats", tags=["statistics"])
|
||||
|
||||
@@ -261,7 +262,7 @@ async def get_system_status() -> SystemStatus:
|
||||
watch_status_path = config.home / ".basic-memory" / WATCH_STATUS_JSON
|
||||
if watch_status_path.exists():
|
||||
try:
|
||||
watch_status = json.loads(watch_status_path.read_text(encoding="utf-8"))
|
||||
watch_status = json.loads(watch_status_path.read_text())
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
@@ -7,15 +7,16 @@ from pathlib import Path
|
||||
from typing import Dict, Any, List, Annotated, Set, Optional
|
||||
|
||||
import typer
|
||||
from basic_memory.cli.app import import_app
|
||||
from basic_memory.config import config
|
||||
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
||||
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
||||
from loguru import logger
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
||||
|
||||
from basic_memory.cli.app import import_app
|
||||
from basic_memory.config import config
|
||||
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
||||
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
@@ -166,7 +167,7 @@ async def process_chatgpt_json(
|
||||
read_task = progress.add_task("Reading chat data...", total=None)
|
||||
|
||||
# Read conversations
|
||||
conversations = json.loads(json_path.read_text(encoding="utf-8"))
|
||||
conversations = json.loads(json_path.read_text())
|
||||
progress.update(read_task, total=len(conversations))
|
||||
|
||||
# Process each conversation
|
||||
|
||||
@@ -7,15 +7,16 @@ from pathlib import Path
|
||||
from typing import Dict, Any, List, Annotated
|
||||
|
||||
import typer
|
||||
from basic_memory.cli.app import claude_app
|
||||
from basic_memory.config import config
|
||||
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
||||
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
||||
from loguru import logger
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
||||
|
||||
from basic_memory.cli.app import claude_app
|
||||
from basic_memory.config import config
|
||||
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
||||
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
@@ -123,7 +124,7 @@ async def process_conversations_json(
|
||||
read_task = progress.add_task("Reading chat data...", total=None)
|
||||
|
||||
# Read chat data - handle array of arrays format
|
||||
data = json.loads(json_path.read_text(encoding="utf-8"))
|
||||
data = json.loads(json_path.read_text())
|
||||
conversations = [chat for chat in data]
|
||||
progress.update(read_task, total=len(conversations))
|
||||
|
||||
|
||||
@@ -6,15 +6,16 @@ from pathlib import Path
|
||||
from typing import Dict, Any, Annotated, Optional
|
||||
|
||||
import typer
|
||||
from basic_memory.cli.app import claude_app
|
||||
from basic_memory.config import config
|
||||
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
||||
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
||||
from loguru import logger
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
||||
|
||||
from basic_memory.cli.app import claude_app
|
||||
from basic_memory.config import config
|
||||
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
||||
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
@@ -102,7 +103,7 @@ async def process_projects_json(
|
||||
read_task = progress.add_task("Reading project data...", total=None)
|
||||
|
||||
# Read project data
|
||||
data = json.loads(json_path.read_text(encoding="utf-8"))
|
||||
data = json.loads(json_path.read_text())
|
||||
progress.update(read_task, total=len(data))
|
||||
|
||||
# Track import counts
|
||||
|
||||
@@ -5,12 +5,13 @@ import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Literal, Optional
|
||||
|
||||
import basic_memory
|
||||
from basic_memory.utils import setup_logging
|
||||
from loguru import logger
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
import basic_memory
|
||||
from basic_memory.utils import setup_logging
|
||||
|
||||
DATABASE_NAME = "memory.db"
|
||||
DATA_DIR_NAME = ".basic-memory"
|
||||
CONFIG_FILE_NAME = "config.json"
|
||||
@@ -110,7 +111,7 @@ class ConfigManager:
|
||||
"""Load configuration from file or create default."""
|
||||
if self.config_file.exists():
|
||||
try:
|
||||
data = json.loads(self.config_file.read_text(encoding="utf-8"))
|
||||
data = json.loads(self.config_file.read_text())
|
||||
return BasicMemoryConfig(**data)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load config: {e}")
|
||||
|
||||
@@ -85,7 +85,7 @@ async def write_file_atomic(path: FilePath, content: str) -> None:
|
||||
temp_path = path_obj.with_suffix(".tmp")
|
||||
|
||||
try:
|
||||
temp_path.write_text(content, encoding="utf-8")
|
||||
temp_path.write_text(content)
|
||||
temp_path.replace(path_obj)
|
||||
logger.debug("Wrote file atomically", path=str(path_obj), content_length=len(content))
|
||||
except Exception as e: # pragma: no cover
|
||||
@@ -203,7 +203,7 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
|
||||
path_obj = Path(path) if isinstance(path, str) else path
|
||||
|
||||
# Read current content
|
||||
content = path_obj.read_text(encoding="utf-8")
|
||||
content = path_obj.read_text()
|
||||
|
||||
# Parse current frontmatter
|
||||
current_fm = {}
|
||||
@@ -215,7 +215,7 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
|
||||
new_fm = {**current_fm, **updates}
|
||||
|
||||
# Write new file with updated frontmatter
|
||||
yaml_fm = yaml.dump(new_fm, sort_keys=False, allow_unicode=True)
|
||||
yaml_fm = yaml.dump(new_fm, sort_keys=False)
|
||||
final_content = f"---\n{yaml_fm}---\n\n{content.strip()}"
|
||||
|
||||
logger.debug("Updating frontmatter", path=str(path_obj), update_keys=list(updates.keys()))
|
||||
|
||||
@@ -83,7 +83,7 @@ class MarkdownProcessor:
|
||||
"""
|
||||
# Dirty check if needed
|
||||
if expected_checksum is not None:
|
||||
current_content = path.read_text(encoding="utf-8")
|
||||
current_content = path.read_text()
|
||||
current_checksum = await file_utils.compute_checksum(current_content)
|
||||
if current_checksum != expected_checksum:
|
||||
raise DirtyFileError(f"File {path} has been modified")
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from pathlib import Path
|
||||
|
||||
from basic_memory.mcp.server import mcp
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory.mcp.server import mcp
|
||||
|
||||
|
||||
@mcp.resource(
|
||||
uri="memory://ai_assistant_guide",
|
||||
@@ -19,9 +20,7 @@ def ai_assistant_guide() -> str:
|
||||
A focused guide on Basic Memory usage.
|
||||
"""
|
||||
logger.info("Loading AI assistant guide resource")
|
||||
guide_doc = (
|
||||
Path(__file__).parent.parent.parent.parent.parent / "static" / "ai_assistant_guide.md"
|
||||
)
|
||||
content = guide_doc.read_text(encoding="utf-8")
|
||||
guide_doc = Path(__file__).parent.parent.parent.parent.parent / "static" / "ai_assistant_guide.md"
|
||||
content = guide_doc.read_text()
|
||||
logger.info(f"Loaded AI assistant guide ({len(content)} chars)")
|
||||
return content
|
||||
|
||||
@@ -5,6 +5,8 @@ from os import stat_result
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Tuple, Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory import file_utils
|
||||
from basic_memory.file_utils import FileError
|
||||
from basic_memory.markdown.markdown_processor import MarkdownProcessor
|
||||
@@ -12,7 +14,6 @@ from basic_memory.models import Entity as EntityModel
|
||||
from basic_memory.schemas import Entity as EntitySchema
|
||||
from basic_memory.services.exceptions import FileOperationError
|
||||
from basic_memory.utils import FilePath
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class FileService:
|
||||
@@ -170,7 +171,8 @@ class FileService:
|
||||
|
||||
try:
|
||||
logger.debug("Reading file", operation="read_file", path=str(full_path))
|
||||
content = full_path.read_text(encoding="utf-8")
|
||||
|
||||
content = full_path.read_text()
|
||||
checksum = await file_utils.compute_checksum(content)
|
||||
|
||||
logger.debug(
|
||||
@@ -234,7 +236,7 @@ class FileService:
|
||||
try:
|
||||
if self.is_markdown(path):
|
||||
# read str
|
||||
content = full_path.read_text(encoding="utf-8")
|
||||
content = full_path.read_text()
|
||||
else:
|
||||
# read bytes
|
||||
content = full_path.read_bytes()
|
||||
|
||||
@@ -5,15 +5,16 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Set
|
||||
|
||||
from basic_memory.config import ProjectConfig
|
||||
from basic_memory.services.file_service import FileService
|
||||
from basic_memory.sync.sync_service import SyncService
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
from rich.console import Console
|
||||
from watchfiles import awatch
|
||||
from watchfiles.main import FileChange, Change
|
||||
|
||||
from basic_memory.config import ProjectConfig
|
||||
from basic_memory.services.file_service import FileService
|
||||
from basic_memory.sync.sync_service import SyncService
|
||||
|
||||
WATCH_STATUS_JSON = "watch-status.json"
|
||||
|
||||
|
||||
@@ -137,10 +138,6 @@ class WatchService:
|
||||
if part.startswith("."):
|
||||
return False
|
||||
|
||||
# Skip temp files used in atomic operations
|
||||
if path.endswith(".tmp"):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def write_status(self):
|
||||
@@ -164,11 +161,6 @@ class WatchService:
|
||||
for change, path in changes:
|
||||
# convert to relative path
|
||||
relative_path = str(Path(path).relative_to(directory))
|
||||
|
||||
# Skip .tmp files - they're temporary and shouldn't be synced
|
||||
if relative_path.endswith(".tmp"):
|
||||
continue
|
||||
|
||||
if change == Change.added:
|
||||
adds.append(relative_path)
|
||||
elif change == Change.deleted:
|
||||
@@ -192,8 +184,8 @@ class WatchService:
|
||||
# We don't need to process directories, only the files inside them
|
||||
# This prevents errors when trying to compute checksums or read directories as files
|
||||
added_full_path = directory / added_path
|
||||
if not added_full_path.exists() or added_full_path.is_dir():
|
||||
logger.debug("Skipping non-existent or directory path", path=added_path)
|
||||
if added_full_path.is_dir():
|
||||
logger.debug("Skipping directory for move detection", path=added_path)
|
||||
processed.add(added_path)
|
||||
continue
|
||||
|
||||
@@ -255,12 +247,10 @@ class WatchService:
|
||||
if path not in processed:
|
||||
# Skip directories - only process files
|
||||
full_path = directory / path
|
||||
if not full_path.exists() or full_path.is_dir():
|
||||
logger.debug(
|
||||
"Skipping non-existent or directory path", path=path
|
||||
) # pragma: no cover
|
||||
processed.add(path) # pragma: no cover
|
||||
continue # pragma: no cover
|
||||
if full_path.is_dir(): # pragma: no cover
|
||||
logger.debug("Skipping directory", path=path)
|
||||
processed.add(path)
|
||||
continue
|
||||
|
||||
logger.debug("Processing new file", path=path)
|
||||
entity, checksum = await self.sync_service.sync_file(path, new=True)
|
||||
@@ -291,8 +281,8 @@ class WatchService:
|
||||
if path not in processed:
|
||||
# Skip directories - only process files
|
||||
full_path = directory / path
|
||||
if not full_path.exists() or full_path.is_dir():
|
||||
logger.debug("Skipping non-existent or directory path", path=path)
|
||||
if full_path.is_dir():
|
||||
logger.debug("Skipping directory", path=path)
|
||||
processed.add(path)
|
||||
continue
|
||||
|
||||
@@ -351,4 +341,4 @@ class WatchService:
|
||||
duration_ms=duration_ms,
|
||||
)
|
||||
|
||||
await self.write_status()
|
||||
await self.write_status()
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
"""Test proper handling of .tmp files during sync."""
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from watchfiles import Change
|
||||
|
||||
from basic_memory.sync.watch_service import WatchService
|
||||
|
||||
|
||||
async def create_test_file(path: Path, content: str = "test content") -> None:
|
||||
"""Create a test file with given content."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_temp_file_filter(watch_service):
|
||||
"""Test that .tmp files are correctly filtered out."""
|
||||
# Test filter_changes method directly
|
||||
tmp_path = str(watch_service.config.home / "test.tmp")
|
||||
assert not watch_service.filter_changes(Change.added, tmp_path)
|
||||
|
||||
# Test with valid file
|
||||
valid_path = str(watch_service.config.home / "test.md")
|
||||
assert watch_service.filter_changes(Change.added, valid_path)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_tmp_files(watch_service, test_config, monkeypatch):
|
||||
"""Test handling of .tmp files during sync process."""
|
||||
project_dir = test_config.home
|
||||
|
||||
# Create a .tmp file - this simulates a file being written with write_file_atomic
|
||||
tmp_file = project_dir / "test.tmp"
|
||||
await create_test_file(tmp_file, "This is a temporary file")
|
||||
|
||||
# Create the target final file
|
||||
final_file = project_dir / "test.md"
|
||||
await create_test_file(final_file, "This is the final file")
|
||||
|
||||
# Setup changes that include both the .tmp and final file
|
||||
changes = {
|
||||
(Change.added, str(tmp_file)),
|
||||
(Change.added, str(final_file)),
|
||||
}
|
||||
|
||||
# Track sync_file calls
|
||||
sync_calls = []
|
||||
|
||||
# Mock sync_file to track calls
|
||||
original_sync_file = watch_service.sync_service.sync_file
|
||||
|
||||
async def mock_sync_file(path, new=True):
|
||||
sync_calls.append(path)
|
||||
return await original_sync_file(path, new)
|
||||
|
||||
monkeypatch.setattr(watch_service.sync_service, "sync_file", mock_sync_file)
|
||||
|
||||
# Handle changes
|
||||
await watch_service.handle_changes(project_dir, changes)
|
||||
|
||||
# Verify .tmp file was not processed
|
||||
assert "test.tmp" not in sync_calls
|
||||
assert "test.md" in sync_calls
|
||||
|
||||
# Verify only the final file got an entity
|
||||
tmp_entity = await watch_service.sync_service.entity_repository.get_by_file_path("test.tmp")
|
||||
final_entity = await watch_service.sync_service.entity_repository.get_by_file_path("test.md")
|
||||
|
||||
assert tmp_entity is None, "Temp file should not have an entity"
|
||||
assert final_entity is not None, "Final file should have an entity"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_atomic_write_tmp_file_handling(watch_service, test_config, monkeypatch):
|
||||
"""Test handling of file changes during atomic write operations."""
|
||||
project_dir = test_config.home
|
||||
|
||||
# This test simulates the full atomic write process:
|
||||
# 1. First a .tmp file is created
|
||||
# 2. Then the .tmp file is renamed to the final file
|
||||
# 3. Both events are processed by the watch service
|
||||
|
||||
# Setup file paths
|
||||
tmp_path = project_dir / "document.tmp"
|
||||
final_path = project_dir / "document.md"
|
||||
|
||||
# Create mockup of the atomic write process
|
||||
await create_test_file(tmp_path, "Content for document")
|
||||
|
||||
# First batch of changes - .tmp file created
|
||||
changes1 = {(Change.added, str(tmp_path))}
|
||||
|
||||
# Process first batch
|
||||
await watch_service.handle_changes(project_dir, changes1)
|
||||
|
||||
# Now "replace" the temp file with the final file
|
||||
tmp_path.rename(final_path)
|
||||
|
||||
# Second batch of changes - .tmp file deleted, final file added
|
||||
changes2 = {
|
||||
(Change.deleted, str(tmp_path)),
|
||||
(Change.added, str(final_path))
|
||||
}
|
||||
|
||||
# Process second batch
|
||||
await watch_service.handle_changes(project_dir, changes2)
|
||||
|
||||
# Verify only the final file is in the database
|
||||
tmp_entity = await watch_service.sync_service.entity_repository.get_by_file_path("document.tmp")
|
||||
final_entity = await watch_service.sync_service.entity_repository.get_by_file_path("document.md")
|
||||
|
||||
assert tmp_entity is None, "Temp file should not have an entity"
|
||||
assert final_entity is not None, "Final file should have an entity"
|
||||
|
||||
# Check events
|
||||
new_events = [e for e in watch_service.state.recent_events if e.action == "new"]
|
||||
assert len(new_events) == 1
|
||||
assert new_events[0].path == "document.md"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rapid_atomic_writes(watch_service, test_config):
|
||||
"""Test handling of rapid atomic writes to the same destination."""
|
||||
project_dir = test_config.home
|
||||
|
||||
# This test simulates multiple rapid atomic writes to the same file:
|
||||
# 1. Several .tmp files are created one after another
|
||||
# 2. Each is then renamed to the same final file
|
||||
# 3. Events are batched and processed together
|
||||
|
||||
# Setup file paths
|
||||
tmp1_path = project_dir / "document.1.tmp"
|
||||
tmp2_path = project_dir / "document.2.tmp"
|
||||
final_path = project_dir / "document.md"
|
||||
|
||||
# Create multiple temp files that will be used in sequence
|
||||
await create_test_file(tmp1_path, "First version")
|
||||
await create_test_file(tmp2_path, "Second version")
|
||||
|
||||
# Simulate the first atomic write
|
||||
tmp1_path.rename(final_path)
|
||||
|
||||
# Brief pause to ensure file system registers the change
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# Read content to verify
|
||||
content1 = final_path.read_text()
|
||||
assert content1 == "First version"
|
||||
|
||||
# Simulate the second atomic write
|
||||
tmp2_path.rename(final_path)
|
||||
|
||||
# Verify content was updated
|
||||
content2 = final_path.read_text()
|
||||
assert content2 == "Second version"
|
||||
|
||||
# Create a batch of changes that might arrive in mixed order
|
||||
changes = {
|
||||
(Change.added, str(tmp1_path)),
|
||||
(Change.deleted, str(tmp1_path)),
|
||||
(Change.added, str(tmp2_path)),
|
||||
(Change.deleted, str(tmp2_path)),
|
||||
(Change.added, str(final_path)),
|
||||
(Change.modified, str(final_path)),
|
||||
}
|
||||
|
||||
# Process all changes
|
||||
await watch_service.handle_changes(project_dir, changes)
|
||||
|
||||
# Verify only the final file is in the database
|
||||
final_entity = await watch_service.sync_service.entity_repository.get_by_file_path("document.md")
|
||||
assert final_entity is not None
|
||||
|
||||
# Also verify no tmp entities were created
|
||||
tmp1_entity = await watch_service.sync_service.entity_repository.get_by_file_path("document.1.tmp")
|
||||
tmp2_entity = await watch_service.sync_service.entity_repository.get_by_file_path("document.2.tmp")
|
||||
assert tmp1_entity is None
|
||||
assert tmp2_entity is None
|
||||
Reference in New Issue
Block a user