Compare commits

..

25 Commits

Author SHA1 Message Date
phernandez 6a7206ad9a feat: add DXT packaging support (experimental)
- Add comprehensive manifest.json with all 19 Basic Memory MCP tools
- Implement automated DXT build process via justfile (just dxt)
- Add DXT assets (icons and screenshots)
- Create .dxtignore for excluding development files
- Update .gitignore to exclude generated DXT bundle files

Note: DXT implementation blocked by Anthropic DXT v0.1 limitations:
- Binary permissions issues (anthropics/dxt#12)
- Platform-specific dependency problems (anthropics/dxt#17)
- Native module loading errors with pydantic_core

See issue #193 for detailed analysis and future implementation plan.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-28 14:52:15 -05:00
Drew Cain 39f811f8b5 Update README.md
Added Homebrew instructions to README.md

Signed-off-by: Drew Cain <groksrc@users.noreply.github.com>
2025-06-26 21:51:14 -05:00
phernandez 8e69c8b533 chore: update version to 0.14.0 for v0.14.0 release 2025-06-26 16:18:10 -05:00
phernandez 627a5c3c22 docs: add comprehensive v0.14.0 changelog entry
Add detailed changelog for v0.14.0 release including:
- Docker Container Registry migration to GitHub Container Registry
- Enhanced search documentation with comprehensive syntax examples
- Cross-project file management with intelligent boundary detection
- 8 major bug fixes with issue numbers and commit links
- Technical improvements and infrastructure enhancements
- Migration guide and installation instructions

Covers all changes since v0.13.7 with proper categorization and
user-facing descriptions for better release communication.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-26 16:15:11 -05:00
phernandez cd88945b22 remove v0.13.0 from changelog
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-26 16:11:05 -05:00
phernandez cd8e372f0a fix: add test coverage for optional permalink in EntityResponse schema
- Add comprehensive test for None permalink validation in EntityResponse
- Ensures schema properly handles markdown files without explicit permalinks
- Addresses GitHub issue #170 validation errors during edit operations
- Test validates that permalink=None doesn't cause ValidationError

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-26 15:58:49 -05:00
Paul Hernandez a589f8b894 feat: enhance search_notes tool documentation with comprehensive syntax examples (#186)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-26 15:51:58 -05:00
Paul Hernandez c2f4b632cf fix: preserve permalink when editing notes without frontmatter permalink (#184)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-06-26 15:35:31 -05:00
phernandez 46d102cef1 update tests for search_repository
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-26 14:30:10 -05:00
phernandez 8e4dc026ce chore: update version to 0.14.0b1 for v0.14.0b1 beta release 2025-06-26 14:08:32 -05:00
phernandez 7af8e198c2 style: fix linting errors in test assertions
Replace equality comparisons to False with 'not' for better style.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-26 14:06:51 -05:00
Paul Hernandez 12b51522bc fix: implement project-specific sync status checks for MCP tools (#183)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-26 13:54:26 -05:00
Paul Hernandez ac9e148bcc test: add more tests for search_repository (#181)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-26 13:45:07 -05:00
Paul Hernandez 546e3cd8db fix: handle Boolean search syntax with hyphenated terms (#180)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-26 12:41:32 -05:00
phernandez de4737cc22 fix: correct typo and update changelog command template
- Fix typo: <versuib> → <version>
- Update version examples to v0.14.0 format
- Improve template formatting clarity

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-26 11:09:32 -05:00
phernandez 77eefeb252 update test-live.md regression suite
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-26 10:25:48 -05:00
phernandez e5923a0378 allow web_search in claude github action
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-26 09:29:23 -05:00
phernandez 1bf348259b fix formatting on files
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-25 22:32:05 -05:00
phernandez 224e4bf9e4 fixes #164 revove log level from mcp_server.run()
Signed-off-by: phernandez <paul@basicmachines.co>
2025-06-25 22:31:47 -05:00
Drew Cain 9f1db23c78 fix: respect BASIC_MEMORY_HOME environment variable in Docker containers (#174)
Signed-off-by: Drew Cain <groksrc@gmail.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2025-06-25 21:40:30 -05:00
Paul Hernandez db5ef7d35c feat: enhance move_note tool with cross-project detection and guidance (#161)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-06-25 12:57:59 -05:00
Paul Hernandez f50650763d fix: ensure permalinks are generated for entities with null permalinks during move operations (#162)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-06-25 12:57:44 -05:00
Drew Cain 8a065c32f4 fix: handle None from_entity in Context API RelationSummary (#166)
Signed-off-by: Drew Cain <groksrc@gmail.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2025-06-25 12:57:31 -05:00
Drew Cain 2a3adc109a fix: scope entity queries by project_id in upsert_entity method (#168)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2025-06-24 00:02:18 -05:00
Drew Cain a52ce1c860 fix: only update Homebrew on stable releases
Signed-off-by: Drew Cain <groksrc@gmail.com>
2025-06-21 08:12:23 -05:00
48 changed files with 1933 additions and 1277 deletions
-190
View File
@@ -1,190 +0,0 @@
# /project:check-health - Project Health Assessment
Comprehensive health check of the Basic Memory project including code quality, test coverage, dependencies, and documentation.
## Usage
```
/project:check-health
```
## Implementation
You are an expert DevOps engineer for the Basic Memory project. When the user runs `/project:check-health`, execute the following comprehensive assessment:
### Step 1: Git Repository Health
1. **Repository Status**
```bash
git status
git log --oneline -5
git branch -vv
```
- Check working directory status
- Verify branch alignment with remote
- Check recent commit activity
2. **Branch Analysis**
- Verify on main branch
- Check if ahead/behind remote
- Identify any untracked files
### Step 2: Code Quality Assessment
1. **Linting and Formatting**
```bash
uv run ruff check .
uv run pyright
```
- Count linting issues by severity
- Check type annotation coverage
- Verify code formatting compliance
2. **Test Suite Health**
```bash
uv run pytest --collect-only -q
uv run pytest --co -q | wc -l
```
- Count total tests
- Check for test discovery issues
- Verify test structure integrity
### Step 3: Dependency Analysis
1. **Dependency Health**
```bash
uv tree
uv lock --dry-run
```
- Check for dependency conflicts
- Identify outdated dependencies
- Verify lock file consistency
2. **Security Scan**
```bash
uv run pip-audit --desc
```
- Scan for known vulnerabilities
- Check dependency licenses
- Identify security advisories
### Step 4: Performance Metrics
1. **Test Performance**
```bash
uv run pytest --durations=10
```
- Identify slowest tests
- Check overall test execution time
- Monitor test suite growth
2. **Build Performance**
```bash
time uv run python -c "import basic_memory"
```
- Check import time
- Validate package installation
- Monitor startup performance
### Step 5: Documentation Health
1. **Documentation Coverage**
- Check README.md currency
- Verify CLI documentation
- Validate MCP tool documentation
- Check changelog completeness
2. **API Documentation**
- Verify docstring coverage
- Check type annotation completeness
- Validate example code
### Step 6: Project Metrics
1. **Code Statistics**
```bash
find src -name "*.py" | xargs wc -l
find tests -name "*.py" | xargs wc -l
```
- Lines of code trends
- Test-to-code ratio
- File organization metrics
## Health Report Format
Generate comprehensive health dashboard:
```
🏥 Basic Memory Project Health Report
📊 OVERALL HEALTH: 🟢 EXCELLENT (92/100)
🗂️ GIT REPOSITORY
✅ Clean working directory
✅ Up to date with origin/main
✅ Recent commit activity (5 commits this week)
🔍 CODE QUALITY
✅ Linting: 0 errors, 2 warnings
✅ Type checking: 100% coverage
✅ Formatting: Compliant
⚠️ Complex functions: 3 need refactoring
🧪 TEST SUITE
✅ Total tests: 744
✅ Test discovery: All tests found
✅ Coverage: 98.2%
⚡ Performance: 45.2s (good)
📦 DEPENDENCIES
✅ Dependencies: Up to date
✅ Security: No vulnerabilities
✅ Conflicts: None detected
⚠️ Outdated: 2 minor updates available
📖 DOCUMENTATION
✅ README: Current
✅ API docs: 95% coverage
⚠️ CLI reference: Needs update
✅ Changelog: Complete
📈 METRICS
├── Source code: 15,432 lines
├── Test code: 8,967 lines
├── Test ratio: 58% (excellent)
└── Complexity: Low (maintainable)
🎯 RECOMMENDATIONS:
1. Update CLI documentation
2. Refactor 3 complex functions
3. Update minor dependencies
4. Consider splitting large test files
🏆 PROJECT STATUS: Ready for v0.13.0 release!
```
## Health Scoring
### Excellent (90-100)
- All quality gates pass
- High test coverage (>95%)
- No security issues
- Documentation current
### Good (75-89)
- Minor issues present
- Good test coverage (>90%)
- No critical security issues
- Most documentation current
### Needs Attention (60-74)
- Several quality issues
- Adequate test coverage (>80%)
- Minor security concerns
- Documentation gaps
### Critical (<60)
- Major quality problems
- Low test coverage (<80%)
- Security vulnerabilities
- Significant documentation issues
## Context
- Provides comprehensive project overview
- Identifies potential issues before they become problems
- Tracks project health trends over time
- Helps prioritize maintenance tasks
- Supports release readiness decisions
-62
View File
@@ -1,62 +0,0 @@
# Basic Memory Custom Commands
This directory contains custom Claude Code slash commands for the Basic Memory project.
## Available Commands
### Release Management (`/project:release:*`)
- `/project:release:beta` - Create beta releases with automated quality checks
- `/project:release:release` - Create stable releases with comprehensive validation
- `/project:release:release-check` - Pre-flight validation without making changes
- `/project:release:changelog` - Generate changelog entries from commits
### Development (`/project:*`)
- `/project:test-coverage` - Run tests with detailed coverage analysis
- `/project:test-live` - Live testing suite using real Basic Memory installation
- `/project:lint-fix` - Run comprehensive linting with auto-fix
- `/project:check-health` - Comprehensive project health assessment
## Command Structure
Commands are organized by functionality:
```
.claude/commands/
├── release/ # Release management commands
│ ├── beta.md # /project:release:beta
│ ├── release.md # /project:release:release
│ ├── release-check.md # /project:release:release-check
│ └── changelog.md # /project:release:changelog
├── test-coverage.md # /project:test-coverage
├── test-live.md # /project:test-live
├── lint-fix.md # /project:lint-fix
├── check-health.md # /project:check-health
└── commands.md # This overview file
```
## Usage
Commands are invoked using the `/project:` prefix:
- `/project:release:beta v0.13.0b4`
- `/project:test-coverage mcp`
- `/project:test-live core`
- `/project:release:release-check`
- `/project:check-health`
## Implementation
Each command is implemented as a Markdown file containing structured prompts that:
1. Validate preconditions
2. Execute steps in the correct order
3. Handle errors gracefully
4. Provide clear status updates
5. Return actionable results
## Tooling Integration
Commands leverage existing project tooling:
- `just check` - Quality checks
- `just test` - Test suite
- `just update-deps` - Dependency updates
- `uv` - Package management
- `git` - Version control
- GitHub Actions - CI/CD pipeline
-145
View File
@@ -1,145 +0,0 @@
# /project:lint-fix - Comprehensive Code Quality Fix
Run comprehensive linting and auto-fix code quality issues across the codebase.
## Usage
```
/project:lint-fix
```
## Implementation
You are an expert code quality engineer for the Basic Memory project. When the user runs `/project:lint-fix`, execute the following steps:
### Step 1: Pre-flight Check
1. **Verify Clean Working Directory**
```bash
git status --porcelain
```
- Check for uncommitted changes
- Warn if working directory is not clean
- Suggest stashing changes if needed
### Step 2: Import Organization
1. **Fix Import Order and Cleanup**
```bash
uv run ruff check --select I --fix .
```
- Sort imports by category (standard, third-party, local)
- Remove unused imports
- Fix import spacing and organization
### Step 3: Code Formatting
1. **Apply Consistent Formatting**
```bash
uv run ruff format .
```
- Format code according to project style
- Fix line length issues (100 chars max)
- Standardize quotes and spacing
### Step 4: Linting with Auto-fix
1. **Fix Linting Issues**
```bash
uv run ruff check --fix .
```
- Auto-fix safe linting issues
- Report any remaining manual fixes needed
- Focus on code quality and best practices
### Step 5: Type Checking
1. **Validate Type Annotations**
```bash
uv run pyright
```
- Check for type errors
- Report any missing type annotations
- Validate type compatibility
### Step 6: Report Generation
Generate comprehensive quality report:
```
🔧 Code Quality Fix Report
✅ FIXES APPLIED:
├── Import organization: 12 files updated
├── Code formatting: 8 files reformatted
├── Auto-fixable lint issues: 23 issues resolved
└── Total files processed: 156
⚠️ MANUAL ATTENTION NEEDED:
├── Type annotations missing in entity_service.py:45
├── Complex function needs refactoring in sync_service.py:123
└── Unused variable in test_utils.py:67
🎯 QUALITY SCORE: 96.2% (excellent)
📁 Run `git diff` to review all changes
```
## Error Handling
### Common Issues
- **Merge Conflicts**: Provide resolution guidance
- **Syntax Errors**: Point to specific files and lines
- **Type Errors**: Suggest specific fixes
- **Import Errors**: Check for missing dependencies
### Recovery Steps
- If auto-fixes introduce issues, provide rollback instructions
- If type checking fails, suggest incremental fixes
- If tests break, provide debugging guidance
## Quality Gates
### Must Pass
- [ ] All auto-fixable lint issues resolved
- [ ] Code formatting consistent
- [ ] No syntax errors
- [ ] Import organization clean
### Should Pass (Warnings)
- [ ] No type checking errors
- [ ] No complex function warnings
- [ ] No unused variables/imports
- [ ] Consistent naming conventions
## Output Examples
### Successful Fix
```
🎉 CODE QUALITY IMPROVED!
✅ All auto-fixes applied successfully
📏 Code formatting: 100% compliant
🔍 Linting: No issues found
🏷️ Type checking: All passed
Ready for commit! Use:
git add -A && git commit -m "style: fix code quality issues"
```
### Issues Requiring Attention
```
⚠️ PARTIAL SUCCESS - MANUAL FIXES NEEDED
✅ Auto-fixes applied: 45 issues
❌ Manual fixes needed: 3 issues
Priority fixes:
1. Fix type annotation in services/entity_service.py:142
2. Simplify complex function in sync/sync_service.py:67
3. Remove unused import in tests/conftest.py:12
Run these commands:
# Fix specific file
uv run pyright src/basic_memory/services/entity_service.py
```
## Context
- Uses ruff for fast Python linting and formatting
- Uses pyright for type checking
- Follows project code style guidelines (100 char line length)
- Maintains backward compatibility
- Integrates with existing pre-commit hooks
+5 -2
View File
@@ -8,7 +8,7 @@ Analyze commits and generate formatted changelog entry for a version.
```
**Parameters:**
- `version` (required): Version like `v0.13.0` or `v0.13.0b4`
- `version` (required): Version like `v0.14.0` or `v0.14.0b1`
- `type` (optional): `beta`, `rc`, or `stable` (default: `stable`)
## Implementation
@@ -59,8 +59,9 @@ You are an expert technical writer for the Basic Memory project. When the user r
### Step 3: Generate Changelog Entry
Create formatted entry following existing CHANGELOG.md style:
Example:
```markdown
## v0.13.0 (2025-06-03)
## <version> (<date>)
### Features
@@ -128,6 +129,8 @@ Create formatted entry following existing CHANGELOG.md style:
## Output Format
### For Beta Releases
Example:
```markdown
## v0.13.0b4 (2025-06-03)
-131
View File
@@ -1,131 +0,0 @@
# /test-coverage - Run Tests with Coverage Analysis
Execute test suite with comprehensive coverage reporting and analysis.
## Usage
```
/test-coverage [pattern]
```
**Parameters:**
- `pattern` (optional): Test pattern to run specific tests (e.g., `test_mcp`, `*integration*`)
## Implementation
You are an expert QA engineer for the Basic Memory project. When the user runs `/test-coverage`, execute the following steps:
### Step 1: Test Execution
1. **Run Tests with Coverage**
```bash
# Full test suite
uv run pytest --cov=basic_memory --cov-report=html --cov-report=term -v
# Or with pattern if provided
uv run pytest tests/*{pattern}* --cov=basic_memory --cov-report=html --cov-report=term -v
```
2. **Generate Coverage Reports**
- Terminal summary with percentages
- HTML report for detailed analysis
- Identify files below coverage threshold
### Step 2: Coverage Analysis
1. **Summary Statistics**
- Overall coverage percentage
- Number of files with 100% coverage
- Files below 95% threshold
- Total lines covered/missed
2. **Detailed Breakdown**
- Coverage by module/package
- Identify untested code paths
- Find missing edge case tests
### Step 3: Report Generation
Generate comprehensive coverage report:
```
🧪 Test Coverage Report
📊 OVERALL COVERAGE: 98.2% (target: 95%+)
✅ EXCELLENT COVERAGE (>95%):
├── basic_memory/mcp/: 99.1%
├── basic_memory/services/: 98.8%
├── basic_memory/repository/: 97.9%
└── basic_memory/api/: 96.2%
⚠️ NEEDS ATTENTION (<95%):
├── basic_memory/sync/: 94.1% (missing 12 lines)
└── basic_memory/importers/: 91.8% (missing 23 lines)
🎯 SPECIFIC GAPS:
├── sync_service.py:142-145 (error handling)
├── importer_base.py:67-70 (edge case)
└── file_utils.py:89 (exception path)
📁 HTML Report: htmlcov/index.html
🚀 Run `open htmlcov/index.html` to view detailed report
```
### Step 4: Actionable Recommendations
1. **Coverage Improvements**
- Suggest specific tests to add
- Identify edge cases to cover
- Recommend integration tests
2. **Quality Insights**
- Highlight well-tested modules
- Point out testing patterns to follow
- Suggest refactoring for testability
## Advanced Analysis
### Performance Metrics
- Test execution time by module
- Slowest tests identification
- Coverage collection overhead
### Integration Coverage
- MCP tool integration tests
- API endpoint coverage
- Database operation coverage
- File system operation coverage
## Output Examples
### Full Coverage Success
```
🎉 EXCELLENT COVERAGE!
📊 Coverage: 98.7% (744 tests passed)
✅ All modules above 95% threshold
🏆 23 files with 100% coverage
⚡ Tests completed in 45.2s
Ready for release! 🚀
```
### Coverage Issues Found
```
⚠️ COVERAGE GAPS DETECTED
📊 Coverage: 92.1% (below 95% target)
❌ 3 modules need attention
🔍 43 uncovered lines found
Priority fixes:
1. Add tests for error handling in sync_service.py
2. Cover edge cases in importer_base.py
3. Test exception paths in file_utils.py
Run specific tests:
uv run pytest tests/sync/ -v
```
## Context
- Uses pytest with coverage plugin
- Generates both terminal and HTML reports
- Focuses on actionable improvement suggestions
- Integrates with existing test infrastructure
- Helps maintain high code quality standards
+265 -80
View File
@@ -1,6 +1,7 @@
# /project:test-live - Live Basic Memory Testing Suite
Execute comprehensive real-world testing of Basic Memory using the installed version, following the methodology in TESTING.md. All test results are recorded as notes in a dedicated test project.
Execute comprehensive real-world testing of Basic Memory using the installed version.
All test results are recorded as notes in a dedicated test project.
## Usage
```
@@ -8,12 +9,45 @@ Execute comprehensive real-world testing of Basic Memory using the installed ver
```
**Parameters:**
- `phase` (optional): Specific test phase to run (`core`, `features`, `edge`, `workflows`, `stress`, or `all`)
- `phase` (optional): Specific test phase to run (`recent`, `core`, `features`, `edge`, `workflows`, `stress`, or `all`)
- `recent` - Focus on recent changes and new features (recommended for regular testing)
- `core` - Essential tools only (Tier 1: write_note, read_note, search_notes, edit_note, list_projects, switch_project)
- `features` - Core + important workflows (Tier 1 + Tier 2)
- `all` - Comprehensive testing of all tools and scenarios
## Implementation
You are an expert QA engineer conducting live testing of Basic Memory.
When the user runs `/project:test-live`, execute comprehensive testing following the TESTING.md methodology:
When the user runs `/project:test-live`, execute comprehensive test plan:
## Tool Testing Priority
### **Tier 1: Critical Core (Always Test)**
1. **write_note** - Foundation of all knowledge creation
2. **read_note** - Primary knowledge retrieval mechanism
3. **search_notes** - Essential for finding information
4. **edit_note** - Core content modification capability
5. **list_memory_projects** - Project discovery and status
6. **switch_project** - Context switching for multi-project workflows
### **Tier 2: Important Workflows (Usually Test)**
7. **recent_activity** - Understanding what's changed
8. **build_context** - Conversation continuity via memory:// URLs
9. **create_memory_project** - Essential for project setup
10. **move_note** - Knowledge organization
11. **sync_status** - Understanding system state
### **Tier 3: Enhanced Functionality (Sometimes Test)**
12. **view_note** - Claude Desktop artifact display
13. **read_content** - Raw content access
14. **delete_note** - Content removal
15. **list_directory** - File system exploration
16. **set_default_project** - Configuration
17. **delete_project** - Administrative cleanup
### **Tier 4: Specialized (Rarely Test)**
18. **canvas** - Obsidian visualization (specialized use case)
19. **MCP Prompts** - Enhanced UX tools (ai_assistant_guide, continue_conversation)
### Pre-Test Setup
@@ -22,7 +56,13 @@ When the user runs `/project:test-live`, execute comprehensive testing following
- Check version and confirm it's the expected release
- Test MCP connection and tool availability
2. **Test Project Creation**
2. **Recent Changes Analysis** (if phase includes 'recent' or 'all')
- Run `git log --oneline -20` to examine recent commits
- Identify new features, bug fixes, and enhancements
- Generate targeted test scenarios for recent changes
- Prioritize regression testing for recently fixed issues
3. **Test Project Creation**
Run the bash `date` command to get the current date/time.
@@ -34,83 +74,158 @@ Run the bash `date` command to get the current date/time.
Make sure to switch to the newly created project with the `switch_project()` tool.
3. **Baseline Documentation**
4. **Baseline Documentation**
Create initial test session note with:
- Test environment details
- Version being tested
- Recent changes identified (if applicable)
- Test objectives and scope
- Start timestamp
### Phase 1: Core Functionality Validation
### Phase 0: Recent Changes Validation (if 'recent' or 'all' phase)
Test all fundamental MCP tools systematically:
Based on recent commit analysis, create targeted test scenarios:
**write_note Tests:**
- Basic note creation with various content types
- Frontmatter handling (tags, custom fields)
- Special characters in titles and content
- Unicode and emoji support
- Empty notes and minimal content
**Recent Changes Test Protocol:**
1. **Feature Addition Tests** - For each new feature identified:
- Test basic functionality
- Test integration with existing tools
- Verify documentation accuracy
- Test edge cases and error handling
**read_note Tests:**
- Read by title, permalink, memory:// URLs
- Non-existent notes (error handling)
- Notes with complex formatting
- Performance with large notes
2. **Bug Fix Regression Tests** - For each recent fix:
- Recreate the original problem scenario
- Verify the fix works as expected
- Test related functionality isn't broken
- Document the verification in test notes
**view_note Tests:**
- View notes as formatted artifacts (Claude Desktop)
- Title extraction from frontmatter and headings
- Unicode and emoji content in artifacts
- Error handling for non-existent notes
- Artifact display quality and readability
3. **Performance/Enhancement Validation** - For optimizations:
- Establish baseline timing
- Compare with expected improvements
- Test under various load conditions
- Document performance observations
**search_notes Tests:**
- Simple text queries
- Tag-based searches
- Boolean operators and complex queries
- Empty/no results scenarios
- Performance with growing knowledge base
**Example Recent Changes (Update based on actual git log):**
- Watch Service Restart (#156): Test project creation → file modification → automatic restart
- Cross-Project Moves (#161): Test move_note with cross-project detection
- Docker Environment Support (#174): Test BASIC_MEMORY_HOME behavior
- MCP Server Logging (#164): Verify log level configurations
**Recent Activity Tests:**
- Various timeframes ("today", "1 week", "1d")
- Type filtering (if available)
- Empty project scenarios
- Performance with many recent changes
### Phase 1: Core Functionality Validation (Tier 1 Tools)
**Context Building Tests:**
- Different depth levels (1, 2, 3+)
- Various timeframes
- Relation traversal accuracy
- Performance with complex graphs
Test essential MCP tools that form the foundation of Basic Memory:
### Phase 2: v0.13.0 Feature Deep Dive
**1. write_note Tests (Critical):**
- ✅ Basic note creation with frontmatter
- ✅ Special characters and Unicode in titles
- ✅ Various content types (lists, headings, code blocks)
- ✅ Empty notes and minimal content edge cases
- ⚠️ Error handling for invalid parameters
**Project Management:**
- Create multiple projects dynamically
- Switch between projects mid-conversation
- Cross-project operations
- Project discovery and status
- Default project behavior
- Invalid project handling
**2. read_note Tests (Critical):**
- ✅ Read by title, permalink, memory:// URLs
- ✅ Non-existent notes (error handling)
- ✅ Notes with complex markdown formatting
- ⚠️ Performance with large notes (>10MB)
**Advanced Note Editing:**
- `edit_note` with append operations
- Prepend operations
- Find/replace with validation
- Section replacement under headers
- Error scenarios (invalid operations)
- Frontmatter preservation
**3. search_notes Tests (Critical):**
- ✅ Simple text queries across content
- ✅ Tag-based searches with multiple tags
- ✅ Boolean operators (AND, OR, NOT)
- ✅ Empty/no results scenarios
- ⚠️ Performance with 100+ notes
**File Management:**
- `move_note` within same project
- Move between projects
- Automatic folder creation
- Special characters in paths
- Database consistency validation
- Search index updates after moves
**4. edit_note Tests (Critical):**
- ✅ Append operations preserving frontmatter
- ✅ Prepend operations
- ✅ Find/replace with validation
- ✅ Section replacement under headers
- ⚠️ Error scenarios (invalid operations)
### Phase 3: Edge Case Exploration
**5. list_memory_projects Tests (Critical):**
- ✅ Display all projects with status indicators
- ✅ Current and default project identification
- ✅ Empty project list handling
- ✅ Project metadata accuracy
**6. switch_project Tests (Critical):**
- ✅ Switch between existing projects
- ✅ Context preservation during switch
- ⚠️ Invalid project name handling
- ✅ Confirmation of successful switch
### Phase 2: Important Workflows (Tier 2 Tools)
**7. recent_activity Tests (Important):**
- ✅ Various timeframes ("today", "1 week", "1d")
- ✅ Type filtering capabilities
- ✅ Empty project scenarios
- ⚠️ Performance with many recent changes
**8. build_context Tests (Important):**
- ✅ Different depth levels (1, 2, 3+)
- ✅ Various timeframes for context
- ✅ memory:// URL navigation
- ⚠️ Performance with complex relation graphs
**9. create_memory_project Tests (Important):**
- ✅ Create projects dynamically
- ✅ Set default during creation
- ✅ Path validation and creation
- ⚠️ Invalid paths and names
- ✅ Integration with existing projects
**10. move_note Tests (Important):**
- ✅ Move within same project
- ✅ Cross-project moves with detection (#161)
- ✅ Automatic folder creation
- ✅ Database consistency validation
- ⚠️ Special characters in paths
**11. sync_status Tests (Important):**
- ✅ Background operation monitoring
- ✅ File synchronization status
- ✅ Project sync state reporting
- ⚠️ Error state handling
### Phase 3: Enhanced Functionality (Tier 3 Tools)
**12. view_note Tests (Enhanced):**
- ✅ Claude Desktop artifact display
- ✅ Title extraction from frontmatter
- ✅ Unicode and emoji content rendering
- ⚠️ Error handling for non-existent notes
**13. read_content Tests (Enhanced):**
- ✅ Raw file content access
- ✅ Binary file handling
- ✅ Image file reading
- ⚠️ Large file performance
**14. delete_note Tests (Enhanced):**
- ✅ Single note deletion
- ✅ Database consistency after deletion
- ⚠️ Non-existent note handling
- ✅ Confirmation of successful deletion
**15. list_directory Tests (Enhanced):**
- ✅ Directory content listing
- ✅ Depth control and filtering
- ✅ File name globbing
- ⚠️ Empty directory handling
**16. set_default_project Tests (Enhanced):**
- ✅ Change default project
- ✅ Configuration persistence
- ⚠️ Invalid project handling
**17. delete_project Tests (Enhanced):**
- ✅ Project removal from config
- ✅ Database cleanup
- ⚠️ Default project protection
- ⚠️ Non-existent project handling
### Phase 4: Edge Case Exploration
**Boundary Testing:**
- Very long titles and content (stress limits)
@@ -134,7 +249,7 @@ Test all fundamental MCP tools systematically:
- Rapid successive operations
- Memory usage monitoring
### Phase 4: Real-World Workflow Scenarios
### Phase 5: Real-World Workflow Scenarios
**Meeting Notes Pipeline:**
1. Create meeting notes with action items
@@ -164,7 +279,35 @@ Test all fundamental MCP tools systematically:
4. Update content with edit operations
5. Validate knowledge graph integrity
### Phase 5: Creative Stress Testing
### Phase 6: Specialized Tools Testing (Tier 4)
**18. canvas Tests (Specialized):**
- ✅ JSON Canvas generation
- ✅ Node and edge creation
- ✅ Obsidian compatibility
- ⚠️ Complex graph handling
**19. MCP Prompts Tests (Specialized):**
- ✅ ai_assistant_guide output
- ✅ continue_conversation functionality
- ✅ Formatted search results
- ✅ Enhanced activity reports
### Phase 7: Integration & File Watching Tests
**File System Integration:**
- ✅ Watch service behavior with file changes
- ✅ Project creation → watch restart (#156)
- ✅ Multi-project synchronization
- ⚠️ MCP→API→DB→File stack validation
**Real Integration Testing:**
- ✅ End-to-end file watching vs manual operations
- ✅ Cross-session persistence
- ✅ Database consistency across operations
- ⚠️ Performance under real file system changes
### Phase 8: Creative Stress Testing
**Creative Exploration:**
- Rapid project creation/switching patterns
@@ -180,6 +323,26 @@ Test all fundamental MCP tools systematically:
- Complex boolean search expressions
- Resource constraint testing
## Test Execution Guidelines
### Quick Testing (core/features phases)
- Focus on Tier 1 tools (core) or Tier 1+2 (features)
- Test essential functionality and common edge cases
- Record critical issues immediately
- Complete in 15-20 minutes
### Comprehensive Testing (all phase)
- Cover all tiers systematically
- Include specialized tools and stress testing
- Document performance baselines
- Complete in 45-60 minutes
### Recent Changes Focus (recent phase)
- Analyze git log for recent commits
- Generate targeted test scenarios
- Focus on regression testing for fixes
- Validate new features thoroughly
## Test Observation Format
Record ALL observations immediately as Basic Memory notes:
@@ -202,14 +365,14 @@ permalink: test-session-[phase]-[timestamp]
## Test Results
### ✅ Successful Operations
- [timestamp] write_note: Created note with emoji title 📝 #functionality
- [timestamp] search_notes: Boolean query returned 23 results in 0.4s #performance
- [timestamp] edit_note: Append operation preserved frontmatter #reliability
- [timestamp] write_note: Created note with emoji title 📝 #tier1 #functionality
- [timestamp] search_notes: Boolean query returned 23 results in 0.4s #tier1 #performance
- [timestamp] edit_note: Append operation preserved frontmatter #tier1 #reliability
### ⚠️ Issues Discovered
- [timestamp] move_note: Slow with deep folder paths (2.1s) #performance
- [timestamp] search_notes: Unicode query returned unexpected results #bug
- [timestamp] project switch: Context lost for memory:// URLs #issue
- [timestamp] ⚠️ move_note: Slow with deep folder paths (2.1s) #tier2 #performance
- [timestamp] 🚨 search_notes: Unicode query returned unexpected results #tier1 #bug #critical
- [timestamp] ⚠️ build_context: Context lost for memory:// URLs #tier2 #issue
### 🚀 Enhancements Identified
- edit_note could benefit from preview mode #ux-improvement
@@ -353,24 +516,44 @@ For each error discovered:
- Create comprehensive summary report
- Generate development recommendations
## Testing Success Criteria
### Core Testing (Tier 1) - Must Pass
- All 6 critical tools function correctly
- No critical bugs in essential workflows
- Acceptable performance for basic operations
- Error handling works as expected
### Feature Testing (Tier 1+2) - Should Pass
- All 11 core + important tools function
- Workflow scenarios complete successfully
- Performance meets baseline expectations
- Integration points work correctly
### Comprehensive Testing (All Tiers) - Complete Coverage
- All tools tested across all scenarios
- Edge cases and stress testing completed
- Performance baselines established
- Full documentation of issues and enhancements
## Expected Outcomes
**System Validation:**
- v0.13.0 feature verification in real usage
- Edge case discovery beyond unit tests
- Feature verification prioritized by tier importance
- Recent changes validated for regression
- Performance baseline establishment
- Bug identification with reproduction cases
- Bug identification with severity assessment
**Knowledge Base Creation:**
- Comprehensive testing documentation
- Prioritized testing documentation
- Real usage examples for user guides
- Edge case scenarios for future testing
- Recent changes validation records
- Performance insights for optimization
**Development Insights:**
- Prioritized bug fix list
- Tier-based bug priority list
- Recent changes impact assessment
- Enhancement ideas from real usage
- Architecture validation results
- User experience improvement areas
## Post-Test Deliverables
@@ -402,9 +585,11 @@ For each error discovered:
- Add performance benchmarks and targets
## Context
- Uses installed basic-memory version (not development)
- Uses real installed basic-memory version
- Tests complete MCP→API→DB→File stack
- Creates living documentation in Basic Memory itself
- Follows integration over isolation philosophy
- Prioritizes testing by tool importance and usage frequency
- Adapts to recent development changes dynamically
- Focuses on real usage patterns over checklist validation
- Generates actionable insights for development team
- Generates actionable insights prioritized by impact
+81
View File
@@ -0,0 +1,81 @@
# Development files
.venv/
.pytest_cache/
.coverage*
htmlcov/
.ruff_cache/
**/__pycache__/
*.pyc
*.pyo
*.pyd
.env
.env.*
# Testing
test*/
tests/
*.test.js
*.test.py
pytest.ini
# Build artifacts
dist/
build/
*.egg-info/
.tox/
# IDE files
.idea/
.vscode/
*.swp
*.swo
*~
# Claude
.claude
# Git files
.git/
.gitignore
# Documentation build
docs/_build/
# Temporary files
*.tmp
*.log
.DS_Store
Thumbs.db
# DXT package output
*.dxt
dxt-bundle/
# Lock files
uv.lock
package-lock.json
yarn.lock
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# CI/CD
.github/
justfile
smithery.yaml
# Project files
.python.version
CHANGELOG.md
CITATION.cff
CLA.md
CLAUDE.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
README.md
SECURITY.md
docs/
llms-install.md
+1 -1
View File
@@ -111,4 +111,4 @@ jobs:
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
allowed_tools: Bash(uv run pytest),Bash(uv run ruff check . --fix),Bash(uv run ruff format .),Bash(uv run pyright),Bash(just test),Bash(just lint),Bash(just format),Bash(just type-check),Bash(just check),Read,Write,Edit,MultiEdit,Glob,Grep,LS
allowed_tools: Bash(uv run pytest),Bash(uv run ruff check . --fix),Bash(uv run ruff format .),Bash(uv run pyright),Bash(just test),Bash(just lint),Bash(just format),Bash(just type-check),Bash(just check),Read,Write,Edit,MultiEdit,Glob,Grep,LS, mcp__web_search
+3
View File
@@ -57,6 +57,8 @@ jobs:
name: Update Homebrew Formula
needs: release
runs-on: ubuntu-latest
# Only run for stable releases (not dev, beta, or rc versions)
if: ${{ !contains(github.ref_name, 'dev') && !contains(github.ref_name, 'b') && !contains(github.ref_name, 'rc') }}
permissions:
contents: write
actions: read
@@ -80,3 +82,4 @@ jobs:
env:
# Personal Access Token with repo scope for homebrew-basic-memory repo
COMMITTER_TOKEN: ${{ secrets.HOMEBREW_TOKEN }}
+5 -1
View File
@@ -52,4 +52,8 @@ ENV/
# claude action
claude-output
**/.claude/settings.local.json
**/.claude/settings.local.json
# DXT package files
basic-memory.dxt
dxt-bundle/
-14
View File
@@ -1,14 +0,0 @@
{
"mcpServers": {
"basic-memory": {
"command": "uv",
"args": [
"--directory",
"/Users/phernandez/dev/basicmachines/basic-memory",
"run",
"src/basic_memory/cli/main.py",
"mcp"
]
}
}
}
+119 -37
View File
@@ -1,56 +1,138 @@
# CHANGELOG
## v0.13.8 (2025-06-20)
## v0.14.0 (2025-06-26)
### Features
- **Docker Container Support** - Complete Docker integration with volume mounting for Obsidian directories
([`3269a2f`](https://github.com/basicmachines-co/basic-memory/commit/3269a2f33a7595f6d9e5207924062e2542f46759))
- Docker Compose configuration for easy deployment
- Volume mounting for persistent data and Obsidian integration
- Comprehensive Docker documentation and setup guides
- Streamlined container-based workflow
- **Docker Container Registry Migration** - Switch from Docker Hub to GitHub Container Registry for better security and integration
([`616c1f0`](https://github.com/basicmachines-co/basic-memory/commit/616c1f0710da59c7098a5f4843d4f017877ff7b2))
- Automated Docker image publishing via GitHub Actions CI/CD pipeline
- Enhanced container security with GitHub's integrated vulnerability scanning
- Streamlined container deployment workflow for production environments
- **Enhanced Search Documentation** - Comprehensive search syntax examples for improved user experience
([`a589f8b`](https://github.com/basicmachines-co/basic-memory/commit/a589f8b894e78cce01eb25656856cfea8785fbbf))
- Detailed examples for Boolean search operators (AND, OR, NOT)
- Advanced search patterns including phrase matching and field-specific queries
- User-friendly documentation for complex search scenarios
- **Cross-Project File Management** - Intelligent move operations with project boundary detection
([`db5ef7d`](https://github.com/basicmachines-co/basic-memory/commit/db5ef7d35cc0894309c7a57b5741c9dd978526d4))
- Automatic detection of cross-project move attempts with helpful guidance
- Clear error messages when attempting unsupported cross-project operations
### Bug Fixes
- **#151**: Fix reset command project configuration persistence issue
([`af44941`](https://github.com/basicmachines-co/basic-memory/commit/af44941d5aa57b5ad7fcc6af4ed700f49bdb6d4d))
- Reset command now properly clears project configuration from `~/.basic-memory/config.json`
- Eliminates issue where projects would be recreated after database reset
- Ensures clean slate when resetting Basic Memory installation
- **#184**: Preserve permalinks when editing notes without frontmatter permalinks
([`c2f4b63`](https://github.com/basicmachines-co/basic-memory/commit/c2f4b632cf04921b1a3c2f0d43831b80c519cb31))
- Fix permalink preservation during note editing operations
- Ensure consistent permalink handling across different note formats
- Maintain note identity and searchability during incremental edits
- **#148**: Resolve project state inconsistency between MCP and CLI
([`35e4f73`](https://github.com/basicmachines-co/basic-memory/commit/35e4f73ae8a65501da4d48258ed702f957184c92))
- Fix "Project not found" errors when switching default projects
- MCP session now automatically refreshes when project configuration changes
- Eliminates need to restart MCP server after project operations
- Ensures consistent project state across CLI and MCP interfaces
- **#183**: Implement project-specific sync status checks for MCP tools
([`12b5152`](https://github.com/basicmachines-co/basic-memory/commit/12b51522bc953fca117fc5bc01fcb29c6ca7e13c))
- Fix sync status reporting to correctly reflect current project state
- Resolve inconsistencies where sync status showed global instead of project-specific information
- Improve project isolation for sync operations and status reporting
- **FastMCP Compatibility** - Resolve deprecation warnings for FastMCP integration
([`7be001c`](https://github.com/basicmachines-co/basic-memory/commit/7be001ca6834b3344bb6160cbe537b36bcbaa579))
- Update FastMCP usage patterns to eliminate deprecation warnings
- Improve future compatibility with FastMCP library updates
- Clean up entity repository and service layer code
- **#180**: Handle Boolean search syntax with hyphenated terms
([`546e3cd`](https://github.com/basicmachines-co/basic-memory/commit/546e3cd8db98b74f746749d41887f8a213cd0b11))
- Fix search parsing issues with hyphenated terms in Boolean queries
- Improve search query tokenization for complex term structures
- Enhanced search reliability for technical documentation and multi-word concepts
- **#174**: Respect BASIC_MEMORY_HOME environment variable in Docker containers
([`9f1db23`](https://github.com/basicmachines-co/basic-memory/commit/9f1db23c78d4648e2c242ad1ee27eed85e3f3b5d))
- Fix Docker container configuration to properly honor custom home directory settings
- Improve containerized deployment flexibility with environment variable support
- Ensure consistent behavior between local and containerized installations
- **#168**: Scope entity queries by project_id in upsert_entity method
([`2a3adc1`](https://github.com/basicmachines-co/basic-memory/commit/2a3adc109a3e4d7ccd65cae4abf63d9bb2338326))
- Fix entity isolation issues in multi-project setups
- Prevent cross-project entity conflicts during database operations
- Strengthen project boundary enforcement at the database level
- **#166**: Handle None from_entity in Context API RelationSummary
([`8a065c3`](https://github.com/basicmachines-co/basic-memory/commit/8a065c32f4e41613207d29aafc952a56e3a52241))
- Fix null pointer exceptions in relation processing
- Improve error handling for incomplete relation data
- Enhanced stability for knowledge graph traversal operations
- **#164**: Remove log level configuration from mcp_server.run()
([`224e4bf`](https://github.com/basicmachines-co/basic-memory/commit/224e4bf9e4438c44a82ffc21bd1a282fe9087690))
- Simplify MCP server startup by removing redundant log level settings
- Fix potential logging configuration conflicts
- Streamline server initialization process
- **#162**: Ensure permalinks are generated for entities with null permalinks during move operations
([`f506507`](https://github.com/basicmachines-co/basic-memory/commit/f50650763dbd4322c132e4bdc959ce4bf074374b))
- Fix move operations for entities without existing permalinks
- Automatic permalink generation during file move operations
- Maintain database consistency during file reorganization
### Technical Improvements
- **Comprehensive Integration Testing** - New test suites for critical user workflows
- Full integration tests for database reset functionality
- End-to-end project state synchronization testing
- Real MCP client-server communication validation
- Direct config file validation without complex mocking
- **Comprehensive Test Coverage** - Extensive test suites for new features and edge cases
- Enhanced test coverage for project-specific sync status functionality
- Additional test scenarios for search syntax validation and edge cases
- Integration tests for Docker CI workflow and container publishing
- Comprehensive move operations testing with project boundary validation
- **Code Quality** - Enhanced error handling and validation
- Improved project state management across system components
- Better session refresh patterns for configuration changes
- Streamlined Docker setup with reduced image size
- **Docker CI/CD Pipeline** - Production-ready automated container publishing
([`74847cc`](https://github.com/basicmachines-co/basic-memory/commit/74847cc3807b0c6ed511e0d83e0d560e9f07ec44))
- Automated Docker image building and publishing on release
- Multi-architecture container support for AMD64 and ARM64 platforms
- Integrated security scanning and vulnerability assessments
- Streamlined deployment pipeline for production environments
### Documentation
- **Release Process Improvements** - Enhanced automation and quality gates
([`a52ce1c`](https://github.com/basicmachines-co/basic-memory/commit/a52ce1c8605ec2cd450d1f909154172cbc30faa2))
- Homebrew formula updates limited to stable releases only
- Improved release automation with better quality control
- Enhanced CI/CD pipeline reliability and error handling
- **Docker Integration Guide** - Complete documentation for container deployment
- Step-by-step Docker Compose setup instructions
- Volume mounting configuration for Obsidian workflows
- Container-based development environment setup
- **Code Quality Enhancements** - Improved error handling and validation
- Better null safety in entity and relation processing
- Enhanced project isolation validation throughout the codebase
- Improved error messages and user guidance for edge cases
- Strengthened database consistency guarantees across operations
### Infrastructure
- **GitHub Container Registry Integration** - Modern container infrastructure
- Migration from Docker Hub to GitHub Container Registry (ghcr.io)
- Improved security with integrated vulnerability scanning
- Better integration with GitHub-based development workflow
- Enhanced container versioning and artifact management
- **Enhanced CI/CD Workflows** - Robust automated testing and deployment
- Automated Docker image publishing on releases
- Comprehensive test coverage validation before deployment
- Multi-platform container building and publishing
- Integration with GitHub's security and monitoring tools
### Migration Guide
This release includes several behind-the-scenes improvements and fixes. All changes are backward compatible:
- **Docker Users**: Container images now served from `ghcr.io/basicmachines-co/basic-memory` instead of Docker Hub
- **Search Users**: Enhanced search syntax handling - existing queries continue to work unchanged
- **Multi-Project Users**: Improved project isolation - all existing projects remain fully functional
- **All Users**: Enhanced stability and error handling - no breaking changes to existing workflows
### Installation
```bash
# Latest stable release
uv tool install basic-memory
# Update existing installation
uv tool upgrade basic-memory
# Docker (new registry)
docker pull ghcr.io/basicmachines-co/basic-memory:latest
```
## v0.13.7 (2025-06-19)
+3 -1
View File
@@ -236,7 +236,9 @@ Basic Memory uses `uv-dynamic-versioning` for automatic version management based
- Create version tag: `git tag v0.13.0 && git push origin v0.13.0`
#### Homebrew Formula Updates
- Automatically triggered after successful PyPI release
- Automatically triggered after successful PyPI release for **stable releases only**
- **Stable releases** (e.g., v0.13.7) automatically update the main `basic-memory` formula
- **Pre-releases** (dev/beta/rc) are NOT automatically updated - users must specify version manually
- Updates formula in `basicmachines-co/homebrew-basic-memory` repo
- Requires `HOMEBREW_TOKEN` secret in GitHub repository settings:
- Create a fine-grained Personal Access Token with `Contents: Read and Write` and `Actions: Read` scopes on `basicmachines-co/homebrew-basic-memory`
+4
View File
@@ -33,6 +33,10 @@ https://github.com/user-attachments/assets/a55d8238-8dd0-454a-be4c-8860dbbd0ddc
# Install with uv (recommended)
uv tool install basic-memory
# or with Homebrew
brew tap basicmachines-co/basic-memory
brew install basic-memory
# Configure Claude Desktop (edit ~/Library/Application Support/Claude/claude_desktop_config.json)
# Add this to your config:
{
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

+34
View File
@@ -177,6 +177,40 @@ beta version:
echo "🔗 Monitor at: https://github.com/basicmachines-co/basic-memory/actions"
echo "📥 Install with: uv tool install basic-memory --pre"
# Build DXT package with bundled virtual environment
dxt:
#!/usr/bin/env bash
echo "🏗️ Building DXT package..."
# Clean and create bundle directory
rm -rf dxt-bundle
mkdir -p dxt-bundle/server/lib dxt-bundle/src
# Bundle dependencies to server/lib (like Anthropic example)
echo "📦 Bundling Python dependencies..."
uv pip install -e . --target dxt-bundle/server/lib --force-reinstall
# Copy source code
echo "📄 Copying source code..."
cp -r src/basic_memory dxt-bundle/src/
# Copy manifest
cp manifest.json dxt-bundle/
# Copy assets (icons, screenshots)
echo "🖼️ Copying assets..."
mkdir -p dxt-bundle/images
cp images/disk-ai-logo-black-fg-white-bg.png dxt-bundle/images/
cp images/claude-*.png dxt-bundle/images/
# Create DXT package
echo "📦 Creating DXT package..."
cd dxt-bundle && dxt pack . ../basic-memory.dxt
echo "✅ DXT package created: basic-memory.dxt"
echo "🔍 Info: dxt info basic-memory.dxt"
echo "🧪 Test: Install in Claude Desktop"
# List all available recipes
default:
@just --list
-128
View File
@@ -1,128 +0,0 @@
# Basic Memory Installation Guide for LLMs
This guide is specifically designed to help AI assistants like Cline install and configure Basic Memory. Follow these
steps in order.
## Installation Steps
### 1. Install Basic Memory Package
Use one of the following package managers to install:
```bash
# Install with uv (recommended)
uv tool install basic-memory
# Or with pip
pip install basic-memory
```
### 2. Configure MCP Server
Add the following to your config:
```json
{
"mcpServers": {
"basic-memory": {
"command": "uvx",
"args": [
"basic-memory",
"mcp"
]
}
}
}
```
For Claude Desktop, this file is located at:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
### 3. Start Synchronization (optional)
To synchronize files in real-time, run:
```bash
basic-memory sync --watch
```
Or for a one-time sync:
```bash
basic-memory sync
```
## Configuration Options
### Custom Directory
To use a directory other than the default `~/basic-memory`:
```bash
basic-memory project add custom-project /path/to/your/directory
basic-memory project default custom-project
```
### Multiple Projects
To manage multiple knowledge bases:
```bash
# List all projects
basic-memory project list
# Add a new project
basic-memory project add work ~/work-basic-memory
# Set default project
basic-memory project default work
```
## Importing Existing Data
### From Claude.ai
```bash
basic-memory import claude conversations path/to/conversations.json
basic-memory import claude projects path/to/projects.json
```
### From ChatGPT
```bash
basic-memory import chatgpt path/to/conversations.json
```
### From MCP Memory Server
```bash
basic-memory import memory-json path/to/memory.json
```
## Troubleshooting
If you encounter issues:
1. Check that Basic Memory is properly installed:
```bash
basic-memory --version
```
2. Verify the sync process is running:
```bash
ps aux | grep basic-memory
```
3. Check sync output for errors:
```bash
basic-memory sync --verbose
```
4. Check log output:
```bash
cat ~/.basic-memory/basic-memory.log
```
For more detailed information, refer to the [full documentation](https://memory.basicmachines.co/).
+166
View File
@@ -0,0 +1,166 @@
{
"dxt_version": "0.1",
"name": "basic-memory",
"display_name": "Basic Memory Knowledge Management",
"version": "0.14.0",
"description": "Local-first knowledge management with AI integration",
"long_description": "Basic Memory is a local-first knowledge management system that creates a personal knowledge graph from markdown files. It enables bidirectional communication between LLMs and your local knowledge base through the Model Context Protocol (MCP), allowing AI assistants to read, write, and navigate your knowledge seamlessly.",
"author": {
"name": "Basic Machines",
"email": "hello@basicmachines.co",
"url": "https://github.com/basicmachines-co"
},
"homepage": "https://basicmemory.com/",
"documentation": "https://memory.basicmachines.co/",
"support": "https://memory.basicmachines.co/user-guide",
"icon": "images/disk-ai-logo-black-fg-white-bg.png",
"screenshots": [
"images/claude-continue-conversation.png",
"images/claude-switch-project.png",
"images/claude-view-note.png"
],
"server": {
"type": "python",
"entry_point": "src/basic_memory/cli/main.py",
"mcp_config": {
"command": "python3",
"args": [
"${__dirname}/src/basic_memory/cli/main.py",
"mcp",
"--transport=stdio"
],
"env": {
"BASIC_MEMORY_HOME": "${user_data_dir}/basic-memory",
"PYTHONPATH": "${__dirname}/server/lib:${__dirname}/src"
}
}
},
"tools": [
{
"name": "write_note",
"description": "Create or update markdown notes with semantic structure"
},
{
"name": "read_note",
"description": "Read notes by title, permalink, or memory:// URL"
},
{
"name": "edit_note",
"description": "Edit notes incrementally (append, prepend, find/replace)"
},
{
"name": "move_note",
"description": "Move or rename notes"
},
{
"name": "view_note",
"description": "Display notes as formatted artifacts"
},
{
"name": "read_content",
"description": "Read raw file content (text, images, binaries)"
},
{
"name": "delete_note",
"description": "Delete notes from knowledge base"
},
{
"name": "search_notes",
"description": "Full-text search across knowledge base"
},
{
"name": "build_context",
"description": "Navigate knowledge graph via memory:// URLs"
},
{
"name": "recent_activity",
"description": "Get recently updated information"
},
{
"name": "list_directory",
"description": "Browse directory contents with filtering"
},
{
"name": "canvas",
"description": "Generate Obsidian canvas files for knowledge visualization"
},
{
"name": "list_memory_projects",
"description": "List all projects with status"
},
{
"name": "switch_project",
"description": "Switch between project contexts"
},
{
"name": "get_current_project",
"description": "Show current project stats"
},
{
"name": "create_memory_project",
"description": "Create new projects"
},
{
"name": "delete_project",
"description": "Remove projects"
},
{
"name": "set_default_project",
"description": "Set default project"
},
{
"name": "sync_status",
"description": "Check synchronization status"
}
],
"prompts": [
{
"name": "continue_conversation",
"description": "Continue previous conversations with relevant historical context",
"arguments": [
"topic",
"timeframe"
],
"text": "Continue our conversation about {{topic}} from {{timeframe}} ago. Use the continue_conversation tool to find relevant context and build on our previous discussion."
},
{
"name": "search_notes",
"description": "Search knowledge base with detailed, formatted results",
"arguments": [
"query",
"after_date"
],
"text": "Search my knowledge base for information about {{query}}. Use the search_notes tool to find relevant notes and provide a comprehensive summary of what I know about this topic."
},
{
"name": "recent_activity",
"description": "View recently changed items with formatted output",
"arguments": [
"timeframe"
],
"text": "Show me what I've been working on recently in the past {{timeframe}}. Use the recent_activity tool to display my recent notes and changes."
}
],
"keywords": [
"knowledge",
"management",
"local-first",
"ai",
"mcp",
"markdown",
"notes",
"zettelkasten"
],
"license": "AGPL-3.0-or-later",
"repository": {
"type": "git",
"url": "https://github.com/basicmachines-co/basic-memory"
},
"compatibility": {
"claude_desktop": ">=0.10.0",
"platforms": ["darwin", "win32", "linux"],
"runtimes": {
"python": ">=3.12.0 <4"
}
}
}
-378
View File
@@ -1,378 +0,0 @@
{"type":"entity","name":"Paul","entityType":"person","observations":["Software developer combining DIY ethics, Free Software principles, and theoretical computer science","Created the Basic Machines project","Values authentic exchange of ideas","Approaches AI interaction with emphasis on genuine technical discussion","Comfortable with uncertainty and open dialogue","Balances practical implementation with broader implications"]}
{"type":"entity","name":"Basic_Machines","entityType":"project","observations":["Local-first knowledge management system","Combines filesystem durability with graph-based knowledge representation","Focuses on enhancing human agency and understanding","Synthesizes DIY ethics, Free Software philosophy, and theoretical computer science","Current focus includes basic-memory system"]}
{"type":"entity","name":"basic-memory","entityType":"software_system","observations":["A core component of Basic Machines","Local-first knowledge management system","Combines filesystem persistence with graph-based knowledge representation","Being implemented collaboratively by Paul and Claude"]}
{"type":"entity","name":"basic-memory_implementation_patterns","entityType":"technical_patterns","observations":["Filesystem is source of truth - all changes write to files first","Clean separation of concerns between models (SQLAlchemy), schemas (Pydantic), and services","Repository pattern for database access","Service layer handling business logic and coordination","Atomic file operations using temporary files for safety","Clear error handling hierarchy with specific error types","Comprehensive test coverage with pytest and fixtures","Async/await used throughout the codebase","Validation using Pydantic models with custom validators"]}
{"type":"entity","name":"fileio_module","entityType":"code_module","observations":["Extracted from EntityService to handle all file operations","Provides read_entity_file, write_entity_file, and delete_entity_file functions","Handles markdown parsing and formatting","Implements atomic file operations","Provides consistent error handling","Enables reuse across services"]}
{"type":"entity","name":"entity_service","entityType":"code_module","observations":["Manages entities in both filesystem and database","Uses fileio module for file operations","Maintains database index of entities","Handles entity creation, retrieval, and deletion","Follows 'filesystem is source of truth' principle","Coordinates with observation service for full entity management"]}
{"type":"entity","name":"observation_service","entityType":"code_module","observations":["Manages observations within entity files","Provides database indexing for efficient observation queries","Works with complete Entity objects rather than IDs","Handles observation addition and search","Maintains consistency between files and database","Under development for update/remove operations"]}
{"type":"entity","name":"observation_management","entityType":"design_challenge","observations":["Key challenge: maintaining observation state across files and database","Exploring bulk update approach - treating all observations as a unit","Considering tracked observations with markdown comments for IDs","Investigating diff-based approach for observation-level changes","Evaluating position-based management without explicit IDs","Trade-offs between implementation complexity and markdown readability"]}
{"type":"entity","name":"testing_infrastructure","entityType":"technical_patterns","observations":["Uses pytest with async support via pytest-asyncio","In-memory SQLite database for test isolation","Temporary directories for file operation testing","Comprehensive fixture system for test setup","Tests organized by component (entity, observation, etc)","Covers happy path, error cases, and edge cases","Uses monkeypatch for mocking dependencies","Clear separation between arrange, act, assert sections","Uses in-memory SQLite database for test isolation","Comprehensive fixture system for test data setup","Proper async test handling with pytest-asyncio"]}
{"type":"entity","name":"test_categories","entityType":"test_suite","observations":["Happy path tests verify core functionality","Error path tests ensure proper error handling","Edge cases test special characters and long content","File operation tests verify atomic writes and rollbacks","Database sync tests verify index consistency","Recovery tests for rebuild operations","Punted on concurrent operation tests due to session management complexity"]}
{"type":"entity","name":"completed_work","entityType":"project_milestone","observations":["Extracted file operations to fileio.py module","Updated EntityService to use fileio functions","Implemented initial ObservationService","Created comprehensive test suite","Established clear project patterns and principles","Set up basic database schema with SQLAlchemy","Created Pydantic models for validation"]}
{"type":"entity","name":"future_work","entityType":"project_tasks","observations":["Implement observation updates/removals","Design proper session management for concurrent operations","Update EntityService tests for new fileio module","Add more sophisticated search functionality","Handle markdown formatting edge cases","Consider versioning for file changes","Implement proper backup strategy"]}
{"type":"entity","name":"design_decisions","entityType":"technical_decisions","observations":["Filesystem as source of truth over database","Markdown format for human readability and editing","Atomic file operations for safety","SQLite + SQLAlchemy for proven reliability","Pydantic for validation and ID generation","Async/await for better scalability","Clear separation between files and database roles","Explicit error hierarchies for better handling"]}
{"type":"entity","name":"concurrency_considerations","entityType":"technical_challenge","observations":["SQLAlchemy session management in async context","File operation atomicity","Transaction isolation levels","Potential for conflicting updates","Need for proper session lifecycle","Possibility of file system race conditions","Database lock management"]}
{"type":"entity","name":"observation_update_approaches","entityType":"design_alternatives","observations":["Each approach trades off between simplicity, efficiency, and robustness","Four main approaches considered: bulk update, tracked IDs, diff-based, and position-based","Discussion revealed importance of human readability in file format","Consideration of manual editing workflows key to design","File system as source of truth principle guides tradeoffs"]}
{"type":"entity","name":"bulk_update_approach","entityType":"design_option","observations":["Update all observations at once in a single operation","Simpler file operations - just rewrite the whole list","No need for observation matching or IDs","Very consistent with source of truth principle","Less efficient for small changes","May have concurrency implications","Simplest implementation option"]}
{"type":"entity","name":"tracked_observations_approach","entityType":"design_option","observations":["Use markdown comments to store observation IDs","Enables precise updates and deletes","IDs stored as HTML comments in markdown","More complex markdown parsing required","IDs visible in raw markdown files","Balances tracking with readability"]}
{"type":"entity","name":"diff_based_approach","entityType":"design_option","observations":["Implement observation-aware diffing","Track changes at observation level","More efficient for updates","Preserves manual edits and changes","More complex implementation needed","Must handle merge conflicts","Most sophisticated option considered"]}
{"type":"entity","name":"position_based_approach","entityType":"design_option","observations":["Track observations by position/order","No explicit IDs needed","Cleanest markdown format","Order changes could break references","Difficult to handle concurrent edits","Most fragile option considered"]}
{"type":"entity","name":"tasks_and_progress","entityType":"project_tracking","observations":["Current focus on observation management implementation","Completed core file operations extraction","Completed EntityService updates","Completed initial ObservationService","Basic test coverage in place","Future work includes concurrent operations","Future work includes search improvements","Need to handle markdown edge cases"]}
{"type":"entity","name":"error_handling_patterns","entityType":"technical_patterns","observations":["Custom exception hierarchy with ServiceError base","Specific error types (FileOperationError, DatabaseSyncError, etc)","Clear separation between file and database errors","Error propagation patterns established","Focus on actionable error messages","Error handling at appropriate levels"]}
{"type":"entity","name":"data_models","entityType":"technical_implementation","observations":["SQLAlchemy models for database structure","Pydantic schemas for API/service layer","Entity model with UUID-based IDs","Observation model with entity relationships","UTCDateTime custom type for timestamps","Automatic ID generation in Pydantic models","Strict validation rules"]}
{"type":"entity","name":"markdown_format","entityType":"file_format","observations":["Simple, human-readable format","Entity name as H1 header","Metadata in key-value format","Observations as bullet points","Atomic file operations for updates","Designed for manual editing","No hidden metadata in main content"]}
{"type":"entity","name":"test_driven_development","entityType":"development_pattern","observations":["Tests revealed need for atomic file operations","Error cases drove error hierarchy design","Edge cases informed validation rules","Test fixtures shaped service interfaces","File operations extracted due to test patterns","Concurrent test issues revealed session management needs"]}
{"type":"entity","name":"architecture_evolution","entityType":"design_process","observations":["Started with simple EntityService implementation","Circular dependency between Entity and Observation services revealed design flaw","Extracted file operations to separate module","Moved to passing Entity objects rather than IDs","Improved separation of concerns through iterations","File operations became reusable across services","Database became true 'index' rather than source of truth"]}
{"type":"entity","name":"validation_patterns","entityType":"technical_patterns","observations":["Pydantic models provide schema validation","Automatic ID generation if not provided","Database constraints via SQLAlchemy","Runtime checks in services","Markdown format validation","Error handling for invalid states"]}
{"type":"entity","name":"markdown_examples","entityType":"documentation","observations":["Example of basic entity:\n# Entity Name\ntype: entity_type\n\n## Observations\n- First observation\n- Second observation","Example with special characters:\n# Test & Entity!\ntype: test\n\n## Observations\n- Test & observation with @#$% special chars!","Format ensures human readability:\n# Basic Machines\ntype: project\n\n## Observations\n- Local-first knowledge management system\n- Combines filesystem durability with graph-based knowledge representation","Future consideration for observation IDs:\n# Entity Name\ntype: entity_type\n\n## Observations\n- <!-- obs-id: abc123 -->\n This is an observation with ID"]}
{"type":"entity","name":"markdown_parsing_rules","entityType":"technical_implementation","observations":["H1 header contains entity name","Metadata uses key: value format","Observations section marked by H2 header","Each observation is a markdown list item","Blank lines separate sections","Special characters allowed in content","No restrictions on observation content"]}
{"type":"entity","name":"schema_definitions","entityType":"technical_documentation","observations":["SQLAlchemy Entity model:\nclass Entity(Base):\n id: str (primary key)\n name: str (unique)\n entity_type: str\n created_at: datetime\n updated_at: datetime","SQLAlchemy Observation model:\nclass Observation(Base):\n id: str (primary key)\n entity_id: str (foreign key)\n content: str\n created_at: datetime\n context: Optional[str]","Pydantic Entity schema:\nclass Entity(BaseModel):\n id: str\n name: str\n entity_type: str\n observations: List[Observation]"]}
{"type":"entity","name":"test_evolution","entityType":"development_history","observations":["Started with basic Entity CRUD tests","Added filesystem verification to all tests","Developed concurrent operation tests (later removed)","Edge case tests drove better error handling","Test fixtures evolved to support both file and DB testing","Mocking patterns for file/DB operations","Special cases for long content and special characters"]}
{"type":"entity","name":"implementation_challenges","entityType":"technical_issues","observations":["Initial circular dependency between services","SQLAlchemy session management in async context","Atomic file operations with proper error handling","Maintaining DB sync with filesystem changes","Handling long content in observations","Managing test isolation with file operations","Deciding on markdown format tradeoffs","Concurrent operation complexity"]}
{"type":"entity","name":"Basic_Factory","entityType":"Project","observations":["Collaborative project between Paul and Claude","Explores AI-human collaboration in software development","Uses MCP tools for file and memory management","Built with git integration capabilities","Focuses on maintaining project context across sessions","About 90% complete with MCP tools","Still needs improvements in collaboration via files/git/github","Will be used to document and share collaborative development process"]}
{"type":"entity","name":"Basic_Factory_Components","entityType":"Technical","observations":["Server-side rendering with JinjaX","HTMX for dynamic updates","Alpine.js for client-side state","Tailwind CSS for styling","Component translation from React/shadcn/ui","Focus on simplicity and understandability","Demonstrates meta-compiler principles in component translation"]}
{"type":"entity","name":"Component_Translation_Process","entityType":"Methodology","observations":["Treats component porting as meta-compilation","Maps between React/TypeScript and JinjaX/Alpine.js domains","Uses formal grammar transformation approaches","Maintains functionality while simplifying implementation","Focuses on server-side rendering patterns","Preserves accessibility and performance","Uses short, focused git branches for each component"]}
{"type":"entity","name":"Basic_Machines_Philosophy","entityType":"Philosophy","observations":["Combines DIY punk ethics with software development","Emphasizes user empowerment and understanding","Values simplicity and composability","Treats complex systems as combinations of simple parts","Focuses on authentic creation and sharing","Draws inspiration from punk rock, Free Software, and theoretical CS","Emphasizes the cycle of creation, complexity, and renewal"]}
{"type":"entity","name":"Basic_Machines_Manifesto","entityType":"Document","observations":["Created through collaboration between Paul and Claude","Explores connection between DIY punk ethics and software development","Emphasizes composition over inheritance in both philosophy and practice","Views software development through lens of basic machines that combine for complex computation","Advocates for user empowerment and technological independence","Structured in sections covering Origins, Philosophy, Technical Implementation, and AI Collaboration","Draws connections between punk rock, free software, and theoretical computer science","Emphasizes importance of sharing knowledge and building community","Released in December 2024"]}
{"type":"entity","name":"AI_Human_Collaboration_Model","entityType":"Methodology","observations":["Focuses on deep collaboration rather than simple task completion","Maintains rich context across sessions via knowledge graph","Uses short, focused git branches for each collaborative session","Values intellectual partnership over simple code generation","Emphasizes both practical implementation and theoretical exploration","Creates space for authentic exchange while maintaining AI/human clarity","Uses formal methods when appropriate (like grammar transformation)","Documents decisions and processes for future reference","Developed through Basic Machines project experience"]}
{"type":"entity","name":"Basic_Machines_Roadmap","entityType":"Project_Plan","observations":["Phase 1 (30 days): Build basic-machines.co website","Phase 2 (60-90 days): Develop premium component bundles","Phase 3 (90-120 days): Launch Basic Foundation commercial offering","Focus on building brand and marketing presence","Prioritize components needed for own site development","Document and share collaboration process","Build sustainable business model aligned with values"]}
{"type":"entity","name":"Basic_Machines_Website","entityType":"Project","observations":["To be built at basic-machines.co","Will showcase products and vision","Needs components for navigation, hero sections, features","Will demonstrate component usage in production","Will include blog for sharing progress","Focus on clear value proposition","Platform for sharing Basic Machines philosophy"]}
{"type":"entity","name":"Basic_Memory_Markdown_Example","entityType":"Example","observations":["Shows complete markdown structure for basic-memory entity","Uses frontmatter for metadata (id, type, created, context)","Has main description section after title","Includes Observations as bullet points","Shows Relations with [id] relation_type | context format","Lists References at bottom","Created during initial design discussion","Serves as canonical example of file format"]}
{"type":"entity","name":"Basic_Memory_Database_Schema","entityType":"Technical","observations":["Uses SQLite for local storage","Entities table with id, name, type, created_at, context, description, references","Observations table linking to entities with content and context","Relations table tracking directional relationships between entities","References column needs quotes as SQL reserved word","Designed for easy rebuilding from markdown files","Foreign key constraints maintain data integrity","Unique constraint on relations prevents duplicates","Created_at timestamps track history","Context fields enable tracking information sources"]}
{"type":"entity","name":"Basic_Memory_Project_Structure","entityType":"Technical","observations":["Uses dbmate for database migrations","Projects directory stores SQLite databases and markdown files","Makefile provides common development commands","Environment vars configure database connection","db/migrations directory for SQL schema changes","Gitignore excludes database files and env config","Uses Python 3.12 with modern tooling","Tests directory for pytest files","Follows Basic Machines project conventions"]}
{"type":"entity","name":"Basic_Memory_Project_Isolation_Decision","entityType":"Decision","observations":["Decided to defer multi-project support to post-MVP","Will use separate SQLite databases per project","Initially using projects directory in code repository","Plan to make location configurable later","No changes needed to core domain model","Keeps initial implementation simple","FTS/search capabilities also deferred for simplicity"]}
{"type":"entity","name":"Basic_Memory_Implementation_Plan","entityType":"Plan","observations":["Start with SQLAlchemy models matching schema","Then build CLI for basic operations","Then implement markdown parser","Use TDD approach throughout","Begin with core domain model","CLI will support CRUD operations","Parser must handle frontmatter and sections","Following modular development approach","Planning to use typer for CLI","Will use modern Python tools and practices"]}
{"type":"entity","name":"Basic_Memory_Implementation_Status","entityType":"Status","observations":["Core modules implemented: models, services, repository, fileio","Modular architecture with clear separation of concerns","File operations extracted to separate fileio module","Initial ObservationService implementation complete","Basic test coverage in place","Exploring observation management strategies","Using SQLAlchemy for database interaction","Markdown file operations working","Entity management functional","Repository layer implementation complete with SQLAlchemy models and tests","Database operations working with proper UTC timestamp handling","In-memory SQLite testing infrastructure proven effective"]}
{"type":"entity","name":"Basic_Memory_Observation_Management_Design","entityType":"Design","observations":["Four approaches under consideration","Bulk Update: Simple but less efficient","Tracked Observations: Precise but clutters markdown","Diff-based: Efficient but complex","Position-based: Clean but fragile","Key challenge is balancing markdown readability with efficient updates","Must maintain filesystem as source of truth","Need to consider concurrent edits","Currently evaluating trade-offs","Implementation choice pending discussion"]}
{"type":"entity","name":"Basic_Memory_Architectural_Decisions","entityType":"Decisions","observations":["Split file operations into separate fileio module","Using SQLAlchemy for database operations","Maintain filesystem as source of truth","Modular service-based architecture","Clear separation between data access and business logic","Repository pattern for database interactions","Schemas separate from models","Focus on maintainability and testability","Services handle business rules","Considering concurrency in design"]}
{"type":"entity","name":"Basic_Memory_Implementation_Analysis","entityType":"Analysis","observations":["Clean modular architecture with clear responsibilities","Strong typing throughout codebase","Excellent error handling with custom exceptions","SQLAlchemy models perfectly match our domain model","Atomic file operations for data safety","Services implement filesystem-as-source-of-truth principle","Async support throughout","Good separation between domain models and database models","Careful handling of UTC timestamps","Smart use of SQLAlchemy relationships"]}
{"type":"entity","name":"Basic_Memory_Current_Challenges","entityType":"Challenges","observations":["Observation update/removal strategy needs to be chosen","Need to handle concurrent file operations safely","Search functionality to be implemented","Edge cases in markdown formatting to be handled","Session management for concurrent operations needed","Balance between file operations and database sync","Testing coverage could be expanded","Need to handle relationship updates in files"]}
{"type":"entity","name":"Basic_Memory_Observation_Hash_Tracking","entityType":"Design","observations":["Use content hashes to track observation identity","Store hashes in database but not in markdown","Can match observations across file edits using hashes","Similar to how git tracks content changes","Keeps markdown clean and human-friendly","Allows efficient bulk updates","Handles reordering of observations","Maintains filesystem as source of truth","No need for visible IDs in markdown","Could track observation history through hash changes"]}
{"type":"entity","name":"Basic_Memory_Repository_Implementation","entityType":"Code_Implementation","observations":["Implemented base Repository class with CRUD operations","Added specialized EntityRepository, ObservationRepository, and RelationRepository","Used string IDs instead of UUIDs","Added UTCDateTime custom type for timestamp handling","Used in-memory SQLite for testing","Achieved 84% test coverage","Created comprehensive pytest fixtures"]}
{"type":"entity","name":"Basic_Memory_Dependencies","entityType":"Technical","observations":["Uses Python 3.12","SQLAlchemy with async support","pytest-asyncio for async testing","aiosqlite for async SQLite operations","greenlet for SQLAlchemy async support","uv for dependency management","pytest-cov for coverage reporting","Development dependencies managed in pyproject.toml"]}
{"type":"entity","name":"Basic_Memory_Current_Architecture","entityType":"Architecture_Analysis","observations":["Clear separation between domain models (Pydantic) and storage models (SQLAlchemy)","File I/O completely separated into dedicated module","Strong 'filesystem as source of truth' pattern in services","Atomic file operations with proper error handling","Service layer coordinates between filesystem and database","Database acts as queryable index rather than primary storage","Clean error hierarchy with specific exception types","Rebuild operations available for recovery scenarios"]}
{"type":"entity","name":"Basic_Memory_Evolution","entityType":"Analysis","observations":["Started with repository pattern following basic-foundation","Evolved to more sophisticated architecture with clear layers","Added Pydantic schemas for domain modeling","Separated file operations into dedicated module","Implemented robust error handling throughout","Maintained filesystem as source of truth principle","Added observation management with context tracking","Introduced rebuild capabilities for system recovery"]}
{"type":"entity","name":"Basic_Memory_Service_Layer","entityType":"Implementation","observations":["EntityService handles entity lifecycle and coordinates storage","ObservationService manages observations within entities","Services ensure filesystem and database stay in sync","Clear error handling with ServiceError hierarchy","Strong typing throughout service interfaces","Implements filesystem as source of truth pattern","Handles UUID generation and timestamp management","Provides methods for system recovery and rebuild"]}
{"type":"entity","name":"Basic_Memory_Schema_Design","entityType":"Implementation","observations":["Uses Pydantic for domain models and validation","Automatic ID generation with timestamp and UUID","Clear separation from SQLAlchemy storage models","Supports optional context tracking","Models match markdown file structure","Enables clean serialization/deserialization","Strong typing with proper validation rules","Independent from storage concerns"]}
{"type":"entity","name":"Basic_Memory_Next_Tasks","entityType":"TaskList","observations":["✅ Implement SQLAlchemy models and repositories (Done)","✅ Add SQLAlchemy migrations (Done)","✅ Create service layer (Done)","✅ Implement file I/O module (Done)","✅ Set up domain models with Pydantic (Done)","✅ Initial test infrastructure (Done)","✅ Basic CRUD operations (Done)","⏳ Implement full test coverage for db.py","⏳ Add more sophisticated search functionality","⏳ Implement CLI interface","⏳ Add relationship management to services","⏳ Handle concurrent file operations safely","⏳ Add versioning for file changes","⏳ Implement proper backup strategy","⏳ Add type hints throughout codebase","⏳ Improve error messages and logging","⏳ Add documentation for core modules"]}
{"type":"entity","name":"Basic_Memory_Meta_Experience","entityType":"Case_Study","observations":["Experienced our own context loss when reconstructing project knowledge","Had to rebuild task list and project context from filesystem and memory","Validated 'filesystem as source of truth' principle through reconstruction","Code and tests served as reliable historical record","Knowledge graph structure helped guide reconstruction process","Markdown files provided human-readable context","Atomic information design made piece-by-piece reconstruction possible","Ironic validation of the need for basic-memory's features","Experience demonstrates value of durable, human-readable knowledge storage","Shows importance of separating durable storage from ephemeral context"]}
{"type":"entity","name":"Model_Context_Protocol","entityType":"protocol","observations":["Core part of the basic-memory architecture","Enables AI-human collaboration on projects","Provides tool-based interaction with knowledge graph","Developed by Anthropic for structured AI-system interaction","Used for maintaining consistent, rich context across conversations"]}
{"type":"entity","name":"basic-memory_core_principles","entityType":"principles","observations":["Local First: All data stored locally in SQLite","Project Isolation: Separate databases per project","Human Readable: Everything exportable to plain text","AI Friendly: Structure optimized for LLM interaction","DIY Ethics: User owns and controls their data","Simple Core: Start simple, expand based on needs","Tool Integration: MCP-based interaction model"]}
{"type":"entity","name":"basic-memory_business_model","entityType":"business_strategy","observations":["Core features free: Local SQLite, basic knowledge graph, search, markdown export, basic MCP tools","Professional features potential: Rich document export, advanced versioning, collaboration features, custom integrations, priority support","Focus on maintaining DIY/punk philosophy while enabling sustainability"]}
{"type":"entity","name":"basic-memory_cli","entityType":"interface","observations":["Supports project management commands (create, switch, list)","Entity management (add entity, add observation, add relation)","Future support for export and batch operations","Follows consistent command structure","Planned integration with MCP tools"]}
{"type":"entity","name":"basic-memory_export_format","entityType":"file_format","observations":["Uses markdown with frontmatter metadata","Includes entity name, type, creation timestamp","Observations as bullet points","Relations in structured format with links","References section at bottom","Designed for human readability and machine parsing","Example format documented in project specs"]}
{"type":"entity","name":"relation_service","entityType":"code_module","observations":["Planned service for managing relations in both filesystem and database","Will follow filesystem-is-source-of-truth principle like other services","Needs to handle atomic file operations for relation updates","Must coordinate with EntityService for relationship integrity","Will handle bidirectional relationship tracking","Will support relation validation and type enforcement","Must implement rebuild functionality for index recovery","Will need careful error handling for file/db sync","Should support relation search and filtering","Must handle relation lifecycle (create/read/update/delete)"]}
{"type":"entity","name":"service_layer_patterns","entityType":"implementation_patterns","observations":["Services handle both file and database operations","Filesystem is always source of truth","Database serves as queryable index","Services implement atomic file operations","Clear error hierarchy with specific exceptions","Use of dependency injection via constructor params","Async/await used throughout service layer","Services coordinate between storage layers","Repository pattern used for database access","Services maintain entity integrity across storage","Rich error types extend from ServiceError base","Rebuild operations available for recovery"]}
{"type":"entity","name":"database_models","entityType":"implementation","observations":["Entity model with unique name and type","Observation model linked to entities","Relation model tracks connections between entities","Custom UTCDateTime type for timestamp handling","Use of SQLAlchemy relationships for navigation","Cascading deletes for dependent objects","String IDs used for compatibility","Rich relationship modeling with backpopulates","Proper indexing on foreign keys","Context tracking available on models","Models include created_at timestamps","Relationships handle bidirectional navigation"]}
{"type":"entity","name":"repository_patterns","entityType":"implementation_patterns","observations":["Generic Repository[T] base class implementation","Type-safe operations with SQLAlchemy","Specialized repositories for each model type","Async operations throughout","Clear error handling patterns","Support for custom queries and filtering","Pagination support built-in","Transaction management via session","Proper type hints and generics usage","Entity-specific query methods in subclasses"]}
{"type":"entity","name":"relation_service_design","entityType":"design","observations":["Must handle relation lifecycle in both files and DB","Needs to validate existence of both entities","Should support relation type enforcement","Must maintain bidirectional consistency","Should support relation querying and filtering","Needs proper error handling for graph consistency","Must integrate with entity file format","Should support bulk operations for efficiency","Must handle relation deletion and cascading","Should provide search by type and entities"]}
{"type":"entity","name":"relation_service_implementation_plan","entityType":"plan","observations":["1. Define core relation operations (create, get, delete)","2. Implement file format handling for relations","3. Add database sync with RelationRepository","4. Implement validation and error handling","5. Add rebuild and recovery operations","6. Implement relation type enforcement","7. Add relation search and filtering","8. Implement bulk operations","9. Add comprehensive tests","10. Document API and error handling"]}
{"type":"entity","name":"relation_service_challenges","entityType":"challenges","observations":["Maintaining consistency between file and database","Handling relation type validation efficiently","Managing bidirectional relationships in files","Ensuring atomic updates across entities","Handling deletion with proper cascading","Efficient querying of relation graphs","Recovery from partial file/db sync failures","Bulk operation atomicity","Clear error reporting for graph operations","Performance with large relation sets"]}
{"type":"entity","name":"relation_file_format","entityType":"file_format","observations":["Relations stored in entity markdown files","Format: [target_id] relation_type | context","Relations section marked by ## Relations header","Outgoing relations only stored in source entity","Relations rebuild on entity load","Clean human-readable format","Context is optional with pipe separator","Links generate valid navigation references","Markdown-friendly formatting","Example: [Paul] authored | with Claude"]}
{"type":"entity","name":"relation_service_error_handling","entityType":"implementation_patterns","observations":["RelationError extends ServiceError base","Specific errors for validation failures","Handles entity not found cases","Manages relation type validation errors","File operation errors properly wrapped","Database sync errors clearly reported","Transaction rollback on errors","Proper error propagation chain","Clear error messages for debugging","Recovery paths for common errors"]}
{"type":"entity","name":"relation_service_testing","entityType":"testing","observations":["Test all relation lifecycle operations","Verify file and database consistency","Test relation type validation","Check error handling paths","Test bulk operations","Verify bidirectional consistency","Test recovery operations","Check cascade operations","Verify search and filtering","Test with large relation sets"]}
{"type":"entity","name":"fileio_patterns","entityType":"implementation_patterns","observations":["Atomic file operations with temporary files","Clear error handling for IO operations","Consistent file naming and paths","Support for different file formats","Efficient file reading and writing","Proper file locking mechanisms","Recovery from partial writes","Consistent encoding handling","Directory management utilities","Path manipulation helpers","Currently implemented in fileio.py module","Uses pathlib for path operations","Handles file not found cases gracefully","Maintains data integrity during writes"]}
{"type":"entity","name":"pytest_patterns","entityType":"implementation_patterns","observations":["Common fixtures should be in conftest.py for reuse","Use pytest_asyncio.fixture for async fixtures","Session fixtures need proper async cleanup","Temporary directories should be managed with context managers","Test categories: happy path, error path, recovery, edge cases","Services need project_path and repo injected","Use monkeypatch for mocking in async context","SQLite in-memory database ideal for testing","Explicit test verification: file content and database state"]}
{"type":"entity","name":"relation_implementation_learnings","entityType":"implementation_learnings","observations":["Better to pass full Entity objects than IDs to services","Services should not re-read entities if they have them","File operations should be atomic and verified","Database serves as queryable index, not source of truth","Relations stored in source entity's markdown file","Clear separation between file ops and database sync","Entity objects should own their relations list","Context is optional but fully supported in implementation"]}
{"type":"entity","name":"test_driven_insights","entityType":"learnings","observations":["Tests help reveal better API design (e.g., passing Entity objects)","Error cases drive proper exception hierarchy","File verification as important as database checks","Edge cases inform markdown format decisions","Recovery tests ensure system resilience","Tests document expected behavior clearly","Fixtures significantly reduce test complexity","Common patterns emerge through test writing"]}
{"type":"entity","name":"meta_development_insights","entityType":"process","observations":["Break down large tasks into reviewable chunks","One file at a time prevents response truncation","Iterative development with tests leads to better design","Infrastructure code (fixtures) should be consolidated early","Test categories help ensure comprehensive coverage","Knowledge capture should happen during development","APIs tend to evolve toward simpler patterns","File operations require careful verification"]}
{"type":"entity","name":"AI_Assistant_Learnings","entityType":"meta_insights","observations":["Output management: Breaking responses into single files prevents truncation and allows better review","Knowledge graph helps maintain context: I can reference previous decisions and patterns accurately","Memory rebuilding experience validated the need for durable storage","Test-driven development provides clear steps and verification","Explicit relation tracking in knowledge graph helps me understand project context","Rich context from multiple sources (code, docs, tests) enables better assistance","File-at-a-time approach allows deeper analysis of each component","Keeping entity names consistent helps with referencing and relationships"]}
{"type":"entity","name":"Effective_Response_Patterns","entityType":"meta_patterns","observations":["When showing code changes, break into discrete files","Review existing code before suggesting changes","Reference knowledge graph for context and patterns","Explicitly connect new code to existing patterns","Validate suggestions against test cases","Keep track of file changes for atomic commits","Check both implementation and test files for consistency","Maintain clear separation of concerns in responses"]}
{"type":"entity","name":"AI_Context_Management","entityType":"meta_practice","observations":["Knowledge graph provides reliable persistent memory","Project documentation gives high-level context","Code review shows implementation patterns","Tests demonstrate expected behavior","Important to actively track what has been modified","Entity relationships help understand dependencies","Regular knowledge capture during development","Using consistent entity references across conversations"]}
{"type":"entity","name":"AI_Tool_Usage_Patterns","entityType":"meta_practice","observations":["read_file before suggesting changes","write_file one file at a time","list_directory to understand project structure","search_nodes to find relevant context","create_entities to capture new learnings","create_relations to connect concepts","Using knowledge graph to track decisions","Validating changes through test execution"]}
{"type":"entity","name":"relation_service_learnings","entityType":"implementation_learnings","observations":["Entity-based API cleaner than ID-based for service layer","Model_dump method can handle storage serialization","File format needs explicit section markers (## Relations)","Whitespace handling important for long content comparisons","Test fixtures allow focused test cases","SQLAlchemy selects better than raw SQL for type safety","Atomic file operations maintained for relations"]}
{"type":"entity","name":"test_driven_insights_relations","entityType":"learnings","observations":["Tests revealed need for whitespace normalization","Edge cases drove file format decisions","SQLAlchemy model access safer than raw queries","Fixtures reduced test setup complexity","File verification as important as database checks","Testing both memory model and storage format","Test categories ensure comprehensive coverage"]}
{"type":"entity","name":"relation_service_patterns","entityType":"patterns","observations":["Use Entity objects in API","Serialize to IDs for storage","Maintain file as source of truth","Keep file format human-readable","Handle circular references in serialization","Use repository pattern for database","Clear error hierarchies"]}
{"type":"entity","name":"packaging_learnings","entityType":"technical_learnings","observations":["When using pytest-mock, traditional pip install works more reliably than uv sync","Package discovery behavior can differ between uv and pip","Clean venv with pip install is a reliable fallback for dependency issues","Package installation location might differ between uv and pip","Dependencies in pyproject.toml dev section work reliably with pip install -e .[dev]"]}
{"type":"entity","name":"Recent_Implementation_Progress","entityType":"progress_update","observations":["Successfully split services.py into modular structure under services/","Created __init__.py, entity_service.py, observation_service.py, relation_service.py","Fixed pytest-mock installation issues by using pip install -e .[dev] instead of uv sync","Improved test structure with minimal mocking - only used for error testing","Implemented relation service with Entity-based API","Achieved good test coverage across services","File operations are only mocked when testing error conditions","Services follow filesystem-as-source-of-truth pattern"]}
{"type":"entity","name":"Next_Steps","entityType":"project_tasks","observations":["Consider adding more relation service tests","Potentially expand relations features","Look for opportunities to improve test coverage","Consider documenting package management preferences (pip vs uv)","Consider adding integration tests for services","Review and possibly expand error handling cases"]}
{"type":"entity","name":"Development_Practices","entityType":"process","observations":["Favor real operations over mocks in tests","Only mock for error condition testing","Use pip install -e .[dev] for reliable dev dependency installation","Maintain modular service structure","Keep filesystem as source of truth","Use Entity objects in service APIs instead of IDs","Validate both file and database state in tests"]}
{"type":"entity","name":"MCP_Resources","entityType":"Concept","observations":["Stateful objects in Model Context Protocol","Enable persistent access to capabilities"]}
{"type":"entity","name":"MCP_Server_Implementation","entityType":"Technical_Design","observations":["Inherits from mcp.server.Server base class","Tools are implemented as async methods","Each tool method maps directly to a function available to the AI","Tools can request user input via Prompts","Simple function call interface rather than explicit resource management","State management handled by server instance","Returns serialized data using model_dump() for consistency"]}
{"type":"entity","name":"MCP_Tools","entityType":"Protocol_Feature","observations":["Defined as async methods on server class","Return values must match tool definition schema","Can maintain state between invocations via server instance","Tools can prompt for user input when needed","No need for explicit Resource objects in implementation"]}
{"type":"entity","name":"Basic_Memory_MCP","entityType":"Implementation","observations":["Uses MemoryService for core operations","Implements project selection via prompts","Maintains project context across tool invocations","Maps directly to memory graph operations","Handles serialization of Pydantic models"]}
{"type":"entity","name":"Basic_Memory_Testing","entityType":"Testing_Design","observations":["Needs pytest for async testing","Should isolate filesystem operations for tests","Needs to handle MCP server lifecycle in tests","Should test both service layer and MCP interface","Will need mocks for project paths and file operations"]}
{"type":"entity","name":"Memory_Service_Tests","entityType":"Test_Suite","observations":["Should test entity creation with observations","Should test relation creation between entities","Should verify proper ID generation and model validation","Should test deletion cascading","Should test search functionality","Must verify proper serialization of entities and relations"]}
{"type":"entity","name":"MCP_Server_Tests","entityType":"Test_Suite","observations":["Should test project initialization workflow","Should test prompt handling","Should verify tool input/output formats","Should test error cases and validation","Must verify proper serialization in tool responses"]}
{"type":"entity","name":"Memory_Service_Refactoring","entityType":"Technical_Task","observations":["MemoryService uses create() but EntityService might expect create_entity()","MemoryService assumes get_by_name() but EntityService might use different method","Need to verify deletion method signatures","Need to check if search interface matches","Should verify observation handling matches ObservationService interface","RelationService methods need verification","EntityService.create_entity takes name, type, and optional observations directly, not an Entity object","EntityService requires project_path and entity_repo in constructor","ObservationService.add_observation takes Entity object and content string, not raw data","RelationService.create_relation takes Entity objects directly, not dict data","All services follow filesystem-as-source-of-truth pattern with DB indexing","All services handle database synchronization internally","Services expect Path objects for filesystem operations"]}
{"type":"entity","name":"Service_Interface_Audit","entityType":"Technical_Task","observations":["Need to review all existing service interfaces","Document current method signatures","Map discrepancies between MemoryService assumptions and actual interfaces","Check return types and error handling patterns","Review transaction/atomicity requirements","Method signatures need alignment: create vs create_entity etc","Need to handle DB repositories in service constructors","File operations should use project_path consistently","Need to maintain filesystem-as-source-of-truth pattern","Should handle database synchronization at service level","Error handling should align with existing patterns","Consider making MemoryService handle DB indexing consistently"]}
{"type":"entity","name":"Memory_Service_Patterns","entityType":"Technical_Pattern","observations":["Uses inner async functions to encapsulate operation logic","Leverages list comprehensions with async functions for parallel operations","Each operation follows a consistent pattern: validate, update DB, write file","Inner functions make the code more readable and maintainable","Operations can run in parallel when using list comprehensions with async functions"]}
{"type":"entity","name":"Pydantic_Create_Pattern","entityType":"Technical_Pattern","observations":["Separate Create models match the exact shape of incoming data","Provides clear contract for MCP tool inputs","Handles validation of raw input data","Converts cleanly to domain models via from_create methods","Maintains separation between external API format and internal models","Similar to FastAPI request model pattern","Allows camelCase in API while using snake_case internally"]}
{"type":"entity","name":"Basic_Memory_Business","entityType":"Business_Model","observations":["Core system is open source and free","Local-first, giving users data control","Professional features could be licensed","Enterprise support and customization services","Potential for MCP tool marketplace"]}
{"type":"entity","name":"MCP_Marketplace","entityType":"Business_Concept","observations":["Could host verified MCP tools for different use cases","Tools rated by performance and reliability","Marketplace takes percentage of tool usage fees","Enterprise tool verification and security scanning","Custom tool development services","Integration support for existing tools"]}
{"type":"entity","name":"Persistence_Of_Vision","entityType":"Concept","observations":["Mental model for continuous AI-human interaction","Like cinema: 24fps creates illusion of smooth motion","Basic-memory provides 'frames' of structured knowledge","Current state: Better than flipbook, not yet digital cinema","Goal: Achieve smoother cognitive continuity between interactions","Proposed by Drew as metaphor for AI conversation continuity"]}
{"type":"entity","name":"Conversation_Continuity_Pattern","entityType":"Usage_Pattern","observations":["Use basic-memory entity/relation schema for conversations","Each chat becomes an entity with observations for key points","Relations link to discussed concepts and other chats","Uses zettelkasten format IDs for natural ordering","Can be used as template/recipe for others","Future possibility: Git SHA integration for versioning"]}
{"type":"entity","name":"Usage_Recipes","entityType":"Feature_Concept","observations":["Predefined patterns users can follow or adapt","Could include conversation tracking recipe","Templates for different knowledge management styles","Shows practical applications of the generic schema","Helps users get started with the system"]}
{"type":"entity","name":"Chat_References","entityType":"Technical_Feature","observations":["Uses ref:* syntax to reference previous conversations","Combines reference semantics with pointer symbolism","Format: ref:*{zettelkasten-id}","Allows explicit context loading between chats","Inspired by C++ references and pointers","Provides memory-model-like access to conversation context","Uses ref:// URI format following MCP Resource pattern","Could support multiple reference schemes (chat/entity/concept)","Makes reference semantics explicit and unambiguous","Aligns with standard URI formatting"]}
{"type":"entity","name":"Chat_Reference_Protocol","entityType":"Technical_Specification","observations":["Uses URI format: ref://basic-memory/chat/[id]","Follows MCP Resource pattern: [protocol]://[host]/[path]","Enables explicit context loading between chats","Can support multiple resource types (chat/entity/concept)","Provides standardized way to reference previous conversations","Example: ref://basic-memory/chat/20240307-drew-ab12ef34"]}
{"type":"entity","name":"20240307-chat-reference-protocol","entityType":"conversation","observations":["Developed ref:// URI format for chat references","Added Chat Reference Protocol to prompt instructions","Discussed implementation of chat continuation","Created complete prompt instructions document","Reference format follows MCP Resource pattern","Reviewed and confirmed complete prompt instructions","Ready to test ref://basic-memory/chat/20240307-chat-reference-protocol in new chat"]}
{"type":"entity","name":"20240307-chat-reference-protocol-test","entityType":"conversation","observations":["First implementation test of chat reference protocol","Testing continuation from 20240307-chat-reference-protocol","Focused on practical implementation of ref:// URI format"]}
{"type":"entity","name":"Write_File_Tool_Usage","entityType":"Tool_Usage_Pattern","observations":["Never use placeholders like '# Rest of...' when writing files - must include complete file content","File content must be complete and valid - partial updates will truncate the file","If showing partial changes, should inform human and let them handle the file write","write_file tool replaces entire file contents - cannot do partial updates","Code files especially must be complete and valid to avoid breaking functionality","Always read_file before write_file to understand current state","Using write_file without reading first risks reverting recent changes","Pattern should be: read current state, make modifications, then write if needed","Especially important in collaborative development where files may have been updated"]}
{"type":"entity","name":"Run_Tests_Tool_Request","entityType":"Feature_Request","observations":["Need to add a tool enabling Claude to run tests locally","Would help with direct validation of code changes","Current workaround: Claude has to ask human to run tests","Should support running specific test functions (e.g. pytest tests/test_memory_service.py::test_create_relations)","Would improve iterative development workflow between human and AI"]}
{"type":"entity","name":"SQLAlchemy_Async_Loading_Pattern","entityType":"Technical_Pattern","observations":["Use selectinload() instead of lazy loading when accessing SQLAlchemy relationships in async code","Lazy loading doesn't work with async due to greenlet context requirements","selectinload performs a single efficient query with an IN clause","Pattern used in basic-memory's EntityRepository for loading relations","Documented in find_by_id method with thorough explanation","Alternative approaches: joinedload (single JOIN query) or subqueryload (subquery approach)","Benefits: prevents 'MissingGreenlet' errors, reduces N+1 query problems","Key insight: load all needed relationships upfront in async code","Example use: selectinload(Entity.outgoing_relations)"]}
{"type":"entity","name":"20241207-sqlalchemy-async-pattern","entityType":"conversation","observations":["Fixed SQLAlchemy async relationship loading issues","Implemented selectinload pattern in EntityRepository","Updated find_by_id to eager load relations","Added documentation about the pattern","Created knowledge graph entry about SQLAlchemy async loading","Fixed failing tests by properly loading relations in memory_service","Discussed SQLAlchemy relationship loading best practices"]}
{"type":"entity","name":"20241207-memory-service-relations","entityType":"conversation","observations":["Fixed SQLAlchemy async loading with selectinload pattern","Updated find_by_id in EntityRepository to eager load relations","Discovered create_relations works but returns empty list","Verified relations are being stored correctly in memory.json","Next step: Work on MemoryService.add_observations implementation","Improved understanding of MCP memory storage format through debugging"]}
{"type":"entity","name":"add_observations_implementation_plan","entityType":"technical_plan","observations":["Follow pattern from create_entity and create_relation methods","File operations first (read & write) - filesystem is source of truth","Database updates in parallel","Simplify current implementation","Current flow is:"," - First read entities and create observations"," - Write files in parallel"," - Update DB indexes sequentially","Key tests needed:"," - Adding observations to multiple entities"," - Verifying filesystem state first"," - Verifying database state"," - Error cases for missing entities"," - Error cases for file operations"]}
{"type":"entity","name":"MCP_Reference_Integration","entityType":"feature_idea","observations":["Can be implemented as a Model Context Protocol integration similar to the fetch tool","Would provide structured way to pass chat references to Claude","Could handle ref:// URL format systematically","Integration would fetch context from referenced chats and inject into conversation","Observed from Claude Desktop UI showing MCP integration pattern with fetch tool","Would be more robust than passing references in chat text"]}
{"type":"entity","name":"Project_Priorities","entityType":"roadmap","observations":["P1: Dogfooding basic-memory system instead of JSON memory store","Future: Implement MCP-based reference system"]}
{"type":"entity","name":"great_observation_loading_saga_20241207","entityType":"debugging_session","observations":["Occurred on December 7, 2024 while debugging basic-memory SQLAlchemy relationship loading","Issue: selectinload() wasn't properly loading relationships in async SQLAlchemy context","Tried multiple solutions: explicit joins, manual loading, various SQLAlchemy loading strategies","Final solution: Using session.refresh() with explicit relationship names","Memorable quote: 'The Great Observation Loading Saga'","Key learning: Sometimes the obvious SQLAlchemy patterns need adaptation for async contexts","Solution preserved in basic-memory repository in EntityRepository.find_by_id()"]}
{"type":"entity","name":"basic_memory_implementation_20241208","entityType":"technical_milestone","observations":["Fixed async SQLAlchemy relationship loading issues by using explicit refresh with relationship names","Established pattern of relationship handling belonging in MemoryService not EntityService","Fixed ID generation flow through Pydantic schemas to DB layer","Standardized error handling using EntityNotFoundError","All 32 tests passing with 70% coverage","Core services (Entity, Observation, Relation) working properly","Ready for MCP server implementation","Notable debugging session: The Great Observation Loading Saga - resolved lazy loading issues","Established clear separation between MemoryService orchestration and individual service responsibilities"]}
{"type":"entity","name":"MCP_Dependency_Risk","entityType":"technical_lesson","observations":["Experienced disruption when MCP npm package disappeared - 'leftpad moment'","Need to ensure basic-memory tools are resilient to external dependency issues","Local implementation of MCP server provides better stability than npm packages","Important to maintain control of critical infrastructure components","Validates DIY/local-first philosophy of basic-memory project","Package manager fragility revealed by simple 'npx @modelcontextprotocol/server-memory' failure"]}
{"type":"entity","name":"basic_memory_project_20241208","entityType":"technical_milestone","observations":["Core MCP server implementation completed with tools: create_entities, search_nodes, open_nodes, add_observations, create_relations, delete_entities, delete_observations","ProjectConfig and dependency injection pattern established","Test framework in place with in-memory DB support","Support for both camelCase (MCP) and snake_case (internal) formats","Filesystem remains source of truth with SQLite as index","Two-way sync pattern identified between Claude MCP tools and direct markdown file editing","Ready for Claude Desktop integration testing phase","Next steps identified: passing tests, markdown format definition, file change tracking, real-world testing","Implementation prioritizes local-first principles with filesystem as source of truth"]}
{"type":"entity","name":"basic_memory_mcp_architecture","entityType":"technical_design","observations":["MemoryServer class extends MCP Server with custom handler registration","Uses ProjectConfig for clean dependency injection and configuration","Memory service can be injected for testing","Handlers exposed as instance attributes for testing","Tool schemas leverage existing Pydantic models"]}
{"type":"entity","name":"basic_memory_sync_considerations","entityType":"design_insight","observations":["Need to handle sync between direct markdown file edits and DB index","Watch for file system changes as potential future enhancement","Consider index rebuild patterns on startup","Keep human-friendly markdown format for direct editing"]}
{"type":"entity","name":"mcp_server_learnings","entityType":"developer_insight","observations":["MCP protocol is new and documentation is still evolving","Test patterns are not well established yet in example implementations","Supporting both camelCase and snake_case helps with protocol/internal compatibility","Server.handle_* naming convention is important for handler registration"]}
{"type":"entity","name":"20241208-mcp-tool-refactoring","entityType":"conversation","observations":["Decision to return structured data via EmbeddedResource instead of TextContent string parsing","Plan to create Pydantic result models (CreateEntitiesResult, SearchNodesResult etc)","Will use application/vnd.basic-memory+json as MIME type for our structured data","Currently debugging test issues with add_observations tool","Entity ID vs name resolution needed in add_observations","Goal is to make tools more joyful to use by eliminating string parsing","MCP spec supports EmbeddedResource for structured data returns"]}
{"type":"entity","name":"Basic Memory MCP Server Implementation","entityType":"technical_notes","observations":["Server implements Model Context Protocol using proper structured data responses","Uses EmbeddedResource with custom MIME type 'application/vnd.basic-memory+json'","Clean separation between input validation and handlers via Pydantic models","All tool operations return structured data through create_response helper","Type safety with Literal types for tool names and proper typing for handlers","Handler registry pattern with TOOL_HANDLERS dictionary","Consistent error handling pattern using MCP error codes","Uses Pydantic ConfigDict for proper ORM integration","Tool schemas organized into Input and Response types","Input validation with Annotated types for extra constraints","Response models consistently use from_attributes=True for ORM data","Entity ID generation moved to model validator on EntityBase","Follows principle of making common operations easy and safe"]}
{"type":"relation","from":"Paul","to":"Basic_Machines","relationType":"created_and_maintains"}
{"type":"relation","from":"basic-memory","to":"Basic_Machines","relationType":"is_component_of"}
{"type":"relation","from":"Paul","to":"basic-memory","relationType":"develops"}
{"type":"relation","from":"fileio_module","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"entity_service","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"observation_service","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"fileio_module","to":"basic-memory","relationType":"is_component_of"}
{"type":"relation","from":"entity_service","to":"basic-memory","relationType":"is_component_of"}
{"type":"relation","from":"observation_service","to":"basic-memory","relationType":"is_component_of"}
{"type":"relation","from":"entity_service","to":"fileio_module","relationType":"uses"}
{"type":"relation","from":"observation_service","to":"fileio_module","relationType":"uses"}
{"type":"relation","from":"observation_management","to":"observation_service","relationType":"influences_design_of"}
{"type":"relation","to":"basic-memory","from":"testing_infrastructure","relationType":"supports"}
{"type":"relation","to":"testing_infrastructure","from":"test_categories","relationType":"implements"}
{"type":"relation","to":"basic-memory","from":"completed_work","relationType":"tracks_progress_of"}
{"type":"relation","to":"basic-memory","from":"future_work","relationType":"guides_development_of"}
{"type":"relation","to":"basic-memory","from":"design_decisions","relationType":"shapes_architecture_of"}
{"type":"relation","to":"basic-memory","from":"concurrency_considerations","relationType":"influences_design_of"}
{"type":"relation","to":"future_work","from":"concurrency_considerations","relationType":"informs"}
{"type":"relation","to":"observation_management","from":"design_decisions","relationType":"guides"}
{"type":"relation","to":"testing_infrastructure","from":"completed_work","relationType":"established"}
{"type":"relation","to":"design_decisions","from":"fileio_module","relationType":"implements"}
{"type":"relation","from":"observation_update_approaches","to":"observation_management","relationType":"analyzes"}
{"type":"relation","from":"bulk_update_approach","to":"observation_update_approaches","relationType":"is_option_of"}
{"type":"relation","from":"tracked_observations_approach","to":"observation_update_approaches","relationType":"is_option_of"}
{"type":"relation","from":"diff_based_approach","to":"observation_update_approaches","relationType":"is_option_of"}
{"type":"relation","from":"position_based_approach","to":"observation_update_approaches","relationType":"is_option_of"}
{"type":"relation","from":"tasks_and_progress","to":"basic-memory","relationType":"tracks_status_of"}
{"type":"relation","from":"design_decisions","to":"observation_update_approaches","relationType":"influences"}
{"type":"relation","from":"observation_update_approaches","to":"future_work","relationType":"informs"}
{"type":"relation","to":"basic-memory_implementation_patterns","from":"error_handling_patterns","relationType":"is_part_of"}
{"type":"relation","to":"basic-memory","from":"data_models","relationType":"implements"}
{"type":"relation","to":"basic-memory","from":"markdown_format","relationType":"defines"}
{"type":"relation","to":"basic-memory","from":"test_driven_development","relationType":"guides_development_of"}
{"type":"relation","to":"basic-memory","from":"architecture_evolution","relationType":"describes_development_of"}
{"type":"relation","to":"basic-memory_implementation_patterns","from":"validation_patterns","relationType":"is_part_of"}
{"type":"relation","to":"design_decisions","from":"architecture_evolution","relationType":"informs"}
{"type":"relation","to":"fileio_module","from":"markdown_format","relationType":"implements"}
{"type":"relation","to":"error_handling_patterns","from":"test_driven_development","relationType":"influenced"}
{"type":"relation","to":"data_models","from":"validation_patterns","relationType":"implements"}
{"type":"relation","to":"markdown_format","from":"markdown_examples","relationType":"documents"}
{"type":"relation","to":"markdown_format","from":"markdown_parsing_rules","relationType":"defines"}
{"type":"relation","to":"data_models","from":"schema_definitions","relationType":"documents"}
{"type":"relation","to":"test_driven_development","from":"test_evolution","relationType":"describes"}
{"type":"relation","to":"architecture_evolution","from":"implementation_challenges","relationType":"influenced"}
{"type":"relation","to":"test_evolution","from":"implementation_challenges","relationType":"shaped"}
{"type":"relation","to":"future_work","from":"implementation_challenges","relationType":"informs"}
{"type":"relation","from":"Basic_Factory","to":"Basic_Machines","relationType":"implements"}
{"type":"relation","from":"Basic_Factory_Components","to":"Basic_Factory","relationType":"is_part_of"}
{"type":"relation","from":"Component_Translation_Process","to":"Basic_Factory_Components","relationType":"enables"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic_Machines","relationType":"guides"}
{"type":"relation","from":"Paul","to":"Basic_Factory","relationType":"develops"}
{"type":"relation","from":"Paul","to":"Basic_Machines_Philosophy","relationType":"created"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Basic_Machines_Philosophy","relationType":"articulates"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"Basic_Factory","relationType":"guides_development_of"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Paul","relationType":"written_by"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Component_Translation_Process","relationType":"documents"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"Basic_Machines","relationType":"shapes_development_of"}
{"type":"relation","from":"Basic_Machines_Roadmap","to":"Basic_Machines","relationType":"guides_development_of"}
{"type":"relation","from":"Basic_Machines_Website","to":"Basic_Machines_Roadmap","relationType":"implements_phase_of"}
{"type":"relation","from":"Basic_Factory_Components","to":"Basic_Machines_Website","relationType":"enables"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic_Machines_Website","relationType":"informs"}
{"type":"relation","from":"Paul","to":"DIY_Ethics","relationType":"embodies"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"DIY_Ethics","relationType":"incorporates"}
{"type":"relation","from":"Basic_Machines","to":"DIY_Ethics","relationType":"exemplifies"}
{"type":"relation","from":"Component_Translation_Process","to":"Basic_Machines_Philosophy","relationType":"implements"}
{"type":"relation","from":"Basic_Factory_Components","to":"DIY_Ethics","relationType":"demonstrates"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"Basic_Machines_Philosophy","relationType":"aligns_with"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"Component_Translation_Process","relationType":"guides"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Basic_Machines","relationType":"defines_vision_for"}
{"type":"relation","from":"Basic_Machines_Website","to":"Basic_Machines_Manifesto","relationType":"implements_vision_of"}
{"type":"relation","from":"Basic_Factory","to":"AI_Human_Collaboration_Model","relationType":"demonstrates"}
{"type":"relation","from":"Paul","to":"AI_Human_Collaboration_Model","relationType":"developed_with_Claude"}
{"type":"relation","from":"Basic_Factory_Components","to":"Component_Translation_Process","relationType":"created_through"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic_Factory","relationType":"guides"}
{"type":"relation","from":"Basic_Factory","to":"MCP_Tools","relationType":"integrates"}
{"type":"relation","from":"Basic_Machines_Website","to":"Basic_Factory_Components","relationType":"will_use"}
{"type":"relation","from":"Basic_Machines_Roadmap","to":"Basic_Machines_Philosophy","relationType":"aligns_with"}
{"type":"relation","from":"Component_Translation_Process","to":"MCP_Tools","relationType":"leverages"}
{"type":"relation","from":"Basic_Factory","to":"basic-memory","relationType":"will_document_process_in"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"basic-memory","relationType":"will_be_implemented_in"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic_Machines_Roadmap","relationType":"informs_priorities_of"}
{"type":"relation","from":"basic-memory","to":"Basic_Machines_Philosophy","relationType":"embodies"}
{"type":"relation","from":"Paul","to":"Basic_Machines_Manifesto","relationType":"authored_with_Claude"}
{"type":"relation","from":"Basic_Factory","to":"Component_Translation_Process","relationType":"validated"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"MCP_Tools","relationType":"utilizes"}
{"type":"relation","from":"Basic_Factory_Components","to":"Basic_Machines_Roadmap","relationType":"supports"}
{"type":"relation","from":"Basic_Machines_Website","to":"Basic_Factory","relationType":"will_demonstrate"}
{"type":"relation","from":"Basic_Factory","to":"basic-memory-webui","relationType":"enables_development_of"}
{"type":"relation","from":"basic-memory","to":"AI_Human_Development_Methodology","relationType":"implements"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"AI_Human_Development_Methodology","relationType":"guides"}
{"type":"relation","from":"Basic_Factory_Components","to":"basic-memory-webui","relationType":"provides_ui_for"}
{"type":"relation","from":"Component_Translation_Process","to":"AI_Human_Development_Methodology","relationType":"exemplifies"}
{"type":"relation","from":"Basic_Factory","to":"Basic Components","relationType":"enabled_creation_of"}
{"type":"relation","from":"Basic_Factory","to":"Tool Integration Discovery","relationType":"led_to"}
{"type":"relation","from":"MCP_Integration_Progress","to":"AI_Human_Development_Methodology","relationType":"validates"}
{"type":"relation","from":"Basic_Factory","to":"MCP_Integration_Progress","relationType":"demonstrates"}
{"type":"relation","from":"Basic_Factory","to":"AI_Human_Development_Methodology","relationType":"proves_effectiveness_of"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic Components","relationType":"inspires_architecture_of"}
{"type":"relation","from":"DIY_Ethics","to":"basic-memory","relationType":"shapes_design_of"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Tool Integration Discovery","relationType":"guides_analysis_of"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"AI_Human_Development_Methodology","relationType":"documents_approach_of"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Basic_Factory_Components","relationType":"explains_principles_of"}
{"type":"relation","from":"Basic_Memory_Project_Structure","to":"basic-memory","relationType":"organizes"}
{"type":"relation","from":"Basic_Memory_Database_Schema","to":"basic-memory","relationType":"defines_storage_for"}
{"type":"relation","from":"Basic_Memory_Markdown_Example","to":"Basic_Memory_File_Format","relationType":"demonstrates"}
{"type":"relation","from":"Basic_Memory_Project_Isolation_Decision","to":"Basic_Memory_Future_Enhancement_Weighted_Relations","relationType":"similar_to"}
{"type":"relation","to":"DIY_Ethics","from":"Basic_Memory_Project_Isolation_Decision","relationType":"follows"}
{"type":"relation","from":"Basic_Memory_Implementation_Plan","to":"basic-memory","relationType":"guides"}
{"type":"relation","from":"Basic_Memory_Implementation_Plan","to":"DIY_Ethics","relationType":"follows"}
{"type":"relation","from":"Basic_Memory_Implementation_Plan","to":"Basic_Memory_Database_Schema","relationType":"implements"}
{"type":"relation","from":"Basic_Memory_Implementation_Status","to":"Basic_Memory_Implementation_Plan","relationType":"updates"}
{"type":"relation","from":"Basic_Memory_Observation_Management_Design","to":"Basic_Memory_Technical_Design","relationType":"extends"}
{"type":"relation","from":"Basic_Memory_Architectural_Decisions","to":"DIY_Ethics","relationType":"guided_by"}
{"type":"relation","from":"Basic_Memory_Architectural_Decisions","to":"basic-memory","relationType":"structures"}
{"type":"relation","from":"Basic_Memory_Implementation_Status","to":"basic-memory","relationType":"describes_state_of"}
{"type":"relation","to":"Basic_Memory_Implementation_Status","from":"Basic_Memory_Implementation_Analysis","relationType":"analyzes"}
{"type":"relation","to":"basic-memory","from":"Basic_Memory_Current_Challenges","relationType":"identifies_issues_in"}
{"type":"relation","to":"DIY_Ethics","from":"Basic_Memory_Implementation_Analysis","relationType":"confirms_alignment_with"}
{"type":"relation","to":"Basic_Memory_Observation_Management_Design","from":"Basic_Memory_Observation_Hash_Tracking","relationType":"solves"}
{"type":"relation","to":"DIY_Ethics","from":"Basic_Memory_Observation_Hash_Tracking","relationType":"aligns_with"}
{"type":"relation","to":"Basic_Memory_File_Format","from":"Basic_Memory_Observation_Hash_Tracking","relationType":"preserves"}
{"type":"relation","to":"Basic_Memory_Technical_Design","from":"Basic_Memory_Observation_Hash_Tracking","relationType":"enhances"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"basic-memory","relationType":"implements_part_of"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"Basic_Memory_Database_Schema","relationType":"follows"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"DIY_Ethics","relationType":"aligns_with"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"testing_infrastructure","relationType":"demonstrates"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"Basic Foundation","relationType":"inspired_by"}
{"type":"relation","from":"Basic_Memory_Dependencies","to":"basic-memory","relationType":"supports"}
{"type":"relation","from":"Basic_Memory_Dependencies","to":"Basic_Memory_Repository_Implementation","relationType":"enables"}
{"type":"relation","from":"Basic_Memory_Dependencies","to":"testing_infrastructure","relationType":"enables"}
{"type":"relation","from":"Basic_Memory_Current_Architecture","to":"basic-memory","relationType":"describes_state_of"}
{"type":"relation","from":"Basic_Memory_Evolution","to":"Basic_Memory_Current_Architecture","relationType":"explains_development_of"}
{"type":"relation","from":"Basic_Memory_Service_Layer","to":"Basic_Memory_Current_Architecture","relationType":"implements"}
{"type":"relation","from":"Basic_Memory_Schema_Design","to":"Basic_Memory_Current_Architecture","relationType":"implements"}
{"type":"relation","from":"Basic_Memory_Evolution","to":"Basic_Memory_Implementation_Plan","relationType":"reflects_on"}
{"type":"relation","from":"Basic_Memory_Evolution","to":"DIY_Ethics","relationType":"demonstrates_alignment_with"}
{"type":"relation","from":"Basic_Memory_Current_Architecture","to":"DIY_Ethics","relationType":"embodies"}
{"type":"relation","from":"Basic_Memory_Service_Layer","to":"fileio_module","relationType":"uses"}
{"type":"relation","from":"Basic_Memory_Schema_Design","to":"markdown_format","relationType":"implements"}
{"type":"relation","to":"basic-memory","from":"Basic_Memory_Next_Tasks","relationType":"guides_development_of"}
{"type":"relation","to":"DIY_Ethics","from":"Basic_Memory_Next_Tasks","relationType":"aligns_with"}
{"type":"relation","to":"Basic_Memory_Current_Architecture","from":"Basic_Memory_Next_Tasks","relationType":"extends"}
{"type":"relation","from":"Basic_Memory_Meta_Experience","to":"basic-memory","relationType":"validates_design_of"}
{"type":"relation","from":"Basic_Memory_Meta_Experience","to":"DIY_Ethics","relationType":"demonstrates_principles_of"}
{"type":"relation","from":"Basic_Memory_Meta_Experience","to":"design_decisions","relationType":"reinforces"}
{"type":"relation","from":"Basic_Memory_Meta_Experience","to":"Basic_Memory_Current_Architecture","relationType":"validates"}
{"type":"relation","from":"Model_Context_Protocol","to":"basic-memory","relationType":"enables"}
{"type":"relation","from":"basic-memory_core_principles","to":"basic-memory","relationType":"guides"}
{"type":"relation","from":"basic-memory_core_principles","to":"DIY_Ethics","relationType":"aligns_with"}
{"type":"relation","from":"basic-memory_business_model","to":"basic-memory","relationType":"defines_sustainability_for"}
{"type":"relation","from":"basic-memory_business_model","to":"DIY_Ethics","relationType":"maintains_alignment_with"}
{"type":"relation","from":"basic-memory_cli","to":"basic-memory","relationType":"provides_interface_for"}
{"type":"relation","from":"basic-memory_cli","to":"Model_Context_Protocol","relationType":"integrates_with"}
{"type":"relation","from":"basic-memory_export_format","to":"basic-memory","relationType":"standardizes_output_of"}
{"type":"relation","from":"basic-memory_export_format","to":"markdown_format","relationType":"extends"}
{"type":"relation","from":"basic-memory_core_principles","to":"Basic_Machines_Philosophy","relationType":"implements"}
{"type":"relation","from":"Model_Context_Protocol","to":"AI_Human_Collaboration_Model","relationType":"enables"}
{"type":"relation","from":"relation_service","to":"basic-memory","relationType":"will_be_component_of"}
{"type":"relation","from":"relation_service","to":"service_layer_patterns","relationType":"follows"}
{"type":"relation","from":"relation_service","to":"fileio_patterns","relationType":"uses"}
{"type":"relation","from":"relation_service","to":"database_models","relationType":"uses"}
{"type":"relation","from":"relation_service","to":"repository_patterns","relationType":"implements"}
{"type":"relation","from":"relation_service_design","to":"relation_service","relationType":"guides_implementation_of"}
{"type":"relation","from":"relation_service_implementation_plan","to":"relation_service","relationType":"defines_implementation_of"}
{"type":"relation","from":"relation_service_challenges","to":"relation_service_design","relationType":"informs"}
{"type":"relation","from":"relation_file_format","to":"markdown_format","relationType":"extends"}
{"type":"relation","from":"relation_service_error_handling","to":"service_layer_patterns","relationType":"implements"}
{"type":"relation","from":"relation_service_testing","to":"testing_infrastructure","relationType":"extends"}
{"type":"relation","from":"fileio_patterns","to":"service_layer_patterns","relationType":"enables"}
{"type":"relation","from":"database_models","to":"repository_patterns","relationType":"enables"}
{"type":"relation","from":"relation_service","to":"entity_service","relationType":"coordinates_with"}
{"type":"relation","from":"relation_file_format","to":"relation_service","relationType":"defines_storage_for"}
{"type":"relation","from":"relation_service_error_handling","to":"relation_service","relationType":"ensures_reliability_of"}
{"type":"relation","from":"relation_service_testing","to":"relation_service","relationType":"verifies"}
{"type":"relation","from":"service_layer_patterns","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"repository_patterns","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"fileio_patterns","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"database_models","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"relation_service_challenges","to":"implementation_challenges","relationType":"extends"}
{"type":"relation","from":"relation_service_implementation_plan","to":"future_work","relationType":"details"}
{"type":"relation","from":"relation_service_design","to":"design_decisions","relationType":"aligns_with"}
{"type":"relation","from":"relation_file_format","to":"design_decisions","relationType":"follows"}
{"type":"relation","from":"pytest_patterns","to":"testing_infrastructure","relationType":"extends"}
{"type":"relation","from":"relation_implementation_learnings","to":"basic-memory_implementation_patterns","relationType":"informs"}
{"type":"relation","from":"test_driven_insights","to":"test_driven_development","relationType":"enriches"}
{"type":"relation","from":"meta_development_insights","to":"AI_Human_Collaboration_Model","relationType":"improves"}
{"type":"relation","from":"relation_implementation_learnings","to":"relation_service","relationType":"guides_implementation_of"}
{"type":"relation","from":"pytest_patterns","to":"test_evolution","relationType":"demonstrates"}
{"type":"relation","from":"test_driven_insights","to":"design_decisions","relationType":"influences"}
{"type":"relation","from":"meta_development_insights","to":"architecture_evolution","relationType":"informs"}
{"type":"relation","from":"relation_service","to":"relation_implementation_learnings","relationType":"validates"}
{"type":"relation","from":"test_driven_insights","to":"implementation_challenges","relationType":"helps_solve"}
{"type":"relation","from":"AI_Assistant_Learnings","to":"meta_development_insights","relationType":"enriches"}
{"type":"relation","from":"Effective_Response_Patterns","to":"AI_Assistant_Learnings","relationType":"implements"}
{"type":"relation","from":"AI_Context_Management","to":"AI_Human_Collaboration_Model","relationType":"improves"}
{"type":"relation","from":"AI_Tool_Usage_Patterns","to":"AI_Context_Management","relationType":"enables"}
{"type":"relation","from":"AI_Assistant_Learnings","to":"Basic_Memory_Meta_Experience","relationType":"validates"}
{"type":"relation","from":"AI_Tool_Usage_Patterns","to":"Model_Context_Protocol","relationType":"demonstrates_effective_use_of"}
{"type":"relation","from":"AI_Context_Management","to":"basic-memory","relationType":"validates_design_of"}
{"type":"relation","from":"Effective_Response_Patterns","to":"AI_Human_Development_Methodology","relationType":"refines"}
{"type":"relation","to":"relation_service","from":"relation_service_patterns","relationType":"guides"}
{"type":"relation","to":"test_driven_development","from":"test_driven_insights_relations","relationType":"enriches"}
{"type":"relation","to":"implementation_challenges","from":"relation_service_learnings","relationType":"solves"}
{"type":"relation","to":"basic-memory_implementation_patterns","from":"relation_service_patterns","relationType":"implements"}
{"type":"relation","to":"markdown_format","from":"relation_service_patterns","relationType":"extends"}
{"type":"relation","to":"service_layer_patterns","from":"relation_service_patterns","relationType":"refines"}
{"type":"relation","from":"packaging_learnings","to":"implementation_challenges","relationType":"informs"}
{"type":"relation","from":"packaging_learnings","to":"test_driven_development","relationType":"impacts"}
{"type":"relation","to":"basic-memory","from":"Recent_Implementation_Progress","relationType":"updates_status_of"}
{"type":"relation","to":"future_work","from":"Next_Steps","relationType":"extends"}
{"type":"relation","to":"design_decisions","from":"Development_Practices","relationType":"informs"}
{"type":"relation","to":"packaging_learnings","from":"Development_Practices","relationType":"incorporates"}
{"type":"relation","to":"test_driven_development","from":"Development_Practices","relationType":"refines"}
{"type":"relation","to":"basic-memory_implementation_patterns","from":"Development_Practices","relationType":"enhances"}
{"type":"relation","from":"Basic_Memory_MCP","to":"MCP_Server_Implementation","relationType":"follows"}
{"type":"relation","from":"Basic_Memory_MCP","to":"MCP_Tools","relationType":"uses"}
{"type":"relation","from":"Basic_Memory","to":"MCP_Server_Implementation","relationType":"implements"}
{"type":"relation","from":"Basic_Memory_Testing","to":"Memory_Service_Tests","relationType":"includes"}
{"type":"relation","from":"Basic_Memory_Testing","to":"MCP_Server_Tests","relationType":"includes"}
{"type":"relation","from":"Memory_Service_Tests","to":"Basic_Memory_MCP","relationType":"validates"}
{"type":"relation","from":"MCP_Server_Tests","to":"Basic_Memory_MCP","relationType":"validates"}
{"type":"relation","from":"Service_Interface_Audit","to":"Memory_Service_Refactoring","relationType":"informs"}
{"type":"relation","from":"Memory_Service_Refactoring","to":"Basic_Memory_MCP","relationType":"affects"}
{"type":"relation","from":"Memory_Service_Patterns","to":"Basic_Memory_MCP","relationType":"improves"}
{"type":"relation","from":"Pydantic_Create_Pattern","to":"Memory_Service_Patterns","relationType":"enables"}
{"type":"relation","from":"Pydantic_Create_Pattern","to":"Basic_Memory_MCP","relationType":"improves"}
{"type":"relation","from":"MCP_Marketplace","to":"Basic_Memory_Business","relationType":"enables"}
{"type":"relation","from":"Basic_Memory","to":"MCP_Marketplace","relationType":"could_integrate_with"}
{"type":"relation","from":"Persistence_Of_Vision","to":"Basic_Memory","relationType":"helps_achieve"}
{"type":"relation","from":"Drew","to":"Persistence_Of_Vision","relationType":"conceptualized"}
{"type":"relation","to":"Usage_Recipes","from":"Conversation_Continuity_Pattern","relationType":"is_example_of"}
{"type":"relation","to":"Basic_Memory","from":"Usage_Recipes","relationType":"enhances"}
{"type":"relation","to":"Basic_Memory","from":"Chat_References","relationType":"enhances"}
{"type":"relation","to":"Conversation_Continuity_Pattern","from":"Chat_References","relationType":"implements"}
{"type":"relation","from":"20240307-chat-reference-protocol-test","to":"20240307-chat-reference-protocol","relationType":"continues_from"}
{"type":"relation","from_id":"Run_Tests_Tool_Request","to_id":"Basic_Machines","relationType":"enhances","context":"development workflow improvement"}
{"type":"relation","from_id":"SQLAlchemy_Async_Loading_Pattern","to_id":"basic-memory","relation_type":"improves","context":"database performance and async compatibility"}
{"type":"relation","from_id":"SQLAlchemy_Async_Loading_Pattern","to_id":"Entity","relation_type":"applies_to","context":"relationship loading strategy"}
{"type":"relation","from":"MCP_Reference_Integration","to":"Project_Priorities","relationType":"prioritized_after"}
{"type":"relation","from":"great_observation_loading_saga_20241207","to":"Basic_Memory","relationType":"occurred_in"}
{"type":"relation","from":"great_observation_loading_saga_20241207","to":"SQLAlchemy","relationType":"relates_to"}
{"type":"relation","from":"basic_memory_implementation_20241208","to":"Basic_Memory","relationType":"improves"}
{"type":"relation","from":"great_observation_loading_saga_20241207","to":"basic_memory_implementation_20241208","relationType":"leads_to"}
{"type":"relation","from":"MCP_Dependency_Risk","to":"DIY_Ethics","relationType":"validates"}
{"type":"relation","from":"MCP_Dependency_Risk","to":"basic-memory_core_principles","relationType":"reinforces"}
{"type":"relation","from":"MCP_Dependency_Risk","to":"Basic_Memory_Implementation_Plan","relationType":"influences"}
{"type":"relation","from":"basic_memory_mcp_architecture","to":"basic_memory_project_20241208","relationType":"implements"}
{"type":"relation","from":"basic_memory_sync_considerations","to":"basic_memory_project_20241208","relationType":"influences"}
{"type":"relation","from":"mcp_server_learnings","to":"basic_memory_mcp_architecture","relationType":"informs"}
{"type":"relation","from":"20241208-mcp-tool-refactoring","to":"Basic_Memory_MCP","relationType":"improves"}
{"type":"relation","from":"20241208-mcp-tool-refactoring","to":"Basic_Memory_Implementation_Plan","relationType":"implements"}
+1 -1
View File
@@ -1,7 +1,7 @@
"""basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
# Package version - updated by release automation
__version__ = "0.13.7"
__version__ = "0.14.0"
# API version for FastAPI - independent of package version
__api_version__ = "v0"
+1 -1
View File
@@ -52,7 +52,7 @@ async def to_graph_context(
file_path=item.file_path,
permalink=item.permalink, # pyright: ignore
relation_type=item.relation_type, # pyright: ignore
from_entity=from_entity.title, # pyright: ignore
from_entity=from_entity.title if from_entity else None,
to_entity=to_entity.title if to_entity else None,
created_at=item.created_at,
)
-1
View File
@@ -78,7 +78,6 @@ def mcp(
if transport == "stdio":
mcp_server.run(
transport=transport,
log_level="INFO",
)
elif transport == "streamable-http" or transport == "sse":
mcp_server.run(
+6 -2
View File
@@ -45,7 +45,9 @@ class BasicMemoryConfig(BaseSettings):
env: Environment = Field(default="dev", description="Environment name")
projects: Dict[str, str] = Field(
default_factory=lambda: {"main": str(Path.home() / "basic-memory")},
default_factory=lambda: {
"main": str(Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory")))
},
description="Mapping of project names to their filesystem paths",
)
default_project: str = Field(
@@ -92,7 +94,9 @@ class BasicMemoryConfig(BaseSettings):
"""Ensure configuration is valid after initialization."""
# Ensure main project exists
if "main" not in self.projects: # pragma: no cover
self.projects["main"] = str(Path.home() / "basic-memory")
self.projects["main"] = str(
Path(os.getenv("BASIC_MEMORY_HOME", Path.home() / "basic-memory"))
)
# Ensure default project is valid
if self.default_project not in self.projects: # pragma: no cover
+3 -1
View File
@@ -38,7 +38,9 @@ def entity_model_from_markdown(
# Update basic fields
model.title = markdown.frontmatter.title
model.entity_type = markdown.frontmatter.type
model.permalink = markdown.frontmatter.permalink
# Only update permalink if it exists in frontmatter, otherwise preserve existing
if markdown.frontmatter.permalink is not None:
model.permalink = markdown.frontmatter.permalink
model.file_path = str(file_path)
model.content_type = "text/markdown"
model.created_at = markdown.created
+6 -3
View File
@@ -82,10 +82,15 @@ async def build_context(
logger.info(f"Building context from {url}")
# URL is already validated and normalized by MemoryUrl type annotation
# Get the active project first to check project-specific sync status
active_project = get_active_project(project)
# Check migration status and wait briefly if needed
from basic_memory.mcp.tools.utils import wait_for_migration_or_return_status
migration_status = await wait_for_migration_or_return_status(timeout=5.0)
migration_status = await wait_for_migration_or_return_status(
timeout=5.0, project_name=active_project.name
)
if migration_status: # pragma: no cover
# Return a proper GraphContext with status message
from basic_memory.schemas.memory import MemoryMetadata
@@ -102,8 +107,6 @@ async def build_context(
uri=migration_status, # Include status in metadata
),
)
active_project = get_active_project(project)
project_url = active_project.project_url
response = await call_get(
+155 -1
View File
@@ -7,9 +7,155 @@ from loguru import logger
from basic_memory.mcp.async_client import client
from basic_memory.mcp.server import mcp
from basic_memory.mcp.tools.utils import call_post
from basic_memory.mcp.tools.utils import call_post, call_get
from basic_memory.mcp.project_session import get_active_project
from basic_memory.schemas import EntityResponse
from basic_memory.schemas.project_info import ProjectList
async def _detect_cross_project_move_attempt(
identifier: str, destination_path: str, current_project: str
) -> Optional[str]:
"""Detect potential cross-project move attempts and return guidance.
Args:
identifier: The note identifier being moved
destination_path: The destination path
current_project: The current active project
Returns:
Error message with guidance if cross-project move is detected, None otherwise
"""
try:
# Get list of all available projects to check against
response = await call_get(client, "/projects/projects")
project_list = ProjectList.model_validate(response.json())
project_names = [p.name.lower() for p in project_list.projects]
# Check if destination path contains any project names
dest_lower = destination_path.lower()
path_parts = dest_lower.split("/")
# Look for project names in the destination path
for part in path_parts:
if part in project_names and part != current_project.lower():
# Found a different project name in the path
matching_project = next(
p.name for p in project_list.projects if p.name.lower() == part
)
return _format_cross_project_error_response(
identifier, destination_path, current_project, matching_project
)
# Check if the destination path looks like it might be trying to reference another project
# (e.g., contains common project-like patterns)
if any(keyword in dest_lower for keyword in ["project", "workspace", "repo"]):
# This might be a cross-project attempt, but we can't be sure
# Return a general guidance message
available_projects = [
p.name for p in project_list.projects if p.name != current_project
]
if available_projects:
return _format_potential_cross_project_guidance(
identifier, destination_path, current_project, available_projects
)
except Exception as e:
# If we can't detect, don't interfere with normal error handling
logger.debug(f"Could not check for cross-project move: {e}")
return None
return None
def _format_cross_project_error_response(
identifier: str, destination_path: str, current_project: str, target_project: str
) -> str:
"""Format error response for detected cross-project move attempts."""
return dedent(f"""
# Move Failed - Cross-Project Move Not Supported
Cannot move '{identifier}' to '{destination_path}' because it appears to reference a different project ('{target_project}').
**Current project:** {current_project}
**Target project:** {target_project}
## Cross-project moves are not supported directly
Notes can only be moved within the same project. To move content between projects, use this workflow:
### Recommended approach:
```
# 1. Read the note content from current project
read_note("{identifier}")
# 2. Switch to the target project
switch_project("{target_project}")
# 3. Create the note in the target project
write_note("Note Title", "content from step 1", "target-folder")
# 4. Switch back to original project (optional)
switch_project("{current_project}")
# 5. Delete the original note if desired
delete_note("{identifier}")
```
### Alternative: Stay in current project
If you want to move the note within the **{current_project}** project only:
```
move_note("{identifier}", "new-folder/new-name.md")
```
## Available projects:
Use `list_projects()` to see all available projects and `switch_project("project-name")` to change projects.
""").strip()
def _format_potential_cross_project_guidance(
identifier: str, destination_path: str, current_project: str, available_projects: list[str]
) -> str:
"""Format guidance for potentially cross-project moves."""
other_projects = ", ".join(available_projects[:3]) # Show first 3 projects
if len(available_projects) > 3:
other_projects += f" (and {len(available_projects) - 3} others)"
return dedent(f"""
# Move Failed - Check Project Context
Cannot move '{identifier}' to '{destination_path}' within the current project '{current_project}'.
## If you intended to move within the current project:
The destination path should be relative to the project root:
```
move_note("{identifier}", "folder/filename.md")
```
## If you intended to move to a different project:
Cross-project moves require switching projects first. Available projects: {other_projects}
### To move to another project:
```
# 1. Read the content
read_note("{identifier}")
# 2. Switch to target project
switch_project("target-project-name")
# 3. Create note in target project
write_note("Title", "content", "folder")
# 4. Switch back and delete original if desired
switch_project("{current_project}")
delete_note("{identifier}")
```
### To see all projects:
```
list_projects()
```
""").strip()
def _format_move_error_response(error_message: str, identifier: str, destination_path: str) -> str:
@@ -258,6 +404,14 @@ async def move_note(
active_project = get_active_project(project)
project_url = active_project.project_url
# Check for potential cross-project move attempts
cross_project_error = await _detect_cross_project_move_attempt(
identifier, destination_path, active_project.name
)
if cross_project_error:
logger.info(f"Detected cross-project move attempt: {identifier} -> {destination_path}")
return cross_project_error
try:
# Prepare move request
move_data = {
+6 -3
View File
@@ -52,14 +52,17 @@ async def read_note(
read_note("Meeting Notes", project="work-project")
"""
# Get the active project first to check project-specific sync status
active_project = get_active_project(project)
# Check migration status and wait briefly if needed
from basic_memory.mcp.tools.utils import wait_for_migration_or_return_status
migration_status = await wait_for_migration_or_return_status(timeout=5.0)
migration_status = await wait_for_migration_or_return_status(
timeout=5.0, project_name=active_project.name
)
if migration_status: # pragma: no cover
return f"# System Status\n\n{migration_status}\n\nPlease wait for migration to complete before reading notes."
active_project = get_active_project(project)
project_url = active_project.project_url
# Get the file via REST API - first try direct permalink lookup
+111 -34
View File
@@ -45,13 +45,18 @@ def _format_search_error_response(error_message: str, query: str, search_type: s
- Boolean OR: `meeting OR discussion`
- Boolean NOT: `project NOT archived`
- Grouped: `(project OR planning) AND notes`
- Exact phrases: `"weekly standup meeting"`
- Content-specific: `tag:example` or `category:observation`
## Try again with:
```
search_notes("INSERT_CLEAN_QUERY_HERE")
search_notes("{clean_query}")
```
Replace INSERT_CLEAN_QUERY_HERE with your simplified search terms.
## Alternative search strategies:
- Break into simpler terms: `search_notes("{' '.join(clean_query.split()[:2])}")`
- Try different search types: `search_notes("{clean_query}", search_type="title")`
- Use filtering: `search_notes("{clean_query}", types=["entity"])`
""").strip()
# Project not found errors (check before general "not found")
@@ -85,24 +90,39 @@ def _format_search_error_response(error_message: str, query: str, search_type: s
No content found matching '{query}' in the current project.
## Suggestions to try:
## Search strategy suggestions:
1. **Broaden your search**: Try fewer or more general terms
- Instead of: `{query}`
- Try: `{simplified_query}`
2. **Check spelling**: Verify terms are spelled correctly
3. **Try different search types**:
- Text search: `search_notes("{query}", search_type="text")`
- Title search: `search_notes("{query}", search_type="title")`
- Permalink search: `search_notes("{query}", search_type="permalink")`
2. **Check spelling and try variations**:
- Verify terms are spelled correctly
- Try synonyms or related terms
4. **Use boolean operators**:
- Try OR search for broader results
3. **Use different search approaches**:
- **Text search**: `search_notes("{query}", search_type="text")` (searches full content)
- **Title search**: `search_notes("{query}", search_type="title")` (searches only titles)
- **Permalink search**: `search_notes("{query}", search_type="permalink")` (searches file paths)
## Check what content exists:
- Recent activity: `recent_activity(timeframe="7d")`
- List files: `list_directory("/")`
- Browse by folder: `list_directory("/notes")` or `list_directory("/docs")`
4. **Try boolean operators for broader results**:
- OR search: `search_notes("{' OR '.join(query.split()[:3])}")`
- Remove restrictive terms: Focus on the most important keywords
5. **Use filtering to narrow scope**:
- By content type: `search_notes("{query}", types=["entity"])`
- By recent content: `search_notes("{query}", after_date="1 week")`
- By entity type: `search_notes("{query}", entity_types=["observation"])`
6. **Try advanced search patterns**:
- Tag search: `search_notes("tag:your-tag")`
- Category search: `search_notes("category:observation")`
- Pattern matching: `search_notes("*{query}*", search_type="permalink")`
## Explore what content exists:
- **Recent activity**: `recent_activity(timeframe="7d")` - See what's been updated recently
- **List directories**: `list_directory("/")` - Browse all content
- **Browse by folder**: `list_directory("/notes")` or `list_directory("/docs")`
- **Check project**: `get_current_project()` - Verify you're in the right project
""").strip()
# Server/API errors
@@ -151,25 +171,36 @@ You don't have permission to search in the current project: {error_message}
Error searching for '{query}': {error_message}
## General troubleshooting:
1. **Check your query**: Ensure it uses valid search syntax
2. **Try simpler terms**: Use basic words without special characters
## Troubleshooting steps:
1. **Simplify your query**: Try basic words without special characters
2. **Check search syntax**: Ensure boolean operators are correctly formatted
3. **Verify project access**: Make sure you can access the current project
4. **Check recent activity**: `recent_activity(timeframe="7d")` to see if content exists
4. **Test with simple search**: Try `search_notes("test")` to verify search is working
## Alternative approaches:
- Browse files: `list_directory("/")`
- Try different search type: `search_notes("{query}", search_type="title")`
- Search with filters: `search_notes("{query}", types=["entity"])`
## Alternative search approaches:
- **Different search types**:
- Title only: `search_notes("{query}", search_type="title")`
- Permalink patterns: `search_notes("{query}*", search_type="permalink")`
- **With filters**: `search_notes("{query}", types=["entity"])`
- **Recent content**: `search_notes("{query}", after_date="1 week")`
- **Boolean variations**: `search_notes("{' OR '.join(query.split()[:2])}")`
## Need help?
- View recent changes: `recent_activity()`
- List projects: `list_projects()`
- Check current project: `get_current_project()`"""
## Explore your content:
- **Browse files**: `list_directory("/")` - See all available content
- **Recent activity**: `recent_activity(timeframe="7d")` - Check what's been updated
- **Project info**: `get_current_project()` - Verify current project
- **All projects**: `list_projects()` - Switch to different project if needed
## Search syntax reference:
- **Basic**: `keyword` or `multiple words`
- **Boolean**: `term1 AND term2`, `term1 OR term2`, `term1 NOT term2`
- **Phrases**: `"exact phrase"`
- **Grouping**: `(term1 OR term2) AND term3`
- **Patterns**: `tag:example`, `category:observation`"""
@mcp.tool(
description="Search across all content in the knowledge base.",
description="Search across all content in the knowledge base with advanced syntax support.",
)
async def search_notes(
query: str,
@@ -181,24 +212,60 @@ async def search_notes(
after_date: Optional[str] = None,
project: Optional[str] = None,
) -> SearchResponse | str:
"""Search across all content in the knowledge base.
"""Search across all content in the knowledge base with comprehensive syntax support.
This tool searches the knowledge base using full-text search, pattern matching,
or exact permalink lookup. It supports filtering by content type, entity type,
and date.
and date, with advanced boolean and phrase search capabilities.
## Search Syntax Examples
### Basic Searches
- `search_notes("keyword")` - Find any content containing "keyword"
- `search_notes("exact phrase")` - Search for exact phrase match
### Advanced Boolean Searches
- `search_notes("term1 term2")` - Find content with both terms (implicit AND)
- `search_notes("term1 AND term2")` - Explicit AND search (both terms required)
- `search_notes("term1 OR term2")` - Either term can be present
- `search_notes("term1 NOT term2")` - Include term1 but exclude term2
- `search_notes("(project OR planning) AND notes")` - Grouped boolean logic
### Content-Specific Searches
- `search_notes("tag:example")` - Search within specific tags (if supported by content)
- `search_notes("category:observation")` - Filter by observation categories
- `search_notes("author:username")` - Find content by author (if metadata available)
### Search Type Examples
- `search_notes("Meeting", search_type="title")` - Search only in titles
- `search_notes("docs/meeting-*", search_type="permalink")` - Pattern match permalinks
- `search_notes("keyword", search_type="text")` - Full-text search (default)
### Filtering Options
- `search_notes("query", types=["entity"])` - Search only entities
- `search_notes("query", types=["note", "person"])` - Multiple content types
- `search_notes("query", entity_types=["observation"])` - Filter by entity type
- `search_notes("query", after_date="2024-01-01")` - Recent content only
- `search_notes("query", after_date="1 week")` - Relative date filtering
### Advanced Pattern Examples
- `search_notes("project AND (meeting OR discussion)")` - Complex boolean logic
- `search_notes("\"exact phrase\" AND keyword")` - Combine phrase and keyword search
- `search_notes("bug NOT fixed")` - Exclude resolved issues
- `search_notes("docs/2024-*", search_type="permalink")` - Year-based permalink search
Args:
query: The search query string
query: The search query string (supports boolean operators, phrases, patterns)
page: The page number of results to return (default 1)
page_size: The number of results to return per page (default 10)
search_type: Type of search to perform, one of: "text", "title", "permalink" (default: "text")
types: Optional list of note types to search (e.g., ["note", "person"])
entity_types: Optional list of entity types to filter by (e.g., ["entity", "observation"])
after_date: Optional date filter for recent content (e.g., "1 week", "2d")
after_date: Optional date filter for recent content (e.g., "1 week", "2d", "2024-01-01")
project: Optional project name to search in. If not provided, uses current active project.
Returns:
SearchResponse with results and pagination info
SearchResponse with results and pagination info, or helpful error guidance if search fails
Examples:
# Basic text search
@@ -216,16 +283,19 @@ async def search_notes(
# Boolean search with grouping
results = await search_notes("(project OR planning) AND notes")
# Exact phrase search
results = await search_notes("\"weekly standup meeting\"")
# Search with type filter
results = await search_notes(
query="meeting notes",
types=["entity"],
)
# Search with entity type filter, e.g., note vs
# Search with entity type filter
results = await search_notes(
query="meeting notes",
types=["entity"],
entity_types=["observation"],
)
# Search for recent content
@@ -242,6 +312,13 @@ async def search_notes(
# Search in specific project
results = await search_notes("meeting notes", project="work-project")
# Complex search with multiple filters
results = await search_notes(
query="(bug OR issue) AND NOT resolved",
types=["entity"],
after_date="2024-01-01"
)
"""
# Create a SearchQuery object based on the parameters
search_query = SearchQuery()
+27 -4
View File
@@ -525,11 +525,16 @@ def check_migration_status() -> Optional[str]:
return None
async def wait_for_migration_or_return_status(timeout: float = 5.0) -> Optional[str]:
async def wait_for_migration_or_return_status(
timeout: float = 5.0, project_name: Optional[str] = None
) -> Optional[str]:
"""Wait briefly for sync/migration to complete, or return status message.
Args:
timeout: Maximum time to wait for sync completion
project_name: Optional project name to check specific project status.
If provided, only checks that project's readiness.
If None, uses global status check (legacy behavior).
Returns:
Status message if sync is still in progress, None if ready
@@ -538,18 +543,36 @@ async def wait_for_migration_or_return_status(timeout: float = 5.0) -> Optional[
from basic_memory.services.sync_status_service import sync_status_tracker
import asyncio
if sync_status_tracker.is_ready:
# Check if we should use project-specific or global status
def is_ready() -> bool:
if project_name:
return sync_status_tracker.is_project_ready(project_name)
return sync_status_tracker.is_ready
if is_ready():
return None
# Wait briefly for sync to complete
start_time = asyncio.get_event_loop().time()
while (asyncio.get_event_loop().time() - start_time) < timeout:
if sync_status_tracker.is_ready:
if is_ready():
return None
await asyncio.sleep(0.1) # Check every 100ms
# Still not ready after timeout
return sync_status_tracker.get_summary()
if project_name:
# For project-specific checks, get project status details
project_status = sync_status_tracker.get_project_status(project_name)
if project_status and project_status.status.value == "failed":
error_msg = project_status.error or "Unknown sync error"
return f"❌ Sync failed for project '{project_name}': {error_msg}"
elif project_status:
return f"🔄 Project '{project_name}' is still syncing: {project_status.message}"
else:
return f"⚠️ Project '{project_name}' status unknown"
else:
# Fall back to global summary for legacy calls
return sync_status_tracker.get_summary()
except Exception: # pragma: no cover
# If there's any error, assume ready
return None
+6 -2
View File
@@ -72,10 +72,15 @@ async def write_note(
"""
logger.info(f"MCP tool call tool=write_note folder={folder}, title={title}, tags={tags}")
# Get the active project first to check project-specific sync status
active_project = get_active_project(project)
# Check migration status and wait briefly if needed
from basic_memory.mcp.tools.utils import wait_for_migration_or_return_status
migration_status = await wait_for_migration_or_return_status(timeout=5.0)
migration_status = await wait_for_migration_or_return_status(
timeout=5.0, project_name=active_project.name
)
if migration_status: # pragma: no cover
return f"# System Status\n\n{migration_status}\n\nPlease wait for migration to complete before creating notes."
@@ -91,7 +96,6 @@ async def write_note(
content=content,
entity_metadata=metadata,
)
active_project = get_active_project(project)
project_url = active_project.project_url
# Create or update via knowledge API
@@ -142,7 +142,7 @@ class EntityRepository(Repository[Entity]):
await session.flush()
# Return with relationships loaded
query = (
select(Entity)
self.select()
.where(Entity.file_path == entity.file_path)
.options(*self.get_load_options())
)
@@ -162,7 +162,7 @@ class EntityRepository(Repository[Entity]):
# Return with relationships loaded
query = (
select(Entity)
self.select()
.where(Entity.file_path == entity.file_path)
.options(*self.get_load_options())
)
@@ -203,7 +203,7 @@ class EntityRepository(Repository[Entity]):
await session.flush()
# Return the updated entity with relationships loaded
query = (
select(Entity)
self.select()
.where(Entity.file_path == entity.file_path)
.options(*self.get_load_options())
)
@@ -243,7 +243,7 @@ class EntityRepository(Repository[Entity]):
# Return the inserted entity with relationships loaded
query = (
select(Entity)
self.select()
.where(Entity.file_path == entity.file_path)
.options(*self.get_load_options())
)
+153 -23
View File
@@ -1,6 +1,7 @@
"""Repository for search operations."""
import json
import re
import time
from dataclasses import dataclass
from datetime import datetime
@@ -120,23 +121,141 @@ class SearchRepository:
logger.error(f"Error initializing search index: {e}")
raise e
def _prepare_search_term(self, term: str, is_prefix: bool = True) -> str:
"""Prepare a search term for FTS5 query.
def _prepare_boolean_query(self, query: str) -> str:
"""Prepare a Boolean query by quoting individual terms while preserving operators.
Args:
term: The search term to prepare
query: A Boolean query like "tier1-test AND unicode" or "(hello OR world) NOT test"
Returns:
A properly formatted Boolean query with quoted terms that need quoting
"""
# Define Boolean operators and their boundaries
boolean_pattern = r"(\bAND\b|\bOR\b|\bNOT\b)"
# Split the query by Boolean operators, keeping the operators
parts = re.split(boolean_pattern, query)
processed_parts = []
for part in parts:
part = part.strip()
if not part:
continue
# If it's a Boolean operator, keep it as is
if part in ["AND", "OR", "NOT"]:
processed_parts.append(part)
else:
# Handle parentheses specially - they should be preserved for grouping
if "(" in part or ")" in part:
# Parse parenthetical expressions carefully
processed_part = self._prepare_parenthetical_term(part)
processed_parts.append(processed_part)
else:
# This is a search term - for Boolean queries, don't add prefix wildcards
prepared_term = self._prepare_single_term(part, is_prefix=False)
processed_parts.append(prepared_term)
return " ".join(processed_parts)
def _prepare_parenthetical_term(self, term: str) -> str:
"""Prepare a term that contains parentheses, preserving the parentheses for grouping.
Args:
term: A term that may contain parentheses like "(hello" or "world)" or "(hello OR world)"
Returns:
A properly formatted term with parentheses preserved
"""
# Handle terms that start/end with parentheses but may contain quotable content
result = ""
i = 0
while i < len(term):
if term[i] in "()":
# Preserve parentheses as-is
result += term[i]
i += 1
else:
# Find the next parenthesis or end of string
start = i
while i < len(term) and term[i] not in "()":
i += 1
# Extract the content between parentheses
content = term[start:i].strip()
if content:
# Only quote if it actually needs quoting (has hyphens, special chars, etc)
# but don't quote if it's just simple words
if self._needs_quoting(content):
escaped_content = content.replace('"', '""')
result += f'"{escaped_content}"'
else:
result += content
return result
def _needs_quoting(self, term: str) -> bool:
"""Check if a term needs to be quoted for FTS5 safety.
Args:
term: The term to check
Returns:
True if the term should be quoted
"""
if not term or not term.strip():
return False
# Characters that indicate we should quote (excluding parentheses which are valid syntax)
needs_quoting_chars = [
" ",
".",
":",
";",
",",
"<",
">",
"?",
"/",
"-",
"'",
'"',
"[",
"]",
"{",
"}",
"+",
"!",
"@",
"#",
"$",
"%",
"^",
"&",
"=",
"|",
"\\",
"~",
"`",
]
return any(c in term for c in needs_quoting_chars)
def _prepare_single_term(self, term: str, is_prefix: bool = True) -> str:
"""Prepare a single search term (no Boolean operators).
Args:
term: A single search term
is_prefix: Whether to add prefix search capability (* suffix)
For FTS5:
- Boolean operators (AND, OR, NOT) are preserved for complex queries
- Terms with FTS5 special characters are quoted to prevent syntax errors
- Simple terms get prefix wildcards for better matching
Returns:
A properly formatted single term
"""
# Check for explicit boolean operators - if present, return the term as is
boolean_operators = [" AND ", " OR ", " NOT "]
if any(op in f" {term} " for op in boolean_operators):
if not term or not term.strip():
return term
term = term.strip()
# Check if term is already a proper wildcard pattern (alphanumeric + *)
# e.g., "hello*", "test*world" - these should be left alone
if "*" in term and all(c.isalnum() or c in "*_-" for c in term):
@@ -218,6 +337,26 @@ class SearchRepository:
return term
def _prepare_search_term(self, term: str, is_prefix: bool = True) -> str:
"""Prepare a search term for FTS5 query.
Args:
term: The search term to prepare
is_prefix: Whether to add prefix search capability (* suffix)
For FTS5:
- Boolean operators (AND, OR, NOT) are preserved for complex queries
- Terms with FTS5 special characters are quoted to prevent syntax errors
- Simple terms get prefix wildcards for better matching
"""
# Check for explicit boolean operators - if present, process as Boolean query
boolean_operators = [" AND ", " OR ", " NOT "]
if any(op in f" {term} " for op in boolean_operators):
return self._prepare_boolean_query(term)
# For non-Boolean queries, use the single term preparation logic
return self._prepare_single_term(term, is_prefix)
async def search(
self,
search_text: Optional[str] = None,
@@ -242,19 +381,10 @@ class SearchRepository:
# For wildcard searches, don't add any text conditions - return all results
pass
else:
# Check for explicit boolean operators - only detect them in proper boolean contexts
has_boolean = any(op in f" {search_text} " for op in [" AND ", " OR ", " NOT "])
if has_boolean:
# If boolean operators are present, use the raw query
# No need to prepare it, FTS5 will understand the operators
params["text"] = search_text
conditions.append("(title MATCH :text OR content_stems MATCH :text)")
else:
# Standard search with term preparation
processed_text = self._prepare_search_term(search_text.strip())
params["text"] = processed_text
conditions.append("(title MATCH :text OR content_stems MATCH :text)")
# Use _prepare_search_term to handle both Boolean and non-Boolean queries
processed_text = self._prepare_search_term(search_text.strip())
params["text"] = processed_text
conditions.append("(title MATCH :text OR content_stems MATCH :text)")
# Handle title match search
if title:
+1 -1
View File
@@ -134,7 +134,7 @@ class RelationSummary(BaseModel):
file_path: str
permalink: str
relation_type: str
from_entity: str
from_entity: Optional[str] = None
to_entity: Optional[str] = None
created_at: datetime
+1 -1
View File
@@ -131,7 +131,7 @@ class EntityResponse(SQLAlchemyModel):
}
"""
permalink: Permalink
permalink: Optional[Permalink]
title: str
file_path: str
entity_type: EntityType
+8 -3
View File
@@ -682,8 +682,8 @@ class EntityService(BaseService[EntityModel]):
# 6. Prepare database updates
updates = {"file_path": destination_path}
# 7. Update permalink if configured
if app_config.update_permalinks_on_move:
# 7. Update permalink if configured or if entity has null permalink
if app_config.update_permalinks_on_move or old_permalink is None:
# Generate new permalink from destination path
new_permalink = await self.resolve_permalink(destination_path)
@@ -693,7 +693,12 @@ class EntityService(BaseService[EntityModel]):
)
updates["permalink"] = new_permalink
logger.info(f"Updated permalink: {old_permalink} -> {new_permalink}")
if old_permalink is None:
logger.info(
f"Generated permalink for entity with null permalink: {new_permalink}"
)
else:
logger.info(f"Updated permalink: {old_permalink} -> {new_permalink}")
# 8. Recalculate checksum
new_checksum = await self.file_service.compute_checksum(destination_path)
@@ -131,6 +131,23 @@ class SyncStatusTracker:
"""Check if system is ready (no sync in progress)."""
return self._global_status in (SyncStatus.IDLE, SyncStatus.COMPLETED)
def is_project_ready(self, project_name: str) -> bool:
"""Check if a specific project is ready for operations.
Args:
project_name: Name of the project to check
Returns:
True if the project is ready (completed, watching, or not tracked),
False if the project is syncing, scanning, or failed
"""
project_status = self._project_statuses.get(project_name)
if not project_status:
# Project not tracked = ready (likely hasn't been synced yet)
return True
return project_status.status in (SyncStatus.COMPLETED, SyncStatus.WATCHING, SyncStatus.IDLE)
def get_project_status(self, project_name: str) -> Optional[ProjectSyncStatus]:
"""Get status for a specific project."""
return self._project_statuses.get(project_name)
+133
View File
@@ -507,3 +507,136 @@ async def test_move_note_using_different_identifier_formats(mcp_server, app):
read3 = await client.call_tool("read_note", {"identifier": "moved/folder-title-moved.md"})
assert "Move by folder/title" in read3[0].text
@pytest.mark.asyncio
async def test_move_note_cross_project_detection(mcp_server, app):
"""Test cross-project move detection and helpful error messages."""
async with Client(mcp_server) as client:
# Create a test project to simulate cross-project scenario
await client.call_tool(
"create_memory_project",
{
"project_name": "test-project-b",
"project_path": "/tmp/test-project-b",
"set_default": False,
},
)
# Create a note in the default project
await client.call_tool(
"write_note",
{
"title": "Cross Project Test Note",
"folder": "source",
"content": "# Cross Project Test Note\n\nThis note is in the default project.",
"tags": "test,cross-project",
},
)
# Try to move to a path that contains the other project name
move_result = await client.call_tool(
"move_note",
{
"identifier": "Cross Project Test Note",
"destination_path": "test-project-b/moved-note.md",
},
)
# Should detect cross-project attempt and provide helpful guidance
assert len(move_result) == 1
error_message = move_result[0].text
assert "Cross-Project Move Not Supported" in error_message
assert "test-project-b" in error_message
assert "switch_project" in error_message
assert "read_note" in error_message
assert "write_note" in error_message
@pytest.mark.asyncio
async def test_move_note_potential_cross_project_guidance(mcp_server, app):
"""Test guidance for potentially cross-project moves with project-like keywords."""
async with Client(mcp_server) as client:
# Create another test project
await client.call_tool(
"create_memory_project",
{
"project_name": "workspace-docs",
"project_path": "/tmp/workspace-docs",
"set_default": False,
},
)
# Create a note in the default project
await client.call_tool(
"write_note",
{
"title": "Potential Cross Project Note",
"folder": "source",
"content": "# Potential Cross Project Note\n\nThis might be moved cross-project.",
"tags": "test,potential-cross-project",
},
)
# Try to move to a path that contains project-like keywords but not exact project names
move_result = await client.call_tool(
"move_note",
{
"identifier": "Potential Cross Project Note",
"destination_path": "project-archive/moved-note.md",
},
)
# Should provide guidance for potential cross-project moves
assert len(move_result) == 1
error_message = move_result[0].text
assert "Check Project Context" in error_message
assert "workspace-docs" in error_message # Should mention other available projects
assert "list_projects" in error_message
assert "switch_project" in error_message
@pytest.mark.asyncio
async def test_move_note_normal_moves_still_work(mcp_server, app):
"""Test that normal within-project moves still work after cross-project detection."""
async with Client(mcp_server) as client:
# Create a note
await client.call_tool(
"write_note",
{
"title": "Normal Move Note",
"folder": "source",
"content": "# Normal Move Note\n\nThis should move normally.",
"tags": "test,normal-move",
},
)
# Try a normal move that should work
move_result = await client.call_tool(
"move_note",
{
"identifier": "Normal Move Note",
"destination_path": "destination/normal-moved.md",
},
)
# Should work normally
assert len(move_result) == 1
move_text = move_result[0].text
assert "✅ Note moved successfully" in move_text
assert "Normal Move Note" in move_text
assert "destination/normal-moved.md" in move_text
# Verify the note can be read from its new location
read_result = await client.call_tool(
"read_note",
{
"identifier": "destination/normal-moved.md",
},
)
content = read_result[0].text
assert "This should move normally" in content
+39
View File
@@ -359,3 +359,42 @@ async def test_edit_note_find_replace_empty_find_text(client):
assert isinstance(result, str)
assert "# Edit Failed" in result
# Should contain helpful guidance about the error
@pytest.mark.asyncio
async def test_edit_note_preserves_permalink_when_frontmatter_missing(client):
"""Test that editing a note preserves the permalink when frontmatter doesn't contain one.
This is a regression test for issue #170 where edit_note would fail with a validation error
because the permalink was being set to None when the markdown file didn't have a permalink
in its frontmatter.
"""
# Create initial note
await write_note.fn(
title="Test Note",
folder="test",
content="# Test Note\nOriginal content here.",
)
# Verify the note was created with a permalink
first_result = await edit_note.fn(
identifier="test/test-note",
operation="append",
content="\nFirst edit.",
)
assert isinstance(first_result, str)
assert "permalink: test/test-note" in first_result
# Perform another edit - this should preserve the permalink even if the
# file doesn't have a permalink in its frontmatter
second_result = await edit_note.fn(
identifier="test/test-note",
operation="append",
content="\nSecond edit.",
)
assert isinstance(second_result, str)
assert "Edited note (append)" in second_result
assert "permalink: test/test-note" in second_result
# The edit should succeed without validation errors
+63 -22
View File
@@ -6,6 +6,7 @@ from unittest.mock import patch
from basic_memory.mcp.tools import write_note
from basic_memory.mcp.tools.search import search_notes, _format_search_error_response
from basic_memory.schemas.search import SearchResponse
@pytest.mark.asyncio
@@ -23,9 +24,14 @@ async def test_search_text(client):
# Search for it
response = await search_notes.fn(query="searchable")
# Verify results
assert len(response.results) > 0
assert any(r.permalink == "test/test-search-note" for r in response.results)
# Verify results - handle both success and error cases
if isinstance(response, SearchResponse):
# Success case - verify SearchResponse
assert len(response.results) > 0
assert any(r.permalink == "test/test-search-note" for r in response.results)
else:
# If search failed and returned error message, test should fail with informative message
pytest.fail(f"Search failed with error: {response}")
@pytest.mark.asyncio
@@ -43,9 +49,14 @@ async def test_search_title(client):
# Search for it
response = await search_notes.fn(query="Search Note", search_type="title")
# Verify results
assert len(response.results) > 0
assert any(r.permalink == "test/test-search-note" for r in response.results)
# Verify results - handle both success and error cases
if isinstance(response, str):
# If search failed and returned error message, test should fail with informative message
pytest.fail(f"Search failed with error: {response}")
else:
# Success case - verify SearchResponse
assert len(response.results) > 0
assert any(r.permalink == "test/test-search-note" for r in response.results)
@pytest.mark.asyncio
@@ -63,9 +74,14 @@ async def test_search_permalink(client):
# Search for it
response = await search_notes.fn(query="test/test-search-note", search_type="permalink")
# Verify results
assert len(response.results) > 0
assert any(r.permalink == "test/test-search-note" for r in response.results)
# Verify results - handle both success and error cases
if isinstance(response, SearchResponse):
# Success case - verify SearchResponse
assert len(response.results) > 0
assert any(r.permalink == "test/test-search-note" for r in response.results)
else:
# If search failed and returned error message, test should fail with informative message
pytest.fail(f"Search failed with error: {response}")
@pytest.mark.asyncio
@@ -83,9 +99,14 @@ async def test_search_permalink_match(client):
# Search for it
response = await search_notes.fn(query="test/test-search-*", search_type="permalink")
# Verify results
assert len(response.results) > 0
assert any(r.permalink == "test/test-search-note" for r in response.results)
# Verify results - handle both success and error cases
if isinstance(response, SearchResponse):
# Success case - verify SearchResponse
assert len(response.results) > 0
assert any(r.permalink == "test/test-search-note" for r in response.results)
else:
# If search failed and returned error message, test should fail with informative message
pytest.fail(f"Search failed with error: {response}")
@pytest.mark.asyncio
@@ -103,9 +124,14 @@ async def test_search_pagination(client):
# Search for it
response = await search_notes.fn(query="searchable", page=1, page_size=1)
# Verify results
assert len(response.results) == 1
assert any(r.permalink == "test/test-search-note" for r in response.results)
# Verify results - handle both success and error cases
if isinstance(response, SearchResponse):
# Success case - verify SearchResponse
assert len(response.results) == 1
assert any(r.permalink == "test/test-search-note" for r in response.results)
else:
# If search failed and returned error message, test should fail with informative message
pytest.fail(f"Search failed with error: {response}")
@pytest.mark.asyncio
@@ -121,8 +147,13 @@ async def test_search_with_type_filter(client):
# Search with type filter
response = await search_notes.fn(query="type", types=["note"])
# Verify all results are entities
assert all(r.type == "entity" for r in response.results)
# Verify results - handle both success and error cases
if isinstance(response, SearchResponse):
# Success case - verify all results are entities
assert all(r.type == "entity" for r in response.results)
else:
# If search failed and returned error message, test should fail with informative message
pytest.fail(f"Search failed with error: {response}")
@pytest.mark.asyncio
@@ -138,8 +169,13 @@ async def test_search_with_entity_type_filter(client):
# Search with entity type filter
response = await search_notes.fn(query="type", entity_types=["entity"])
# Verify all results are entities
assert all(r.type == "entity" for r in response.results)
# Verify results - handle both success and error cases
if isinstance(response, SearchResponse):
# Success case - verify all results are entities
assert all(r.type == "entity" for r in response.results)
else:
# If search failed and returned error message, test should fail with informative message
pytest.fail(f"Search failed with error: {response}")
@pytest.mark.asyncio
@@ -156,8 +192,13 @@ async def test_search_with_date_filter(client):
one_hour_ago = datetime.now() - timedelta(hours=1)
response = await search_notes.fn(query="recent", after_date=one_hour_ago.isoformat())
# Verify we get results within timeframe
assert len(response.results) > 0
# Verify results - handle both success and error cases
if isinstance(response, SearchResponse):
# Success case - verify we get results within timeframe
assert len(response.results) > 0
else:
# If search failed and returned error message, test should fail with informative message
pytest.fail(f"Search failed with error: {response}")
class TestSearchErrorFormatting:
@@ -212,7 +253,7 @@ class TestSearchErrorFormatting:
assert "# Search Failed" in result
assert "Error searching for 'test query': unknown error" in result
assert "General troubleshooting" in result
assert "## Troubleshooting steps:" in result
class TestSearchToolErrorHandling:
@@ -5,6 +5,7 @@ from datetime import datetime, timezone
from basic_memory.models.knowledge import Entity
from basic_memory.repository.entity_repository import EntityRepository
from basic_memory.repository.project_repository import ProjectRepository
@pytest.mark.asyncio
@@ -246,3 +247,205 @@ async def test_upsert_entity_gap_in_suffixes(entity_repository: EntityRepository
# so it should be "test/gap-2" (filling the gap)
assert result.permalink == "test/gap-2"
assert result.title == "Gap Filler"
@pytest.mark.asyncio
async def test_upsert_entity_project_scoping_isolation(session_maker):
"""Test that upsert_entity properly scopes entities by project_id.
This test ensures that the fix for issue #167 works correctly by verifying:
1. Entities with same permalinks/file_paths can exist in different projects
2. Upsert operations properly scope queries by project_id
3. No "multiple rows" errors occur when similar entities exist across projects
"""
# Create two separate projects
project_repository = ProjectRepository(session_maker)
project1_data = {
"name": "project-1",
"description": "First test project",
"path": "/tmp/project1",
"is_active": True,
"is_default": False,
}
project1 = await project_repository.create(project1_data)
project2_data = {
"name": "project-2",
"description": "Second test project",
"path": "/tmp/project2",
"is_active": True,
"is_default": False,
}
project2 = await project_repository.create(project2_data)
# Create entity repositories for each project
repo1 = EntityRepository(session_maker, project_id=project1.id)
repo2 = EntityRepository(session_maker, project_id=project2.id)
# Create entities with identical permalinks and file_paths in different projects
entity1 = Entity(
project_id=project1.id,
title="Shared Entity",
entity_type="note",
permalink="docs/shared-name",
file_path="docs/shared-name.md",
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
entity2 = Entity(
project_id=project2.id,
title="Shared Entity",
entity_type="note",
permalink="docs/shared-name", # Same permalink
file_path="docs/shared-name.md", # Same file_path
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
# These should succeed without "multiple rows" errors
result1 = await repo1.upsert_entity(entity1)
result2 = await repo2.upsert_entity(entity2)
# Verify both entities were created successfully
assert result1.id is not None
assert result2.id is not None
assert result1.id != result2.id # Different entities
assert result1.project_id == project1.id
assert result2.project_id == project2.id
assert result1.permalink == "docs/shared-name"
assert result2.permalink == "docs/shared-name"
# Test updating entities in different projects (should also work without conflicts)
entity1_update = Entity(
project_id=project1.id,
title="Updated Shared Entity",
entity_type="note",
permalink="docs/shared-name",
file_path="docs/shared-name.md",
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
entity2_update = Entity(
project_id=project2.id,
title="Also Updated Shared Entity",
entity_type="note",
permalink="docs/shared-name",
file_path="docs/shared-name.md",
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
# Updates should work without conflicts
updated1 = await repo1.upsert_entity(entity1_update)
updated2 = await repo2.upsert_entity(entity2_update)
# Should update existing entities (same IDs)
assert updated1.id == result1.id
assert updated2.id == result2.id
assert updated1.title == "Updated Shared Entity"
assert updated2.title == "Also Updated Shared Entity"
# Verify cross-project queries don't interfere
found_in_project1 = await repo1.get_by_permalink("docs/shared-name")
found_in_project2 = await repo2.get_by_permalink("docs/shared-name")
assert found_in_project1 is not None
assert found_in_project2 is not None
assert found_in_project1.id == updated1.id
assert found_in_project2.id == updated2.id
assert found_in_project1.title == "Updated Shared Entity"
assert found_in_project2.title == "Also Updated Shared Entity"
@pytest.mark.asyncio
async def test_upsert_entity_permalink_conflict_within_project_only(session_maker):
"""Test that permalink conflicts only occur within the same project.
This ensures that the project scoping fix allows entities with identical
permalinks to exist across different projects without triggering
permalink conflict resolution.
"""
# Create two separate projects
project_repository = ProjectRepository(session_maker)
project1_data = {
"name": "conflict-project-1",
"description": "First conflict test project",
"path": "/tmp/conflict1",
"is_active": True,
"is_default": False,
}
project1 = await project_repository.create(project1_data)
project2_data = {
"name": "conflict-project-2",
"description": "Second conflict test project",
"path": "/tmp/conflict2",
"is_active": True,
"is_default": False,
}
project2 = await project_repository.create(project2_data)
# Create entity repositories for each project
repo1 = EntityRepository(session_maker, project_id=project1.id)
repo2 = EntityRepository(session_maker, project_id=project2.id)
# Create first entity in project1
entity1 = Entity(
project_id=project1.id,
title="Original Entity",
entity_type="note",
permalink="test/conflict-permalink",
file_path="test/original.md",
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
result1 = await repo1.upsert_entity(entity1)
assert result1.permalink == "test/conflict-permalink"
# Create entity with same permalink in project2 (should NOT get suffix)
entity2 = Entity(
project_id=project2.id,
title="Cross-Project Entity",
entity_type="note",
permalink="test/conflict-permalink", # Same permalink, different project
file_path="test/cross-project.md",
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
result2 = await repo2.upsert_entity(entity2)
# Should keep original permalink (no suffix) since it's in a different project
assert result2.permalink == "test/conflict-permalink"
# Now create entity with same permalink in project1 (should get suffix)
entity3 = Entity(
project_id=project1.id,
title="Conflict Entity",
entity_type="note",
permalink="test/conflict-permalink", # Same permalink, same project
file_path="test/conflict.md",
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
result3 = await repo1.upsert_entity(entity3)
# Should get suffix since it conflicts within the same project
assert result3.permalink == "test/conflict-permalink-1"
# Verify all entities exist correctly
assert result1.id != result2.id != result3.id
assert result1.project_id == project1.id
assert result2.project_id == project2.id
assert result3.project_id == project1.id
@@ -329,6 +329,36 @@ class TestSearchTermPreparation:
== "(hello AND world) OR test"
)
def test_hyphenated_terms_with_boolean_operators(self, search_repository):
"""Hyphenated terms with Boolean operators should be properly quoted."""
# Test the specific case from the GitHub issue
result = search_repository._prepare_search_term("tier1-test AND unicode")
assert result == '"tier1-test" AND unicode'
# Test other hyphenated Boolean combinations
assert (
search_repository._prepare_search_term("multi-word OR single")
== '"multi-word" OR single'
)
assert (
search_repository._prepare_search_term("well-formed NOT badly-formed")
== '"well-formed" NOT "badly-formed"'
)
assert (
search_repository._prepare_search_term("test-case AND (hello OR world)")
== '"test-case" AND (hello OR world)'
)
# Test mixed special characters with Boolean operators
assert (
search_repository._prepare_search_term("config.json AND test-file")
== '"config.json" AND "test-file"'
)
assert (
search_repository._prepare_search_term("C++ OR python-script")
== '"C++" OR "python-script"'
)
def test_programming_terms_should_work(self, search_repository):
"""Programming-related terms with special chars should be searchable."""
# These should be quoted to handle special characters safely
@@ -517,3 +547,53 @@ class TestSearchTermPreparation:
# Test whitespace-only search
results_whitespace = await search_repository.search(search_text=" ")
assert isinstance(results_whitespace, list) # Should not crash
def test_boolean_query_empty_parts_coverage(self, search_repository):
"""Test Boolean query parsing with empty parts (line 143 coverage)."""
# Create queries that will result in empty parts after splitting
result1 = search_repository._prepare_boolean_query(
"hello AND AND world"
) # Double operator
assert "hello" in result1 and "world" in result1
result2 = search_repository._prepare_boolean_query(" OR test") # Leading operator
assert "test" in result2
result3 = search_repository._prepare_boolean_query("test OR ") # Trailing operator
assert "test" in result3
def test_parenthetical_term_quote_escaping(self, search_repository):
"""Test quote escaping in parenthetical terms (lines 190-191 coverage)."""
# Test term with quotes that needs escaping
result = search_repository._prepare_parenthetical_term('(say "hello" world)')
# Should escape quotes by doubling them
assert '""hello""' in result
# Test term with single quotes
result2 = search_repository._prepare_parenthetical_term("(it's working)")
assert "it's working" in result2
def test_needs_quoting_empty_input(self, search_repository):
"""Test _needs_quoting with empty inputs (line 207 coverage)."""
# Test empty string
assert not search_repository._needs_quoting("")
# Test whitespace-only string
assert not search_repository._needs_quoting(" ")
# Test None-like cases
assert not search_repository._needs_quoting("\t")
def test_prepare_single_term_empty_input(self, search_repository):
"""Test _prepare_single_term with empty inputs (line 227 coverage)."""
# Test empty string
result1 = search_repository._prepare_single_term("")
assert result1 == ""
# Test whitespace-only string
result2 = search_repository._prepare_single_term(" ")
assert result2 == " " # Should return as-is
# Test string that becomes empty after strip
result3 = search_repository._prepare_single_term("\t\n")
assert result3 == "\t\n" # Should return original
+30
View File
@@ -127,6 +127,36 @@ def test_entity_out_from_attributes():
assert len(entity.relations) == 1
def test_entity_response_with_none_permalink():
"""Test EntityResponse can handle None permalink (fixes issue #170).
This test ensures that EntityResponse properly validates when the permalink
field is None, which can occur when markdown files don't have explicit
permalinks in their frontmatter during edit operations.
"""
# Simulate database model attributes with None permalink
db_data = {
"title": "Test Entity",
"permalink": None, # This should not cause validation errors
"file_path": "test/test-entity.md",
"entity_type": "note",
"content_type": "text/markdown",
"observations": [],
"relations": [],
"created_at": "2023-01-01T00:00:00",
"updated_at": "2023-01-01T00:00:00",
}
# This should not raise a ValidationError
entity = EntityResponse.model_validate(db_data)
assert entity.permalink is None
assert entity.title == "Test Entity"
assert entity.file_path == "test/test-entity.md"
assert entity.entity_type == "note"
assert len(entity.observations) == 0
assert len(entity.relations) == 0
def test_search_nodes_input():
"""Test SearchNodesInput validation."""
search = SearchNodesRequest.model_validate({"query": "test query"})
+66
View File
@@ -1728,3 +1728,69 @@ async def test_move_entity_with_complex_observations(
assert "Branch Strategy" in relation_targets
assert "Multiple" in relation_targets
assert "Links" in relation_targets
@pytest.mark.asyncio
async def test_move_entity_with_null_permalink_generates_permalink(
entity_service: EntityService,
project_config: ProjectConfig,
entity_repository: EntityRepository,
):
"""Test that moving entity with null permalink generates a new permalink automatically.
This tests the fix for issue #155 where entities with null permalinks from the database
migration would fail validation when being moved. The fix ensures that entities with
null permalinks get a generated permalink during move operations, regardless of the
update_permalinks_on_move setting.
"""
# Create entity through direct database insertion to simulate migrated entity with null permalink
from datetime import datetime, timezone
# Create an entity with null permalink directly in database (simulating migrated data)
entity_data = {
"title": "Test Entity",
"file_path": "test/null-permalink-entity.md",
"entity_type": "note",
"content_type": "text/markdown",
"permalink": None, # This is the key - null permalink from migration
"created_at": datetime.now(timezone.utc),
"updated_at": datetime.now(timezone.utc),
}
# Create the entity directly in database
created_entity = await entity_repository.create(entity_data)
assert created_entity.permalink is None
# Create the physical file
file_path = project_config.home / created_entity.file_path
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text("# Test Entity\n\nContent here.")
# Configure move without permalink updates (the default setting that previously triggered the bug)
app_config = BasicMemoryConfig(update_permalinks_on_move=False)
# Move entity - this should now succeed and generate a permalink
moved_entity = await entity_service.move_entity(
identifier=created_entity.title, # Use title since permalink is None
destination_path="moved/test-entity.md",
project_config=project_config,
app_config=app_config,
)
# Verify the move succeeded and a permalink was generated
assert moved_entity is not None
assert moved_entity.file_path == "moved/test-entity.md"
assert moved_entity.permalink is not None
assert moved_entity.permalink != ""
# Verify the moved entity can be used to create an EntityResponse without validation errors
from basic_memory.schemas.response import EntityResponse
response = EntityResponse.model_validate(moved_entity)
assert response.permalink == moved_entity.permalink
# Verify the physical file was moved
old_path = project_config.home / "test/null-permalink-entity.md"
new_path = project_config.home / "moved/test-entity.md"
assert not old_path.exists()
assert new_path.exists()
@@ -17,6 +17,9 @@ def test_sync_tracker_initial_state(sync_tracker):
assert sync_tracker.global_status == SyncStatus.IDLE
assert sync_tracker.get_summary() == "✅ System ready"
# Test project-specific ready check for unknown project
assert sync_tracker.is_project_ready("unknown-project")
def test_start_project_sync(sync_tracker):
"""Test starting project sync."""
@@ -211,3 +214,49 @@ def test_summary_without_file_counts(sync_tracker):
summary = sync_tracker.get_summary()
assert "🔄 Syncing 2 projects" in summary
assert "files" not in summary # Should not show file progress
def test_is_project_ready_functionality(sync_tracker):
"""Test project-specific ready checks."""
# Unknown project should be ready
assert sync_tracker.is_project_ready("unknown-project")
# Project in different states
sync_tracker.start_project_sync("scanning-project")
assert not sync_tracker.is_project_ready("scanning-project") # SCANNING = not ready
sync_tracker.update_project_progress("scanning-project", SyncStatus.SYNCING, "Processing")
assert not sync_tracker.is_project_ready("scanning-project") # SYNCING = not ready
sync_tracker.fail_project_sync("scanning-project", "Test error")
assert not sync_tracker.is_project_ready("scanning-project") # FAILED = not ready
sync_tracker.complete_project_sync("scanning-project")
assert sync_tracker.is_project_ready("scanning-project") # COMPLETED = ready
# Test watching project
sync_tracker.start_project_watch("watching-project")
assert sync_tracker.is_project_ready("watching-project") # WATCHING = ready
def test_project_isolation_scenario(sync_tracker):
"""Test the specific bug scenario: project isolation with mixed sync states."""
# Set up the bug scenario: one failed project, one healthy project
sync_tracker.start_project_sync("main")
sync_tracker.fail_project_sync(
"main", "UNIQUE constraint failed: entity.file_path, entity.project_id"
)
sync_tracker.start_project_sync("basic-memory-testing-20250626-1009")
sync_tracker.complete_project_sync("basic-memory-testing-20250626-1009")
sync_tracker.start_project_watch("basic-memory-testing-20250626-1009")
# Global status should be failed due to "main" project
assert sync_tracker.global_status == SyncStatus.FAILED
assert not sync_tracker.is_ready
# But the healthy project should be ready for operations
assert sync_tracker.is_project_ready("basic-memory-testing-20250626-1009")
assert not sync_tracker.is_project_ready("main")
# This demonstrates the fix: project-specific checks allow isolation
+78
View File
@@ -0,0 +1,78 @@
"""Test configuration management."""
from basic_memory.config import BasicMemoryConfig
class TestBasicMemoryConfig:
"""Test BasicMemoryConfig behavior with BASIC_MEMORY_HOME environment variable."""
def test_default_behavior_without_basic_memory_home(self, config_home, monkeypatch):
"""Test that config uses default path when BASIC_MEMORY_HOME is not set."""
# Ensure BASIC_MEMORY_HOME is not set
monkeypatch.delenv("BASIC_MEMORY_HOME", raising=False)
config = BasicMemoryConfig()
# Should use the default path (home/basic-memory)
expected_path = str(config_home / "basic-memory")
assert config.projects["main"] == expected_path
def test_respects_basic_memory_home_environment_variable(self, config_home, monkeypatch):
"""Test that config respects BASIC_MEMORY_HOME environment variable."""
custom_path = str(config_home / "app" / "data")
monkeypatch.setenv("BASIC_MEMORY_HOME", custom_path)
config = BasicMemoryConfig()
# Should use the custom path from environment variable
assert config.projects["main"] == custom_path
def test_model_post_init_respects_basic_memory_home(self, config_home, monkeypatch):
"""Test that model_post_init creates main project with BASIC_MEMORY_HOME when missing."""
custom_path = str(config_home / "custom" / "memory" / "path")
monkeypatch.setenv("BASIC_MEMORY_HOME", custom_path)
# Create config without main project
other_path = str(config_home / "some" / "path")
config = BasicMemoryConfig(projects={"other": other_path})
# model_post_init should have added main project with BASIC_MEMORY_HOME
assert "main" in config.projects
assert config.projects["main"] == custom_path
def test_model_post_init_fallback_without_basic_memory_home(self, config_home, monkeypatch):
"""Test that model_post_init falls back to default when BASIC_MEMORY_HOME is not set."""
# Ensure BASIC_MEMORY_HOME is not set
monkeypatch.delenv("BASIC_MEMORY_HOME", raising=False)
# Create config without main project
other_path = str(config_home / "some" / "path")
config = BasicMemoryConfig(projects={"other": other_path})
# model_post_init should have added main project with default path
expected_path = str(config_home / "basic-memory")
assert "main" in config.projects
assert config.projects["main"] == expected_path
def test_basic_memory_home_with_relative_path(self, config_home, monkeypatch):
"""Test that BASIC_MEMORY_HOME works with relative paths."""
relative_path = "relative/memory/path"
monkeypatch.setenv("BASIC_MEMORY_HOME", relative_path)
config = BasicMemoryConfig()
# Should use the exact value from environment variable
assert config.projects["main"] == relative_path
def test_basic_memory_home_overrides_existing_main_project(self, config_home, monkeypatch):
"""Test that BASIC_MEMORY_HOME is not used when a map is passed in the constructor."""
custom_path = str(config_home / "override" / "memory" / "path")
monkeypatch.setenv("BASIC_MEMORY_HOME", custom_path)
# Try to create config with a different main project path
original_path = str(config_home / "original" / "path")
config = BasicMemoryConfig(projects={"main": original_path})
# The default_factory should override with BASIC_MEMORY_HOME value
# Note: This tests the current behavior where default_factory takes precedence
assert config.projects["main"] == original_path