Compare commits
166 Commits
v0.9.0
...
v0.13.8-dev1
| Author | SHA1 | Date | |
|---|---|---|---|
| 616c1f0710 | |||
| 74847cc380 | |||
| d3b6c85184 | |||
| af44941d5a | |||
| 35e4f73ae8 | |||
| 7be001ca68 | |||
| 3269a2f33a | |||
| b8191d090f | |||
| 2ce8a8e4b0 | |||
| f8099cd004 | |||
| 688e0b0971 | |||
| ed09ea4ec7 | |||
| c85d9f74d7 | |||
| 84d2aaf641 | |||
| 7789864493 | |||
| c6215fd819 | |||
| b4c26a6133 | |||
| 3fdce683d7 | |||
| 782cb2df28 | |||
| 56c875f137 | |||
| 5049de7e2d | |||
| 49011768f7 | |||
| bc3557f000 | |||
| 611f5cd305 | |||
| 4ea392d284 | |||
| d491757980 | |||
| 7a69ca2c36 | |||
| 70a6ce3411 | |||
| 5b69fd65cd | |||
| 85a178a6b8 | |||
| e4b32d7bc9 | |||
| 9590b934cf | |||
| 735f239f9b | |||
| 3ee30e1f36 | |||
| ac401ea254 | |||
| fb2fd62ed9 | |||
| 126d1655e6 | |||
| 2abf626c46 | |||
| ba8e3d112d | |||
| 7108a7baf1 | |||
| 35884ef3a7 | |||
| 040be05a81 | |||
| c141d7d1e6 | |||
| b73aeb5ed8 | |||
| 9a0e0bd82d | |||
| 117fa44ecf | |||
| 69d7610d47 | |||
| f608cd13f1 | |||
| 2162ad57fe | |||
| dd6ca80716 | |||
| ae3eeb0cc1 | |||
| 602c55fe90 | |||
| 91bfe2dc92 | |||
| a3cae1064d | |||
| c5c70cb0f4 | |||
| 80ec860a1c | |||
| f64d5b2152 | |||
| 69a625acd1 | |||
| 53c29a37ca | |||
| d8c13bf1d3 | |||
| 3f70f5ed42 | |||
| 569a3de80b | |||
| ac08a8d024 | |||
| c13d4b1511 | |||
| 5b85d33a99 | |||
| e2bd3142dc | |||
| 40b7de9168 | |||
| 455a280707 | |||
| 634486107b | |||
| 23d34289fd | |||
| 5ec4087f7c | |||
| 71941c35dd | |||
| 020957cd76 | |||
| b3af6aa685 | |||
| 9437a5f83b | |||
| 3c1cc346df | |||
| 81616ab42e | |||
| 321471f29d | |||
| 73ea91fe0d | |||
| 03d4e97b90 | |||
| 98622a7a47 | |||
| 54dfa08aba | |||
| 9433065a57 | |||
| 2934176331 | |||
| ac89eb47cb | |||
| 78a3412bcf | |||
| fa314b5d2b | |||
| d2ac62a27c | |||
| 7ce7a29f35 | |||
| 3f4d9e4d87 | |||
| 00c8633cfc | |||
| 617e60bda4 | |||
| 6c19c9edf5 | |||
| 9bff1f732e | |||
| 248214cb11 | |||
| 40ea28b0bf | |||
| 43cbb7b38c | |||
| 7930ddb291 | |||
| 1844e58210 | |||
| 7fbb5ebfc5 | |||
| 0e5a465b10 | |||
| 9d581cee13 | |||
| f58852954b | |||
| edd426b99f | |||
| 6b4a421315 | |||
| 4632405446 | |||
| 3a6d5ff58d | |||
| 03de85ec32 | |||
| 084c91ecce | |||
| 3d45227b72 | |||
| 069c0a21c6 | |||
| b27827671d | |||
| 0743ade5fc | |||
| f1c95709cb | |||
| 3c68b7d5dd | |||
| 3d0077990f | |||
| 731b502d36 | |||
| 2a881b1425 | |||
| 681af5d450 | |||
| b35037eb86 | |||
| b667bca5a2 | |||
| e716946b44 | |||
| 46c4fd2164 | |||
| 9c791259a0 | |||
| cc2cae72c1 | |||
| 0a1fdd8f15 | |||
| 78f234b180 | |||
| cbe72be10a | |||
| 0ade6b0603 | |||
| 390ff9d31c | |||
| 3ed74b4a4d | |||
| dfaf0fea9c | |||
| b26afa927f | |||
| 3806943f8c | |||
| 3bffb2e190 | |||
| 9fe34dcddf | |||
| eb5e4ec6bd | |||
| 6b110b28dd | |||
| 88d193e0aa | |||
| fa9175f049 | |||
| 7c787a413c | |||
| 0fa1adf3b5 | |||
| cbe488fbd2 | |||
| 99ff8f60c2 | |||
| 06ee2852a0 | |||
| 939bab1a24 | |||
| 67736487fb | |||
| 2ac620f98b | |||
| 4cbda7a9d4 | |||
| 3070ade69a | |||
| 9af913da4f | |||
| 7b7bec9557 | |||
| fea2f40d1b | |||
| eb1e7b6088 | |||
| 9203730ba7 | |||
| 1a473c02e9 | |||
| b6f5d8a545 | |||
| addeeb1e7f | |||
| 85f9099b0a | |||
| 82b6f3d60e | |||
| da97353cfc | |||
| 2e9d673e54 | |||
| c4732a47b3 | |||
| 2d5176e800 | |||
| 9bb8a020c3 | |||
| 31cca6f913 |
@@ -0,0 +1,190 @@
|
||||
# /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
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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
|
||||
@@ -0,0 +1,145 @@
|
||||
# /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
|
||||
@@ -0,0 +1,95 @@
|
||||
# /beta - Create Beta Release
|
||||
|
||||
Create a new beta release using the automated justfile target with quality checks and tagging.
|
||||
|
||||
## Usage
|
||||
```
|
||||
/beta <version>
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `version` (required): Beta version like `v0.13.2b1` or `v0.13.2rc1`
|
||||
|
||||
## Implementation
|
||||
|
||||
You are an expert release manager for the Basic Memory project. When the user runs `/beta`, execute the following steps:
|
||||
|
||||
### Step 1: Pre-flight Validation
|
||||
1. Verify version format matches `v\d+\.\d+\.\d+(b\d+|rc\d+)` pattern
|
||||
2. Check current git status for uncommitted changes
|
||||
3. Verify we're on the `main` branch
|
||||
4. Confirm no existing tag with this version
|
||||
|
||||
### Step 2: Use Justfile Automation
|
||||
Execute the automated beta release process:
|
||||
```bash
|
||||
just beta <version>
|
||||
```
|
||||
|
||||
The justfile target handles:
|
||||
- ✅ Beta version format validation (supports b1, b2, rc1, etc.)
|
||||
- ✅ Git status and branch checks
|
||||
- ✅ Quality checks (`just check` - lint, format, type-check, tests)
|
||||
- ✅ Version update in `src/basic_memory/__init__.py`
|
||||
- ✅ Automatic commit with proper message
|
||||
- ✅ Tag creation and pushing to GitHub
|
||||
- ✅ Beta release workflow trigger
|
||||
|
||||
### Step 3: Monitor Beta Release
|
||||
1. Check GitHub Actions workflow starts successfully
|
||||
2. Monitor workflow at: https://github.com/basicmachines-co/basic-memory/actions
|
||||
3. Verify PyPI pre-release publication
|
||||
4. Test beta installation: `uv tool install basic-memory --pre`
|
||||
|
||||
### Step 4: Beta Testing Instructions
|
||||
Provide users with beta testing instructions:
|
||||
|
||||
```bash
|
||||
# Install/upgrade to beta
|
||||
uv tool install basic-memory --pre
|
||||
|
||||
# Or upgrade existing installation
|
||||
uv tool upgrade basic-memory --prerelease=allow
|
||||
```
|
||||
|
||||
## Version Guidelines
|
||||
- **First beta**: `v0.13.2b1`
|
||||
- **Subsequent betas**: `v0.13.2b2`, `v0.13.2b3`, etc.
|
||||
- **Release candidates**: `v0.13.2rc1`, `v0.13.2rc2`, etc.
|
||||
- **Final release**: `v0.13.2` (use `/release` command)
|
||||
|
||||
## Error Handling
|
||||
- If `just beta` fails, examine the error output for specific issues
|
||||
- If quality checks fail, fix issues and retry
|
||||
- If version format is invalid, correct and retry
|
||||
- If tag already exists, increment version number
|
||||
|
||||
## Success Output
|
||||
```
|
||||
✅ Beta Release v0.13.2b1 Created Successfully!
|
||||
|
||||
🏷️ Tag: v0.13.2b1
|
||||
🚀 GitHub Actions: Running
|
||||
📦 PyPI: Will be available in ~5 minutes as pre-release
|
||||
|
||||
Install/test with:
|
||||
uv tool install basic-memory --pre
|
||||
|
||||
Monitor release: https://github.com/basicmachines-co/basic-memory/actions
|
||||
```
|
||||
|
||||
## Beta Testing Workflow
|
||||
1. **Create beta**: Use `/beta v0.13.2b1`
|
||||
2. **Test features**: Install and validate new functionality
|
||||
3. **Fix issues**: Address bugs found during testing
|
||||
4. **Iterate**: Create `v0.13.2b2` if needed
|
||||
5. **Release candidate**: Create `v0.13.2rc1` when stable
|
||||
6. **Final release**: Use `/release v0.13.2` when ready
|
||||
|
||||
## Context
|
||||
- Beta releases are pre-releases for testing new features
|
||||
- Automatically published to PyPI with pre-release flag
|
||||
- Uses the automated justfile target for consistency
|
||||
- Version is automatically updated in `__init__.py`
|
||||
- Ideal for validating changes before stable release
|
||||
- Supports both beta (b1, b2) and release candidate (rc1, rc2) versions
|
||||
@@ -0,0 +1,157 @@
|
||||
# /changelog - Generate or Update Changelog Entry
|
||||
|
||||
Analyze commits and generate formatted changelog entry for a version.
|
||||
|
||||
## Usage
|
||||
```
|
||||
/changelog <version> [type]
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `version` (required): Version like `v0.13.0` or `v0.13.0b4`
|
||||
- `type` (optional): `beta`, `rc`, or `stable` (default: `stable`)
|
||||
|
||||
## Implementation
|
||||
|
||||
You are an expert technical writer for the Basic Memory project. When the user runs `/changelog`, execute the following steps:
|
||||
|
||||
### Step 1: Version Analysis
|
||||
1. **Determine Commit Range**
|
||||
```bash
|
||||
# Find last release tag
|
||||
git tag -l "v*" --sort=-version:refname | grep -v "b\|rc" | head -1
|
||||
|
||||
# Get commits since last release
|
||||
git log --oneline ${last_tag}..HEAD
|
||||
```
|
||||
|
||||
2. **Parse Conventional Commits**
|
||||
- Extract feat: (features)
|
||||
- Extract fix: (bug fixes)
|
||||
- Extract BREAKING CHANGE: (breaking changes)
|
||||
- Extract chore:, docs:, test: (other improvements)
|
||||
|
||||
### Step 2: Categorize Changes
|
||||
1. **Features (feat:)**
|
||||
- New MCP tools
|
||||
- New CLI commands
|
||||
- New API endpoints
|
||||
- Major functionality additions
|
||||
|
||||
2. **Bug Fixes (fix:)**
|
||||
- User-facing bug fixes
|
||||
- Critical issues resolved
|
||||
- Performance improvements
|
||||
- Security fixes
|
||||
|
||||
3. **Technical Improvements**
|
||||
- Test coverage improvements
|
||||
- Code quality enhancements
|
||||
- Dependency updates
|
||||
- Documentation updates
|
||||
|
||||
4. **Breaking Changes**
|
||||
- API changes
|
||||
- Configuration changes
|
||||
- Behavior changes
|
||||
- Migration requirements
|
||||
|
||||
### Step 3: Generate Changelog Entry
|
||||
Create formatted entry following existing CHANGELOG.md style:
|
||||
|
||||
```markdown
|
||||
## v0.13.0 (2025-06-03)
|
||||
|
||||
### Features
|
||||
|
||||
- **Multi-Project Management System** - Switch between projects instantly during conversations
|
||||
([`993e88a`](https://github.com/basicmachines-co/basic-memory/commit/993e88a))
|
||||
- Instant project switching with session context
|
||||
- Project-specific operations and isolation
|
||||
- Project discovery and management tools
|
||||
|
||||
- **Advanced Note Editing** - Incremental editing with append, prepend, find/replace, and section operations
|
||||
([`6fc3904`](https://github.com/basicmachines-co/basic-memory/commit/6fc3904))
|
||||
- `edit_note` tool with multiple operation types
|
||||
- Smart frontmatter-aware editing
|
||||
- Validation and error handling
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **#118**: Fix YAML tag formatting to follow standard specification
|
||||
([`2dc7e27`](https://github.com/basicmachines-co/basic-memory/commit/2dc7e27))
|
||||
|
||||
- **#110**: Make --project flag work consistently across CLI commands
|
||||
([`02dd91a`](https://github.com/basicmachines-co/basic-memory/commit/02dd91a))
|
||||
|
||||
### Technical Improvements
|
||||
|
||||
- **Comprehensive Testing** - 100% test coverage with integration testing
|
||||
([`468a22f`](https://github.com/basicmachines-co/basic-memory/commit/468a22f))
|
||||
- MCP integration test suite
|
||||
- End-to-end testing framework
|
||||
- Performance and edge case validation
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- **Database Migration**: Automatic migration from per-project to unified database.
|
||||
Data will be re-index from the filesystem, resulting in no data loss.
|
||||
- **Configuration Changes**: Projects now synced between config.json and database
|
||||
- **Full Backward Compatibility**: All existing setups continue to work seamlessly
|
||||
```
|
||||
|
||||
### Step 4: Integration
|
||||
1. **Update CHANGELOG.md**
|
||||
- Insert new entry at top
|
||||
- Maintain consistent formatting
|
||||
- Include commit links and issue references
|
||||
|
||||
2. **Validation**
|
||||
- Check all major changes are captured
|
||||
- Verify commit links work
|
||||
- Ensure issue numbers are correct
|
||||
|
||||
## Smart Analysis Features
|
||||
|
||||
### Automatic Classification
|
||||
- Detect feature additions from file changes
|
||||
- Identify bug fixes from commit messages
|
||||
- Find breaking changes from code analysis
|
||||
- Extract issue numbers from commit messages
|
||||
|
||||
### Content Enhancement
|
||||
- Add context for technical changes
|
||||
- Include migration guidance for breaking changes
|
||||
- Suggest installation/upgrade instructions
|
||||
- Link to relevant documentation
|
||||
|
||||
## Output Format
|
||||
|
||||
### For Beta Releases
|
||||
```markdown
|
||||
## v0.13.0b4 (2025-06-03)
|
||||
|
||||
### Beta Changes Since v0.13.0b3
|
||||
|
||||
- Fix FastMCP API compatibility issues
|
||||
- Update dependencies to latest versions
|
||||
- Resolve setuptools import error
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
uv tool install basic-memory --prerelease=allow
|
||||
```
|
||||
|
||||
### Known Issues
|
||||
- [List any known issues for beta testing]
|
||||
```
|
||||
|
||||
### For Stable Releases
|
||||
Full changelog with complete feature list, organized by impact and category.
|
||||
|
||||
## Context
|
||||
- Follows existing CHANGELOG.md format and style
|
||||
- Uses conventional commit standards
|
||||
- Includes GitHub commit links for traceability
|
||||
- Focuses on user-facing changes and value
|
||||
- Maintains consistency with previous entries
|
||||
@@ -0,0 +1,131 @@
|
||||
# /release-check - Pre-flight Release Validation
|
||||
|
||||
Comprehensive pre-flight check for release readiness without making any changes.
|
||||
|
||||
## Usage
|
||||
```
|
||||
/release-check [version]
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `version` (optional): Version to validate like `v0.13.0`. If not provided, determines from context.
|
||||
|
||||
## Implementation
|
||||
|
||||
You are an expert QA engineer for the Basic Memory project. When the user runs `/release-check`, execute the following validation steps:
|
||||
|
||||
### Step 1: Environment Validation
|
||||
1. **Git Status Check**
|
||||
- Verify working directory is clean
|
||||
- Confirm on `main` branch
|
||||
- Check if ahead/behind origin
|
||||
|
||||
2. **Version Validation**
|
||||
- Validate version format if provided
|
||||
- Check for existing tags with same version
|
||||
- Verify version increments properly from last release
|
||||
|
||||
### Step 2: Code Quality Gates
|
||||
1. **Test Suite Validation**
|
||||
```bash
|
||||
just test
|
||||
```
|
||||
- All tests must pass
|
||||
- Check test coverage (target: 95%+)
|
||||
- Validate no skipped critical tests
|
||||
|
||||
2. **Code Quality Checks**
|
||||
```bash
|
||||
just lint
|
||||
just type-check
|
||||
```
|
||||
- No linting errors
|
||||
- No type checking errors
|
||||
- Code formatting is consistent
|
||||
|
||||
### Step 3: Documentation Validation
|
||||
1. **Changelog Check**
|
||||
- CHANGELOG.md contains entry for target version
|
||||
- Entry includes all major features and fixes
|
||||
- Breaking changes are documented
|
||||
|
||||
2. **Documentation Currency**
|
||||
- README.md reflects current functionality
|
||||
- CLI reference is up to date
|
||||
- MCP tools are documented
|
||||
|
||||
### Step 4: Dependency Validation
|
||||
1. **Security Scan**
|
||||
- No known vulnerabilities in dependencies
|
||||
- All dependencies are at appropriate versions
|
||||
- No conflicting dependency versions
|
||||
|
||||
2. **Build Validation**
|
||||
- Package builds successfully
|
||||
- All required files are included
|
||||
- No missing dependencies
|
||||
|
||||
### Step 5: Issue Tracking Validation
|
||||
1. **GitHub Issues Check**
|
||||
- No critical open issues blocking release
|
||||
- All milestone issues are resolved
|
||||
- High-priority bugs are fixed
|
||||
|
||||
2. **Testing Coverage**
|
||||
- Integration tests pass
|
||||
- MCP tool tests pass
|
||||
- Cross-platform compatibility verified
|
||||
|
||||
## Report Format
|
||||
|
||||
Generate a comprehensive report:
|
||||
|
||||
```
|
||||
🔍 Release Readiness Check for v0.13.0
|
||||
|
||||
✅ PASSED CHECKS:
|
||||
├── Git status clean
|
||||
├── On main branch
|
||||
├── All tests passing (744/744)
|
||||
├── Test coverage: 98.2%
|
||||
├── Type checking passed
|
||||
├── Linting passed
|
||||
├── CHANGELOG.md updated
|
||||
└── No critical issues open
|
||||
|
||||
⚠️ WARNINGS:
|
||||
├── 2 medium-priority issues still open
|
||||
└── Documentation could be updated
|
||||
|
||||
❌ BLOCKING ISSUES:
|
||||
└── None found
|
||||
|
||||
🎯 RELEASE READINESS: ✅ READY
|
||||
|
||||
Recommended next steps:
|
||||
1. Address warnings if desired
|
||||
2. Run `/release v0.13.0` when ready
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
### Must Pass (Blocking)
|
||||
- [ ] All tests pass
|
||||
- [ ] No type errors
|
||||
- [ ] No linting errors
|
||||
- [ ] Working directory clean
|
||||
- [ ] On main branch
|
||||
- [ ] CHANGELOG.md has version entry
|
||||
- [ ] No critical open issues
|
||||
|
||||
### Should Pass (Warnings)
|
||||
- [ ] Test coverage >95%
|
||||
- [ ] No medium-priority open issues
|
||||
- [ ] Documentation up to date
|
||||
- [ ] No dependency vulnerabilities
|
||||
|
||||
## Context
|
||||
- This is a read-only validation - makes no changes
|
||||
- Provides confidence before running actual release
|
||||
- Helps identify issues early in release process
|
||||
- Can be run multiple times safely
|
||||
@@ -0,0 +1,92 @@
|
||||
# /release - Create Stable Release
|
||||
|
||||
Create a stable release using the automated justfile target with comprehensive validation.
|
||||
|
||||
## Usage
|
||||
```
|
||||
/release <version>
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `version` (required): Release version like `v0.13.2`
|
||||
|
||||
## Implementation
|
||||
|
||||
You are an expert release manager for the Basic Memory project. When the user runs `/release`, execute the following steps:
|
||||
|
||||
### Step 1: Pre-flight Validation
|
||||
1. Verify version format matches `v\d+\.\d+\.\d+` pattern
|
||||
2. Check current git status for uncommitted changes
|
||||
3. Verify we're on the `main` branch
|
||||
4. Confirm no existing tag with this version
|
||||
|
||||
#### Documentation Validation
|
||||
1. **Changelog Check**
|
||||
- CHANGELOG.md contains entry for target version
|
||||
- Entry includes all major features and fixes
|
||||
- Breaking changes are documented
|
||||
|
||||
### Step 2: Use Justfile Automation
|
||||
Execute the automated release process:
|
||||
```bash
|
||||
just release <version>
|
||||
```
|
||||
|
||||
The justfile target handles:
|
||||
- ✅ Version format validation
|
||||
- ✅ Git status and branch checks
|
||||
- ✅ Quality checks (`just check` - lint, format, type-check, tests)
|
||||
- ✅ Version update in `src/basic_memory/__init__.py`
|
||||
- ✅ Automatic commit with proper message
|
||||
- ✅ Tag creation and pushing to GitHub
|
||||
- ✅ Release workflow trigger
|
||||
|
||||
### Step 3: Monitor Release Process
|
||||
1. Check that GitHub Actions workflow starts successfully
|
||||
2. Monitor workflow completion at: https://github.com/basicmachines-co/basic-memory/actions
|
||||
3. Verify PyPI publication
|
||||
4. Test installation: `uv tool install basic-memory`
|
||||
|
||||
### Step 4: Post-Release Validation
|
||||
1. Verify GitHub release is created automatically
|
||||
2. Check PyPI publication
|
||||
3. Validate release assets
|
||||
4. Update any post-release documentation
|
||||
|
||||
## Pre-conditions Check
|
||||
Before starting, verify:
|
||||
- [ ] All beta testing is complete
|
||||
- [ ] Critical bugs are fixed
|
||||
- [ ] Breaking changes are documented
|
||||
- [ ] CHANGELOG.md is updated (if needed)
|
||||
- [ ] Version number follows semantic versioning
|
||||
|
||||
## Error Handling
|
||||
- If `just release` fails, examine the error output for specific issues
|
||||
- If quality checks fail, fix issues and retry
|
||||
- If changelog entry missing, update CHANGELOG.md and commit before retrying
|
||||
- If GitHub Actions fail, check workflow logs for debugging
|
||||
|
||||
## Success Output
|
||||
```
|
||||
🎉 Stable Release v0.13.2 Created Successfully!
|
||||
|
||||
🏷️ Tag: v0.13.2
|
||||
📋 GitHub Release: https://github.com/basicmachines-co/basic-memory/releases/tag/v0.13.2
|
||||
📦 PyPI: https://pypi.org/project/basic-memory/0.13.2/
|
||||
🚀 GitHub Actions: Completed
|
||||
|
||||
Install with:
|
||||
uv tool install basic-memory
|
||||
|
||||
Users can now upgrade:
|
||||
uv tool upgrade basic-memory
|
||||
```
|
||||
|
||||
## Context
|
||||
- This creates production releases used by end users
|
||||
- Must pass all quality gates before proceeding
|
||||
- Uses the automated justfile target for consistency
|
||||
- Version is automatically updated in `__init__.py`
|
||||
- Triggers automated GitHub release with changelog
|
||||
- Leverages uv-dynamic-versioning for package version management
|
||||
@@ -0,0 +1,131 @@
|
||||
# /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
|
||||
@@ -0,0 +1,410 @@
|
||||
# /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.
|
||||
|
||||
## Usage
|
||||
```
|
||||
/project:test-live [phase]
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `phase` (optional): Specific test phase to run (`core`, `features`, `edge`, `workflows`, `stress`, or `all`)
|
||||
|
||||
## 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:
|
||||
|
||||
### Pre-Test Setup
|
||||
|
||||
1. **Environment Verification**
|
||||
- Verify basic-memory is installed and accessible via MCP
|
||||
- Check version and confirm it's the expected release
|
||||
- Test MCP connection and tool availability
|
||||
|
||||
2. **Test Project Creation**
|
||||
|
||||
Run the bash `date` command to get the current date/time.
|
||||
|
||||
```
|
||||
Create project: "basic-memory-testing-[timestamp]"
|
||||
Location: ~/basic-memory-testing-[timestamp]
|
||||
Purpose: Record all test observations and results
|
||||
```
|
||||
|
||||
Make sure to switch to the newly created project with the `switch_project()` tool.
|
||||
|
||||
3. **Baseline Documentation**
|
||||
Create initial test session note with:
|
||||
- Test environment details
|
||||
- Version being tested
|
||||
- Test objectives and scope
|
||||
- Start timestamp
|
||||
|
||||
### Phase 1: Core Functionality Validation
|
||||
|
||||
Test all fundamental MCP tools systematically:
|
||||
|
||||
**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
|
||||
|
||||
**read_note Tests:**
|
||||
- Read by title, permalink, memory:// URLs
|
||||
- Non-existent notes (error handling)
|
||||
- Notes with complex formatting
|
||||
- Performance with large 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
|
||||
|
||||
**search_notes Tests:**
|
||||
- Simple text queries
|
||||
- Tag-based searches
|
||||
- Boolean operators and complex queries
|
||||
- Empty/no results scenarios
|
||||
- Performance with growing knowledge base
|
||||
|
||||
**Recent Activity Tests:**
|
||||
- Various timeframes ("today", "1 week", "1d")
|
||||
- Type filtering (if available)
|
||||
- Empty project scenarios
|
||||
- Performance with many recent changes
|
||||
|
||||
**Context Building Tests:**
|
||||
- Different depth levels (1, 2, 3+)
|
||||
- Various timeframes
|
||||
- Relation traversal accuracy
|
||||
- Performance with complex graphs
|
||||
|
||||
### Phase 2: v0.13.0 Feature Deep Dive
|
||||
|
||||
**Project Management:**
|
||||
- Create multiple projects dynamically
|
||||
- Switch between projects mid-conversation
|
||||
- Cross-project operations
|
||||
- Project discovery and status
|
||||
- Default project behavior
|
||||
- Invalid project handling
|
||||
|
||||
**Advanced Note Editing:**
|
||||
- `edit_note` with append operations
|
||||
- Prepend operations
|
||||
- Find/replace with validation
|
||||
- Section replacement under headers
|
||||
- Error scenarios (invalid operations)
|
||||
- Frontmatter preservation
|
||||
|
||||
**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
|
||||
|
||||
### Phase 3: Edge Case Exploration
|
||||
|
||||
**Boundary Testing:**
|
||||
- Very long titles and content (stress limits)
|
||||
- Empty projects and notes
|
||||
- Unicode, emojis, special symbols
|
||||
- Deeply nested folder structures
|
||||
- Circular relations and self-references
|
||||
- Maximum relation depths
|
||||
|
||||
**Error Scenarios:**
|
||||
- Invalid memory:// URLs
|
||||
- Missing files referenced in database
|
||||
- Invalid project names and paths
|
||||
- Malformed note structures
|
||||
- Concurrent operation conflicts
|
||||
|
||||
**Performance Testing:**
|
||||
- Create 100+ notes rapidly
|
||||
- Complex search queries
|
||||
- Deep relation chains (5+ levels)
|
||||
- Rapid successive operations
|
||||
- Memory usage monitoring
|
||||
|
||||
### Phase 4: Real-World Workflow Scenarios
|
||||
|
||||
**Meeting Notes Pipeline:**
|
||||
1. Create meeting notes with action items
|
||||
2. Extract action items using edit_note
|
||||
3. Build relations to project documents
|
||||
4. Update progress incrementally
|
||||
5. Search and track completion
|
||||
|
||||
**Research Knowledge Building:**
|
||||
1. Create research topic hierarchy
|
||||
2. Build complex relation networks
|
||||
3. Add incremental findings over time
|
||||
4. Search for connections and patterns
|
||||
5. Reorganize as knowledge evolves
|
||||
|
||||
**Multi-Project Workflow:**
|
||||
1. Technical documentation project
|
||||
2. Personal recipe collection project
|
||||
3. Learning/course notes project
|
||||
4. Switch contexts during conversation
|
||||
5. Cross-reference related concepts
|
||||
|
||||
**Content Evolution:**
|
||||
1. Start with basic notes
|
||||
2. Enhance with relations and observations
|
||||
3. Reorganize file structure using moves
|
||||
4. Update content with edit operations
|
||||
5. Validate knowledge graph integrity
|
||||
|
||||
### Phase 5: Creative Stress Testing
|
||||
|
||||
**Creative Exploration:**
|
||||
- Rapid project creation/switching patterns
|
||||
- Unusual but valid markdown structures
|
||||
- Creative observation categories
|
||||
- Novel relation types and patterns
|
||||
- Unexpected tool combinations
|
||||
|
||||
**Stress Scenarios:**
|
||||
- Bulk operations (many notes quickly)
|
||||
- Complex nested moves and edits
|
||||
- Deep context building
|
||||
- Complex boolean search expressions
|
||||
- Resource constraint testing
|
||||
|
||||
## Test Observation Format
|
||||
|
||||
Record ALL observations immediately as Basic Memory notes:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Test Session [Phase] YYYY-MM-DD HH:MM
|
||||
tags: [testing, v0.13.0, live-testing, [phase]]
|
||||
permalink: test-session-[phase]-[timestamp]
|
||||
---
|
||||
|
||||
# Test Session [Phase] - [Date/Time]
|
||||
|
||||
## Environment
|
||||
- Basic Memory version: [version]
|
||||
- MCP connection: [status]
|
||||
- Test project: [name]
|
||||
- Phase focus: [description]
|
||||
|
||||
## 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
|
||||
|
||||
### ⚠️ 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
|
||||
|
||||
### 🚀 Enhancements Identified
|
||||
- edit_note could benefit from preview mode #ux-improvement
|
||||
- search_notes needs fuzzy matching for typos #feature-idea
|
||||
- move_note could auto-suggest folder creation #usability
|
||||
|
||||
### 📊 Performance Metrics
|
||||
- Average write_note time: 0.3s
|
||||
- Search with 100+ notes: 0.6s
|
||||
- Project switch overhead: 0.1s
|
||||
- Memory usage: [observed levels]
|
||||
|
||||
## Relations
|
||||
- tests [[Basic Memory v0.13.0]]
|
||||
- part_of [[Live Testing Suite]]
|
||||
- found_issues [[Bug Report: Unicode Search]]
|
||||
- discovered [[Performance Optimization Opportunities]]
|
||||
```
|
||||
|
||||
## Quality Assessment Areas
|
||||
|
||||
**User Experience & Usability:**
|
||||
- Tool instruction clarity and examples
|
||||
- Error message actionability
|
||||
- Response time acceptability
|
||||
- Tool consistency and discoverability
|
||||
- Learning curve and intuitiveness
|
||||
|
||||
**System Behavior:**
|
||||
- Context preservation across operations
|
||||
- memory:// URL navigation reliability
|
||||
- Multi-step workflow cohesion
|
||||
- Edge case graceful handling
|
||||
- Recovery from user errors
|
||||
|
||||
**Documentation Alignment:**
|
||||
- Tool output clarity and helpfulness
|
||||
- Behavior vs. documentation accuracy
|
||||
- Example validity and usefulness
|
||||
- Real-world vs. documented workflows
|
||||
|
||||
**Mental Model Validation:**
|
||||
- Natural user expectation alignment
|
||||
- Surprising behavior identification
|
||||
- Mistake recovery ease
|
||||
- Knowledge graph concept naturalness
|
||||
|
||||
**Performance & Reliability:**
|
||||
- Operation completion times
|
||||
- Consistency across sessions
|
||||
- Scaling behavior with growth
|
||||
- Unexpected slowness identification
|
||||
|
||||
## Error Documentation Protocol
|
||||
|
||||
For each error discovered:
|
||||
|
||||
1. **Immediate Recording**
|
||||
- Create dedicated error note
|
||||
- Include exact reproduction steps
|
||||
- Capture error messages verbatim
|
||||
- Note system state when error occurred
|
||||
|
||||
2. **Error Note Format**
|
||||
```markdown
|
||||
---
|
||||
title: Bug Report - [Short Description]
|
||||
tags: [bug, testing, v0.13.0, [severity]]
|
||||
---
|
||||
|
||||
# Bug Report: [Description]
|
||||
|
||||
## Reproduction Steps
|
||||
1. [Exact steps to reproduce]
|
||||
2. [Include all parameters used]
|
||||
3. [Note any special conditions]
|
||||
|
||||
## Expected Behavior
|
||||
[What should have happened]
|
||||
|
||||
## Actual Behavior
|
||||
[What actually happened]
|
||||
|
||||
## Error Messages
|
||||
```
|
||||
[Exact error text]
|
||||
```
|
||||
|
||||
## Environment
|
||||
- Version: [version]
|
||||
- Project: [name]
|
||||
- Timestamp: [when]
|
||||
|
||||
## Severity
|
||||
- [ ] Critical (blocks major functionality)
|
||||
- [ ] High (impacts user experience)
|
||||
- [ ] Medium (workaround available)
|
||||
- [ ] Low (minor inconvenience)
|
||||
|
||||
## Relations
|
||||
- discovered_during [[Test Session [Phase]]]
|
||||
- affects [[Feature Name]]
|
||||
```
|
||||
|
||||
## Success Metrics Tracking
|
||||
|
||||
**Quantitative Measures:**
|
||||
- Test scenario completion rate
|
||||
- Bug discovery count with severity
|
||||
- Performance benchmark establishment
|
||||
- Tool coverage completeness
|
||||
|
||||
**Qualitative Measures:**
|
||||
- Conversation flow naturalness
|
||||
- Knowledge graph quality
|
||||
- User experience insights
|
||||
- System reliability assessment
|
||||
|
||||
## Test Execution Flow
|
||||
|
||||
1. **Setup Phase** (5 minutes)
|
||||
- Verify environment and create test project
|
||||
- Record baseline system state
|
||||
- Establish performance benchmarks
|
||||
|
||||
2. **Core Testing** (15-20 minutes per phase)
|
||||
- Execute test scenarios systematically
|
||||
- Record observations immediately
|
||||
- Note timestamps for performance tracking
|
||||
- Explore variations when interesting behaviors occur
|
||||
|
||||
3. **Documentation** (5 minutes per phase)
|
||||
- Create phase summary note
|
||||
- Link related test observations
|
||||
- Update running issues list
|
||||
- Record enhancement ideas
|
||||
|
||||
4. **Analysis Phase** (10 minutes)
|
||||
- Review all observations across phases
|
||||
- Identify patterns and trends
|
||||
- Create comprehensive summary report
|
||||
- Generate development recommendations
|
||||
|
||||
## Expected Outcomes
|
||||
|
||||
**System Validation:**
|
||||
- v0.13.0 feature verification in real usage
|
||||
- Edge case discovery beyond unit tests
|
||||
- Performance baseline establishment
|
||||
- Bug identification with reproduction cases
|
||||
|
||||
**Knowledge Base Creation:**
|
||||
- Comprehensive testing documentation
|
||||
- Real usage examples for user guides
|
||||
- Edge case scenarios for future testing
|
||||
- Performance insights for optimization
|
||||
|
||||
**Development Insights:**
|
||||
- Prioritized bug fix list
|
||||
- Enhancement ideas from real usage
|
||||
- Architecture validation results
|
||||
- User experience improvement areas
|
||||
|
||||
## Post-Test Deliverables
|
||||
|
||||
1. **Test Summary Note**
|
||||
- Overall results and findings
|
||||
- Critical issues requiring immediate attention
|
||||
- Enhancement opportunities discovered
|
||||
- System readiness assessment
|
||||
|
||||
2. **Bug Report Collection**
|
||||
- All discovered issues with reproduction steps
|
||||
- Severity and impact assessments
|
||||
- Suggested fixes where applicable
|
||||
|
||||
3. **Performance Baseline**
|
||||
- Timing data for all operations
|
||||
- Scaling behavior observations
|
||||
- Resource usage patterns
|
||||
|
||||
4. **UX Improvement Recommendations**
|
||||
- Usability enhancement suggestions
|
||||
- Documentation improvement areas
|
||||
- Tool design optimization ideas
|
||||
|
||||
5. **Updated TESTING.md**
|
||||
- Incorporate new test scenarios discovered
|
||||
- Update based on real execution experience
|
||||
- Add performance benchmarks and targets
|
||||
|
||||
## Context
|
||||
- Uses installed basic-memory version (not development)
|
||||
- Tests complete MCP→API→DB→File stack
|
||||
- Creates living documentation in Basic Memory itself
|
||||
- Follows integration over isolation philosophy
|
||||
- Focuses on real usage patterns over checklist validation
|
||||
- Generates actionable insights for development team
|
||||
@@ -0,0 +1,60 @@
|
||||
# Git files
|
||||
.git/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Development files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Testing files
|
||||
tests/
|
||||
test-int/
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Build artifacts
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
|
||||
# Virtual environments (uv creates these during build)
|
||||
.venv/
|
||||
venv/
|
||||
.env
|
||||
|
||||
# CI/CD files
|
||||
.github/
|
||||
|
||||
# Documentation (keep README.md and pyproject.toml)
|
||||
docs/
|
||||
CHANGELOG.md
|
||||
CLAUDE.md
|
||||
CONTRIBUTING.md
|
||||
|
||||
# Example files not needed for runtime
|
||||
examples/
|
||||
|
||||
# Local development files
|
||||
.basic-memory/
|
||||
*.db
|
||||
*.sqlite3
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
*.log
|
||||
@@ -0,0 +1,12 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Check user permissions
|
||||
id: check_membership
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let actor;
|
||||
if (context.eventName === 'issue_comment') {
|
||||
actor = context.payload.comment.user.login;
|
||||
} else if (context.eventName === 'pull_request_review_comment') {
|
||||
actor = context.payload.comment.user.login;
|
||||
} else if (context.eventName === 'pull_request_review') {
|
||||
actor = context.payload.review.user.login;
|
||||
} else if (context.eventName === 'issues') {
|
||||
actor = context.payload.issue.user.login;
|
||||
}
|
||||
|
||||
console.log(`Checking permissions for user: ${actor}`);
|
||||
|
||||
// List of explicitly allowed users (organization members)
|
||||
const allowedUsers = [
|
||||
'phernandez',
|
||||
'groksrc',
|
||||
'nellins',
|
||||
'bm-claudeai'
|
||||
];
|
||||
|
||||
if (allowedUsers.includes(actor)) {
|
||||
console.log(`User ${actor} is in the allowed list`);
|
||||
core.setOutput('is_member', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: Check if user has repository permissions
|
||||
try {
|
||||
const collaboration = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: actor
|
||||
});
|
||||
|
||||
const permission = collaboration.data.permission;
|
||||
console.log(`User ${actor} has permission level: ${permission}`);
|
||||
|
||||
// Allow if user has push access or higher (write, maintain, admin)
|
||||
const allowed = ['write', 'maintain', 'admin'].includes(permission);
|
||||
|
||||
core.setOutput('is_member', allowed);
|
||||
|
||||
if (!allowed) {
|
||||
core.notice(`User ${actor} does not have sufficient repository permissions (has: ${permission})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error checking permissions: ${error.message}`);
|
||||
|
||||
// Final fallback: Check if user is a public member of the organization
|
||||
try {
|
||||
const membership = await github.rest.orgs.getMembershipForUser({
|
||||
org: 'basicmachines-co',
|
||||
username: actor
|
||||
});
|
||||
|
||||
const allowed = membership.data.state === 'active';
|
||||
core.setOutput('is_member', allowed);
|
||||
|
||||
if (!allowed) {
|
||||
core.notice(`User ${actor} is not a public member of basicmachines-co organization`);
|
||||
}
|
||||
} catch (membershipError) {
|
||||
console.log(`Error checking organization membership: ${membershipError.message}`);
|
||||
core.setOutput('is_member', false);
|
||||
core.notice(`User ${actor} does not have access to this repository`);
|
||||
}
|
||||
}
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.check_membership.outputs.is_member == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
if: steps.check_membership.outputs.is_member == 'true'
|
||||
id: claude
|
||||
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
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Dev Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
|
||||
jobs:
|
||||
dev-release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
run: |
|
||||
pip install uv
|
||||
|
||||
- name: Install dependencies and build
|
||||
run: |
|
||||
uv venv
|
||||
uv sync
|
||||
uv build
|
||||
|
||||
- name: Check if this is a dev version
|
||||
id: check_version
|
||||
run: |
|
||||
VERSION=$(uv run python -c "import basic_memory; print(basic_memory.__version__)")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
if [[ "$VERSION" == *"dev"* ]]; then
|
||||
echo "is_dev=true" >> $GITHUB_OUTPUT
|
||||
echo "Dev version detected: $VERSION"
|
||||
else
|
||||
echo "is_dev=false" >> $GITHUB_OUTPUT
|
||||
echo "Release version detected: $VERSION, skipping dev release"
|
||||
fi
|
||||
|
||||
- name: Publish dev version to PyPI
|
||||
if: steps.check_version.outputs.is_dev == 'true'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
skip-existing: true # Don't fail if version already exists
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Trigger on version tags like v1.0.0, v0.13.0, etc.
|
||||
workflow_dispatch: # Allow manual triggering for testing
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: basicmachines-co/basic-memory
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -1,96 +1,82 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_type:
|
||||
description: 'Type of version bump (major, minor, patch)'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Trigger on version tags like v1.0.0, v0.13.0, etc.
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency: release
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
outputs:
|
||||
released: ${{ steps.release.outputs.released }}
|
||||
tag: ${{ steps.release.outputs.tag }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Python Semantic Release
|
||||
id: release
|
||||
uses: python-semantic-release/python-semantic-release@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
if: steps.release.outputs.released == 'true'
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
|
||||
- name: Publish to GitHub Release Assets
|
||||
uses: python-semantic-release/publish-action@v9.8.9
|
||||
if: steps.release.outputs.released == 'true'
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ steps.release.outputs.tag }}
|
||||
|
||||
build-macos:
|
||||
needs: release
|
||||
if: needs.release.outputs.released == 'true'
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.tag }}
|
||||
|
||||
- name: Set up Python "3.12"
|
||||
uses: actions/setup-python@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install librsvg
|
||||
run: brew install librsvg
|
||||
|
||||
- name: Install uv
|
||||
run: |
|
||||
pip install uv
|
||||
|
||||
- name: Create virtual env
|
||||
- name: Install dependencies and build
|
||||
run: |
|
||||
uv venv
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync
|
||||
uv build
|
||||
|
||||
- name: Build macOS installer
|
||||
- name: Verify build succeeded
|
||||
run: |
|
||||
make installer-mac
|
||||
xattr -dr com.apple.quarantine "installer/build/Basic Memory Installer.app"
|
||||
# Verify that build artifacts exist
|
||||
ls -la dist/
|
||||
echo "Build completed successfully"
|
||||
|
||||
- name: Zip macOS installer
|
||||
run: |
|
||||
cd installer/build
|
||||
zip -ry "Basic-Memory-Installer-${{ needs.release.outputs.tag }}.zip" "Basic Memory Installer.app"
|
||||
|
||||
- name: Upload macOS installer
|
||||
uses: softprops/action-gh-release@v1
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: installer/build/Basic-Memory-Installer-${{ needs.release.outputs.tag }}.zip
|
||||
tag_name: ${{ needs.release.outputs.tag }}
|
||||
files: |
|
||||
dist/*.whl
|
||||
dist/*.tar.gz
|
||||
generate_release_notes: true
|
||||
tag_name: ${{ github.ref_name }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
|
||||
homebrew:
|
||||
name: Update Homebrew Formula
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Update Homebrew formula
|
||||
uses: mislav/bump-homebrew-formula-action@v3
|
||||
with:
|
||||
# Formula name in homebrew-basic-memory repo
|
||||
formula-name: basic-memory
|
||||
# The tap repository
|
||||
homebrew-tap: basicmachines-co/homebrew-basic-memory
|
||||
# Base branch of the tap repository
|
||||
base-branch: main
|
||||
# Download URL will be automatically constructed from the tag
|
||||
download-url: https://github.com/basicmachines-co/basic-memory/archive/refs/tags/${{ github.ref_name }}.tar.gz
|
||||
# Commit message for the formula update
|
||||
commit-message: |
|
||||
{{formulaName}} {{version}}
|
||||
|
||||
Created by https://github.com/basicmachines-co/basic-memory/actions/runs/${{ github.run_id }}
|
||||
env:
|
||||
# Personal Access Token with repo scope for homebrew-basic-memory repo
|
||||
COMMITTER_TOKEN: ${{ secrets.HOMEBREW_TOKEN }}
|
||||
|
||||
@@ -5,6 +5,12 @@ on:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
# pull_request_target runs on the BASE of the PR, not the merge result.
|
||||
# It has write permissions and access to secrets.
|
||||
# It's useful for PRs from forks or automated PRs but requires careful use for security reasons.
|
||||
# See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
|
||||
pull_request_target:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -29,6 +35,10 @@ jobs:
|
||||
run: |
|
||||
pip install uv
|
||||
|
||||
- name: Install just
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
|
||||
|
||||
- name: Create virtual env
|
||||
run: |
|
||||
uv venv
|
||||
@@ -39,9 +49,9 @@ jobs:
|
||||
|
||||
- name: Run type checks
|
||||
run: |
|
||||
uv run make type-check
|
||||
just type-check
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
uv pip install pytest pytest-cov
|
||||
uv run make test
|
||||
just test
|
||||
|
||||
@@ -42,4 +42,14 @@ ENV/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
/.coverage.*
|
||||
.coverage.*
|
||||
|
||||
# obsidian docs:
|
||||
/docs/.obsidian/
|
||||
/examples/.obsidian/
|
||||
/examples/.basic-memory/
|
||||
|
||||
|
||||
# claude action
|
||||
claude-output
|
||||
**/.claude/settings.local.json
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"basic-memory": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"--directory",
|
||||
"/Users/phernandez/dev/basicmachines/basic-memory",
|
||||
"run",
|
||||
"src/basic_memory/cli/main.py",
|
||||
"mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,761 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.13.8 (2025-06-20)
|
||||
|
||||
### 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
|
||||
|
||||
### 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
|
||||
|
||||
- **#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
|
||||
|
||||
- **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
|
||||
|
||||
### 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
|
||||
|
||||
- **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
|
||||
|
||||
### Documentation
|
||||
|
||||
- **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
|
||||
|
||||
## v0.13.7 (2025-06-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Homebrew Integration** - Automatic Homebrew formula updates
|
||||
- **Documentation** - Add git sign-off reminder to development guide
|
||||
|
||||
## v0.13.6 (2025-06-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Custom Entity Types** - Support for custom entity types in write_note
|
||||
([`7789864`](https://github.com/basicmachines-co/basic-memory/commit/77898644933589c2da9bdd60571d54137a5309ed))
|
||||
- Fixed `entity_type` parameter for `write_note` MCP tool to respect value passed in
|
||||
- Frontmatter `type` field automatically respected when no explicit parameter provided
|
||||
- Maintains backward compatibility with default "note" type
|
||||
|
||||
- **#139**: Fix "UNIQUE constraint failed: entity.permalink" database error
|
||||
([`c6215fd`](https://github.com/basicmachines-co/basic-memory/commit/c6215fd819f9564ead91cf3a950f855241446096))
|
||||
- Implement SQLAlchemy UPSERT strategy to handle permalink conflicts gracefully
|
||||
- Eliminates crashes when creating notes with existing titles in same folders
|
||||
- Seamlessly updates existing entities instead of failing with constraint errors
|
||||
|
||||
- **Database Migration Performance** - Eliminate redundant migration initialization
|
||||
([`84d2aaf`](https://github.com/basicmachines-co/basic-memory/commit/84d2aaf6414dd083af4b0df73f6c8139b63468f6))
|
||||
- Fix duplicate migration calls that slowed system startup
|
||||
- Improve performance with multiple projects (tested with 28+ projects)
|
||||
- Add migration deduplication safeguards with comprehensive test coverage
|
||||
|
||||
- **User Experience** - Correct spelling error in continue_conversation prompt
|
||||
([`b4c26a6`](https://github.com/basicmachines-co/basic-memory/commit/b4c26a613379e6f2ba655efe3d7d8d40c27999e5))
|
||||
- Fix "Chose a folder" → "Choose a folder" in MCP prompt instructions
|
||||
- Improve grammar and clarity in user-facing prompt text
|
||||
|
||||
### Documentation
|
||||
|
||||
- **Website Updates** - Add new website and community links to README
|
||||
([`3fdce68`](https://github.com/basicmachines-co/basic-memory/commit/3fdce683d7ad8b6f4855d7138d5ff2136d4c07bc))
|
||||
|
||||
- **Project Documentation** - Update README.md and CLAUDE.md with latest project information
|
||||
([`782cb2d`](https://github.com/basicmachines-co/basic-memory/commit/782cb2df28803482d209135a054e67cc32d7363e))
|
||||
|
||||
### Technical Improvements
|
||||
|
||||
- **Comprehensive Test Coverage** - Add extensive test suites for new features
|
||||
- Custom entity type validation with 8 new test scenarios
|
||||
- UPSERT behavior testing with edge case coverage
|
||||
- Migration deduplication testing with 6 test scenarios
|
||||
- Database constraint handling validation
|
||||
|
||||
- **Code Quality** - Enhanced error handling and validation
|
||||
- Improved SQLAlchemy patterns with modern UPSERT operations
|
||||
- Better conflict resolution strategies for entity management
|
||||
- Strengthened database consistency guarantees
|
||||
|
||||
### Performance
|
||||
|
||||
- **Database Operations** - Faster startup and improved scalability
|
||||
- Reduced migration overhead for multi-project setups
|
||||
- Optimized conflict resolution for entity creation
|
||||
- Enhanced performance with growing knowledge bases
|
||||
|
||||
### Migration Guide
|
||||
|
||||
This release includes automatic database improvements. No manual migration required:
|
||||
|
||||
- Existing notes and entity types continue working unchanged
|
||||
- New `entity_type` parameter is optional and backward compatible
|
||||
- Database performance improvements apply automatically
|
||||
- All existing MCP tool behavior preserved
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Latest stable release
|
||||
uv tool install basic-memory
|
||||
|
||||
# Update existing installation
|
||||
uv tool upgrade basic-memory
|
||||
```
|
||||
|
||||
## v0.13.5 (2025-06-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **MCP Tools**: Renamed `create_project` tool to `create_memory_project` for namespace isolation
|
||||
- **Namespace**: Continued namespace isolation effort to prevent conflicts with other MCP servers
|
||||
|
||||
### Changes
|
||||
|
||||
- Tool functionality remains identical - only the name changed from `create_project` to `create_memory_project`
|
||||
- All integration tests updated to use the new tool name
|
||||
- Completes namespace isolation for project management tools alongside `list_memory_projects`
|
||||
|
||||
## v0.13.4 (2025-06-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **MCP Tools**: Renamed `list_projects` tool to `list_memory_projects` to avoid naming conflicts with other MCP servers
|
||||
- **Namespace**: Improved tool naming specificity for better MCP server integration and isolation
|
||||
|
||||
### Changes
|
||||
|
||||
- Tool functionality remains identical - only the name changed from `list_projects` to `list_memory_projects`
|
||||
- All integration tests updated to use the new tool name
|
||||
- Better namespace isolation for Basic Memory MCP tools
|
||||
|
||||
## v0.13.3 (2025-06-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Projects**: Fixed case-insensitive project switching where switching succeeded but subsequent operations failed due to session state inconsistency
|
||||
- **Config**: Enhanced config manager with case-insensitive project lookup using permalink-based matching
|
||||
- **MCP Tools**: Updated project management tools to store canonical project names from database instead of user input
|
||||
- **API**: Improved project service to handle both name and permalink lookups consistently
|
||||
|
||||
### Technical Improvements
|
||||
|
||||
- Added comprehensive case-insensitive project switching test coverage with 5 new integration test scenarios
|
||||
- Fixed permalink generation inconsistencies where different case inputs could generate different permalinks
|
||||
- Enhanced project URL construction to use permalinks consistently across all API calls
|
||||
- Improved error handling and session state management for project operations
|
||||
|
||||
### Changes
|
||||
|
||||
- Project switching now preserves canonical project names from database in session state
|
||||
- All project operations use permalink-based lookups for case-insensitive matching
|
||||
- Enhanced test coverage ensures reliable case-insensitive project operations
|
||||
|
||||
## v0.13.2 (2025-06-11)
|
||||
|
||||
### Features
|
||||
|
||||
- **Release Management**: Added automated release management system with version control in `__init__.py`
|
||||
- **Automation**: Implemented justfile targets for `release` and `beta` commands with comprehensive quality gates
|
||||
- **CI/CD**: Enhanced release process with automatic version updates, git tagging, and GitHub release creation
|
||||
|
||||
### Development Experience
|
||||
|
||||
- Added `.claude/commands/release/` directory with automation documentation
|
||||
- Implemented release validation including lint, type-check, and test execution
|
||||
- Streamlined release workflow from manual process to single-command automation
|
||||
|
||||
### Technical Improvements
|
||||
|
||||
- Updated package version management to use actual version numbers instead of dynamic versioning
|
||||
- Added release process documentation and command references
|
||||
- Enhanced justfile with comprehensive release automation targets
|
||||
|
||||
## v0.13.1 (2025-06-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **CLI**: Fixed `basic-memory project` project management commands that were not working in v0.13.0 (#129)
|
||||
- **Projects**: Resolved case sensitivity issues when switching between projects that caused "Project not found" errors (#127)
|
||||
- **API**: Standardized CLI project command endpoints and improved error handling
|
||||
- **Core**: Implemented consistent project name handling using permalinks to avoid case-related conflicts
|
||||
|
||||
### Changes
|
||||
|
||||
- Renamed `basic-memory project sync` command to `basic-memory project sync-config` for clarity
|
||||
- Improved project switching reliability across different case variations
|
||||
- Removed redundant server status messages from CLI error outputs
|
||||
|
||||
## v0.13.0 (2025-06-11)
|
||||
|
||||
### Overview
|
||||
|
||||
Basic Memory v0.13.0 is a **major release** that transforms Basic Memory into a true multi-project knowledge management system. This release introduces fluid project switching, advanced note editing capabilities, robust file management, and production-ready OAuth authentication - all while maintaining full backward compatibility.
|
||||
|
||||
**What's New for Users:**
|
||||
- 🎯 **Switch between projects instantly** during conversations with Claude
|
||||
- ✏️ **Edit notes incrementally** without rewriting entire documents
|
||||
- 📁 **Move and organize notes** with full database consistency
|
||||
- 📖 **View notes as formatted artifacts** for better readability in Claude Desktop
|
||||
- 🔍 **Search frontmatter tags** to discover content more easily
|
||||
- 🔐 **OAuth authentication** for secure remote access
|
||||
- ⚡ **Development builds** automatically published for beta testing
|
||||
|
||||
**Key v0.13.0 Accomplishments:**
|
||||
- ✅ **Complete Project Management System** - Project switching and project-specific operations
|
||||
- ✅ **Advanced Note Editing** - Incremental editing with append, prepend, find/replace, and section operations
|
||||
- ✅ **View Notes as Artifacts in Claude Desktop/Web** - Use the view_note tool to view a note as an artifact
|
||||
- ✅ **File Management System** - Full move operations with database consistency and rollback protection
|
||||
- ✅ **Enhanced Search Capabilities** - Frontmatter tags now searchable, improved content discoverability
|
||||
- ✅ **Unified Database Architecture** - Single app-level database for better performance and project management
|
||||
|
||||
### Major Features
|
||||
|
||||
#### 1. Multiple Project Management
|
||||
|
||||
**Switch between projects instantly during conversations:**
|
||||
|
||||
```
|
||||
💬 "What projects do I have?"
|
||||
🤖 Available projects:
|
||||
• main (current, default)
|
||||
• work-notes
|
||||
• personal-journal
|
||||
• code-snippets
|
||||
|
||||
💬 "Switch to work-notes"
|
||||
🤖 ✓ Switched to work-notes project
|
||||
|
||||
Project Summary:
|
||||
• 47 entities
|
||||
• 125 observations
|
||||
• 23 relations
|
||||
|
||||
💬 "What did I work on yesterday?"
|
||||
🤖 [Shows recent activity from work-notes project]
|
||||
```
|
||||
|
||||
**Key Capabilities:**
|
||||
- **Instant Project Switching**: Change project context mid-conversation without restart
|
||||
- **Project-Specific Operations**: Operations work within the currently active project context
|
||||
- **Project Discovery**: List all available projects with status indicators
|
||||
- **Session Context**: Maintains active project throughout conversation
|
||||
- **Backward Compatibility**: Existing single-project setups continue to work seamlessly
|
||||
|
||||
#### 2. Advanced Note Editing
|
||||
|
||||
**Edit notes incrementally without rewriting entire documents:**
|
||||
|
||||
```python
|
||||
# Append new sections to existing notes
|
||||
edit_note("project-planning", "append", "\n## New Requirements\n- Feature X\n- Feature Y")
|
||||
|
||||
# Prepend timestamps to meeting notes
|
||||
edit_note("meeting-notes", "prepend", "## 2025-05-27 Update\n- Progress update...")
|
||||
|
||||
# Replace specific sections under headers
|
||||
edit_note("api-spec", "replace_section", "New implementation details", section="## Implementation")
|
||||
|
||||
# Find and replace with validation
|
||||
edit_note("config", "find_replace", "v0.13.0", find_text="v0.12.0", expected_replacements=2)
|
||||
```
|
||||
|
||||
**Key Capabilities:**
|
||||
- **Append Operations**: Add content to end of notes (most common use case)
|
||||
- **Prepend Operations**: Add content to beginning of notes
|
||||
- **Section Replacement**: Replace content under specific markdown headers
|
||||
- **Find & Replace**: Simple text replacements with occurrence counting
|
||||
- **Smart Error Handling**: Helpful guidance when operations fail
|
||||
- **Project Context**: Works within the active project with session awareness
|
||||
|
||||
#### 3. Smart File Management
|
||||
|
||||
**Move and organize notes:**
|
||||
|
||||
```python
|
||||
# Simple moves with automatic folder creation
|
||||
move_note("my-note", "work/projects/my-note.md")
|
||||
|
||||
# Organize within the active project
|
||||
move_note("shared-doc", "archive/old-docs/shared-doc.md")
|
||||
|
||||
# Rename operations
|
||||
move_note("old-name", "same-folder/new-name.md")
|
||||
```
|
||||
|
||||
**Key Capabilities:**
|
||||
- **Database Consistency**: Updates file paths, permalinks, and checksums automatically
|
||||
- **Search Reindexing**: Maintains search functionality after moves
|
||||
- **Folder Creation**: Automatically creates destination directories
|
||||
- **Project Isolation**: Operates within the currently active project
|
||||
- **Link Preservation**: Maintains internal links and references
|
||||
|
||||
#### 4. Enhanced Search & Discovery
|
||||
|
||||
**Find content more easily with improved search capabilities:**
|
||||
|
||||
- **Frontmatter Tag Search**: Tags from YAML frontmatter are now indexed and searchable
|
||||
- **Improved Content Discovery**: Search across titles, content, tags, and metadata
|
||||
- **Project-Scoped Search**: Search within the currently active project
|
||||
- **Better Search Quality**: Enhanced FTS5 indexing with tag content inclusion
|
||||
|
||||
**Example:**
|
||||
```yaml
|
||||
---
|
||||
title: Coffee Brewing Methods
|
||||
tags: [coffee, brewing, equipment]
|
||||
---
|
||||
```
|
||||
Now searchable by: "coffee", "brewing", "equipment", or "Coffee Brewing Methods"
|
||||
|
||||
#### 5. Unified Database Architecture
|
||||
|
||||
**Single app-level database for better performance and project management:**
|
||||
|
||||
- **Migration from Per-Project DBs**: Moved from multiple SQLite files to single app database
|
||||
- **Project Isolation**: Proper data separation with project_id foreign keys
|
||||
- **Better Performance**: Optimized queries and reduced file I/O
|
||||
|
||||
### Complete MCP Tool Suite
|
||||
|
||||
#### New Project Management Tools
|
||||
- **`list_projects()`** - Discover and list all available projects with status
|
||||
- **`switch_project(project_name)`** - Change active project context during conversations
|
||||
- **`get_current_project()`** - Show currently active project with statistics
|
||||
- **`set_default_project(project_name)`** - Update default project configuration
|
||||
- **`sync_status()`** - Check file synchronization status and background operations
|
||||
|
||||
#### New Note Operations Tools
|
||||
- **`edit_note()`** - Incremental note editing (append, prepend, find/replace, section replace)
|
||||
- **`move_note()`** - Move notes with database consistency and search reindexing
|
||||
- **`view_note()`** - Display notes as formatted artifacts for better readability in Claude Desktop
|
||||
|
||||
#### Enhanced Existing Tools
|
||||
All existing tools now support:
|
||||
- **Session context awareness** (operates within the currently active project)
|
||||
- **Enhanced error messages** with project context metadata
|
||||
- **Improved response formatting** with project information footers
|
||||
- **Project isolation** ensures operations stay within the correct project boundaries
|
||||
|
||||
|
||||
### User Experience Improvements
|
||||
|
||||
#### Installation Options
|
||||
|
||||
**Multiple ways to install and test Basic Memory:**
|
||||
|
||||
```bash
|
||||
# Stable release
|
||||
uv tool install basic-memory
|
||||
|
||||
# Beta/pre-releases
|
||||
uv tool install basic-memory --pre
|
||||
```
|
||||
|
||||
|
||||
#### Bug Fixes & Quality Improvements
|
||||
|
||||
**Major issues resolved in v0.13.0:**
|
||||
|
||||
- **#118**: Fixed YAML tag formatting to follow standard specification
|
||||
- **#110**: Fixed `--project` flag consistency across all CLI commands
|
||||
- **#107**: Fixed write_note update failures with existing notes
|
||||
- **#93**: Fixed custom permalink handling in frontmatter
|
||||
- **#52**: Enhanced search capabilities with frontmatter tag indexing
|
||||
- **FTS5 Search**: Fixed special character handling in search queries
|
||||
- **Error Handling**: Improved error messages and validation across all tools
|
||||
|
||||
### Breaking Changes & Migration
|
||||
|
||||
#### For Existing Users
|
||||
|
||||
**Automatic Migration**: First run will automatically migrate existing data to the new unified database structure. No manual action required.
|
||||
|
||||
**What Changes:**
|
||||
- Database location: Moved to `~/.basic-memory/memory.db` (unified across projects)
|
||||
- Configuration: Projects defined in `~/.basic-memory/config.json` are synced with database
|
||||
|
||||
**What Stays the Same:**
|
||||
- All existing notes and data remain unchanged
|
||||
- Default project behavior maintained for single-project users
|
||||
- All existing MCP tools continue to work without modification
|
||||
|
||||
### Documentation & Resources
|
||||
|
||||
#### New Documentation
|
||||
- [Project Management Guide](docs/Project%20Management.md) - Multi-project workflows
|
||||
- [Note Editing Guide](docs/Note%20Editing.md) - Advanced editing techniques
|
||||
|
||||
#### Updated Documentation
|
||||
- [README.md](README.md) - Installation options and beta build instructions
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) - Release process and version management
|
||||
- [CLAUDE.md](CLAUDE.md) - Development workflow and CI/CD documentation
|
||||
- [Claude.ai Integration](docs/Claude.ai%20Integration.md) - Updated MCP tool examples
|
||||
|
||||
#### Quick Start Examples
|
||||
|
||||
**Project Switching:**
|
||||
```
|
||||
💬 "Switch to my work project and show recent activity"
|
||||
🤖 [Calls switch_project("work") then recent_activity()]
|
||||
```
|
||||
|
||||
**Note Editing:**
|
||||
```
|
||||
💬 "Add a section about deployment to my API docs"
|
||||
🤖 [Calls edit_note("api-docs", "append", "## Deployment\n...")]
|
||||
```
|
||||
|
||||
**File Organization:**
|
||||
```
|
||||
💬 "Move my old meeting notes to the archive folder"
|
||||
🤖 [Calls move_note("meeting-notes", "archive/old-meetings.md")]
|
||||
```
|
||||
|
||||
|
||||
|
||||
## v0.12.3 (2025-04-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add extra logic for permalink generation with mixed Latin unicode and Chinese characters
|
||||
([`73ea91f`](https://github.com/basicmachines-co/basic-memory/commit/73ea91fe0d1f7ab89b99a1b691d59fe608b7fcbb))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Modify recent_activity args to be strings instead of enums
|
||||
([`3c1cc34`](https://github.com/basicmachines-co/basic-memory/commit/3c1cc346df519e703fae6412d43a92c7232c6226))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
|
||||
## v0.12.2 (2025-04-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Utf8 for all file reads/write/open instead of default platform encoding
|
||||
([#91](https://github.com/basicmachines-co/basic-memory/pull/91),
|
||||
[`2934176`](https://github.com/basicmachines-co/basic-memory/commit/29341763318408ea8f1e954a41046c4185f836c6))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
|
||||
## v0.12.1 (2025-04-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Run migrations and sync when starting mcp
|
||||
([#88](https://github.com/basicmachines-co/basic-memory/pull/88),
|
||||
[`78a3412`](https://github.com/basicmachines-co/basic-memory/commit/78a3412bcff83b46e78e26f8b9fce42ed9e05991))
|
||||
|
||||
|
||||
## v0.12.0 (2025-04-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [bug] `#` character accumulation in markdown frontmatter tags prop
|
||||
([#79](https://github.com/basicmachines-co/basic-memory/pull/79),
|
||||
[`6c19c9e`](https://github.com/basicmachines-co/basic-memory/commit/6c19c9edf5131054ba201a109b37f15c83ef150c))
|
||||
|
||||
- [bug] Cursor has errors calling search tool
|
||||
([#78](https://github.com/basicmachines-co/basic-memory/pull/78),
|
||||
[`9d581ce`](https://github.com/basicmachines-co/basic-memory/commit/9d581cee133f9dde4a0a85118868227390c84161))
|
||||
|
||||
- [bug] Some notes never exit "modified" status
|
||||
([#77](https://github.com/basicmachines-co/basic-memory/pull/77),
|
||||
[`7930ddb`](https://github.com/basicmachines-co/basic-memory/commit/7930ddb2919057be30ceac8c4c19da6aaa1d3e92))
|
||||
|
||||
- [bug] write_note Tool Fails to Update Existing Files in Some Situations.
|
||||
([#80](https://github.com/basicmachines-co/basic-memory/pull/80),
|
||||
[`9bff1f7`](https://github.com/basicmachines-co/basic-memory/commit/9bff1f732e71bc60f88b5c2ce3db5a2aa60b8e28))
|
||||
|
||||
- Set default mcp log level to ERROR
|
||||
([#81](https://github.com/basicmachines-co/basic-memory/pull/81),
|
||||
[`248214c`](https://github.com/basicmachines-co/basic-memory/commit/248214cb114a269ca60ff6398e382f9e2495ad8e))
|
||||
|
||||
- Write_note preserves frontmatter fields in content
|
||||
([#84](https://github.com/basicmachines-co/basic-memory/pull/84),
|
||||
[`3f4d9e4`](https://github.com/basicmachines-co/basic-memory/commit/3f4d9e4d872ebc0ed719c61b24d803c14a9db5e6))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add VS Code instructions to README
|
||||
([#76](https://github.com/basicmachines-co/basic-memory/pull/76),
|
||||
[`43cbb7b`](https://github.com/basicmachines-co/basic-memory/commit/43cbb7b38cc0482ac0a41b6759320e3588186e43))
|
||||
|
||||
- Updated basicmachines.co links to be https
|
||||
([#69](https://github.com/basicmachines-co/basic-memory/pull/69),
|
||||
[`40ea28b`](https://github.com/basicmachines-co/basic-memory/commit/40ea28b0bfc60012924a69ecb76511daa4c7d133))
|
||||
|
||||
### Features
|
||||
|
||||
- Add watch to mcp process ([#83](https://github.com/basicmachines-co/basic-memory/pull/83),
|
||||
[`00c8633`](https://github.com/basicmachines-co/basic-memory/commit/00c8633cfcee75ff640ff8fe81dafeb956281a94))
|
||||
|
||||
- Permalink enhancements ([#82](https://github.com/basicmachines-co/basic-memory/pull/82),
|
||||
[`617e60b`](https://github.com/basicmachines-co/basic-memory/commit/617e60bda4a590678a5f551f10a73e7b47e3b13e))
|
||||
|
||||
- Avoiding "useless permalink values" for files without metadata - Enable permalinks to be updated
|
||||
on move via config setting
|
||||
|
||||
|
||||
## v0.11.0 (2025-03-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Just delete db for reset db instead of using migrations.
|
||||
([#65](https://github.com/basicmachines-co/basic-memory/pull/65),
|
||||
[`0743ade`](https://github.com/basicmachines-co/basic-memory/commit/0743ade5fc07440f95ecfd816ba7e4cfd74bca12))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Make logs for each process - mcp, sync, cli
|
||||
([#64](https://github.com/basicmachines-co/basic-memory/pull/64),
|
||||
[`f1c9570`](https://github.com/basicmachines-co/basic-memory/commit/f1c95709cbffb1b88292547b0b8f29fcca22d186))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update broken "Multiple Projects" link in README.md
|
||||
([#55](https://github.com/basicmachines-co/basic-memory/pull/55),
|
||||
[`3c68b7d`](https://github.com/basicmachines-co/basic-memory/commit/3c68b7d5dd689322205c67637dca7d188111ee6b))
|
||||
|
||||
### Features
|
||||
|
||||
- Add bm command alias for basic-memory
|
||||
([#67](https://github.com/basicmachines-co/basic-memory/pull/67),
|
||||
[`069c0a2`](https://github.com/basicmachines-co/basic-memory/commit/069c0a21c630784e1bf47d2b7de5d6d1f6fadd7a))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Rename search tool to search_notes
|
||||
([#66](https://github.com/basicmachines-co/basic-memory/pull/66),
|
||||
[`b278276`](https://github.com/basicmachines-co/basic-memory/commit/b27827671dc010be3e261b8b221aca6b7f836661))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
|
||||
## v0.10.1 (2025-03-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Make set_default_project also activate project for current session to fix #37
|
||||
([`cbe72be`](https://github.com/basicmachines-co/basic-memory/commit/cbe72be10a646c0b03931bb39aff9285feae47f9))
|
||||
|
||||
This change makes the 'basic-memory project default <name>' command both: 1. Set the default project
|
||||
for future invocations (persistent change) 2. Activate the project for the current session
|
||||
(immediate change)
|
||||
|
||||
Added tests to verify this behavior, which resolves issue #37 where the project name and path
|
||||
weren't changing properly when the default project was changed.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
|
||||
- Make set_default_project also activate project for current session to fix #37
|
||||
([`46c4fd2`](https://github.com/basicmachines-co/basic-memory/commit/46c4fd21645b109af59eb2a0201c7bd849b34a49))
|
||||
|
||||
This change makes the 'basic-memory project default <name>' command both: 1. Set the default project
|
||||
for future invocations (persistent change) 2. Activate the project for the current session
|
||||
(immediate change)
|
||||
|
||||
Added tests to verify this behavior, which resolves issue #37 where the project name and path
|
||||
weren't changing properly when the default project was changed.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Move ai_assistant_guide.md into package resources to fix #39
|
||||
([`390ff9d`](https://github.com/basicmachines-co/basic-memory/commit/390ff9d31ccee85bef732e8140b5eeecd7ee176f))
|
||||
|
||||
This change relocates the AI assistant guide from the static directory into the package resources
|
||||
directory, ensuring it gets properly included in the distribution package and is accessible when
|
||||
installed via pip/uv.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
|
||||
- Move ai_assistant_guide.md into package resources to fix #39
|
||||
([`cc2cae7`](https://github.com/basicmachines-co/basic-memory/commit/cc2cae72c14b380f78ffeb67c2261e4dbee45faf))
|
||||
|
||||
This change relocates the AI assistant guide from the static directory into the package resources
|
||||
directory, ensuring it gets properly included in the distribution package and is accessible when
|
||||
installed via pip/uv.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Preserve custom frontmatter fields when updating notes
|
||||
([`78f234b`](https://github.com/basicmachines-co/basic-memory/commit/78f234b1806b578a0a833e8ee4184015b7369a97))
|
||||
|
||||
Fixes #36 by modifying entity_service.update_entity() to read existing frontmatter from files before
|
||||
updating them. Custom metadata fields such as Status, Priority, and Version are now preserved when
|
||||
notes are updated through the write_note MCP tool.
|
||||
|
||||
Added test case that verifies this behavior by creating a note with custom frontmatter and then
|
||||
updating it.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
|
||||
- Preserve custom frontmatter fields when updating notes
|
||||
([`e716946`](https://github.com/basicmachines-co/basic-memory/commit/e716946b4408d017eca4be720956d5a210b4e6b1))
|
||||
|
||||
Fixes #36 by modifying entity_service.update_entity() to read existing frontmatter from files before
|
||||
updating them. Custom metadata fields such as Status, Priority, and Version are now preserved when
|
||||
notes are updated through the write_note MCP tool.
|
||||
|
||||
Added test case that verifies this behavior by creating a note with custom frontmatter and then
|
||||
updating it.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
### Chores
|
||||
|
||||
- Remove duplicate code in entity_service.py from bad merge
|
||||
([`681af5d`](https://github.com/basicmachines-co/basic-memory/commit/681af5d4505dadc40b4086630f739d76bac9201d))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add help docs to mcp cli tools
|
||||
([`731b502`](https://github.com/basicmachines-co/basic-memory/commit/731b502d36cec253d114403d73b48fab3c47786e))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Add mcp badge, update cli reference, llms-install.md
|
||||
([`b26afa9`](https://github.com/basicmachines-co/basic-memory/commit/b26afa927f98021246cd8b64858e57333595ea90))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Update CLAUDE.md ([#33](https://github.com/basicmachines-co/basic-memory/pull/33),
|
||||
[`dfaf0fe`](https://github.com/basicmachines-co/basic-memory/commit/dfaf0fea9cf5b97d169d51a6276ec70162c21a7e))
|
||||
|
||||
fix spelling in CLAUDE.md: enviroment -> environment Signed-off-by: Ikko Eltociear Ashimine
|
||||
<eltociear@gmail.com>
|
||||
|
||||
### Refactoring
|
||||
|
||||
- Move project stats into projct subcommand
|
||||
([`2a881b1`](https://github.com/basicmachines-co/basic-memory/commit/2a881b1425c73947f037fbe7ac5539c015b62526))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
|
||||
## v0.10.0 (2025-03-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Ai_resource_guide.md path
|
||||
([`da97353`](https://github.com/basicmachines-co/basic-memory/commit/da97353cfc3acc1ceb0eca22ac6af326f77dc199))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Ai_resource_guide.md path
|
||||
([`c4732a4`](https://github.com/basicmachines-co/basic-memory/commit/c4732a47b37dd2e404139fb283b65556c81ce7c9))
|
||||
|
||||
- Ai_resource_guide.md path
|
||||
([`2e9d673`](https://github.com/basicmachines-co/basic-memory/commit/2e9d673e54ad6a63a971db64f01fc2f4e59c2e69))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Don't sync *.tmp files on watch ([#31](https://github.com/basicmachines-co/basic-memory/pull/31),
|
||||
[`6b110b2`](https://github.com/basicmachines-co/basic-memory/commit/6b110b28dd8ba705ebfc0bcb41faf2cb993da2c3))
|
||||
|
||||
Fixes #30
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Drop search_index table on db reindex
|
||||
([`31cca6f`](https://github.com/basicmachines-co/basic-memory/commit/31cca6f913849a0ab8fc944803533e3072e9ef88))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Improve utf-8 support for file reading/writing
|
||||
([#32](https://github.com/basicmachines-co/basic-memory/pull/32),
|
||||
[`eb5e4ec`](https://github.com/basicmachines-co/basic-memory/commit/eb5e4ec6bd4d2fe757087be030d867f4ca1d38ba))
|
||||
|
||||
fixes #29
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
### Chores
|
||||
|
||||
- Remove logfire
|
||||
([`9bb8a02`](https://github.com/basicmachines-co/basic-memory/commit/9bb8a020c3425a02cb3a88f6f02adcd281bccee2))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add glama badge. Fix typos in README.md
|
||||
([#28](https://github.com/basicmachines-co/basic-memory/pull/28),
|
||||
[`9af913d`](https://github.com/basicmachines-co/basic-memory/commit/9af913da4fba7bb4908caa3f15f2db2aa03777ec))
|
||||
|
||||
Signed-off-by: phernandez <paul@basicmachines.co>
|
||||
|
||||
- Update CLAUDE.md with GitHub integration capabilities
|
||||
([#25](https://github.com/basicmachines-co/basic-memory/pull/25),
|
||||
[`fea2f40`](https://github.com/basicmachines-co/basic-memory/commit/fea2f40d1b54d0c533e6d7ee7ce1aa7b83ad9a47))
|
||||
|
||||
This PR updates the CLAUDE.md file to document the GitHub integration capabilities that enable
|
||||
Claude to participate directly in the development workflow.
|
||||
|
||||
### Features
|
||||
|
||||
- Add Smithery integration for easier installation
|
||||
([#24](https://github.com/basicmachines-co/basic-memory/pull/24),
|
||||
[`eb1e7b6`](https://github.com/basicmachines-co/basic-memory/commit/eb1e7b6088b0b3dead9c104ee44174b2baebf417))
|
||||
|
||||
This PR adds support for deploying Basic Memory on the Smithery platform.
|
||||
|
||||
Signed-off-by: bm-claudeai <claude@basicmachines.co>
|
||||
|
||||
|
||||
## v0.9.0 (2025-03-07)
|
||||
|
||||
@@ -472,4 +1228,4 @@ Co-authored-by: phernandez <phernandez@basicmachines.co>
|
||||
### Chores
|
||||
|
||||
- Remove basic-foundation src ref in pyproject.toml
|
||||
([`29fce8b`](https://github.com/basicmachines-co/basic-memory/commit/29fce8b0b922d54d7799bf2534107ee6cfb961b8))
|
||||
([`29fce8b`](https://github.com/basicmachines-co/basic-memory/commit/29fce8b0b922d54d7799bf2534107ee6cfb961b8))
|
||||
@@ -14,15 +14,15 @@ See the [README.md](README.md) file for a project overview.
|
||||
|
||||
### Build and Test Commands
|
||||
|
||||
- Install: `make install` or `pip install -e ".[dev]"`
|
||||
- Run tests: `uv run pytest -p pytest_mock -v` or `make test`
|
||||
- Install: `just install` or `pip install -e ".[dev]"`
|
||||
- Run tests: `uv run pytest -p pytest_mock -v` or `just test`
|
||||
- Single test: `pytest tests/path/to/test_file.py::test_function_name`
|
||||
- Lint: `make lint` or `ruff check . --fix`
|
||||
- Type check: `make type-check` or `uv run pyright`
|
||||
- Format: `make format` or `uv run ruff format .`
|
||||
- Run all code checks: `make check` (runs lint, format, type-check, test)
|
||||
- Create db migration: `make migration m="Your migration message"`
|
||||
- Run development MCP Inspector: `make run-inspector`
|
||||
- Lint: `just lint` or `ruff check . --fix`
|
||||
- Type check: `just type-check` or `uv run pyright`
|
||||
- Format: `just format` or `uv run ruff format .`
|
||||
- Run all code checks: `just check` (runs lint, format, type-check, test)
|
||||
- Create db migration: `just migration "Your migration message"`
|
||||
- Run development MCP Inspector: `just run-inspector`
|
||||
|
||||
### Code Style Guidelines
|
||||
|
||||
@@ -37,6 +37,7 @@ See the [README.md](README.md) file for a project overview.
|
||||
- API uses FastAPI for endpoints
|
||||
- Follow the repository pattern for data access
|
||||
- Tools communicate to api routers via the httpx ASGI client (in process)
|
||||
- avoid using "private" functions in modules or classes (prepended with _)
|
||||
|
||||
### Codebase Architecture
|
||||
|
||||
@@ -63,7 +64,8 @@ See the [README.md](README.md) file for a project overview.
|
||||
- Testing uses pytest with asyncio support (strict mode)
|
||||
- Test database uses in-memory SQLite
|
||||
- Avoid creating mocks in tests in most circumstances.
|
||||
- Each test runs in a standalone enviroment with in memory SQLite and tmp_file directory
|
||||
- Each test runs in a standalone environment with in memory SQLite and tmp_file directory
|
||||
- Do not use mocks in tests if possible. Tests run with an in memory sqlite db, so they are not needed. See fixtures in conftest.py
|
||||
|
||||
## BASIC MEMORY PRODUCT USAGE
|
||||
|
||||
@@ -95,18 +97,29 @@ See the [README.md](README.md) file for a project overview.
|
||||
|
||||
**Content Management:**
|
||||
- `write_note(title, content, folder, tags)` - Create/update markdown notes with semantic observations and relations
|
||||
- `read_note(identifier, page, page_size)` - Read notes by title, permalink, or memory:// URL with knowledge graph
|
||||
awareness
|
||||
- `read_file(path)` - Read raw file content (text, images, binaries) without knowledge graph processing
|
||||
- `read_note(identifier, page, page_size)` - Read notes by title, permalink, or memory:// URL with knowledge graph awareness
|
||||
- `edit_note(identifier, operation, content)` - Edit notes incrementally (append, prepend, find/replace, section replace)
|
||||
- `move_note(identifier, destination_path)` - Move notes with database consistency and search reindexing
|
||||
- `view_note(identifier)` - Display notes as formatted artifacts for better readability in Claude Desktop
|
||||
- `read_content(path)` - Read raw file content (text, images, binaries) without knowledge graph processing
|
||||
- `delete_note(identifier)` - Delete notes from knowledge base
|
||||
|
||||
**Project Management:**
|
||||
- `list_memory_projects()` - List all available projects with status indicators
|
||||
- `switch_project(project_name)` - Switch to different project context during conversations
|
||||
- `get_current_project()` - Show currently active project with statistics
|
||||
- `create_memory_project(name, path, set_default)` - Create new Basic Memory projects
|
||||
- `delete_project(name)` - Delete projects from configuration and database
|
||||
- `set_default_project(name)` - Set default project in config
|
||||
- `sync_status()` - Check file synchronization status and background operations
|
||||
|
||||
**Knowledge Graph Navigation:**
|
||||
- `build_context(url, depth, timeframe)` - Navigate the knowledge graph via memory:// URLs for conversation
|
||||
continuity
|
||||
- `recent_activity(type, depth, timeframe)` - Get recently updated information with specified timeframe (e.g., "
|
||||
1d", "1 week")
|
||||
- `build_context(url, depth, timeframe)` - Navigate the knowledge graph via memory:// URLs for conversation continuity
|
||||
- `recent_activity(type, depth, timeframe)` - Get recently updated information with specified timeframe (e.g., "1d", "1 week")
|
||||
- `list_directory(dir_name, depth, file_name_glob)` - List directory contents with filtering and depth control
|
||||
|
||||
**Search & Discovery:**
|
||||
- `search(query, page, page_size)` - Full-text search across all content with filtering options
|
||||
- `search_notes(query, page, page_size)` - Full-text search across all content with filtering options
|
||||
|
||||
**Visualization:**
|
||||
- `canvas(nodes, edges, title, folder)` - Generate Obsidian canvas files for knowledge graph visualization
|
||||
@@ -114,7 +127,7 @@ See the [README.md](README.md) file for a project overview.
|
||||
- MCP Prompts for better AI interaction:
|
||||
- `ai_assistant_guide()` - Guidance on effectively using Basic Memory tools for AI assistants
|
||||
- `continue_conversation(topic, timeframe)` - Continue previous conversations with relevant historical context
|
||||
- `search(query, after_date)` - Search with detailed, formatted results for better context understanding
|
||||
- `search_notes(query, after_date)` - Search with detailed, formatted results for better context understanding
|
||||
- `recent_activity(timeframe)` - View recently changed items with formatted output
|
||||
- `json_canvas_spec()` - Full JSON Canvas specification for Obsidian visualization
|
||||
|
||||
@@ -130,4 +143,113 @@ of using AI just for code generation, we've developed a true collaborative workf
|
||||
5. Results improve through iterative collaboration and shared understanding
|
||||
|
||||
This approach has allowed us to tackle more complex challenges and build a more robust system than either humans or AI
|
||||
could achieve independently.
|
||||
could achieve independently.
|
||||
|
||||
## GitHub Integration
|
||||
|
||||
Basic Memory uses Claude directly into the development workflow through GitHub:
|
||||
|
||||
### GitHub MCP Tools
|
||||
|
||||
Using the GitHub Model Context Protocol server, Claude can:
|
||||
|
||||
- **Repository Management**:
|
||||
- View repository files and structure
|
||||
- Read file contents
|
||||
- Create new branches
|
||||
- Create and update files
|
||||
|
||||
- **Issue Management**:
|
||||
- Create new issues
|
||||
- Comment on existing issues
|
||||
- Close and update issues
|
||||
- Search across issues
|
||||
|
||||
- **Pull Request Workflow**:
|
||||
- Create pull requests
|
||||
- Review code changes
|
||||
- Add comments to PRs
|
||||
|
||||
This integration enables Claude to participate as a full team member in the development process, not just as a code
|
||||
generation tool. Claude's GitHub account ([bm-claudeai](https://github.com/bm-claudeai)) is a member of the Basic
|
||||
Machines organization with direct contributor access to the codebase.
|
||||
|
||||
### Collaborative Development Process
|
||||
|
||||
With GitHub integration, the development workflow includes:
|
||||
|
||||
1. **Direct code review** - Claude can analyze PRs and provide detailed feedback
|
||||
2. **Contribution tracking** - All of Claude's contributions are properly attributed in the Git history
|
||||
3. **Branch management** - Claude can create feature branches for implementations
|
||||
4. **Documentation maintenance** - Claude can keep documentation updated as the code evolves
|
||||
|
||||
With this integration, the AI assistant is a full-fledged team member rather than just a tool for generating code
|
||||
snippets.
|
||||
|
||||
|
||||
### Basic Memory Pro
|
||||
|
||||
Basic Memory Pro is a desktop GUI application that wraps the basic-memory CLI/MCP tools:
|
||||
|
||||
- Built with Tauri (Rust), React (TypeScript), and a Python FastAPI sidecar
|
||||
- Provides visual knowledge graph exploration and project management
|
||||
- Uses the same core codebase but adds a desktop-friendly interface
|
||||
- Project configuration is shared between CLI and Pro versions
|
||||
- Multiple project support with visual switching interface
|
||||
|
||||
local repo: /Users/phernandez/dev/basicmachines/basic-memory-pro
|
||||
github: https://github.com/basicmachines-co/basic-memory-pro
|
||||
|
||||
## Release and Version Management
|
||||
|
||||
Basic Memory uses `uv-dynamic-versioning` for automatic version management based on git tags:
|
||||
|
||||
### Version Types
|
||||
- **Development versions**: Automatically generated from commits (e.g., `0.12.4.dev26+468a22f`)
|
||||
- **Beta releases**: Created by tagging with beta suffixes (e.g., `v0.13.0b1`, `v0.13.0rc1`)
|
||||
- **Stable releases**: Created by tagging with version numbers (e.g., `v0.13.0`)
|
||||
|
||||
### Release Workflows
|
||||
|
||||
#### Development Builds (Automatic)
|
||||
- Triggered on every push to `main` branch
|
||||
- Publishes dev versions like `0.12.4.dev26+468a22f` to PyPI
|
||||
- Allows continuous testing of latest changes
|
||||
- Users install with: `pip install basic-memory --pre --force-reinstall`
|
||||
|
||||
#### Beta/RC Releases (Manual)
|
||||
- Create beta tag: `git tag v0.13.0b1 && git push origin v0.13.0b1`
|
||||
- Automatically builds and publishes to PyPI as pre-release
|
||||
- Users install with: `pip install basic-memory --pre`
|
||||
- Use for milestone testing before stable release
|
||||
|
||||
#### Stable Releases (Automated)
|
||||
- Use the automated release system: `just release v0.13.0`
|
||||
- Includes comprehensive quality checks (lint, format, type-check, tests)
|
||||
- Automatically updates version in `__init__.py`
|
||||
- Creates git tag and pushes to GitHub
|
||||
- Triggers GitHub Actions workflow for:
|
||||
- PyPI publication
|
||||
- Homebrew formula update (requires HOMEBREW_TOKEN secret)
|
||||
|
||||
**Manual method (legacy):**
|
||||
- Create version tag: `git tag v0.13.0 && git push origin v0.13.0`
|
||||
|
||||
#### Homebrew Formula Updates
|
||||
- Automatically triggered after successful PyPI release
|
||||
- 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`
|
||||
- Add as repository secret named `HOMEBREW_TOKEN` in `basicmachines-co/basic-memory`
|
||||
- Formula updates include new version URL and SHA256 checksum
|
||||
|
||||
### For Development
|
||||
- **Automated releases**: Use `just release v0.13.x` for stable releases and `just beta v0.13.0b1` for beta releases
|
||||
- **Quality gates**: All releases require passing lint, format, type-check, and test suites
|
||||
- **Version management**: Versions automatically derived from git tags via `uv-dynamic-versioning`
|
||||
- **Configuration**: `pyproject.toml` uses `dynamic = ["version"]`
|
||||
- **Release automation**: `__init__.py` updated automatically during release process
|
||||
- **CI/CD**: GitHub Actions handles building and PyPI publication
|
||||
|
||||
## Development Notes
|
||||
- make sure you sign off on commits
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Contributing to Basic Memory
|
||||
|
||||
Thank you for considering contributing to Basic Memory! This document outlines the process for contributing to the project and how to get started as a developer.
|
||||
Thank you for considering contributing to Basic Memory! This document outlines the process for contributing to the
|
||||
project and how to get started as a developer.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -14,8 +15,8 @@ Thank you for considering contributing to Basic Memory! This document outlines t
|
||||
|
||||
2. **Install Dependencies**:
|
||||
```bash
|
||||
# Using make (recommended)
|
||||
make install
|
||||
# Using just (recommended)
|
||||
just install
|
||||
|
||||
# Or using uv
|
||||
uv install -e ".[dev]"
|
||||
@@ -24,10 +25,12 @@ Thank you for considering contributing to Basic Memory! This document outlines t
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
> **Note**: Basic Memory uses [just](https://just.systems) as a modern command runner. Install with `brew install just` or `cargo install just`.
|
||||
|
||||
3. **Run the Tests**:
|
||||
```bash
|
||||
# Run all tests
|
||||
make test
|
||||
just test
|
||||
# or
|
||||
uv run pytest -p pytest_mock -v
|
||||
|
||||
@@ -48,16 +51,16 @@ Thank you for considering contributing to Basic Memory! This document outlines t
|
||||
4. **Check Code Quality**:
|
||||
```bash
|
||||
# Run all checks at once
|
||||
make check
|
||||
just check
|
||||
|
||||
# Or run individual checks
|
||||
make lint # Run linting
|
||||
make format # Format code
|
||||
make type-check # Type checking
|
||||
just lint # Run linting
|
||||
just format # Format code
|
||||
just type-check # Type checking
|
||||
```
|
||||
5. **Test Your Changes**: Ensure all tests pass locally and maintain 100% test coverage.
|
||||
```bash
|
||||
make test
|
||||
just test
|
||||
```
|
||||
6. **Submit a PR**: Submit a pull request with a detailed description of your changes.
|
||||
|
||||
@@ -65,61 +68,64 @@ Thank you for considering contributing to Basic Memory! This document outlines t
|
||||
|
||||
This project is designed for collaborative development between humans and LLMs (Large Language Models):
|
||||
|
||||
1. **CLAUDE.md**: The repository includes a `CLAUDE.md` file that serves as a project guide for both humans and LLMs. This file contains:
|
||||
- Key project information and architectural overview
|
||||
- Development commands and workflows
|
||||
- Code style guidelines
|
||||
- Documentation standards
|
||||
1. **CLAUDE.md**: The repository includes a `CLAUDE.md` file that serves as a project guide for both humans and LLMs.
|
||||
This file contains:
|
||||
- Key project information and architectural overview
|
||||
- Development commands and workflows
|
||||
- Code style guidelines
|
||||
- Documentation standards
|
||||
|
||||
2. **AI-Human Collaborative Workflow**:
|
||||
- We encourage using LLMs like Claude for code generation, reviews, and documentation
|
||||
- When possible, save context in markdown files that can be referenced later
|
||||
- This enables seamless knowledge transfer between different development sessions
|
||||
- Claude can help with implementation details while you focus on architecture and design
|
||||
- We encourage using LLMs like Claude for code generation, reviews, and documentation
|
||||
- When possible, save context in markdown files that can be referenced later
|
||||
- This enables seamless knowledge transfer between different development sessions
|
||||
- Claude can help with implementation details while you focus on architecture and design
|
||||
|
||||
3. **Adding to CLAUDE.md**:
|
||||
- If you discover useful project information or common commands, consider adding them to CLAUDE.md
|
||||
- This helps all contributors (human and AI) maintain consistent knowledge of the project
|
||||
- If you discover useful project information or common commands, consider adding them to CLAUDE.md
|
||||
- This helps all contributors (human and AI) maintain consistent knowledge of the project
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. **Create a Pull Request**: Open a PR against the `main` branch with a clear title and description.
|
||||
2. **Sign the Developer Certificate of Origin (DCO)**: All contributions require signing our DCO, which certifies that you have the right to submit your contributions. This will be automatically checked by our CLA assistant when you create a PR.
|
||||
2. **Sign the Developer Certificate of Origin (DCO)**: All contributions require signing our DCO, which certifies that
|
||||
you have the right to submit your contributions. This will be automatically checked by our CLA assistant when you
|
||||
create a PR.
|
||||
3. **PR Description**: Include:
|
||||
- What the PR changes
|
||||
- Why the change is needed
|
||||
- How you tested the changes
|
||||
- Any related issues (use "Fixes #123" to automatically close issues)
|
||||
- What the PR changes
|
||||
- Why the change is needed
|
||||
- How you tested the changes
|
||||
- Any related issues (use "Fixes #123" to automatically close issues)
|
||||
4. **Code Review**: Wait for code review and address any feedback.
|
||||
5. **CI Checks**: Ensure all CI checks pass.
|
||||
6. **Merge**: Once approved, a maintainer will merge your PR.
|
||||
|
||||
## Developer Certificate of Origin
|
||||
|
||||
By contributing to this project, you agree to the [Developer Certificate of Origin (DCO)](CLA.md). This means you certify that:
|
||||
By contributing to this project, you agree to the [Developer Certificate of Origin (DCO)](CLA.md). This means you
|
||||
certify that:
|
||||
|
||||
- You have the right to submit your contributions
|
||||
- You're not knowingly submitting code with patent or copyright issues
|
||||
- Your contributions are provided under the project's license (AGPL-3.0)
|
||||
|
||||
This is a lightweight alternative to a Contributor License Agreement and helps ensure that all contributions can be properly incorporated into the project and potentially used in commercial applications.
|
||||
This is a lightweight alternative to a Contributor License Agreement and helps ensure that all contributions can be
|
||||
properly incorporated into the project and potentially used in commercial applications.
|
||||
|
||||
### Signing Your Commits
|
||||
|
||||
You can sign your commits in one of two ways:
|
||||
Sign your commit:
|
||||
|
||||
1. **Using the `-s` or `--signoff` flag**:
|
||||
```bash
|
||||
git commit -s -m "Your commit message"
|
||||
```
|
||||
This adds a `Signed-off-by` line to your commit message, certifying that you adhere to the DCO.
|
||||
**Using the `-s` or `--signoff` flag**:
|
||||
|
||||
2. **Configuring Git to automatically sign off**:
|
||||
```bash
|
||||
git config --global alias.cs 'commit -s'
|
||||
```
|
||||
Then use `git cs -m "Your commit message"` to commit with sign-off.
|
||||
```bash
|
||||
git commit -s -m "Your commit message"
|
||||
```
|
||||
|
||||
The sign-off certifies that you have the right to submit your contribution under the project's license and verifies your agreement to the DCO.
|
||||
This adds a `Signed-off-by` line to your commit message, certifying that you adhere to the DCO.
|
||||
|
||||
The sign-off certifies that you have the right to submit your contribution under the project's license and verifies your
|
||||
agreement to the DCO.
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
@@ -135,10 +141,44 @@ The sign-off certifies that you have the right to submit your contribution under
|
||||
|
||||
- **Coverage Target**: We aim for 100% test coverage for all code
|
||||
- **Test Framework**: Use pytest for unit and integration tests
|
||||
- **Mocking**: Use pytest-mock for mocking dependencies
|
||||
- **Mocking**: Use pytest-mock for mocking dependencies only when necessary
|
||||
- **Edge Cases**: Test both normal operation and edge cases
|
||||
- **Database Testing**: Use in-memory SQLite for testing database operations
|
||||
- **Fixtures**: Use pytest fixtures for setup and teardown
|
||||
- **Fixtures**: Use async pytest fixtures for setup and teardown
|
||||
|
||||
## Release Process
|
||||
|
||||
Basic Memory uses automatic versioning based on git tags with `uv-dynamic-versioning`. Here's how releases work:
|
||||
|
||||
### Version Management
|
||||
- **Development versions**: Automatically generated from git commits (e.g., `0.12.4.dev26+468a22f`)
|
||||
- **Beta releases**: Created by tagging with beta suffixes (e.g., `git tag v0.13.0b1`)
|
||||
- **Stable releases**: Created by tagging with version numbers (e.g., `git tag v0.13.0`)
|
||||
|
||||
### Release Workflows
|
||||
|
||||
#### Development Builds
|
||||
- Automatically published to PyPI on every commit to `main`
|
||||
- Version format: `0.12.4.dev26+468a22f` (base version + dev + commit count + hash)
|
||||
- Users install with: `pip install basic-memory --pre --force-reinstall`
|
||||
|
||||
#### Beta Releases
|
||||
1. Create and push a beta tag: `git tag v0.13.0b1 && git push origin v0.13.0b1`
|
||||
2. GitHub Actions automatically builds and publishes to PyPI
|
||||
3. Users install with: `pip install basic-memory --pre`
|
||||
|
||||
#### Stable Releases
|
||||
1. Create and push a version tag: `git tag v0.13.0 && git push origin v0.13.0`
|
||||
2. GitHub Actions automatically:
|
||||
- Builds the package with version `0.13.0`
|
||||
- Creates GitHub release with auto-generated notes
|
||||
- Publishes to PyPI
|
||||
3. Users install with: `pip install basic-memory`
|
||||
|
||||
### For Contributors
|
||||
- No manual version bumping required
|
||||
- Versions are automatically derived from git tags
|
||||
- Focus on code changes, not version management
|
||||
|
||||
## Creating Issues
|
||||
|
||||
@@ -156,4 +196,4 @@ All contributors must follow the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## Thank You!
|
||||
|
||||
Your contributions help make Basic Memory better for everyone. We appreciate your time and effort!
|
||||
Your contributions help make Basic Memory better. We appreciate your time and effort!
|
||||
@@ -0,0 +1,32 @@
|
||||
FROM python:3.12-slim-bookworm
|
||||
|
||||
# Copy uv from official image
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Copy the project into the image
|
||||
ADD . /app
|
||||
|
||||
# Sync the project into a new environment, asserting the lockfile is up to date
|
||||
WORKDIR /app
|
||||
RUN uv sync --locked
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
# Set default data directory and add venv to PATH
|
||||
ENV BASIC_MEMORY_HOME=/app/data \
|
||||
PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD basic-memory --version || exit 1
|
||||
|
||||
# Use the basic-memory entrypoint to run the MCP server with default SSE transport
|
||||
CMD ["basic-memory", "mcp", "--transport", "sse", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -1,67 +0,0 @@
|
||||
.PHONY: install test test-module lint clean format type-check installer-mac installer-win check
|
||||
|
||||
install:
|
||||
pip install -e ".[dev]"
|
||||
|
||||
test:
|
||||
uv run pytest -p pytest_mock -v
|
||||
|
||||
# Run tests for a specific module
|
||||
# Usage: make test-module m=path/to/module.py [cov=module_path]
|
||||
test-module:
|
||||
@if [ -z "$(m)" ]; then \
|
||||
echo "Usage: make test-module m=path/to/module.py [cov=module_path]"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
if [ -z "$(cov)" ]; then \
|
||||
uv run pytest $(m) -v; \
|
||||
else \
|
||||
uv run pytest $(m) -v --cov=$(cov); \
|
||||
fi
|
||||
|
||||
lint:
|
||||
ruff check . --fix
|
||||
|
||||
type-check:
|
||||
uv run pyright
|
||||
|
||||
clean:
|
||||
find . -type f -name '*.pyc' -delete
|
||||
find . -type d -name '__pycache__' -exec rm -r {} +
|
||||
rm -rf installer/build/
|
||||
rm -rf installer/dist/
|
||||
rm -f rw.*.dmg
|
||||
rm -rf dist
|
||||
rm -rf installer/build
|
||||
rm -rf installer/dist
|
||||
rm -f .coverage.*
|
||||
|
||||
format:
|
||||
uv run ruff format .
|
||||
|
||||
# run inspector tool
|
||||
run-inspector:
|
||||
uv run mcp dev src/basic_memory/mcp/main.py
|
||||
|
||||
# Build app installer
|
||||
installer-mac:
|
||||
cd installer && chmod +x make_icons.sh && ./make_icons.sh
|
||||
cd installer && uv run python setup.py bdist_mac
|
||||
|
||||
installer-win:
|
||||
cd installer && uv run python setup.py bdist_win32
|
||||
|
||||
|
||||
update-deps:
|
||||
uv lock --upgrade
|
||||
|
||||
check: lint format type-check test
|
||||
|
||||
|
||||
# Target for generating Alembic migrations with a message from command line
|
||||
migration:
|
||||
@if [ -z "$(m)" ]; then \
|
||||
echo "Usage: make migration m=\"Your migration message\""; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
cd src/basic_memory/alembic && alembic revision --autogenerate -m "$(m)"
|
||||
@@ -1,38 +1,37 @@
|
||||
# Basic Memory
|
||||
|
||||
[](https://www.gnu.org/licenses/agpl-3.0)
|
||||
[](https://badge.fury.io/py/basic-memory)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://github.com/basicmachines-co/basic-memory/actions)
|
||||
[](https://github.com/astral-sh/ruff)
|
||||

|
||||

|
||||
[](https://smithery.ai/server/@basicmachines-co/basic-memory)
|
||||
|
||||
```
|
||||
██████╗ █████╗ ███████╗██╗ ██████╗ ███╗ ███╗███████╗███╗ ███╗ ██████╗ ██████╗ ██╗ ██╗
|
||||
██╔══██╗██╔══██╗██╔════╝██║██╔════╝ ████╗ ████║██╔════╝████╗ ████║██╔═══██╗██╔══██╗╚██╗ ██╔╝
|
||||
██████╔╝███████║███████╗██║██║ ██╔████╔██║█████╗ ██╔████╔██║██║ ██║██████╔╝ ╚████╔╝
|
||||
██╔══██╗██╔══██║╚════██║██║██║ ██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║██║ ██║██╔══██╗ ╚██╔╝
|
||||
██████╔╝██║ ██║███████║██║╚██████╗ ██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║╚██████╔╝██║ ██║ ██║
|
||||
╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
|
||||
|
||||
```
|
||||
# Basic Memory
|
||||
|
||||
Basic Memory lets you build persistent knowledge through natural conversations with Large Language Models (LLMs) like
|
||||
Claude, while keeping everything in simple Markdown files on your computer. It uses the Model Context Protocol (MCP) to
|
||||
enable any compatible LLM to read and write to your local knowledge base.
|
||||
|
||||
LLMs can build context from local knowledge bases.
|
||||
- Website: https://basicmemory.com
|
||||
- Company: https://basicmachines.co
|
||||
- Documentation: https://memory.basicmachines.co
|
||||
- Discord: https://discord.gg/tyvKNccgqN
|
||||
- YouTube: https://www.youtube.com/@basicmachines-co
|
||||
|
||||

|
||||
## Pick up your conversation right where you left off
|
||||
|
||||
Basic Memory provides persistent contextual awareness across sessions through a structured knowledge graph.
|
||||
The system enables LLMs to access and reference prior conversations, track semantic relationships between concepts, and
|
||||
incorporate human edits made directly to knowledge files.
|
||||
- AI assistants can load context from local files in a new conversation
|
||||
- Notes are saved locally as Markdown files in real time
|
||||
- No project knowledge or special prompting required
|
||||
|
||||
https://github.com/user-attachments/assets/a55d8238-8dd0-454a-be4c-8860dbbd0ddc
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install with uv (recommended)
|
||||
uv install basic-memory
|
||||
uv tool install basic-memory
|
||||
|
||||
# Configure Claude Desktop (edit ~/Library/Application Support/Claude/claude_desktop_config.json)
|
||||
# Add this to your config:
|
||||
@@ -56,33 +55,22 @@ uv install basic-memory
|
||||
|
||||
You can view shared context via files in `~/basic-memory` (default directory location).
|
||||
|
||||
You can also install the cli tools to sync files or manage projects.
|
||||
### Alternative Installation via Smithery
|
||||
|
||||
```bash
|
||||
uv tool install basic-memory
|
||||
You can use [Smithery](https://smithery.ai/server/@basicmachines-co/basic-memory) to automatically configure Basic
|
||||
Memory for Claude Desktop:
|
||||
|
||||
# create a new project in a different directory
|
||||
basic-memory project add coffee ./examples/coffee
|
||||
|
||||
# you can set the project to the default
|
||||
basic-memory project default coffee
|
||||
```bash
|
||||
npx -y @smithery/cli install @basicmachines-co/basic-memory --client claude
|
||||
```
|
||||
|
||||
View available projects
|
||||
This installs and configures Basic Memory without requiring manual edits to the Claude Desktop configuration file. Note: The Smithery installation uses their hosted MCP server, while your data remains stored locally as Markdown files.
|
||||
|
||||
```bash
|
||||
basic-memory project list
|
||||
Basic Memory Projects
|
||||
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓
|
||||
┃ Name ┃ Path ┃ Default ┃ Active ┃
|
||||
┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━┩
|
||||
│ main │ ~/basic-memory │ ✓ │ ✓ │
|
||||
│ coffee │ ~/dev/basicmachines/basic-memory/examples/coffee │ │ │
|
||||
└────────┴──────────────────────────────────────────────────┴─────────┴────────┘
|
||||
```
|
||||
### Glama.ai
|
||||
|
||||
Basic Memory will write notes in Markdown format. Open you project directory in your text editor to view project files
|
||||
while you have conversations with an LLM.
|
||||
<a href="https://glama.ai/mcp/servers/o90kttu9ym">
|
||||
<img width="380" height="200" src="https://glama.ai/mcp/servers/o90kttu9ym/badge" alt="basic-memory MCP server" />
|
||||
</a>
|
||||
|
||||
## Why Basic Memory?
|
||||
|
||||
@@ -163,36 +151,14 @@ tags:
|
||||
- affects [[Flavor Extraction]]
|
||||
```
|
||||
|
||||
The note embeds semantic content and links to other topics via simple Markdown
|
||||
formatting.
|
||||
The note embeds semantic content and links to other topics via simple Markdown formatting.
|
||||
|
||||
3. You see this file on your computer in real time in the `~/$HOME/basic-memory` directory:
|
||||
3. You see this file on your computer in real time in the current project directory (default `~/$HOME/basic-memory`).
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Coffee Brewing Methods
|
||||
permalink: coffee-brewing-methods
|
||||
type: note
|
||||
---
|
||||
- Realtime sync is enabled by default starting with v0.12.0
|
||||
- Project switching during conversations is supported starting with v0.13.0
|
||||
|
||||
# Coffee Brewing Methods
|
||||
|
||||
## Observations
|
||||
|
||||
- [method] Pour over provides more clarity and highlights subtle flavors
|
||||
- [technique] Water temperature at 205°F (96°C) extracts optimal compounds
|
||||
- [principle] Freshly ground beans preserve aromatics and flavor
|
||||
- [preference] Medium-light roasts work best for pour over # Added by you
|
||||
|
||||
## Relations
|
||||
|
||||
- relates_to [[Coffee Bean Origins]]
|
||||
- requires [[Proper Grinding Technique]]
|
||||
- affects [[Flavor Extraction]]
|
||||
- pairs_with [[Breakfast Pastries]] # Added by you
|
||||
```
|
||||
|
||||
4. In a new chat with the LLM, you can reference this knowledge:
|
||||
4. In a chat with the LLM, you can reference a topic:
|
||||
|
||||
```
|
||||
Look at `coffee-brewing-methods` for context about pour over coffee
|
||||
@@ -212,15 +178,14 @@ Following relation 'requires [[Proper Grinding Technique]]':
|
||||
- Impact of consistent particle size on extraction
|
||||
```
|
||||
|
||||
Each related document can lead to more context, building a rich semantic understanding of your knowledge base. All of
|
||||
this context comes from standard Markdown files that both humans and LLMs can read and write.
|
||||
Each related document can lead to more context, building a rich semantic understanding of your knowledge base.
|
||||
|
||||
Every time the LLM writes notes,they are saved in local Markdown files that you can:
|
||||
This creates a two-way flow where:
|
||||
|
||||
- Edit in any text editor
|
||||
- Version via git
|
||||
- Back up normally
|
||||
- Share when you want to
|
||||
- Humans write and edit Markdown files
|
||||
- LLMs read and write through the MCP protocol
|
||||
- Sync keeps everything consistent
|
||||
- All knowledge stays in local files.
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
@@ -256,7 +221,7 @@ permalink: <a uri slug>
|
||||
|
||||
Observations are facts about a topic.
|
||||
They can be added by creating a Markdown list with a special format that can reference a `category`, `tags` using a
|
||||
"#" charactor, and an optional `context`.
|
||||
"#" character, and an optional `context`.
|
||||
|
||||
Observation Markdown format:
|
||||
|
||||
@@ -300,72 +265,42 @@ Examples of relations:
|
||||
- documented_in [[Coffee Journal]]
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
## Using with VS Code
|
||||
For one-click installation, click one of the install buttons below...
|
||||
|
||||
Here's a complete example of a note with frontmatter, observations, and relations:
|
||||
[](https://insiders.vscode.dev/redirect/mcp/install?name=basic-memory&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22basic-memory%22%2C%22mcp%22%5D%7D) [](https://insiders.vscode.dev/redirect/mcp/install?name=basic-memory&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22basic-memory%22%2C%22mcp%22%5D%7D&quality=insiders)
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Pour Over Coffee Method
|
||||
type: note
|
||||
permalink: pour-over-coffee-method
|
||||
tags:
|
||||
- brewing
|
||||
- coffee
|
||||
- techniques
|
||||
---
|
||||
You can use Basic Memory with VS Code to easily retrieve and store information while coding. Click the installation buttons above for one-click setup, or follow the manual installation instructions below.
|
||||
|
||||
# Pour Over Coffee Method
|
||||
### Manual Installation
|
||||
|
||||
This note documents the pour over brewing method and my experiences with it.
|
||||
Add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`.
|
||||
|
||||
## Overview
|
||||
|
||||
The pour over method involves pouring hot water through coffee grounds in a filter. The water drains through the coffee
|
||||
and filter into a carafe or cup.
|
||||
|
||||
## Observations
|
||||
|
||||
- [equipment] Hario V60 dripper produces clean, bright cup #gear
|
||||
- [technique] Pour in concentric circles to ensure even extraction
|
||||
- [ratio] 1:16 coffee-to-water ratio works best for balanced flavor
|
||||
- [timing] Total brew time should be 2:30-3:00 minutes for medium roast
|
||||
- [temperature] Water at 205°F (96°C) extracts optimal flavor compounds
|
||||
- [grind] Medium-fine grind similar to table salt texture
|
||||
- [tip] 30-45 second bloom with double the coffee weight in water
|
||||
- [result] Produces a cleaner cup with more distinct flavor notes than immersion methods
|
||||
|
||||
## Relations
|
||||
|
||||
- complements [[Light Roast Beans]]
|
||||
- requires [[Gooseneck Kettle]]
|
||||
- contrasts_with [[French Press Method]]
|
||||
- pairs_with [[Breakfast Pastries]]
|
||||
- documented_in [[Brewing Journal]]
|
||||
- inspired_by [[Japanese Brewing Techniques]]
|
||||
- affects [[Flavor Extraction]]
|
||||
- part_of [[Morning Ritual]]
|
||||
```json
|
||||
{
|
||||
"mcp": {
|
||||
"servers": {
|
||||
"basic-memory": {
|
||||
"command": "uvx",
|
||||
"args": ["basic-memory", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Basic Memory will parse the Markdown and derive the semantic relationships in the content. When you run
|
||||
`basic-memory sync`:
|
||||
Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others.
|
||||
|
||||
1. New and changed files are detected
|
||||
2. Markdown patterns become semantic knowledge:
|
||||
|
||||
- `[tech]` becomes a categorized observation
|
||||
- `[[WikiLink]]` creates a relation in the knowledge graph
|
||||
- Tags and metadata are indexed for search
|
||||
|
||||
3. A SQLite database maintains these relationships for fast querying
|
||||
4. MCP-compatible LLMs can access this knowledge via memory:// URLs
|
||||
|
||||
This creates a two-way flow where:
|
||||
|
||||
- Humans write and edit Markdown files
|
||||
- LLMs read and write through the MCP protocol
|
||||
- Sync keeps everything consistent
|
||||
- All knowledge stays in local files.
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"basic-memory": {
|
||||
"command": "uvx",
|
||||
"args": ["basic-memory", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using with Claude Desktop
|
||||
|
||||
@@ -390,7 +325,8 @@ for OS X):
|
||||
}
|
||||
```
|
||||
|
||||
If you want to use a specific project (see [Multiple Projects](#multiple-projects) below), update your Claude Desktop
|
||||
If you want to use a specific project (see [Multiple Projects](docs/User%20Guide.md#multiple-projects)), update your
|
||||
Claude Desktop
|
||||
config:
|
||||
|
||||
```json
|
||||
@@ -400,9 +336,9 @@ config:
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"basic-memory",
|
||||
"mcp",
|
||||
"--project",
|
||||
"your-project-name"
|
||||
"your-project-name",
|
||||
"mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -411,23 +347,27 @@ config:
|
||||
|
||||
2. Sync your knowledge:
|
||||
|
||||
```bash
|
||||
# One-time sync of local knowledge updates
|
||||
basic-memory sync
|
||||
|
||||
# Run realtime sync process (recommended)
|
||||
basic-memory sync --watch
|
||||
```
|
||||
Basic Memory will sync the files in your project in real time if you make manual edits.
|
||||
|
||||
3. In Claude Desktop, the LLM can now use these tools:
|
||||
|
||||
```
|
||||
write_note(title, content, folder, tags) - Create or update notes
|
||||
read_note(identifier, page, page_size) - Read notes by title or permalink
|
||||
edit_note(identifier, operation, content) - Edit notes incrementally (append, prepend, find/replace)
|
||||
move_note(identifier, destination_path) - Move notes with database consistency
|
||||
view_note(identifier) - Display notes as formatted artifacts for better readability
|
||||
build_context(url, depth, timeframe) - Navigate knowledge graph via memory:// URLs
|
||||
search(query, page, page_size) - Search across your knowledge base
|
||||
search_notes(query, page, page_size) - Search across your knowledge base
|
||||
recent_activity(type, depth, timeframe) - Find recently updated information
|
||||
canvas(nodes, edges, title, folder) - Generate knowledge visualizations
|
||||
list_memory_projects() - List all available projects with status
|
||||
switch_project(project_name) - Switch to different project context
|
||||
get_current_project() - Show current project and statistics
|
||||
create_memory_project(name, path, set_default) - Create new projects
|
||||
delete_project(name) - Delete projects from configuration
|
||||
set_default_project(name) - Set default project
|
||||
sync_status() - Check file synchronization status
|
||||
```
|
||||
|
||||
5. Example prompts to try:
|
||||
@@ -438,265 +378,79 @@ canvas(nodes, edges, title, folder) - Generate knowledge visualizations
|
||||
"Create a canvas visualization of my project components"
|
||||
"Read my notes on the authentication system"
|
||||
"What have I been working on in the past week?"
|
||||
"Switch to my work-notes project"
|
||||
"List all my available projects"
|
||||
"Edit my coffee brewing note to add a new technique"
|
||||
"Move my old meeting notes to the archive folder"
|
||||
```
|
||||
|
||||
## Multiple Projects
|
||||
## Futher info
|
||||
|
||||
Basic Memory supports managing multiple separate knowledge bases through projects. This feature allows you to maintain
|
||||
separate knowledge graphs for different purposes (e.g., personal notes, work projects, research topics).
|
||||
See the [Documentation](https://memory.basicmachines.co/) for more info, including:
|
||||
|
||||
### Managing Projects
|
||||
- [Complete User Guide](https://memory.basicmachines.co/docs/user-guide)
|
||||
- [CLI tools](https://memory.basicmachines.co/docs/cli-reference)
|
||||
- [Managing multiple Projects](https://memory.basicmachines.co/docs/cli-reference#project)
|
||||
- [Importing data from OpenAI/Claude Projects](https://memory.basicmachines.co/docs/cli-reference#import)
|
||||
|
||||
## Installation Options
|
||||
|
||||
### Stable Release
|
||||
```bash
|
||||
pip install basic-memory
|
||||
```
|
||||
|
||||
### Beta/Pre-releases
|
||||
```bash
|
||||
pip install basic-memory --pre
|
||||
```
|
||||
|
||||
### Development Builds
|
||||
Development versions are automatically published on every commit to main with versions like `0.12.4.dev26+468a22f`:
|
||||
```bash
|
||||
pip install basic-memory --pre --force-reinstall
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Run Basic Memory in a container with volume mounting for your Obsidian vault:
|
||||
|
||||
```bash
|
||||
# List all configured projects
|
||||
basic-memory project list
|
||||
# Clone and start with Docker Compose
|
||||
git clone https://github.com/basicmachines-co/basic-memory.git
|
||||
cd basic-memory
|
||||
|
||||
# Add a new project
|
||||
basic-memory project add work ~/work-basic-memory
|
||||
|
||||
# Set the default project
|
||||
basic-memory project default work
|
||||
|
||||
# Remove a project (doesn't delete files)
|
||||
basic-memory project remove personal
|
||||
|
||||
# Show current project
|
||||
basic-memory project current
|
||||
# Edit docker-compose.yml to point to your Obsidian vault
|
||||
# Then start the container
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Using Projects in Commands
|
||||
|
||||
All commands support the `--project` flag to specify which project to use:
|
||||
|
||||
Or use Docker directly:
|
||||
```bash
|
||||
# Sync a specific project
|
||||
basic-memory --project=work sync
|
||||
|
||||
# Run MCP server for a specific project
|
||||
basic-memory --project=personal mcp
|
||||
docker run -d \
|
||||
--name basic-memory-server \
|
||||
-v /path/to/your/obsidian-vault:/data/knowledge:rw \
|
||||
-v basic-memory-config:/root/.basic-memory:rw \
|
||||
ghcr.io/basicmachines-co/basic-memory:latest
|
||||
```
|
||||
|
||||
You can also set the `BASIC_MEMORY_PROJECT` environment variable:
|
||||
|
||||
```bash
|
||||
BASIC_MEMORY_PROJECT=work basic-memory sync
|
||||
```
|
||||
|
||||
### Project Isolation
|
||||
|
||||
Each project maintains:
|
||||
|
||||
- Its own collection of markdown files in the specified directory
|
||||
- A separate SQLite database for that project
|
||||
- Complete knowledge graph isolation from other projects
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Basic Memory is built on some key ideas:
|
||||
|
||||
- Your knowledge should stay in files you control
|
||||
- Both humans and AI should use natural formats
|
||||
- Simple text patterns can capture rich meaning
|
||||
- Local-first doesn't mean feature-poor
|
||||
- Knowledge should persist across conversations
|
||||
- AI assistants should build on past context
|
||||
- File formats should be human-readable and editable
|
||||
- Semantic structure should emerge from natural patterns
|
||||
- Knowledge graphs should be both AI and human navigable
|
||||
- Systems should augment human memory, not replace it
|
||||
|
||||
## Importing Existing Data
|
||||
|
||||
Basic Memory provides CLI commands to import data from various sources, converting them into the structured Markdown
|
||||
format:
|
||||
|
||||
### Claude.ai
|
||||
|
||||
First, request an export of your data from your Claude account. The data will be emailed to you in several files,
|
||||
including
|
||||
`conversations.json` and `projects.json`.
|
||||
|
||||
Import Claude.ai conversation data
|
||||
|
||||
```bash
|
||||
basic-memory import claude conversations
|
||||
```
|
||||
|
||||
The conversations will be turned into Markdown files and placed in the "conversations" folder by default (this can be
|
||||
changed with the --folder arg).
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
Importing chats from conversations.json...writing to .../basic-memory
|
||||
Reading chat data... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
╭────────────────────────────╮
|
||||
│ Import complete! │
|
||||
│ │
|
||||
│ Imported 307 conversations │
|
||||
│ Containing 7769 messages │
|
||||
╰────────────────────────────╯
|
||||
```
|
||||
|
||||
Next, you can run the `sync` command to import the data into basic-memory
|
||||
|
||||
```bash
|
||||
basic-memory sync
|
||||
```
|
||||
|
||||
You can also import project data from Claude.ai
|
||||
|
||||
```bash
|
||||
➜ basic-memory import claude projects
|
||||
Importing projects from projects.json...writing to .../basic-memory/projects
|
||||
Reading project data... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
╭────────────────────────────────╮
|
||||
│ Import complete! │
|
||||
│ │
|
||||
│ Imported 101 project documents │
|
||||
│ Imported 32 prompt templates │
|
||||
╰────────────────────────────────╯
|
||||
|
||||
Run 'basic-memory sync' to index the new files.
|
||||
```
|
||||
|
||||
### OpenAI ChatGPT
|
||||
|
||||
```bash
|
||||
➜ basic-memory import chatgpt
|
||||
Importing chats from conversations.json...writing to .../basic-memory/conversations
|
||||
|
||||
Reading chat data... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
╭────────────────────────────╮
|
||||
│ Import complete! │
|
||||
│ │
|
||||
│ Imported 198 conversations │
|
||||
│ Containing 11777 messages │
|
||||
╰────────────────────────────╯
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Knowledge Graph Memory Server
|
||||
|
||||
From the MCP Server: https://github.com/modelcontextprotocol/servers/tree/main/src/memory
|
||||
|
||||
```bash
|
||||
➜ basic-memory import memory-json
|
||||
Importing from memory.json...writing to .../basic-memory
|
||||
Reading memory.json... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
Creating entities... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
|
||||
╭──────────────────────╮
|
||||
│ Import complete! │
|
||||
│ │
|
||||
│ Created 126 entities │
|
||||
│ Added 252 relations │
|
||||
╰──────────────────────╯
|
||||
```
|
||||
|
||||
## Working with Your Knowledge Base
|
||||
|
||||
Once you've built up a knowledge base, you can interact with it in several ways:
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
Basic Memory provides a powerful CLI for managing your knowledge:
|
||||
|
||||
```bash
|
||||
# See all available commands
|
||||
basic-memory --help
|
||||
|
||||
# Check the status of your knowledge sync
|
||||
basic-memory status
|
||||
|
||||
# Access specific tool functionality directly
|
||||
basic-memory tools
|
||||
|
||||
# Start a continuous sync process
|
||||
basic-memory sync --watch
|
||||
```
|
||||
|
||||
### Obsidian Integration
|
||||
|
||||
Basic Memory works seamlessly with [Obsidian](https://obsidian.md/), a popular knowledge management app:
|
||||
|
||||
1. Point Obsidian to your Basic Memory directory
|
||||
2. Use standard Obsidian features like backlinks and graph view
|
||||
3. See your knowledge graph visually
|
||||
4. Use the canvas visualization generated by Basic Memory
|
||||
|
||||
### File Organization
|
||||
|
||||
Basic Memory is flexible about how you organize your files:
|
||||
|
||||
- Group by topic in folders
|
||||
- Use a flat structure with descriptive filenames
|
||||
- Add custom metadata in frontmatter
|
||||
- Tag files for better searchability
|
||||
|
||||
The system will build the semantic knowledge graph regardless of your file organization preference.
|
||||
|
||||
## Using stdin with Basic Memory's `write_note` Tool
|
||||
|
||||
The `write-note` tool supports reading content from standard input (stdin), allowing for more flexible workflows when
|
||||
creating or updating notes in your Basic Memory knowledge base.
|
||||
|
||||
### Use Cases
|
||||
|
||||
This feature is particularly useful for:
|
||||
|
||||
1. **Piping output from other commands** directly into Basic Memory notes
|
||||
2. **Creating notes with multi-line content** without having to escape quotes or special characters
|
||||
3. **Integrating with AI assistants** like Claude Code that can generate content and pipe it to Basic Memory
|
||||
4. **Processing text data** from files or other sources
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Method 1: Using a Pipe
|
||||
|
||||
You can pipe content from another command into `write_note`:
|
||||
|
||||
```bash
|
||||
# Pipe output of a command into a new note
|
||||
echo "# My Note\n\nThis is a test note" | basic-memory tools write-note --title "Test Note" --folder "notes"
|
||||
|
||||
# Pipe output of a file into a new note
|
||||
cat README.md | basic-memory tools write-note --title "Project README" --folder "documentation"
|
||||
|
||||
# Process text through other tools before saving as a note
|
||||
cat data.txt | grep "important" | basic-memory tools write-note --title "Important Data" --folder "data"
|
||||
```
|
||||
|
||||
### Method 2: Using Heredoc Syntax
|
||||
|
||||
For multi-line content, you can use heredoc syntax:
|
||||
|
||||
```bash
|
||||
# Create a note with heredoc
|
||||
cat << EOF | basic-memory tools write_note --title "Project Ideas" --folder "projects"
|
||||
# Project Ideas for Q2
|
||||
|
||||
## AI Integration
|
||||
- Improve recommendation engine
|
||||
- Add semantic search to product catalog
|
||||
|
||||
## Infrastructure
|
||||
- Migrate to Kubernetes
|
||||
- Implement CI/CD pipeline
|
||||
EOF
|
||||
```
|
||||
|
||||
### Method 3: Input Redirection
|
||||
|
||||
You can redirect input from a file:
|
||||
|
||||
```bash
|
||||
# Create a note from file content
|
||||
basic-memory tools write-note --title "Meeting Notes" --folder "meetings" < meeting_notes.md
|
||||
```
|
||||
See [Docker Setup Guide](docs/Docker.md) for detailed configuration options, multiple project setup, and integration examples.
|
||||
|
||||
## License
|
||||
|
||||
AGPL-3.0
|
||||
|
||||
Built with ♥️ by Basic Machines
|
||||
Contributions are welcome. See the [Contributing](CONTRIBUTING.md) guide for info about setting up the project locally
|
||||
and submitting PRs.
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://www.star-history.com/#basicmachines-co/basic-memory&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=basicmachines-co/basic-memory&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=basicmachines-co/basic-memory&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=basicmachines-co/basic-memory&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
Built with ♥️ by Basic Machines
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.x.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
If you find a vulnerability, please contact hello@basicmachines.co
|
||||
@@ -0,0 +1,83 @@
|
||||
# Docker Compose configuration for Basic Memory
|
||||
# See docs/Docker.md for detailed setup instructions
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
basic-memory:
|
||||
# Use pre-built image (recommended for most users)
|
||||
image: ghcr.io/basicmachines-co/basic-memory:latest
|
||||
|
||||
# Uncomment to build locally instead:
|
||||
# build: .
|
||||
|
||||
container_name: basic-memory-server
|
||||
|
||||
# Volume mounts for knowledge directories and persistent data
|
||||
volumes:
|
||||
|
||||
# Persistent storage for configuration and database
|
||||
- basic-memory-config:/root/.basic-memory:rw
|
||||
|
||||
# Mount your knowledge directory (required)
|
||||
# Change './knowledge' to your actual Obsidian vault or knowledge directory
|
||||
- ./knowledge:/app/data:rw
|
||||
|
||||
# OPTIONAL: Mount additional knowledge directories for multiple projects
|
||||
# - ./work-notes:/app/data/work:rw
|
||||
# - ./personal-notes:/app/data/personal:rw
|
||||
|
||||
# You can edit the project config manually in the mounted config volume
|
||||
# The default project will be configured to use /app/data
|
||||
environment:
|
||||
# Project configuration
|
||||
- BASIC_MEMORY_DEFAULT_PROJECT=main
|
||||
|
||||
# Enable real-time file synchronization (recommended for Docker)
|
||||
- BASIC_MEMORY_SYNC_CHANGES=true
|
||||
|
||||
# Logging configuration
|
||||
- BASIC_MEMORY_LOG_LEVEL=INFO
|
||||
|
||||
# Sync delay in milliseconds (adjust for performance vs responsiveness)
|
||||
- BASIC_MEMORY_SYNC_DELAY=1000
|
||||
|
||||
# Port exposure for HTTP transport (only needed if not using STDIO)
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
# Command with SSE transport (configurable via environment variables above)
|
||||
# IMPORTANT: The SSE and streamable-http endpoints are not secured
|
||||
command: ["basic-memory", "mcp", "--transport", "sse", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
# Container management
|
||||
restart: unless-stopped
|
||||
|
||||
# Health monitoring
|
||||
healthcheck:
|
||||
test: ["CMD", "basic-memory", "--version"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# Optional: Resource limits
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# memory: 512M
|
||||
# cpus: '0.5'
|
||||
# reservations:
|
||||
# memory: 256M
|
||||
# cpus: '0.25'
|
||||
|
||||
volumes:
|
||||
# Named volume for persistent configuration and database
|
||||
# This ensures your configuration and knowledge graph persist across container restarts
|
||||
basic-memory-config:
|
||||
driver: local
|
||||
|
||||
# Network configuration (optional)
|
||||
# networks:
|
||||
# basic-memory-net:
|
||||
# driver: bridge
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"file-explorer": true,
|
||||
"global-search": true,
|
||||
"switcher": true,
|
||||
"graph": true,
|
||||
"backlink": true,
|
||||
"canvas": true,
|
||||
"outgoing-link": true,
|
||||
"tag-pane": true,
|
||||
"properties": false,
|
||||
"page-preview": true,
|
||||
"daily-notes": true,
|
||||
"templates": true,
|
||||
"note-composer": true,
|
||||
"command-palette": true,
|
||||
"slash-command": false,
|
||||
"editor-status": true,
|
||||
"bookmarks": true,
|
||||
"markdown-importer": false,
|
||||
"zk-prefixer": false,
|
||||
"random-note": false,
|
||||
"outline": true,
|
||||
"word-count": true,
|
||||
"slides": false,
|
||||
"audio-recorder": false,
|
||||
"workspaces": false,
|
||||
"file-recovery": true,
|
||||
"publish": true,
|
||||
"sync": true,
|
||||
"webviewer": false
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"siteId": "947ee055a8c6f1a57efa4afa09791e62",
|
||||
"host": "publish-01.obsidian.md",
|
||||
"included": [],
|
||||
"excluded": []
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
{
|
||||
"main": {
|
||||
"id": "9361625269255fc5",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "9220768da355d9a9",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "cd58e5dfc86465e3",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "empty",
|
||||
"state": {},
|
||||
"icon": "lucide-file",
|
||||
"title": "New tab"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "vertical"
|
||||
},
|
||||
"left": {
|
||||
"id": "932aa23fccfbb069",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "c60acde475f2bc34",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "a06f3217ff27ae41",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "file-explorer",
|
||||
"state": {
|
||||
"sortOrder": "alphabetical",
|
||||
"autoReveal": false
|
||||
},
|
||||
"icon": "lucide-folder-closed",
|
||||
"title": "Files"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c338fa22e7339c9a",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "search",
|
||||
"state": {
|
||||
"query": "",
|
||||
"matchingCase": false,
|
||||
"explainSearch": false,
|
||||
"collapseAll": false,
|
||||
"extraContext": false,
|
||||
"sortOrder": "alphabetical"
|
||||
},
|
||||
"icon": "lucide-search",
|
||||
"title": "Search"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "56de0827023b2535",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "bookmarks",
|
||||
"state": {},
|
||||
"icon": "lucide-bookmark",
|
||||
"title": "Bookmarks"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 300
|
||||
},
|
||||
"right": {
|
||||
"id": "c3389d21c08cd71c",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "11a1174f51a1cf86",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "bf2a1fb608e63866",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "backlink",
|
||||
"state": {
|
||||
"collapseAll": false,
|
||||
"extraContext": false,
|
||||
"sortOrder": "alphabetical",
|
||||
"showSearch": false,
|
||||
"searchQuery": "",
|
||||
"backlinkCollapsed": false,
|
||||
"unlinkedCollapsed": true
|
||||
},
|
||||
"icon": "links-coming-in",
|
||||
"title": "Backlinks"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6f08673b57226dc1",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "outgoing-link",
|
||||
"state": {
|
||||
"linksCollapsed": false,
|
||||
"unlinkedCollapsed": true
|
||||
},
|
||||
"icon": "links-going-out",
|
||||
"title": "Outgoing links"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "e987a9643ca3ec81",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "tag",
|
||||
"state": {
|
||||
"sortOrder": "frequency",
|
||||
"useHierarchy": true,
|
||||
"showSearch": false,
|
||||
"searchQuery": ""
|
||||
},
|
||||
"icon": "lucide-tags",
|
||||
"title": "Tags"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "108a72a022d7e501",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "outline",
|
||||
"state": {
|
||||
"followCursor": false,
|
||||
"showSearch": false,
|
||||
"searchQuery": ""
|
||||
},
|
||||
"icon": "lucide-list",
|
||||
"title": "Outline"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 300,
|
||||
"collapsed": true
|
||||
},
|
||||
"left-ribbon": {
|
||||
"hiddenItems": {
|
||||
"publish:Publish changes...": false,
|
||||
"switcher:Open quick switcher": false,
|
||||
"graph:Open graph view": false,
|
||||
"canvas:Create new canvas": false,
|
||||
"daily-notes:Open today's daily note": false,
|
||||
"templates:Insert template": false,
|
||||
"command-palette:Open command palette": false
|
||||
}
|
||||
},
|
||||
"active": "a06f3217ff27ae41",
|
||||
"lastOpenFiles": []
|
||||
}
|
||||
@@ -3,11 +3,33 @@ title: AI Assistant Guide
|
||||
type: note
|
||||
permalink: docs/ai-assistant-guide
|
||||
---
|
||||
> Note: This is an optional document that can be copy/pasted into the project knowledge for an LLM to provide a full description of how it can work with Basic Memory. It is provided as a helpful resource. The tools contain extensive usage description prompts with enable the LLM to understand them.
|
||||
|
||||
You can [download](https://github.com/basicmachines-co/basic-memory/blob/main/docs/AI%20Assistant%20Guide.md) the contents of this file from GitHub
|
||||
# AI Assistant Guide for Basic Memory
|
||||
|
||||
This guide helps you, the AI assistant, use Basic Memory tools effectively when working with users. It covers reading, writing, and navigating knowledge through the Model Context Protocol (MCP).
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Essential Tools:**
|
||||
- `write_note()` - Create/update notes (primary tool)
|
||||
- `read_note()` - Read existing content
|
||||
- `search_notes()` - Find information
|
||||
- `edit_note()` - Modify existing notes incrementally (v0.13.0)
|
||||
- `move_note()` - Organize files with database consistency (v0.13.0)
|
||||
|
||||
**Project Management (v0.13.0):**
|
||||
- `list_projects()` - Show available projects
|
||||
- `switch_project()` - Change active project
|
||||
- `get_current_project()` - Current project info
|
||||
|
||||
**Key Principles:**
|
||||
1. **Build connections** - Rich knowledge graphs > isolated notes
|
||||
2. **Ask permission** - "Would you like me to record this?"
|
||||
3. **Use exact titles** - For accurate `[[WikiLinks]]`
|
||||
4. **Leverage v0.13.0** - Edit incrementally, organize proactively, switch projects contextually
|
||||
|
||||
## Overview
|
||||
|
||||
Basic Memory allows you and users to record context in local Markdown files, building a rich knowledge base through natural conversations. The system automatically creates a semantic knowledge graph from simple text patterns.
|
||||
@@ -35,50 +57,67 @@ Remember that a knowledge graph with 10 heavily connected notes is more valuable
|
||||
|
||||
## Core Tools Reference
|
||||
|
||||
```python
|
||||
# Writing knowledge - THE MOST IMPORTANT TOOL!
|
||||
response = await write_note(
|
||||
title="Search Design", # Required: Note title
|
||||
content="# Search Design\n...", # Required: Note content
|
||||
folder="specs", # Optional: Folder to save in
|
||||
tags=["search", "design"], # Optional: Tags for categorization
|
||||
verbose=True # Optional: Get parsing details
|
||||
)
|
||||
### Essential Content Management
|
||||
|
||||
# Reading knowledge
|
||||
content = await read_note("Search Design") # By title
|
||||
content = await read_note("specs/search-design") # By path
|
||||
content = await read_note("memory://specs/search") # By memory URL
|
||||
|
||||
# Searching for knowledge
|
||||
results = await search(
|
||||
query="authentication system", # Text to search for
|
||||
page=1, # Optional: Pagination
|
||||
page_size=10 # Optional: Results per page
|
||||
**Writing knowledge** (most important tool):
|
||||
```
|
||||
write_note(
|
||||
title="Search Design",
|
||||
content="# Search Design\n...",
|
||||
folder="specs", # Optional
|
||||
tags=["search", "design"], # v0.13.0: now searchable!
|
||||
project="work-notes" # v0.13.0: target specific project
|
||||
)
|
||||
```
|
||||
|
||||
# Building context from the knowledge graph
|
||||
context = await build_context(
|
||||
url="memory://specs/search", # Starting point
|
||||
depth=2, # Optional: How many hops to follow
|
||||
timeframe="1 month" # Optional: Recent timeframe
|
||||
)
|
||||
**Reading knowledge:**
|
||||
```
|
||||
read_note("Search Design") # By title
|
||||
read_note("specs/search-design") # By path
|
||||
read_note("memory://specs/search") # By memory URL
|
||||
```
|
||||
|
||||
# Checking recent changes
|
||||
activity = await recent_activity(
|
||||
type="all", # Optional: Entity types to include
|
||||
depth=1, # Optional: Related items to include
|
||||
timeframe="1 week" # Optional: Time window
|
||||
)
|
||||
**Viewing notes as formatted artifacts (Claude Desktop):**
|
||||
```
|
||||
view_note("Search Design") # Creates readable artifact
|
||||
view_note("specs/search-design") # By permalink
|
||||
view_note("memory://specs/search") # By memory URL
|
||||
```
|
||||
|
||||
# Creating a knowledge visualization
|
||||
canvas_result = await canvas(
|
||||
nodes=[{"id": "note1", "label": "Search Design"}], # Nodes to display
|
||||
edges=[{"from": "note1", "to": "note2"}], # Connections
|
||||
title="Project Overview", # Canvas title
|
||||
folder="diagrams" # Storage location
|
||||
**Incremental editing** (v0.13.0):
|
||||
```
|
||||
edit_note(
|
||||
identifier="Search Design", # Must be EXACT title/permalink (strict matching)
|
||||
operation="append", # append, prepend, find_replace, replace_section
|
||||
content="\n## New Section\nContent here..."
|
||||
)
|
||||
```
|
||||
**⚠️ Important:** `edit_note` requires exact identifiers (no fuzzy matching). Use `search_notes()` first if uncertain.
|
||||
|
||||
**File organization** (v0.13.0):
|
||||
```
|
||||
move_note(
|
||||
identifier="Old Note", # Must be EXACT title/permalink (strict matching)
|
||||
destination="archive/old-note.md" # Folders created automatically
|
||||
)
|
||||
```
|
||||
**⚠️ Important:** `move_note` requires exact identifiers (no fuzzy matching). Use `search_notes()` first if uncertain.
|
||||
|
||||
### Project Management (v0.13.0)
|
||||
|
||||
```
|
||||
list_projects() # Show available projects
|
||||
switch_project("work-notes") # Change active project
|
||||
get_current_project() # Current project info
|
||||
```
|
||||
|
||||
### Search & Discovery
|
||||
|
||||
```
|
||||
search_notes("authentication system") # v0.13.0: includes frontmatter tags
|
||||
build_context("memory://specs/search") # Follow knowledge graph connections
|
||||
recent_activity(timeframe="1 week") # Check what's been updated
|
||||
```
|
||||
|
||||
## memory:// URLs Explained
|
||||
|
||||
@@ -152,10 +191,34 @@ Users will interact with Basic Memory in patterns like:
|
||||
Human: "What were our decisions about auth?"
|
||||
|
||||
You: Let me find that information for you.
|
||||
[Use search() to find relevant notes]
|
||||
[Use search_notes() to find relevant notes]
|
||||
[Then build_context() to understand connections]
|
||||
```
|
||||
|
||||
4. **Editing existing notes (v0.13.0)**:
|
||||
```
|
||||
Human: "Add a section about deployment to my API documentation"
|
||||
|
||||
You: I'll add that section to your existing documentation.
|
||||
[Use edit_note() with operation="append" to add new content]
|
||||
```
|
||||
|
||||
5. **Project management (v0.13.0)**:
|
||||
```
|
||||
Human: "Switch to my work project and show recent activity"
|
||||
|
||||
You: I'll switch to your work project and check what's been updated recently.
|
||||
[Use switch_project() then recent_activity()]
|
||||
```
|
||||
|
||||
6. **File organization (v0.13.0)**:
|
||||
```
|
||||
Human: "Move my old meeting notes to the archive folder"
|
||||
|
||||
You: I'll organize those notes for you.
|
||||
[Use move_note() to relocate files with database consistency]
|
||||
```
|
||||
|
||||
## Key Things to Remember
|
||||
|
||||
1. **Files are Truth**
|
||||
@@ -172,16 +235,27 @@ Users will interact with Basic Memory in patterns like:
|
||||
- Combine related information
|
||||
|
||||
3. **Writing Knowledge Wisely**
|
||||
- Using the same title+folder will overwrite existing notes
|
||||
- Structure content with clear headings and sections
|
||||
- Use semantic markup for observations and relations
|
||||
- Same title+folder overwrites existing notes
|
||||
- Structure with clear headings and semantic markup
|
||||
- Use tags for searchability (v0.13.0: frontmatter tags indexed)
|
||||
- Keep files organized in logical folders
|
||||
|
||||
4. **Leverage v0.13.0 Features**
|
||||
- **Edit incrementally**: Use `edit_note()` for small changes vs rewriting
|
||||
- **Switch projects**: Change context when user mentions different work areas
|
||||
- **Organize proactively**: Move old content to archive folders
|
||||
- **Cross-project operations**: Create notes in specific projects while maintaining context
|
||||
|
||||
## Common Knowledge Patterns
|
||||
|
||||
### Capturing Decisions
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Coffee Brewing Methods
|
||||
tags: [coffee, brewing, pour-over, techniques] # v0.13.0: Now searchable!
|
||||
---
|
||||
|
||||
# Coffee Brewing Methods
|
||||
|
||||
## Context
|
||||
@@ -194,11 +268,13 @@ Pour over is my preferred method for light to medium roasts because it highlight
|
||||
- [technique] Blooming the coffee grounds for 30 seconds improves extraction #brewing
|
||||
- [preference] Water temperature between 195-205°F works best #temperature
|
||||
- [equipment] Gooseneck kettle provides better control of water flow #tools
|
||||
- [timing] Total brew time of 3-4 minutes produces optimal extraction #process
|
||||
|
||||
## Relations
|
||||
- pairs_with [[Light Roast Beans]]
|
||||
- contrasts_with [[French Press Method]]
|
||||
- requires [[Proper Grinding Technique]]
|
||||
- part_of [[Morning Coffee Routine]]
|
||||
```
|
||||
|
||||
### Recording Project Structure
|
||||
@@ -239,119 +315,77 @@ Discussed strategies for improving the chocolate chip cookie recipe.
|
||||
- pairs_with [[Homemade Ice Cream]]
|
||||
```
|
||||
|
||||
## v0.13.0 Workflow Examples
|
||||
|
||||
### Multi-Project Conversations
|
||||
|
||||
**User:** "I need to update my work documentation and also add a personal recipe note."
|
||||
|
||||
**Workflow:**
|
||||
1. `list_projects()` - Check available projects
|
||||
2. `write_note(title="Sprint Planning", project="work-notes")` - Work content
|
||||
3. `write_note(title="Weekend Recipes", project="personal")` - Personal content
|
||||
|
||||
### Incremental Note Building
|
||||
|
||||
**User:** "Add a troubleshooting section to my setup guide."
|
||||
|
||||
**Workflow:**
|
||||
1. `edit_note(identifier="Setup Guide", operation="append", content="\n## Troubleshooting\n...")`
|
||||
|
||||
**User:** "Update the authentication section in my API docs."
|
||||
|
||||
**Workflow:**
|
||||
1. `edit_note(identifier="API Documentation", operation="replace_section", section="## Authentication")`
|
||||
|
||||
### Smart File Organization
|
||||
|
||||
**User:** "My notes are getting messy in the main folder."
|
||||
|
||||
**Workflow:**
|
||||
1. `move_note("Old Meeting Notes", "archive/2024/old-meetings.md")`
|
||||
2. `move_note("Project Notes", "projects/client-work/notes.md")`
|
||||
|
||||
### Creating Effective Relations
|
||||
|
||||
When creating relations, you can:
|
||||
1. Reference existing entities by their exact title
|
||||
2. Create forward references to entities that don't exist yet
|
||||
When creating relations:
|
||||
1. **Reference existing entities** by their exact title: `[[Exact Title]]`
|
||||
2. **Create forward references** to entities that don't exist yet - they'll be linked automatically when created
|
||||
3. **Search first** to find existing entities to reference
|
||||
4. **Use meaningful relation types**: `implements`, `requires`, `part_of` vs generic `relates_to`
|
||||
|
||||
```python
|
||||
# Example workflow for creating notes with effective relations
|
||||
async def create_note_with_effective_relations():
|
||||
# Search for existing entities to reference
|
||||
search_results = await search("travel")
|
||||
existing_entities = [result.title for result in search_results.primary_results]
|
||||
|
||||
# Check if specific entities exist
|
||||
packing_tips_exists = "Packing Tips" in existing_entities
|
||||
japan_travel_exists = "Japan Travel Guide" in existing_entities
|
||||
|
||||
# Prepare relations section - include both existing and forward references
|
||||
relations_section = "## Relations\n"
|
||||
|
||||
# Existing reference - exact match to known entity
|
||||
if packing_tips_exists:
|
||||
relations_section += "- references [[Packing Tips]]\n"
|
||||
else:
|
||||
# Forward reference - will be linked when that entity is created later
|
||||
relations_section += "- references [[Packing Tips]]\n"
|
||||
|
||||
# Another possible reference
|
||||
if japan_travel_exists:
|
||||
relations_section += "- part_of [[Japan Travel Guide]]\n"
|
||||
|
||||
# You can also check recently modified notes to reference them
|
||||
recent = await recent_activity(timeframe="1 week")
|
||||
recent_titles = [item.title for item in recent.primary_results]
|
||||
|
||||
if "Transportation Options" in recent_titles:
|
||||
relations_section += "- relates_to [[Transportation Options]]\n"
|
||||
|
||||
# Always include meaningful forward references, even if they don't exist yet
|
||||
relations_section += "- located_in [[Tokyo]]\n"
|
||||
relations_section += "- visited_during [[Spring 2023 Trip]]\n"
|
||||
|
||||
# Now create the note with both verified and forward relations
|
||||
content = f"""# Tokyo Neighborhood Guide
|
||||
|
||||
## Overview
|
||||
Details about different Tokyo neighborhoods and their unique characteristics.
|
||||
**Example workflow:**
|
||||
1. `search_notes("travel")` to find existing travel-related notes
|
||||
2. Reference found entities: `- part_of [[Japan Travel Guide]]`
|
||||
3. Add forward references: `- located_in [[Tokyo]]` (even if Tokyo note doesn't exist yet)
|
||||
|
||||
## Observations
|
||||
- [area] Shibuya is a busy shopping district #shopping
|
||||
- [transportation] Yamanote Line connects major neighborhoods #transit
|
||||
- [recommendation] Visit Shimokitazawa for vintage shopping #unique
|
||||
- [tip] Get a Suica card for easy train travel #convenience
|
||||
## Common Issues & Solutions
|
||||
|
||||
{relations_section}
|
||||
"""
|
||||
|
||||
result = await write_note(
|
||||
title="Tokyo Neighborhood Guide",
|
||||
content=content,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# You can check which relations were resolved and which are forward references
|
||||
if result and 'relations' in result:
|
||||
resolved = [r['to_name'] for r in result['relations'] if r.get('target_id')]
|
||||
forward_refs = [r['to_name'] for r in result['relations'] if not r.get('target_id')]
|
||||
|
||||
print(f"Resolved relations: {resolved}")
|
||||
print(f"Forward references that will be resolved later: {forward_refs}")
|
||||
```
|
||||
**Missing Content:**
|
||||
- Try `search_notes()` with broader terms if `read_note()` fails
|
||||
- Use fuzzy matching: search for partial titles
|
||||
|
||||
## Error Handling
|
||||
**Forward References:**
|
||||
- These are normal! Basic Memory links them automatically when target notes are created
|
||||
- Inform users: "I've created forward references that will be linked when you create those notes"
|
||||
|
||||
Common issues to watch for:
|
||||
**Sync Issues:**
|
||||
- If information seems outdated, suggest `basic-memory sync`
|
||||
- Use `recent_activity()` to check if content is current
|
||||
|
||||
1. **Missing Content**
|
||||
```python
|
||||
try:
|
||||
content = await read_note("Document")
|
||||
except:
|
||||
# Try search instead
|
||||
results = await search("Document")
|
||||
if results and results.primary_results:
|
||||
# Found something similar
|
||||
content = await read_note(results.primary_results[0].permalink)
|
||||
```
|
||||
|
||||
2. **Forward References (Unresolved Relations)**
|
||||
```python
|
||||
response = await write_note(..., verbose=True)
|
||||
# Check for forward references (unresolved relations)
|
||||
forward_refs = []
|
||||
for relation in response.get('relations', []):
|
||||
if not relation.get('target_id'):
|
||||
forward_refs.append(relation.get('to_name'))
|
||||
|
||||
if forward_refs:
|
||||
# This is a feature, not an error! Inform the user about forward references
|
||||
print(f"Note created with forward references to: {forward_refs}")
|
||||
print("These will be automatically linked when those notes are created.")
|
||||
|
||||
# Optionally suggest creating those entities now
|
||||
print("Would you like me to create any of these notes now to complete the connections?")
|
||||
```
|
||||
|
||||
3. **Sync Issues**
|
||||
```python
|
||||
# If information seems outdated
|
||||
activity = await recent_activity(timeframe="1 hour")
|
||||
if not activity or not activity.primary_results:
|
||||
print("It seems there haven't been recent updates. You might need to run 'basic-memory sync'.")
|
||||
```
|
||||
**Strict Mode for Edit/Move Operations:**
|
||||
- `edit_note()` and `move_note()` require **exact identifiers** (no fuzzy matching for safety)
|
||||
- If identifier not found: use `search_notes()` first to find the exact title/permalink
|
||||
- Error messages will guide you to find correct identifiers
|
||||
- Example workflow:
|
||||
```
|
||||
# ❌ This might fail if identifier isn't exact
|
||||
edit_note("Meeting Note", "append", "content")
|
||||
|
||||
# ✅ Safe approach: search first, then use exact result
|
||||
results = search_notes("meeting")
|
||||
edit_note("Meeting Notes 2024", "append", "content") # Use exact title from search
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
@@ -367,7 +401,7 @@ Common issues to watch for:
|
||||
- **Create deliberate relations**: Connect each note to at least 2-3 related entities
|
||||
- **Use existing entities**: Before creating a new relation, search for existing entities
|
||||
- **Verify wikilinks**: When referencing `[[Entity]]`, use exact titles of existing notes
|
||||
- **Check accuracy**: Use `search()` or `recent_activity()` to confirm entity titles
|
||||
- **Check accuracy**: Use `search_notes()` or `recent_activity()` to confirm entity titles
|
||||
- **Use precise relation types**: Choose specific relation types that convey meaning (e.g., "implements" instead of "relates_to")
|
||||
- **Consider bidirectional relations**: When appropriate, create inverse relations in both entities
|
||||
|
||||
@@ -393,4 +427,5 @@ Common issues to watch for:
|
||||
- Offer to create summaries of scattered information
|
||||
- Suggest potential missing relations: "I notice this might relate to [topic], would you like me to add that connection?"
|
||||
|
||||
|
||||
Built with ♥️ by Basic Machines
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
---
|
||||
title: CLI Reference
|
||||
type: note
|
||||
permalink: docs/cli-reference
|
||||
---
|
||||
|
||||
# CLI Reference
|
||||
|
||||
Basic Memory provides command line tools for managing your knowledge base. This reference covers the available commands and their options.
|
||||
|
||||
## Core Commands
|
||||
|
||||
### sync
|
||||
|
||||
Keeps files and the knowledge graph in sync:
|
||||
|
||||
```bash
|
||||
# Basic sync
|
||||
basic-memory sync
|
||||
|
||||
# Watch for changes
|
||||
basic-memory sync --watch
|
||||
|
||||
# Sync specific folder
|
||||
basic-memory sync path/to/folder
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--watch`: Continuously monitor for changes
|
||||
- `--verbose`: Show detailed output
|
||||
- `PATH`: Optional path to sync (defaults to ~/basic-memory)
|
||||
|
||||
### import
|
||||
|
||||
Imports external knowledge sources:
|
||||
|
||||
```bash
|
||||
# Claude conversations
|
||||
basic-memory import claude conversations
|
||||
|
||||
# Claude projects
|
||||
basic-memory import claude projects
|
||||
|
||||
# ChatGPT history
|
||||
basic-memory import chatgpt
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--folder PATH`: Target folder for imported content
|
||||
- `--overwrite`: Replace existing files
|
||||
- `--skip-existing`: Keep existing files
|
||||
|
||||
### status
|
||||
|
||||
Shows system status information:
|
||||
|
||||
```bash
|
||||
# Basic status check
|
||||
basic-memory status
|
||||
|
||||
# Detailed status
|
||||
basic-memory status --verbose
|
||||
|
||||
# JSON output
|
||||
basic-memory status --json
|
||||
```
|
||||
|
||||
|
||||
### project
|
||||
|
||||
Create multiple projects to manage your knowledge.
|
||||
|
||||
```bash
|
||||
# List all configured projects
|
||||
basic-memory project list
|
||||
|
||||
# Add a new project
|
||||
basic-memory project add work ~/work-basic-memory
|
||||
|
||||
# Set the default project
|
||||
basic-memory project default work
|
||||
|
||||
# Remove a project (doesn't delete files)
|
||||
basic-memory project remove personal
|
||||
|
||||
# Show current project
|
||||
basic-memory project current
|
||||
```
|
||||
|
||||
> Be sure to restart Claude Desktop after changing projects.
|
||||
|
||||
#### Using Projects in Commands
|
||||
|
||||
All commands support the `--project` flag to specify which project to use:
|
||||
|
||||
```bash
|
||||
# Sync a specific project
|
||||
basic-memory --project=work sync
|
||||
|
||||
# Run MCP server for a specific project
|
||||
basic-memory --project=personal mcp
|
||||
```
|
||||
|
||||
You can also set the `BASIC_MEMORY_PROJECT` environment variable:
|
||||
|
||||
```bash
|
||||
BASIC_MEMORY_PROJECT=work basic-memory sync
|
||||
```
|
||||
|
||||
### help
|
||||
|
||||
The full list of commands and help for each can be viewed with the `--help` argument.
|
||||
|
||||
```
|
||||
✗ basic-memory --help
|
||||
|
||||
Usage: basic-memory [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Basic Memory - Local-first personal knowledge management system.
|
||||
|
||||
╭─ Options ─────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ --project -p TEXT Specify which project to use │
|
||||
│ [env var: BASIC_MEMORY_PROJECT] │
|
||||
│ [default: None] │
|
||||
│ --version -V Show version information and exit. │
|
||||
│ --install-completion Install completion for the current shell. │
|
||||
│ --show-completion Show completion for the current shell, to copy it or │
|
||||
│ customize the installation. │
|
||||
│ --help Show this message and exit. │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭─ Commands ────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ sync Sync knowledge files with the database. │
|
||||
│ status Show sync status between files and database. │
|
||||
│ reset Reset database (drop all tables and recreate). │
|
||||
│ mcp Run the MCP server for Claude Desktop integration. │
|
||||
│ import Import data from various sources │
|
||||
│ tool Direct access to MCP tools via CLI │
|
||||
│ project Manage multiple Basic Memory projects │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
## Initial Setup
|
||||
|
||||
```bash
|
||||
# Install Basic Memory
|
||||
uv install basic-memory
|
||||
|
||||
# First sync
|
||||
basic-memory sync
|
||||
|
||||
# Start watching mode
|
||||
basic-memory sync --watch
|
||||
```
|
||||
|
||||
> **Important**: You need to install Basic Memory via `uv` or `pip` to use the command line tools, see [[Getting Started with Basic Memory#Installation]].
|
||||
|
||||
## Regular Usage
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
basic-memory status
|
||||
|
||||
# Import new content
|
||||
basic-memory import claude conversations
|
||||
|
||||
# Sync changes
|
||||
basic-memory sync
|
||||
|
||||
# Sync changes continuously
|
||||
basic-memory sync --watch
|
||||
```
|
||||
|
||||
## Maintenance Tasks
|
||||
|
||||
```bash
|
||||
# Check system status in detail
|
||||
basic-memory status --verbose
|
||||
|
||||
# Full resync of all files
|
||||
basic-memory sync
|
||||
|
||||
# Import updates to specific folder
|
||||
basic-memory import claude conversations --folder new
|
||||
```
|
||||
|
||||
|
||||
## Using stdin with Basic Memory's `write_note` Tool
|
||||
|
||||
The `write-note` tool supports reading content from standard input (stdin), allowing for more flexible workflows when creating or updating notes in your Basic Memory knowledge base.
|
||||
|
||||
### Use Cases
|
||||
|
||||
This feature is particularly useful for:
|
||||
|
||||
1. **Piping output from other commands** directly into Basic Memory notes
|
||||
2. **Creating notes with multi-line content** without having to escape quotes or special characters
|
||||
3. **Integrating with AI assistants** like Claude Code that can generate content and pipe it to Basic Memory
|
||||
4. **Processing text data** from files or other sources
|
||||
|
||||
### Basic Usage
|
||||
|
||||
#### Method 1: Using a Pipe
|
||||
|
||||
You can pipe content from another command into `write_note`:
|
||||
|
||||
```bash
|
||||
# Pipe output of a command into a new note
|
||||
echo "# My Note\n\nThis is a test note" | basic-memory tool write-note --title "Test Note" --folder "notes"
|
||||
|
||||
# Pipe output of a file into a new note
|
||||
cat README.md | basic-memory tool write-note --title "Project README" --folder "documentation"
|
||||
|
||||
# Process text through other tools before saving as a note
|
||||
cat data.txt | grep "important" | basic-memory tool write-note --title "Important Data" --folder "data"
|
||||
```
|
||||
|
||||
#### Method 2: Using Heredoc Syntax
|
||||
|
||||
For multi-line content, you can use heredoc syntax:
|
||||
|
||||
```bash
|
||||
# Create a note with heredoc
|
||||
cat << EOF | basic-memory tool write_note --title "Project Ideas" --folder "projects"
|
||||
# Project Ideas for Q2
|
||||
|
||||
## AI Integration
|
||||
- Improve recommendation engine
|
||||
- Add semantic search to product catalog
|
||||
|
||||
## Infrastructure
|
||||
- Migrate to Kubernetes
|
||||
- Implement CI/CD pipeline
|
||||
EOF
|
||||
```
|
||||
|
||||
#### Method 3: Input Redirection
|
||||
|
||||
You can redirect input from a file:
|
||||
|
||||
```bash
|
||||
# Create a note from file content
|
||||
basic-memory tool write-note --title "Meeting Notes" --folder "meetings" < meeting_notes.md
|
||||
```
|
||||
|
||||
#### Integration with Claude Code
|
||||
|
||||
This feature works well with Claude Code in the terminal:
|
||||
|
||||
In a Claude Code session, let Claude know he can use the basic-memory tools, then he can execute them via the cli:
|
||||
|
||||
```
|
||||
⏺ Bash(echo "# Test Note from Claude\n\nThis is a test note created by Claude to test the stdin functionality." | basic-memory tool write-note --title "Claude Test Note" --folder "test" --tags "test" --tags "claude")…
|
||||
⎿ # Created test/Claude Test Note.md (23e00eec)
|
||||
permalink: test/claude-test-note
|
||||
|
||||
## Tags
|
||||
- test, claude
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
### Sync Conflicts
|
||||
|
||||
If you encounter a file changed during sync error:
|
||||
1. Check the file referenced in the error message
|
||||
2. Resolve any conflicts manually
|
||||
3. Run sync again
|
||||
|
||||
### Import Errors
|
||||
|
||||
If import fails:
|
||||
1. Check that the source file is in the correct format
|
||||
2. Verify permissions on the target directory
|
||||
3. Use --verbose flag for detailed error information
|
||||
|
||||
### Status Issues
|
||||
|
||||
If status shows problems:
|
||||
1. Note any unresolved relations or warnings
|
||||
2. Run a full sync to attempt automatic resolution
|
||||
3. Check file permissions if database access errors occur
|
||||
|
||||
|
||||
## Relations
|
||||
- used_by [[Getting Started with Basic Memory]] (Installation instructions)
|
||||
- complements [[User Guide]] (How to use Basic Memory)
|
||||
- relates_to [[Introduction to Basic Memory]] (System overview)
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
title: Canvas Visualizations
|
||||
type: note
|
||||
permalink: docs/canvas
|
||||
tags:
|
||||
- visualization
|
||||
- mapping
|
||||
- obsidian
|
||||
---
|
||||
|
||||
# Canvas Visualizations
|
||||
|
||||
Basic Memory can create visual knowledge maps using Obsidian's Canvas feature. These visualizations help you understand relationships between concepts, map out processes, and visualize your knowledge structure.
|
||||
|
||||
## Creating Canvas Visualizations
|
||||
|
||||
Ask Claude to create a visualization by describing what you want to map:
|
||||
|
||||
```
|
||||
You: "Create a canvas visualization of my project components and their relationships."
|
||||
|
||||
You: "Make a concept map showing the main themes from our discussion about climate change."
|
||||
|
||||
You: "Can you make a canvas diagram of the perfect pour over method?"
|
||||
```
|
||||
|
||||
![[Canvas.png]]
|
||||
|
||||
## Types of Visualizations
|
||||
|
||||
Basic Memory can create several types of visual maps:
|
||||
|
||||
### Document Maps
|
||||
Visualize connections between your notes and documents
|
||||
|
||||
### Concept Maps
|
||||
Create visual representations of ideas and their relationships
|
||||
|
||||
### Process Diagrams
|
||||
Map workflows, sequences, and procedures
|
||||
|
||||
### Thematic Analysis
|
||||
Organize ideas around central themes
|
||||
|
||||
### Relationship Networks
|
||||
Show how different entities relate to each other
|
||||
|
||||
## Visualization Sources
|
||||
|
||||
Claude can create visualizations based on:
|
||||
|
||||
### Documents in Your Knowledge Base
|
||||
```
|
||||
You: "Create a canvas showing the connections between my project planning documents"
|
||||
```
|
||||
|
||||
### Conversation Content
|
||||
```
|
||||
You: "Make a canvas visualization of the main points we just discussed"
|
||||
```
|
||||
|
||||
### Search Results
|
||||
```
|
||||
You: "Find all my notes about psychology and create a visual map of the concepts"
|
||||
```
|
||||
|
||||
### Themes and Relationships
|
||||
```
|
||||
You: "Create a visual map showing how different philosophical schools relate to each other"
|
||||
```
|
||||
|
||||
## Visualization Workflow
|
||||
|
||||
1. **Request a visualization** by describing what you want to see
|
||||
2. **Claude creates the canvas file** in your Basic Memory directory
|
||||
3. **Open the file in Obsidian** to view the visualization
|
||||
4. **Refine the visualization** by asking Claude for adjustments:
|
||||
```
|
||||
You: "Could you reorganize the canvas to group related components together?"
|
||||
|
||||
You: "Please add more detail about the connection between these two concepts."
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
Behind the scenes, Claude:
|
||||
|
||||
1. Creates a `.canvas` file in JSON format
|
||||
2. Adds nodes for each concept or document
|
||||
3. Creates edges to represent relationships
|
||||
4. Sets positions for visual clarity
|
||||
5. Includes any relevant metadata
|
||||
|
||||
The resulting file is fully compatible with Obsidian's Canvas feature and can be edited directly in Obsidian.
|
||||
|
||||
## Tips for Effective Visualizations
|
||||
|
||||
- **Be specific** about what you want to visualize
|
||||
- **Specify the level of detail** you need
|
||||
- **Mention the visualization type** you want (concept map, process flow, etc.)
|
||||
- **Start simple** and ask for refinements
|
||||
- **Provide context** about what documents or concepts to include
|
||||
|
||||
## Relations
|
||||
- enhances [[Obsidian Integration]] (Using Basic Memory with Obsidian)
|
||||
- visualizes [[Knowledge Format]] (The structure of your knowledge)
|
||||
- complements [[User Guide]] (Ways to use Basic Memory)
|
||||
@@ -0,0 +1,334 @@
|
||||
# Docker Setup Guide
|
||||
|
||||
Basic Memory can be run in Docker containers to provide a consistent, isolated environment for your knowledge management
|
||||
system. This is particularly useful for integrating with existing Dockerized MCP servers or for deployment scenarios.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option 1: Using Pre-built Images (Recommended)
|
||||
|
||||
Basic Memory provides pre-built Docker images on GitHub Container Registry that are automatically updated with each release.
|
||||
|
||||
1. **Use the official image directly:**
|
||||
```bash
|
||||
docker run -d \
|
||||
--name basic-memory-server \
|
||||
-p 8000:8000 \
|
||||
-v /path/to/your/obsidian-vault:/app/data:rw \
|
||||
-v basic-memory-config:/root/.basic-memory:rw \
|
||||
ghcr.io/basicmachines-co/basic-memory:latest
|
||||
```
|
||||
|
||||
2. **Or use Docker Compose with the pre-built image:**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
basic-memory:
|
||||
image: ghcr.io/basicmachines-co/basic-memory:latest
|
||||
container_name: basic-memory-server
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- /path/to/your/obsidian-vault:/app/data:rw
|
||||
- basic-memory-config:/root/.basic-memory:rw
|
||||
environment:
|
||||
- BASIC_MEMORY_DEFAULT_PROJECT=main
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
### Option 2: Using Docker Compose (Building Locally)
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone https://github.com/basicmachines-co/basic-memory.git
|
||||
cd basic-memory
|
||||
```
|
||||
|
||||
2. **Update the docker-compose.yml:**
|
||||
Edit the volume mount to point to your Obsidian vault:
|
||||
```yaml
|
||||
volumes:
|
||||
# Change './obsidian-vault' to your actual directory path
|
||||
- /path/to/your/obsidian-vault:/app/data:rw
|
||||
```
|
||||
|
||||
3. **Start the container:**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Option 3: Using Docker CLI
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -t basic-memory .
|
||||
|
||||
# Run with volume mounting
|
||||
docker run -d \
|
||||
--name basic-memory-server \
|
||||
-v /path/to/your/obsidian-vault:/app/data:rw \
|
||||
-v basic-memory-config:/root/.basic-memory:rw \
|
||||
-e BASIC_MEMORY_DEFAULT_PROJECT=main \
|
||||
basic-memory
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Volume Mounts
|
||||
|
||||
Basic Memory requires several volume mounts for proper operation:
|
||||
|
||||
1. **Knowledge Directory** (Required):
|
||||
```yaml
|
||||
- /path/to/your/obsidian-vault:/app/data:rw
|
||||
```
|
||||
Mount your Obsidian vault or knowledge base directory.
|
||||
|
||||
2. **Configuration and Database** (Recommended):
|
||||
```yaml
|
||||
- basic-memory-config:/root/.basic-memory:rw
|
||||
```
|
||||
Persistent storage for configuration and SQLite database.
|
||||
|
||||
You can edit the basic-memory config.json file located in the /root/.basic-memory/config.json after Basic Memory starts.
|
||||
|
||||
3. **Multiple Projects** (Optional):
|
||||
```yaml
|
||||
- /path/to/project1:/app/data/project1:rw
|
||||
- /path/to/project2:/app/data/project2:rw
|
||||
```
|
||||
|
||||
You can edit the basic-memory config.json file located in the /root/.basic-memory/config.json
|
||||
|
||||
## CLI Commands via Docker
|
||||
|
||||
You can run Basic Memory CLI commands inside the container using `docker exec`:
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
docker exec basic-memory-server basic-memory status
|
||||
|
||||
# Sync files
|
||||
docker exec basic-memory-server basic-memory sync
|
||||
|
||||
# Show help
|
||||
docker exec basic-memory-server basic-memory --help
|
||||
```
|
||||
|
||||
### Managing Projects with Volume Mounts
|
||||
|
||||
When using Docker volumes, you'll need to configure projects to point to your mounted directories:
|
||||
|
||||
1. **Check current configuration:**
|
||||
```bash
|
||||
docker exec basic-memory-server cat /root/.basic-memory/config.json
|
||||
```
|
||||
|
||||
2. **Add a project for your mounted volume:**
|
||||
```bash
|
||||
# If you mounted /path/to/your/vault to /app/data
|
||||
docker exec basic-memory-server basic-memory project create my-vault /app/data
|
||||
|
||||
# Set it as default
|
||||
docker exec basic-memory-server basic-memory project set-default my-vault
|
||||
```
|
||||
|
||||
3. **Sync the new project:**
|
||||
```bash
|
||||
docker exec basic-memory-server basic-memory sync
|
||||
```
|
||||
|
||||
### Example: Setting up an Obsidian Vault
|
||||
|
||||
If you mounted your Obsidian vault like this in docker-compose.yml:
|
||||
```yaml
|
||||
volumes:
|
||||
- /Users/yourname/Documents/ObsidianVault:/app/data:rw
|
||||
```
|
||||
|
||||
Then configure it:
|
||||
```bash
|
||||
# Create project pointing to mounted vault
|
||||
docker exec basic-memory-server basic-memory project create obsidian /app/data
|
||||
|
||||
# Set as default
|
||||
docker exec basic-memory-server basic-memory project set-default obsidian
|
||||
|
||||
# Sync to index all files
|
||||
docker exec basic-memory-server basic-memory sync
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Configure Basic Memory using environment variables:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
|
||||
# Default project
|
||||
- BASIC_MEMORY_DEFAULT_PROJECT=main
|
||||
|
||||
# Enable real-time sync
|
||||
- BASIC_MEMORY_SYNC_CHANGES=true
|
||||
|
||||
# Logging level
|
||||
- BASIC_MEMORY_LOG_LEVEL=INFO
|
||||
|
||||
# Sync delay in milliseconds
|
||||
- BASIC_MEMORY_SYNC_DELAY=1000
|
||||
```
|
||||
|
||||
## File Permissions
|
||||
|
||||
### Linux/macOS
|
||||
|
||||
Ensure your knowledge directories have proper permissions:
|
||||
|
||||
```bash
|
||||
# Make directories readable/writable
|
||||
chmod -R 755 /path/to/your/obsidian-vault
|
||||
|
||||
# If using specific user/group
|
||||
chown -R $USER:$USER /path/to/your/obsidian-vault
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
When using Docker Desktop on Windows, ensure the directories are shared:
|
||||
|
||||
1. Open Docker Desktop
|
||||
2. Go to Settings → Resources → File Sharing
|
||||
3. Add your knowledge directory path
|
||||
4. Apply & Restart
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **File Watching Not Working:**
|
||||
- Ensure volume mounts are read-write (`:rw`)
|
||||
- Check directory permissions
|
||||
- On Linux, may need to increase inotify limits:
|
||||
```bash
|
||||
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
|
||||
sudo sysctl -p
|
||||
```
|
||||
|
||||
2. **Configuration Not Persisting:**
|
||||
- Use named volumes for `/root/.basic-memory`
|
||||
- Check volume mount permissions
|
||||
|
||||
3. **Network Connectivity:**
|
||||
- For HTTP transport, ensure port 8000 is exposed
|
||||
- Check firewall settings
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Run with debug logging:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- BASIC_MEMORY_LOG_LEVEL=DEBUG
|
||||
```
|
||||
|
||||
View logs:
|
||||
|
||||
```bash
|
||||
docker-compose logs -f basic-memory
|
||||
```
|
||||
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Docker Security:**
|
||||
The container runs as root for simplicity. For production, consider additional security measures.
|
||||
|
||||
2. **Volume Permissions:**
|
||||
Ensure mounted directories have appropriate permissions and don't expose sensitive data.
|
||||
|
||||
3. **Network Security:**
|
||||
If using HTTP transport, consider using reverse proxy with SSL/TLS and authentication if the endpoint is available on
|
||||
a network.
|
||||
|
||||
4. **IMPORTANT:** The HTTP endpoints have no authorization. They should not be exposed on a public network.
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Claude Desktop with Docker
|
||||
|
||||
The recommended way to connect Claude Desktop to the containerized Basic Memory is using `mcp-proxy`, which converts the HTTP transport to STDIO that Claude Desktop expects:
|
||||
|
||||
1. **Start the Docker container:**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. **Configure Claude Desktop** to use mcp-proxy:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"basic-memory": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-proxy",
|
||||
"http://localhost:8000/mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
For Docker-specific issues:
|
||||
|
||||
1. Check the [troubleshooting section](#troubleshooting) above
|
||||
2. Review container logs: `docker-compose logs basic-memory`
|
||||
3. Verify volume mounts: `docker inspect basic-memory-server`
|
||||
4. Test file permissions: `docker exec basic-memory-server ls -la /root`
|
||||
|
||||
For general Basic Memory support, see the main [README](../README.md)
|
||||
and [documentation](https://memory.basicmachines.co/).
|
||||
|
||||
## GitHub Container Registry Images
|
||||
|
||||
### Available Images
|
||||
|
||||
Pre-built Docker images are available on GitHub Container Registry at [`ghcr.io/basicmachines-co/basic-memory`](https://github.com/basicmachines-co/basic-memory/pkgs/container/basic-memory).
|
||||
|
||||
**Supported architectures:**
|
||||
- `linux/amd64` (Intel/AMD x64)
|
||||
- `linux/arm64` (ARM64, including Apple Silicon)
|
||||
|
||||
**Available tags:**
|
||||
- `latest` - Latest stable release
|
||||
- `v0.13.8`, `v0.13.7`, etc. - Specific version tags
|
||||
- `v0.13`, `v0.12`, etc. - Major.minor tags
|
||||
|
||||
### Automated Builds
|
||||
|
||||
Docker images are automatically built and published when new releases are tagged:
|
||||
|
||||
1. **Release Process:** When a git tag matching `v*` (e.g., `v0.13.8`) is pushed, the CI workflow automatically:
|
||||
- Builds multi-platform Docker images
|
||||
- Pushes to GitHub Container Registry with appropriate tags
|
||||
- Uses native GitHub integration for seamless publishing
|
||||
|
||||
2. **CI/CD Pipeline:** The Docker workflow includes:
|
||||
- Multi-platform builds (AMD64 and ARM64)
|
||||
- Layer caching for faster builds
|
||||
- Automatic tagging with semantic versioning
|
||||
- Security scanning and optimization
|
||||
|
||||
### Setup Requirements (For Maintainers)
|
||||
|
||||
GitHub Container Registry integration is automatic for this repository:
|
||||
|
||||
1. **No external setup required** - GHCR is natively integrated with GitHub
|
||||
2. **Automatic permissions** - Uses `GITHUB_TOKEN` with `packages: write` permission
|
||||
3. **Public by default** - Images are automatically public for public repositories
|
||||
|
||||
The Docker CI workflow (`.github/workflows/docker.yml`) handles everything automatically when version tags are pushed.
|
||||
@@ -1,176 +0,0 @@
|
||||
---
|
||||
title: Getting Started with Basic Memory
|
||||
type: note
|
||||
permalink: docs/getting-started
|
||||
---
|
||||
|
||||
# Getting Started with Basic Memory
|
||||
|
||||
This guide will help you install Basic Memory, configure it with Claude Desktop, and create your first knowledge notes through conversations.
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Install Basic Memory
|
||||
|
||||
```bash
|
||||
# Install with uv (recommended)
|
||||
uv install basic-memory
|
||||
|
||||
# Or with pip
|
||||
pip install basic-memory
|
||||
```
|
||||
|
||||
> **Important**: You need to install Basic Memory using one of the commands above to use the command line tools. The `uvx` command mentioned in the Claude Desktop configuration is only for enabling Claude to access Basic Memory.
|
||||
|
||||
### 2. Configure Claude Desktop
|
||||
|
||||
To enable Claude to read and write to your knowledge base, edit the Claude Desktop configuration file (usually at `~/Library/Application Support/Claude/claude_desktop_config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"basic-memory": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"basic-memory",
|
||||
"mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration uses `uvx` to execute Basic Memory without requiring a full installation in Claude's environment.
|
||||
|
||||
### 3. Start the Sync Service
|
||||
|
||||
Start the sync service to monitor your files for changes:
|
||||
|
||||
```bash
|
||||
# One-time sync
|
||||
basic-memory sync
|
||||
|
||||
# For continuous monitoring (recommended)
|
||||
basic-memory sync --watch
|
||||
```
|
||||
|
||||
The `--watch` flag enables automatic detection of file changes, keeping your knowledge base current.
|
||||
|
||||
## Creating Your First Knowledge Note
|
||||
|
||||
1. **Start a conversation in Claude Desktop** about any topic:
|
||||
```
|
||||
You: "Let's talk about coffee brewing methods I've been experimenting with."
|
||||
```
|
||||
|
||||
2. **Have a natural conversation** about the topic
|
||||
|
||||
3. **Ask Claude to create a note**:
|
||||
```
|
||||
You: "Could you create a note summarizing what we've discussed about coffee brewing?"
|
||||
```
|
||||
|
||||
4. **Claude creates a Markdown file** in your `~/basic-memory` directory
|
||||
|
||||
5. **View and edit the file** with any text editor or Obsidian
|
||||
|
||||
## Using Special Prompts
|
||||
|
||||
Basic Memory includes special prompts that help you start conversations with context from your knowledge base:
|
||||
|
||||
### Continue Conversation
|
||||
|
||||
To resume a previous topic:
|
||||
|
||||
```
|
||||
You: "Let's continue our conversation about coffee brewing."
|
||||
```
|
||||
|
||||
This prompt triggers Claude to:
|
||||
1. Search your knowledge base for relevant content about coffee brewing
|
||||
2. Build context from these documents
|
||||
3. Resume the conversation with full awareness of previous discussions
|
||||
|
||||
### Recent Activity
|
||||
|
||||
To see what you've been working on:
|
||||
|
||||
```
|
||||
You: "What have we been discussing recently?"
|
||||
```
|
||||
|
||||
This prompt causes Claude to:
|
||||
1. Retrieve documents modified in the recent past
|
||||
2. Summarize the topics and main points
|
||||
3. Offer to continue any of those discussions
|
||||
|
||||
### Search
|
||||
|
||||
To find specific information:
|
||||
|
||||
```
|
||||
You: "Find information about pour over coffee methods."
|
||||
```
|
||||
|
||||
Claude will:
|
||||
1. Search your knowledge base for relevant documents
|
||||
2. Summarize the key findings
|
||||
3. Offer to explore specific documents in more detail
|
||||
|
||||
## Using Your Knowledge Base
|
||||
|
||||
### Referencing Knowledge
|
||||
|
||||
In future conversations, reference your existing knowledge:
|
||||
|
||||
```
|
||||
You: "What water temperature did we decide was optimal for coffee brewing?"
|
||||
```
|
||||
|
||||
Or directly reference notes using memory:// URLs:
|
||||
|
||||
```
|
||||
You: "Take a look at memory://coffee-brewing-methods and let's discuss how to improve my technique."
|
||||
```
|
||||
|
||||
### Building On Previous Knowledge
|
||||
|
||||
Basic Memory enables continuous knowledge building:
|
||||
|
||||
1. **Reference previous discussions** in new conversations
|
||||
2. **Add to existing notes** through conversations
|
||||
3. **Create connections** between related topics
|
||||
4. **Follow relationships** to build comprehensive context
|
||||
|
||||
## Importing Existing Conversations
|
||||
|
||||
Import your existing AI conversations:
|
||||
|
||||
```bash
|
||||
# From Claude
|
||||
basic-memory import claude conversations
|
||||
|
||||
# From ChatGPT
|
||||
basic-memory import chatgpt
|
||||
```
|
||||
|
||||
After importing, run `basic-memory sync` to index everything.
|
||||
|
||||
## Quick Tips
|
||||
|
||||
- Keep `basic-memory sync --watch` running in a terminal window
|
||||
- Use special prompts (Continue Conversation, Recent Activity, Search) to start contextual discussions
|
||||
- Build connections between notes for a richer knowledge graph
|
||||
- Use direct memory:// URLs when you need precise context
|
||||
- Use git to version control your knowledge base
|
||||
- Review and edit AI-generated notes for accuracy
|
||||
|
||||
## Next Steps
|
||||
|
||||
After getting started, explore these areas:
|
||||
|
||||
1. **Read the [[User Guide]]** for comprehensive usage instructions
|
||||
2. **Understand the [[Knowledge Format]]** to learn how knowledge is structured
|
||||
3. **Set up [[Obsidian Integration]]** for visual knowledge navigation
|
||||
4. **Learn about [[Canvas]]** visualizations for mapping concepts
|
||||
5. **Review the [[CLI Reference]]** for command line tools
|
||||
@@ -1,195 +0,0 @@
|
||||
---
|
||||
title: Knowledge Format
|
||||
type: note
|
||||
permalink: docs/knowledge-format
|
||||
tags:
|
||||
- architecture
|
||||
- patterns
|
||||
- knowledge
|
||||
- design
|
||||
---
|
||||
|
||||
# Knowledge Format
|
||||
|
||||
Basic Memory uses standard Markdown with simple semantic patterns to create a knowledge graph. This document details the file structure and patterns used to organize knowledge.
|
||||
|
||||
## File-First Architecture
|
||||
|
||||
All knowledge in Basic Memory is stored in plain text Markdown files:
|
||||
|
||||
- Files are the source of truth for all knowledge
|
||||
- Changes to files automatically update the knowledge graph
|
||||
- You maintain complete ownership and control
|
||||
- Files work with git and other version control systems
|
||||
- Knowledge persists independently of any AI conversation
|
||||
|
||||
## Core Document Structure
|
||||
|
||||
Every document uses this basic structure:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Document Title
|
||||
type: note
|
||||
tags: [tag1, tag2]
|
||||
permalink: custom-path
|
||||
---
|
||||
|
||||
# Document Title
|
||||
|
||||
Regular markdown content...
|
||||
|
||||
## Observations
|
||||
- [category] Content with #tags (optional context)
|
||||
|
||||
## Relations
|
||||
- relation_type [[Other Document]] (optional context)
|
||||
```
|
||||
|
||||
### Frontmatter
|
||||
|
||||
The YAML frontmatter at the top of each file defines essential metadata:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Document Title # Used for linking and references
|
||||
type: note # Document type
|
||||
tags: [tag1, tag2] # For organization and searching
|
||||
permalink: custom-link # Optional custom URL path
|
||||
---
|
||||
```
|
||||
|
||||
The title is particularly important as it's used to create links between documents.
|
||||
|
||||
### Observations
|
||||
|
||||
Observations are facts or statements about a topic:
|
||||
|
||||
```markdown
|
||||
## Observations
|
||||
- [tech] Uses SQLite for storage #database
|
||||
- [design] Follows local-first architecture #architecture
|
||||
- [decision] Selected bcrypt for passwords #security (Based on audit)
|
||||
```
|
||||
|
||||
Each observation contains:
|
||||
- **Category** in [brackets] - classifies the information type
|
||||
- **Content text** - the main information
|
||||
- Optional **#tags** - additional categorization
|
||||
- Optional **(context)** - supporting details
|
||||
|
||||
Common categories include:
|
||||
- `[tech]`: Technical details
|
||||
- `[design]`: Architecture decisions
|
||||
- `[feature]`: User capabilities
|
||||
- `[decision]`: Choices that were made
|
||||
- `[principle]`: Fundamental concepts
|
||||
- `[method]`: Approaches or techniques
|
||||
- `[preference]`: Personal opinions
|
||||
|
||||
### Relations
|
||||
|
||||
Relations connect documents to form the knowledge graph:
|
||||
|
||||
```markdown
|
||||
## Relations
|
||||
- implements [[Search Design]]
|
||||
- depends_on [[Database Schema]]
|
||||
- relates_to [[User Interface]]
|
||||
```
|
||||
|
||||
You can also create inline references:
|
||||
```markdown
|
||||
This builds on [[Core Design]] and uses [[Utility Functions]].
|
||||
```
|
||||
|
||||
Common relation types include:
|
||||
- `implements`: Implementation of a specification
|
||||
- `depends_on`: Required dependency
|
||||
- `relates_to`: General connection
|
||||
- `inspired_by`: Source of ideas
|
||||
- `extends`: Enhancement
|
||||
- `part_of`: Component relationship
|
||||
- `contains`: Hierarchical relationship
|
||||
- `pairs_with`: Complementary relationship
|
||||
|
||||
## Knowledge Graph
|
||||
|
||||
Basic Memory automatically builds a knowledge graph from your document connections:
|
||||
|
||||
- Each document becomes a node in the graph
|
||||
- Relations create edges between nodes
|
||||
- Relation types add semantic meaning to connections
|
||||
- Forward references can link to documents that don't exist yet
|
||||
|
||||
This graph enables rich context building and navigation across your knowledge base.
|
||||
|
||||
## Permalinks and memory:// URLs
|
||||
|
||||
Every document in Basic Memory has a unique permalink that serves as its stable identifier:
|
||||
|
||||
### How Permalinks Work
|
||||
|
||||
- **Automatically assigned**: The system generates a permalink for each document
|
||||
- **Based on title**: By default, derived from the document title
|
||||
- **Always unique**: If conflicts exist, the system adds a suffix to ensure uniqueness
|
||||
- **Stable reference**: Remains the same even if the file moves in the directory structure
|
||||
- **Used in memory:// URLs**: Forms the basis of the memory:// addressing scheme
|
||||
|
||||
You can specify a custom permalink in the frontmatter:
|
||||
```yaml
|
||||
---
|
||||
title: Authentication Approaches
|
||||
permalink: auth-approaches-2024
|
||||
---
|
||||
```
|
||||
|
||||
If not specified, one will be generated automatically from the title.
|
||||
|
||||
### Using memory:// URLs
|
||||
|
||||
The memory:// URL scheme provides a reliable way to reference knowledge:
|
||||
|
||||
```
|
||||
memory://auth-approaches-2024 # Direct access by permalink
|
||||
memory://Authentication Approaches # Access by title (automatically resolves)
|
||||
memory://project/auth-approaches # Access by path
|
||||
```
|
||||
|
||||
Memory URLs support pattern matching for more powerful queries:
|
||||
|
||||
```
|
||||
memory://auth* # All documents with permalinks starting with "auth"
|
||||
memory://*/approaches # All documents with permalinks ending with "approaches"
|
||||
memory://project/*/requirements # All requirements documents in the project folder
|
||||
memory://docs/search/implements/* # Follow all implements relations from search docs
|
||||
```
|
||||
|
||||
This addressing scheme ensures content remains accessible even as your knowledge base evolves and files are reorganized.
|
||||
|
||||
## File Organization
|
||||
|
||||
Organize files in any structure that suits your needs:
|
||||
|
||||
```
|
||||
docs/
|
||||
architecture/
|
||||
design.md
|
||||
patterns.md
|
||||
features/
|
||||
search.md
|
||||
auth.md
|
||||
```
|
||||
|
||||
You can:
|
||||
- Group by topic in folders
|
||||
- Use a flat structure with descriptive filenames
|
||||
- Tag files for easier discovery
|
||||
- Add custom metadata in frontmatter
|
||||
|
||||
The system will build the semantic knowledge graph regardless of how you organize your files.
|
||||
|
||||
## Relations
|
||||
- implemented_by [[User Guide]] (How to work with this format)
|
||||
- relates_to [[Getting Started with Basic Memory]] (Setup instructions)
|
||||
- explained_in [[Introduction to Basic Memory]] (Overview of the system)
|
||||
@@ -1,146 +0,0 @@
|
||||
---
|
||||
title: Obsidian Integration
|
||||
type: note
|
||||
permalink: docs/obsidian-integration
|
||||
---
|
||||
|
||||
# Obsidian Integration
|
||||
|
||||
Basic Memory integrates seamlessly with [Obsidian](https://obsidian.md), providing powerful visualization and navigation capabilities for your knowledge graph.
|
||||
|
||||
## Setup
|
||||
|
||||
### Creating an Obsidian Vault
|
||||
|
||||
1. Download and install [Obsidian](https://obsidian.md)
|
||||
2. Create a new vault
|
||||
3. Point it to your Basic Memory directory (~/basic-memory by default)
|
||||
4. Enable core plugins like Graph View, Backlinks, and Tags
|
||||
|
||||
## Visualization Features
|
||||
|
||||
### Graph View
|
||||
|
||||
Obsidian's Graph View provides a visual representation of your knowledge network:
|
||||
|
||||
- Each document appears as a node
|
||||
- Relations appear as connections between nodes
|
||||
- Colors can be customized to distinguish types
|
||||
- Filters let you focus on specific aspects
|
||||
- Local graphs show connections for individual documents
|
||||
|
||||
### Backlinks
|
||||
|
||||
Obsidian automatically tracks references between documents:
|
||||
|
||||
- View all documents that reference the current one
|
||||
- See the exact context of each reference
|
||||
- Navigate easily through connections
|
||||
- Track how concepts relate to each other
|
||||
|
||||
### Tag Explorer
|
||||
|
||||
Use tags to organize and filter content:
|
||||
|
||||
- View all tags in your knowledge base
|
||||
- See how many documents use each tag
|
||||
- Filter documents by tag combinations
|
||||
- Create hierarchical tag structures
|
||||
|
||||
## Knowledge Elements
|
||||
|
||||
Basic Memory's knowledge format works natively with Obsidian:
|
||||
|
||||
### Wiki Links
|
||||
|
||||
```markdown
|
||||
## Relations
|
||||
- implements [[Search Design]]
|
||||
- depends_on [[Database Schema]]
|
||||
```
|
||||
|
||||
These display as clickable links in Obsidian and appear in the graph view.
|
||||
|
||||
### Observations with Tags
|
||||
|
||||
```markdown
|
||||
## Observations
|
||||
- [tech] Using SQLite #database
|
||||
- [design] Local-first #architecture
|
||||
```
|
||||
|
||||
Tags become searchable and filterable in Obsidian's tag pane.
|
||||
|
||||
### Frontmatter
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Document Title
|
||||
type: note
|
||||
tags: [search, design]
|
||||
---
|
||||
```
|
||||
|
||||
Frontmatter provides metadata for Obsidian to use in search and filtering.
|
||||
|
||||
## Canvas Integration
|
||||
|
||||
Basic Memory can create [Obsidian Canvas](https://obsidian.md/canvas) files:
|
||||
|
||||
1. Ask Claude to create a visualization:
|
||||
```
|
||||
You: "Create a canvas showing the structure of our project components."
|
||||
```
|
||||
|
||||
2. Claude generates a .canvas file in your knowledge base
|
||||
|
||||
3. Open the file in Obsidian to view and edit the visual representation
|
||||
|
||||
4. Canvas files maintain references to your documents
|
||||
|
||||
## Recommended Plugins
|
||||
|
||||
These Obsidian plugins work especially well with Basic Memory:
|
||||
|
||||
- **Dataview**: Query your knowledge base programmatically
|
||||
- **Kanban**: Organize tasks from knowledge files
|
||||
- **Calendar**: View and navigate temporal knowledge
|
||||
- **Templates**: Create consistent knowledge structures
|
||||
|
||||
## Workflow Suggestions
|
||||
|
||||
### Daily Notes
|
||||
|
||||
```markdown
|
||||
# 2024-01-21
|
||||
|
||||
## Progress
|
||||
- Updated [[Search Design]]
|
||||
- Fixed [[Bug Report 123]]
|
||||
|
||||
## Notes
|
||||
- [idea] Better indexing #enhancement
|
||||
- [todo] Update docs #documentation
|
||||
|
||||
## Links
|
||||
- relates_to [[Current Sprint]]
|
||||
- updates [[Project Status]]
|
||||
```
|
||||
|
||||
### Project Tracking
|
||||
|
||||
```markdown
|
||||
# Current Sprint
|
||||
|
||||
## Tasks
|
||||
- [ ] Update [[Search]]
|
||||
- [ ] Fix [[Auth Bug]]
|
||||
|
||||
## Tags
|
||||
#sprint #planning #current
|
||||
```
|
||||
|
||||
## Relations
|
||||
- enhances [[Introduction to Basic Memory]] (Overview of system)
|
||||
- relates_to [[Canvas]] (Visual knowledge mapping)
|
||||
- complements [[User Guide]] (Using Basic Memory)
|
||||
@@ -1,99 +0,0 @@
|
||||
---
|
||||
title: Technical Information
|
||||
type: note
|
||||
permalink: docs/technical-information
|
||||
---
|
||||
|
||||
# Technical Information
|
||||
|
||||
This document provides technical details about Basic Memory's implementation, licensing, and integration with the Model Context Protocol (MCP).
|
||||
|
||||
## Architecture
|
||||
|
||||
Basic Memory consists of:
|
||||
|
||||
1. **Core Knowledge Engine**: Parses and indexes Markdown files
|
||||
2. **SQLite Database**: Provides fast querying and search
|
||||
3. **MCP Server**: Implements the Model Context Protocol
|
||||
4. **CLI Tools**: Command-line utilities for management
|
||||
5. **Sync Service**: Monitors file changes and updates the database
|
||||
|
||||
The system follows a file-first architecture where all knowledge is represented in standard Markdown files and the database serves as a secondary index.
|
||||
|
||||
## Model Context Protocol (MCP)
|
||||
|
||||
Basic Memory implements the [Model Context Protocol](https://github.com/modelcontextprotocol/spec), an open standard for enabling AI models to access external tools:
|
||||
|
||||
- **Standardized Interface**: Common protocol for tool integration
|
||||
- **Tool Registration**: Basic Memory registers as a tool provider
|
||||
- **Asynchronous Communication**: Enables efficient interaction with AI models
|
||||
- **Standardized Schema**: Structured data exchange format
|
||||
|
||||
Integration with Claude Desktop uses the MCP to grant Claude access to your knowledge base through a set of specialized tools that search, read, and write knowledge.
|
||||
|
||||
## Licensing
|
||||
|
||||
Basic Memory is licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.en.html):
|
||||
|
||||
- **Free Software**: You can use, study, share, and modify the software
|
||||
- **Copyleft**: Derivative works must be distributed under the same license
|
||||
- **Network Use**: Network users must be able to receive the source code
|
||||
- **Commercial Use**: Allowed, subject to license requirements
|
||||
|
||||
The AGPL license ensures Basic Memory remains open source while protecting against proprietary forks.
|
||||
|
||||
## Source Code
|
||||
|
||||
Basic Memory is developed as an open-source project:
|
||||
|
||||
- **GitHub Repository**: [https://github.com/basicmachines-co/basic-memory](https://github.com/basicmachines-co/basic-memory)
|
||||
- **Issue Tracker**: Report bugs and request features on GitHub
|
||||
- **Contributions**: Pull requests are welcome following the contributing guidelines
|
||||
- **Documentation**: Source for this documentation is also available in the repository
|
||||
|
||||
## Data Storage and Privacy
|
||||
|
||||
Basic Memory is designed with privacy as a core principle:
|
||||
|
||||
- **Local-First**: All data remains on your local machine
|
||||
- **No Cloud Dependency**: No remote servers or accounts required
|
||||
- **Telemetry**: Optional and disabled by default
|
||||
- **Standard Formats**: All data is stored in standard file formats you control
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Entity Model
|
||||
|
||||
Basic Memory's core data model consists of:
|
||||
|
||||
- **Entities**: Documents in your knowledge base
|
||||
- **Observations**: Facts or statements about entities
|
||||
- **Relations**: Connections between entities
|
||||
- **Tags**: Additional categorization for entities and observations
|
||||
|
||||
The system parses Markdown files to extract this structured information while preserving the human-readable format.
|
||||
|
||||
### Sync Process
|
||||
|
||||
The sync process:
|
||||
|
||||
1. Detects changes to files in the knowledge directory
|
||||
2. Parses modified files to extract structured data
|
||||
3. Updates the SQLite database with changes
|
||||
4. Resolves forward references when new entities are created
|
||||
5. Updates the search index for fast querying
|
||||
|
||||
### Search Engine
|
||||
|
||||
The search functionality:
|
||||
|
||||
1. Uses a combination of full-text search and semantic matching
|
||||
2. Indexes observations, relations, and content
|
||||
3. Supports wildcards and pattern matching in memory:// URLs
|
||||
4. Traverses the knowledge graph to follow relationships
|
||||
5. Ranks results by relevance to the query
|
||||
|
||||
## Relations
|
||||
- relates_to [[Introduction to Basic Memory]] (System overview)
|
||||
- relates_to [[CLI Reference]] (Command line tools)
|
||||
- implements [[Knowledge Format]] (File structure and format)
|
||||
@@ -1,518 +0,0 @@
|
||||
---
|
||||
title: User Guide
|
||||
type: note
|
||||
permalink: docs/user-guide
|
||||
---
|
||||
|
||||
# User Guide
|
||||
|
||||
This guide explains how to effectively use Basic Memory in your daily workflow, from creating knowledge through conversations to building a rich semantic network.
|
||||
|
||||
## Basic Memory Workflow
|
||||
|
||||
Using Basic Memory follows a natural cycle:
|
||||
|
||||
1. **Have conversations** with AI assistants like Claude
|
||||
2. **Capture knowledge** in Markdown files
|
||||
3. **Build connections** between pieces of knowledge
|
||||
4. **Reference your knowledge** in future conversations
|
||||
5. **Edit files directly** when needed
|
||||
6. **Sync changes** automatically
|
||||
|
||||
## Creating Knowledge
|
||||
|
||||
### Through Conversations
|
||||
|
||||
To create knowledge during conversations with Claude:
|
||||
|
||||
```
|
||||
You: We've covered several authentication approaches. Could you create a note summarizing what we've discussed?
|
||||
|
||||
Claude: I'll create a note summarizing our authentication discussion.
|
||||
```
|
||||
|
||||
This creates a Markdown file in your `~/basic-memory` directory with semantic markup.
|
||||
|
||||
### Direct File Creation
|
||||
|
||||
You can create files directly:
|
||||
|
||||
1. Create a new Markdown file in your `~/basic-memory` directory
|
||||
2. Add frontmatter with title, type, and optional tags
|
||||
3. Structure content with observations and relations
|
||||
4. Save the file
|
||||
5. Run `basic-memory sync` if not in watch mode
|
||||
|
||||
## Using Special Prompts
|
||||
|
||||
Basic Memory includes several special prompts that help you leverage your knowledge base more effectively. In apps like Claude Desktop, these prompts trigger specific tools to search and analyze your knowledge base.
|
||||
|
||||
### Continue Conversation
|
||||
|
||||
When you want to pick up where you left off on a topic:
|
||||
|
||||
```
|
||||
You: Let's continue our conversation about authentication systems.
|
||||
```
|
||||
|
||||
Behind the scenes:
|
||||
- Claude searches your knowledge base for content about "authentication systems"
|
||||
- It retrieves relevant documents and their relations
|
||||
- It analyzes the context to understand where you left off
|
||||
- It builds a comprehensive picture of what you've previously discussed
|
||||
- It can then resume the conversation with all that context
|
||||
|
||||
This is particularly useful when:
|
||||
- Starting a new session days or weeks after your last discussion
|
||||
- Switching between multiple ongoing projects
|
||||
- Building on previous work without repeating yourself
|
||||
|
||||
### Recent Activity
|
||||
|
||||
To get an overview of what you've been working on:
|
||||
|
||||
```
|
||||
You: What have we been discussing recently?
|
||||
```
|
||||
|
||||
Behind the scenes:
|
||||
- Claude retrieves documents modified recently
|
||||
- It analyzes patterns and themes
|
||||
- It summarizes the key topics and changes
|
||||
- It offers to continue working on any of those topics
|
||||
|
||||
This is useful for:
|
||||
- Coming back after a break
|
||||
- Getting a quick reminder of ongoing projects
|
||||
- Deciding what to work on next
|
||||
|
||||
### Search
|
||||
|
||||
To find specific information in your knowledge base:
|
||||
|
||||
```
|
||||
You: Find information about JWT authentication in my notes.
|
||||
```
|
||||
|
||||
Behind the scenes:
|
||||
- Claude performs a semantic search for "JWT authentication"
|
||||
- It retrieves and ranks the most relevant documents
|
||||
- It summarizes the key findings
|
||||
- It offers to explore specific areas in more detail
|
||||
|
||||
This is useful for:
|
||||
- Finding specific information quickly
|
||||
- Exploring what you know about a topic
|
||||
- Starting work on an existing topic
|
||||
|
||||
### Example
|
||||
|
||||
Choose "Continue Conversation"
|
||||
![[prompt 1.png|500]]
|
||||
|
||||
Enter a topic
|
||||
![[prompt2.png|500]]
|
||||
|
||||
Give instructions
|
||||
![[prompt3.png|500]]
|
||||
|
||||
Claude can build context from the supplied topic.
|
||||
![[prompt4.png|500]]
|
||||
|
||||
|
||||
## Searching Your Knowledge Base
|
||||
|
||||
Basic Memory provides multiple ways to search and explore your knowledge base:
|
||||
|
||||
### Natural Language Search
|
||||
|
||||
The simplest way to search is to ask Claude directly:
|
||||
|
||||
```
|
||||
You: What do I know about authentication methods?
|
||||
```
|
||||
|
||||
Claude will search your knowledge base semantically and return relevant information.
|
||||
|
||||
### Search Prompt
|
||||
|
||||
Use the dedicated search prompt for more focused searches:
|
||||
|
||||
```
|
||||
You: Search for "JWT authentication"
|
||||
```
|
||||
|
||||
This triggers a specialized search that returns precise results with document titles, relevant excerpts, and offers to explore specific documents.
|
||||
|
||||
### Boolean Search
|
||||
|
||||
For more precise searches, use boolean operators to refine your queries:
|
||||
|
||||
```
|
||||
You: Search for "authentication AND OAuth NOT basic"
|
||||
```
|
||||
|
||||
Basic Memory supports standard boolean operators:
|
||||
|
||||
- **AND**: Find documents containing both terms
|
||||
```
|
||||
You: Search for "python AND flask"
|
||||
```
|
||||
This finds documents containing both "python" and "flask"
|
||||
|
||||
- **OR**: Find documents containing either term
|
||||
```
|
||||
You: Search for "python OR javascript"
|
||||
```
|
||||
This finds documents containing either "python" or "javascript"
|
||||
|
||||
- **NOT**: Exclude documents containing specific terms
|
||||
```
|
||||
You: Search for "python NOT django"
|
||||
```
|
||||
This finds documents containing "python" but excludes those containing "django"
|
||||
|
||||
- **Grouping with parentheses**: Control operator precedence
|
||||
```
|
||||
You: Search for "(python OR javascript) AND web"
|
||||
```
|
||||
This finds documents about web development that mention either Python or JavaScript
|
||||
|
||||
Boolean search is particularly useful for:
|
||||
- Narrowing down results in large knowledge bases
|
||||
- Finding specific combinations of concepts
|
||||
- Excluding irrelevant content from search results
|
||||
- Creating complex queries for precise information retrieval
|
||||
|
||||
### Memory URL Pattern Matching
|
||||
|
||||
For advanced searches, use memory:// URL patterns with wildcards:
|
||||
|
||||
```
|
||||
You: Look at memory://auth* and summarize all authentication approaches.
|
||||
```
|
||||
|
||||
Pattern matching supports:
|
||||
- **Wildcards**: `memory://auth*` matches all permalinks starting with "auth"
|
||||
- **Path patterns**: `memory://project/*/auth` matches auth documents in any project subfolder
|
||||
- **Relation traversal**: `memory://auth-system/implements/*` finds all documents that implement the auth system
|
||||
|
||||
### Combining Search with Context Building
|
||||
|
||||
The most powerful searches build comprehensive context by following relationships:
|
||||
|
||||
```
|
||||
You: Search for JWT authentication and then follow all implementation relations.
|
||||
```
|
||||
|
||||
This builds a complete picture by:
|
||||
1. Finding documents about JWT authentication
|
||||
2. Following implementation relationships from those documents
|
||||
3. Building a complete picture of how JWT is implemented across your system
|
||||
|
||||
### Search Best Practices
|
||||
|
||||
For effective searching:
|
||||
1. **Be specific** with search terms and phrases
|
||||
2. **Use boolean operators** to refine searches and find precise information
|
||||
3. **Use technical terms** when searching for technical content
|
||||
4. **Follow up** on search results by asking for more details about specific documents
|
||||
5. **Combine approaches** by starting with search and then using memory:// URLs for precision
|
||||
6. **Use relation traversal** to explore connected concepts after finding initial documents
|
||||
|
||||
## Referencing Knowledge
|
||||
|
||||
### Using memory:// URLs
|
||||
|
||||
Reference specific knowledge directly:
|
||||
|
||||
```
|
||||
You: Please look at memory://authentication-approaches and suggest which approach would be best for our mobile app.
|
||||
```
|
||||
|
||||
### Natural Language References
|
||||
|
||||
Reference knowledge conversationally:
|
||||
|
||||
```
|
||||
You: What did we decide about authentication for the project?
|
||||
```
|
||||
|
||||
### Advanced References
|
||||
|
||||
Follow connections across your knowledge graph:
|
||||
|
||||
```
|
||||
You: Look at memory://project-architecture and check related documents to give me a complete picture.
|
||||
```
|
||||
|
||||
## Working with Files
|
||||
|
||||
### File Location and Organization
|
||||
|
||||
By default, Basic Memory stores files in `~/basic-memory`:
|
||||
|
||||
- Browse this directory in your file explorer
|
||||
- Organize files into subfolders
|
||||
- Use git for version control
|
||||
|
||||
### File Format
|
||||
|
||||
Each knowledge file follows this structure:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Authentication Approaches
|
||||
type: note
|
||||
tags: [security, architecture]
|
||||
permalink: authentication-approaches
|
||||
---
|
||||
|
||||
# Authentication Approaches
|
||||
|
||||
A comparison of authentication methods.
|
||||
|
||||
## Observations
|
||||
- [approach] JWT provides stateless authentication #security
|
||||
- [limitation] Session tokens require server-side storage #infrastructure
|
||||
|
||||
## Relations
|
||||
- implements [[Security Requirements]]
|
||||
- affects [[User Login Flow]]
|
||||
```
|
||||
|
||||
### Editing Files
|
||||
|
||||
Modify files in any text editor:
|
||||
|
||||
1. Open the file in your preferred editor
|
||||
2. Make changes to content, observations, or relations
|
||||
3. Save the file
|
||||
4. Basic Memory detects changes automatically when running in watch mode
|
||||
|
||||
## Building a Knowledge Graph
|
||||
|
||||
The value of Basic Memory comes from connections between pieces of knowledge.
|
||||
|
||||
### Creating Relations
|
||||
|
||||
When creating or editing notes, build connections:
|
||||
|
||||
```markdown
|
||||
## Relations
|
||||
- implements [[Security Requirements]]
|
||||
- depends_on [[User Authentication]]
|
||||
```
|
||||
|
||||
Relations can be:
|
||||
- Hierarchical (part_of, contains)
|
||||
- Directional (implements, depends_on)
|
||||
- Associative (relates_to, similar_to)
|
||||
- Temporal (precedes, follows)
|
||||
|
||||
Relations are also created via regular wiki-link style links within the body text.
|
||||
|
||||
### Forward References
|
||||
|
||||
Reference documents that don't exist yet:
|
||||
|
||||
```markdown
|
||||
- will_impact [[Future Feature]]
|
||||
```
|
||||
|
||||
These references resolve automatically when you create the referenced document.
|
||||
|
||||
## Conversation Continuity
|
||||
|
||||
Basic Memory maintains context across different conversations.
|
||||
|
||||
### Starting New Sessions with Context
|
||||
|
||||
When starting a new conversation with Claude, you can:
|
||||
|
||||
1. **Use special prompts** like "Continue conversation about..." or "What were we working on?"
|
||||
2. **Reference specific documents** with memory:// URLs
|
||||
3. **Ask about recent work** with "What have we been discussing recently?"
|
||||
4. **Search for specific topics** with "Find information about..."
|
||||
|
||||
### Long-Term Projects
|
||||
|
||||
Maintain context for complex projects over time:
|
||||
|
||||
1. **Document key decisions** as you make them
|
||||
2. **Create relationships** between project components
|
||||
3. **Reference past decisions** when implementing features
|
||||
4. **Update documentation** as the project evolves
|
||||
|
||||
### Tips for Effective Continuity
|
||||
|
||||
1. **Be specific about topics** when continuing a conversation
|
||||
2. **Reference documents directly** with memory:// URLs for precision
|
||||
3. **Create summary notes** after important discussions
|
||||
4. **Update existing notes** rather than creating duplicates
|
||||
5. **Build robust connections** between related topics
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Importing External Knowledge
|
||||
|
||||
Import existing conversations:
|
||||
|
||||
```bash
|
||||
# From Claude
|
||||
basic-memory import claude conversations
|
||||
|
||||
# From ChatGPT
|
||||
basic-memory import chatgpt
|
||||
```
|
||||
|
||||
After importing, run `basic-memory sync` to index everything.
|
||||
|
||||
### Obsidian Integration
|
||||
|
||||
Use with [Obsidian](https://obsidian.md):
|
||||
|
||||
1. Point Obsidian to your `~/basic-memory` directory
|
||||
2. Use Obsidian's graph view to visualize your knowledge network
|
||||
3. All changes sync back to Basic Memory
|
||||
|
||||
### Canvas Visualizations
|
||||
|
||||
Create visual knowledge maps:
|
||||
|
||||
```
|
||||
You: Could you create a canvas visualization of our project components?
|
||||
```
|
||||
|
||||
This generates an Obsidian canvas file showing the relationships between concepts.
|
||||
|
||||
### Advanced Memory URI Patterns
|
||||
|
||||
Use wildcards and patterns:
|
||||
|
||||
```
|
||||
You: Review memory://project/*/requirements to summarize all project requirements.
|
||||
```
|
||||
|
||||
## Command Line Interface
|
||||
|
||||
### Sync Commands
|
||||
|
||||
```bash
|
||||
# One-time sync
|
||||
basic-memory sync
|
||||
|
||||
# Watch for changes
|
||||
basic-memory sync --watch
|
||||
```
|
||||
|
||||
### Status and Information
|
||||
|
||||
```bash
|
||||
# Check system status
|
||||
basic-memory status
|
||||
|
||||
# View CLI help
|
||||
basic-memory --help
|
||||
```
|
||||
|
||||
### Import Commands
|
||||
|
||||
```bash
|
||||
# Import from Claude
|
||||
basic-memory import claude conversations
|
||||
|
||||
# Import from ChatGPT
|
||||
basic-memory import chatgpt
|
||||
```
|
||||
|
||||
|
||||
## Multiple Projects
|
||||
|
||||
Basic Memory supports managing multiple separate knowledge bases through projects. This feature allows you to maintain
|
||||
separate knowledge graphs for different purposes (e.g., personal notes, work projects, research topics).
|
||||
|
||||
Basic Memory keeps a list of projects in a config file: ` ~/.basic-memory/config.json`
|
||||
|
||||
### Managing Projects
|
||||
|
||||
```bash
|
||||
# List all configured projects
|
||||
basic-memory project list
|
||||
|
||||
# Add a new project
|
||||
basic-memory project add work ~/work-basic-memory
|
||||
|
||||
# Set the default project
|
||||
basic-memory project default work
|
||||
|
||||
# Remove a project (doesn't delete files)
|
||||
basic-memory project remove personal
|
||||
|
||||
# Show current project
|
||||
basic-memory project current
|
||||
```
|
||||
|
||||
### Using Projects in Commands
|
||||
|
||||
All commands support the `--project` flag to specify which project to use:
|
||||
|
||||
```bash
|
||||
# Sync a specific project
|
||||
basic-memory --project=work sync
|
||||
|
||||
# Run MCP server for a specific project
|
||||
basic-memory --project=personal mcp
|
||||
```
|
||||
|
||||
You can also set the `BASIC_MEMORY_PROJECT` environment variable:
|
||||
|
||||
```bash
|
||||
BASIC_MEMORY_PROJECT=work basic-memory sync
|
||||
```
|
||||
|
||||
### Project Isolation
|
||||
|
||||
Each project maintains:
|
||||
|
||||
- Its own collection of markdown files in the specified directory
|
||||
- A separate SQLite database for that project
|
||||
- Complete knowledge graph isolation from other projects
|
||||
## Workflow Tips
|
||||
|
||||
1. Run sync in watch mode for automatic updates
|
||||
2. Use git for version control of your knowledge base
|
||||
3. Review and edit AI-created content for accuracy
|
||||
4. Periodically organize and refine your knowledge structure
|
||||
5. Build rich connections between related ideas
|
||||
6. Use forward references to plan future documentation
|
||||
7. Start conversations with special prompts to leverage existing knowledge
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Sync Issues
|
||||
|
||||
If changes aren't showing up:
|
||||
1. Verify `basic-memory sync --watch` is running
|
||||
2. Run `basic-memory status` to check system state
|
||||
3. Try a manual sync with `basic-memory sync`
|
||||
|
||||
### Missing Content
|
||||
|
||||
If content isn't found:
|
||||
1. Check the exact path and permalink
|
||||
2. Try searching with more general terms
|
||||
3. Verify the file exists in your knowledge base
|
||||
|
||||
### Relation Problems
|
||||
|
||||
If relations aren't working:
|
||||
1. Ensure exact title matching in [[WikiLinks]]
|
||||
2. Check for typos in relation types
|
||||
3. Verify both documents exist
|
||||
|
||||
## Relations
|
||||
- implements [[Knowledge Format]] (How knowledge is structured)
|
||||
- relates_to [[Getting Started with Basic Memory]] (Setup and first steps)
|
||||
- relates_to [[Canvas]] (Creating visual knowledge maps)
|
||||
- relates_to [[CLI Reference]] (Command line tools)
|
||||
@@ -1,181 +0,0 @@
|
||||
---
|
||||
title: Introduction to Basic Memory
|
||||
type: docs
|
||||
permalink: docs/introduction
|
||||
tags:
|
||||
- documentation
|
||||
- index
|
||||
- overview
|
||||
---
|
||||
|
||||
# BASIC MEMORY
|
||||
|
||||
Basic Memory is a knowledge management system that allows you to build a persistent semantic graph from conversations
|
||||
with AI assistants. All knowledge is stored in standard Markdown files on your computer, giving you full control and
|
||||
ownership of your data.
|
||||
|
||||
## Core Functions
|
||||
|
||||
Basic Memory connects you and AI assistants through shared knowledge:
|
||||
|
||||
1. **Captures knowledge** from natural conversations with AI assistants
|
||||
2. **Structures information** using simple semantic patterns in Markdown
|
||||
3. **Enables knowledge reuse** across different conversations and sessions
|
||||
4. **Maintains persistence** through local files you control completely
|
||||
|
||||
Both you and AI assistants like Claude can read from and write to the same knowledge base, creating a continuous
|
||||
learning environment where each conversation builds upon previous ones.
|
||||
|
||||
![[Obsidian-CoffeeKnowledgeBase-examples-overlays.gif]]
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
Basic Memory uses:
|
||||
|
||||
- **Files as the source of truth** - Everything is stored in plain Markdown files
|
||||
- **Git-compatible storage** - All knowledge can be versioned, branched, and merged
|
||||
- **Local SQLite database** - For fast indexing and searching only (not primary storage)
|
||||
- **Memory:// URI scheme** - For precise knowledge referencing and navigation
|
||||
- **Model Context Protocol (MCP)** - For seamless AI assistant integration
|
||||
|
||||
## Knowledge Structure
|
||||
|
||||
Knowledge in Basic Memory is organized as a semantic graph:
|
||||
|
||||
1. **Entities** - Distinct concepts represented by Markdown documents
|
||||
2. **Observations** - Categorized facts and information about entities
|
||||
3. **Relations** - Connections between entities that form the knowledge graph
|
||||
|
||||
This structure emerges from simple text patterns in standard Markdown:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Coffee Brewing Methods
|
||||
type: note
|
||||
permalink: coffee/coffee-brewing-methods
|
||||
tags:
|
||||
- '#coffee'
|
||||
- '#brewing'
|
||||
- '#methods'
|
||||
- '#demo'
|
||||
---
|
||||
|
||||
# Coffee Brewing Methods
|
||||
|
||||
An exploration of different coffee brewing techniques, their characteristics, and how they affect flavor extraction.
|
||||
|
||||
## Overview
|
||||
|
||||
Coffee brewing is both an art and a science. Different brewing methods extract different compounds from coffee beans,
|
||||
resulting in unique flavor profiles, body, and mouthfeel. The key variables in any brewing method are:
|
||||
|
||||
- Grind size
|
||||
- Water temperature
|
||||
- Brew time
|
||||
- Coffee-to-water ratio
|
||||
- Agitation/turbulence
|
||||
|
||||
## Observations
|
||||
|
||||
- [principle] Coffee extraction follows a predictable pattern: acids extract first, then sugars, then bitter compounds
|
||||
#extraction
|
||||
- [method] Pour over methods generally produce cleaner, brighter cups with more distinct flavor notes #clarity
|
||||
|
||||
## Relations
|
||||
|
||||
- requires [[Proper Grinding Technique]]
|
||||
- affects [[Flavor Extraction]]
|
||||
```
|
||||
|
||||
Becomes
|
||||
|
||||
```json
|
||||
{
|
||||
"entities": [
|
||||
{
|
||||
"permalink": "coffee/coffee-brewing-methods",
|
||||
"title": "Coffee Brewing Methods",
|
||||
"file_path": "Coffee Notes/Coffee Brewing Methods.md",
|
||||
"entity_type": "note",
|
||||
"entity_metadata": {
|
||||
"title": "Coffee Brewing Methods",
|
||||
"type": "note",
|
||||
"permalink": "coffee/coffee-brewing-methods",
|
||||
"tags": "['#coffee', '#brewing', '#methods', '#demo']"
|
||||
},
|
||||
"checksum": "bfa32a0f23fa124b53f0694c344d2788b0ce50bd090b55b6d738401d2a349e4c",
|
||||
"content_type": "text/markdown",
|
||||
"observations": [
|
||||
{
|
||||
"category": "principle",
|
||||
"content": "Coffee extraction follows a predictable pattern: acids extract first, then sugars, then bitter compounds #extraction",
|
||||
"tags": [
|
||||
"extraction"
|
||||
],
|
||||
"permalink": "coffee/coffee-brewing-methods/observations/principle/coffee-extraction-follows-a-predictable-pattern-acids-extract-first-then-sugars-then-bitter-compounds-extraction"
|
||||
},
|
||||
{
|
||||
"category": "method",
|
||||
"content": "Pour over methods generally produce cleaner, brighter cups with more distinct flavor notes #clarity",
|
||||
"tags": [
|
||||
"clarity"
|
||||
],
|
||||
"permalink": "coffee/coffee-brewing-methods/observations/method/pour-over-methods-generally-produce-cleaner-brighter-cups-with-more-distinct-flavor-notes-clarity"
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"from_id": "coffee/coffee-bean-origins",
|
||||
"to_id": "coffee/coffee-brewing-methods",
|
||||
"relation_type": "pairs_with",
|
||||
"context": null,
|
||||
"permalink": "coffee/coffee-bean-origins/pairs-with/coffee/coffee-brewing-methods",
|
||||
"to_name": "Coffee Brewing Methods"
|
||||
},
|
||||
{
|
||||
"from_id": "coffee/flavor-extraction",
|
||||
"to_id": "coffee/coffee-brewing-methods",
|
||||
"relation_type": "affected_by",
|
||||
"context": null,
|
||||
"permalink": "coffee/flavor-extraction/affected-by/coffee/coffee-brewing-methods",
|
||||
"to_name": "Coffee Brewing Methods"
|
||||
}
|
||||
],
|
||||
"created_at": "2025-03-06T14:01:23.445071",
|
||||
"updated_at": "2025-03-06T13:34:48.563606"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Basic Memory understands how to build context via its semantic graph.
|
||||
|
||||
## User Control and File Management
|
||||
|
||||
Basic Memory gives you complete control over your knowledge:
|
||||
|
||||
- **Local-first storage** - All knowledge lives on your computer
|
||||
- **Standard file formats** - Plain Markdown compatible with any editor
|
||||
- **Directory organization** - Knowledge stored in `~/basic-memory` by default
|
||||
- **Version control ready** - Use git for history, branching, and collaboration
|
||||
- **Edit anywhere** - Modify files with any text editor or Obsidian
|
||||
|
||||
Changes to files automatically update the knowledge graph, and AI assistants can see your edits in future conversations.
|
||||
|
||||
## Documentation Map
|
||||
|
||||
Continue exploring Basic Memory with these guides:
|
||||
|
||||
- Installation and setup [[Getting Started with Basic Memory]]
|
||||
- Comprehensive usage instructions [[User Guide]]
|
||||
- Detailed explanation of knowledge structure [[Knowledge Format]]
|
||||
- Reference for AI assistants using Basic Memory [[AI Assistant Guide]]
|
||||
- Technical implementation details [[Technical Information]]
|
||||
- Command line tool reference [[CLI Reference]]
|
||||
- Obsidian integration guide [[Obsidian Integration]]
|
||||
- Canvas visualization guide [[Canvas]]
|
||||
|
||||
## Next Steps
|
||||
|
||||
Start with the [[Getting Started with Basic Memory]] guide to install Basic Memory and configure it with your AI
|
||||
assistant.
|
||||
|
Before Width: | Height: | Size: 374 KiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 908 KiB |
|
Before Width: | Height: | Size: 302 KiB |
|
Before Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 277 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"promptDelete": false
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,3 +0,0 @@
|
||||
[
|
||||
"optimize-canvas-connections"
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"file-explorer": true,
|
||||
"global-search": true,
|
||||
"switcher": true,
|
||||
"graph": true,
|
||||
"backlink": true,
|
||||
"canvas": true,
|
||||
"outgoing-link": true,
|
||||
"tag-pane": true,
|
||||
"properties": false,
|
||||
"page-preview": true,
|
||||
"daily-notes": true,
|
||||
"templates": true,
|
||||
"note-composer": true,
|
||||
"command-palette": true,
|
||||
"slash-command": false,
|
||||
"editor-status": true,
|
||||
"bookmarks": true,
|
||||
"markdown-importer": false,
|
||||
"zk-prefixer": false,
|
||||
"random-note": false,
|
||||
"outline": true,
|
||||
"word-count": true,
|
||||
"slides": false,
|
||||
"audio-recorder": false,
|
||||
"workspaces": false,
|
||||
"file-recovery": true,
|
||||
"publish": true,
|
||||
"sync": true,
|
||||
"webviewer": false
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"collapse-filter": true,
|
||||
"search": "",
|
||||
"showTags": false,
|
||||
"showAttachments": false,
|
||||
"hideUnresolved": false,
|
||||
"showOrphans": true,
|
||||
"collapse-color-groups": true,
|
||||
"colorGroups": [],
|
||||
"collapse-display": true,
|
||||
"showArrow": false,
|
||||
"textFadeMultiplier": 0,
|
||||
"nodeSizeMultiplier": 1,
|
||||
"lineSizeMultiplier": 1,
|
||||
"collapse-forces": true,
|
||||
"centerStrength": 0.518713248970312,
|
||||
"repelStrength": 10,
|
||||
"linkStrength": 1,
|
||||
"linkDistance": 250,
|
||||
"scale": 1,
|
||||
"close": false
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// main.ts
|
||||
var main_exports = {};
|
||||
__export(main_exports, {
|
||||
default: () => OptimizeCanvasConnectionsPlugin
|
||||
});
|
||||
module.exports = __toCommonJS(main_exports);
|
||||
var import_obsidian = require("obsidian");
|
||||
var OptimizeCanvasConnectionsPlugin = class extends import_obsidian.Plugin {
|
||||
async onload() {
|
||||
this.addCommand({
|
||||
id: "optimize-preserve-axes-selection",
|
||||
name: "Optimize selection (preserve axes)",
|
||||
checkCallback: (checking) => {
|
||||
const canvasView = app.workspace.getActiveViewOfType(import_obsidian.ItemView);
|
||||
if ((canvasView == null ? void 0 : canvasView.getViewType()) == "canvas") {
|
||||
if (!checking) {
|
||||
this.optimize("preserve-axes");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
this.addCommand({
|
||||
id: "optimize-shortest-path-selection",
|
||||
name: "Optimize selection (shortest path)",
|
||||
checkCallback: (checking) => {
|
||||
const canvasView = app.workspace.getActiveViewOfType(import_obsidian.ItemView);
|
||||
if ((canvasView == null ? void 0 : canvasView.getViewType()) == "canvas") {
|
||||
if (!checking) {
|
||||
this.optimize("shortest-path");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
onunload() {
|
||||
}
|
||||
async optimize(option) {
|
||||
const canvasView = app.workspace.getActiveViewOfType(import_obsidian.ItemView);
|
||||
const canvas = canvasView == null ? void 0 : canvasView.canvas;
|
||||
const currentSelection = canvas == null ? void 0 : canvas.selection;
|
||||
let selectedIDs = new Array();
|
||||
currentSelection.forEach(function(selection) {
|
||||
selectedIDs.push(selection.id);
|
||||
});
|
||||
let applyToAll = false;
|
||||
if (selectedIDs.length == 0) {
|
||||
applyToAll = true;
|
||||
}
|
||||
for (let [edgeKey, edge] of canvas["edges"]) {
|
||||
let fromNode = edge["from"]["node"];
|
||||
let toNode = edge["to"]["node"];
|
||||
let fromPossibilities = [edge["from"]["side"]];
|
||||
if (applyToAll || selectedIDs.includes(fromNode["id"])) {
|
||||
switch (option) {
|
||||
case "shortest-path":
|
||||
fromPossibilities = ["top", "bottom", "left", "right"];
|
||||
break;
|
||||
case "preserve-axes":
|
||||
switch (edge["from"]["side"]) {
|
||||
case "top":
|
||||
case "bottom":
|
||||
fromPossibilities = ["top", "bottom"];
|
||||
break;
|
||||
case "left":
|
||||
case "right":
|
||||
fromPossibilities = ["left", "right"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let toPossibilities = [edge["to"]["side"]];
|
||||
if (applyToAll || selectedIDs.includes(toNode["id"])) {
|
||||
switch (option) {
|
||||
case "shortest-path":
|
||||
toPossibilities = ["top", "bottom", "left", "right"];
|
||||
break;
|
||||
case "preserve-axes":
|
||||
switch (edge["to"]["side"]) {
|
||||
case "top":
|
||||
case "bottom":
|
||||
toPossibilities = ["top", "bottom"];
|
||||
break;
|
||||
case "left":
|
||||
case "right":
|
||||
toPossibilities = ["left", "right"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let distances = [];
|
||||
for (const fromSide of fromPossibilities) {
|
||||
let fromPoint = { "x": 0, "y": 0 };
|
||||
if (fromSide == "top") {
|
||||
fromPoint = { "x": fromNode["x"] + fromNode["width"] / 2, "y": fromNode["y"] };
|
||||
} else if (fromSide == "bottom") {
|
||||
fromPoint = { "x": fromNode["x"] + fromNode["width"] / 2, "y": fromNode["y"] + fromNode["height"] };
|
||||
} else if (fromSide == "left") {
|
||||
fromPoint = { "x": fromNode["x"], "y": fromNode["y"] + fromNode["height"] / 2 };
|
||||
} else if (fromSide == "right") {
|
||||
fromPoint = { "x": fromNode["x"] + fromNode["width"], "y": fromNode["y"] + fromNode["height"] / 2 };
|
||||
}
|
||||
for (const toSide of toPossibilities) {
|
||||
let toPoint = { "x": 0, "y": 0 };
|
||||
if (toSide == "top") {
|
||||
toPoint = { "x": toNode["x"] + toNode["width"] / 2, "y": toNode["y"] };
|
||||
} else if (toSide == "bottom") {
|
||||
toPoint = { "x": toNode["x"] + toNode["width"] / 2, "y": toNode["y"] + toNode["height"] };
|
||||
} else if (toSide == "left") {
|
||||
toPoint = { "x": toNode["x"], "y": toNode["y"] + toNode["height"] / 2 };
|
||||
} else if (toSide == "right") {
|
||||
toPoint = { "x": toNode["x"] + toNode["width"], "y": toNode["y"] + toNode["height"] / 2 };
|
||||
}
|
||||
distances.push({
|
||||
"fromSide": fromSide,
|
||||
"toSide": toSide,
|
||||
"distance": (toPoint.x - fromPoint.x) ** 2 + (toPoint.y - fromPoint.y) ** 2
|
||||
});
|
||||
}
|
||||
}
|
||||
distances = distances.sort(function(a, b) {
|
||||
return a.distance - b.distance;
|
||||
});
|
||||
edge["from"]["side"] = distances[0]["fromSide"];
|
||||
edge["to"]["side"] = distances[0]["toSide"];
|
||||
edge.render();
|
||||
}
|
||||
canvas.requestSave();
|
||||
}
|
||||
};
|
||||
|
||||
/* nosourcemap */
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "optimize-canvas-connections",
|
||||
"name": "Optimize Canvas Connections",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "1.1.9",
|
||||
"description": "An Obsidian plugin that declutters a canvas by reconnecting notes using their nearest edges.",
|
||||
"author": "Félix Chénier",
|
||||
"authorUrl": "https://felixchenier.uqam.ca",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"siteId": null,
|
||||
"host": null,
|
||||
"included": [],
|
||||
"excluded": []
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
{
|
||||
"main": {
|
||||
"id": "7ae5f8d0583c3a18",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "22434516a5f1a19f",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "6fae14c14e044a89",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Coffee Notes/Coffee Brewing Methods.md",
|
||||
"mode": "source",
|
||||
"source": true
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Coffee Brewing Methods"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "vertical"
|
||||
},
|
||||
"left": {
|
||||
"id": "402ef826b6e8c27a",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "7df070b9023ec8de",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "6fc235d3a84e414f",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "file-explorer",
|
||||
"state": {
|
||||
"sortOrder": "alphabetical",
|
||||
"autoReveal": false
|
||||
},
|
||||
"icon": "lucide-folder-closed",
|
||||
"title": "Files"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "8679c962d1bfd321",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "search",
|
||||
"state": {
|
||||
"query": "",
|
||||
"matchingCase": false,
|
||||
"explainSearch": false,
|
||||
"collapseAll": false,
|
||||
"extraContext": false,
|
||||
"sortOrder": "alphabetical"
|
||||
},
|
||||
"icon": "lucide-search",
|
||||
"title": "Search"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "8848cf5cc01540b2",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "bookmarks",
|
||||
"state": {},
|
||||
"icon": "lucide-bookmark",
|
||||
"title": "Bookmarks"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 264.5
|
||||
},
|
||||
"right": {
|
||||
"id": "5bfd609e7511200a",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "5b83fb5c13b27ce5",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "396b2dfa99a4a44e",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "backlink",
|
||||
"state": {
|
||||
"file": "Untitled.md",
|
||||
"collapseAll": false,
|
||||
"extraContext": false,
|
||||
"sortOrder": "alphabetical",
|
||||
"showSearch": false,
|
||||
"searchQuery": "",
|
||||
"backlinkCollapsed": false,
|
||||
"unlinkedCollapsed": true
|
||||
},
|
||||
"icon": "links-coming-in",
|
||||
"title": "Backlinks for Untitled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b606de8f7f078b60",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "outgoing-link",
|
||||
"state": {
|
||||
"file": "Untitled.md",
|
||||
"linksCollapsed": false,
|
||||
"unlinkedCollapsed": true
|
||||
},
|
||||
"icon": "links-going-out",
|
||||
"title": "Outgoing links from Untitled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2788e9059ef83663",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "tag",
|
||||
"state": {
|
||||
"sortOrder": "frequency",
|
||||
"useHierarchy": true,
|
||||
"showSearch": false,
|
||||
"searchQuery": ""
|
||||
},
|
||||
"icon": "lucide-tags",
|
||||
"title": "Tags"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cdf344bf83b2f251",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "outline",
|
||||
"state": {
|
||||
"file": "Untitled.md",
|
||||
"followCursor": false,
|
||||
"showSearch": false,
|
||||
"searchQuery": ""
|
||||
},
|
||||
"icon": "lucide-list",
|
||||
"title": "Outline of Untitled"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 682.5,
|
||||
"collapsed": true
|
||||
},
|
||||
"left-ribbon": {
|
||||
"hiddenItems": {
|
||||
"switcher:Open quick switcher": false,
|
||||
"graph:Open graph view": false,
|
||||
"canvas:Create new canvas": false,
|
||||
"daily-notes:Open today's daily note": false,
|
||||
"templates:Insert template": false,
|
||||
"command-palette:Open command palette": false,
|
||||
"publish:Publish changes...": false
|
||||
}
|
||||
},
|
||||
"active": "6fae14c14e044a89",
|
||||
"lastOpenFiles": [
|
||||
"Untitled.md",
|
||||
"Coffee Notes/Coffee Brewing Methods.md",
|
||||
"Coffee Notes/Coffee Knowledge Base.md",
|
||||
"Coffee Notes/Flavor Extraction.md",
|
||||
"Coffee Notes/Coffee Flavor Map.md",
|
||||
"Coffee Notes/Coffee Bean Origins.md",
|
||||
"Coffee Notes/Perfect Pour Over Coffee Method.canvas",
|
||||
"Coffee Notes/Coffee Brewing Methods Venn Diagram.canvas",
|
||||
"Coffee Notes/Pour Over Techniques.md",
|
||||
"Coffee Notes/Tasting Notes.md",
|
||||
"Coffee Notes/Hybrid Pour Over Technique.md"
|
||||
]
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
title: Brewing Equipment
|
||||
type: note
|
||||
permalink: coffee/brewing-equipment
|
||||
tags:
|
||||
- '#coffee'
|
||||
- '#equipment'
|
||||
- '#gear'
|
||||
- '#brewing'
|
||||
- '#demo'
|
||||
---
|
||||
|
||||
# Brewing Equipment
|
||||
|
||||
Essential tools and equipment for brewing coffee, their characteristics, and how they affect the brewing process.
|
||||
|
||||
## Overview
|
||||
|
||||
The equipment used to brew coffee plays a crucial role in determining the final cup quality. From grinders to brewers to kettles, each piece of equipment contributes to different aspects of the brewing process.
|
||||
|
||||
## Observations
|
||||
|
||||
- [principle] Equipment quality often has a bigger impact on consistency than on absolute quality potential #quality
|
||||
- [principle] Good grind consistency is the most important technical factor in extraction quality #grind
|
||||
- [investment] A good burr grinder is often the most important investment for improving home coffee #gear
|
||||
- [technique] Equipment maintenance and cleaning significantly impact flavor consistency over time #maintenance
|
||||
|
||||
## Grinders
|
||||
|
||||
- [equipment] Burr grinders crush beans between two abrasive surfaces for more consistent particle size #grinders
|
||||
- [equipment] Blade grinders chop beans unevenly, leading to inconsistent extraction #grinders
|
||||
- [equipment] Flat burr grinders produce very consistent particle size but generate more heat #burrs
|
||||
- [equipment] Conical burr grinders create slightly less uniform grounds but with less heat and noise #burrs
|
||||
- [feature] Grind adjustment mechanisms range from stepped to stepless for different precision levels #adjustment
|
||||
- [feature] Retention (grounds trapped in grinder) affects dose consistency and freshness #retention
|
||||
- [price] Hand grinders offer excellent value, with models like Timemore C2 and 1Zpresso JX providing excellent results around $100-150 #budget
|
||||
- [price] Entry-level electric burr grinders like Baratza Encore start around $170 but provide significant improvement over blade grinders #value
|
||||
|
||||
## Brewers
|
||||
|
||||
### Pour Over Brewers
|
||||
- [equipment] Hario V60 uses a conical design with spiral ridges to control flow rate #pourover
|
||||
- [equipment] Kalita Wave has a flat bottom with three small holes for more consistent extraction #pourover
|
||||
- [equipment] Chemex combines brewer and server with thick proprietary filters for ultra-clean cup #pourover
|
||||
- [material] Ceramic brewers retain heat better than plastic but are more fragile #materials
|
||||
- [material] Glass brewers provide neutral flavor but less heat retention #materials
|
||||
- [material] Plastic brewers are inexpensive, durable, and surprisingly good for heat retention #materials
|
||||
|
||||
### Immersion Brewers
|
||||
- [equipment] French Press uses a metal mesh to separate grounds, allowing oils and fine particles to pass #immersion
|
||||
- [equipment] AeroPress uses pressure and paper filter for clean, versatile brewing #immersion
|
||||
- [equipment] Clever Dripper combines immersion and drip methods with a valve mechanism #hybrid
|
||||
- [material] Glass French presses look elegant but break easily and have poor heat retention #materials
|
||||
- [material] Stainless steel or ceramic French presses offer better durability and heat retention #materials
|
||||
|
||||
### Pressure Brewers
|
||||
- [equipment] Espresso machines use 9 bars of pressure, requiring significant investment for good results #espresso
|
||||
- [equipment] Moka pot uses steam pressure for strong, concentrated coffee at affordable price #moka
|
||||
- [equipment] Manual lever machines like Flair or Robot provide espresso-style coffee with manual control #manual_espresso
|
||||
|
||||
## Kettles
|
||||
|
||||
- [equipment] Gooseneck kettles provide precision pouring control essential for pour over methods #kettles
|
||||
- [feature] Variable temperature kettles allow precise temperature control for different roast levels #temp_control
|
||||
- [feature] Flow restrictors can help beginners maintain consistent pour rates #pour_control
|
||||
- [material] Electric kettles offer convenience and temperature stability #convenience
|
||||
- [material] Stovetop kettles may be more durable but offer less temperature control #durability
|
||||
|
||||
## Accessories
|
||||
|
||||
- [equipment] Coffee scale with 0.1g precision helps maintain consistent ratios #measurement
|
||||
- [equipment] Timer ensures consistent extraction times #consistency
|
||||
- [equipment] Quality filters significantly impact flavor clarity and body #filters
|
||||
- [equipment] Storage containers with one-way valves help preserve bean freshness #storage
|
||||
- [equipment] Blind shaker or dosing cup reduces grinder mess and improves workflow #workflow
|
||||
|
||||
## Relations
|
||||
|
||||
- improves [[Coffee Brewing Methods]]
|
||||
- affects [[Flavor Extraction]]
|
||||
- requires [[Proper Maintenance]]
|
||||
- enhances [[Home Coffee Setup]]
|
||||
- part_of [[Coffee Knowledge Base]]
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
title: Coffee Bean Origins
|
||||
type: note
|
||||
permalink: coffee/coffee-bean-origins
|
||||
tags:
|
||||
- '#coffee'
|
||||
- '#origins'
|
||||
- '#beans'
|
||||
- '#regions'
|
||||
- '#demo'
|
||||
---
|
||||
|
||||
# Coffee Bean Origins
|
||||
|
||||
An exploration of coffee-growing regions around the world and how geography, climate, and processing methods affect flavor profiles.
|
||||
|
||||
## Overview
|
||||
|
||||
Coffee beans are grown in various regions around the world, primarily in what's known as the "Coffee Belt" - the area between the Tropics of Cancer and Capricorn. The flavor characteristics of coffee beans are influenced by:
|
||||
|
||||
- Geographic region and climate
|
||||
- Altitude
|
||||
- Soil composition
|
||||
- Variety of coffee plant
|
||||
- Processing method
|
||||
- Harvest and sorting practices
|
||||
|
||||
## Observations
|
||||
|
||||
- [principle] Higher altitude generally produces harder, denser beans with more complex acidity #altitude
|
||||
- [region] Ethiopian beans often feature bright, fruity notes with floral aromatics #ethiopia
|
||||
- [region] Colombian coffee typically offers balanced acidity with caramel sweetness and nutty undertones #colombia
|
||||
- [region] Guatemalan coffee presents complex acidity with chocolate notes and sometimes spice characteristics #guatemala
|
||||
- [region] Brazilian coffee tends toward nutty, chocolate notes with lower acidity and fuller body #brazil
|
||||
- [region] Kenyan coffee is known for bright, wine-like acidity and berry or citrus notes #kenya
|
||||
- [processing] Natural (dry) processing tends to create fruitier, more fermented flavors #processing
|
||||
- [processing] Washed (wet) processing generally results in cleaner, brighter cups with more clarity #processing
|
||||
- [processing] Honey processing creates a middle ground with some fruity notes while maintaining clarity #processing
|
||||
- [factor] Shade-grown coffee typically develops more slowly, resulting in more complex flavors #cultivation
|
||||
- [factor] Soil volcanic soil often imparts distinctive mineral characteristics to coffee #terroir
|
||||
- [variety] Gesha/Geisha variety is known for exceptional floral and tea-like qualities #varieties
|
||||
- [variety] Bourbon varieties often feature sweet, complex cup profiles #varieties
|
||||
- [variety] Robusta beans have higher caffeine content but generally less complex flavor than Arabica #varieties
|
||||
|
||||
## Major Growing Regions
|
||||
|
||||
- [africa] Ethiopian coffees: Yirgacheffe, Sidamo, Harrar regions each with distinctive profiles #ethiopia
|
||||
- [africa] Kenyan coffees: Often categorized by grade (AA, AB, etc.) based on bean size #kenya
|
||||
- [americas] Colombian regions: Huila, Nariño, Antioquia each with unique characteristics #colombia
|
||||
- [americas] Central American producers: Guatemala, Costa Rica, Panama known for balanced profiles #central_america
|
||||
- [americas] Brazilian regions: Cerrado, Sul de Minas, Mogiana with varying profiles #brazil
|
||||
- [asia] Indonesian islands: Sumatra, Java, Sulawesi producing earthy, full-bodied coffees #indonesia
|
||||
- [asia] Vietnamese coffee: World's largest Robusta producer, often used in blends and commercial coffee #vietnam
|
||||
|
||||
## Processing Methods
|
||||
|
||||
- [natural] Beans dried inside the fruit, creating fruity, fermented notes and heavier body #processing
|
||||
- [washed] Fruit removed before drying, resulting in cleaner cup with more pronounced acidity #processing
|
||||
- [honey] Some fruit mucilage left on during drying, creates balanced sweetness and body #processing
|
||||
- [wet-hulled] Unique to Indonesia, creates earthy, herbal, low-acid profiles #processing
|
||||
- [experimental] Anaerobic fermentation, wine-yeast inoculation, and other newer methods #innovation
|
||||
|
||||
## Tasting Notes by Region
|
||||
|
||||
- [ethiopia] Blueberry, jasmine, bergamot, stone fruit, citrus #flavor_notes
|
||||
- [kenya] Blackcurrant, tomato, tropical fruit, wine-like acidity #flavor_notes
|
||||
- [colombia] Caramel, nuts, red apple, chocolate, balanced acidity #flavor_notes
|
||||
- [guatemala] Chocolate, spice, green apple, balanced #flavor_notes
|
||||
- [brazil] Nuts, chocolate, low acidity, full body #flavor_notes
|
||||
- [indonesia] Earthy, herbal, spice, cedar, full body, low acidity #flavor_notes
|
||||
|
||||
## Relations
|
||||
|
||||
- influences [[Flavor Extraction]]
|
||||
- pairs_with [[Coffee Brewing Methods]]
|
||||
- affects [[Tasting Notes]]
|
||||
- relates_to [[Specialty Coffee]]
|
||||
- part_of [[Coffee Knowledge Base]]
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
title: Coffee Brewing Methods
|
||||
type: note
|
||||
permalink: coffee/coffee-brewing-methods
|
||||
tags:
|
||||
- '#coffee'
|
||||
- '#brewing'
|
||||
- '#methods'
|
||||
- '#demo'
|
||||
---
|
||||
|
||||
# Coffee Brewing Methods
|
||||
|
||||
An exploration of different coffee brewing techniques, their characteristics, and how they affect flavor extraction.
|
||||
|
||||
## Overview
|
||||
|
||||
Coffee brewing is both an art and a science. Different brewing methods extract different compounds from coffee beans, resulting in unique flavor profiles, body, and mouthfeel. The key variables in any brewing method are:
|
||||
|
||||
- Grind size
|
||||
- Water temperature
|
||||
- Brew time
|
||||
- Coffee-to-water ratio
|
||||
- Agitation/turbulence
|
||||
|
||||
## Observations
|
||||
|
||||
- [principle] Coffee extraction follows a predictable pattern: acids extract first, then sugars, then bitter compounds #extraction
|
||||
- [method] Pour over methods generally produce cleaner, brighter cups with more distinct flavor notes #clarity
|
||||
- [method] Immersion methods like French press create fuller body and more rounded flavors #body
|
||||
- [technique] Water at 195-205°F (90-96°C) extracts optimal flavor compounds for most brewing methods #temperature
|
||||
- [technique] Grind size directly correlates with ideal extraction time (finer = shorter, coarser = longer) #grind
|
||||
- [preference] Medium-light roasts often showcase more origin characteristics in pour over methods #roast
|
||||
- [equipment] Burr grinders produce more consistent particle size than blade grinders, resulting in more even extraction #gear
|
||||
- [ratio] 1:15 to 1:17 coffee-to-water ratio (by weight) works well for most brew methods #brewing
|
||||
- [science] Different brewing temperatures extract different chemical compounds from the beans #chemistry
|
||||
- [technique] Bloom phase (pre-infusion with small amount of water) allows CO2 to escape and improves extraction #bloom
|
||||
|
||||
## Pour Over Methods
|
||||
|
||||
- [method] V60 produces very clean cup with excellent clarity of flavor #pourover
|
||||
- [method] Chemex uses thicker filter paper, resulting in even cleaner cup with fewer oils #pourover
|
||||
- [method] Kalita Wave provides more consistent extraction due to flat bottom design #pourover
|
||||
- [technique] Concentric circular pouring pattern ensures even saturation of grounds #technique
|
||||
- [timing] Most pour over methods complete in 2:30-3:30 total brew time #brewing
|
||||
|
||||
## Immersion Methods
|
||||
|
||||
- [method] French Press creates full-bodied cup with rich mouthfeel due to metal filter allowing oils to pass #immersion
|
||||
- [method] AeroPress is versatile, capable of producing both espresso-like and filter-style coffee #immersion
|
||||
- [method] Cold brew uses time instead of heat to extract, resulting in lower acidity #immersion
|
||||
- [technique] French press ideal steep time is 4-5 minutes before plunging #timing
|
||||
- [technique] AeroPress inverted method prevents dripping during extraction phase #technique
|
||||
|
||||
## Pressure Methods
|
||||
|
||||
- [method] Espresso uses 9 bars of pressure to force water through finely ground coffee #pressure
|
||||
- [method] Moka pot uses steam pressure to push water through grounds, creating strong, concentrated coffee #pressure
|
||||
- [technique] Espresso requires very fine grind, almost powder-like consistency #grind
|
||||
- [timing] Espresso shots typically extract in 25-30 seconds #timing
|
||||
- [principle] Pressure methods can extract compounds that aren't soluble in regular brewing methods #extraction
|
||||
|
||||
## Relations
|
||||
|
||||
- requires [[Proper Grinding Technique]]
|
||||
- affects [[Flavor Extraction]]
|
||||
- pairs_with [[Coffee Bean Origins]]
|
||||
- uses [[Brewing Equipment]]
|
||||
- influences [[Tasting Notes]]
|
||||
- part_of [[Coffee Knowledge Base]]
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
title: Coffee Flavor Map
|
||||
type: note
|
||||
permalink: coffee/coffee-flavor-map
|
||||
tags:
|
||||
- '#coffee'
|
||||
- '#visualization'
|
||||
- '#canvas'
|
||||
- '#demo'
|
||||
---
|
||||
|
||||
# Coffee Flavor Map
|
||||
|
||||
A visual mapping of coffee flavor attributes, brewing methods, and their relationships. This note describes a canvas visualization that could be generated to demonstrate Basic Memory's visualization capabilities.
|
||||
|
||||
## Overview
|
||||
|
||||
The Coffee Flavor Map provides a visual representation of how different brewing methods, coffee origins, and equipment choices affect flavor outcomes. This canvas visualization helps users understand the complex relationships in coffee brewing and tasting.
|
||||
|
||||
## Canvas Visualization Elements
|
||||
|
||||
### Core Nodes
|
||||
- **Flavor Attributes**: Acidity, Sweetness, Body, Clarity, Bitterness, Complexity
|
||||
- **Brewing Methods**: Pour Over, French Press, AeroPress, Espresso, Moka Pot, Cold Brew
|
||||
- **Origin Regions**: Ethiopia, Kenya, Colombia, Brazil, Guatemala, Indonesia
|
||||
- **Equipment Elements**: Grinder Quality, Water Temperature, Brewing Device, Filter Type
|
||||
|
||||
### Node Connections
|
||||
- Lines connecting brewing methods to their typical flavor outcomes
|
||||
- Arrows showing how equipment choices affect extraction variables
|
||||
- Connections between origins and their characteristic flavor profiles
|
||||
- Highlighting of optimal brewing methods for different origins
|
||||
|
||||
### Visual Organization
|
||||
- Flavor outcomes in the center
|
||||
- Brewing methods on the left side
|
||||
- Origins on the right side
|
||||
- Equipment variables at the bottom
|
||||
- Color coding by category (methods, origins, equipment, flavors)
|
||||
|
||||
## Using This Visualization
|
||||
|
||||
### For Coffee Exploration
|
||||
- Identify which brewing methods might highlight the characteristics you prefer
|
||||
- See which origins naturally pair well with your preferred brewing method
|
||||
- Understand how equipment changes can modify flavor outcomes
|
||||
- Visualize the complex interplay between all coffee variables
|
||||
|
||||
### As a Basic Memory Demo
|
||||
- Demonstrates Canvas visualization capabilities
|
||||
- Shows how relations can be visually mapped
|
||||
- Illustrates complex knowledge organization
|
||||
- Provides an intuitive way to navigate coffee knowledge
|
||||
|
||||
## How To Generate This Canvas
|
||||
|
||||
In a conversation with Claude, you could request:
|
||||
|
||||
```
|
||||
Please create a canvas visualization mapping the relationships between coffee brewing methods, origins, and flavor outcomes. Show how different equipment and techniques influence extraction and resulting flavor profiles.
|
||||
```
|
||||
|
||||
This would generate a `.canvas` file in your Basic Memory directory that could be opened with Obsidian for an interactive visualization of these coffee relationships.
|
||||
|
||||
## Example Visualization Snippets
|
||||
|
||||
### Pour Over Method Node
|
||||
- Connected to: High Clarity, Bright Acidity, Medium Body
|
||||
- Best pairs with: Ethiopian and Kenyan beans
|
||||
- Equipment dependencies: Gooseneck Kettle, Paper Filter, Burr Grinder
|
||||
|
||||
### Ethiopian Coffee Node
|
||||
- Characteristic flavors: Floral, Fruity, Bright
|
||||
- Best brewing methods: Pour Over, AeroPress
|
||||
- Challenging with: French Press (loses clarity of delicate notes)
|
||||
|
||||
### Grind Size Node
|
||||
- Affects: Extraction Rate, Flavor Balance
|
||||
- Fine grind increases: Extraction Speed, Surface Area
|
||||
- Coarse grind increases: Flow Rate, Reduces Bitter Compounds
|
||||
|
||||
## Relations
|
||||
|
||||
- visualizes [[Coffee Knowledge Base]]
|
||||
- relates_to [[Coffee Brewing Methods]]
|
||||
- relates_to [[Coffee Bean Origins]]
|
||||
- relates_to [[Flavor Extraction]]
|
||||
- relates_to [[Tasting Notes]]
|
||||
- demonstrates [[Canvas]]
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
title: Coffee Knowledge Base
|
||||
type: note
|
||||
permalink: coffee/coffee-knowledge-base
|
||||
tags:
|
||||
- '#coffee'
|
||||
- '#index'
|
||||
- '#demo'
|
||||
- '#knowledge'
|
||||
---
|
||||
|
||||
# Coffee Knowledge Base
|
||||
|
||||
A comprehensive collection of coffee knowledge, from bean origins to brewing methods to tasting notes. This knowledge base demonstrates Basic Memory's ability to organize and connect information in a meaningful way.
|
||||
|
||||
## Overview
|
||||
|
||||
This Coffee Knowledge Base captures key information about coffee, structured with semantic observations and relations that connect different aspects of coffee knowledge. It serves as both a useful reference for coffee enthusiasts and a demonstration of how Basic Memory organizes information.
|
||||
|
||||
## Key Topics
|
||||
|
||||
### Core Coffee Knowledge
|
||||
|
||||
- [[Coffee Brewing Methods]] - Different techniques for preparing coffee
|
||||
- [[Coffee Bean Origins]] - Where coffee comes from and how region affects flavor
|
||||
- [[Brewing Equipment]] - Tools and devices used to prepare coffee
|
||||
- [[Flavor Extraction]] - The science of dissolving flavor compounds from coffee
|
||||
- [[Tasting Notes]] - How to taste and describe coffee flavors
|
||||
|
||||
### Brewing Techniques
|
||||
|
||||
- Proper grinding is fundamental to good extraction
|
||||
- Water quality significantly impacts flavor
|
||||
- Different brewing methods highlight different characteristics
|
||||
- Time, temperature, and grind size are the key variables to control
|
||||
- Freshness of beans dramatically affects quality
|
||||
|
||||
### Coffee Preferences
|
||||
|
||||
- Light roasts preserve more origin characteristics and acidity
|
||||
- Dark roasts emphasize body and chocolatey/roasted flavors
|
||||
- Pour over methods highlight clarity and distinct flavor notes
|
||||
- Immersion methods create fuller body and rounded flavor
|
||||
- Personal preference matters more than "correctness"
|
||||
|
||||
## Using This Knowledge Base
|
||||
|
||||
### For Learning
|
||||
|
||||
Use this knowledge base to:
|
||||
- Understand coffee fundamentals
|
||||
- Explore connections between brewing methods and flavor outcomes
|
||||
- Learn how different origins produce distinct flavor profiles
|
||||
- Discover how equipment affects the brewing process
|
||||
- Develop a vocabulary for describing coffee experiences
|
||||
|
||||
### As a Demo
|
||||
|
||||
This knowledge base demonstrates:
|
||||
- Semantic knowledge organization with categories and relations
|
||||
- Building connections between related concepts
|
||||
- Creating a navigable knowledge graph
|
||||
- Structuring information in a way both humans and AI assistants can understand
|
||||
- How Basic Memory enables persistent knowledge across conversations
|
||||
|
||||
## Relations
|
||||
|
||||
- contains [[Coffee Brewing Methods]]
|
||||
- contains [[Coffee Bean Origins]]
|
||||
- contains [[Brewing Equipment]]
|
||||
- contains [[Flavor Extraction]]
|
||||
- contains [[Tasting Notes]]
|
||||
- demonstrates [[Basic Memory Capabilities]]
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
title: Flavor Extraction
|
||||
type: note
|
||||
permalink: coffee/flavor-extraction
|
||||
tags:
|
||||
- '#coffee'
|
||||
- '#extraction'
|
||||
- '#brewing'
|
||||
- '#science'
|
||||
- '#demo'
|
||||
---
|
||||
|
||||
# Flavor Extraction
|
||||
|
||||
Understanding the science of coffee extraction, how different compounds dissolve at different rates, and how to control extraction to achieve desired flavor profiles.
|
||||
|
||||
## Overview
|
||||
|
||||
Coffee extraction is the process of dissolving flavor compounds from ground coffee into water. The science of extraction is key to producing a balanced, flavorful cup. Extraction is affected by numerous variables including grind size, water temperature, contact time, agitation, and pressure.
|
||||
|
||||
## Observations
|
||||
|
||||
- [science] Coffee contains over 1,000 aroma compounds and hundreds of flavor compounds #chemistry
|
||||
- [principle] Extraction occurs in a predictable sequence: acids → sugars → bitter compounds #extraction_order
|
||||
- [principle] Under-extraction results in sour, bright, thin coffee lacking sweetness and body #under_extraction
|
||||
- [principle] Over-extraction results in bitter, hollow, astringent flavors #over_extraction
|
||||
- [principle] The goal is typically balanced extraction (18-22% of coffee solubles dissolved) #balanced_extraction
|
||||
- [technique] Finer grind size increases extraction rate due to greater surface area #grind_size
|
||||
- [technique] Higher water temperature increases extraction rate and solubility of compounds #temperature
|
||||
- [technique] Longer contact time allows more complete extraction #brew_time
|
||||
- [technique] Agitation (stirring, turbulence) increases extraction rate by preventing saturation zones #agitation
|
||||
- [technique] Pressure (as in espresso) can extract compounds that aren't water-soluble at atmospheric pressure #pressure
|
||||
|
||||
## Factors Affecting Extraction
|
||||
|
||||
- [factor] Grind size: Finer = faster extraction, coarser = slower extraction #grind
|
||||
- [factor] Water temperature: Higher = faster extraction, lower = slower extraction #temperature
|
||||
- [factor] Contact time: Longer = more extraction, shorter = less extraction #time
|
||||
- [factor] Agitation: More = faster extraction, less = slower extraction #agitation
|
||||
- [factor] Coffee-to-water ratio: More coffee = lower extraction percentage #ratio
|
||||
- [factor] Water quality: Mineral content affects extraction of different compounds #water
|
||||
- [factor] Roast level: Darker roasts extract more easily than lighter roasts #roast
|
||||
- [factor] Bean density: Denser beans (typically high-altitude) require more effort to extract #density
|
||||
- [factor] Freshness: Freshly roasted coffee extracts differently than aged coffee #freshness
|
||||
- [factor] Brewing method: Different methods extract different compounds at different rates #method
|
||||
|
||||
## Signs of Extraction Levels
|
||||
|
||||
- [under] Sour, bright, lack of sweetness, thin body, quick finish #flavor
|
||||
- [under] Typically from: too coarse grind, too cool water, too short brew time #causes
|
||||
- [balanced] Sweet, bright but not sour, rich but not bitter, pleasing finish #flavor
|
||||
- [balanced] Achieved through proper ratio of variables for given coffee #technique
|
||||
- [over] Bitter, hollow, astringent, dry finish, sometimes papery #flavor
|
||||
- [over] Typically from: too fine grind, too hot water, too long brew time #causes
|
||||
|
||||
## Measuring Extraction
|
||||
|
||||
- [method] Total Dissolved Solids (TDS) meters measure concentration of coffee solution #measurement
|
||||
- [method] Extraction yield = percentage of coffee grounds dissolved in the final brew #calculation
|
||||
- [preference] Specialty coffee typically targets 18-22% extraction yield #standards
|
||||
- [preference] Some specialty light roasts may taste best at higher extraction percentages #speciality
|
||||
|
||||
## Controlling Extraction
|
||||
|
||||
- [technique] Adjust grind size as primary extraction control #basics
|
||||
- [technique] Use water temperature to fine-tune extraction #fine_tuning
|
||||
- [technique] Modify pour technique to control agitation level #technique
|
||||
- [technique] Adjust coffee-to-water ratio to balance strength and extraction #ratio
|
||||
- [technique] Pre-infusion (blooming) helps achieve even extraction #blooming
|
||||
- [technique] Pulse pouring creates different extraction dynamics than continuous pour #pour_technique
|
||||
|
||||
## Relations
|
||||
|
||||
- affected_by [[Coffee Brewing Methods]]
|
||||
- influenced_by [[Coffee Bean Origins]]
|
||||
- enhanced_by [[Brewing Equipment]]
|
||||
- determines [[Tasting Notes]]
|
||||
- requires [[Water Quality]]
|
||||
- part_of [[Coffee Knowledge Base]]
|
||||
@@ -1,161 +0,0 @@
|
||||
{
|
||||
"nodes":[
|
||||
{
|
||||
"id":"node-5",
|
||||
"type":"text",
|
||||
"text":"## Main Pour Phase\n- Use concentric circles from center outward\n- Maintain steady, controlled flow rate\n- Avoid pouring directly on filter walls\n- Keep water level consistent\n- Pulse pour in 2-3 stages (or continuous pour)\n- Total brew time target: 2:30-3:30",
|
||||
"position":{"x":450,"y":200},
|
||||
"x":530,
|
||||
"y":-100,
|
||||
"width":300,
|
||||
"height":200,
|
||||
"color":"1"
|
||||
},
|
||||
{
|
||||
"id":"node-8",
|
||||
"type":"text",
|
||||
"text":"## Drawdown\n- Allow water to fully drain\n- Flat bed indicates even extraction\n- Total brew time should be ~2:30-3:30\n- Remove filter promptly after brewing",
|
||||
"position":{"x":450,"y":700},
|
||||
"x":540,
|
||||
"y":375,
|
||||
"width":300,
|
||||
"height":150,
|
||||
"color":"1"
|
||||
},
|
||||
{
|
||||
"id":"node-6",
|
||||
"type":"text",
|
||||
"text":"## Pour Pattern\n\nConcentric circles ensure even saturation of coffee grounds. Begin at the center and work outward, avoiding filter edges. Pour height of 1-2 inches above coffee bed.",
|
||||
"position":{"x":250,"y":450},
|
||||
"x":960,
|
||||
"y":25,
|
||||
"width":300,
|
||||
"height":150,
|
||||
"color":"5"
|
||||
},
|
||||
{
|
||||
"id":"node-12",
|
||||
"type":"text",
|
||||
"text":"## Tasting Notes\n\n- Balanced extraction: sweet, bright, complex\n- Under-extraction: sour, lacking sweetness\n- Over-extraction: bitter, astringent, hollow\n\nTake notes on each brew to track improvements and preferences.",
|
||||
"position":{"x":-250,"y":700},
|
||||
"x":1020,
|
||||
"y":420,
|
||||
"width":300,
|
||||
"height":150,
|
||||
"color":"6"
|
||||
},
|
||||
{
|
||||
"id":"node-9",
|
||||
"type":"text",
|
||||
"text":"## Troubleshooting\n\n- Too sour/weak: Grind finer, water hotter, increase brew time\n- Too bitter/strong: Grind coarser, water cooler, decrease brew time\n- Uneven extraction: Improve pour technique, better grinder\n- Channeling: More careful pouring, better bloom\n- Slow drawdown: Coarser grind, less agitation\n- Fast drawdown: Finer grind, more careful pouring",
|
||||
"position":{"x":100,"y":700},
|
||||
"x":30,
|
||||
"y":570,
|
||||
"width":300,
|
||||
"height":200,
|
||||
"color":"6"
|
||||
},
|
||||
{
|
||||
"id":"node-3",
|
||||
"type":"text",
|
||||
"text":"## Preparation\n- Heat water to 195-205°F (90-96°C)\n- Measure coffee (1:15 to 1:17 ratio)\n- Medium-fine grind (sea salt consistency)\n- Rinse filter with hot water\n- Discard rinse water\n- Add ground coffee to filter\n- Level coffee bed",
|
||||
"position":{"x":-250,"y":200},
|
||||
"x":30,
|
||||
"y":-500,
|
||||
"width":300,
|
||||
"height":200,
|
||||
"color":"3"
|
||||
},
|
||||
{
|
||||
"id":"node-1",
|
||||
"type":"text",
|
||||
"text":"# Perfect Pour Over Method\n\nA systematic approach to brewing exceptional pour over coffee by controlling key variables and following proper technique.",
|
||||
"position":{"x":0,"y":0},
|
||||
"x":-580,
|
||||
"y":-760,
|
||||
"width":400,
|
||||
"height":120,
|
||||
"color":"4"
|
||||
},
|
||||
{
|
||||
"id":"node-10",
|
||||
"type":"text",
|
||||
"text":"## Grinding Parameters\n\n- V60: Medium-fine (sea salt)\n- Chemex: Medium (slightly coarser than V60)\n- Kalita Wave: Medium (between V60 and Chemex)\n\nConsistent particle size is critical; use quality burr grinder.",
|
||||
"position":{"x":-250,"y":450},
|
||||
"x":30,
|
||||
"y":-910,
|
||||
"width":300,
|
||||
"height":150,
|
||||
"color":"5"
|
||||
},
|
||||
{
|
||||
"id":"node-2",
|
||||
"type":"text",
|
||||
"text":"## Equipment Setup\n- Clean V60/Chemex/Kalita Wave\n- Paper filter (rinsed)\n- Server/mug\n- Scale with timer\n- Gooseneck kettle\n- Burr grinder\n- Fresh coffee beans",
|
||||
"position":{"x":-600,"y":200},
|
||||
"x":-530,
|
||||
"y":-500,
|
||||
"width":300,
|
||||
"height":200,
|
||||
"color":"3"
|
||||
},
|
||||
{
|
||||
"id":"node-4",
|
||||
"type":"text",
|
||||
"text":"## The Bloom\n- Start timer\n- Pour 2-3x coffee weight water\n- Ensure all grounds are saturated\n- Gentle stir or swirl if needed\n- Allow 30-45 seconds for degassing\n- Look for bubbling and dome formation",
|
||||
"position":{"x":100,"y":200},
|
||||
"x":530,
|
||||
"y":-500,
|
||||
"width":300,
|
||||
"height":200,
|
||||
"color":"1"
|
||||
},
|
||||
{
|
||||
"id":"node-13",
|
||||
"type":"text",
|
||||
"text":"## Coffee-to-Water Ratio\n\n- Standard: 1:15 to 1:17 (coffee:water)\n- Stronger cup: 1:15 (67g/L)\n- Medium cup: 1:16 (62.5g/L)\n- Lighter cup: 1:17 (58.8g/L)\n\nExample: For 300ml water, use ~18-20g coffee",
|
||||
"position":{"x":-600,"y":700},
|
||||
"x":30,
|
||||
"y":-100,
|
||||
"width":300,
|
||||
"height":150,
|
||||
"color":"6"
|
||||
},
|
||||
{
|
||||
"id":"node-7",
|
||||
"type":"text",
|
||||
"text":"## Brew Time Guideline\n\n- Bloom: 30-45 seconds\n- First pour: 1:00-1:15\n- Second pour: 1:45-2:00\n- Final pour: 2:15-2:30\n- Drawdown complete: 2:45-3:30\n\nAdjust for taste: shorter for lighter, longer for stronger",
|
||||
"position":{"x":600,"y":450},
|
||||
"x":-80,
|
||||
"y":220,
|
||||
"width":300,
|
||||
"height":200,
|
||||
"color":"5"
|
||||
},
|
||||
{
|
||||
"id":"node-11",
|
||||
"type":"text",
|
||||
"text":"## Water Quality\n\n- Clean, filtered water\n- No strong odors or flavors\n- Ideal TDS: 75-150 ppm\n- Ideal pH: 7.0-7.5\n- Avoid distilled water (lacks minerals)\n- Avoid hard water (scaling issues)",
|
||||
"position":{"x":-600,"y":450},
|
||||
"x":-780,
|
||||
"y":-125,
|
||||
"width":300,
|
||||
"height":150,
|
||||
"color":"5"
|
||||
}
|
||||
],
|
||||
"edges":[
|
||||
{"id":"edge-1","fromNode":"node-1","fromSide":"bottom","toNode":"node-2","toSide":"top","label":"Step 1"},
|
||||
{"id":"edge-2","fromNode":"node-2","fromSide":"right","toNode":"node-3","toSide":"left","label":"Step 2"},
|
||||
{"id":"edge-3","fromNode":"node-3","fromSide":"right","toNode":"node-4","toSide":"left","label":"Step 3"},
|
||||
{"id":"edge-4","fromNode":"node-4","fromSide":"bottom","toNode":"node-5","toSide":"top","label":"Step 4"},
|
||||
{"id":"edge-5","fromNode":"node-5","fromSide":"bottom","toNode":"node-8","toSide":"top","label":"Step 5"},
|
||||
{"id":"edge-6","fromNode":"node-5","fromSide":"left","toNode":"node-7","toSide":"right","label":"Timing"},
|
||||
{"id":"edge-7","fromNode":"node-5","fromSide":"right","toNode":"node-6","toSide":"left","label":"Technique"},
|
||||
{"id":"edge-8","fromNode":"node-8","fromSide":"left","toNode":"node-9","toSide":"right","label":"if problems"},
|
||||
{"id":"edge-9","fromNode":"node-3","fromSide":"top","toNode":"node-10","toSide":"bottom","label":"Grinding details"},
|
||||
{"id":"edge-10","fromNode":"node-2","fromSide":"bottom","toNode":"node-11","toSide":"right","label":"Water details"},
|
||||
{"id":"edge-11","fromNode":"node-8","fromSide":"right","toNode":"node-12","toSide":"left","label":"Evaluate"},
|
||||
{"id":"edge-12","fromNode":"node-3","fromSide":"bottom","toNode":"node-13","toSide":"top","label":"Ratio details"}
|
||||
]
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
title: Tasting Notes
|
||||
type: note
|
||||
permalink: coffee/tasting-notes
|
||||
tags:
|
||||
- '#coffee'
|
||||
- '#tasting'
|
||||
- '#flavor'
|
||||
- '#cupping'
|
||||
- '#demo'
|
||||
---
|
||||
|
||||
# Tasting Notes
|
||||
|
||||
How to taste and evaluate coffee, identify flavor characteristics, and develop a personal coffee palate.
|
||||
|
||||
## Overview
|
||||
|
||||
Coffee tasting, or "cupping" in professional contexts, is the practice of observing the tastes and aromas of brewed coffee. Developing a coffee palate helps identify preferences, communicate about coffee experiences, and better understand how brewing variables affect the cup.
|
||||
|
||||
## Observations
|
||||
|
||||
- [principle] Flavor perception includes taste, aroma, mouthfeel, and retronasal perception #sensory
|
||||
- [principle] Our taste buds can only perceive sweet, sour, salty, bitter, and umami #taste
|
||||
- [principle] Most of what we call "flavor" is actually aroma detected retronasally #aroma
|
||||
- [technique] Professional coffee tasting (cupping) uses a standardized protocol for consistency #cupping
|
||||
- [technique] Slurping coffee aerates it and spreads it across all taste receptors #technique
|
||||
- [technique] Allowing coffee to cool reveals different flavor notes at different temperatures #temperature
|
||||
|
||||
## Coffee Flavor Wheel
|
||||
|
||||
- [tool] The SCA Coffee Flavor Wheel provides a standardized vocabulary for describing coffee #flavor_wheel
|
||||
- [category] Primary categories include: Fruity, Floral, Sweet, Nutty/Cocoa, Spice, Roasted, Other #categories
|
||||
- [subcategory] Fruity breaks down into: Berry, Dried Fruit, Citrus Fruit, Stone Fruit, Tropical Fruit, etc. #fruit_notes
|
||||
- [subcategory] Floral includes: Floral, Black Tea, Chamomile, Rose, Jasmine, etc. #floral_notes
|
||||
- [subcategory] Sweet includes: Brown Sugar, Molasses, Honey, Maple Syrup, Vanilla, etc. #sweet_notes
|
||||
- [subcategory] Nutty/Cocoa includes: Nut, Cocoa, Dark Chocolate, Chocolate, etc. #nutty_notes
|
||||
- [subcategory] Spice includes: Brown Spice, Pepper, Anise, Nutmeg, Cinnamon, etc. #spice_notes
|
||||
|
||||
## Basic Tasting Components
|
||||
|
||||
- [component] Acidity: The bright, tangy quality (not sourness from under-extraction) #acidity
|
||||
- [component] Sweetness: The pleasant, sugary quality balancing other elements #sweetness
|
||||
- [component] Body: The physical mouthfeel and weight of the coffee #body
|
||||
- [component] Finish/Aftertaste: The flavor that lingers after swallowing #finish
|
||||
- [component] Balance: How well all elements work together #balance
|
||||
- [component] Complexity: The range and layers of distinct flavors #complexity
|
||||
- [component] Cleanliness: Absence of defects or off-flavors #cleanliness
|
||||
|
||||
## Common Flavor Notes by Origin
|
||||
|
||||
- [ethiopia] Blueberry, jasmine, bergamot, lemon, tea-like #flavor_notes
|
||||
- [kenya] Blackcurrant, grapefruit, tomato-like acidity, winey #flavor_notes
|
||||
- [colombia] Caramel, red apple, nuts, chocolate, balanced acidity #flavor_notes
|
||||
- [guatemala] Chocolate, spice, apple, medium acidity #flavor_notes
|
||||
- [brazil] Nuts, chocolate, low-to-medium acidity, full body #flavor_notes
|
||||
- [indonesia] Earthy, herbal, spice, cedar, full body, low acidity #flavor_notes
|
||||
- [costa_rica] Clean, bright, citrus, balanced, light chocolate #flavor_notes
|
||||
|
||||
## Developing Your Palate
|
||||
|
||||
- [technique] Taste coffees side-by-side to identify differences #comparison
|
||||
- [technique] Try describing flavors before looking at roaster's notes #blind_tasting
|
||||
- [technique] Keep a coffee journal with detailed notes about each coffee #journaling
|
||||
- [technique] Explore different processing methods of the same origin #processing
|
||||
- [technique] Try the same coffee brewed with different methods #brewing_comparison
|
||||
- [technique] Use reference flavors (actual fruits, chocolates, etc.) to calibrate your palate #calibration
|
||||
|
||||
## Personal Coffee Experiences
|
||||
|
||||
- [experience] Ethiopian Yirgacheffe prepared as pour over: intense blueberry, jasmine aromatics, tea-like body
|
||||
- [experience] Sumatra Mandheling in French press: earthy, cedar, herbal, tobacco, full body
|
||||
- [experience] Panama Gesha as pour over: intense floral notes, jasmine, bergamot, delicate body
|
||||
- [experience] Brazil Cerrado as espresso: nutty, chocolate, caramel, low acidity, great crema
|
||||
- [experience] Kenya AA as pour over: bright blackcurrant, tomato-like acidity, winey finish
|
||||
|
||||
## Relations
|
||||
|
||||
- determined_by [[Flavor Extraction]]
|
||||
- influenced_by [[Coffee Bean Origins]]
|
||||
- varies_with [[Coffee Brewing Methods]]
|
||||
- enhanced_by [[Proper Grinding Technique]]
|
||||
- documented_in [[Coffee Journal]]
|
||||
- part_of [[Coffee Knowledge Base]]
|
||||
@@ -1,26 +0,0 @@
|
||||
# Basic Memory Installer
|
||||
|
||||
This installer configures Basic Memory to work with Claude Desktop.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the latest installer from the [releases page](https://github.com/basicmachines-co/basic-memory/releases)
|
||||
2. Unzip the downloaded file
|
||||
3. Since the app is currently unsigned, you'll need to:
|
||||
|
||||
On your Mac, choose Apple menu > System Settings, then click Privacy & Security in the sidebar. (You may need to
|
||||
scroll down.)
|
||||
|
||||
Go to Security, then click Open.
|
||||
|
||||
Click Open Anyway.
|
||||
|
||||
This button is available for about an hour after you try to open the app.
|
||||
|
||||
Enter your login password, then click OK.
|
||||
|
||||
https://support.apple.com/guide/mac-help/apple-cant-check-app-for-malicious-software-mchleab3a043/mac
|
||||
|
||||
5. Restart Claude Desktop
|
||||
|
||||
The warning only appears the first time you open the app. Future updates will include proper code signing.
|
||||
@@ -1,64 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Background -->
|
||||
<rect x="0" y="0" width="512" height="512" rx="64" fill="#111111"/>
|
||||
|
||||
<!-- Define arrowhead marker -->
|
||||
<defs>
|
||||
<marker id="arrowhead"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
refX="8"
|
||||
refY="5"
|
||||
orient="auto">
|
||||
<path d="M 0 0 L 10 5 L 0 10 Z"
|
||||
fill="#00cc00"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- State 1 (initial) -->
|
||||
<circle cx="156" cy="256" r="30" fill="none" stroke="#00cc00" stroke-width="3"/>
|
||||
|
||||
<!-- State 2 (accept) -->
|
||||
<circle cx="356" cy="176" r="34" fill="none" stroke="#00cc00" stroke-width="3"/>
|
||||
<circle cx="356" cy="176" r="28" fill="none" stroke="#00cc00" stroke-width="3"/>
|
||||
|
||||
<!-- State 3 (accept) -->
|
||||
<circle cx="356" cy="336" r="34" fill="none" stroke="#00cc00" stroke-width="3"/>
|
||||
<circle cx="356" cy="336" r="28" fill="none" stroke="#00cc00" stroke-width="3"/>
|
||||
|
||||
<!-- Initial arrow -->
|
||||
<path d="M 96 256 L 126 256"
|
||||
stroke="#00cc00" stroke-width="3" fill="none"
|
||||
marker-end="url(#arrowhead)"/>
|
||||
|
||||
<!-- State transitions -->
|
||||
<!-- 1 -> 2 -->
|
||||
<path d="M 180 240
|
||||
Q 260 200, 320 176"
|
||||
stroke="#00cc00" stroke-width="3" fill="none"
|
||||
marker-end="url(#arrowhead)"/>
|
||||
|
||||
<!-- 1 -> 3 -->
|
||||
<path d="M 180 272
|
||||
Q 260 312, 320 336"
|
||||
stroke="#00cc00" stroke-width="3" fill="none"
|
||||
marker-end="url(#arrowhead)"/>
|
||||
|
||||
<!-- Self loops -->
|
||||
<path d="M 356 142
|
||||
Q 396 142, 396 176
|
||||
Q 396 210, 356 210
|
||||
Q 316 210, 316 176
|
||||
Q 316 142, 356 142"
|
||||
stroke="#00cc00" stroke-width="2" fill="none"
|
||||
marker-end="url(#arrowhead)"/>
|
||||
|
||||
<path d="M 356 302
|
||||
Q 396 302, 396 336
|
||||
Q 396 370, 356 370
|
||||
Q 316 370, 316 336
|
||||
Q 316 302, 356 302"
|
||||
stroke="#00cc00" stroke-width="2" fill="none"
|
||||
marker-end="url(#arrowhead)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,93 +0,0 @@
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Use tkinter for GUI alerts on macOS
|
||||
if sys.platform == "darwin":
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
|
||||
|
||||
def ensure_uv_installed():
|
||||
"""Check if uv is installed, install if not."""
|
||||
try:
|
||||
subprocess.run(["uv", "--version"], capture_output=True, check=True)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
print("Installing uv package manager...")
|
||||
subprocess.run(
|
||||
[
|
||||
"curl",
|
||||
"-LsSf",
|
||||
"https://astral.sh/uv/install.sh",
|
||||
"|",
|
||||
"sh",
|
||||
],
|
||||
shell=True,
|
||||
)
|
||||
|
||||
|
||||
def get_config_path():
|
||||
"""Get Claude Desktop config path for current platform."""
|
||||
if sys.platform == "darwin":
|
||||
return Path.home() / "Library/Application Support/Claude/claude_desktop_config.json"
|
||||
elif sys.platform == "win32":
|
||||
return Path.home() / "AppData/Roaming/Claude/claude_desktop_config.json"
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported platform: {sys.platform}")
|
||||
|
||||
|
||||
def update_claude_config():
|
||||
"""Update Claude Desktop config to include basic-memory."""
|
||||
config_path = get_config_path()
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Load existing config or create new
|
||||
if config_path.exists():
|
||||
config = json.loads(config_path.read_text())
|
||||
else:
|
||||
config = {"mcpServers": {}}
|
||||
|
||||
# Add/update basic-memory config
|
||||
config["mcpServers"]["basic-memory"] = {
|
||||
"command": "uvx",
|
||||
"args": ["basic-memory@latest", "mcp"],
|
||||
}
|
||||
|
||||
# Write back config
|
||||
config_path.write_text(json.dumps(config, indent=2))
|
||||
|
||||
|
||||
def print_completion_message():
|
||||
"""Show completion message with helpful tips."""
|
||||
message = """Installation complete! Basic Memory is now available in Claude Desktop.
|
||||
|
||||
Please restart Claude Desktop for changes to take effect.
|
||||
|
||||
Quick Start:
|
||||
1. You can run sync directly using: uvx basic-memory sync
|
||||
2. Optionally, install globally with: uv pip install basic-memory
|
||||
|
||||
Built with ♥️ by Basic Machines."""
|
||||
|
||||
if sys.platform == "darwin":
|
||||
# Show GUI message on macOS
|
||||
root = tk.Tk()
|
||||
root.withdraw() # Hide the main window
|
||||
messagebox.showinfo("Basic Memory", message)
|
||||
root.destroy()
|
||||
else:
|
||||
# Fallback to console output
|
||||
print(message)
|
||||
|
||||
|
||||
def main():
|
||||
print("Welcome to Basic Memory installer")
|
||||
ensure_uv_installed()
|
||||
print("Configuring Claude Desktop...")
|
||||
update_claude_config()
|
||||
print_completion_message()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Convert SVG to PNG at various required sizes
|
||||
rsvg-convert -h 16 -w 16 icon.svg > icon_16x16.png
|
||||
rsvg-convert -h 32 -w 32 icon.svg > icon_32x32.png
|
||||
rsvg-convert -h 128 -w 128 icon.svg > icon_128x128.png
|
||||
rsvg-convert -h 256 -w 256 icon.svg > icon_256x256.png
|
||||
rsvg-convert -h 512 -w 512 icon.svg > icon_512x512.png
|
||||
|
||||
# Create iconset directory
|
||||
mkdir -p Basic.iconset
|
||||
|
||||
# Move files into iconset with Mac-specific names
|
||||
cp icon_16x16.png Basic.iconset/icon_16x16.png
|
||||
cp icon_32x32.png Basic.iconset/icon_16x16@2x.png
|
||||
cp icon_32x32.png Basic.iconset/icon_32x32.png
|
||||
cp icon_128x128.png Basic.iconset/icon_32x32@2x.png
|
||||
cp icon_256x256.png Basic.iconset/icon_128x128.png
|
||||
cp icon_512x512.png Basic.iconset/icon_256x256.png
|
||||
cp icon_512x512.png Basic.iconset/icon_512x512.png
|
||||
|
||||
# Convert iconset to icns
|
||||
iconutil -c icns Basic.iconset
|
||||
|
||||
# Clean up
|
||||
rm -rf Basic.iconset
|
||||
rm icon_*.png
|
||||
@@ -1,40 +0,0 @@
|
||||
from cx_Freeze import setup, Executable
|
||||
import sys
|
||||
|
||||
# Build options for all platforms
|
||||
build_exe_options = {
|
||||
"packages": ["json", "pathlib"],
|
||||
"excludes": ["unittest", "pydoc", "test"],
|
||||
}
|
||||
|
||||
# Platform-specific options
|
||||
if sys.platform == "win32":
|
||||
base = "Win32GUI" # Use GUI base for Windows
|
||||
build_exe_options.update(
|
||||
{
|
||||
"include_msvcr": True,
|
||||
}
|
||||
)
|
||||
target_name = "Basic Memory Installer.exe"
|
||||
else: # darwin
|
||||
base = None # Don't use GUI base for macOS
|
||||
target_name = "Basic Memory Installer"
|
||||
|
||||
executables = [
|
||||
Executable(script="installer.py", target_name=target_name, base=base, icon="Basic.icns")
|
||||
]
|
||||
|
||||
setup(
|
||||
name="basic-memory",
|
||||
version=open("../pyproject.toml").read().split('version = "', 1)[1].split('"', 1)[0],
|
||||
description="Basic Memory - Local-first knowledge management",
|
||||
options={
|
||||
"build_exe": build_exe_options,
|
||||
"bdist_mac": {
|
||||
"bundle_name": "Basic Memory Installer",
|
||||
"iconfile": "Basic.icns",
|
||||
"codesign_identity": "-", # Force ad-hoc signing
|
||||
},
|
||||
},
|
||||
executables=executables,
|
||||
)
|
||||
@@ -0,0 +1,182 @@
|
||||
# Basic Memory - Modern Command Runner
|
||||
|
||||
# Install dependencies
|
||||
install:
|
||||
pip install -e ".[dev]"
|
||||
|
||||
# Run unit tests in parallel
|
||||
test-unit:
|
||||
uv run pytest -p pytest_mock -v -n auto
|
||||
|
||||
# Run integration tests in parallel
|
||||
test-int:
|
||||
uv run pytest -p pytest_mock -v --no-cov -n auto test-int
|
||||
|
||||
# Run all tests
|
||||
test: test-unit test-int
|
||||
|
||||
# Lint and fix code
|
||||
lint:
|
||||
ruff check . --fix
|
||||
|
||||
# Type check code
|
||||
type-check:
|
||||
uv run pyright
|
||||
|
||||
# Clean build artifacts and cache files
|
||||
clean:
|
||||
find . -type f -name '*.pyc' -delete
|
||||
find . -type d -name '__pycache__' -exec rm -r {} +
|
||||
rm -rf installer/build/ installer/dist/ dist/
|
||||
rm -f rw.*.dmg .coverage.*
|
||||
|
||||
# Format code with ruff
|
||||
format:
|
||||
uv run ruff format .
|
||||
|
||||
# Run MCP inspector tool
|
||||
run-inspector:
|
||||
npx @modelcontextprotocol/inspector
|
||||
|
||||
# Build macOS installer
|
||||
installer-mac:
|
||||
cd installer && chmod +x make_icons.sh && ./make_icons.sh
|
||||
cd installer && uv run python setup.py bdist_mac
|
||||
|
||||
# Build Windows installer
|
||||
installer-win:
|
||||
cd installer && uv run python setup.py bdist_win32
|
||||
|
||||
# Update all dependencies to latest versions
|
||||
update-deps:
|
||||
uv sync --upgrade
|
||||
|
||||
# Run all code quality checks and tests
|
||||
check: lint format type-check test
|
||||
|
||||
# Generate Alembic migration with descriptive message
|
||||
migration message:
|
||||
cd src/basic_memory/alembic && alembic revision --autogenerate -m "{{message}}"
|
||||
|
||||
# Create a stable release (e.g., just release v0.13.2)
|
||||
release version:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Validate version format
|
||||
if [[ ! "{{version}}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "❌ Invalid version format. Use: v0.13.2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract version number without 'v' prefix
|
||||
VERSION_NUM=$(echo "{{version}}" | sed 's/^v//')
|
||||
|
||||
echo "🚀 Creating stable release {{version}}"
|
||||
|
||||
# Pre-flight checks
|
||||
echo "📋 Running pre-flight checks..."
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "❌ Uncommitted changes found. Please commit or stash them first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $(git branch --show-current) != "main" ]]; then
|
||||
echo "❌ Not on main branch. Switch to main first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if tag already exists
|
||||
if git tag -l "{{version}}" | grep -q "{{version}}"; then
|
||||
echo "❌ Tag {{version}} already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run quality checks
|
||||
echo "🔍 Running quality checks..."
|
||||
just check
|
||||
|
||||
# Update version in __init__.py
|
||||
echo "📝 Updating version in __init__.py..."
|
||||
sed -i.bak "s/__version__ = \".*\"/__version__ = \"$VERSION_NUM\"/" src/basic_memory/__init__.py
|
||||
rm -f src/basic_memory/__init__.py.bak
|
||||
|
||||
# Commit version update
|
||||
git add src/basic_memory/__init__.py
|
||||
git commit -m "chore: update version to $VERSION_NUM for {{version}} release"
|
||||
|
||||
# Create and push tag
|
||||
echo "🏷️ Creating tag {{version}}..."
|
||||
git tag "{{version}}"
|
||||
|
||||
echo "📤 Pushing to GitHub..."
|
||||
git push origin main
|
||||
git push origin "{{version}}"
|
||||
|
||||
echo "✅ Release {{version}} created successfully!"
|
||||
echo "📦 GitHub Actions will build and publish to PyPI"
|
||||
echo "🔗 Monitor at: https://github.com/basicmachines-co/basic-memory/actions"
|
||||
|
||||
# Create a beta release (e.g., just beta v0.13.2b1)
|
||||
beta version:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Validate version format (allow beta/rc suffixes)
|
||||
if [[ ! "{{version}}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(b[0-9]+|rc[0-9]+)$ ]]; then
|
||||
echo "❌ Invalid beta version format. Use: v0.13.2b1 or v0.13.2rc1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract version number without 'v' prefix
|
||||
VERSION_NUM=$(echo "{{version}}" | sed 's/^v//')
|
||||
|
||||
echo "🧪 Creating beta release {{version}}"
|
||||
|
||||
# Pre-flight checks
|
||||
echo "📋 Running pre-flight checks..."
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "❌ Uncommitted changes found. Please commit or stash them first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $(git branch --show-current) != "main" ]]; then
|
||||
echo "❌ Not on main branch. Switch to main first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if tag already exists
|
||||
if git tag -l "{{version}}" | grep -q "{{version}}"; then
|
||||
echo "❌ Tag {{version}} already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run quality checks
|
||||
echo "🔍 Running quality checks..."
|
||||
just check
|
||||
|
||||
# Update version in __init__.py
|
||||
echo "📝 Updating version in __init__.py..."
|
||||
sed -i.bak "s/__version__ = \".*\"/__version__ = \"$VERSION_NUM\"/" src/basic_memory/__init__.py
|
||||
rm -f src/basic_memory/__init__.py.bak
|
||||
|
||||
# Commit version update
|
||||
git add src/basic_memory/__init__.py
|
||||
git commit -m "chore: update version to $VERSION_NUM for {{version}} beta release"
|
||||
|
||||
# Create and push tag
|
||||
echo "🏷️ Creating tag {{version}}..."
|
||||
git tag "{{version}}"
|
||||
|
||||
echo "📤 Pushing to GitHub..."
|
||||
git push origin main
|
||||
git push origin "{{version}}"
|
||||
|
||||
echo "✅ Beta release {{version}} created successfully!"
|
||||
echo "📦 GitHub Actions will build and publish to PyPI as pre-release"
|
||||
echo "🔗 Monitor at: https://github.com/basicmachines-co/basic-memory/actions"
|
||||
echo "📥 Install with: uv tool install basic-memory --pre"
|
||||
|
||||
# List all available recipes
|
||||
default:
|
||||
@just --list
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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/).
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "basic-memory"
|
||||
version = "0.9.0"
|
||||
dynamic = ["version"]
|
||||
description = "Local-first knowledge management combining Zettelkasten with knowledge graphs"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12.1"
|
||||
@@ -28,8 +28,12 @@ dependencies = [
|
||||
"watchfiles>=1.0.4",
|
||||
"fastapi[standard]>=0.115.8",
|
||||
"alembic>=1.14.1",
|
||||
"qasync>=0.27.1",
|
||||
"pillow>=11.1.0",
|
||||
"pybars3>=0.9.7",
|
||||
"fastmcp>=2.3.4",
|
||||
"pyjwt>=2.10.1",
|
||||
"python-dotenv>=1.1.0",
|
||||
"pytest-aio>=1.9.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -40,9 +44,10 @@ Documentation = "https://github.com/basicmachines-co/basic-memory#readme"
|
||||
|
||||
[project.scripts]
|
||||
basic-memory = "basic_memory.cli.main:app"
|
||||
bm = "basic_memory.cli.main:app"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
@@ -64,16 +69,19 @@ dev-dependencies = [
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-mock>=3.12.0",
|
||||
"pytest-asyncio>=0.24.0",
|
||||
"pytest-xdist>=3.0.0",
|
||||
"ruff>=0.1.6",
|
||||
"pytest>=8.3.4",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-mock>=3.12.0",
|
||||
"pytest-asyncio>=0.24.0",
|
||||
"ruff>=0.1.6",
|
||||
"cx-freeze>=7.2.10",
|
||||
"pyqt6>=6.8.1",
|
||||
]
|
||||
|
||||
[tool.hatch.version]
|
||||
source = "uv-dynamic-versioning"
|
||||
|
||||
[tool.uv-dynamic-versioning]
|
||||
vcs = "git"
|
||||
style = "pep440"
|
||||
bump = true
|
||||
fallback-version = "0.0.0"
|
||||
|
||||
[tool.pyright]
|
||||
include = ["src/"]
|
||||
exclude = ["**/__pycache__"]
|
||||
@@ -84,23 +92,35 @@ reportMissingTypeStubs = false
|
||||
pythonVersion = "3.12"
|
||||
|
||||
|
||||
[tool.semantic_release]
|
||||
version_variables = [
|
||||
"src/basic_memory/__init__.py:__version__",
|
||||
]
|
||||
version_toml = [
|
||||
"pyproject.toml:project.version",
|
||||
]
|
||||
major_on_zero = false
|
||||
branch = "main"
|
||||
changelog_file = "CHANGELOG.md"
|
||||
build_command = "pip install uv && uv build"
|
||||
dist_path = "dist/"
|
||||
upload_to_pypi = true
|
||||
commit_message = "chore(release): {version} [skip ci]"
|
||||
|
||||
[tool.coverage.run]
|
||||
concurrency = ["thread", "gevent"]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = [
|
||||
"pragma: no cover",
|
||||
"def __repr__",
|
||||
"if self.debug:",
|
||||
"if settings.DEBUG",
|
||||
"raise AssertionError",
|
||||
"raise NotImplementedError",
|
||||
"if 0:",
|
||||
"if __name__ == .__main__.:",
|
||||
"class .*\\bProtocol\\):",
|
||||
"@(abc\\.)?abstractmethod",
|
||||
]
|
||||
|
||||
# Exclude specific modules that are difficult to test comprehensively
|
||||
omit = [
|
||||
"*/external_auth_provider.py", # External HTTP calls to OAuth providers
|
||||
"*/supabase_auth_provider.py", # External HTTP calls to Supabase APIs
|
||||
"*/watch_service.py", # File system watching - complex integration testing
|
||||
"*/background_sync.py", # Background processes
|
||||
"*/cli/main.py", # CLI entry point
|
||||
"*/mcp/tools/project_management.py", # Covered by integration tests
|
||||
"*/mcp/tools/sync_status.py", # Covered by integration tests
|
||||
"*/services/migration_service.py", # Complex migration scenarios
|
||||
]
|
||||
|
||||
[tool.logfire]
|
||||
ignore_no_config = true
|
||||
ignore_no_config = true
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Welcome to Basic Memory installer"
|
||||
|
||||
# 1. Install uv if not present
|
||||
if ! command -v uv &> /dev/null; then
|
||||
echo "Installing uv package manager..."
|
||||
curl -LsSf https://github.com/astral-sh/uv/releases/download/0.1.23/uv-installer.sh | sh
|
||||
fi
|
||||
|
||||
# 2. Configure Claude Desktop
|
||||
echo "Configuring Claude Desktop..."
|
||||
CONFIG_FILE="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
|
||||
|
||||
# Create config directory if it doesn't exist
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
# If config file doesn't exist, create it with initial structure
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo '{"mcpServers": {}}' > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Add/update the basic-memory config using jq
|
||||
jq '.mcpServers."basic-memory" = {
|
||||
"command": "uvx",
|
||||
"args": ["basic-memory"]
|
||||
}' "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
|
||||
|
||||
echo "Installation complete! Basic Memory is now available in Claude Desktop."
|
||||
echo "Please restart Claude Desktop for changes to take effect."
|
||||
|
||||
echo -e "\nQuick Start:"
|
||||
echo "1. You can run sync directly using: uvx basic-memory sync"
|
||||
echo "2. Optionally, install globally with: uv pip install basic-memory"
|
||||
echo -e "\nBuilt with ♥️ by Basic Machines."
|
||||
@@ -0,0 +1,15 @@
|
||||
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
|
||||
|
||||
startCommand:
|
||||
type: stdio
|
||||
configSchema:
|
||||
# JSON Schema defining the configuration options for the MCP.
|
||||
type: object
|
||||
properties: {}
|
||||
description: No configuration required. This MCP server runs using the default command.
|
||||
commandFunction: |-
|
||||
(config) => ({
|
||||
command: 'basic-memory',
|
||||
args: ['mcp']
|
||||
})
|
||||
exampleConfig: {}
|
||||
@@ -1,3 +1,7 @@
|
||||
"""basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
|
||||
|
||||
__version__ = "0.9.0"
|
||||
# Package version - updated by release automation
|
||||
__version__ = "0.13.7"
|
||||
|
||||
# API version for FastAPI - independent of package version
|
||||
__api_version__ = "v0"
|
||||
|
||||
@@ -8,12 +8,12 @@ from sqlalchemy import pool
|
||||
|
||||
from alembic import context
|
||||
|
||||
from basic_memory.models import Base
|
||||
|
||||
# set config.env to "test" for pytest to prevent logging to file in utils.setup_logging()
|
||||
os.environ["BASIC_MEMORY_ENV"] = "test"
|
||||
|
||||
from basic_memory.config import config as app_config
|
||||
# Import after setting environment variable # noqa: E402
|
||||
from basic_memory.config import app_config # noqa: E402
|
||||
from basic_memory.models import Base # noqa: E402
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
"""add projects table
|
||||
|
||||
Revision ID: 5fe1ab1ccebe
|
||||
Revises: cc7172b46608
|
||||
Create Date: 2025-05-14 09:05:18.214357
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "5fe1ab1ccebe"
|
||||
down_revision: Union[str, None] = "cc7172b46608"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"project",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("permalink", sa.String(), nullable=False),
|
||||
sa.Column("path", sa.String(), nullable=False),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("is_default", sa.Boolean(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("is_default"),
|
||||
sa.UniqueConstraint("name"),
|
||||
sa.UniqueConstraint("permalink"),
|
||||
if_not_exists=True,
|
||||
)
|
||||
with op.batch_alter_table("project", schema=None) as batch_op:
|
||||
batch_op.create_index(
|
||||
"ix_project_created_at", ["created_at"], unique=False, if_not_exists=True
|
||||
)
|
||||
batch_op.create_index("ix_project_name", ["name"], unique=True, if_not_exists=True)
|
||||
batch_op.create_index("ix_project_path", ["path"], unique=False, if_not_exists=True)
|
||||
batch_op.create_index(
|
||||
"ix_project_permalink", ["permalink"], unique=True, if_not_exists=True
|
||||
)
|
||||
batch_op.create_index(
|
||||
"ix_project_updated_at", ["updated_at"], unique=False, if_not_exists=True
|
||||
)
|
||||
|
||||
with op.batch_alter_table("entity", schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column("project_id", sa.Integer(), nullable=False))
|
||||
batch_op.drop_index(
|
||||
"uix_entity_permalink",
|
||||
sqlite_where=sa.text("content_type = 'text/markdown' AND permalink IS NOT NULL"),
|
||||
)
|
||||
batch_op.drop_index("ix_entity_file_path")
|
||||
batch_op.create_index(batch_op.f("ix_entity_file_path"), ["file_path"], unique=False)
|
||||
batch_op.create_index("ix_entity_project_id", ["project_id"], unique=False)
|
||||
batch_op.create_index(
|
||||
"uix_entity_file_path_project", ["file_path", "project_id"], unique=True
|
||||
)
|
||||
batch_op.create_index(
|
||||
"uix_entity_permalink_project",
|
||||
["permalink", "project_id"],
|
||||
unique=True,
|
||||
sqlite_where=sa.text("content_type = 'text/markdown' AND permalink IS NOT NULL"),
|
||||
)
|
||||
batch_op.create_foreign_key("fk_entity_project_id", "project", ["project_id"], ["id"])
|
||||
|
||||
# drop the search index table. it will be recreated
|
||||
op.drop_table("search_index")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("entity", schema=None) as batch_op:
|
||||
batch_op.drop_constraint("fk_entity_project_id", type_="foreignkey")
|
||||
batch_op.drop_index(
|
||||
"uix_entity_permalink_project",
|
||||
sqlite_where=sa.text("content_type = 'text/markdown' AND permalink IS NOT NULL"),
|
||||
)
|
||||
batch_op.drop_index("uix_entity_file_path_project")
|
||||
batch_op.drop_index("ix_entity_project_id")
|
||||
batch_op.drop_index(batch_op.f("ix_entity_file_path"))
|
||||
batch_op.create_index("ix_entity_file_path", ["file_path"], unique=1)
|
||||
batch_op.create_index(
|
||||
"uix_entity_permalink",
|
||||
["permalink"],
|
||||
unique=1,
|
||||
sqlite_where=sa.text("content_type = 'text/markdown' AND permalink IS NOT NULL"),
|
||||
)
|
||||
batch_op.drop_column("project_id")
|
||||
|
||||
with op.batch_alter_table("project", schema=None) as batch_op:
|
||||
batch_op.drop_index("ix_project_updated_at")
|
||||
batch_op.drop_index("ix_project_permalink")
|
||||
batch_op.drop_index("ix_project_path")
|
||||
batch_op.drop_index("ix_project_name")
|
||||
batch_op.drop_index("ix_project_created_at")
|
||||
|
||||
op.drop_table("project")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,104 @@
|
||||
"""project constraint fix
|
||||
|
||||
Revision ID: 647e7a75e2cd
|
||||
Revises: 5fe1ab1ccebe
|
||||
Create Date: 2025-06-03 12:48:30.162566
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "647e7a75e2cd"
|
||||
down_revision: Union[str, None] = "5fe1ab1ccebe"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Remove the problematic UNIQUE constraint on is_default column.
|
||||
|
||||
The UNIQUE constraint prevents multiple projects from having is_default=FALSE,
|
||||
which breaks project creation when the service sets is_default=False.
|
||||
|
||||
Since SQLite doesn't support dropping specific constraints easily, we'll
|
||||
recreate the table without the problematic constraint.
|
||||
"""
|
||||
# For SQLite, we need to recreate the table without the UNIQUE constraint
|
||||
# Create a new table without the UNIQUE constraint on is_default
|
||||
op.create_table(
|
||||
"project_new",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("permalink", sa.String(), nullable=False),
|
||||
sa.Column("path", sa.String(), nullable=False),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("is_default", sa.Boolean(), nullable=True), # No UNIQUE constraint!
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("name"),
|
||||
sa.UniqueConstraint("permalink"),
|
||||
)
|
||||
|
||||
# Copy data from old table to new table
|
||||
op.execute("INSERT INTO project_new SELECT * FROM project")
|
||||
|
||||
# Drop the old table
|
||||
op.drop_table("project")
|
||||
|
||||
# Rename the new table
|
||||
op.rename_table("project_new", "project")
|
||||
|
||||
# Recreate the indexes
|
||||
with op.batch_alter_table("project", schema=None) as batch_op:
|
||||
batch_op.create_index("ix_project_created_at", ["created_at"], unique=False)
|
||||
batch_op.create_index("ix_project_name", ["name"], unique=True)
|
||||
batch_op.create_index("ix_project_path", ["path"], unique=False)
|
||||
batch_op.create_index("ix_project_permalink", ["permalink"], unique=True)
|
||||
batch_op.create_index("ix_project_updated_at", ["updated_at"], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Add back the UNIQUE constraint on is_default column.
|
||||
|
||||
WARNING: This will break project creation again if multiple projects
|
||||
have is_default=FALSE.
|
||||
"""
|
||||
# Recreate the table with the UNIQUE constraint
|
||||
op.create_table(
|
||||
"project_old",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("permalink", sa.String(), nullable=False),
|
||||
sa.Column("path", sa.String(), nullable=False),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("is_default", sa.Boolean(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("is_default"), # Add back the problematic constraint
|
||||
sa.UniqueConstraint("name"),
|
||||
sa.UniqueConstraint("permalink"),
|
||||
)
|
||||
|
||||
# Copy data (this may fail if multiple FALSE values exist)
|
||||
op.execute("INSERT INTO project_old SELECT * FROM project")
|
||||
|
||||
# Drop the current table and rename
|
||||
op.drop_table("project")
|
||||
op.rename_table("project_old", "project")
|
||||
|
||||
# Recreate indexes
|
||||
with op.batch_alter_table("project", schema=None) as batch_op:
|
||||
batch_op.create_index("ix_project_created_at", ["created_at"], unique=False)
|
||||
batch_op.create_index("ix_project_name", ["name"], unique=True)
|
||||
batch_op.create_index("ix_project_path", ["path"], unique=False)
|
||||
batch_op.create_index("ix_project_permalink", ["permalink"], unique=True)
|
||||
batch_op.create_index("ix_project_updated_at", ["updated_at"], unique=False)
|
||||
@@ -56,12 +56,6 @@ def upgrade() -> None:
|
||||
);
|
||||
""")
|
||||
|
||||
# Print instruction to manually reindex after migration
|
||||
print("\n------------------------------------------------------------------")
|
||||
print("IMPORTANT: After migration completes, manually run the reindex command:")
|
||||
print("basic-memory sync")
|
||||
print("------------------------------------------------------------------\n")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade database schema to use old search index."""
|
||||
|
||||
@@ -1,22 +1,51 @@
|
||||
"""FastAPI application for basic-memory knowledge graph API."""
|
||||
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.exception_handlers import http_exception_handler
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory import __version__ as version
|
||||
from basic_memory import db
|
||||
from basic_memory.config import config as app_config
|
||||
from basic_memory.api.routers import knowledge, search, memory, resource, project_info
|
||||
from basic_memory.api.routers import (
|
||||
directory_router,
|
||||
importer_router,
|
||||
knowledge,
|
||||
management,
|
||||
memory,
|
||||
project,
|
||||
resource,
|
||||
search,
|
||||
prompt_router,
|
||||
)
|
||||
from basic_memory.config import app_config
|
||||
from basic_memory.services.initialization import initialize_app, initialize_file_sync
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI): # pragma: no cover
|
||||
"""Lifecycle manager for the FastAPI app."""
|
||||
await db.run_migrations(app_config)
|
||||
# Initialize app and database
|
||||
logger.info("Starting Basic Memory API")
|
||||
await initialize_app(app_config)
|
||||
|
||||
logger.info(f"Sync changes enabled: {app_config.sync_changes}")
|
||||
if app_config.sync_changes:
|
||||
# start file sync task in background
|
||||
app.state.sync_task = asyncio.create_task(initialize_file_sync(app_config))
|
||||
else:
|
||||
logger.info("Sync changes disabled. Skipping file sync service.")
|
||||
|
||||
# proceed with startup
|
||||
yield
|
||||
|
||||
logger.info("Shutting down Basic Memory API")
|
||||
if app.state.sync_task:
|
||||
logger.info("Stopping sync...")
|
||||
app.state.sync_task.cancel() # pyright: ignore
|
||||
|
||||
await db.shutdown_db()
|
||||
|
||||
|
||||
@@ -24,17 +53,26 @@ async def lifespan(app: FastAPI): # pragma: no cover
|
||||
app = FastAPI(
|
||||
title="Basic Memory API",
|
||||
description="Knowledge graph API for basic-memory",
|
||||
version="0.1.0",
|
||||
version=version,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
||||
# Include routers
|
||||
app.include_router(knowledge.router)
|
||||
app.include_router(search.router)
|
||||
app.include_router(memory.router)
|
||||
app.include_router(resource.router)
|
||||
app.include_router(project_info.router)
|
||||
app.include_router(knowledge.router, prefix="/{project}")
|
||||
app.include_router(memory.router, prefix="/{project}")
|
||||
app.include_router(resource.router, prefix="/{project}")
|
||||
app.include_router(search.router, prefix="/{project}")
|
||||
app.include_router(project.project_router, prefix="/{project}")
|
||||
app.include_router(directory_router.router, prefix="/{project}")
|
||||
app.include_router(prompt_router.router, prefix="/{project}")
|
||||
app.include_router(importer_router.router, prefix="/{project}")
|
||||
|
||||
# Project resource router works accross projects
|
||||
app.include_router(project.project_resource_router)
|
||||
app.include_router(management.router)
|
||||
|
||||
# Auth routes are handled by FastMCP automatically when auth is enabled
|
||||
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"""API routers."""
|
||||
|
||||
from . import knowledge_router as knowledge
|
||||
from . import management_router as management
|
||||
from . import memory_router as memory
|
||||
from . import project_router as project
|
||||
from . import resource_router as resource
|
||||
from . import search_router as search
|
||||
from . import project_info_router as project_info
|
||||
from . import prompt_router as prompt
|
||||
|
||||
__all__ = ["knowledge", "memory", "resource", "search", "project_info"]
|
||||
__all__ = ["knowledge", "management", "memory", "project", "resource", "search", "prompt"]
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Router for directory tree operations."""
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from basic_memory.deps import DirectoryServiceDep, ProjectIdDep
|
||||
from basic_memory.schemas.directory import DirectoryNode
|
||||
|
||||
router = APIRouter(prefix="/directory", tags=["directory"])
|
||||
|
||||
|
||||
@router.get("/tree", response_model=DirectoryNode)
|
||||
async def get_directory_tree(
|
||||
directory_service: DirectoryServiceDep,
|
||||
project_id: ProjectIdDep,
|
||||
):
|
||||
"""Get hierarchical directory structure from the knowledge base.
|
||||
|
||||
Args:
|
||||
directory_service: Service for directory operations
|
||||
project_id: ID of the current project
|
||||
|
||||
Returns:
|
||||
DirectoryNode representing the root of the hierarchical tree structure
|
||||
"""
|
||||
# Get a hierarchical directory tree for the specific project
|
||||
tree = await directory_service.get_directory_tree()
|
||||
|
||||
# Return the hierarchical tree
|
||||
return tree
|
||||
|
||||
|
||||
@router.get("/list", response_model=List[DirectoryNode])
|
||||
async def list_directory(
|
||||
directory_service: DirectoryServiceDep,
|
||||
project_id: ProjectIdDep,
|
||||
dir_name: str = Query("/", description="Directory path to list"),
|
||||
depth: int = Query(1, ge=1, le=10, description="Recursion depth (1-10)"),
|
||||
file_name_glob: Optional[str] = Query(
|
||||
None, description="Glob pattern for filtering file names"
|
||||
),
|
||||
):
|
||||
"""List directory contents with filtering and depth control.
|
||||
|
||||
Args:
|
||||
directory_service: Service for directory operations
|
||||
project_id: ID of the current project
|
||||
dir_name: Directory path to list (default: root "/")
|
||||
depth: Recursion depth (1-10, default: 1 for immediate children only)
|
||||
file_name_glob: Optional glob pattern for filtering file names (e.g., "*.md", "*meeting*")
|
||||
|
||||
Returns:
|
||||
List of DirectoryNode objects matching the criteria
|
||||
"""
|
||||
# Get directory listing with filtering
|
||||
nodes = await directory_service.list_directory(
|
||||
dir_name=dir_name,
|
||||
depth=depth,
|
||||
file_name_glob=file_name_glob,
|
||||
)
|
||||
|
||||
return nodes
|
||||
@@ -0,0 +1,152 @@
|
||||
"""Import router for Basic Memory API."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Form, HTTPException, UploadFile, status
|
||||
|
||||
from basic_memory.deps import (
|
||||
ChatGPTImporterDep,
|
||||
ClaudeConversationsImporterDep,
|
||||
ClaudeProjectsImporterDep,
|
||||
MemoryJsonImporterDep,
|
||||
)
|
||||
from basic_memory.importers import Importer
|
||||
from basic_memory.schemas.importer import (
|
||||
ChatImportResult,
|
||||
EntityImportResult,
|
||||
ProjectImportResult,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/import", tags=["import"])
|
||||
|
||||
|
||||
@router.post("/chatgpt", response_model=ChatImportResult)
|
||||
async def import_chatgpt(
|
||||
importer: ChatGPTImporterDep,
|
||||
file: UploadFile,
|
||||
folder: str = Form("conversations"),
|
||||
) -> ChatImportResult:
|
||||
"""Import conversations from ChatGPT JSON export.
|
||||
|
||||
Args:
|
||||
file: The ChatGPT conversations.json file.
|
||||
folder: The folder to place the files in.
|
||||
markdown_processor: MarkdownProcessor instance.
|
||||
|
||||
Returns:
|
||||
ChatImportResult with import statistics.
|
||||
|
||||
Raises:
|
||||
HTTPException: If import fails.
|
||||
"""
|
||||
return await import_file(importer, file, folder)
|
||||
|
||||
|
||||
@router.post("/claude/conversations", response_model=ChatImportResult)
|
||||
async def import_claude_conversations(
|
||||
importer: ClaudeConversationsImporterDep,
|
||||
file: UploadFile,
|
||||
folder: str = Form("conversations"),
|
||||
) -> ChatImportResult:
|
||||
"""Import conversations from Claude conversations.json export.
|
||||
|
||||
Args:
|
||||
file: The Claude conversations.json file.
|
||||
folder: The folder to place the files in.
|
||||
markdown_processor: MarkdownProcessor instance.
|
||||
|
||||
Returns:
|
||||
ChatImportResult with import statistics.
|
||||
|
||||
Raises:
|
||||
HTTPException: If import fails.
|
||||
"""
|
||||
return await import_file(importer, file, folder)
|
||||
|
||||
|
||||
@router.post("/claude/projects", response_model=ProjectImportResult)
|
||||
async def import_claude_projects(
|
||||
importer: ClaudeProjectsImporterDep,
|
||||
file: UploadFile,
|
||||
folder: str = Form("projects"),
|
||||
) -> ProjectImportResult:
|
||||
"""Import projects from Claude projects.json export.
|
||||
|
||||
Args:
|
||||
file: The Claude projects.json file.
|
||||
base_folder: The base folder to place the files in.
|
||||
markdown_processor: MarkdownProcessor instance.
|
||||
|
||||
Returns:
|
||||
ProjectImportResult with import statistics.
|
||||
|
||||
Raises:
|
||||
HTTPException: If import fails.
|
||||
"""
|
||||
return await import_file(importer, file, folder)
|
||||
|
||||
|
||||
@router.post("/memory-json", response_model=EntityImportResult)
|
||||
async def import_memory_json(
|
||||
importer: MemoryJsonImporterDep,
|
||||
file: UploadFile,
|
||||
folder: str = Form("conversations"),
|
||||
) -> EntityImportResult:
|
||||
"""Import entities and relations from a memory.json file.
|
||||
|
||||
Args:
|
||||
file: The memory.json file.
|
||||
destination_folder: Optional destination folder within the project.
|
||||
markdown_processor: MarkdownProcessor instance.
|
||||
|
||||
Returns:
|
||||
EntityImportResult with import statistics.
|
||||
|
||||
Raises:
|
||||
HTTPException: If import fails.
|
||||
"""
|
||||
try:
|
||||
file_data = []
|
||||
file_bytes = await file.read()
|
||||
file_str = file_bytes.decode("utf-8")
|
||||
for line in file_str.splitlines():
|
||||
json_data = json.loads(line)
|
||||
file_data.append(json_data)
|
||||
|
||||
result = await importer.import_data(file_data, folder)
|
||||
if not result.success: # pragma: no cover
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=result.error_message or "Import failed",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Import failed")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Import failed: {str(e)}",
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
async def import_file(importer: Importer, file: UploadFile, destination_folder: str):
|
||||
try:
|
||||
# Process file
|
||||
json_data = json.load(file.file)
|
||||
result = await importer.import_data(json_data, destination_folder)
|
||||
if not result.success: # pragma: no cover
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=result.error_message or "Import failed",
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Import failed")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Import failed: {str(e)}",
|
||||
)
|
||||
@@ -10,6 +10,11 @@ from basic_memory.deps import (
|
||||
get_search_service,
|
||||
SearchServiceDep,
|
||||
LinkResolverDep,
|
||||
ProjectPathDep,
|
||||
FileServiceDep,
|
||||
ProjectConfigDep,
|
||||
AppConfigDep,
|
||||
SyncServiceDep,
|
||||
)
|
||||
from basic_memory.schemas import (
|
||||
EntityListResponse,
|
||||
@@ -17,8 +22,8 @@ from basic_memory.schemas import (
|
||||
DeleteEntitiesResponse,
|
||||
DeleteEntitiesRequest,
|
||||
)
|
||||
from basic_memory.schemas.request import EditEntityRequest, MoveEntityRequest
|
||||
from basic_memory.schemas.base import Permalink, Entity
|
||||
from basic_memory.services.exceptions import EntityNotFoundError
|
||||
|
||||
router = APIRouter(prefix="/knowledge", tags=["knowledge"])
|
||||
|
||||
@@ -44,43 +49,37 @@ async def create_entity(
|
||||
result = EntityResponse.model_validate(entity)
|
||||
|
||||
logger.info(
|
||||
"API response",
|
||||
endpoint="create_entity",
|
||||
title=result.title,
|
||||
permalink=result.permalink,
|
||||
status_code=201,
|
||||
f"API response: endpoint='create_entity' title={result.title}, permalink={result.permalink}, status_code=201"
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@router.put("/entities/{permalink:path}", response_model=EntityResponse)
|
||||
async def create_or_update_entity(
|
||||
project: ProjectPathDep,
|
||||
permalink: Permalink,
|
||||
data: Entity,
|
||||
response: Response,
|
||||
background_tasks: BackgroundTasks,
|
||||
entity_service: EntityServiceDep,
|
||||
search_service: SearchServiceDep,
|
||||
file_service: FileServiceDep,
|
||||
sync_service: SyncServiceDep,
|
||||
) -> EntityResponse:
|
||||
"""Create or update an entity. If entity exists, it will be updated, otherwise created."""
|
||||
logger.info(
|
||||
"API request",
|
||||
endpoint="create_or_update_entity",
|
||||
permalink=permalink,
|
||||
entity_type=data.entity_type,
|
||||
title=data.title,
|
||||
f"API request: create_or_update_entity for {project=}, {permalink=}, {data.entity_type=}, {data.title=}"
|
||||
)
|
||||
|
||||
# Validate permalink matches
|
||||
if data.permalink != permalink:
|
||||
logger.warning(
|
||||
"API validation error",
|
||||
endpoint="create_or_update_entity",
|
||||
permalink=permalink,
|
||||
data_permalink=data.permalink,
|
||||
error="Permalink mismatch",
|
||||
f"API validation error: creating/updating entity with permalink mismatch - url={permalink}, data={data.permalink}",
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Entity permalink {data.permalink} must match URL path: '{permalink}'",
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="Entity permalink must match URL path")
|
||||
|
||||
# Try create_or_update operation
|
||||
entity, created = await entity_service.create_or_update_entity(data)
|
||||
@@ -88,41 +87,144 @@ async def create_or_update_entity(
|
||||
|
||||
# reindex
|
||||
await search_service.index_entity(entity, background_tasks=background_tasks)
|
||||
|
||||
# Attempt immediate relation resolution when creating new entities
|
||||
# This helps resolve forward references when related entities are created in the same session
|
||||
if created:
|
||||
try:
|
||||
await sync_service.resolve_relations()
|
||||
logger.debug(f"Resolved relations after creating entity: {entity.permalink}")
|
||||
except Exception as e: # pragma: no cover
|
||||
# Don't fail the entire request if relation resolution fails
|
||||
logger.warning(f"Failed to resolve relations after entity creation: {e}")
|
||||
|
||||
result = EntityResponse.model_validate(entity)
|
||||
|
||||
logger.info(
|
||||
"API response",
|
||||
endpoint="create_or_update_entity",
|
||||
title=result.title,
|
||||
permalink=result.permalink,
|
||||
created=created,
|
||||
status_code=response.status_code,
|
||||
f"API response: {result.title=}, {result.permalink=}, {created=}, status_code={response.status_code}"
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@router.patch("/entities/{identifier:path}", response_model=EntityResponse)
|
||||
async def edit_entity(
|
||||
identifier: str,
|
||||
data: EditEntityRequest,
|
||||
background_tasks: BackgroundTasks,
|
||||
entity_service: EntityServiceDep,
|
||||
search_service: SearchServiceDep,
|
||||
) -> EntityResponse:
|
||||
"""Edit an existing entity using various operations like append, prepend, find_replace, or replace_section.
|
||||
|
||||
This endpoint allows for targeted edits without requiring the full entity content.
|
||||
"""
|
||||
logger.info(
|
||||
f"API request: endpoint='edit_entity', identifier='{identifier}', operation='{data.operation}'"
|
||||
)
|
||||
|
||||
try:
|
||||
# Edit the entity using the service
|
||||
entity = await entity_service.edit_entity(
|
||||
identifier=identifier,
|
||||
operation=data.operation,
|
||||
content=data.content,
|
||||
section=data.section,
|
||||
find_text=data.find_text,
|
||||
expected_replacements=data.expected_replacements,
|
||||
)
|
||||
|
||||
# Reindex the updated entity
|
||||
await search_service.index_entity(entity, background_tasks=background_tasks)
|
||||
|
||||
# Return the updated entity response
|
||||
result = EntityResponse.model_validate(entity)
|
||||
|
||||
logger.info(
|
||||
"API response",
|
||||
endpoint="edit_entity",
|
||||
identifier=identifier,
|
||||
operation=data.operation,
|
||||
permalink=result.permalink,
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error editing entity: {e}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/move")
|
||||
async def move_entity(
|
||||
data: MoveEntityRequest,
|
||||
background_tasks: BackgroundTasks,
|
||||
entity_service: EntityServiceDep,
|
||||
project_config: ProjectConfigDep,
|
||||
app_config: AppConfigDep,
|
||||
search_service: SearchServiceDep,
|
||||
) -> EntityResponse:
|
||||
"""Move an entity to a new file location with project consistency.
|
||||
|
||||
This endpoint moves a note to a different path while maintaining project
|
||||
consistency and optionally updating permalinks based on configuration.
|
||||
"""
|
||||
logger.info(
|
||||
f"API request: endpoint='move_entity', identifier='{data.identifier}', destination='{data.destination_path}'"
|
||||
)
|
||||
|
||||
try:
|
||||
# Move the entity using the service
|
||||
moved_entity = await entity_service.move_entity(
|
||||
identifier=data.identifier,
|
||||
destination_path=data.destination_path,
|
||||
project_config=project_config,
|
||||
app_config=app_config,
|
||||
)
|
||||
|
||||
# Get the moved entity to reindex it
|
||||
entity = await entity_service.link_resolver.resolve_link(data.destination_path)
|
||||
if entity:
|
||||
await search_service.index_entity(entity, background_tasks=background_tasks)
|
||||
|
||||
logger.info(
|
||||
"API response",
|
||||
endpoint="move_entity",
|
||||
identifier=data.identifier,
|
||||
destination=data.destination_path,
|
||||
status_code=200,
|
||||
)
|
||||
result = EntityResponse.model_validate(moved_entity)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error moving entity: {e}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
## Read endpoints
|
||||
|
||||
|
||||
@router.get("/entities/{permalink:path}", response_model=EntityResponse)
|
||||
@router.get("/entities/{identifier:path}", response_model=EntityResponse)
|
||||
async def get_entity(
|
||||
entity_service: EntityServiceDep,
|
||||
permalink: str,
|
||||
link_resolver: LinkResolverDep,
|
||||
identifier: str,
|
||||
) -> EntityResponse:
|
||||
"""Get a specific entity by ID.
|
||||
"""Get a specific entity by file path or permalink..
|
||||
|
||||
Args:
|
||||
permalink: Entity path ID
|
||||
content: If True, include full file content
|
||||
identifier: Entity file path or permalink
|
||||
:param entity_service: EntityService
|
||||
:param link_resolver: LinkResolver
|
||||
"""
|
||||
logger.info(f"request: get_entity with permalink={permalink}")
|
||||
try:
|
||||
entity = await entity_service.get_by_permalink(permalink)
|
||||
result = EntityResponse.model_validate(entity)
|
||||
return result
|
||||
except EntityNotFoundError:
|
||||
raise HTTPException(status_code=404, detail=f"Entity with {permalink} not found")
|
||||
logger.info(f"request: get_entity with identifier={identifier}")
|
||||
entity = await link_resolver.resolve_link(identifier)
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail=f"Entity {identifier} not found")
|
||||
|
||||
result = EntityResponse.model_validate(entity)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/entities", response_model=EntityListResponse)
|
||||
@@ -161,8 +263,8 @@ async def delete_entity(
|
||||
# Delete the entity
|
||||
deleted = await entity_service.delete_entity(entity.permalink or entity.id)
|
||||
|
||||
# Remove from search index
|
||||
background_tasks.add_task(search_service.delete_by_permalink, entity.permalink)
|
||||
# Remove from search index (entity, observations, and relations)
|
||||
background_tasks.add_task(search_service.handle_delete, entity)
|
||||
|
||||
result = DeleteEntitiesResponse(deleted=deleted)
|
||||
return result
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Management router for basic-memory API."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
|
||||
from basic_memory.config import app_config
|
||||
from basic_memory.deps import SyncServiceDep, ProjectRepositoryDep
|
||||
|
||||
router = APIRouter(prefix="/management", tags=["management"])
|
||||
|
||||
|
||||
class WatchStatusResponse(BaseModel):
|
||||
"""Response model for watch status."""
|
||||
|
||||
running: bool
|
||||
"""Whether the watch service is currently running."""
|
||||
|
||||
|
||||
@router.get("/watch/status", response_model=WatchStatusResponse)
|
||||
async def get_watch_status(request: Request) -> WatchStatusResponse:
|
||||
"""Get the current status of the watch service."""
|
||||
return WatchStatusResponse(
|
||||
running=request.app.state.watch_task is not None and not request.app.state.watch_task.done()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/watch/start", response_model=WatchStatusResponse)
|
||||
async def start_watch_service(
|
||||
request: Request, project_repository: ProjectRepositoryDep, sync_service: SyncServiceDep
|
||||
) -> WatchStatusResponse:
|
||||
"""Start the watch service if it's not already running."""
|
||||
|
||||
# needed because of circular imports from sync -> app
|
||||
from basic_memory.sync import WatchService
|
||||
from basic_memory.sync.background_sync import create_background_sync_task
|
||||
|
||||
if request.app.state.watch_task is not None and not request.app.state.watch_task.done():
|
||||
# Watch service is already running
|
||||
return WatchStatusResponse(running=True)
|
||||
|
||||
# Create and start a new watch service
|
||||
logger.info("Starting watch service via management API")
|
||||
|
||||
# Get services needed for the watch task
|
||||
watch_service = WatchService(
|
||||
app_config=app_config,
|
||||
project_repository=project_repository,
|
||||
)
|
||||
|
||||
# Create and store the task
|
||||
watch_task = create_background_sync_task(sync_service, watch_service)
|
||||
request.app.state.watch_task = watch_task
|
||||
|
||||
return WatchStatusResponse(running=True)
|
||||
|
||||
|
||||
@router.post("/watch/stop", response_model=WatchStatusResponse)
|
||||
async def stop_watch_service(request: Request) -> WatchStatusResponse: # pragma: no cover
|
||||
"""Stop the watch service if it's running."""
|
||||
if request.app.state.watch_task is None or request.app.state.watch_task.done():
|
||||
# Watch service is not running
|
||||
return WatchStatusResponse(running=False)
|
||||
|
||||
# Cancel the running task
|
||||
logger.info("Stopping watch service via management API")
|
||||
request.app.state.watch_task.cancel()
|
||||
|
||||
# Wait for it to be properly cancelled
|
||||
try:
|
||||
await request.app.state.watch_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
request.app.state.watch_task = None
|
||||
return WatchStatusResponse(running=False)
|
||||
@@ -1,78 +1,22 @@
|
||||
"""Routes for memory:// URI operations."""
|
||||
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from dateparser import parse
|
||||
from fastapi import APIRouter, Query
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory.deps import ContextServiceDep, EntityRepositoryDep
|
||||
from basic_memory.repository import EntityRepository
|
||||
from basic_memory.repository.search_repository import SearchIndexRow
|
||||
from basic_memory.schemas.base import TimeFrame
|
||||
from basic_memory.schemas.base import TimeFrame, parse_timeframe
|
||||
from basic_memory.schemas.memory import (
|
||||
GraphContext,
|
||||
RelationSummary,
|
||||
EntitySummary,
|
||||
ObservationSummary,
|
||||
MemoryMetadata,
|
||||
normalize_memory_url,
|
||||
)
|
||||
from basic_memory.schemas.search import SearchItemType
|
||||
from basic_memory.services.context_service import ContextResultRow
|
||||
from basic_memory.api.routers.utils import to_graph_context
|
||||
|
||||
router = APIRouter(prefix="/memory", tags=["memory"])
|
||||
|
||||
|
||||
async def to_graph_context(context, entity_repository: EntityRepository, page: int, page_size: int):
|
||||
# return results
|
||||
async def to_summary(item: SearchIndexRow | ContextResultRow):
|
||||
match item.type:
|
||||
case SearchItemType.ENTITY:
|
||||
return EntitySummary(
|
||||
title=item.title, # pyright: ignore
|
||||
permalink=item.permalink,
|
||||
content=item.content,
|
||||
file_path=item.file_path,
|
||||
created_at=item.created_at,
|
||||
)
|
||||
case SearchItemType.OBSERVATION:
|
||||
return ObservationSummary(
|
||||
title=item.title, # pyright: ignore
|
||||
file_path=item.file_path,
|
||||
category=item.category, # pyright: ignore
|
||||
content=item.content, # pyright: ignore
|
||||
permalink=item.permalink, # pyright: ignore
|
||||
created_at=item.created_at,
|
||||
)
|
||||
case SearchItemType.RELATION:
|
||||
from_entity = await entity_repository.find_by_id(item.from_id) # pyright: ignore
|
||||
to_entity = await entity_repository.find_by_id(item.to_id) if item.to_id else None
|
||||
return RelationSummary(
|
||||
title=item.title, # pyright: ignore
|
||||
file_path=item.file_path,
|
||||
permalink=item.permalink, # pyright: ignore
|
||||
relation_type=item.type,
|
||||
from_entity=from_entity.permalink, # pyright: ignore
|
||||
to_entity=to_entity.permalink if to_entity else None,
|
||||
created_at=item.created_at,
|
||||
)
|
||||
case _: # pragma: no cover
|
||||
raise ValueError(f"Unexpected type: {item.type}")
|
||||
|
||||
primary_results = [await to_summary(r) for r in context["primary_results"]]
|
||||
related_results = [await to_summary(r) for r in context["related_results"]]
|
||||
metadata = MemoryMetadata.model_validate(context["metadata"])
|
||||
# Transform to GraphContext
|
||||
return GraphContext(
|
||||
primary_results=primary_results,
|
||||
related_results=related_results,
|
||||
metadata=metadata,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/recent", response_model=GraphContext)
|
||||
async def recent(
|
||||
context_service: ContextServiceDep,
|
||||
@@ -95,7 +39,7 @@ async def recent(
|
||||
f"Getting recent context: `{types}` depth: `{depth}` timeframe: `{timeframe}` page: `{page}` page_size: `{page_size}` max_related: `{max_related}`"
|
||||
)
|
||||
# Parse timeframe
|
||||
since = parse(timeframe)
|
||||
since = parse_timeframe(timeframe)
|
||||
limit = page_size
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
@@ -119,7 +63,7 @@ async def get_memory_context(
|
||||
entity_repository: EntityRepositoryDep,
|
||||
uri: str,
|
||||
depth: int = 1,
|
||||
timeframe: TimeFrame = "7d",
|
||||
timeframe: Optional[TimeFrame] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
max_related: int = 10,
|
||||
@@ -133,7 +77,7 @@ async def get_memory_context(
|
||||
memory_url = normalize_memory_url(uri)
|
||||
|
||||
# Parse timeframe
|
||||
since = parse(timeframe)
|
||||
since = parse_timeframe(timeframe) if timeframe else None
|
||||
limit = page_size
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
"""Router for statistics and system information."""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter
|
||||
from sqlalchemy import text
|
||||
|
||||
from basic_memory.config import config, config_manager
|
||||
from basic_memory.deps import (
|
||||
ProjectInfoRepositoryDep,
|
||||
)
|
||||
from basic_memory.repository.project_info_repository import ProjectInfoRepository
|
||||
from basic_memory.schemas import (
|
||||
ProjectInfoResponse,
|
||||
ProjectStatistics,
|
||||
ActivityMetrics,
|
||||
SystemStatus,
|
||||
)
|
||||
from basic_memory.sync.watch_service import WATCH_STATUS_JSON
|
||||
|
||||
router = APIRouter(prefix="/stats", tags=["statistics"])
|
||||
|
||||
|
||||
@router.get("/project-info", response_model=ProjectInfoResponse)
|
||||
async def get_project_info(
|
||||
repository: ProjectInfoRepositoryDep,
|
||||
) -> ProjectInfoResponse:
|
||||
"""Get comprehensive information about the current Basic Memory project."""
|
||||
# Get statistics
|
||||
statistics = await get_statistics(repository)
|
||||
|
||||
# Get activity metrics
|
||||
activity = await get_activity_metrics(repository)
|
||||
|
||||
# Get system status
|
||||
system = await get_system_status()
|
||||
|
||||
# Get project configuration information
|
||||
project_name = config.project
|
||||
project_path = str(config.home)
|
||||
available_projects = config_manager.projects
|
||||
default_project = config_manager.default_project
|
||||
|
||||
# Construct the response
|
||||
return ProjectInfoResponse(
|
||||
project_name=project_name,
|
||||
project_path=project_path,
|
||||
available_projects=available_projects,
|
||||
default_project=default_project,
|
||||
statistics=statistics,
|
||||
activity=activity,
|
||||
system=system,
|
||||
)
|
||||
|
||||
|
||||
async def get_statistics(repository: ProjectInfoRepository) -> ProjectStatistics:
|
||||
"""Get statistics about the current project."""
|
||||
# Get basic counts
|
||||
entity_count_result = await repository.execute_query(text("SELECT COUNT(*) FROM entity"))
|
||||
total_entities = entity_count_result.scalar() or 0
|
||||
|
||||
observation_count_result = await repository.execute_query(
|
||||
text("SELECT COUNT(*) FROM observation")
|
||||
)
|
||||
total_observations = observation_count_result.scalar() or 0
|
||||
|
||||
relation_count_result = await repository.execute_query(text("SELECT COUNT(*) FROM relation"))
|
||||
total_relations = relation_count_result.scalar() or 0
|
||||
|
||||
unresolved_count_result = await repository.execute_query(
|
||||
text("SELECT COUNT(*) FROM relation WHERE to_id IS NULL")
|
||||
)
|
||||
total_unresolved = unresolved_count_result.scalar() or 0
|
||||
|
||||
# Get entity counts by type
|
||||
entity_types_result = await repository.execute_query(
|
||||
text("SELECT entity_type, COUNT(*) FROM entity GROUP BY entity_type")
|
||||
)
|
||||
entity_types = {row[0]: row[1] for row in entity_types_result.fetchall()}
|
||||
|
||||
# Get observation counts by category
|
||||
category_result = await repository.execute_query(
|
||||
text("SELECT category, COUNT(*) FROM observation GROUP BY category")
|
||||
)
|
||||
observation_categories = {row[0]: row[1] for row in category_result.fetchall()}
|
||||
|
||||
# Get relation counts by type
|
||||
relation_types_result = await repository.execute_query(
|
||||
text("SELECT relation_type, COUNT(*) FROM relation GROUP BY relation_type")
|
||||
)
|
||||
relation_types = {row[0]: row[1] for row in relation_types_result.fetchall()}
|
||||
|
||||
# Find most connected entities (most outgoing relations)
|
||||
connected_result = await repository.execute_query(
|
||||
text("""
|
||||
SELECT e.id, e.title, e.permalink, COUNT(r.id) AS relation_count
|
||||
FROM entity e
|
||||
JOIN relation r ON e.id = r.from_id
|
||||
GROUP BY e.id
|
||||
ORDER BY relation_count DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
)
|
||||
most_connected = [
|
||||
{"id": row[0], "title": row[1], "permalink": row[2], "relation_count": row[3]}
|
||||
for row in connected_result.fetchall()
|
||||
]
|
||||
|
||||
# Count isolated entities (no relations)
|
||||
isolated_result = await repository.execute_query(
|
||||
text("""
|
||||
SELECT COUNT(e.id)
|
||||
FROM entity e
|
||||
LEFT JOIN relation r1 ON e.id = r1.from_id
|
||||
LEFT JOIN relation r2 ON e.id = r2.to_id
|
||||
WHERE r1.id IS NULL AND r2.id IS NULL
|
||||
""")
|
||||
)
|
||||
isolated_count = isolated_result.scalar() or 0
|
||||
|
||||
return ProjectStatistics(
|
||||
total_entities=total_entities,
|
||||
total_observations=total_observations,
|
||||
total_relations=total_relations,
|
||||
total_unresolved_relations=total_unresolved,
|
||||
entity_types=entity_types,
|
||||
observation_categories=observation_categories,
|
||||
relation_types=relation_types,
|
||||
most_connected_entities=most_connected,
|
||||
isolated_entities=isolated_count,
|
||||
)
|
||||
|
||||
|
||||
async def get_activity_metrics(repository: ProjectInfoRepository) -> ActivityMetrics:
|
||||
"""Get activity metrics for the current project."""
|
||||
# Get recently created entities
|
||||
created_result = await repository.execute_query(
|
||||
text("""
|
||||
SELECT id, title, permalink, entity_type, created_at
|
||||
FROM entity
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
)
|
||||
recently_created = [
|
||||
{
|
||||
"id": row[0],
|
||||
"title": row[1],
|
||||
"permalink": row[2],
|
||||
"entity_type": row[3],
|
||||
"created_at": row[4],
|
||||
}
|
||||
for row in created_result.fetchall()
|
||||
]
|
||||
|
||||
# Get recently updated entities
|
||||
updated_result = await repository.execute_query(
|
||||
text("""
|
||||
SELECT id, title, permalink, entity_type, updated_at
|
||||
FROM entity
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
)
|
||||
recently_updated = [
|
||||
{
|
||||
"id": row[0],
|
||||
"title": row[1],
|
||||
"permalink": row[2],
|
||||
"entity_type": row[3],
|
||||
"updated_at": row[4],
|
||||
}
|
||||
for row in updated_result.fetchall()
|
||||
]
|
||||
|
||||
# Get monthly growth over the last 6 months
|
||||
# Calculate the start of 6 months ago
|
||||
now = datetime.now()
|
||||
six_months_ago = datetime(
|
||||
now.year - (1 if now.month <= 6 else 0), ((now.month - 6) % 12) or 12, 1
|
||||
)
|
||||
|
||||
# Query for monthly entity creation
|
||||
entity_growth_result = await repository.execute_query(
|
||||
text(f"""
|
||||
SELECT
|
||||
strftime('%Y-%m', created_at) AS month,
|
||||
COUNT(*) AS count
|
||||
FROM entity
|
||||
WHERE created_at >= '{six_months_ago.isoformat()}'
|
||||
GROUP BY month
|
||||
ORDER BY month
|
||||
""")
|
||||
)
|
||||
entity_growth = {row[0]: row[1] for row in entity_growth_result.fetchall()}
|
||||
|
||||
# Query for monthly observation creation
|
||||
observation_growth_result = await repository.execute_query(
|
||||
text(f"""
|
||||
SELECT
|
||||
strftime('%Y-%m', created_at) AS month,
|
||||
COUNT(*) AS count
|
||||
FROM observation
|
||||
INNER JOIN entity ON observation.entity_id = entity.id
|
||||
WHERE entity.created_at >= '{six_months_ago.isoformat()}'
|
||||
GROUP BY month
|
||||
ORDER BY month
|
||||
""")
|
||||
)
|
||||
observation_growth = {row[0]: row[1] for row in observation_growth_result.fetchall()}
|
||||
|
||||
# Query for monthly relation creation
|
||||
relation_growth_result = await repository.execute_query(
|
||||
text(f"""
|
||||
SELECT
|
||||
strftime('%Y-%m', created_at) AS month,
|
||||
COUNT(*) AS count
|
||||
FROM relation
|
||||
INNER JOIN entity ON relation.from_id = entity.id
|
||||
WHERE entity.created_at >= '{six_months_ago.isoformat()}'
|
||||
GROUP BY month
|
||||
ORDER BY month
|
||||
""")
|
||||
)
|
||||
relation_growth = {row[0]: row[1] for row in relation_growth_result.fetchall()}
|
||||
|
||||
# Combine all monthly growth data
|
||||
monthly_growth = {}
|
||||
for month in set(
|
||||
list(entity_growth.keys()) + list(observation_growth.keys()) + list(relation_growth.keys())
|
||||
):
|
||||
monthly_growth[month] = {
|
||||
"entities": entity_growth.get(month, 0),
|
||||
"observations": observation_growth.get(month, 0),
|
||||
"relations": relation_growth.get(month, 0),
|
||||
"total": (
|
||||
entity_growth.get(month, 0)
|
||||
+ observation_growth.get(month, 0)
|
||||
+ relation_growth.get(month, 0)
|
||||
),
|
||||
}
|
||||
|
||||
return ActivityMetrics(
|
||||
recently_created=recently_created,
|
||||
recently_updated=recently_updated,
|
||||
monthly_growth=monthly_growth,
|
||||
)
|
||||
|
||||
|
||||
async def get_system_status() -> SystemStatus:
|
||||
"""Get system status information."""
|
||||
import basic_memory
|
||||
|
||||
# Get database information
|
||||
db_path = config.database_path
|
||||
db_size = db_path.stat().st_size if db_path.exists() else 0
|
||||
db_size_readable = f"{db_size / (1024 * 1024):.2f} MB"
|
||||
|
||||
# Get watch service status if available
|
||||
watch_status = None
|
||||
watch_status_path = config.home / ".basic-memory" / WATCH_STATUS_JSON
|
||||
if watch_status_path.exists():
|
||||
try:
|
||||
watch_status = json.loads(watch_status_path.read_text())
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
|
||||
return SystemStatus(
|
||||
version=basic_memory.__version__,
|
||||
database_path=str(db_path),
|
||||
database_size=db_size_readable,
|
||||
watch_status=watch_status,
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
@@ -0,0 +1,234 @@
|
||||
"""Router for project management."""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Path, Body
|
||||
from typing import Optional
|
||||
|
||||
from basic_memory.deps import ProjectServiceDep, ProjectPathDep
|
||||
from basic_memory.schemas import ProjectInfoResponse
|
||||
from basic_memory.schemas.project_info import (
|
||||
ProjectList,
|
||||
ProjectItem,
|
||||
ProjectInfoRequest,
|
||||
ProjectStatusResponse,
|
||||
)
|
||||
|
||||
# Router for resources in a specific project
|
||||
project_router = APIRouter(prefix="/project", tags=["project"])
|
||||
|
||||
# Router for managing project resources
|
||||
project_resource_router = APIRouter(prefix="/projects", tags=["project_management"])
|
||||
|
||||
|
||||
@project_router.get("/info", response_model=ProjectInfoResponse)
|
||||
async def get_project_info(
|
||||
project_service: ProjectServiceDep,
|
||||
project: ProjectPathDep,
|
||||
) -> ProjectInfoResponse:
|
||||
"""Get comprehensive information about the specified Basic Memory project."""
|
||||
return await project_service.get_project_info(project)
|
||||
|
||||
|
||||
# Update a project
|
||||
@project_router.patch("/{name}", response_model=ProjectStatusResponse)
|
||||
async def update_project(
|
||||
project_service: ProjectServiceDep,
|
||||
project_name: str = Path(..., description="Name of the project to update"),
|
||||
path: Optional[str] = Body(None, description="New path for the project"),
|
||||
is_active: Optional[bool] = Body(None, description="Status of the project (active/inactive)"),
|
||||
) -> ProjectStatusResponse:
|
||||
"""Update a project's information in configuration and database.
|
||||
|
||||
Args:
|
||||
project_name: The name of the project to update
|
||||
path: Optional new path for the project
|
||||
is_active: Optional status update for the project
|
||||
|
||||
Returns:
|
||||
Response confirming the project was updated
|
||||
"""
|
||||
try: # pragma: no cover
|
||||
# Get original project info for the response
|
||||
old_project_info = ProjectItem(
|
||||
name=project_name,
|
||||
path=project_service.projects.get(project_name, ""),
|
||||
)
|
||||
|
||||
await project_service.update_project(project_name, updated_path=path, is_active=is_active)
|
||||
|
||||
# Get updated project info
|
||||
updated_path = path if path else project_service.projects.get(project_name, "")
|
||||
|
||||
return ProjectStatusResponse(
|
||||
message=f"Project '{project_name}' updated successfully",
|
||||
status="success",
|
||||
default=(project_name == project_service.default_project),
|
||||
old_project=old_project_info,
|
||||
new_project=ProjectItem(name=project_name, path=updated_path),
|
||||
)
|
||||
except ValueError as e: # pragma: no cover
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
# List all available projects
|
||||
@project_resource_router.get("/projects", response_model=ProjectList)
|
||||
async def list_projects(
|
||||
project_service: ProjectServiceDep,
|
||||
) -> ProjectList:
|
||||
"""List all configured projects.
|
||||
|
||||
Returns:
|
||||
A list of all projects with metadata
|
||||
"""
|
||||
projects = await project_service.list_projects()
|
||||
default_project = project_service.default_project
|
||||
|
||||
project_items = [
|
||||
ProjectItem(
|
||||
name=project.name,
|
||||
path=project.path,
|
||||
is_default=project.is_default or False,
|
||||
)
|
||||
for project in projects
|
||||
]
|
||||
|
||||
return ProjectList(
|
||||
projects=project_items,
|
||||
default_project=default_project,
|
||||
)
|
||||
|
||||
|
||||
# Add a new project
|
||||
@project_resource_router.post("/projects", response_model=ProjectStatusResponse)
|
||||
async def add_project(
|
||||
project_data: ProjectInfoRequest,
|
||||
project_service: ProjectServiceDep,
|
||||
) -> ProjectStatusResponse:
|
||||
"""Add a new project to configuration and database.
|
||||
|
||||
Args:
|
||||
project_data: The project name and path, with option to set as default
|
||||
|
||||
Returns:
|
||||
Response confirming the project was added
|
||||
"""
|
||||
try: # pragma: no cover
|
||||
await project_service.add_project(
|
||||
project_data.name, project_data.path, set_default=project_data.set_default
|
||||
)
|
||||
|
||||
return ProjectStatusResponse( # pyright: ignore [reportCallIssue]
|
||||
message=f"Project '{project_data.name}' added successfully",
|
||||
status="success",
|
||||
default=project_data.set_default,
|
||||
new_project=ProjectItem(
|
||||
name=project_data.name, path=project_data.path, is_default=project_data.set_default
|
||||
),
|
||||
)
|
||||
except ValueError as e: # pragma: no cover
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
# Remove a project
|
||||
@project_resource_router.delete("/{name}", response_model=ProjectStatusResponse)
|
||||
async def remove_project(
|
||||
project_service: ProjectServiceDep,
|
||||
name: str = Path(..., description="Name of the project to remove"),
|
||||
) -> ProjectStatusResponse:
|
||||
"""Remove a project from configuration and database.
|
||||
|
||||
Args:
|
||||
name: The name of the project to remove
|
||||
|
||||
Returns:
|
||||
Response confirming the project was removed
|
||||
"""
|
||||
try:
|
||||
old_project = await project_service.get_project(name)
|
||||
if not old_project: # pragma: no cover
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Project: '{name}' does not exist"
|
||||
) # pragma: no cover
|
||||
|
||||
await project_service.remove_project(name)
|
||||
|
||||
return ProjectStatusResponse(
|
||||
message=f"Project '{name}' removed successfully",
|
||||
status="success",
|
||||
default=False,
|
||||
old_project=ProjectItem(name=old_project.name, path=old_project.path),
|
||||
new_project=None,
|
||||
)
|
||||
except ValueError as e: # pragma: no cover
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
# Set a project as default
|
||||
@project_resource_router.put("/{name}/default", response_model=ProjectStatusResponse)
|
||||
async def set_default_project(
|
||||
project_service: ProjectServiceDep,
|
||||
name: str = Path(..., description="Name of the project to set as default"),
|
||||
) -> ProjectStatusResponse:
|
||||
"""Set a project as the default project.
|
||||
|
||||
Args:
|
||||
name: The name of the project to set as default
|
||||
|
||||
Returns:
|
||||
Response confirming the project was set as default
|
||||
"""
|
||||
try:
|
||||
# Get the old default project
|
||||
default_name = project_service.default_project
|
||||
default_project = await project_service.get_project(default_name)
|
||||
if not default_project: # pragma: no cover
|
||||
raise HTTPException( # pragma: no cover
|
||||
status_code=404, detail=f"Default Project: '{default_name}' does not exist"
|
||||
)
|
||||
|
||||
# get the new project
|
||||
new_default_project = await project_service.get_project(name)
|
||||
if not new_default_project: # pragma: no cover
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Project: '{name}' does not exist"
|
||||
) # pragma: no cover
|
||||
|
||||
await project_service.set_default_project(name)
|
||||
|
||||
return ProjectStatusResponse(
|
||||
message=f"Project '{name}' set as default successfully",
|
||||
status="success",
|
||||
default=True,
|
||||
old_project=ProjectItem(name=default_name, path=default_project.path),
|
||||
new_project=ProjectItem(
|
||||
name=name,
|
||||
path=new_default_project.path,
|
||||
is_default=True,
|
||||
),
|
||||
)
|
||||
except ValueError as e: # pragma: no cover
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
# Synchronize projects between config and database
|
||||
@project_resource_router.post("/sync", response_model=ProjectStatusResponse)
|
||||
async def synchronize_projects(
|
||||
project_service: ProjectServiceDep,
|
||||
) -> ProjectStatusResponse:
|
||||
"""Synchronize projects between configuration file and database.
|
||||
|
||||
Ensures that all projects in the configuration file exist in the database
|
||||
and vice versa.
|
||||
|
||||
Returns:
|
||||
Response confirming synchronization was completed
|
||||
"""
|
||||
try: # pragma: no cover
|
||||
await project_service.synchronize_projects()
|
||||
|
||||
return ProjectStatusResponse( # pyright: ignore [reportCallIssue]
|
||||
message="Projects synchronized successfully between configuration and database",
|
||||
status="success",
|
||||
default=False,
|
||||
)
|
||||
except ValueError as e: # pragma: no cover
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
@@ -0,0 +1,260 @@
|
||||
"""Router for prompt-related operations.
|
||||
|
||||
This router is responsible for rendering various prompts using Handlebars templates.
|
||||
It centralizes all prompt formatting logic that was previously in the MCP prompts.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory.api.routers.utils import to_graph_context, to_search_results
|
||||
from basic_memory.api.template_loader import template_loader
|
||||
from basic_memory.schemas.base import parse_timeframe
|
||||
from basic_memory.deps import (
|
||||
ContextServiceDep,
|
||||
EntityRepositoryDep,
|
||||
SearchServiceDep,
|
||||
EntityServiceDep,
|
||||
)
|
||||
from basic_memory.schemas.prompt import (
|
||||
ContinueConversationRequest,
|
||||
SearchPromptRequest,
|
||||
PromptResponse,
|
||||
PromptMetadata,
|
||||
)
|
||||
from basic_memory.schemas.search import SearchItemType, SearchQuery
|
||||
|
||||
router = APIRouter(prefix="/prompt", tags=["prompt"])
|
||||
|
||||
|
||||
@router.post("/continue-conversation", response_model=PromptResponse)
|
||||
async def continue_conversation(
|
||||
search_service: SearchServiceDep,
|
||||
entity_service: EntityServiceDep,
|
||||
context_service: ContextServiceDep,
|
||||
entity_repository: EntityRepositoryDep,
|
||||
request: ContinueConversationRequest,
|
||||
) -> PromptResponse:
|
||||
"""Generate a prompt for continuing a conversation.
|
||||
|
||||
This endpoint takes a topic and/or timeframe and generates a prompt with
|
||||
relevant context from the knowledge base.
|
||||
|
||||
Args:
|
||||
request: The request parameters
|
||||
|
||||
Returns:
|
||||
Formatted continuation prompt with context
|
||||
"""
|
||||
logger.info(
|
||||
f"Generating continue conversation prompt, topic: {request.topic}, timeframe: {request.timeframe}"
|
||||
)
|
||||
|
||||
since = parse_timeframe(request.timeframe) if request.timeframe else None
|
||||
|
||||
# Initialize search results
|
||||
search_results = []
|
||||
|
||||
# Get data needed for template
|
||||
if request.topic:
|
||||
query = SearchQuery(text=request.topic, after_date=request.timeframe)
|
||||
results = await search_service.search(query, limit=request.search_items_limit)
|
||||
search_results = await to_search_results(entity_service, results)
|
||||
|
||||
# Build context from results
|
||||
all_hierarchical_results = []
|
||||
for result in search_results:
|
||||
if hasattr(result, "permalink") and result.permalink:
|
||||
# Get hierarchical context using the new dataclass-based approach
|
||||
context_result = await context_service.build_context(
|
||||
result.permalink,
|
||||
depth=request.depth,
|
||||
since=since,
|
||||
max_related=request.related_items_limit,
|
||||
include_observations=True, # Include observations for entities
|
||||
)
|
||||
|
||||
# Process results into the schema format
|
||||
graph_context = await to_graph_context(
|
||||
context_result, entity_repository=entity_repository
|
||||
)
|
||||
|
||||
# Add results to our collection (limit to top results for each permalink)
|
||||
if graph_context.results:
|
||||
all_hierarchical_results.extend(graph_context.results[:3])
|
||||
|
||||
# Limit to a reasonable number of total results
|
||||
all_hierarchical_results = all_hierarchical_results[:10]
|
||||
|
||||
template_context = {
|
||||
"topic": request.topic,
|
||||
"timeframe": request.timeframe,
|
||||
"hierarchical_results": all_hierarchical_results,
|
||||
"has_results": len(all_hierarchical_results) > 0,
|
||||
}
|
||||
else:
|
||||
# If no topic, get recent activity
|
||||
context_result = await context_service.build_context(
|
||||
types=[SearchItemType.ENTITY],
|
||||
depth=request.depth,
|
||||
since=since,
|
||||
max_related=request.related_items_limit,
|
||||
include_observations=True,
|
||||
)
|
||||
recent_context = await to_graph_context(context_result, entity_repository=entity_repository)
|
||||
|
||||
hierarchical_results = recent_context.results[:5] # Limit to top 5 recent items
|
||||
|
||||
template_context = {
|
||||
"topic": f"Recent Activity from ({request.timeframe})",
|
||||
"timeframe": request.timeframe,
|
||||
"hierarchical_results": hierarchical_results,
|
||||
"has_results": len(hierarchical_results) > 0,
|
||||
}
|
||||
|
||||
try:
|
||||
# Render template
|
||||
rendered_prompt = await template_loader.render(
|
||||
"prompts/continue_conversation.hbs", template_context
|
||||
)
|
||||
|
||||
# Calculate metadata
|
||||
# Count items of different types
|
||||
observation_count = 0
|
||||
relation_count = 0
|
||||
entity_count = 0
|
||||
|
||||
# Get the hierarchical results from the template context
|
||||
hierarchical_results_for_count = template_context.get("hierarchical_results", [])
|
||||
|
||||
# For topic-based search
|
||||
if request.topic:
|
||||
for item in hierarchical_results_for_count:
|
||||
if hasattr(item, "observations"):
|
||||
observation_count += len(item.observations) if item.observations else 0
|
||||
|
||||
if hasattr(item, "related_results"):
|
||||
for related in item.related_results or []:
|
||||
if hasattr(related, "type"):
|
||||
if related.type == "relation":
|
||||
relation_count += 1
|
||||
elif related.type == "entity": # pragma: no cover
|
||||
entity_count += 1 # pragma: no cover
|
||||
# For recent activity
|
||||
else:
|
||||
for item in hierarchical_results_for_count:
|
||||
if hasattr(item, "observations"):
|
||||
observation_count += len(item.observations) if item.observations else 0
|
||||
|
||||
if hasattr(item, "related_results"):
|
||||
for related in item.related_results or []:
|
||||
if hasattr(related, "type"):
|
||||
if related.type == "relation":
|
||||
relation_count += 1
|
||||
elif related.type == "entity": # pragma: no cover
|
||||
entity_count += 1 # pragma: no cover
|
||||
|
||||
# Build metadata
|
||||
metadata = {
|
||||
"query": request.topic,
|
||||
"timeframe": request.timeframe,
|
||||
"search_count": len(search_results)
|
||||
if request.topic
|
||||
else 0, # Original search results count
|
||||
"context_count": len(hierarchical_results_for_count),
|
||||
"observation_count": observation_count,
|
||||
"relation_count": relation_count,
|
||||
"total_items": (
|
||||
len(hierarchical_results_for_count)
|
||||
+ observation_count
|
||||
+ relation_count
|
||||
+ entity_count
|
||||
),
|
||||
"search_limit": request.search_items_limit,
|
||||
"context_depth": request.depth,
|
||||
"related_limit": request.related_items_limit,
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
|
||||
prompt_metadata = PromptMetadata(**metadata)
|
||||
|
||||
return PromptResponse(
|
||||
prompt=rendered_prompt, context=template_context, metadata=prompt_metadata
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error rendering continue conversation template: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error rendering prompt template: {str(e)}",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/search", response_model=PromptResponse)
|
||||
async def search_prompt(
|
||||
search_service: SearchServiceDep,
|
||||
entity_service: EntityServiceDep,
|
||||
request: SearchPromptRequest,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
) -> PromptResponse:
|
||||
"""Generate a prompt for search results.
|
||||
|
||||
This endpoint takes a search query and formats the results into a helpful
|
||||
prompt with context and suggestions.
|
||||
|
||||
Args:
|
||||
request: The search parameters
|
||||
page: The page number for pagination
|
||||
page_size: The number of results per page, defaults to 10
|
||||
|
||||
Returns:
|
||||
Formatted search results prompt with context
|
||||
"""
|
||||
logger.info(f"Generating search prompt, query: {request.query}, timeframe: {request.timeframe}")
|
||||
|
||||
limit = page_size
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
query = SearchQuery(text=request.query, after_date=request.timeframe)
|
||||
results = await search_service.search(query, limit=limit, offset=offset)
|
||||
search_results = await to_search_results(entity_service, results)
|
||||
|
||||
template_context = {
|
||||
"query": request.query,
|
||||
"timeframe": request.timeframe,
|
||||
"results": search_results,
|
||||
"has_results": len(search_results) > 0,
|
||||
"result_count": len(search_results),
|
||||
}
|
||||
|
||||
try:
|
||||
# Render template
|
||||
rendered_prompt = await template_loader.render("prompts/search.hbs", template_context)
|
||||
|
||||
# Build metadata
|
||||
metadata = {
|
||||
"query": request.query,
|
||||
"timeframe": request.timeframe,
|
||||
"search_count": len(search_results),
|
||||
"context_count": len(search_results),
|
||||
"observation_count": 0, # Search results don't include observations
|
||||
"relation_count": 0, # Search results don't include relations
|
||||
"total_items": len(search_results),
|
||||
"search_limit": limit,
|
||||
"context_depth": 0, # No context depth for basic search
|
||||
"related_limit": 0, # No related items for basic search
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
|
||||
prompt_metadata = PromptMetadata(**metadata)
|
||||
|
||||
return PromptResponse(
|
||||
prompt=rendered_prompt, context=template_context, metadata=prompt_metadata
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error rendering search template: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error rendering prompt template: {str(e)}",
|
||||
)
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks
|
||||
|
||||
from basic_memory.schemas.search import SearchQuery, SearchResult, SearchResponse
|
||||
from basic_memory.api.routers.utils import to_search_results
|
||||
from basic_memory.schemas.search import SearchQuery, SearchResponse
|
||||
from basic_memory.deps import SearchServiceDep, EntityServiceDep
|
||||
|
||||
router = APIRouter(prefix="/search", tags=["search"])
|
||||
@@ -20,26 +21,7 @@ async def search(
|
||||
limit = page_size
|
||||
offset = (page - 1) * page_size
|
||||
results = await search_service.search(query, limit=limit, offset=offset)
|
||||
|
||||
search_results = []
|
||||
for r in results:
|
||||
entities = await entity_service.get_entities_by_id([r.entity_id, r.from_id, r.to_id]) # pyright: ignore
|
||||
search_results.append(
|
||||
SearchResult(
|
||||
title=r.title, # pyright: ignore
|
||||
type=r.type, # pyright: ignore
|
||||
permalink=r.permalink,
|
||||
score=r.score, # pyright: ignore
|
||||
entity=entities[0].permalink if entities else None,
|
||||
content=r.content,
|
||||
file_path=r.file_path,
|
||||
metadata=r.metadata,
|
||||
category=r.category,
|
||||
from_entity=entities[0].permalink if entities else None,
|
||||
to_entity=entities[1].permalink if len(entities) > 1 else None,
|
||||
relation_type=r.relation_type,
|
||||
)
|
||||
)
|
||||
search_results = await to_search_results(entity_service, results)
|
||||
return SearchResponse(
|
||||
results=search_results,
|
||||
current_page=page,
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
from typing import Optional, List
|
||||
|
||||
from basic_memory.repository import EntityRepository
|
||||
from basic_memory.repository.search_repository import SearchIndexRow
|
||||
from basic_memory.schemas.memory import (
|
||||
EntitySummary,
|
||||
ObservationSummary,
|
||||
RelationSummary,
|
||||
MemoryMetadata,
|
||||
GraphContext,
|
||||
ContextResult,
|
||||
)
|
||||
from basic_memory.schemas.search import SearchItemType, SearchResult
|
||||
from basic_memory.services import EntityService
|
||||
from basic_memory.services.context_service import (
|
||||
ContextResultRow,
|
||||
ContextResult as ServiceContextResult,
|
||||
)
|
||||
|
||||
|
||||
async def to_graph_context(
|
||||
context_result: ServiceContextResult,
|
||||
entity_repository: EntityRepository,
|
||||
page: Optional[int] = None,
|
||||
page_size: Optional[int] = None,
|
||||
):
|
||||
# Helper function to convert items to summaries
|
||||
async def to_summary(item: SearchIndexRow | ContextResultRow):
|
||||
match item.type:
|
||||
case SearchItemType.ENTITY:
|
||||
return EntitySummary(
|
||||
title=item.title, # pyright: ignore
|
||||
permalink=item.permalink,
|
||||
content=item.content,
|
||||
file_path=item.file_path,
|
||||
created_at=item.created_at,
|
||||
)
|
||||
case SearchItemType.OBSERVATION:
|
||||
return ObservationSummary(
|
||||
title=item.title, # pyright: ignore
|
||||
file_path=item.file_path,
|
||||
category=item.category, # pyright: ignore
|
||||
content=item.content, # pyright: ignore
|
||||
permalink=item.permalink, # pyright: ignore
|
||||
created_at=item.created_at,
|
||||
)
|
||||
case SearchItemType.RELATION:
|
||||
from_entity = await entity_repository.find_by_id(item.from_id) # pyright: ignore
|
||||
to_entity = await entity_repository.find_by_id(item.to_id) if item.to_id else None
|
||||
return RelationSummary(
|
||||
title=item.title, # pyright: ignore
|
||||
file_path=item.file_path,
|
||||
permalink=item.permalink, # pyright: ignore
|
||||
relation_type=item.relation_type, # pyright: ignore
|
||||
from_entity=from_entity.title, # pyright: ignore
|
||||
to_entity=to_entity.title if to_entity else None,
|
||||
created_at=item.created_at,
|
||||
)
|
||||
case _: # pragma: no cover
|
||||
raise ValueError(f"Unexpected type: {item.type}")
|
||||
|
||||
# Process the hierarchical results
|
||||
hierarchical_results = []
|
||||
for context_item in context_result.results:
|
||||
# Process primary result
|
||||
primary_result = await to_summary(context_item.primary_result)
|
||||
|
||||
# Process observations
|
||||
observations = []
|
||||
for obs in context_item.observations:
|
||||
observations.append(await to_summary(obs))
|
||||
|
||||
# Process related results
|
||||
related = []
|
||||
for rel in context_item.related_results:
|
||||
related.append(await to_summary(rel))
|
||||
|
||||
# Add to hierarchical results
|
||||
hierarchical_results.append(
|
||||
ContextResult(
|
||||
primary_result=primary_result,
|
||||
observations=observations,
|
||||
related_results=related,
|
||||
)
|
||||
)
|
||||
|
||||
# Create schema metadata from service metadata
|
||||
metadata = MemoryMetadata(
|
||||
uri=context_result.metadata.uri,
|
||||
types=context_result.metadata.types,
|
||||
depth=context_result.metadata.depth,
|
||||
timeframe=context_result.metadata.timeframe,
|
||||
generated_at=context_result.metadata.generated_at,
|
||||
primary_count=context_result.metadata.primary_count,
|
||||
related_count=context_result.metadata.related_count,
|
||||
total_results=context_result.metadata.primary_count + context_result.metadata.related_count,
|
||||
total_relations=context_result.metadata.total_relations,
|
||||
total_observations=context_result.metadata.total_observations,
|
||||
)
|
||||
|
||||
# Return new GraphContext with just hierarchical results
|
||||
return GraphContext(
|
||||
results=hierarchical_results,
|
||||
metadata=metadata,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
async def to_search_results(entity_service: EntityService, results: List[SearchIndexRow]):
|
||||
search_results = []
|
||||
for r in results:
|
||||
entities = await entity_service.get_entities_by_id([r.entity_id, r.from_id, r.to_id]) # pyright: ignore
|
||||
search_results.append(
|
||||
SearchResult(
|
||||
title=r.title, # pyright: ignore
|
||||
type=r.type, # pyright: ignore
|
||||
permalink=r.permalink,
|
||||
score=r.score, # pyright: ignore
|
||||
entity=entities[0].permalink if entities else None,
|
||||
content=r.content,
|
||||
file_path=r.file_path,
|
||||
metadata=r.metadata,
|
||||
category=r.category,
|
||||
from_entity=entities[0].permalink if entities else None,
|
||||
to_entity=entities[1].permalink if len(entities) > 1 else None,
|
||||
relation_type=r.relation_type,
|
||||
)
|
||||
)
|
||||
return search_results
|
||||
@@ -0,0 +1,292 @@
|
||||
"""Template loading and rendering utilities for the Basic Memory API.
|
||||
|
||||
This module handles the loading and rendering of Handlebars templates from the
|
||||
templates directory, providing a consistent interface for all prompt-related
|
||||
formatting needs.
|
||||
"""
|
||||
|
||||
import textwrap
|
||||
from typing import Dict, Any, Optional, Callable
|
||||
from pathlib import Path
|
||||
import json
|
||||
import datetime
|
||||
|
||||
import pybars
|
||||
from loguru import logger
|
||||
|
||||
# Get the base path of the templates directory
|
||||
TEMPLATES_DIR = Path(__file__).parent.parent / "templates"
|
||||
|
||||
|
||||
# Custom helpers for Handlebars
|
||||
def _date_helper(this, *args):
|
||||
"""Format a date using the given format string."""
|
||||
if len(args) < 1: # pragma: no cover
|
||||
return ""
|
||||
|
||||
timestamp = args[0]
|
||||
format_str = args[1] if len(args) > 1 else "%Y-%m-%d %H:%M"
|
||||
|
||||
if hasattr(timestamp, "strftime"):
|
||||
result = timestamp.strftime(format_str)
|
||||
elif isinstance(timestamp, str):
|
||||
try:
|
||||
dt = datetime.datetime.fromisoformat(timestamp)
|
||||
result = dt.strftime(format_str)
|
||||
except ValueError:
|
||||
result = timestamp
|
||||
else:
|
||||
result = str(timestamp) # pragma: no cover
|
||||
|
||||
return pybars.strlist([result])
|
||||
|
||||
|
||||
def _default_helper(this, *args):
|
||||
"""Return a default value if the given value is None or empty."""
|
||||
if len(args) < 2: # pragma: no cover
|
||||
return ""
|
||||
|
||||
value = args[0]
|
||||
default_value = args[1]
|
||||
|
||||
result = default_value if value is None or value == "" else value
|
||||
# Use strlist for consistent handling of HTML escaping
|
||||
return pybars.strlist([str(result)])
|
||||
|
||||
|
||||
def _capitalize_helper(this, *args):
|
||||
"""Capitalize the first letter of a string."""
|
||||
if len(args) < 1: # pragma: no cover
|
||||
return ""
|
||||
|
||||
text = args[0]
|
||||
if not text or not isinstance(text, str): # pragma: no cover
|
||||
result = ""
|
||||
else:
|
||||
result = text.capitalize()
|
||||
|
||||
return pybars.strlist([result])
|
||||
|
||||
|
||||
def _round_helper(this, *args):
|
||||
"""Round a number to the specified number of decimal places."""
|
||||
if len(args) < 1:
|
||||
return ""
|
||||
|
||||
value = args[0]
|
||||
decimal_places = args[1] if len(args) > 1 else 2
|
||||
|
||||
try:
|
||||
result = str(round(float(value), int(decimal_places)))
|
||||
except (ValueError, TypeError):
|
||||
result = str(value)
|
||||
|
||||
return pybars.strlist([result])
|
||||
|
||||
|
||||
def _size_helper(this, *args):
|
||||
"""Return the size/length of a collection."""
|
||||
if len(args) < 1:
|
||||
return 0
|
||||
|
||||
value = args[0]
|
||||
if value is None:
|
||||
result = "0"
|
||||
elif isinstance(value, (list, tuple, dict, str)):
|
||||
result = str(len(value)) # pragma: no cover
|
||||
else: # pragma: no cover
|
||||
result = "0"
|
||||
|
||||
return pybars.strlist([result])
|
||||
|
||||
|
||||
def _json_helper(this, *args):
|
||||
"""Convert a value to a JSON string."""
|
||||
if len(args) < 1: # pragma: no cover
|
||||
return "{}"
|
||||
|
||||
value = args[0]
|
||||
# For pybars, we need to return a SafeString to prevent HTML escaping
|
||||
result = json.dumps(value) # pragma: no cover
|
||||
# Safe string implementation to prevent HTML escaping
|
||||
return pybars.strlist([result])
|
||||
|
||||
|
||||
def _math_helper(this, *args):
|
||||
"""Perform basic math operations."""
|
||||
if len(args) < 3:
|
||||
return pybars.strlist(["Math error: Insufficient arguments"])
|
||||
|
||||
lhs = args[0]
|
||||
operator = args[1]
|
||||
rhs = args[2]
|
||||
|
||||
try:
|
||||
lhs = float(lhs)
|
||||
rhs = float(rhs)
|
||||
if operator == "+":
|
||||
result = str(lhs + rhs)
|
||||
elif operator == "-":
|
||||
result = str(lhs - rhs)
|
||||
elif operator == "*":
|
||||
result = str(lhs * rhs)
|
||||
elif operator == "/":
|
||||
result = str(lhs / rhs)
|
||||
else:
|
||||
result = f"Unsupported operator: {operator}"
|
||||
except (ValueError, TypeError) as e:
|
||||
result = f"Math error: {e}"
|
||||
|
||||
return pybars.strlist([result])
|
||||
|
||||
|
||||
def _lt_helper(this, *args):
|
||||
"""Check if left hand side is less than right hand side."""
|
||||
if len(args) < 2:
|
||||
return False
|
||||
|
||||
lhs = args[0]
|
||||
rhs = args[1]
|
||||
|
||||
try:
|
||||
return float(lhs) < float(rhs)
|
||||
except (ValueError, TypeError):
|
||||
# Fall back to string comparison for non-numeric values
|
||||
return str(lhs) < str(rhs)
|
||||
|
||||
|
||||
def _if_cond_helper(this, options, condition):
|
||||
"""Block helper for custom if conditionals."""
|
||||
if condition:
|
||||
return options["fn"](this)
|
||||
elif "inverse" in options:
|
||||
return options["inverse"](this)
|
||||
return "" # pragma: no cover
|
||||
|
||||
|
||||
def _dedent_helper(this, options):
|
||||
"""Dedent a block of text to remove common leading whitespace.
|
||||
|
||||
Usage:
|
||||
{{#dedent}}
|
||||
This text will have its
|
||||
common leading whitespace removed
|
||||
while preserving relative indentation.
|
||||
{{/dedent}}
|
||||
"""
|
||||
if "fn" not in options: # pragma: no cover
|
||||
return ""
|
||||
|
||||
# Get the content from the block
|
||||
content = options["fn"](this)
|
||||
|
||||
# Convert to string if it's a strlist
|
||||
if (
|
||||
isinstance(content, list)
|
||||
or hasattr(content, "__iter__")
|
||||
and not isinstance(content, (str, bytes))
|
||||
):
|
||||
content_str = "".join(str(item) for item in content) # pragma: no cover
|
||||
else:
|
||||
content_str = str(content) # pragma: no cover
|
||||
|
||||
# Add trailing and leading newlines to ensure proper dedenting
|
||||
# This is critical for textwrap.dedent to work correctly with mixed content
|
||||
content_str = "\n" + content_str + "\n"
|
||||
|
||||
# Use textwrap to dedent the content and remove the extra newlines we added
|
||||
dedented = textwrap.dedent(content_str)[1:-1]
|
||||
|
||||
# Return as a SafeString to prevent HTML escaping
|
||||
return pybars.strlist([dedented]) # pragma: no cover
|
||||
|
||||
|
||||
class TemplateLoader:
|
||||
"""Loader for Handlebars templates.
|
||||
|
||||
This class is responsible for loading templates from disk and rendering
|
||||
them with the provided context data.
|
||||
"""
|
||||
|
||||
def __init__(self, template_dir: Optional[str] = None):
|
||||
"""Initialize the template loader.
|
||||
|
||||
Args:
|
||||
template_dir: Optional custom template directory path
|
||||
"""
|
||||
self.template_dir = Path(template_dir) if template_dir else TEMPLATES_DIR
|
||||
self.template_cache: Dict[str, Callable] = {}
|
||||
self.compiler = pybars.Compiler()
|
||||
|
||||
# Set up standard helpers
|
||||
self.helpers = {
|
||||
"date": _date_helper,
|
||||
"default": _default_helper,
|
||||
"capitalize": _capitalize_helper,
|
||||
"round": _round_helper,
|
||||
"size": _size_helper,
|
||||
"json": _json_helper,
|
||||
"math": _math_helper,
|
||||
"lt": _lt_helper,
|
||||
"if_cond": _if_cond_helper,
|
||||
"dedent": _dedent_helper,
|
||||
}
|
||||
|
||||
logger.debug(f"Initialized template loader with directory: {self.template_dir}")
|
||||
|
||||
def get_template(self, template_path: str) -> Callable:
|
||||
"""Get a template by path, using cache if available.
|
||||
|
||||
Args:
|
||||
template_path: The path to the template, relative to the templates directory
|
||||
|
||||
Returns:
|
||||
The compiled Handlebars template
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the template doesn't exist
|
||||
"""
|
||||
if template_path in self.template_cache:
|
||||
return self.template_cache[template_path]
|
||||
|
||||
# Convert from Liquid-style path to Handlebars extension
|
||||
if template_path.endswith(".liquid"):
|
||||
template_path = template_path.replace(".liquid", ".hbs")
|
||||
elif not template_path.endswith(".hbs"):
|
||||
template_path = f"{template_path}.hbs"
|
||||
|
||||
full_path = self.template_dir / template_path
|
||||
|
||||
if not full_path.exists():
|
||||
raise FileNotFoundError(f"Template not found: {full_path}")
|
||||
|
||||
with open(full_path, "r", encoding="utf-8") as f:
|
||||
template_str = f.read()
|
||||
|
||||
template = self.compiler.compile(template_str)
|
||||
self.template_cache[template_path] = template
|
||||
|
||||
logger.debug(f"Loaded template: {template_path}")
|
||||
return template
|
||||
|
||||
async def render(self, template_path: str, context: Dict[str, Any]) -> str:
|
||||
"""Render a template with the given context.
|
||||
|
||||
Args:
|
||||
template_path: The path to the template, relative to the templates directory
|
||||
context: The context data to pass to the template
|
||||
|
||||
Returns:
|
||||
The rendered template as a string
|
||||
"""
|
||||
template = self.get_template(template_path)
|
||||
return template(context, helpers=self.helpers)
|
||||
|
||||
def clear_cache(self) -> None:
|
||||
"""Clear the template cache."""
|
||||
self.template_cache.clear()
|
||||
logger.debug("Template cache cleared")
|
||||
|
||||
|
||||
# Global template loader instance
|
||||
template_loader = TemplateLoader()
|
||||
@@ -1,18 +1,20 @@
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
import typer
|
||||
|
||||
from basic_memory import db
|
||||
from basic_memory.config import config
|
||||
from basic_memory.config import get_project_config
|
||||
from basic_memory.mcp.project_session import session
|
||||
|
||||
|
||||
def version_callback(value: bool) -> None:
|
||||
"""Show version and exit."""
|
||||
if value: # pragma: no cover
|
||||
import basic_memory
|
||||
from basic_memory.config import config
|
||||
|
||||
typer.echo(f"Basic Memory version: {basic_memory.__version__}")
|
||||
typer.echo(f"Current project: {config.project}")
|
||||
typer.echo(f"Project path: {config.home}")
|
||||
raise typer.Exit()
|
||||
|
||||
|
||||
@@ -21,11 +23,12 @@ app = typer.Typer(name="basic-memory")
|
||||
|
||||
@app.callback()
|
||||
def app_callback(
|
||||
ctx: typer.Context,
|
||||
project: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--project",
|
||||
"-p",
|
||||
help="Specify which project to use",
|
||||
help="Specify which project to use 1",
|
||||
envvar="BASIC_MEMORY_PROJECT",
|
||||
),
|
||||
version: Optional[bool] = typer.Option(
|
||||
@@ -38,28 +41,29 @@ def app_callback(
|
||||
),
|
||||
) -> None:
|
||||
"""Basic Memory - Local-first personal knowledge management."""
|
||||
# We use the project option to set the BASIC_MEMORY_PROJECT environment variable
|
||||
# The config module will pick this up when loading
|
||||
if project: # pragma: no cover
|
||||
import os
|
||||
import importlib
|
||||
from basic_memory import config as config_module
|
||||
|
||||
# Set the environment variable
|
||||
os.environ["BASIC_MEMORY_PROJECT"] = project
|
||||
# Run initialization for every command unless --version was specified
|
||||
if not version and ctx.invoked_subcommand is not None:
|
||||
from basic_memory.config import app_config
|
||||
from basic_memory.services.initialization import ensure_initialization
|
||||
|
||||
# Reload the config module to pick up the new project
|
||||
importlib.reload(config_module)
|
||||
ensure_initialization(app_config)
|
||||
|
||||
# Update the local reference
|
||||
global config
|
||||
from basic_memory.config import config as new_config
|
||||
# Initialize MCP session with the specified project or default
|
||||
if project: # pragma: no cover
|
||||
# Use the project specified via --project flag
|
||||
current_project_config = get_project_config(project)
|
||||
session.set_current_project(current_project_config.name)
|
||||
|
||||
config = new_config
|
||||
# Update the global config to use this project
|
||||
from basic_memory.config import update_current_project
|
||||
|
||||
update_current_project(project)
|
||||
else:
|
||||
# Use the default project
|
||||
current_project = app_config.default_project
|
||||
session.set_current_project(current_project)
|
||||
|
||||
# Run database migrations
|
||||
asyncio.run(db.run_migrations(config))
|
||||
|
||||
# Register sub-command groups
|
||||
import_app = typer.Typer(help="Import data from various sources")
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""CLI commands for basic-memory."""
|
||||
|
||||
from . import status, sync, db, import_memory_json, mcp, import_claude_conversations
|
||||
from . import import_claude_projects, import_chatgpt, tool, project, project_info
|
||||
from . import auth, status, sync, db, import_memory_json, mcp, import_claude_conversations
|
||||
from . import import_claude_projects, import_chatgpt, tool, project
|
||||
|
||||
__all__ = [
|
||||
"auth",
|
||||
"status",
|
||||
"sync",
|
||||
"db",
|
||||
@@ -14,5 +15,4 @@ __all__ = [
|
||||
"import_chatgpt",
|
||||
"tool",
|
||||
"project",
|
||||
"project_info",
|
||||
]
|
||||
|
||||