Compare commits

..

179 Commits

Author SHA1 Message Date
phernandez 48e6e84beb chore: update version to 0.17.2 for v0.17.2 release 2025-12-29 16:47:04 -06:00
phernandez 02c14acddb docs: add CHANGELOG entry for v0.17.2
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-29 16:46:26 -06:00
phernandez 0b5425f163 don't run full tests in release, just lint, typecheck
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-29 16:33:38 -06:00
phernandez 0bcda4a14a fix: allow recent_activity discovery mode in cloud mode
Add `allow_discovery` parameter to `resolve_project_parameter()` that
enables tools like `recent_activity` to work across all projects in
cloud mode without requiring a project parameter.

- Add `allow_discovery: bool = False` param to resolve_project_parameter
- In cloud mode with allow_discovery=True, return None instead of error
- Update recent_activity to use allow_discovery=True
- Fix circular import by deferring call_get import inside functions
- Add comprehensive tests for resolve_project_parameter

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-29 16:22:48 -06:00
phernandez 7a49f57dee chore: update version to 0.17.1 for v0.17.1 release
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-29 10:34:06 -06:00
phernandez 58db2817d2 docs: add CHANGELOG entry for v0.17.1
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-29 10:20:00 -06:00
Paul Hernandez 98fbd60527 fix: only set BASIC_MEMORY_ENV=test during pytest runs (#482)
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-12-29 10:16:45 -06:00
phernandez 6281a81256 chore: update version to 0.17.0 for v0.17.0 release
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-28 16:06:03 -06:00
phernandez be1d0b169f style: format telemetry.py
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-28 16:05:07 -06:00
phernandez 148bf6f75a docs: add CHANGELOG entry for v0.17.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-28 16:03:13 -06:00
phernandez 272a983709 add foss as telementry source, disable analytics for tests
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-28 13:41:40 -06:00
phernandez ef7adb7b99 fix: add cloud_mode check to initialize_app()
MCP server crashes in cloud mode with:
ValueError: DATABASE_URL must be set when using Postgres backend

Root cause: initialize_app() did not check cloud_mode_enabled before
trying to initialize the database. Only ensure_initialization() had
the check. In cloud mode, tenant DBs are per-request via headers,
not via DATABASE_URL environment variable.

Also includes minor formatting fix in telemetry.py.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-28 10:31:17 -06:00
phernandez 3cd9178415 refactor: centralize test environment detection in config.is_test_env
Add is_test_env property to BasicMemoryConfig that checks:
- config.env == "test"
- BASIC_MEMORY_ENV env var is "test"
- PYTEST_CURRENT_TEST is set

Replace duplicated test detection logic in:
- api/app.py
- mcp/server.py
- services/initialization.py
- telemetry.py (disables telemetry during tests)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-27 12:44:42 -06:00
Paul Hernandez 856737fe3c feat: add anonymous usage telemetry (Homebrew-style opt-out) (#478)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 22:09:48 -06:00
Paul Hernandez 1fd680c3f1 feat: add auto-format files on save with built-in Python formatter (#474)
Signed-off-by: Cedric Hurst <cedric@spantree.net>
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Cedric Hurst <cedric@spantree.net>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Sebastian B Otaegui <feniix@users.noreply.github.com>
Co-authored-by: Cedric Hurst <cedric@divideby0.io>
2025-12-24 15:39:22 -06:00
phernandez 38919d11cb docs: update CLAUDE.md with accurate CLI commands and code guidelines
- Add Code Change Guidelines section (full file read, minimize diffs, fail fast, no guessing)
- Add Literate Programming Style section (section headers, decision point comments)
- Fix CLI commands: `tools` -> `tool`, add project management commands
- Fix cloud commands to match current CLI (status, setup)
- Remove non-existent MCP tools (get_current_project, sync_status)
- Add ChatGPT-compatible tools (search, fetch)
- Remove non-existent json_canvas_spec prompt
- Add /importers to codebase architecture
- Remove unused python-developer and system-architect agents

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-24 15:16:02 -06:00
phernandez 85684f848f fix: handle UTF-8 BOM in frontmatter parsing
Fixes #452 - Imported conversations not fully indexed

Files with UTF-8 BOM (Byte Order Mark) at the start would fail frontmatter
detection, causing:
- Title to fall back to filename instead of frontmatter value
- Permalink to be null in the database

Added strip_bom() helper function and updated all frontmatter-related
functions to strip BOM before processing:
- has_frontmatter()
- parse_frontmatter()
- remove_frontmatter()
- EntityParser.parse_markdown_content()

Added comprehensive tests for BOM handling with various scenarios.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-24 14:28:50 -06:00
Paul Hernandez 14ce5a3bd0 fix: handle null titles in ChatGPT import (#475)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-24 13:50:30 -06:00
phernandez 45d6caf723 fix: remove MaxLen constraint from observation content
The API Pydantic schema had a MaxLen(1000) constraint on observation
content while the database uses SQLAlchemy's Text type (unlimited).
This mismatch caused validation errors when observations exceeded
1000 characters (e.g., JSON schemas with 1458+ chars).

Removed the MaxLen constraint to match the DB schema. Retained:
- BeforeValidator(str.strip) for whitespace cleaning
- MinLen(1) to ensure non-empty content

Added comprehensive tests to verify:
- Long content (10K+ chars) is accepted
- Very long content (50K+ chars) is accepted
- Empty/whitespace-only content is still rejected
- Whitespace stripping still works

Fixes #385

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-24 13:41:28 -06:00
phernandez 1652f862dd fix: handle FileNotFoundError gracefully during sync
When a file exists in the database but is missing from the filesystem,
the sync worker now treats this as a deletion instead of crashing.

The sync_file() method catches FileNotFoundError specifically and calls
handle_delete() to clean up the orphaned database record. This prevents
the sync from failing on database/filesystem inconsistencies that can
occur due to race conditions, manual file deletions, or cloud storage
caching issues.

Includes a test to verify the graceful handling behavior.

Fixes #386

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-24 13:34:59 -06:00
phernandez c23927d124 fix: use canonical project names in API response messages
Use database-retrieved project names (new_project.name, old_project.name)
instead of input parameters (project_data.name, name) in v1 API response
messages to ensure consistent project name casing.

The v2 API already did this correctly. This fixes issue #450 where project
names would display with different casing between add and remove operations.

Fixes #450

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-24 13:28:06 -06:00
jope-bm 1a74d85973 feat: Complete Phase 2 of API v2 migration - Update MCP tools to use v2 endpoints (#447)
Signed-off-by: Joe P <joe@basicmemory.com>
Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Co-authored-by: phernandez <paul@basicmachines.co>
2025-12-24 12:59:14 -06:00
phernandez d71c6e8568 fix: suppress CLI warnings for cleaner output
Suppress DeprecationWarning from aiosqlite and LogfireNotConfiguredWarning
that were cluttering CLI output.

The key fix is applying warnings.filterwarnings("ignore") AFTER all imports
in main.py, because authlib (imported via cloud commands) adds a
DeprecationWarning filter that overrides earlier suppressions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-24 12:21:14 -06:00
phernandez 63b98491be fix: prevent DEBUG logs from appearing on CLI stdout
Remove loguru's default handler at the very start of cli/app.py,
before any other imports. This prevents module-level code (like
TemplateLoader.__init__) from logging to stdout during import.

The import chain cli/commands/project.py -> mcp/async_client.py ->
api/app.py -> api/routers/prompt_router.py -> api/template_loader.py
triggers TemplateLoader() instantiation at DEBUG level before
init_cli_logging() can remove the default handler.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-24 11:42:25 -06:00
Paul Hernandez 622d37e4a8 fix: detect rclone version for --create-empty-src-dirs support (#473)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 11:27:49 -06:00
Paul Hernandez 916baf8971 fix: prevent CLI commands from hanging on exit (#471)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 11:27:11 -06:00
Drew Cain 95937c6d0a fix: make test-int-postgres compatible with macOS
Use gtimeout (Homebrew) or timeout (Linux), falling back to running
without timeout if neither is available. This fixes 'command not found'
errors on macOS which doesn't have GNU timeout by default.
2025-12-20 10:27:26 -06:00
Drew Cain 24dc9a2931 chore: update version to 0.16.3 for v0.16.3 release 2025-12-20 09:53:37 -06:00
Drew Cain 85c63e5a7a docs: add CHANGELOG entry for v0.16.3 2025-12-20 09:26:00 -06:00
Drew Cain f227ef6a86 fix: Pin FastMCP to 2.12.3 to fix MCP tools visibility (#464)
Signed-off-by: Drew Cain <groksrc@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-20 08:54:44 -06:00
Paul Hernandez 897b1edaa4 fix: Reduce watch service CPU usage by increasing reload interval (#458)
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-12-17 11:37:01 -06:00
Paul Hernandez 0c12a39a98 test: Add integration test for issue #416 (read_note with underscored folders) (#453)
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-12-17 09:56:45 -06:00
Paul Hernandez efbc758325 fix: await background sync task cancellation in lifespan shutdown (#456)
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-12-17 09:53:34 -06:00
Paul Hernandez a0f20eb102 chore: more Tenantless fixes (#457)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 18:34:05 -06:00
Paul Hernandez 78673d8e51 chore: Cloud compatibility fixes and performance improvements (#454)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 20:07:55 -06:00
phernandez 126c0495c0 Merge branch 'main' of github.com:basicmachines-co/basic-memory 2025-12-13 15:24:43 -06:00
Paul Hernandez 4a43d7df4a remove logfire instrumentation
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-13 15:22:14 -06:00
Paul Hernandez c462faf046 Replace py-pglite with testcontainers for Postgres testing (#449)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 22:17:56 -06:00
Cedric Hurst 70bb10be1d fix: respect --project flag in background sync (fixes #434) (#436)
Signed-off-by: Cedric Hurst <cedric@spantree.net>
2025-12-08 12:58:18 -06:00
phernandez fbf9045d78 use asyncpg for just db-migrate
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-05 15:01:35 -06:00
phernandez 1094210c52 fix broken sqlite migration
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-02 20:23:55 -06:00
phernandez 391feb639f add delete cascade to entity to delete search_index (postgres only)
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-02 10:04:21 -06:00
phernandez a920a9ff29 feat: Add project_id to Relation and Observation for efficient project-scoped queries
Denormalizes project_id onto Relation and Observation tables to enable
efficient project-scoped queries without joins. Migration backfills
from associated entity and adds pg_trgm extension with GIN indexes
for fuzzy link resolution on PostgreSQL.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-01 21:54:56 -06:00
phernandez 05efe8701c test: Verify update() returns entity with eager-loaded relations
Add test confirming entity_repository.update() returns the entity with
observations and relations eagerly loaded, eliminating the need for a
separate find_by_id() call after update.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-12-01 16:22:21 -06:00
phernandez 0eaf30bb06 remove conflict constraint name from relation_repository.py
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-30 19:31:09 -06:00
phernandez 0818bda565 feat: Add bulk insert with ON CONFLICT handling for relations
Add add_all_ignore_duplicates() method to RelationRepository for bulk
inserting relations with ON CONFLICT DO NOTHING. This handles cases
where the same [[wiki link]] appears multiple times in a document,
silently ignoring duplicates based on the (from_id, to_name, relation_type)
unique constraint.

Works with both SQLite and PostgreSQL dialects.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-30 14:59:52 -06:00
phernandez 6f99d2e551 perf: lightweight permalink resolution to avoid eager loading
Add optimized repository methods for resolve_permalink() that skip
eager loading of observations and relations:

- permalink_exists(): Check existence without loading entity
- get_file_path_for_permalink(): Get only file_path column
- get_permalink_for_file_path(): Get only permalink column
- get_all_permalinks(): Get all permalinks as strings
- get_permalink_to_file_path_map(): Bulk lookup mapping
- get_file_path_to_permalink_map(): Reverse mapping

Updated entity_service.resolve_permalink() to use these lightweight
methods instead of loading full entities with all relationships.

Also added logfire instrumentation to markdown utils.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-30 14:34:31 -06:00
phernandez 73d940e064 fix: observation parsing and permalink limits (#446)
1. Hashtag detection now checks for standalone words starting with #
   instead of just checking if # appears anywhere in content.
   This prevents HTML color codes like #4285F4 from being
   interpreted as hashtags.

2. Observation permalinks now truncate content to 200 chars
   to stay under PostgreSQL's btree index limit of 2704 bytes.

Added tests for both fixes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-30 00:12:04 -06:00
phernandez c3678a11d2 truncate content_stems to fix Postgres 8KB index row limit
Large documents (like ~1MB conversation imports) exceed Postgres's 8KB
index row limit, causing ProgramLimitExceededError. Truncate content_stems
to 6000 characters (with headroom for other columns) before indexing.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-29 20:23:09 -06:00
phernandez 203d684c24 fix integrity error handling when setting forward relation refs
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-29 19:03:12 -06:00
phernandez a872220924 disable pooling for postgres db
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-29 13:24:47 -06:00
phernandez 7d763a66ff use entity.mtime for updated at in api
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-29 13:21:58 -06:00
phernandez b5d4fb559c fix: postgres/neon connection settings and search index dedupe
- Reduce db_pool_recycle from 3600s to 180s for Neon scale-to-zero
- Add connect_args for Neon serverless (statement cache, timeouts, app name)
- Dedupe observation permalinks in search indexing to avoid unique constraint violations
- Add tests for duplicate observation permalink handling

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-29 11:24:08 -06:00
phernandez 830775276d remove record_return=True from logfire spans
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-28 18:36:09 -06:00
phernandez ed894fc3ed get db pool sizes from config
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-28 16:50:57 -06:00
phernandez 704338edcf remove logfire.instrument_fastapi(app) from app.py
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-28 13:52:18 -06:00
phernandez 0ca02a7ebe add logfire instrumentation to services and repository code
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-28 12:47:29 -06:00
jope-bm 28cc5225a7 feat: Implement API v2 with ID-based endpoints (Phase 1) (#441)
Signed-off-by: Joe P <joe@basicmemory.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Co-authored-by: phernandez <paul@basicmachines.co>
2025-11-27 10:35:55 -06:00
phernandez 9b7bbc7116 formatting and logic change to resolve_relations, remove fuzzy search 2025-11-25 22:54:37 -06:00
phernandez 138c283d6c add postgres db type 2025-11-25 20:25:56 -06:00
phernandez 7a8954c37e add extra logic for cloud-indexing improvements 2025-11-25 13:52:58 -06:00
phernandez 10c7c19c03 fix db url for sqlite migrations
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-21 13:21:20 -06:00
Paul Hernandez fb5e9e1d77 feat: Add PostgreSQL database backend support (#439)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-20 11:20:29 -06:00
phernandez 66b91b2847 ci: Add PostgreSQL testing to GitHub Actions workflow
Add Postgres service container and separate test step for PostgreSQL backend testing.
The Postgres tests only run on Linux runners since GitHub Actions service containers
are only available on Linux.

- Add postgres:17 service container with health checks
- Add 'Run tests (Postgres)' step with Linux-only condition
- Rename existing test step to 'Run tests (SQLite)' for clarity

This enables CI testing of dual database backend support introduced in the
postgres-support feature branch.

Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-18 12:25:18 -06:00
Cedric Hurst b004565df9 fix: handle periods in kebab_filenames mode (#424) 2025-11-18 06:49:55 -05:00
Drew Cain a258b73e1d chore: update version to 0.16.2 for v0.16.2 release 2025-11-16 21:30:59 -06:00
Drew Cain 9a845f2906 docs: prepare for v0.16.2 release 2025-11-16 21:28:13 -06:00
Drew Cain 6517e9845f fix: Use platform-native path separators in config.json (#429)
Signed-off-by: Drew Cain <groksrc@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-13 09:12:26 -06:00
Drew Cain 1af05392ee fix: Add rclone installation checks for Windows bisync commands (#427) 2025-11-12 14:22:14 -06:00
Brandon Mayes cad7019c89 fix: main project always recreated on project list command (#421) 2025-11-12 09:57:08 -05:00
phernandez 099c334e3d chore: update version to 0.16.1 for v0.16.1 release 2025-11-11 09:21:47 -06:00
phernandez 7685586178 docs: Add v0.16.1 CHANGELOG entry for Windows line ending fix
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-11 09:10:26 -06:00
Paul Hernandez e9d0a944a9 fix: Handle Windows line endings in rclone bisync (#422)
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-11 09:08:20 -06:00
phernandez caf3c14bb1 chore: update version to 0.16.0 for v0.16.0 release 2025-11-10 19:19:48 -06:00
phernandez c5d9067754 docs: Add v0.16.0 CHANGELOG entry with comprehensive release notes
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-10 19:17:29 -06:00
phernandez e0fc59ea97 style: Format upload.py for better readability
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-10 19:15:03 -06:00
Paul Hernandez 49b2adc35c fix: skip archive files during cloud upload (#420)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-11-10 19:04:49 -06:00
Paul Hernandez 1646572f69 fix: Rename write_note entity_type to note_type for clarity (#419)
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-11-10 19:02:50 -06:00
Paul Hernandez f0d7398815 fix: Quote string values in YAML frontmatter to handle special characters (#418)
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-11-10 18:13:48 -06:00
Paul Hernandez 581b7b17c6 fix: Add explicit type annotations to MCP tool parameters (#394)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-11-10 17:02:17 -06:00
Paul Hernandez d775f7bab9 fix: Simplify search_notes schema by removing Optional wrappers (#395)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-11-10 16:30:59 -06:00
Drew Cain fc01f6abaf fix: Replace Unicode arrows with ASCII for Windows compatibility (#414) 2025-11-10 16:30:41 -06:00
Paul Hernandez 4614fd09d5 fix: Handle dict objects in write_resource endpoint (#415)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-11-10 16:30:27 -06:00
phernandez 0d4ad7bbf0 remove v0.15.0 info from assistant-guide
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-10 16:25:49 -06:00
jope-bm 7ccec7eba2 feat: Add run_in_background parameter to sync endpoint with tests (#417)
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-07 09:04:43 -07:00
Paul Hernandez 021af74545 fix: Strip duplicate headers in edit_note replace_section (#396)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Drew Cain <groksrc@users.noreply.github.com>
2025-11-02 14:20:11 -06:00
phernandez 2ad0ee9d5d fix: Use force_full=true for database sync after project sync/bisync
After rclone synchronizes files between local and cloud storage, the
database needs to perform a full scan to ensure it captures all changes.
Previously, incremental sync (watermark optimization) could miss files
that were changed remotely.

Changes:
- project sync command now calls /project/sync?force_full=true
- project bisync command now calls /project/sync?force_full=true
- Ensures complete database refresh after file synchronization

This guarantees the database is fully in sync with the filesystem
after any rclone sync or bisync operation.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-11-02 13:26:08 -06:00
Brandon Mayes c9946ecf1e fix: Various rclone fixes for cloud sync on Windows (#410)
Signed-off-by: Brandon Mayes <5610870+bdmayes@users.noreply.github.com>
Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Co-authored-by: phernandez <paul@basicmachines.co>
2025-11-02 11:26:57 -06:00
Drew Cain 0ba6f219f1 fix: Windows CLI Unicode encoding errors (#411)
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Co-authored-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
2025-11-02 10:20:48 -06:00
Paul Hernandez 0b3272ae6e feat: SPEC-20 Simplified Project-Scoped Rclone Sync (#405)
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-02 09:35:26 -06:00
Paul Hernandez a7d7cc5ee6 fix: Normalize YAML frontmatter types to prevent AttributeError (#236) (#402)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-27 09:19:50 -05:00
Paul Hernandez a7e696b039 Add free trial information to README
Added information about a 7-day free trial.

Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
2025-10-24 10:05:11 -05:00
Paul Hernandez 8aaddb6d45 Add free trial information to README
Added information about a 7-day free trial to the README.

Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
2025-10-24 10:04:41 -05:00
Paul Hernandez d7565312fc Announce Basic Memory Cloud launch in README
Added a section announcing the launch of Basic Memory Cloud with details on cross-device support and early supporter pricing.

Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
2025-10-24 09:53:14 -05:00
Paul Hernandez c7e6eab02f feat: Add delete_notes parameter to remove project endpoint (#391)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-21 14:09:20 -05:00
Paul Hernandez bb8da31472 fix: Handle null, empty, and string 'None' title in markdown frontmatter (#387) (#389)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-10-21 09:29:19 -05:00
Paul Hernandez e78345ff25 feat: Streaming Foundation & Async I/O Consolidation (SPEC-19) (#384)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-21 09:03:59 -05:00
Paul Hernandez 32236cd247 fix: Handle YAML parsing errors gracefully in update_frontmatter (#378) (#379)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 20:17:58 -05:00
Paul Hernandez 4fd6d0c648 fix: Optimize sync memory usage to prevent OOM on large projects (#380)
Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-16 20:17:34 -05:00
Paul Hernandez e6c8e3662c fix: preserve mtime webdav upload 376 (#377)
Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 17:18:10 -05:00
Paul Hernandez 449b62d947 fix: Prevent deleted projects from being recreated by background sync (#193) (#370)
Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-16 15:16:24 -05:00
Paul Hernandez b7497d7484 fix: Use filesystem timestamps for entity sync instead of database operation time (#138) (#369)
Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-16 14:21:27 -05:00
Paul Hernandez d1431bdb1b fix: Handle YAML parsing errors and missing entity_type in markdown files (#368)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 13:18:58 -05:00
Paul Hernandez 171bef717f fix: Resolve UNIQUE constraint violation in entity upsert with observations (#187) (#367)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 12:30:56 -05:00
Paul Hernandez 729a5a3b8d fix: Terminate sync immediately when project is deleted (#366)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 11:07:12 -05:00
Paul Hernandez 434cdf24dd feat: Add circuit breaker for file sync failures (#364)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 09:47:48 -05:00
Paul Hernandez 7f9c1a97a4 feat: Add --verbose and --no-gitignore options to cloud upload (#362)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-15 20:03:36 -05:00
Paul Hernandez 53fb13b054 fix: Make project creation endpoint idempotent (#357)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-15 19:27:41 -05:00
Paul Hernandez bd6c8348b8 fix: Handle None text values in Claude conversations importer (#353)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-10-15 16:08:57 -05:00
phernandez 994c8b8e7e chore: update version to 0.15.2 for v0.15.2 release 2025-10-14 09:36:47 -05:00
phernandez a78e8c3ac5 style: Apply linter formatting changes 2025-10-14 09:34:10 -05:00
phernandez 53900c5baa fix: Project commands now respect cloud_mode at runtime
- Moved config evaluation from module load time to runtime
- Unified add_project command to handle both cloud and local modes
- Commands (default, sync-config, move) now check cloud_mode at runtime
- Fixes test failures where monkeypatch wasn't applied before command registration
2025-10-14 09:24:13 -05:00
phernandez 02c6de3387 docs: Add v0.15.2 changelog entry 2025-10-14 00:43:14 -05:00
phernandez 9ccf4b6b56 remove extra conole out from sync message after upload
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-14 00:38:02 -05:00
phernandez ba74ca7e18 fix: Update CloudProjectCreateResponse schema to match API response
The /proxy/projects/projects POST endpoint returns a ProjectStatusResponse
with fields: message, status, default, old_project, new_project.

Updated CloudProjectCreateResponse schema to match this format instead of
expecting name, path, message fields.

Also updated all related tests to use the correct response format:
- tests/cli/test_cloud_utils.py (3 tests)
- tests/cli/test_bisync_commands.py (1 test)

Fixes the validation error when creating cloud projects via upload command:
"bm cloud upload --project test --create-project specs"

Signed-off-by: Pablo Hernandez <pablo@basicmachines.co>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-13 23:56:59 -05:00
Paul Hernandez 5258f45730 feat: Add WebDAV upload command for cloud projects (#356)
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Pablo Hernandez <pablo@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-13 23:20:54 -05:00
phernandez e773c002ce chore: update version to 0.15.1 for v0.15.1 release 2025-10-13 11:04:44 -05:00
phernandez e70ba944e7 docs: Add v0.15.1 changelog entry
Add comprehensive changelog for v0.15.1 release including:
- Performance improvements (43% faster sync, 10-100x faster directory ops)
- Bug fixes for cloud mode, project paths, and Claude Desktop compatibility
- New features: async client context manager, BASIC_MEMORY_PROJECT_ROOT
- Documentation updates and SPEC-15/16 additions

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-13 10:58:21 -05:00
phernandez e41579f971 Merge branch 'main' of github.com:basicmachines-co/basic-memory 2025-10-13 10:44:54 -05:00
Paul Hernandez 2b7008d997 fix: Update view_note and ChatGPT tools for Claude Desktop compatibility (#355)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-13 10:44:35 -05:00
phernandez 56e5cc072b Merge branch 'main' of github.com:basicmachines-co/basic-memory 2025-10-13 07:30:08 -05:00
Paul Hernandez c0538ad2dd perf: Optimize sync/indexing for 43% faster performance (#352)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-12 14:41:44 -05:00
phernandez 962d88ea43 add specs-17/18
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-12 10:59:45 -05:00
phernandez cd5efd4a44 perf: exclude null fields from directory endpoint responses
Reduces JSON payload size by 50-70% for directory-heavy responses by omitting
null fields from serialization.

Changes:
- Added response_model_exclude_none=True to all directory endpoints:
  - GET /directory/tree
  - GET /directory/structure
  - GET /directory/list

Impact:
- Directory nodes no longer serialize 7 null fields (title, permalink,
  entity_id, entity_type, content_type, updated_at, file_path)
- For 50+ directories: eliminates 350+ null fields from response
- Payload reduction: ~2.3kb → ~1kb for typical directory trees
- File nodes still include all metadata when present

Example directory node output:
{
  "name": "Tools",
  "directory_path": "/Tools",
  "type": "directory",
  "children": []
}

Testing:
- All 29 directory tests passing
- Type checking passing (0 errors)
- Backward compatible (clients just see missing keys vs null)

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-11 09:56:26 -05:00
Paul Hernandez 00b73b0d08 feat: Optimize directory operations for 10-100x performance improvement (#350)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-11 09:11:46 -05:00
jope-bm a09066e0f0 fix: Add permalink normalization to project lookups in deps.py (#348)
Signed-off-by: Joe P <joe@basicmemory.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: phernandez <paul@basicmachines.co>
2025-10-10 21:21:35 -05:00
Drew Cain be352ab474 fix: Project deletion failing with permalink normalization (#345) 2025-10-10 12:18:03 -05:00
Paul Hernandez 8d2e70cfc8 refactor: async client context manager pattern for cloud consolidation (#344)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-09 19:09:47 -05:00
Paul Hernandez 53438d1eab feat: Add SPEC-15 for configuration persistence via Tigris (#343)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-08 18:07:00 -05:00
phernandez 032de7e3f2 fix: formatting in test file
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-08 09:13:05 -05:00
phernandez fd2b188645 Revert "feat: add optional logfire instrumentation for cloud mode distributed tracing"
This reverts commit 1fa93ecbd2.
2025-10-08 09:08:15 -05:00
phernandez 453cba94e4 Revert "fix: instrument httpx client at module level for MCP context"
This reverts commit 48cb4be4cd.
2025-10-08 09:07:26 -05:00
phernandez 48cb4be4cd fix: instrument httpx client at module level for MCP context
The lifespan-based instrumentation only runs when FastAPI app starts.
In MCP context, the app never starts but the httpx client is still used.

Solution: Instrument the client immediately after creation at module level.
This works in both contexts:
- MCP: client is instrumented when module is imported
- API: client is instrumented before lifespan runs (lifespan still safe)

This enables distributed tracing from MCP -> Cloud -> API.

Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-08 08:10:54 -05:00
Paul Hernandez 3e876a7549 fix: correct ProjectItem.home property to return path instead of name (#341)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-08 01:03:12 -05:00
phernandez 1fa93ecbd2 feat: add optional logfire instrumentation for cloud mode distributed tracing 2025-10-08 00:23:00 -05:00
Paul Hernandez 73202d1aab fix: add tool use doc to write note for using empty string for root folder (#339)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-07 23:48:02 -05:00
Paul Hernandez 795e339333 fix: prevent nested project paths to avoid data conflicts (#338)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-07 09:44:39 -05:00
Paul Hernandez 07e304ce8e fix: normalize paths to lowercase in cloud mode to prevent case collisions (#336)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-05 17:56:58 -05:00
phernandez 2a1c06d9ad fix link in ai_assistant_guide resource
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-05 17:26:14 -05:00
Paul Hernandez c6f93a0294 chore: v0.15.0 assistant guide (#335)
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-05 17:20:54 -05:00
Paul Hernandez ccc4386627 feat: introduce BASIC_MEMORY_PROJECT_ROOT for path constraints (#334)
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-05 10:42:06 -05:00
Paul Hernandez 7616b2bb08 fix: cloud mode path validation and sanitization (bmc-issue-103) (#332)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-04 22:10:21 -05:00
phernandez 14c1fe4e89 chore: update version to 0.15.0 for v0.15.0 release 2025-10-04 15:02:28 -05:00
phernandez 367dc6962a style: apply ruff formatting to test files
Auto-format test files for permalink collision tests.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-04 15:00:19 -05:00
phernandez ee18eb2fea docs: add v0.15.0 changelog entry
Comprehensive changelog for v0.15.0 release covering:
- Critical permalink collision data loss fix
- 10+ bug fixes including #330, #329, #328, #312
- 9 new features including cloud sync and subscription validation
- Platform improvements (Python 3.13, Windows, Docker)
- Enhanced testing and documentation

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-04 14:53:06 -05:00
phernandez 2a050edee4 fix: prevent permalink collision via strict link resolution
Fixes critical data loss bug where creating similar entity names
(e.g., "Node C") would overwrite existing entities (e.g., "Node A.md")
due to fuzzy search incorrectly matching similar file paths.

Changes:
- Add strict=True to resolve_link() calls in entity_service.py
- Disables fuzzy search fallback during entity creation/update
- Prevents false positive matches on similar paths like
  "edge-cases/Node A.md" and "edge-cases/Node C.md"

Testing:
- Added comprehensive integration test reproducing the bug scenario
- Added MCP-level permalink collision tests
- All 55 entity service tests pass
- Manual testing confirms fix prevents file overwrite

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-04 14:45:37 -05:00
Paul Hernandez f3b1945e4c fix: remove .env file loading from BasicMemoryConfig (#330)
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-04 01:13:06 -05:00
Paul Hernandez 16d7eddbf7 ci: Add Python 3.13 to test matrix (#331)
Signed-off-by: phernandez <paul@basicmachines.co>
2025-10-04 01:12:44 -05:00
Paul Hernandez f5a11f3911 fix: normalize underscores in memory:// URLs for build_context (#329)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-10-04 00:16:06 -05:00
Paul Hernandez ee83b0e5a8 fix: simplify entity upsert to use database-level conflict resolution (#328)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-03 23:25:34 -05:00
Paul Hernandez a7bf42ef49 fix: Add proper datetime JSON schema format annotations for MCP validation (#312)
Signed-off-by: Claude Code <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-10-03 22:47:02 -05:00
Paul Hernandez 903591384d feat: Add disable_permalinks config flag (#313)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-03 22:11:47 -05:00
Paul Hernandez 33ee1e0831 feat: integrate ignore_utils to skip .gitignored files in sync process (#314)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-03 21:39:31 -05:00
Paul Hernandez c83d567917 fix: enable WAL mode and add Windows-specific SQLite optimizations (#316)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-03 21:10:09 -05:00
Paul Hernandez ace6a0f50d feat: CLI Subscription Validation (SPEC-13 Phase 2) (#327)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-03 17:59:32 -05:00
Jonathan Nguyen fc38877008 Fix: Corrected dead links in README (#321)
Signed-off-by: Jonathan Nguyen <74562467+jonathan-d-nguyen@users.noreply.github.com>
2025-10-03 10:23:16 -05:00
Paul Hernandez 99a35a7fb4 feat: Cloud CLI cloud sync via rclone bisync (#322)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-03 10:18:43 -05:00
Paul Hernandez ea2e93d926 fix: rework lifecycle management to optimize cloud deployment (#320)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-28 15:11:38 -05:00
Paul Hernandez 324844a670 fix: resolve entity relations in background to prevent cold start blocking (#319)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-28 09:24:29 -05:00
Paul Hernandez f818702ab7 fix: enforce minimum 1-day timeframe for recent_activity to handle timezone issues (#318)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-27 23:37:00 -05:00
Paul Hernandez 2efd8f44e2 fix: critical cloud deployment fixes for MCP stability (#317)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-27 21:57:39 -05:00
Paul Hernandez 5da97e4820 feat: implement SPEC-11 API performance optimizations (#315)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-26 14:34:46 -05:00
Paul Hernandez 17a6733c9d fix: remove obsolete update_current_project function and --project flag reference (#310)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-09-26 11:46:33 -05:00
Drew Cain 3e168b98f3 fix: move_note without file extension (#281)
Signed-off-by: Drew Cain <groksrc@gmail.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: phernandez <paul@basicmachines.co>
2025-09-26 11:41:45 -05:00
Paul Hernandez 1091e11322 fix: Make sync operations truly non-blocking with thread pool (#309)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-26 10:10:58 -05:00
Paul Hernandez f40ab31685 feat: chatgpt tools for search and fetch (#305)
Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
Co-authored-by: Drew Cain <groksrc@gmail.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Drew Cain <groksrc@users.noreply.github.com>
2025-09-25 11:55:56 -05:00
phernandez bcf7f40979 fix: Correct GitHub workflow conditions for org member @claude mentions
Fixed the conditional logic in claude.yml to properly handle different event types:
- Use github.event.comment.author_association for issue_comment events
- Use github.event.sender.author_association for other events
- Maintain support for all basicmachines-co org members (OWNER/MEMBER/COLLABORATOR)

This ensures @claude mentions in PR comments trigger the workflow correctly.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-09-23 10:00:51 -05:00
Paul Hernandez 8c7e29e325 chore: Update Claude Code GitHub Workflow (#308)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-23 09:50:38 -05:00
phernandez 84c0b36dee feat: Add comprehensive cloud mount CLI commands and documentation
This commit implements SPEC-7 Phase 4 by adding local file access capabilities
to the Basic Memory Cloud CLI, enabling users to mount their cloud files locally
for real-time editing.

New features:
- Cloud mount setup with automatic rclone installation
- Mount/unmount/status commands with three performance profiles
- Cross-platform rclone installer with package manager fallbacks
- Mount configuration management with tenant-specific credentials
- Comprehensive documentation with examples and troubleshooting

Mount profiles:
- fast: 5s sync for active development
- balanced: 10-15s sync (recommended)
- safe: 15s+ sync with conflict detection

Technical implementation:
- Uses rclone NFS mount (no FUSE dependencies)
- Tigris object storage with scoped credentials
- Bidirectional sync with configurable cache settings
- Process management and cleanup

Fixes Python module conflict by moving cloud.py commands to cloud/core_commands.py
to resolve typer CLI loading issues with cloud.py file vs cloud/ directory.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
2025-09-22 17:47:06 -05:00
Paul Hernandez 2c5c606a39 feat: Implement cloud mount CLI commands for local file access (#306)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-22 14:19:25 -05:00
Paul Hernandez a1d7792bdb feat: Implement SPEC-6 Stateless Architecture for MCP Tools (#298)
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Drew Cain <groksrc@users.noreply.github.com>
2025-09-21 20:39:19 -05:00
phernandez 7979b4192e remove no content-encoding: none header
Signed-off-by: phernandez <paul@basicmachines.co>
2025-09-16 16:35:32 -05:00
Drew Cain 52d9b3c752 setting content-encoding to none for mcp
Signed-off-by: Drew Cain <groksrc@gmail.com>
2025-09-16 16:09:33 -05:00
Paul Hernandez e0d8aeb149 feat: Basic memory cloud upload (#296)
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Drew Cain <groksrc@gmail.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Drew Cain <groksrc@gmail.com>
2025-09-16 15:07:14 -05:00
Brandon Mayes 17b929446a fix: Sanitize folder names and properly join paths (#292) 2025-09-15 23:26:56 -04:00
Paul Hernandez b00e4ff5a1 fix: replace deprecated json_encoders with Pydantic V2 field serializers (#295)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-09-14 22:19:16 -05:00
Drew Cain 0499319ded fix: rename MCP prompt names to avoid slash command parsing issues (#289)
Signed-off-by: Drew Cain <groksrc@gmail.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2025-09-09 21:42:35 -05:00
jope-bm 3a6baf80fc feat: Merge Cloud auth (#291)
Signed-off-by: phernandez <paul@basicmachines.co>
Signed-off-by: Joe P <joe@basicmemory.com>
Co-authored-by: phernandez <paul@basicmachines.co>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-09 14:48:17 -06:00
jope-bm ec2fa07350 chore: apply lint and formatting fixes for 0.14.4 release (#290)
Signed-off-by: Joe P <joe@basicmemory.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-05 10:00:52 -06:00
319 changed files with 53316 additions and 29089 deletions
+94 -17
View File
@@ -15,10 +15,16 @@ Create a stable release using the automated justfile target with comprehensive v
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
#### Version Check
1. Check current version in `src/basic_memory/__init__.py`
2. Verify new version format matches `v\d+\.\d+\.\d+` pattern
3. Confirm version is higher than current version
#### Git Status
1. Check current git status for uncommitted changes
2. Verify we're on the `main` branch
3. Confirm no existing tag with this version
#### Documentation Validation
1. **Changelog Check**
@@ -39,19 +45,83 @@ The justfile target handles:
- ✅ Version update in `src/basic_memory/__init__.py`
- ✅ Automatic commit with proper message
- ✅ Tag creation and pushing to GitHub
- ✅ Release workflow trigger
- ✅ Release workflow trigger (automatic on tag push)
The GitHub Actions workflow (`.github/workflows/release.yml`) then:
- ✅ Builds the package using `uv build`
- ✅ Creates GitHub release with auto-generated notes
- ✅ Publishes to PyPI
- ✅ Updates Homebrew formula (stable releases only)
### 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`
1. Verify tag push triggered the workflow (should start automatically within seconds)
2. Monitor workflow progress at: https://github.com/basicmachines-co/basic-memory/actions
3. Watch for successful completion of both jobs:
- `release` - Builds package and publishes to PyPI
- `homebrew` - Updates Homebrew formula (stable releases only)
4. Check for any workflow failures and investigate logs if needed
### 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
#### GitHub Release
1. Verify GitHub release is created at: https://github.com/basicmachines-co/basic-memory/releases/tag/<version>
2. Check that release notes are auto-generated from commits
3. Validate release assets (`.whl` and `.tar.gz` files are attached)
#### PyPI Publication
1. Verify package published at: https://pypi.org/project/basic-memory/<version>/
2. Test installation: `uv tool install basic-memory`
3. Verify installed version: `basic-memory --version`
#### Homebrew Formula (Stable Releases Only)
1. Check formula update at: https://github.com/basicmachines-co/homebrew-basic-memory
2. Verify formula version matches release
3. Test Homebrew installation: `brew install basicmachines-co/basic-memory/basic-memory`
#### Website Updates
**1. basicmachines.co** (`/Users/drew/code/basicmachines.co`)
- **Goal**: Update version number displayed on the homepage
- **Location**: Search for "Basic Memory v0." in the codebase to find version displays
- **What to update**:
- Hero section heading that shows "Basic Memory v{VERSION}"
- "What's New in v{VERSION}" section heading
- Feature highlights array (look for array of features with title/description)
- **Process**:
1. Pull latest from GitHub: `git pull origin main`
2. Create release branch: `git checkout -b release/v{VERSION}`
3. Search codebase for current version number (e.g., "v0.16.1")
4. Update version numbers to new release version
5. Update feature highlights with 3-5 key features from this release (extract from CHANGELOG.md)
6. Commit changes: `git commit -m "chore: update to v{VERSION}"`
7. Push branch: `git push origin release/v{VERSION}`
- **Deploy**: Follow deployment process for basicmachines.co
**2. docs.basicmemory.com** (`/Users/drew/code/docs.basicmemory.com`)
- **Goal**: Add new release notes section to the latest-releases page
- **File**: `src/pages/latest-releases.mdx`
- **What to do**:
1. Pull latest from GitHub: `git pull origin main`
2. Create release branch: `git checkout -b release/v{VERSION}`
3. Read the existing file to understand the format and structure
4. Read `/Users/drew/code/basic-memory/CHANGELOG.md` to get release content
5. Add new release section **at the top** (after MDX imports, before other releases)
6. Follow the existing pattern:
- Heading: `## [v{VERSION}](github-link) — YYYY-MM-DD`
- Focus statement if applicable
- `<Info>` block with highlights (3-5 key items)
- Sections for Features, Bug Fixes, Breaking Changes, etc.
- Link to full changelog at the end
- Separator `---` between releases
7. Commit changes: `git commit -m "docs: add v{VERSION} release notes"`
8. Push branch: `git push origin release/v{VERSION}`
- **Source content**: Extract and format sections from CHANGELOG.md for this version
- **Deploy**: Follow deployment process for docs.basicmemory.com
**4. Announce Release**
- Post to Discord community if significant changes
- Update social media if major release
- Notify users via appropriate channels
## Pre-conditions Check
Before starting, verify:
@@ -74,13 +144,18 @@ Before starting, verify:
🏷️ 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/
🍺 Homebrew: https://github.com/basicmachines-co/homebrew-basic-memory
🚀 GitHub Actions: Completed
Install with:
uv tool install basic-memory
Install with pip/uv:
uv tool install basic-memory
Install with Homebrew:
brew install basicmachines-co/basic-memory/basic-memory
Users can now upgrade:
uv tool upgrade basic-memory
uv tool upgrade basic-memory
brew upgrade basic-memory
```
## Context
@@ -89,4 +164,6 @@ uv tool upgrade basic-memory
- 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
- Package is published to PyPI for `pip` and `uv` users
- Homebrew formula is automatically updated for stable releases
- Supports multiple installation methods (uv, pip, Homebrew)
+51
View File
@@ -0,0 +1,51 @@
---
allowed-tools: mcp__basic-memory__write_note, mcp__basic-memory__read_note, mcp__basic-memory__search_notes, mcp__basic-memory__edit_note
argument-hint: [create|status|show|review] [spec-name]
description: Manage specifications in our development process
---
## Context
Specifications are managed in the Basic Memory "specs" project. All specs live in a centralized location accessible across all repositories via MCP tools.
See SPEC-1 and SPEC-2 in the "specs" project for the full specification-driven development process.
Available commands:
- `create [name]` - Create new specification
- `status` - Show all spec statuses
- `show [spec-name]` - Read a specific spec
- `review [spec-name]` - Review implementation against spec
## Your task
Execute the spec command: `/spec $ARGUMENTS`
### If command is "create":
1. Get next SPEC number by searching existing specs in "specs" project
2. Create new spec using template from SPEC-2
3. Use mcp__basic-memory__write_note with project="specs"
4. Include standard sections: Why, What, How, How to Evaluate
### If command is "status":
1. Use mcp__basic-memory__search_notes with project="specs"
2. Display table with spec number, title, and progress
3. Show completion status from checkboxes in content
### If command is "show":
1. Use mcp__basic-memory__read_note with project="specs"
2. Display the full spec content
### If command is "review":
1. Read the specified spec and its "How to Evaluate" section
2. Review current implementation against success criteria with careful evaluation of:
- **Functional completeness** - All specified features working
- **Test coverage analysis** - Actual test files and coverage percentage
- Count existing test files vs required components/APIs/composables
- Verify unit tests, integration tests, and end-to-end tests
- Check for missing test categories (component, API, workflow)
- **Code quality metrics** - TypeScript compilation, linting, performance
- **Architecture compliance** - Component isolation, state management patterns
- **Documentation completeness** - Implementation matches specification
3. Provide honest, accurate assessment - do not overstate completeness
4. Document findings and update spec with review results using mcp__basic-memory__edit_note
5. If gaps found, clearly identify what still needs to be implemented/tested
+74 -47
View File
@@ -11,7 +11,7 @@ All test results are recorded as notes in a dedicated test project.
**Parameters:**
- `phase` (optional): Specific test phase to run (`recent`, `core`, `features`, `edge`, `workflows`, `stress`, or `all`)
- `recent` - Focus on recent changes and new features (recommended for regular testing)
- `core` - Essential tools only (Tier 1: write_note, read_note, search_notes, edit_note, list_projects, switch_project)
- `core` - Essential tools only (Tier 1: write_note, read_note, search_notes, edit_note, list_memory_projects, recent_activity)
- `features` - Core + important workflows (Tier 1 + Tier 2)
- `all` - Comprehensive testing of all tools and scenarios
@@ -24,30 +24,66 @@ When the user runs `/project:test-live`, execute comprehensive test plan:
### **Tier 1: Critical Core (Always Test)**
1. **write_note** - Foundation of all knowledge creation
2. **read_note** - Primary knowledge retrieval mechanism
2. **read_note** - Primary knowledge retrieval mechanism
3. **search_notes** - Essential for finding information
4. **edit_note** - Core content modification capability
5. **list_memory_projects** - Project discovery and status
6. **switch_project** - Context switching for multi-project workflows
5. **list_memory_projects** - Project discovery and session guidance
6. **recent_activity** - Project discovery mode and activity analysis
### **Tier 2: Important Workflows (Usually Test)**
7. **recent_activity** - Understanding what's changed
8. **build_context** - Conversation continuity via memory:// URLs
9. **create_memory_project** - Essential for project setup
10. **move_note** - Knowledge organization
11. **sync_status** - Understanding system state
7. **build_context** - Conversation continuity via memory:// URLs
8. **create_memory_project** - Essential for project setup
9. **move_note** - Knowledge organization
10. **sync_status** - Understanding system state
11. **delete_project** - Project lifecycle management
### **Tier 3: Enhanced Functionality (Sometimes Test)**
12. **view_note** - Claude Desktop artifact display
13. **read_content** - Raw content access
14. **delete_note** - Content removal
15. **list_directory** - File system exploration
16. **set_default_project** - Configuration
17. **delete_project** - Administrative cleanup
16. **edit_note** (advanced modes) - Complex find/replace operations
### **Tier 4: Specialized (Rarely Test)**
18. **canvas** - Obsidian visualization (specialized use case)
19. **MCP Prompts** - Enhanced UX tools (ai_assistant_guide, continue_conversation)
17. **canvas** - Obsidian visualization (specialized use case)
18. **MCP Prompts** - Enhanced UX tools (ai_assistant_guide, continue_conversation)
## Stateless Architecture Testing
### **Project Discovery Workflow (CRITICAL)**
Test the new stateless project selection flow:
1. **Initial Discovery**
- Call `list_memory_projects()` without knowing which project to use
- Verify clear session guidance appears: "Next: Ask which project to use"
- Confirm removal of CLI-specific references
2. **Activity-Based Discovery**
- Call `recent_activity()` without project parameter (discovery mode)
- Verify intelligent project suggestions based on activity
- Test guidance: "Should I use [most-active-project] for this task?"
3. **Session Tracking Validation**
- Verify all tool responses include `[Session: Using project 'name']`
- Confirm guidance reminds about session-wide project tracking
4. **Single Project Constraint Mode**
- Test MCP server with `--project` parameter
- Verify all operations constrained to specified project
- Test project override behavior in constrained mode
### **Explicit Project Parameters (CRITICAL)**
All tools must require explicit project parameters:
1. **Parameter Validation**
- Test all Tier 1 tools require `project` parameter
- Verify clear error messages for missing project
- Test invalid project name handling
2. **No Session State Dependencies**
- Confirm no tool relies on "current project" concept
- Test rapid project switching within conversation
- Verify each call is truly independent
### Pre-Test Setup
@@ -72,7 +108,7 @@ Run the bash `date` command to get the current date/time.
Purpose: Record all test observations and results
```
Make sure to switch to the newly created project with the `switch_project()` tool.
Make sure to use the newly created project for all subsequent test operations by specifying it in the `project` parameter of each tool call.
4. **Baseline Documentation**
Create initial test session note with:
@@ -143,46 +179,42 @@ Test essential MCP tools that form the foundation of Basic Memory:
- ⚠️ Error scenarios (invalid operations)
**5. list_memory_projects Tests (Critical):**
- ✅ Display all projects with status indicators
- ✅ Current and default project identification
- ✅ Display all projects with clear session guidance
- ✅ Project discovery workflow prompts
- ✅ Removal of CLI-specific references
- ✅ Empty project list handling
- ✅ Project metadata accuracy
- ✅ Single project constraint mode display
**6. switch_project Tests (Critical):**
- ✅ Switch between existing projects
- ✅ Context preservation during switch
- ⚠️ Invalid project name handling
- ✅ Confirmation of successful switch
**6. recent_activity Tests (Critical - Discovery Mode):**
- ✅ Discovery mode without project parameter
- ✅ Intelligent project suggestions based on activity
- ✅ Guidance prompts for project selection
- ✅ Session tracking reminders in responses
- ⚠️ Performance with multiple projects
### Phase 2: Important Workflows (Tier 2 Tools)
**7. recent_activity Tests (Important):**
- ✅ Various timeframes ("today", "1 week", "1d")
- ✅ Type filtering capabilities
- ✅ Empty project scenarios
- ⚠️ Performance with many recent changes
**8. build_context Tests (Important):**
**7. build_context Tests (Important):**
- ✅ Different depth levels (1, 2, 3+)
- ✅ Various timeframes for context
- ✅ memory:// URL navigation
- ⚠️ Performance with complex relation graphs
**9. create_memory_project Tests (Important):**
**8. create_memory_project Tests (Important):**
- ✅ Create projects dynamically
- ✅ Set default during creation
- ✅ Path validation and creation
- ⚠️ Invalid paths and names
- ✅ Integration with existing projects
**10. move_note Tests (Important):**
**9. move_note Tests (Important):**
- ✅ Move within same project
- ✅ Cross-project moves with detection (#161)
- ✅ Automatic folder creation
- ✅ Database consistency validation
- ⚠️ Special characters in paths
**11. sync_status Tests (Important):**
**10. sync_status Tests (Important):**
- ✅ Background operation monitoring
- ✅ File synchronization status
- ✅ Project sync state reporting
@@ -190,36 +222,31 @@ Test essential MCP tools that form the foundation of Basic Memory:
### Phase 3: Enhanced Functionality (Tier 3 Tools)
**12. view_note Tests (Enhanced):**
**11. view_note Tests (Enhanced):**
- ✅ Claude Desktop artifact display
- ✅ Title extraction from frontmatter
- ✅ Unicode and emoji content rendering
- ⚠️ Error handling for non-existent notes
**13. read_content Tests (Enhanced):**
**12. read_content Tests (Enhanced):**
- ✅ Raw file content access
- ✅ Binary file handling
- ✅ Image file reading
- ⚠️ Large file performance
**14. delete_note Tests (Enhanced):**
**13. delete_note Tests (Enhanced):**
- ✅ Single note deletion
- ✅ Database consistency after deletion
- ⚠️ Non-existent note handling
- ✅ Confirmation of successful deletion
**15. list_directory Tests (Enhanced):**
**14. list_directory Tests (Enhanced):**
- ✅ Directory content listing
- ✅ Depth control and filtering
- ✅ File name globbing
- ⚠️ Empty directory handling
**16. set_default_project Tests (Enhanced):**
- ✅ Change default project
- ✅ Configuration persistence
- ⚠️ Invalid project handling
**17. delete_project Tests (Enhanced):**
**15. delete_project Tests (Enhanced):**
- ✅ Project removal from config
- ✅ Database cleanup
- ⚠️ Default project protection
@@ -269,7 +296,7 @@ Test essential MCP tools that form the foundation of Basic Memory:
1. Technical documentation project
2. Personal recipe collection project
3. Learning/course notes project
4. Switch contexts during conversation
4. Specify different projects for different operations
5. Cross-reference related concepts
**Content Evolution:**
@@ -281,13 +308,13 @@ Test essential MCP tools that form the foundation of Basic Memory:
### Phase 6: Specialized Tools Testing (Tier 4)
**18. canvas Tests (Specialized):**
**16. canvas Tests (Specialized):**
- ✅ JSON Canvas generation
- ✅ Node and edge creation
- ✅ Obsidian compatibility
- ⚠️ Complex graph handling
**19. MCP Prompts Tests (Specialized):**
**17. MCP Prompts Tests (Specialized):**
- ✅ ai_assistant_guide output
- ✅ continue_conversation functionality
- ✅ Formatted search results
@@ -382,7 +409,7 @@ permalink: test-session-[phase]-[timestamp]
### 📊 Performance Metrics
- Average write_note time: 0.3s
- Search with 100+ notes: 0.6s
- Project switch overhead: 0.1s
- Project parameter overhead: <0.1s
- Memory usage: [observed levels]
## Relations
@@ -402,7 +429,7 @@ permalink: test-session-[phase]-[timestamp]
- Learning curve and intuitiveness
**System Behavior:**
- Context preservation across operations
- Stateless operation independence
- memory:// URL navigation reliability
- Multi-step workflow cohesion
- Edge case graceful handling
+5
View File
@@ -0,0 +1,5 @@
{
"enabledPlugins": {
"basic-memory@basicmachines": true
}
}
+28
View File
@@ -0,0 +1,28 @@
# Basic Memory Environment Variables Example
# Copy this file to .env and customize as needed
# Note: .env files are gitignored and should never be committed
# ============================================================================
# PostgreSQL Test Database Configuration
# ============================================================================
# These variables allow you to override the default test database credentials
# Default values match docker-compose-postgres.yml for local development
#
# Only needed if you want to use different credentials or a remote test database
# By default, tests use: postgresql://basic_memory_user:dev_password@localhost:5433/basic_memory_test
# Full PostgreSQL test database URL (used by tests and migrations)
# POSTGRES_TEST_URL=postgresql+asyncpg://basic_memory_user:dev_password@localhost:5433/basic_memory_test
# Individual components (used by justfile postgres-reset command)
# POSTGRES_USER=basic_memory_user
# POSTGRES_TEST_DB=basic_memory_test
# ============================================================================
# Production Database Configuration
# ============================================================================
# For production use, set these in your deployment environment
# DO NOT use the test credentials above in production!
# BASIC_MEMORY_DATABASE_BACKEND=postgres # or "sqlite"
# BASIC_MEMORY_DATABASE_URL=postgresql+asyncpg://user:password@host:port/database
+82
View File
@@ -0,0 +1,82 @@
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
jobs:
claude-review:
# Only run for organization members and collaborators
if: |
github.event.pull_request.author_association == 'OWNER' ||
github.event.pull_request.author_association == 'MEMBER' ||
github.event.pull_request.author_association == 'COLLABORATOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
track_progress: true # Enable visual progress tracking
allowed_bots: '*'
prompt: |
Review this Basic Memory PR against our team checklist:
## Code Quality & Standards
- [ ] Follows Basic Memory's coding conventions in CLAUDE.md
- [ ] Python 3.12+ type annotations and async patterns
- [ ] SQLAlchemy 2.0 best practices
- [ ] FastAPI and Typer conventions followed
- [ ] 100-character line length limit maintained
- [ ] No commented-out code blocks
## Testing & Documentation
- [ ] Unit tests for new functions/methods
- [ ] Integration tests for new MCP tools
- [ ] Test coverage for edge cases
- [ ] Documentation updated (README, docstrings)
- [ ] CLAUDE.md updated if conventions change
## Basic Memory Architecture
- [ ] MCP tools follow atomic, composable design
- [ ] Database changes include Alembic migrations
- [ ] Preserves local-first architecture principles
- [ ] Knowledge graph operations maintain consistency
- [ ] Markdown file handling preserves integrity
- [ ] AI-human collaboration patterns followed
## Security & Performance
- [ ] No hardcoded secrets or credentials
- [ ] Input validation for MCP tools
- [ ] Proper error handling and logging
- [ ] Performance considerations addressed
- [ ] No sensitive data in logs or commits
## Compatability
- [ ] File path comparisons must be windows compatible
- [ ] Avoid using emojis and unicode characters in console and log output
Read the CLAUDE.md file for detailed project context. For each checklist item, verify if it's satisfied and comment on any that need attention. Use inline comments for specific code issues and post a summary with checklist results.
# Allow broader tool access for thorough code review
claude_args: '--allowed-tools "Bash(gh pr:*),Bash(gh issue:*),Bash(gh api:*),Bash(git log:*),Bash(git show:*),Read,Grep,Glob"'
+71
View File
@@ -0,0 +1,71 @@
name: Claude Issue Triage
on:
issues:
types: [opened]
jobs:
triage:
runs-on: ubuntu-latest
permissions:
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Issue Triage
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
track_progress: true # Show triage progress
prompt: |
Analyze this new Basic Memory issue and perform triage:
**Issue Analysis:**
1. **Type Classification:**
- Bug report (code defect)
- Feature request (new functionality)
- Enhancement (improvement to existing feature)
- Documentation (docs improvement)
- Question/Support (user help)
- MCP tool issue (specific to MCP functionality)
2. **Priority Assessment:**
- Critical: Security issues, data loss, complete breakage
- High: Major functionality broken, affects many users
- Medium: Minor bugs, usability issues
- Low: Nice-to-have improvements, cosmetic issues
3. **Component Classification:**
- CLI commands
- MCP tools
- Database/sync
- Cloud functionality
- Documentation
- Testing
4. **Complexity Estimate:**
- Simple: Quick fix, documentation update
- Medium: Requires some investigation/testing
- Complex: Major feature work, architectural changes
**Actions to Take:**
1. Add appropriate labels using: `gh issue edit ${{ github.event.issue.number }} --add-label "label1,label2"`
2. Check for duplicates using: `gh search issues`
3. If duplicate found, comment mentioning the original issue
4. For feature requests, ask clarifying questions if needed
5. For bugs, request reproduction steps if missing
**Available Labels:**
- Type: bug, enhancement, feature, documentation, question, mcp-tool
- Priority: critical, high, medium, low
- Component: cli, mcp, database, cloud, docs, testing
- Complexity: simple, medium, complex
- Status: needs-reproduction, needs-clarification, duplicate
Read the issue carefully and provide helpful triage with appropriate labels.
claude_args: '--allowed-tools "Bash(gh issue:*),Bash(gh search:*),Read"'
+38 -84
View File
@@ -9,106 +9,60 @@ on:
types: [opened, assigned]
pull_request_review:
types: [submitted]
pull_request_target:
types: [opened, synchronize]
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')))
(
(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'))) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@claude'))
) && (
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR' ||
github.event.sender.author_association == 'OWNER' ||
github.event.sender.author_association == 'MEMBER' ||
github.event.sender.author_association == 'COLLABORATOR' ||
github.event.pull_request.author_association == 'OWNER' ||
github.event.pull_request.author_association == 'MEMBER' ||
github.event.pull_request.author_association == 'COLLABORATOR'
)
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
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:
# For pull_request_target, checkout the PR head to review the actual changes
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 1
- name: Run Claude Code
if: steps.check_membership.outputs.is_member == 'true'
id: claude
uses: anthropics/claude-code-action@beta
uses: anthropics/claude-code-action@v1
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, mcp__web_search
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
track_progress: true # Enable visual progress tracking
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options
# claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
+52 -5
View File
@@ -13,12 +13,13 @@ on:
branches: [ "main" ]
jobs:
test:
test-sqlite:
name: Test SQLite (${{ matrix.os }}, Python ${{ matrix.python-version }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: [ "3.12" ]
python-version: [ "3.12", "3.13" ]
runs-on: ${{ matrix.os }}
steps:
@@ -58,9 +59,55 @@ jobs:
- name: Run type checks
run: |
just type-check
just typecheck
- name: Run tests
- name: Run linting
run: |
just lint
- name: Run tests (SQLite)
run: |
uv pip install pytest pytest-cov
just test
just test-sqlite
test-postgres:
name: Test Postgres (Python ${{ matrix.python-version }})
strategy:
fail-fast: false
matrix:
python-version: [ "3.12", "3.13" ]
runs-on: ubuntu-latest
# Note: No services section needed - testcontainers handles Postgres in Docker
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install uv
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
- name: Install dependencies
run: |
uv pip install -e .[dev]
- name: Run tests (Postgres via testcontainers)
run: |
uv pip install pytest pytest-cov
just test-postgres
+2 -1
View File
@@ -52,4 +52,5 @@ ENV/
# claude action
claude-output
**/.claude/settings.local.json
**/.claude/settings.local.json
.mcp.json
+882 -1
View File
@@ -1,5 +1,886 @@
# CHANGELOG
## v0.17.2 (2025-12-29)
### Bug Fixes
- Allow recent_activity discovery mode in cloud mode
([`0bcda4a`](https://github.com/basicmachines-co/basic-memory/commit/0bcda4a))
- Add `allow_discovery` parameter to `resolve_project_parameter()`
- Tools like `recent_activity` can now work across all projects in cloud mode
- Fix circular import in project_context module
### Internal
- Optimize release workflow by running lint/typecheck only (skip full tests)
([`0b5425f`](https://github.com/basicmachines-co/basic-memory/commit/0b5425f))
## v0.17.1 (2025-12-29)
### Bug Fixes
- **#482**: Only set BASIC_MEMORY_ENV=test during pytest runs
([`98fbd60`](https://github.com/basicmachines-co/basic-memory/commit/98fbd60))
- Fixes environment variable pollution affecting alembic migrations
- Test environment detection now scoped to pytest execution only
## v0.17.0 (2025-12-28)
### Features
- **#478**: Add anonymous usage telemetry with Homebrew-style opt-out
([`856737f`](https://github.com/basicmachines-co/basic-memory/commit/856737f))
- Privacy-respecting anonymous usage analytics
- Easy opt-out via `BASIC_MEMORY_NO_ANALYTICS=1` environment variable
- Helps improve Basic Memory based on real usage patterns
- **#474**: Add auto-format files on save with built-in Python formatter
([`1fd680c`](https://github.com/basicmachines-co/basic-memory/commit/1fd680c))
- Automatic markdown formatting on file save
- Built-in Python formatter for consistent code style
- Configurable formatting options
- **#447**: Complete Phase 2 of API v2 migration - MCP tools use v2 endpoints
([`1a74d85`](https://github.com/basicmachines-co/basic-memory/commit/1a74d85))
- All MCP tools now use optimized v2 API endpoints
- Improved performance for knowledge graph operations
- Foundation for future API enhancements
### Bug Fixes
- Fix UTF-8 BOM handling in frontmatter parsing
([`85684f8`](https://github.com/basicmachines-co/basic-memory/commit/85684f8))
- Handles files with UTF-8 byte order marks correctly
- Prevents frontmatter parsing failures
- **#475**: Handle null titles in ChatGPT import
([`14ce5a3`](https://github.com/basicmachines-co/basic-memory/commit/14ce5a3))
- Gracefully handles conversations without titles
- Improved import robustness
- Remove MaxLen constraint from observation content
([`45d6caf`](https://github.com/basicmachines-co/basic-memory/commit/45d6caf))
- Allows longer observation content without truncation
- Removes arbitrary 2000 character limit
- Handle FileNotFoundError gracefully during sync
([`1652f86`](https://github.com/basicmachines-co/basic-memory/commit/1652f86))
- Prevents sync failures when files are deleted during sync
- More resilient file watching
- Use canonical project names in API response messages
([`c23927d`](https://github.com/basicmachines-co/basic-memory/commit/c23927d))
- Consistent project name formatting in all responses
- Suppress CLI warnings for cleaner output
([`d71c6e8`](https://github.com/basicmachines-co/basic-memory/commit/d71c6e8))
- Cleaner terminal output without spurious warnings
- Prevent DEBUG logs from appearing on CLI stdout
([`63b9849`](https://github.com/basicmachines-co/basic-memory/commit/63b9849))
- Debug logging no longer pollutes CLI output
- **#473**: Detect rclone version for --create-empty-src-dirs support
([`622d37e`](https://github.com/basicmachines-co/basic-memory/commit/622d37e))
- Automatic rclone version detection for compatibility
- Prevents errors on older rclone versions
- **#471**: Prevent CLI commands from hanging on exit
([`916baf8`](https://github.com/basicmachines-co/basic-memory/commit/916baf8))
- Fixes CLI hang on shutdown
- Proper async cleanup
- Add cloud_mode check to initialize_app()
([`ef7adb7`](https://github.com/basicmachines-co/basic-memory/commit/ef7adb7))
- Correct initialization for cloud deployments
### Internal
- Centralize test environment detection in config.is_test_env
([`3cd9178`](https://github.com/basicmachines-co/basic-memory/commit/3cd9178))
- Unified test environment detection
- Disables analytics in test environments
- Make test-int-postgres compatible with macOS
([`95937c6`](https://github.com/basicmachines-co/basic-memory/commit/95937c6))
- Cross-platform PostgreSQL testing support
## v0.16.3 (2025-12-20)
### Features
- **#439**: Add PostgreSQL database backend support
([`fb5e9e1`](https://github.com/basicmachines-co/basic-memory/commit/fb5e9e1))
- Full PostgreSQL/Neon database support as alternative to SQLite
- Async connection pooling with asyncpg
- Alembic migrations support for both backends
- Configurable via `BASIC_MEMORY_DATABASE_BACKEND` environment variable
- **#441**: Implement API v2 with ID-based endpoints (Phase 1)
([`28cc522`](https://github.com/basicmachines-co/basic-memory/commit/28cc522))
- New ID-based API endpoints for improved performance
- Foundation for future API enhancements
- Backward compatible with existing endpoints
- Add project_id to Relation and Observation for efficient project-scoped queries
([`a920a9f`](https://github.com/basicmachines-co/basic-memory/commit/a920a9f))
- Enables faster queries in multi-project environments
- Improved database schema for cloud deployments
- Add bulk insert with ON CONFLICT handling for relations
([`0818bda`](https://github.com/basicmachines-co/basic-memory/commit/0818bda))
- Faster relation creation during sync operations
- Handles duplicate relations gracefully
### Performance
- Lightweight permalink resolution to avoid eager loading
([`6f99d2e`](https://github.com/basicmachines-co/basic-memory/commit/6f99d2e))
- Reduces database queries during entity lookups
- Improved response times for read operations
### Bug Fixes
- **#464**: Pin FastMCP to 2.12.3 to fix MCP tools visibility
([`f227ef6`](https://github.com/basicmachines-co/basic-memory/commit/f227ef6))
- Fixes issue where MCP tools were not visible to Claude
- Reverts to last known working FastMCP version
- **#458**: Reduce watch service CPU usage by increasing reload interval
([`897b1ed`](https://github.com/basicmachines-co/basic-memory/commit/897b1ed))
- Lowers CPU usage during file watching
- More efficient resource utilization
- **#456**: Await background sync task cancellation in lifespan shutdown
([`efbc758`](https://github.com/basicmachines-co/basic-memory/commit/efbc758))
- Prevents hanging on shutdown
- Clean async task cleanup
- **#434**: Respect --project flag in background sync
([`70bb10b`](https://github.com/basicmachines-co/basic-memory/commit/70bb10b))
- Background sync now correctly uses specified project
- Fixes multi-project sync issues
- **#446**: Fix observation parsing and permalink limits
([`73d940e`](https://github.com/basicmachines-co/basic-memory/commit/73d940e))
- Handles edge cases in observation content
- Prevents permalink truncation issues
- **#424**: Handle periods in kebab_filenames mode
([`b004565`](https://github.com/basicmachines-co/basic-memory/commit/b004565))
- Fixes filename handling for files with multiple periods
- Improved kebab-case conversion
- Fix Postgres/Neon connection settings and search index dedupe
([`b5d4fb5`](https://github.com/basicmachines-co/basic-memory/commit/b5d4fb5))
- Optimized connection pooling for Postgres
- Prevents duplicate search index entries
### Testing & CI
- Replace py-pglite with testcontainers for Postgres testing
([`c462faf`](https://github.com/basicmachines-co/basic-memory/commit/c462faf))
- More reliable Postgres testing infrastructure
- Uses Docker-based test containers
- Add PostgreSQL testing to GitHub Actions workflow
([`66b91b2`](https://github.com/basicmachines-co/basic-memory/commit/66b91b2))
- CI now tests both SQLite and PostgreSQL backends
- Ensures cross-database compatibility
- **#416**: Add integration test for read_note with underscored folders
([`0c12a39`](https://github.com/basicmachines-co/basic-memory/commit/0c12a39))
- Verifies folder name handling edge cases
### Internal
- Cloud compatibility fixes and performance improvements (#454)
- Remove logfire instrumentation for cleaner production deployments
- Truncate content_stems to fix Postgres 8KB index row limit
## v0.16.2 (2025-11-16)
### Bug Fixes
- **#429**: Use platform-native path separators in config.json
([`6517e98`](https://github.com/basicmachines-co/basic-memory/commit/6517e98))
- Fixes config.json path separator issues on Windows
- Uses os.path.join for platform-native path construction
- Ensures consistent path handling across platforms
- **#427**: Add rclone installation checks for Windows bisync commands
([`1af0539`](https://github.com/basicmachines-co/basic-memory/commit/1af0539))
- Validates rclone installation before running bisync commands
- Provides clear error messages when rclone is not installed
- Improves user experience on Windows
- **#421**: Main project always recreated on project list command
([`cad7019`](https://github.com/basicmachines-co/basic-memory/commit/cad7019))
- Fixes issue where main project was recreated unnecessarily
- Improves project list command reliability
- Reduces unnecessary file system operations
## v0.16.1 (2025-11-11)
### Bug Fixes
- **#422**: Handle Windows line endings in rclone bisync
([`e9d0a94`](https://github.com/basicmachines-co/basic-memory/commit/e9d0a94))
- Added `--compare=modtime` flag to rclone bisync to ignore size differences from line ending conversions
- Fixes issue where LF→CRLF conversion on Windows was treated as file corruption
- Resolves "corrupted on transfer: sizes differ" errors during cloud sync on Windows
- Users will need to run `--resync` once after updating to establish new baseline
## v0.16.0 (2025-11-10)
### Features
- **#417**: Add run_in_background parameter to sync endpoint
([`7ccec7e`](https://github.com/basicmachines-co/basic-memory/commit/7ccec7e))
- New `run_in_background` parameter for async sync operations
- Improved API flexibility for long-running sync tasks
- Comprehensive test coverage for background sync behavior
- **#405**: SPEC-20 Simplified Project-Scoped Rclone Sync
([`0b3272a`](https://github.com/basicmachines-co/basic-memory/commit/0b3272a))
- Simplified and more reliable cloud synchronization
- Project-scoped rclone configuration
- Better error handling and status reporting
- **#384**: Streaming Foundation & Async I/O Consolidation (SPEC-19)
([`e78345f`](https://github.com/basicmachines-co/basic-memory/commit/e78345f))
- Foundation for streaming support in future releases
- Consolidated async I/O patterns across codebase
- Improved performance and resource management
- **#364**: Add circuit breaker for file sync failures
([`434cdf2`](https://github.com/basicmachines-co/basic-memory/commit/434cdf2))
- Prevents cascading failures during sync operations
- Automatic recovery from transient errors
- Better resilience in cloud sync scenarios
- **#362**: Add --verbose and --no-gitignore options to cloud upload
([`7f9c1a9`](https://github.com/basicmachines-co/basic-memory/commit/7f9c1a9))
- Enhanced upload control with verbose logging
- Option to bypass gitignore filtering when needed
- Better debugging and troubleshooting capabilities
- **#391**: Add delete_notes parameter to remove project endpoint
([`c9946ec`](https://github.com/basicmachines-co/basic-memory/commit/c9946ec))
- Option to delete notes when removing projects
- Safer project cleanup workflows
- Prevents accidental data loss
### Bug Fixes
- **#420**: Skip archive files during cloud upload
([`49b2adc`](https://github.com/basicmachines-co/basic-memory/commit/49b2adc))
- Prevents uploading of zip, tar, gz and other archive files
- Reduces storage usage and upload time
- Better file filtering during cloud operations
- **#419**: Rename write_note entity_type to note_type for clarity
([`1646572`](https://github.com/basicmachines-co/basic-memory/commit/1646572))
- Clearer parameter naming in write_note tool
- Improved API consistency and documentation
- Better developer experience
- **#418**: Quote string values in YAML frontmatter to handle special characters
([`f0d7398`](https://github.com/basicmachines-co/basic-memory/commit/f0d7398))
- Fixes YAML parsing errors with special characters
- More robust frontmatter handling
- Prevents data corruption in edge cases
- **#415**: Handle dict objects in write_resource endpoint
([`4614fd0`](https://github.com/basicmachines-co/basic-memory/commit/4614fd0))
- Fixes errors when writing dictionary resources
- Better type handling in resource endpoints
- Improved API robustness
- **#414**: Replace Unicode arrows with ASCII for Windows compatibility
([`fc01f6a`](https://github.com/basicmachines-co/basic-memory/commit/fc01f6a))
- Fixes display issues on Windows terminals
- Better cross-platform compatibility
- Improved CLI user experience on Windows
- **#411**: Windows CLI Unicode encoding errors
([`0ba6f21`](https://github.com/basicmachines-co/basic-memory/commit/0ba6f21))
- Resolves Unicode encoding issues on Windows
- Better handling of international characters
- Improved Windows platform support
- **#410**: Various rclone fixes for cloud sync on Windows
([`c9946ec`](https://github.com/basicmachines-co/basic-memory/commit/c9946ec))
- Fixes cloud sync reliability on Windows
- Better path handling for Windows filesystem
- Improved rclone integration on Windows
- **#402**: Normalize YAML frontmatter types to prevent AttributeError
([`a7d7cc5`](https://github.com/basicmachines-co/basic-memory/commit/a7d7cc5))
- Fixes AttributeError when reading frontmatter
- More robust type normalization
- Better error handling in markdown parsing
- **#396**: Strip duplicate headers in edit_note replace_section
([`021af74`](https://github.com/basicmachines-co/basic-memory/commit/021af74))
- Prevents duplicate headers when replacing sections
- Cleaner note editing behavior
- Better content consistency
- **#395**: Simplify search_notes schema by removing Optional wrappers
([`d775f7b`](https://github.com/basicmachines-co/basic-memory/commit/d775f7b))
- Cleaner API schema definition
- Better type safety and validation
- Improved developer experience
- **#394**: Add explicit type annotations to MCP tool parameters
([`581b7b1`](https://github.com/basicmachines-co/basic-memory/commit/581b7b1))
- Better type safety in MCP tools
- Improved IDE support and autocomplete
- Clearer tool documentation
- **#389**: Handle null, empty, and string 'None' title in markdown frontmatter
([`bb8da31`](https://github.com/basicmachines-co/basic-memory/commit/bb8da31))
- Fixes errors with malformed frontmatter titles
- More robust title handling
- Better error recovery
- **#380**: Optimize sync memory usage to prevent OOM on large projects
([`4fd6d0c`](https://github.com/basicmachines-co/basic-memory/commit/4fd6d0c))
- Prevents out-of-memory errors on large knowledge bases
- Better memory management during sync
- Improved scalability
- **#379**: Handle YAML parsing errors gracefully in update_frontmatter
([`32236cd`](https://github.com/basicmachines-co/basic-memory/commit/32236cd))
- Better error handling for malformed YAML
- Graceful degradation instead of crashes
- Improved robustness
- **#377**: Preserve mtime on WebDAV upload
([`e6c8e36`](https://github.com/basicmachines-co/basic-memory/commit/e6c8e36))
- Maintains file modification times during upload
- Better sync accuracy
- Prevents unnecessary re-syncing
- **#370**: Prevent deleted projects from being recreated by background sync
([`449b62d`](https://github.com/basicmachines-co/basic-memory/commit/449b62d))
- Fixes race condition with project deletion
- Better lifecycle management
- Prevents unwanted project recreation
- **#369**: Use filesystem timestamps for entity sync instead of database operation time
([`b7497d7`](https://github.com/basicmachines-co/basic-memory/commit/b7497d7))
- More accurate sync detection
- Better handling of external file modifications
- Improved sync reliability
- **#368**: Handle YAML parsing errors and missing entity_type in markdown files
([`d1431bd`](https://github.com/basicmachines-co/basic-memory/commit/d1431bd))
- Better error handling for malformed markdown
- Graceful handling of missing metadata
- Improved robustness
- **#367**: Resolve UNIQUE constraint violation in entity upsert with observations
([`171bef7`](https://github.com/basicmachines-co/basic-memory/commit/171bef7))
- Fixes database constraint errors during sync
- Better handling of duplicate observations
- Improved data integrity
- **#366**: Terminate sync immediately when project is deleted
([`729a5a3`](https://github.com/basicmachines-co/basic-memory/commit/729a5a3))
- Faster project deletion
- Better resource cleanup
- Improved user experience
- **#357**: Make project creation endpoint idempotent
([`53fb13b`](https://github.com/basicmachines-co/basic-memory/commit/53fb13b))
- Prevents errors when creating existing projects
- Better API reliability
- Improved cloud integration
- **#353**: Handle None text values in Claude conversations importer
([`bd6c834`](https://github.com/basicmachines-co/basic-memory/commit/bd6c834))
- Fixes import errors with empty messages
- Better error handling in importers
- Improved data migration
### Performance Improvements
- Force full database sync after project sync/bisync operations
([`2ad0ee9`](https://github.com/basicmachines-co/basic-memory/commit/2ad0ee9))
- Ensures database consistency after cloud operations
- Better sync reliability
- Improved data integrity
### Documentation
- Add free trial information to README
([`a7d7cc5`](https://github.com/basicmachines-co/basic-memory/commit/a7d7cc5), [`8aaddb6`](https://github.com/basicmachines-co/basic-memory/commit/8aaddb6))
- Updated README with Basic Memory Cloud trial info
- Better onboarding experience
- Clearer pricing information
- Announce Basic Memory Cloud launch in README
([`d756531`](https://github.com/basicmachines-co/basic-memory/commit/d756531))
- Official cloud service announcement
- Updated documentation for cloud features
- Improved product positioning
### Migration Guide
No manual migration required. Upgrade with:
```bash
# Update via uv
uv tool upgrade basic-memory
# Or install fresh
uv tool install basic-memory
```
**What's New in v0.16.0:**
- Streaming foundation and consolidated async I/O (SPEC-19)
- Simplified project-scoped rclone sync (SPEC-20)
- Circuit breaker for sync failure resilience
- Enhanced Windows platform support
- Improved cloud upload with verbose and gitignore options
- Better error handling across YAML parsing and frontmatter
- Memory optimization for large projects
- Archive file filtering during upload
**Breaking Changes:**
- `write_note` parameter renamed: `entity_type``note_type` for clarity
### Installation
```bash
# Latest stable release
uv tool install basic-memory
# Update existing installation
uv tool upgrade basic-memory
# Docker
docker pull ghcr.io/basicmachines-co/basic-memory:v0.16.0
```
## v0.15.2 (2025-10-14)
### Features
- **#356**: Add WebDAV upload command for cloud projects
([`5258f45`](https://github.com/basicmachines-co/basic-memory/commit/5258f457))
- New `bm cloud upload` command for uploading local files/directories to cloud projects
- WebDAV-based file transfer with automatic directory creation
- Support for `.gitignore` and `.bmignore` pattern filtering
- Automatic project creation with `--create-project` flag
- Optional post-upload sync with `--sync` flag (enabled by default)
- Human-readable file size reporting (bytes, KB, MB)
- Comprehensive test coverage (28 unit tests)
### Migration Guide
No manual migration required. Upgrade with:
```bash
# Update via uv
uv tool upgrade basic-memory
# Or install fresh
uv tool install basic-memory
```
**What's New:**
- Upload local files to cloud projects with `bm cloud upload`
- Streamlined cloud project creation and management
- Better file filtering with gitignore integration
### Installation
```bash
# Latest stable release
uv tool install basic-memory
# Update existing installation
uv tool upgrade basic-memory
# Docker
docker pull ghcr.io/basicmachines-co/basic-memory:v0.15.2
```
## v0.15.1 (2025-10-13)
### Performance Improvements
- **#352**: Optimize sync/indexing for 43% faster performance
([`c0538ad`](https://github.com/basicmachines-co/basic-memory/commit/c0538ad2perf0d68a2a3604e255c3f2c42c5ed))
- Significant performance improvements to file synchronization and indexing operations
- 43% reduction in sync time for large knowledge bases
- Optimized database queries and file processing
- **#350**: Optimize directory operations for 10-100x performance improvement
([`00b73b0`](https://github.com/basicmachines-co/basic-memory/commit/00b73b0d))
- Dramatic performance improvements for directory listing operations
- 10-100x faster directory traversal depending on knowledge base size
- Reduced memory footprint for large directory structures
- Exclude null fields from directory endpoint responses for smaller payloads
### Bug Fixes
- **#355**: Update view_note and ChatGPT tools for Claude Desktop compatibility
([`2b7008d`](https://github.com/basicmachines-co/basic-memory/commit/2b7008d9))
- Fix view_note tool formatting for better Claude Desktop rendering
- Update ChatGPT tool integration for improved compatibility
- Enhanced artifact display in Claude Desktop interface
- **#348**: Add permalink normalization to project lookups in deps.py
([`a09066e`](https://github.com/basicmachines-co/basic-memory/commit/a09066e0))
- Fix project lookup failures due to case sensitivity
- Normalize permalinks consistently across project operations
- Improve project switching reliability
- **#345**: Project deletion failing with permalink normalization
([`be352ab`](https://github.com/basicmachines-co/basic-memory/commit/be352ab4))
- Fix project deletion errors related to permalink handling
- Ensure proper cleanup of project resources
- Improve error messages for deletion failures
- **#341**: Correct ProjectItem.home property to return path instead of name
([`3e876a7`](https://github.com/basicmachines-co/basic-memory/commit/3e876a75))
- Fix ProjectItem.home to return correct project path
- Resolve configuration issues with project paths
- Improve project path resolution consistency
- **#339**: Prevent nested project paths to avoid data conflicts
([`795e339`](https://github.com/basicmachines-co/basic-memory/commit/795e3393))
- Block creation of nested project paths that could cause data conflicts
- Add validation to prevent project path hierarchy issues
- Improve error messages for invalid project configurations
- **#338**: Normalize paths to lowercase in cloud mode to prevent case collisions
([`07e304c`](https://github.com/basicmachines-co/basic-memory/commit/07e304ce))
- Fix path case sensitivity issues in cloud deployments
- Normalize paths consistently across cloud operations
- Prevent data loss from case-insensitive filesystem collisions
- **#336**: Cloud mode path validation and sanitization (bmc-issue-103)
([`2a1c06d`](https://github.com/basicmachines-co/basic-memory/commit/2a1c06d9))
- Enhanced path validation for cloud deployments
- Improved path sanitization to prevent security issues
- Better error handling for invalid paths in cloud mode
- **#332**: Cloud mode path validation and sanitization
([`7616b2b`](https://github.com/basicmachines-co/basic-memory/commit/7616b2bb))
- Additional cloud mode path fixes and improvements
- Comprehensive path validation for cloud environments
- Security enhancements for path handling
### Features
- **#344**: Async client context manager pattern for cloud consolidation (SPEC-16)
([`8d2e70c`](https://github.com/basicmachines-co/basic-memory/commit/8d2e70cf))
- Refactor async client to use context manager pattern
- Improve resource management and cleanup
- Enable better dependency injection for cloud deployments
- Foundation for cloud platform consolidation
- **#343**: Add SPEC-15 for configuration persistence via Tigris
([`53438d1`](https://github.com/basicmachines-co/basic-memory/commit/53438d1e))
- Design specification for persistent configuration storage
- Foundation for cloud configuration management
- Tigris S3-compatible storage integration planning
- **#334**: Introduce BASIC_MEMORY_PROJECT_ROOT for path constraints
([`ccc4386`](https://github.com/basicmachines-co/basic-memory/commit/ccc43866))
- Add environment variable for constraining project paths
- Improve security by limiting project creation locations
- Better control over project directory structure
### Documentation
- **#335**: v0.15.0 assistant guide updates
([`c6f93a0`](https://github.com/basicmachines-co/basic-memory/commit/c6f93a02))
- Update AI assistant guide for v0.15.0 features
- Improve documentation for new MCP tools
- Better examples and usage patterns
- **#339**: Add tool use documentation to write_note for root folder usage
([`73202d1`](https://github.com/basicmachines-co/basic-memory/commit/73202d1a))
- Document how to use empty string for root folder in write_note
- Clarify folder parameter usage
- Improve tool documentation clarity
- Fix link in ai_assistant_guide resource
([`2a1c06d`](https://github.com/basicmachines-co/basic-memory/commit/2a1c06d9))
- Correct broken documentation links
- Improve resource accessibility
### Refactoring
- Add SPEC-17 and SPEC-18 documentation
([`962d88e`](https://github.com/basicmachines-co/basic-memory/commit/962d88ea))
- New specification documents for future features
- Architecture planning and design documentation
### Breaking Changes
**None** - This release maintains full backward compatibility with v0.15.0
### Migration Guide
No manual migration required. Upgrade with:
```bash
# Update via uv
uv tool upgrade basic-memory
# Or install fresh
uv tool install basic-memory
```
**What's Fixed:**
- Significant performance improvements (43% faster sync, 10-100x faster directory operations)
- Multiple cloud deployment stability fixes
- Project path validation and normalization issues resolved
- Better Claude Desktop and ChatGPT integration
**What's New:**
- Context manager pattern for async clients (foundation for cloud consolidation)
- BASIC_MEMORY_PROJECT_ROOT environment variable for path constraints
- Enhanced cloud mode path handling and security
- SPEC-15 and SPEC-16 architecture documentation
### Installation
```bash
# Latest stable release
uv tool install basic-memory
# Update existing installation
uv tool upgrade basic-memory
# Docker
docker pull ghcr.io/basicmachines-co/basic-memory:v0.15.1
```
## v0.15.0 (2025-10-04)
### Critical Bug Fixes
- **Permalink Collision Data Loss Prevention** - Fixed critical bug where creating similar entity names would overwrite existing files
([`2a050ed`](https://github.com/basicmachines-co/basic-memory/commit/2a050edee42b07294f5199902a60b626bfc47be8))
- **Issue**: Creating "Node C" would overwrite "Node A.md" due to fuzzy search incorrectly matching similar file paths
- **Solution**: Added `strict=True` parameter to link resolution, disabling fuzzy search fallback during entity creation
- **Impact**: Prevents data loss from false positive path matching like "edge-cases/Node A.md" vs "edge-cases/Node C.md"
- **Testing**: Comprehensive integration tests and MCP-level permalink collision tests added
- **Status**: Manually verified fix prevents file overwrite in production scenarios
### Bug Fixes
- **#330**: Remove .env file loading from BasicMemoryConfig
([`f3b1945`](https://github.com/basicmachines-co/basic-memory/commit/f3b1945e4c0070d0282eaf98c085ef188c8edd2d))
- Clean up configuration initialization to prevent unintended environment variable loading
- **#329**: Normalize underscores in memory:// URLs for build_context
([`f5a11f3`](https://github.com/basicmachines-co/basic-memory/commit/f5a11f3911edda55bee6970ed9e7c38f7fd7a059))
- Fix URL normalization to handle underscores consistently in memory:// protocol
- Improve knowledge graph navigation with standardized URL handling
- **#328**: Simplify entity upsert to use database-level conflict resolution
([`ee83b0e`](https://github.com/basicmachines-co/basic-memory/commit/ee83b0e5a8f00cdcc8e24a0f8c9449c6eaddf649))
- Leverage SQLite's native UPSERT for cleaner entity creation/update logic
- Reduce application-level complexity by using database conflict resolution
- **#312**: Add proper datetime JSON schema format annotations for MCP validation
([`a7bf42e`](https://github.com/basicmachines-co/basic-memory/commit/a7bf42ef495a3e2c66230985c3445cab5c52c408))
- Fix MCP schema validation errors with proper datetime format annotations
- Ensure compatibility with strict MCP schema validators
- **#281**: Fix move_note without file extension
([`3e168b9`](https://github.com/basicmachines-co/basic-memory/commit/3e168b98f3962681799f4537eb86ded47e771665))
- Allow moving notes by title alone without requiring .md extension
- Improve move operation usability and error handling
- **#310**: Remove obsolete update_current_project function and --project flag reference
([`17a6733`](https://github.com/basicmachines-co/basic-memory/commit/17a6733c9d280def922e37c2cd171e3ee44fce21))
- Clean up deprecated project management code
- Remove unused CLI flag references
- **#309**: Make sync operations truly non-blocking with thread pool
([`1091e11`](https://github.com/basicmachines-co/basic-memory/commit/1091e113227c86f10f574e56a262aff72f728113))
- Move sync operations to background thread pool for improved responsiveness
- Prevent blocking during file synchronization operations
### Features
- **#327**: CLI Subscription Validation (SPEC-13 Phase 2)
([`ace6a0f`](https://github.com/basicmachines-co/basic-memory/commit/ace6a0f50d8d0b4ea31fb526361c2d9616271740))
- Implement subscription validation for CLI operations
- Foundation for future cloud billing integration
- **#322**: Cloud CLI sync via rclone bisync
([`99a35a7`](https://github.com/basicmachines-co/basic-memory/commit/99a35a7fb410ef88c50050e666e4244350e44a6e))
- Add bidirectional cloud synchronization using rclone
- Enable local-cloud file sync with conflict detection
- **#315**: Implement SPEC-11 API performance optimizations
([`5da97e4`](https://github.com/basicmachines-co/basic-memory/commit/5da97e482052907d68a2a3604e255c3f2c42c5ed))
- Comprehensive API performance improvements
- Optimized database queries and response times
- **#314**: Integrate ignore_utils to skip .gitignored files in sync process
([`33ee1e0`](https://github.com/basicmachines-co/basic-memory/commit/33ee1e0831d2060587de2c9886e74ff111b04583))
- Respect .gitignore patterns during file synchronization
- Prevent syncing build artifacts and temporary files
- **#313**: Add disable_permalinks config flag
([`9035913`](https://github.com/basicmachines-co/basic-memory/commit/903591384dffeba9996a18463818bdb8b28ca03e))
- Optional permalink generation for users who don't need them
- Improves flexibility for different knowledge management workflows
- **#306**: Implement cloud mount CLI commands for local file access
([`2c5c606`](https://github.com/basicmachines-co/basic-memory/commit/2c5c606a394ab994334c8fd307b30370037bbf39))
- Mount cloud files locally using rclone for real-time editing
- Three performance profiles: fast (5s), balanced (10-15s), safe (15s+)
- Cross-platform rclone installer with package manager fallbacks
- **#305**: ChatGPT tools for search and fetch
([`f40ab31`](https://github.com/basicmachines-co/basic-memory/commit/f40ab31685a9510a07f5d87bf24436c0df80680f))
- Add ChatGPT-specific search and fetch tools
- Expand AI assistant integration options
- **#298**: Implement SPEC-6 Stateless Architecture for MCP Tools
([`a1d7792`](https://github.com/basicmachines-co/basic-memory/commit/a1d7792bdb6f71c4943b861d1c237f6ac7021247))
- Redesign MCP tools for stateless operation
- Enable cloud deployment with better scalability
- **#296**: Basic Memory cloud upload
([`e0d8aeb`](https://github.com/basicmachines-co/basic-memory/commit/e0d8aeb14913f3471b9716d0c60c61adb1d74687))
- Implement file upload capabilities for cloud storage
- Foundation for cloud-hosted Basic Memory instances
- **#291**: Merge Cloud auth
([`3a6baf8`](https://github.com/basicmachines-co/basic-memory/commit/3a6baf80fc6012e9434e06b1605f9a8b198d8688))
- OAuth 2.1 authentication with Supabase integration
- JWT-based tenant isolation for multi-user cloud deployments
### Platform & Infrastructure
- **#331**: Add Python 3.13 to test matrix
([`16d7edd`](https://github.com/basicmachines-co/basic-memory/commit/16d7eddbf704abfe53373663e80d05ecdde15aa7))
- Ensure compatibility with latest Python version
- Expand CI/CD testing coverage
- **#316**: Enable WAL mode and add Windows-specific SQLite optimizations
([`c83d567`](https://github.com/basicmachines-co/basic-memory/commit/c83d567917267bb3d52708f4b38d2daf36c1f135))
- Enable Write-Ahead Logging for better concurrency
- Platform-specific SQLite optimizations for Windows users
- **#320**: Rework lifecycle management to optimize cloud deployment
([`ea2e93d`](https://github.com/basicmachines-co/basic-memory/commit/ea2e93d9265bfa366d2d3796f99f579ab2aed48c))
- Optimize application lifecycle for cloud environments
- Improve startup time and resource management
- **#319**: Resolve entity relations in background to prevent cold start blocking
([`324844a`](https://github.com/basicmachines-co/basic-memory/commit/324844a670d874410c634db520a68c09149045ea))
- Move relation resolution to background processing
- Faster MCP server cold starts
- **#318**: Enforce minimum 1-day timeframe for recent_activity
([`f818702`](https://github.com/basicmachines-co/basic-memory/commit/f818702ab7f8d2d706178e7b0ed3467501c9c4a2))
- Fix timezone-related issues in recent activity queries
- Ensure consistent behavior across time zones
- **#317**: Critical cloud deployment fixes for MCP stability
([`2efd8f4`](https://github.com/basicmachines-co/basic-memory/commit/2efd8f44e2d0259079ed5105fea34308875c0e10))
- Multiple stability improvements for cloud-hosted MCP servers
- Enhanced error handling and recovery
### Technical Improvements
- **Comprehensive Testing** - Extensive test coverage for critical fixes
- New permalink collision test suite with 4 MCP-level integration tests
- Entity service test coverage expanded to reproduce fuzzy search bug
- Manual testing verification of data loss prevention
- All 55 entity service tests passing with new strict resolution
- **Windows Support Enhancements**
([`7a8b08d`](https://github.com/basicmachines-co/basic-memory/commit/7a8b08d11ee627b54af6f5ea7ab4ef9fcd8cf4ed),
[`9aa4024`](https://github.com/basicmachines-co/basic-memory/commit/9aa40246a8ad1c3cef82b32e1ca7ce8ea23e1e05))
- Fix Windows test failures and add Windows CI support
- Address platform-specific issues for Windows users
- Enhanced cross-platform compatibility
- **Docker Improvements**
([`105bcaa`](https://github.com/basicmachines-co/basic-memory/commit/105bcaa025576a06f889183baded6c18f3782696))
- Implement non-root Docker container to fix file ownership issues
- Improved security and compatibility in containerized deployments
- **Code Quality**
- Enhanced filename sanitization with optional kebab case support
- Improved character conflict detection for sync operations
- Better error handling across the codebase
- Path traversal security vulnerability fixes
### Documentation
- **#321**: Corrected dead links in README
([`fc38877`](https://github.com/basicmachines-co/basic-memory/commit/fc38877008cd8c762116f7ff4b2573495b4e5c0f))
- Fix broken documentation links
- Improve navigation and accessibility
- **#308**: Update Claude Code GitHub Workflow
([`8c7e29e`](https://github.com/basicmachines-co/basic-memory/commit/8c7e29e325f36a67bdefe8811637493bef4bbf56))
- Enhanced GitHub integration documentation
- Better Claude Code collaboration workflow
### Breaking Changes
**None** - This release maintains full backward compatibility with v0.14.x
All changes are either:
- Bug fixes that correct unintended behavior
- New optional features that don't affect existing functionality
- Internal optimizations that are transparent to users
### Migration Guide
No manual migration required. Upgrade with:
```bash
# Update via uv
uv tool upgrade basic-memory
# Or install fresh
uv tool install basic-memory
```
**What's Fixed:**
- Data loss bug from permalink collisions is completely resolved
- Cloud deployment stability significantly improved
- Windows platform compatibility enhanced
- Better performance across all operations
**What's New:**
- Cloud sync capabilities via rclone
- Subscription validation foundation
- Python 3.13 support
- Enhanced security and stability
### Installation
```bash
# Latest stable release
uv tool install basic-memory
# Update existing installation
uv tool upgrade basic-memory
# Docker
docker pull ghcr.io/basicmachines-co/basic-memory:v0.15.0
```
## v0.14.2 (2025-07-03)
### Bug Fixes
@@ -1356,4 +2237,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))
+201 -27
View File
@@ -14,20 +14,40 @@ 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 all tests (SQLite + Postgres): `just test`
- Run all tests against SQLite: `just test-sqlite`
- Run all tests against Postgres: `just test-postgres` (uses testcontainers)
- Run unit tests (SQLite): `just test-unit-sqlite`
- Run unit tests (Postgres): `just test-unit-postgres`
- Run integration tests (SQLite): `just test-int-sqlite`
- Run integration tests (Postgres): `just test-int-postgres`
- Generate HTML coverage: `just coverage`
- 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`
- Run benchmarks: `pytest test-int/test_sync_performance_benchmark.py -v -m "benchmark and not slow"`
- Lint: `just lint` or `ruff check . --fix`
- Type check: `just typecheck` or `uv run pyright`
- Format: `just format` or `uv run ruff format .`
- Run all code checks: `just check` (runs lint, format, typecheck, test)
- Create db migration: `just migration "Your migration message"`
- Run development MCP Inspector: `just run-inspector`
**Note:** Project requires Python 3.12+ (uses type parameter syntax and `type` aliases introduced in 3.12)
**Postgres Testing:** Uses [testcontainers](https://testcontainers-python.readthedocs.io/) which automatically spins up a Postgres instance in Docker. No manual database setup required - just have Docker running.
### Test Structure
- `tests/` - Unit tests for individual components (mocked, fast)
- `test-int/` - Integration tests for real-world scenarios (no mocks, realistic)
- Both directories are covered by unified coverage reporting
- Benchmark tests in `test-int/` are marked with `@pytest.mark.benchmark`
- Slow tests are marked with `@pytest.mark.slow`
### Code Style Guidelines
- Line length: 100 characters max
- Python 3.12+ with full type annotations
- Python 3.12+ with full type annotations (uses type parameters and type aliases)
- Format with ruff (consistent styling)
- Import order: standard lib, third-party, local imports
- Naming: snake_case for functions/variables, PascalCase for classes
@@ -38,11 +58,69 @@ See the [README.md](README.md) file for a project overview.
- Follow the repository pattern for data access
- Tools communicate to api routers via the httpx ASGI client (in process)
### Code Change Guidelines
- **Full file read before edits**: Before editing any file, read it in full first to ensure complete context; partial reads lead to corrupted edits
- **Minimize diffs**: Prefer the smallest change that satisfies the request. Avoid unrelated refactors or style rewrites unless necessary for correctness
- **No speculative getattr**: Never use `getattr(obj, "attr", default)` when unsure about attribute names. Check the class definition or source code first
- **Fail fast**: Write code with fail-fast logic by default. Do not swallow exceptions with errors or warnings
- **No fallback logic**: Do not add fallback logic unless explicitly told to and agreed with the user
- **No guessing**: Do not say "The issue is..." before you actually know what the issue is. Investigate first.
### Literate Programming Style
Code should tell a story. Comments must explain the "why" and narrative flow, not just the "what".
**Section Headers:**
For files with multiple phases of logic, add section headers so the control flow reads like chapters:
```python
# --- Authentication ---
# ... auth logic ...
# --- Data Validation ---
# ... validation logic ...
# --- Business Logic ---
# ... core logic ...
```
**Decision Point Comments:**
For conditionals that materially change behavior (gates, fallbacks, retries, feature flags), add comments with:
- **Trigger**: what condition causes this branch
- **Why**: the rationale (cost, correctness, UX, determinism)
- **Outcome**: what changes downstream
```python
# Trigger: project has no active sync watcher
# Why: avoid duplicate file system watchers consuming resources
# Outcome: starts new watcher, registers in active_watchers dict
if project_id not in active_watchers:
start_watcher(project_id)
```
**Constraint Comments:**
If code exists because of a constraint (async requirements, rate limits, schema compatibility), explain the constraint near the code:
```python
# SQLite requires WAL mode for concurrent read/write access
connection.execute("PRAGMA journal_mode=WAL")
```
**What NOT to Comment:**
Avoid comments that restate obvious code:
```python
# Bad - restates code
counter += 1 # increment counter
# Good - explains why
counter += 1 # track retries for backoff calculation
```
### Codebase Architecture
- `/alembic` - Alembic db migrations
- `/api` - FastAPI implementation of REST endpoints
- `/cli` - Typer command-line interface
- `/importers` - Import functionality for Claude, ChatGPT, and other sources
- `/markdown` - Markdown parsing and processing
- `/mcp` - Model Context Protocol server implementation
- `/models` - SQLAlchemy ORM models
@@ -61,9 +139,48 @@ See the [README.md](README.md) file for a project overview.
- Schema changes require Alembic migrations
- SQLite is used for indexing and full text search, files are source of truth
- 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 environment with in memory SQLite and tmp_file directory
- Unit tests (`tests/`) use mocks when necessary; integration tests (`test-int/`) use real implementations
- By default, tests run against SQLite (fast, no Docker needed)
- Set `BASIC_MEMORY_TEST_POSTGRES=1` to run against Postgres (uses testcontainers - Docker required)
- Each test runs in a standalone environment with isolated database and tmp_path directory
- CI runs SQLite and Postgres tests in parallel for faster feedback
- Performance benchmarks are in `test-int/test_sync_performance_benchmark.py`
- Use pytest markers: `@pytest.mark.benchmark` for benchmarks, `@pytest.mark.slow` for slow tests
### Async Client Pattern (Important!)
**All MCP tools and CLI commands use the context manager pattern for HTTP clients:**
```python
from basic_memory.mcp.async_client import get_client
async def my_mcp_tool():
async with get_client() as client:
# Use client for API calls
response = await call_get(client, "/path")
return response
```
**Do NOT use:**
-`from basic_memory.mcp.async_client import client` (deprecated module-level client)
- ❌ Manual auth header management
-`inject_auth_header()` (deleted)
**Key principles:**
- Auth happens at client creation, not per-request
- Proper resource management via context managers
- Supports three modes: Local (ASGI), CLI cloud (HTTP + auth), Cloud app (factory injection)
- Factory pattern enables dependency injection for cloud consolidation
**For cloud app integration:**
```python
from basic_memory.mcp import async_client
# Set custom factory before importing tools
async_client.set_client_factory(your_custom_factory)
```
See SPEC-16 for full context manager refactor details.
## BASIC MEMORY PRODUCT USAGE
@@ -80,14 +197,27 @@ See the [README.md](README.md) file for a project overview.
### Basic Memory Commands
- Sync knowledge: `basic-memory sync` or `basic-memory sync --watch`
**Local Commands:**
- Check sync status: `basic-memory status`
- Import from Claude: `basic-memory import claude conversations`
- Import from ChatGPT: `basic-memory import chatgpt`
- Import from Memory JSON: `basic-memory import memory-json`
- Check sync status: `basic-memory status`
- Tool access: `basic-memory tools` (provides CLI access to MCP tools)
- Guide: `basic-memory tools basic-memory-guide`
- Continue: `basic-memory tools continue-conversation --topic="search"`
- Tool access: `basic-memory tool` (provides CLI access to MCP tools)
- Continue: `basic-memory tool continue-conversation --topic="search"`
**Project Management:**
- List projects: `basic-memory project list`
- Add project: `basic-memory project add "name" ~/path`
- Project info: `basic-memory project info`
- One-way sync (local -> cloud): `basic-memory project sync`
- Bidirectional sync: `basic-memory project bisync`
- Integrity check: `basic-memory project check`
**Cloud Commands (requires subscription):**
- Authenticate: `basic-memory cloud login`
- Logout: `basic-memory cloud logout`
- Check cloud status: `basic-memory cloud status`
- Setup cloud sync: `basic-memory cloud setup`
### MCP Capabilities
@@ -95,28 +225,66 @@ 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
- `read_content(path)` - Read raw file content (text, images, binaries) without knowledge graph processing
- `view_note(identifier, page, page_size)` - View notes as formatted artifacts for better readability
- `edit_note(identifier, operation, content)` - Edit notes incrementally (append, prepend, find/replace, replace_section)
- `move_note(identifier, destination_path)` - Move notes to new locations, updating database and maintaining links
- `delete_note(identifier)` - Delete notes from the knowledge base
**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)` - Browse 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, search_type, types, entity_types, after_date)` - Full-text search across all content with advanced filtering options
**Project Management:**
- `list_memory_projects()` - List all available projects with their status
- `create_memory_project(project_name, project_path, set_default)` - Create new Basic Memory projects
- `delete_project(project_name)` - Delete a project from configuration
**Visualization:**
- `canvas(nodes, edges, title, folder)` - Generate Obsidian canvas files for knowledge graph visualization
**ChatGPT-Compatible Tools:**
- `search(query)` - Search across knowledge base (OpenAI actions compatible)
- `fetch(id)` - Fetch full content of a search result document
- 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
- `recent_activity(timeframe)` - View recently changed items with formatted output
- `json_canvas_spec()` - Full JSON Canvas specification for Obsidian visualization
### Cloud Features (v0.15.0+)
Basic Memory now supports cloud synchronization and storage (requires active subscription):
**Authentication:**
- JWT-based authentication with subscription validation
- Secure session management with token refresh
- Support for multiple cloud projects
**Bidirectional Sync:**
- rclone bisync integration for two-way synchronization
- Conflict resolution and integrity verification
- Real-time sync with change detection
- Mount/unmount cloud storage for direct file access
**Cloud Project Management:**
- Create and manage projects in the cloud
- Toggle between local and cloud modes
- Per-project sync configuration
- Subscription-based access control
**Security & Performance:**
- Removed .env file loading for improved security
- .gitignore integration (respects gitignored files)
- WAL mode for SQLite performance
- Background relation resolution (non-blocking startup)
- API performance optimizations (SPEC-11)
## AI-Human Collaborative Development
@@ -132,6 +300,11 @@ of using AI just for code generation, we've developed a true collaborative workf
This approach has allowed us to tackle more complex challenges and build a more robust system than either humans or AI
could achieve independently.
**Problem-Solving Guidance:**
- If a solution isn't working after reasonable effort, suggest alternative approaches
- Don't persist with a problematic library or pattern when better alternatives exist
- Example: When py-pglite caused cascading test failures, switching to testcontainers-postgres was the right call
## GitHub Integration
Basic Memory has taken AI-Human collaboration to the next level by integrating Claude directly into the development workflow through GitHub:
@@ -167,5 +340,6 @@ With GitHub integration, the development workflow includes:
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
5. **Code Commits**: ALWAYS sign off commits with `git commit -s`
This level of integration represents a new paradigm in AI-human collaboration, where the AI assistant becomes a full-fledged team member rather than just a tool for generating code snippets.
This level of integration represents a new paradigm in AI-human collaboration, where the AI assistant becomes a full-fledged team member rather than just a tool for generating code snippets.
+80 -7
View File
@@ -34,11 +34,18 @@ project and how to get started as a developer.
4. **Run the Tests**:
```bash
# Run all tests
# Run all tests with unified coverage (unit + integration)
just test
# or
uv run pytest -p pytest_mock -v
# Run unit tests only (fast, no coverage)
just test-unit
# Run integration tests only (fast, no coverage)
just test-int
# Generate HTML coverage report
just coverage
# Run a specific test
pytest tests/path/to/test_file.py::test_function_name
```
@@ -134,7 +141,7 @@ agreement to the DCO.
## Code Style Guidelines
- **Python Version**: Python 3.12+ with full type annotations
- **Python Version**: Python 3.12+ with full type annotations (3.12+ required for type parameter syntax)
- **Line Length**: 100 characters maximum
- **Formatting**: Use ruff for consistent styling
- **Import Order**: Standard lib, third-party, local imports
@@ -144,12 +151,78 @@ agreement to the DCO.
## Testing Guidelines
- **Coverage Target**: We aim for 100% test coverage for all code
### Test Structure
Basic Memory uses two test directories with unified coverage reporting:
- **`tests/`**: Unit tests that test individual components in isolation
- Fast execution with extensive mocking
- Test individual functions, classes, and modules
- Run with: `just test-unit` (no coverage, fast)
- **`test-int/`**: Integration tests that test real-world scenarios
- Test full workflows with real database and file operations
- Include performance benchmarks
- More realistic but slower than unit tests
- Run with: `just test-int` (no coverage, fast)
### Running Tests
```bash
# Run all tests with unified coverage report
just test
# Run only unit tests (fast iteration)
just test-unit
# Run only integration tests
just test-int
# Generate HTML coverage report
just coverage
# Run specific test
pytest tests/path/to/test_file.py::test_function_name
# Run tests excluding benchmarks
pytest -m "not benchmark"
# Run only benchmark tests
pytest -m benchmark test-int/test_sync_performance_benchmark.py
```
### Performance Benchmarks
The `test-int/test_sync_performance_benchmark.py` file contains performance benchmarks that measure sync and indexing speed:
- `test_benchmark_sync_100_files` - Small repository performance
- `test_benchmark_sync_500_files` - Medium repository performance
- `test_benchmark_sync_1000_files` - Large repository performance (marked slow)
- `test_benchmark_resync_no_changes` - Re-sync performance baseline
Run benchmarks with:
```bash
# Run all benchmarks (excluding slow ones)
pytest test-int/test_sync_performance_benchmark.py -v -m "benchmark and not slow"
# Run all benchmarks including slow ones
pytest test-int/test_sync_performance_benchmark.py -v -m benchmark
# Run specific benchmark
pytest test-int/test_sync_performance_benchmark.py::test_benchmark_sync_100_files -v
```
See `test-int/BENCHMARKS.md` for detailed benchmark documentation.
### Testing Best Practices
- **Coverage Target**: We aim for high test coverage for all code
- **Test Framework**: Use pytest for unit and integration tests
- **Mocking**: Use pytest-mock for mocking dependencies only when necessary
- **Mocking**: Avoid mocking in integration tests; use sparingly in unit tests
- **Edge Cases**: Test both normal operation and edge cases
- **Database Testing**: Use in-memory SQLite for testing database operations
- **Fixtures**: Use async pytest fixtures for setup and teardown
- **Markers**: Use `@pytest.mark.benchmark` for benchmarks, `@pytest.mark.slow` for slow tests
## Release Process
+3 -2
View File
@@ -24,11 +24,12 @@ WORKDIR /app
RUN uv sync --locked
# Create necessary directories and set ownership
RUN mkdir -p /app/data /app/.basic-memory && \
RUN mkdir -p /app/data/basic-memory /app/.basic-memory && \
chown -R appuser:${GID} /app
# Set default data directory and add venv to PATH
ENV BASIC_MEMORY_HOME=/app/data \
ENV BASIC_MEMORY_HOME=/app/data/basic-memory \
BASIC_MEMORY_PROJECT_ROOT=/app/data \
PATH="/app/.venv/bin:$PATH"
# Switch to the non-root user
-250
View File
@@ -1,250 +0,0 @@
# Legal File Inventory Guide
## Overview
This guide documents the comprehensive file inventory script created for Basic Memory's legal documentation needs, including copyright assignments and company agreement exhibits.
## Repository Analysis Summary
### Repository Structure
- **Primary Language**: Python (3,958 files)
- **License**: GNU Affero General Public License v3.0
- **Total Contributors**: 12 identified contributors
- **Main Contributors**:
- `phernandez@basicmachines.co` / `paul@basicmachines.co` (Paul Hernandez) - 700+ commits
- `groksrc@gmail.com` / `groksrc@users.noreply.github.com` (Drew Cain) - 18+ commits
- Various AI bots and automated systems
### File Type Distribution
Based on the analysis, the repository contains:
| Extension | Count | Purpose |
|-----------|-------|---------|
| `.py` | 3,958 | Python source code |
| `.pyi` | 4,536 | Python type stubs (dependencies) |
| `.pyc` | 1,435 | Compiled Python (excluded from inventory) |
| `.md` | 34 | Documentation files |
| `.toml` | 179 | Configuration files |
| `.html` | 119 | Coverage reports and documentation |
| `.txt` | 127 | Various text files |
### Contributors by Email Domain
**Basic Machines Contributors:**
- `phernandez@basicmachines.co` - 547 commits (Primary maintainer)
- `paul@basicmachines.co` / `paulmh@gmail.com` - 170 commits (Paul Hernandez)
- `claude@basicmachines.co` - 3 commits (AI assistant)
**External Contributors:**
- `groksrc@gmail.com` / `groksrc@users.noreply.github.com` - 18 commits (Drew Cain)
- Various one-time contributors (1-2 commits each)
**Automated Systems:**
- GitHub Actions, semantic-release, and other bots
## Legal Inventory Script Features
### What It Includes
**Source Files:**
- All Python source code (`.py` files)
- Configuration files (`.toml`, `.yaml`, `.json`, etc.)
- Documentation (`.md`, `.rst`, `.txt`)
- Build and deployment scripts
- Database migrations and SQL files
- Legal and license files
**Metadata for Each File:**
- File path, size, and creation/modification dates
- Git history (creation date, last modified, commit count)
- Contributors and their line contributions
- Primary author identification
- File categorization
- SHA-256 hash for integrity verification
### What It Excludes
**Generated/Build Artifacts:**
- `__pycache__/` and `.pyc` files
- Build directories (`build/`, `dist/`, `htmlcov/`)
- Coverage reports and cache files
**Dependencies:**
- Virtual environment files (`.venv/`, `venv/`)
- Third-party packages (`site-packages/`, `*.dist-info/`)
- Lock files (`uv.lock`, `package-lock.json`)
**IDE/Editor Files:**
- `.idea/`, `.vscode/`, `.DS_Store`
- Temporary and swap files
**Version Control:**
- `.git/` directory contents
- Git configuration files
## Usage Instructions
### Basic Usage
```bash
# Generate CSV inventory (default)
python3 legal_file_inventory.py
# Generate Markdown report
python3 legal_file_inventory.py --format markdown --output legal_report.md
# Generate JSON with full metadata
python3 legal_file_inventory.py --format json --output legal_data.json
# Specify different repository path
python3 legal_file_inventory.py --repo-path /path/to/repo --output inventory.csv
```
### Command Line Options
- `--output`, `-o`: Output file path (default: `basic_memory_legal_inventory.csv`)
- `--format`, `-f`: Output format - `csv`, `json`, or `markdown` (default: `csv`)
- `--repo-path`, `-r`: Repository path (default: current directory)
### Output Formats
**CSV Format:**
- Suitable for spreadsheet applications
- Contains all metadata fields
- Contributors stored as JSON string in separate column
**JSON Format:**
- Complete structured data
- Includes summary statistics and detailed file information
- Best for programmatic processing
**Markdown Format:**
- Human-readable report
- Summary statistics and contributor rankings
- Detailed table of all files
## Legal Documentation Applications
### Copyright Assignment Use Cases
1. **Contributor Identification**: The script identifies all contributors to each file based on git blame analysis
2. **Primary Author Recognition**: Determines the primary author of each file (contributor with most lines)
3. **Contribution Metrics**: Provides line counts and commit counts per contributor
4. **File Categorization**: Groups files by purpose (source code, documentation, configuration, etc.)
### Company Agreement Exhibits
The inventory provides comprehensive documentation of:
1. **Intellectual Property Scope**: All source code files and their origins
2. **Contributor Tracking**: Complete list of all individuals who have contributed code
3. **File Integrity**: SHA-256 hashes for verification of file contents
4. **Historical Documentation**: Git creation dates and modification history
### Due Diligence Documentation
The script generates data suitable for:
1. **Legal Review**: Comprehensive file listing with contributor information
2. **IP Audit**: Identification of all copyright holders
3. **License Compliance**: Verification of file ownership and licensing
4. **Asset Documentation**: Complete inventory of company code assets
## Integration with Legal Processes
### Recommended Workflow
1. **Generate Inventory**: Run the script to create current file inventory
2. **Legal Review**: Have legal counsel review the contributor list and file categorization
3. **Copyright Assignment**: Use contributor data to ensure proper copyright assignments
4. **Document Attachment**: Include inventory as exhibit in company agreements
5. **Regular Updates**: Re-run inventory for significant releases or legal milestones
### Key Legal Considerations
**AGPL-3.0 License:**
- All files are under AGPL-3.0 unless otherwise specified
- Contributors retain copyright but license under AGPL-3.0 terms
- Company needs proper copyright assignments for proprietary licensing
**Contributor Rights:**
- External contributors may retain rights to their contributions
- Proper contributor license agreements (CLAs) should be in place
- AI-generated content may have different legal status
**File Categories for Legal Review:**
- **Source Code**: Core IP, requires copyright assignment
- **Configuration**: May contain proprietary deployment information
- **Documentation**: Usually less sensitive but may contain trade secrets
- **Legal/License**: Critical for compliance verification
## Maintenance and Updates
### When to Regenerate Inventory
- Before major releases
- During legal document preparation
- After significant contributor additions
- For annual compliance reviews
- During acquisition or investment processes
### Validation Steps
1. Verify contributor email mapping is accurate
2. Check that file categorization makes sense
3. Ensure excluded files are appropriate
4. Review contributor counts against expectations
5. Validate file hashes for integrity
## Technical Implementation Notes
### Git Integration
- Uses `git blame --line-porcelain` for detailed contributor analysis
- Tracks file history with `git log --follow`
- Handles renamed files and complex git histories
### Performance Considerations
- Scans repository incrementally to handle large codebases
- Excludes binary dependencies to reduce processing time
- Caches git operations where possible
### Error Handling
- Gracefully handles files not in git
- Continues processing if individual files fail
- Provides detailed error reporting
## Security and Privacy
### Data Sensitivity
- Contains contributor names and email addresses
- May reveal internal file structure and organization
- Should be treated as confidential legal documentation
### Recommended Handling
- Limit access to legal and executive team
- Store in secure, access-controlled systems
- Consider redacting contributor emails for external sharing
- Regular cleanup of generated inventory files
## Troubleshooting
### Common Issues
**Git Not Available:**
- Ensure git is installed and repository is initialized
- Check that the script is run from within the repository
**Permission Errors:**
- Ensure read access to all repository files
- Check write permissions for output directory
**Large Repository Performance:**
- Consider running on subsets of files for very large repositories
- Use `--repo-path` to target specific subdirectories
**Contributor Mapping Issues:**
- Git usernames may not match real identities
- Consider post-processing to normalize contributor names
- Review .mailmap files for git identity consolidation
This comprehensive legal file inventory system provides the foundation for proper intellectual property documentation and legal compliance for the Basic Memory project.
+182 -7
View File
@@ -7,6 +7,16 @@
![](https://badge.mcpx.dev?type=dev 'MCP Dev')
[![smithery badge](https://smithery.ai/badge/@basicmachines-co/basic-memory)](https://smithery.ai/server/@basicmachines-co/basic-memory)
## 🚀 Basic Memory Cloud is Live!
- **Cross-device and multi-platform support is here.** Your knowledge graph now works on desktop, web, and mobile - seamlessly synced across all your AI tools (Claude, ChatGPT, Gemini, Claude Code, and Codex)
- **Early Supporter Pricing:** Early users get 25% off forever.
The open source project continues as always. Cloud just makes it work everywhere.
[Sign up now →](https://basicmemory.com/beta)
with a 7 day free trial
# Basic Memory
Basic Memory lets you build persistent knowledge through natural conversations with Large Language Models (LLMs) like
@@ -100,6 +110,9 @@ With Basic Memory, you can:
- Keep everything local and under your control
- Use familiar tools like Obsidian to view and edit notes
- Build a personal knowledge base that grows over time
- Sync your knowledge to the cloud with bidirectional synchronization
- Authenticate and manage cloud projects with subscription validation
- Mount cloud storage for direct file access
## How It Works in Practice
@@ -346,14 +359,57 @@ basic-memory sync
basic-memory sync --watch
```
3. In Claude Desktop, the LLM can now use these tools:
3. Cloud features (optional, requires subscription):
```bash
# Authenticate with cloud
basic-memory cloud login
# Bidirectional sync with cloud
basic-memory cloud sync
# Verify cloud integrity
basic-memory cloud check
# Mount cloud storage
basic-memory cloud mount
```
4. In Claude Desktop, the LLM can now use these tools:
**Content Management:**
```
write_note(title, content, folder, tags) - Create or update notes
read_note(identifier, page, page_size) - Read notes by title or permalink
read_content(path) - Read raw file content (text, images, binaries)
view_note(identifier) - View notes as formatted artifacts
edit_note(identifier, operation, content) - Edit notes incrementally
move_note(identifier, destination_path) - Move notes with database consistency
delete_note(identifier) - Delete notes from knowledge base
```
**Knowledge Graph Navigation:**
```
build_context(url, depth, timeframe) - Navigate knowledge graph via memory:// URLs
search(query, page, page_size) - Search across your knowledge base
recent_activity(type, depth, timeframe) - Find recently updated information
list_directory(dir_name, depth) - Browse directory contents with filtering
```
**Search & Discovery:**
```
search(query, page, page_size) - Search across your knowledge base
```
**Project Management:**
```
list_memory_projects() - List all available projects
create_memory_project(project_name, project_path) - Create new projects
get_current_project() - Show current project stats
sync_status() - Check synchronization status
```
**Visualization:**
```
canvas(nodes, edges, title, folder) - Generate knowledge visualizations
```
@@ -371,10 +427,129 @@ canvas(nodes, edges, title, folder) - Generate knowledge visualizations
See the [Documentation](https://memory.basicmachines.co/) for more info, including:
- [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)
- [Complete User Guide](https://docs.basicmemory.com/user-guide/)
- [CLI tools](https://docs.basicmemory.com/guides/cli-reference/)
- [Cloud CLI and Sync](https://docs.basicmemory.com/guides/cloud-cli/)
- [Managing multiple Projects](https://docs.basicmemory.com/guides/cli-reference/#project)
- [Importing data from OpenAI/Claude Projects](https://docs.basicmemory.com/guides/cli-reference/#import)
## Logging
Basic Memory uses [Loguru](https://github.com/Delgan/loguru) for logging. The logging behavior varies by entry point:
| Entry Point | Default Behavior | Use Case |
|-------------|------------------|----------|
| CLI commands | File only | Prevents log output from interfering with command output |
| MCP server | File only | Stdout would corrupt the JSON-RPC protocol |
| API server | File (local) or stdout (cloud) | Docker/cloud deployments use stdout |
**Log file location:** `~/.basic-memory/basic-memory.log` (10MB rotation, 10 days retention)
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `BASIC_MEMORY_LOG_LEVEL` | `INFO` | Log level: DEBUG, INFO, WARNING, ERROR |
| `BASIC_MEMORY_CLOUD_MODE` | `false` | When `true`, API logs to stdout with structured context |
| `BASIC_MEMORY_ENV` | `dev` | Set to `test` for test mode (stderr only) |
### Examples
```bash
# Enable debug logging
BASIC_MEMORY_LOG_LEVEL=DEBUG basic-memory sync
# View logs
tail -f ~/.basic-memory/basic-memory.log
# Cloud/Docker mode (stdout logging with structured context)
BASIC_MEMORY_CLOUD_MODE=true uvicorn basic_memory.api.app:app
```
## Telemetry
Basic Memory collects anonymous usage statistics to help improve the software. This follows the [Homebrew model](https://docs.brew.sh/Analytics) - telemetry is on by default with easy opt-out.
**What we collect:**
- App version, Python version, OS, architecture
- Feature usage (which MCP tools and CLI commands are used)
- Error types (sanitized - no file paths or personal data)
**What we NEVER collect:**
- Note content, file names, or paths
- Personal information
- IP addresses
**Opting out:**
```bash
# Disable telemetry
basic-memory telemetry disable
# Check status
basic-memory telemetry status
# Re-enable
basic-memory telemetry enable
```
Or set the environment variable:
```bash
export BASIC_MEMORY_TELEMETRY_ENABLED=false
```
For more details, see the [Telemetry documentation](https://basicmemory.com/telemetry).
## Development
### Running Tests
Basic Memory supports dual database backends (SQLite and Postgres). By default, tests run against SQLite. Set `BASIC_MEMORY_TEST_POSTGRES=1` to run against Postgres (uses testcontainers - Docker required).
**Quick Start:**
```bash
# Run all tests against SQLite (default, fast)
just test-sqlite
# Run all tests against Postgres (uses testcontainers)
just test-postgres
# Run both SQLite and Postgres tests
just test
```
**Available Test Commands:**
- `just test` - Run all tests against both SQLite and Postgres
- `just test-sqlite` - Run all tests against SQLite (fast, no Docker needed)
- `just test-postgres` - Run all tests against Postgres (uses testcontainers)
- `just test-unit-sqlite` - Run unit tests against SQLite
- `just test-unit-postgres` - Run unit tests against Postgres
- `just test-int-sqlite` - Run integration tests against SQLite
- `just test-int-postgres` - Run integration tests against Postgres
- `just test-windows` - Run Windows-specific tests (auto-skips on other platforms)
- `just test-benchmark` - Run performance benchmark tests
**Postgres Testing:**
Postgres tests use [testcontainers](https://testcontainers-python.readthedocs.io/) which automatically spins up a Postgres instance in Docker. No manual database setup required - just have Docker running.
**Test Markers:**
Tests use pytest markers for selective execution:
- `windows` - Windows-specific database optimizations
- `benchmark` - Performance tests (excluded from default runs)
**Other Development Commands:**
```bash
just install # Install with dev dependencies
just lint # Run linting checks
just typecheck # Run type checking
just format # Format code with ruff
just check # Run all quality checks
just migration "msg" # Create database migration
```
See the [justfile](justfile) for the complete list of development commands.
## License
@@ -393,4 +568,4 @@ and submitting PRs.
</picture>
</a>
Built with ♥️ by Basic Machines
Built with ♥️ by Basic Machines
-88
View File
@@ -1,88 +0,0 @@
#!/usr/bin/env python3
"""
Create CSV exhibits for individual contributors
"""
import json
import csv
from pathlib import Path
def create_csv_exhibits():
"""Create CSV Exhibit A files for each contributor."""
# Read the JSON inventory
inventory_files = list(Path("legal_inventory_main").glob("*.json"))
if not inventory_files:
print("Error: No JSON inventory files found")
return
inventory_file = inventory_files[0] # Use the most recent one
print(f"Using inventory file: {inventory_file}")
with open(inventory_file, 'r') as f:
data = json.load(f)
files = data['files']
# Create output directory
output_dir = Path("legal_exhibits")
output_dir.mkdir(exist_ok=True)
# Contributors we need exhibits for
target_contributors = {
'jope-bm': 'joe_exhibit_a.csv',
'Drew Cain': 'drew_cain_exhibit_a.csv'
}
print("Creating CSV exhibits for contributors...")
for contributor_key, filename in target_contributors.items():
# Find files for this contributor
contributor_files = []
for file_info in files:
# Check if this contributor is listed in the file's contributors
for contrib in file_info.get('contributors', []):
if contributor_key in contrib['name']:
contributor_files.append(file_info)
break
if not contributor_files:
print(f"No files found for {contributor_key}")
continue
# Sort files by path
contributor_files.sort(key=lambda x: x['path'])
# Create CSV file
csv_file = output_dir / filename
with open(csv_file, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = [
'file_path', 'file_name', 'category', 'size_bytes',
'modified_date', 'primary_author', 'all_contributors', 'sha256_hash'
]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for file_info in contributor_files:
contributors_list = '; '.join([c['name'] for c in file_info['contributors']])
writer.writerow({
'file_path': file_info['path'],
'file_name': file_info['name'],
'category': file_info['category'],
'size_bytes': file_info['size_bytes'],
'modified_date': file_info['modified_time'][:10],
'primary_author': file_info['primary_author'],
'all_contributors': contributors_list,
'sha256_hash': file_info['sha256_hash']
})
print(f"Created CSV exhibit for {contributor_key}: {csv_file}")
print(f" - {len(contributor_files)} files")
print(f" - {sum(f['size_bytes'] for f in contributor_files):,} bytes")
if __name__ == '__main__':
create_csv_exhibits()
-208
View File
@@ -1,208 +0,0 @@
#!/usr/bin/env python3
"""
Create individual Exhibit A files for copyright assignments
"""
import json
from pathlib import Path
from datetime import datetime
def create_individual_exhibits():
"""Create individual Exhibit A files for each contributor."""
# Read the JSON inventory
inventory_file = Path("legal_inventory_main/basic_memory_inventory_20250730_101521.json")
if not inventory_file.exists():
print(f"Error: {inventory_file} not found")
return
with open(inventory_file, 'r') as f:
data = json.load(f)
files = data['files']
# Create output directory
output_dir = Path("legal_exhibits")
output_dir.mkdir(exist_ok=True)
# Contributors we need exhibits for (based on copyright assignments)
target_contributors = {
'jope-bm': 'Joseph "Joe" [Last Name]', # Need to get his full name
'Drew Cain': 'Drew Cain'
}
print("Creating individual contributor exhibits...")
for contributor_key, full_name in target_contributors.items():
# Find files for this contributor
contributor_files = []
for file_info in files:
# Check if this contributor is listed in the file's contributors
for contrib in file_info.get('contributors', []):
if contributor_key in contrib['name']:
contributor_files.append(file_info)
break
if not contributor_files:
print(f"No files found for {contributor_key}")
continue
# Sort files by path
contributor_files.sort(key=lambda x: x['path'])
# Create exhibit markdown
exhibit_content = f"""# Exhibit A - Assigned Works
## Copyright Assignment: {full_name} to Basic Memory LLC
**Date:** [To be filled]
**Assignor:** {full_name}
**Assignee:** Basic Memory LLC
## Summary
- **Total Files:** {len(contributor_files)}
- **Total Size:** {sum(f['size_bytes'] for f in contributor_files):,} bytes
- **Categories:** {', '.join(set(f['category'] for f in contributor_files))}
## Detailed File List
"""
# Group by category
categories = {}
for file_info in contributor_files:
category = file_info['category']
if category not in categories:
categories[category] = []
categories[category].append(file_info)
# Add files by category
for category, category_files in sorted(categories.items()):
exhibit_content += f"### {category.replace('_', ' ').title()}\n\n"
for file_info in category_files:
exhibit_content += f"**{file_info['path']}**\n"
exhibit_content += f"- Size: {file_info['size_bytes']:,} bytes\n"
exhibit_content += f"- Modified: {file_info['modified_time'][:10]}\n"
exhibit_content += f"- Primary Author: {file_info['primary_author']}\n"
# Show all contributors for this file
if len(file_info['contributors']) > 1:
contributors_list = ', '.join([c['name'] for c in file_info['contributors']])
exhibit_content += f"- All Contributors: {contributors_list}\n"
exhibit_content += f"- SHA-256: `{file_info['sha256_hash']}`\n\n"
# Add verification section
exhibit_content += f"""
## Verification
This exhibit lists all files in the Basic Memory repository where {full_name} is identified as a contributor based on git commit history analysis.
**Analysis Date:** {datetime.now().strftime('%Y-%m-%d')}
**Repository State:** Basic Memory main branch
**Method:** Git history analysis via `git log --follow` for each file
## Legal Representation
{full_name} hereby represents and warrants that they are the author of the contributions listed above and have the right to assign copyright in these works to Basic Memory LLC.
---
*This exhibit is attached to and forms part of the Copyright Assignment Agreement between {full_name} and Basic Memory LLC.*
"""
# Write exhibit file
safe_name = contributor_key.replace(' ', '_').replace('-', '_').lower()
exhibit_file = output_dir / f"exhibit_a_{safe_name}.md"
with open(exhibit_file, 'w') as f:
f.write(exhibit_content)
print(f"Created exhibit for {full_name}: {exhibit_file}")
print(f" - {len(contributor_files)} files")
print(f" - {sum(f['size_bytes'] for f in contributor_files):,} bytes")
# Create overall summary exhibit (for Paul's assignment to Basic Machines LLC)
create_overall_summary_exhibit(data, output_dir)
def create_overall_summary_exhibit(data, output_dir):
"""Create overall summary exhibit for Company Agreement."""
files = data['files']
summary = data['summary']
contributors = data['contributors']
summary_content = f"""# Basic Memory Repository - Complete IP Inventory
## For Basic Memory LLC Company Agreement
**Analysis Date:** {summary['scan_date'][:10]}
**Repository:** {summary['repository_path']}
## Executive Summary
- **Total Files:** {summary['total_files']:,}
- **Total Size:** {summary['total_size_bytes']:,} bytes
- **Contributors:** {summary['contributor_count']}
- **Primary Author:** Paul Hernandez ({len(contributors.get('Paul Hernandez', {}).get('files', []))} files)
## File Categories
"""
for category, count in sorted(summary['categories'].items()):
summary_content += f"- **{category.replace('_', ' ').title()}:** {count} files\n"
summary_content += """
## Contributor Summary
"""
for contrib in summary['top_contributors']:
summary_content += f"- **{contrib['name']}** ({contrib['email']}): {contrib['file_count']} files, {contrib['commit_count']} commits\n"
summary_content += """
## Legal Significance
This inventory represents the complete codebase of Basic Memory as licensed from Basic Machines LLC to Basic Memory LLC under the copyright license agreement dated [DATE].
### IP Rights Chain
1. **Paul Hernandez** → Basic Machines LLC (copyright assignment)
2. **Basic Machines LLC** → Basic Memory LLC (15% royalty license)
3. **Co-founders** → Basic Memory LLC (direct copyright assignments)
### Due Diligence Documentation
This comprehensive file inventory serves as:
- **Company Agreement Exhibit:** Original codebase licensed to Basic Memory LLC
- **Acquisition Documentation:** Complete IP inventory for due diligence
- **Copyright Verification:** Establishes chain of title for all repository contents
## Repository Contents by Category
"""
# Add sample files by category (first 10 in each category)
for category in sorted(summary['categories'].keys()):
category_files = [f for f in files if f['category'] == category][:10]
if category_files:
summary_content += f"### {category.replace('_', ' ').title()} (Sample)\n\n"
for file_info in category_files:
summary_content += f"- `{file_info['path']}` ({file_info['size_bytes']:,} bytes)\n"
if len([f for f in files if f['category'] == category]) > 10:
remaining = len([f for f in files if f['category'] == category]) - 10
summary_content += f"- *... and {remaining} more files*\n"
summary_content += "\n"
summary_content += f"""
---
*This inventory was generated automatically from git repository analysis and represents the complete intellectual property foundation of Basic Memory as of {summary['scan_date'][:10]}.*
"""
# Write summary file
summary_file = output_dir / "basic_memory_complete_inventory.md"
with open(summary_file, 'w') as f:
f.write(summary_content)
print(f"Created complete inventory summary: {summary_file}")
if __name__ == '__main__':
create_individual_exhibits()
+42
View File
@@ -0,0 +1,42 @@
# Docker Compose configuration for Basic Memory with PostgreSQL
# Use this for local development and testing with Postgres backend
#
# Usage:
# docker-compose -f docker-compose-postgres.yml up -d
# docker-compose -f docker-compose-postgres.yml down
services:
postgres:
image: postgres:17
container_name: basic-memory-postgres
environment:
# Local development/test credentials - NOT for production
# These values are referenced by tests and justfile commands
POSTGRES_DB: basic_memory
POSTGRES_USER: basic_memory_user
POSTGRES_PASSWORD: dev_password # Simple password for local testing only
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U basic_memory_user -d basic_memory"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
# Named volume for Postgres data
postgres_data:
driver: local
# Named volume for persistent configuration
# Database will be stored in Postgres, not in this volume
basic-memory-config:
driver: local
# Network configuration (optional)
# networks:
# basic-memory-net:
# driver: bridge
-431
View File
@@ -1,431 +0,0 @@
---
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.
- **Local-First**: All data is stored in plain text files on the user's computer
- **Real-Time**: Users see content updates immediately
- **Bi-Directional**: Both you and users can read and edit notes
- **Semantic**: Simple patterns create a structured knowledge graph
- **Persistent**: Knowledge persists across sessions and conversations
## The Importance of the Knowledge Graph
Basic Memory's value comes from connections between notes, not just the notes themselves. When writing notes, your primary goal should be creating a rich, interconnected knowledge graph.
When creating content, focus on:
1. **Increasing Semantic Density**: Add multiple observations and relations to each note
2. **Using Accurate References**: Aim to reference existing entities by their exact titles
3. **Creating Forward References**: Feel free to reference entities that don't exist yet - Basic Memory will resolve these when they're created later
4. **Creating Bidirectional Links**: When appropriate, connect entities from both directions
5. **Using Meaningful Categories**: Add semantic context with appropriate observation categories
6. **Choosing Precise Relations**: Use specific relation types that convey meaning
Remember that a knowledge graph with 10 heavily connected notes is more valuable than 20 isolated notes. Your job is to help build these connections.
## Core Tools Reference
### Essential Content Management
**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
)
```
**Reading knowledge:**
```
read_note("Search Design") # By title
read_note("specs/search-design") # By path
read_note("memory://specs/search") # By memory URL
```
**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
```
**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
Basic Memory uses a special URL format to reference entities in the knowledge graph:
- `memory://title` - Reference by title
- `memory://folder/title` - Reference by folder and title
- `memory://permalink` - Reference by permalink
- `memory://path/relation_type/*` - Follow all relations of a specific type
- `memory://path/*/target` - Find all entities with relations to target
## Semantic Markdown Format
Knowledge is encoded in standard markdown using simple patterns:
**Observations** - Facts about an entity:
```markdown
- [category] This is an observation #tag1 #tag2 (optional context)
```
**Relations** - Links between entities:
```markdown
- relation_type [[Target Entity]] (optional context)
```
**Common Categories & Relation Types:**
- Categories: `[idea]`, `[decision]`, `[question]`, `[fact]`, `[requirement]`, `[technique]`, `[recipe]`, `[preference]`
- Relations: `relates_to`, `implements`, `requires`, `extends`, `part_of`, `pairs_with`, `inspired_by`, `originated_from`
## When to Record Context
**Always consider recording context when**:
1. Users make decisions or reach conclusions
2. Important information emerges during conversation
3. Multiple related topics are discussed
4. The conversation contains information that might be useful later
5. Plans, tasks, or action items are mentioned
**Protocol for recording context**:
1. Identify valuable information in the conversation
2. Ask the user: "Would you like me to record our discussion about [topic] in Basic Memory?"
3. If they agree, use `write_note` to capture the information
4. If they decline, continue without recording
5. Let the user know when information has been recorded: "I've saved our discussion about [topic] to Basic Memory."
## Understanding User Interactions
Users will interact with Basic Memory in patterns like:
1. **Creating knowledge**:
```
Human: "Let's write up what we discussed about search."
You: I'll create a note capturing our discussion about the search functionality.
[Use write_note() to record the conversation details]
```
2. **Referencing existing knowledge**:
```
Human: "Take a look at memory://specs/search"
You: I'll examine that information.
[Use build_context() to gather related information]
[Then read_note() to access specific content]
```
3. **Finding information**:
```
Human: "What were our decisions about auth?"
You: Let me find that information for you.
[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**
- All knowledge lives in local files on the user's computer
- Users can edit files outside your interaction
- Changes need to be synced by the user (usually automatic)
- Always verify information is current with `recent_activity()`
2. **Building Context Effectively**
- Start with specific entities
- Follow meaningful relations
- Check recent changes
- Build context incrementally
- Combine related information
3. **Writing Knowledge Wisely**
- 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
I've experimented with various brewing methods including French press, pour over, and espresso.
## Decision
Pour over is my preferred method for light to medium roasts because it highlights subtle flavors and offers more control over the extraction.
## Observations
- [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
```markdown
# Garden Planning
## Overview
This document outlines the garden layout and planting strategy for this season.
## Observations
- [structure] Raised beds in south corner for sun exposure #layout
- [structure] Drip irrigation system installed for efficiency #watering
- [pattern] Companion planting used to deter pests naturally #technique
## Relations
- contains [[Vegetable Section]]
- contains [[Herb Garden]]
- implements [[Organic Gardening Principles]]
```
### Technical Discussions
```markdown
# Recipe Improvement Discussion
## Key Points
Discussed strategies for improving the chocolate chip cookie recipe.
## Observations
- [issue] Cookies spread too thin when baked at 350°F #texture
- [solution] Chilling dough for 24 hours improves flavor and reduces spreading #technique
- [decision] Will use brown butter instead of regular butter #flavor
## Relations
- improves [[Basic Cookie Recipe]]
- inspired_by [[Bakery-Style Cookies]]
- 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:
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`
**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)
## Common Issues & Solutions
**Missing Content:**
- Try `search_notes()` with broader terms if `read_note()` fails
- Use fuzzy matching: search for partial titles
**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"
**Sync Issues:**
- If information seems outdated, suggest `basic-memory sync`
- Use `recent_activity()` to check if content is current
**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
1. **Proactively Record Context**
- Offer to capture important discussions
- Record decisions, rationales, and conclusions
- Link to related topics
- Ask for permission first: "Would you like me to save our discussion about [topic]?"
- Confirm when complete: "I've saved our discussion to Basic Memory"
2. **Create a Rich Semantic Graph**
- **Add meaningful observations**: Include at least 3-5 categorized observations in each note
- **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_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
3. **Structure Content Thoughtfully**
- Use clear, descriptive titles
- Organize with logical sections (Context, Decision, Implementation, etc.)
- Include relevant context and background
- Add semantic observations with appropriate categories
- Use a consistent format for similar types of notes
- Balance detail with conciseness
4. **Navigate Knowledge Effectively**
- Start with specific searches
- Follow relation paths
- Combine information from multiple sources
- Verify information is current
- Build a complete picture before responding
5. **Help Users Maintain Their Knowledge**
- Suggest organizing related topics
- Identify potential duplicates
- Recommend adding relations between topics
- 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
File diff suppressed because it is too large Load Diff
+726
View File
@@ -0,0 +1,726 @@
# Basic Memory Cloud CLI Guide
The Basic Memory Cloud CLI provides seamless integration between local and cloud knowledge bases using **project-scoped synchronization**. Each project can optionally sync with the cloud, giving you fine-grained control over what syncs and where.
## Overview
The cloud CLI enables you to:
- **Toggle cloud mode** - All regular `bm` commands work with cloud when enabled
- **Project-scoped sync** - Each project independently manages its sync configuration
- **Explicit operations** - Sync only what you want, when you want
- **Bidirectional sync** - Keep local and cloud in sync with rclone bisync
- **Offline access** - Work locally, sync when ready
## Prerequisites
Before using Basic Memory Cloud, you need:
- **Active Subscription**: An active Basic Memory Cloud subscription is required to access cloud features
- **Subscribe**: Visit [https://basicmemory.com/subscribe](https://basicmemory.com/subscribe) to sign up
If you attempt to log in without an active subscription, you'll receive a "Subscription Required" error with a link to subscribe.
## Architecture: Project-Scoped Sync
### The Problem
**Old approach (SPEC-8):** All projects lived in a single `~/basic-memory-cloud-sync/` directory. This caused:
- ❌ Directory conflicts between mount and bisync
- ❌ Auto-discovery creating phantom projects
- ❌ Confusion about what syncs and when
- ❌ All-or-nothing sync (couldn't sync just one project)
**New approach (SPEC-20):** Each project independently configures sync.
### How It Works
**Projects can exist in three states:**
1. **Cloud-only** - Project exists on cloud, no local copy
2. **Cloud + Local (synced)** - Project has a local working directory that syncs
3. **Local-only** - Project exists locally (when cloud mode is disabled)
**Example:**
```bash
# You have 3 projects on cloud:
# - research: wants local sync at ~/Documents/research
# - work: wants local sync at ~/work-notes
# - temp: cloud-only, no local sync needed
bm project add research --local-path ~/Documents/research
bm project add work --local-path ~/work-notes
bm project add temp # No local sync
# Now you can sync individually (after initial --resync):
bm project bisync --name research
bm project bisync --name work
# temp stays cloud-only
```
**What happens under the covers:**
- Config stores `cloud_projects` dict mapping project names to local paths
- Each project gets its own bisync state in `~/.basic-memory/bisync-state/{project}/`
- Rclone syncs using single remote: `basic-memory-cloud`
- Projects can live anywhere on your filesystem, not forced into sync directory
## Quick Start
### 1. Enable Cloud Mode
Authenticate and enable cloud mode:
```bash
bm cloud login
```
**What this does:**
1. Opens browser to Basic Memory Cloud authentication page
2. Stores authentication token in `~/.basic-memory/auth/token`
3. **Enables cloud mode** - all CLI commands now work against cloud
4. Validates your subscription status
**Result:** All `bm project`, `bm tools` commands now work with cloud.
### 2. Set Up Sync
Install rclone and configure credentials:
```bash
bm cloud setup
```
**What this does:**
1. Installs rclone automatically (if needed)
2. Fetches your tenant information from cloud
3. Generates scoped S3 credentials for sync
4. Configures single rclone remote: `basic-memory-cloud`
**Result:** You're ready to sync projects. No sync directories created yet - those come with project setup.
### 3. Add Projects with Sync
Create projects with optional local sync paths:
```bash
# Create cloud project without local sync
bm project add research
# Create cloud project WITH local sync
bm project add research --local-path ~/Documents/research
# Or configure sync for existing project
bm project sync-setup research ~/Documents/research
```
**What happens under the covers:**
When you add a project with `--local-path`:
1. Project created on cloud at `/app/data/research`
2. Local path stored in config: `cloud_projects.research.local_path = "~/Documents/research"`
3. Local directory created if it doesn't exist
4. Bisync state directory created at `~/.basic-memory/bisync-state/research/`
**Result:** Project is ready to sync, but no files synced yet.
### 4. Sync Your Project
Establish the initial sync baseline. **Best practice:** Always preview with `--dry-run` first:
```bash
# Step 1: Preview the initial sync (recommended)
bm project bisync --name research --resync --dry-run
# Step 2: If all looks good, run the actual sync
bm project bisync --name research --resync
```
**What happens under the covers:**
1. Rclone reads from `~/Documents/research` (local)
2. Connects to `basic-memory-cloud:bucket-name/app/data/research` (remote)
3. Creates bisync state files in `~/.basic-memory/bisync-state/research/`
4. Syncs files bidirectionally with settings:
- `conflict_resolve=newer` (most recent wins)
- `max_delete=25` (safety limit)
- Respects `.bmignore` patterns
**Result:** Local and cloud are in sync. Baseline established.
**Why `--resync`?** This is an rclone requirement for the first bisync run. It establishes the initial state that future syncs will compare against. After the first sync, never use `--resync` unless you need to force a new baseline.
See: https://rclone.org/bisync/#resync
```
--resync
This will effectively make both Path1 and Path2 filesystems contain a matching superset of all files. By default, Path2 files that do not exist in Path1 will be copied to Path1, and the process will then copy the Path1 tree to Path2.
```
### 5. Subsequent Syncs
After the first sync, just run bisync without `--resync`:
```bash
bm project bisync --name research
```
**What happens:**
1. Rclone compares local and cloud states
2. Syncs changes in both directions
3. Auto-resolves conflicts (newer file wins)
4. Updates `last_sync` timestamp in config
**Result:** Changes flow both ways - edit locally or in cloud, both stay in sync.
### 6. Verify Setup
Check status:
```bash
bm cloud status
```
You should see:
- `Mode: Cloud (enabled)`
- `Cloud instance is healthy`
- Instructions for project sync commands
## Working with Projects
### Understanding Project Commands
**Key concept:** When cloud mode is enabled, use regular `bm project` commands (not `bm cloud project`).
```bash
# In cloud mode:
bm project list # Lists cloud projects
bm project add research # Creates cloud project
# In local mode:
bm project list # Lists local projects
bm project add research ~/Documents/research # Creates local project
```
### Creating Projects
**Use case 1: Cloud-only project (no local sync)**
```bash
bm project add temp-notes
```
**What this does:**
- Creates project on cloud at `/app/data/temp-notes`
- No local directory created
- No sync configuration
**Result:** Project exists on cloud, accessible via MCP tools, but no local copy.
**Use case 2: Cloud project with local sync**
```bash
bm project add research --local-path ~/Documents/research
```
**What this does:**
- Creates project on cloud at `/app/data/research`
- Creates local directory `~/Documents/research`
- Stores sync config in `~/.basic-memory/config.json`
- Prepares for bisync (but doesn't sync yet)
**Result:** Project ready to sync. Run `bm project bisync --name research --resync` to establish baseline.
**Use case 3: Add sync to existing cloud project**
```bash
# Project already exists on cloud
bm project sync-setup research ~/Documents/research
```
**What this does:**
- Updates existing project's sync configuration
- Creates local directory
- Prepares for bisync
**Result:** Existing cloud project now has local sync path. Run bisync to pull files down.
### Listing Projects
View all projects:
```bash
bm project list
```
**What you see:**
- All projects in cloud (when cloud mode enabled)
- Default project marked
- Project paths shown
**Future:** Will show sync status (synced/not synced, last sync time).
## File Synchronization
### Understanding the Sync Commands
**There are three sync-related commands:**
1. `bm project sync` - One-way: local → cloud (make cloud match local)
2. `bm project bisync` - Two-way: local ↔ cloud (recommended)
3. `bm project check` - Verify files match (no changes)
### One-Way Sync: Local → Cloud
**Use case:** You made changes locally and want to push to cloud (overwrite cloud).
```bash
bm project sync --name research
```
**What happens:**
1. Reads files from `~/Documents/research` (local)
2. Uses rclone sync to make cloud identical to local
3. Respects `.bmignore` patterns
4. Shows progress bar
**Result:** Cloud now matches local exactly. Any cloud-only changes are overwritten.
**When to use:**
- You know local is the source of truth
- You want to force cloud to match local
- You don't care about cloud changes
### Two-Way Sync: Local ↔ Cloud (Recommended)
**Use case:** You edit files both locally and in cloud UI, want both to stay in sync.
```bash
# First time - establish baseline
bm project bisync --name research --resync
# Subsequent syncs
bm project bisync --name research
```
**What happens:**
1. Compares local and cloud states using bisync metadata
2. Syncs changes in both directions
3. Auto-resolves conflicts (newer file wins)
4. Detects excessive deletes and fails safely (max 25 files)
**Conflict resolution example:**
```bash
# Edit locally
echo "Local change" > ~/Documents/research/notes.md
# Edit same file in cloud UI
# Cloud now has: "Cloud change"
# Run bisync
bm project bisync --name research
# Result: Newer file wins (based on modification time)
# If cloud was more recent, cloud version kept
# If local was more recent, local version kept
```
**When to use:**
- Default workflow for most users
- You edit in multiple places
- You want automatic conflict resolution
### Verify Sync Integrity
**Use case:** Check if local and cloud match without making changes.
```bash
bm project check --name research
```
**What happens:**
1. Compares file checksums between local and cloud
2. Reports differences
3. No files transferred
**Result:** Shows which files differ. Run bisync to sync them.
```bash
# One-way check (faster)
bm project check --name research --one-way
```
### Preview Changes (Dry Run)
**Use case:** See what would change without actually syncing.
```bash
bm project bisync --name research --dry-run
```
**What happens:**
1. Runs bisync logic
2. Shows what would be transferred/deleted
3. No actual changes made
**Result:** Safe preview of sync operations.
### Advanced: List Remote Files
**Use case:** See what files exist on cloud without syncing.
```bash
# List all files in project
bm project ls --name research
# List files in subdirectory
bm project ls --name research --path subfolder
```
**What happens:**
1. Connects to cloud via rclone
2. Lists files in remote project path
3. No files transferred
**Result:** See cloud file listing.
## Multiple Projects
### Syncing Multiple Projects
**Use case:** You have several projects with local sync, want to sync all at once.
```bash
# Setup multiple projects
bm project add research --local-path ~/Documents/research
bm project add work --local-path ~/work-notes
bm project add personal --local-path ~/personal
# Establish baselines
bm project bisync --name research --resync
bm project bisync --name work --resync
bm project bisync --name personal --resync
# Daily workflow: sync everything
bm project bisync --name research
bm project bisync --name work
bm project bisync --name personal
```
**Future:** `--all` flag will sync all configured projects:
```bash
bm project bisync --all # Coming soon
```
### Mixed Usage
**Use case:** Some projects sync, some stay cloud-only.
```bash
# Projects with sync
bm project add research --local-path ~/Documents/research
bm project add work --local-path ~/work
# Cloud-only projects
bm project add archive
bm project add temp-notes
# Sync only the configured ones
bm project bisync --name research
bm project bisync --name work
# Archive and temp-notes stay cloud-only
```
**Result:** Fine-grained control over what syncs.
## Disable Cloud Mode
Return to local mode:
```bash
bm cloud logout
```
**What this does:**
1. Disables cloud mode in config
2. All commands now work locally
3. Auth token remains (can re-enable with login)
**Result:** All `bm` commands work with local projects again.
## Filter Configuration
### Understanding .bmignore
**The problem:** You don't want to sync everything (e.g., `.git`, `node_modules`, database files).
**The solution:** `.bmignore` file with gitignore-style patterns.
**Location:** `~/.basic-memory/.bmignore`
**Default patterns:**
```gitignore
# Version control
.git/**
# Python
__pycache__/**
*.pyc
.venv/**
venv/**
# Node.js
node_modules/**
# Basic Memory internals
memory.db/**
memory.db-shm/**
memory.db-wal/**
config.json/**
watch-status.json/**
.bmignore.rclone/**
# OS files
.DS_Store/**
Thumbs.db/**
# Environment files
.env/**
.env.local/**
```
**How it works:**
1. On first sync, `.bmignore` created with defaults
2. Patterns converted to rclone filter format (`.bmignore.rclone`)
3. Rclone uses filters during sync
4. Same patterns used by all projects
**Customizing:**
```bash
# Edit patterns
code ~/.basic-memory/.bmignore
# Add custom patterns
echo "*.tmp/**" >> ~/.basic-memory/.bmignore
# Next sync uses updated patterns
bm project bisync --name research
```
## Troubleshooting
### Authentication Issues
**Problem:** "Authentication failed" or "Invalid token"
**Solution:** Re-authenticate:
```bash
bm cloud logout
bm cloud login
```
### Subscription Issues
**Problem:** "Subscription Required" error
**Solution:**
1. Visit subscribe URL shown in error
2. Sign up for subscription
3. Run `bm cloud login` again
**Note:** Access is immediate when subscription becomes active.
### Bisync Initialization
**Problem:** "First bisync requires --resync"
**Explanation:** Bisync needs a baseline state before it can sync changes.
**Solution:**
```bash
bm project bisync --name research --resync
```
**What this does:**
- Establishes initial sync state
- Creates baseline in `~/.basic-memory/bisync-state/research/`
- Syncs all files bidirectionally
**Result:** Future syncs work without `--resync`.
### Empty Directory Issues
**Problem:** "Empty prior Path1 listing. Cannot sync to an empty directory"
**Explanation:** Rclone bisync doesn't work well with completely empty directories. It needs at least one file to establish a baseline.
**Solution:** Add at least one file before running `--resync`:
```bash
# Create a placeholder file
echo "# Research Notes" > ~/Documents/research/README.md
# Now run bisync
bm project bisync --name research --resync
```
**Why this happens:** Bisync creates listing files that track the state of each side. When both directories are completely empty, these listing files are considered invalid by rclone.
**Best practice:** Always have at least one file (like a README.md) in your project directory before setting up sync.
### Bisync State Corruption
**Problem:** Bisync fails with errors about corrupted state or listing files
**Explanation:** Sometimes bisync state can become inconsistent (e.g., after mixing dry-run and actual runs, or after manual file operations).
**Solution:** Clear bisync state and re-establish baseline:
```bash
# Clear bisync state
bm project bisync-reset research
# Re-establish baseline
bm project bisync --name research --resync
```
**What this does:**
- Removes all bisync metadata from `~/.basic-memory/bisync-state/research/`
- Forces fresh baseline on next `--resync`
- Safe operation (doesn't touch your files)
**Note:** This command also runs automatically when you remove a project to clean up state directories.
### Too Many Deletes
**Problem:** "Error: max delete limit (25) exceeded"
**Explanation:** Bisync detected you're about to delete more than 25 files. This is a safety check to prevent accidents.
**Solution 1:** Review what you're deleting, then force resync:
```bash
# Check what would be deleted
bm project bisync --name research --dry-run
# If correct, establish new baseline
bm project bisync --name research --resync
```
**Solution 2:** Use one-way sync if you know local is correct:
```bash
bm project sync --name research
```
### Project Not Configured for Sync
**Problem:** "Project research has no local_sync_path configured"
**Explanation:** Project exists on cloud but has no local sync path.
**Solution:**
```bash
bm project sync-setup research ~/Documents/research
bm project bisync --name research --resync
```
### Connection Issues
**Problem:** "Cannot connect to cloud instance"
**Solution:** Check status:
```bash
bm cloud status
```
If instance is down, wait a few minutes and retry.
## Security
- **Authentication**: OAuth 2.1 with PKCE flow
- **Tokens**: Stored securely in `~/.basic-memory/basic-memory-cloud.json`
- **Transport**: All data encrypted in transit (HTTPS)
- **Credentials**: Scoped S3 credentials (read-write to your tenant only)
- **Isolation**: Your data isolated from other tenants
- **Ignore patterns**: Sensitive files automatically excluded via `.bmignore`
## Command Reference
### Cloud Mode Management
```bash
bm cloud login # Authenticate and enable cloud mode
bm cloud logout # Disable cloud mode
bm cloud status # Check cloud mode and instance health
```
### Setup
```bash
bm cloud setup # Install rclone and configure credentials
```
### Project Management
When cloud mode is enabled:
```bash
bm project list # List cloud projects
bm project add <name> # Create cloud project (no sync)
bm project add <name> --local-path <path> # Create with local sync
bm project sync-setup <name> <path> # Add sync to existing project
bm project rm <name> # Delete project
```
### File Synchronization
```bash
# One-way sync (local → cloud)
bm project sync --name <project>
bm project sync --name <project> --dry-run
bm project sync --name <project> --verbose
# Two-way sync (local ↔ cloud) - Recommended
bm project bisync --name <project> # After first --resync
bm project bisync --name <project> --resync # First time / force baseline
bm project bisync --name <project> --dry-run
bm project bisync --name <project> --verbose
# Integrity check
bm project check --name <project>
bm project check --name <project> --one-way
# List remote files
bm project ls --name <project>
bm project ls --name <project> --path <subpath>
```
## Summary
**Basic Memory Cloud uses project-scoped sync:**
1. **Enable cloud mode** - `bm cloud login`
2. **Install rclone** - `bm cloud setup`
3. **Add projects with sync** - `bm project add research --local-path ~/Documents/research`
4. **Preview first sync** - `bm project bisync --name research --resync --dry-run`
5. **Establish baseline** - `bm project bisync --name research --resync`
6. **Daily workflow** - `bm project bisync --name research`
**Key benefits:**
- ✅ Each project independently syncs (or doesn't)
- ✅ Projects can live anywhere on disk
- ✅ Explicit sync operations (no magic)
- ✅ Safe by design (max delete limits, conflict resolution)
- ✅ Full offline access (work locally, sync when ready)
**Future enhancements:**
- `--all` flag to sync all configured projects
- Project list showing sync status
- Watch mode for automatic sync
+102 -15
View File
@@ -2,28 +2,114 @@
# Install dependencies
install:
pip install -e ".[dev]"
uv pip install -e ".[dev]"
uv sync
@echo ""
@echo "💡 Remember to activate the virtual environment by running: source .venv/bin/activate"
# Run unit tests in parallel
test-unit:
uv run pytest -p pytest_mock -v -n auto
# ==============================================================================
# DATABASE BACKEND TESTING
# ==============================================================================
# Basic Memory supports dual database backends (SQLite and Postgres).
# By default, tests run against SQLite (fast, no dependencies).
# Set BASIC_MEMORY_TEST_POSTGRES=1 to run against Postgres (uses testcontainers).
#
# Quick Start:
# just test # Run all tests against SQLite (default)
# just test-sqlite # Run all tests against SQLite
# just test-postgres # Run all tests against Postgres (testcontainers)
# just test-unit-sqlite # Run unit tests against SQLite
# just test-unit-postgres # Run unit tests against Postgres
# just test-int-sqlite # Run integration tests against SQLite
# just test-int-postgres # Run integration tests against Postgres
#
# CI runs both in parallel for faster feedback.
# ==============================================================================
# Run integration tests in parallel
test-int:
uv run pytest -p pytest_mock -v --no-cov -n auto test-int
# Run all tests against SQLite and Postgres
test: test-sqlite test-postgres
# Run all tests
test: test-unit test-int
# Run all tests against SQLite
test-sqlite: test-unit-sqlite test-int-sqlite
# Run all tests against Postgres (uses testcontainers)
test-postgres: test-unit-postgres test-int-postgres
# Run unit tests against SQLite
test-unit-sqlite:
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov tests
# Run unit tests against Postgres
test-unit-postgres:
BASIC_MEMORY_ENV=test BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov tests
# Run integration tests against SQLite
test-int-sqlite:
uv run pytest -p pytest_mock -v --no-cov test-int
# Run integration tests against Postgres
# Note: Uses timeout due to FastMCP Client + asyncpg cleanup hang (tests pass, process hangs on exit)
# See: https://github.com/jlowin/fastmcp/issues/1311
test-int-postgres:
#!/usr/bin/env bash
set -euo pipefail
# Use gtimeout (macOS/Homebrew) or timeout (Linux)
TIMEOUT_CMD=$(command -v gtimeout || command -v timeout || echo "")
if [[ -n "$TIMEOUT_CMD" ]]; then
$TIMEOUT_CMD --signal=KILL 600 bash -c 'BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov test-int' || test $? -eq 137
else
echo "⚠️ No timeout command found, running without timeout..."
BASIC_MEMORY_TEST_POSTGRES=1 uv run pytest -p pytest_mock -v --no-cov test-int
fi
# Reset Postgres test database (drops and recreates schema)
# Useful when Alembic migration state gets out of sync during development
# Uses credentials from docker-compose-postgres.yml
postgres-reset:
docker exec basic-memory-postgres psql -U ${POSTGRES_USER:-basic_memory_user} -d ${POSTGRES_TEST_DB:-basic_memory_test} -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
@echo "✅ Postgres test database reset"
# Run Alembic migrations manually against Postgres test database
# Useful for debugging migration issues
# Uses credentials from docker-compose-postgres.yml (can override with env vars)
postgres-migrate:
@cd src/basic_memory/alembic && \
BASIC_MEMORY_DATABASE_BACKEND=postgres \
BASIC_MEMORY_DATABASE_URL=${POSTGRES_TEST_URL:-postgresql+asyncpg://basic_memory_user:dev_password@localhost:5433/basic_memory_test} \
uv run alembic upgrade head
@echo "✅ Migrations applied to Postgres test database"
# Run Windows-specific tests only (only works on Windows platform)
# These tests verify Windows-specific database optimizations (locking mode, NullPool)
# Will be skipped automatically on non-Windows platforms
test-windows:
uv run pytest -p pytest_mock -v --no-cov -m windows tests test-int
# Run benchmark tests only (performance testing)
# These are slow tests that measure sync performance with various file counts
# Excluded from default test runs to keep CI fast
test-benchmark:
uv run pytest -p pytest_mock -v --no-cov -m benchmark tests test-int
# Run all tests including Windows, Postgres, and Benchmarks (for CI/comprehensive testing)
# Use this before releasing to ensure everything works across all backends and platforms
test-all:
uv run pytest -p pytest_mock -v --no-cov tests test-int
# Generate HTML coverage report
coverage:
uv run pytest -p pytest_mock -v -n auto tests test-int --cov-report=html
@echo "Coverage report generated in htmlcov/index.html"
# Lint and fix code (calls fix)
lint: fix
# Lint and fix code
lint:
uv run ruff check . --fix
fix:
uv run ruff check --fix --unsafe-fixes src tests test-int
# Type check code
type-check:
typecheck:
uv run pyright
# Clean build artifacts and cache files
@@ -55,7 +141,7 @@ update-deps:
uv sync --upgrade
# Run all code quality checks and tests
check: lint format type-check test
check: lint format typecheck test
# Generate Alembic migration with descriptive message
migration message:
@@ -96,8 +182,9 @@ release version:
fi
# Run quality checks
echo "🔍 Running quality checks..."
just check
echo "🔍 Running lint checks..."
just lint
just typecheck
# Update version in __init__.py
echo "📝 Updating version in __init__.py..."
-31
View File
@@ -1,31 +0,0 @@
file_path,file_name,category,size_bytes,modified_date,primary_author,all_contributors,sha256_hash
.github/workflows/release.yml,release.yml,configuration,2479,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez,163307a99e2f3d317af6ae8927e1537654d88bf574a6958ac0bcd2d4de266b2a
CLAUDE.md,CLAUDE.md,legal,12310,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez; bm-claudeai (AI Assistant); Ikko Eltociear Ashimine,751f10c1a9a9c0ba2054d9c9cd36cf0989d6a347491fd505bb6504ee74b12900
README.md,README.md,documentation,16261,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez; Jason Noble; Marc Baiza; Matias Forbord; bm-claudeai (AI Assistant),2c4b82b30f49ab465617034fda999157450ed168f8e097fe8e9ca7ac65a197a7
justfile,justfile,other,5496,2025-07-30,jope-bm,jope-bm; Paul Hernandez; Drew Cain,a8df6c73ee8cf3eaa48e4cba193dffbc2821b843a8cc0c0cd58b8517f2252907
pyproject.toml,pyproject.toml,configuration,3279,2025-07-30,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant); semantic-release,428a5d64d4555dc7cd2a81b89a3444bdb1d2f3017257fbc9160bd0ae982718e1
src/basic_memory/__init__.py,__init__.py,source_code,256,2025-07-06,Paul Hernandez,Paul Hernandez; Drew Cain; semantic-release,c946490ab9d64957d25df37f721392024916f8a81ed838f70a8e938b1c2637db
src/basic_memory/alembic/env.py,env.py,source_code,2838,2025-07-08,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant),e241d987eadfcd5011cbd9ddbecb833d306de87937759d81c08d37d04a6906b0
src/basic_memory/api/routers/utils.py,utils.py,source_code,5159,2025-07-03,Drew Cain,Drew Cain; bm-claudeai (AI Assistant),9e60f57da24e1dc9d64236c26a7a2353003dc618a77fbeb853c494aa3357a57c
src/basic_memory/cli/commands/mcp.py,mcp.py,source_code,2593,2025-07-08,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant),3e1be3d27b18ef4743204b7cf8a37fbc2a1a91fc1636c468e08cefcd8597bac6
src/basic_memory/config.py,config.py,source_code,12487,2025-07-30,jope-bm,jope-bm; Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant); github-actions[bot] (AI Assistant),86341330a041b94d38f5e4f1b94f3509d74351c67cc826af1a4ff57313e5f726
src/basic_memory/db.py,db.py,source_code,7428,2025-07-08,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant),25e053b0b3f9e8fa823b05fc099001efe8a3a4f6e0f80728e5079ba45aa406d8
src/basic_memory/mcp/server.py,server.py,source_code,1248,2025-07-08,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant),0a910cae27d61ea923e90fbe2d4b5c7cacf3b29d3e156f0b7f77872ab973c761
src/basic_memory/mcp/tools/__init__.py,__init__.py,source_code,1619,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez,872b771dd52ec3b763645a2baf6e10a70f049ce3106830a6d3f053b3e09a5fe6
src/basic_memory/mcp/tools/project_management.py,project_management.py,source_code,12944,2025-07-06,Drew Cain,Drew Cain; Paul Hernandez,cdac736d65129f689a85c6b82f8e043bc84a31657ead7a8331771171eeaa860f
src/basic_memory/mcp/tools/search.py,search.py,source_code,15883,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez; bm-claudeai (AI Assistant); github-actions[bot] (AI Assistant),8519b0057468c4450eb543a2f561bcd0d7cb96e1ce1b95e948e02b309ba6b7ca
src/basic_memory/repository/entity_repository.py,entity_repository.py,source_code,10405,2025-07-03,Drew Cain,Drew Cain; bm-claudeai (AI Assistant); Paul Hernandez,e2a8d1eba6c8d64bc61d7168df0fe8c29a670858bf9daeac464a0f440273fae0
src/basic_memory/schemas/memory.py,memory.py,source_code,5833,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez; bm-claudeai (AI Assistant),acb4a953a553feca672c4895798a7d948ec51f922f75b66dd7d55716e3bebed3
src/basic_memory/services/entity_service.py,entity_service.py,source_code,30440,2025-07-03,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant); github-actions[bot] (AI Assistant),7cd5163eca6b8a0772e838c8c869e478166763cd6a2d745b0b9aa5c25a65baee
src/basic_memory/services/initialization.py,initialization.py,source_code,6715,2025-07-08,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant),4dde58b799cf4ac90631e59949b55b3355a93bdf99df0d9c2408c0cea67e10c4
src/basic_memory/templates/prompts/continue_conversation.hbs,continue_conversation.hbs,templates,3076,2025-07-01,Drew Cain,Drew Cain; bm-claudeai (AI Assistant),b6bac31d25c0e52d0909b2273285092f4e31bc219107f92ed511cd407b65e992
src/basic_memory/utils.py,utils.py,source_code,8654,2025-07-30,jope-bm,jope-bm; Paul Hernandez; Drew Cain; andyxinweiminicloud; github-actions[bot] (AI Assistant),111f343e3367339730e081c08acf4613667be574f946126513b5d82da3086bed
tests/mcp/test_tool_edit_note.py,test_tool_edit_note.py,source_code,13503,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez,dd441969aa54b3739f2d9202d3feaf46aee807ec51e01a13ef41eda52743944f
tests/mcp/test_tool_search.py,test_tool_search.py,source_code,10959,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez; github-actions[bot] (AI Assistant),07839cf8e89d74ba05ec1dc55fa842444c1f0cc3d1587823b0ac3458ad89e87c
tests/mcp/test_tool_write_note.py,test_tool_write_note.py,source_code,35136,2025-07-30,jope-bm,jope-bm; Drew Cain; Paul Hernandez,45172de20cfc4f4436287573b4f5a94b8b616a360647af67ba1c6c343ccd41b8
tests/repository/test_entity_repository_upsert.py,test_entity_repository_upsert.py,source_code,16983,2025-07-03,Paul Hernandez,Paul Hernandez; Drew Cain,ee0ecd6f4ff8b31838bd6de6fb1018af437e02813d8315c06f4e20a6b78a0572
tests/schemas/test_schemas.py,test_schemas.py,source_code,16880,2025-07-03,Drew Cain,Drew Cain; Paul Hernandez; bm-claudeai (AI Assistant),2c746a00c340f063c160ace67569955a300a294040949695c79fd4ae124fb611
tests/services/test_entity_service.py,test_entity_service.py,source_code,59278,2025-07-03,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant),d7d495756faa10b415d705cf26b69d2efbc5f190a834b6767386ff296629ea71
tests/test_config.py,test_config.py,source_code,3680,2025-07-03,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant),6a246431cff739274efda895dfa28f38710289a73e645312f5fa05fce0885ec8
tests/test_db_migration_deduplication.py,test_db_migration_deduplication.py,source_code,6166,2025-07-08,Paul Hernandez,Paul Hernandez; Drew Cain,ab768e061531ca2edcd598c101b5d884e549f90c7a4b305a23713a199d263fbb
uv.lock,uv.lock,other,232710,2025-07-30,Paul Hernandez,Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant),caa1e3f6e66464b884a78cab77df2fe303fa3c0dc573be0196474b6d3c150581
1 file_path file_name category size_bytes modified_date primary_author all_contributors sha256_hash
2 .github/workflows/release.yml release.yml configuration 2479 2025-07-03 Drew Cain Drew Cain; Paul Hernandez 163307a99e2f3d317af6ae8927e1537654d88bf574a6958ac0bcd2d4de266b2a
3 CLAUDE.md CLAUDE.md legal 12310 2025-07-03 Drew Cain Drew Cain; Paul Hernandez; bm-claudeai (AI Assistant); Ikko Eltociear Ashimine 751f10c1a9a9c0ba2054d9c9cd36cf0989d6a347491fd505bb6504ee74b12900
4 README.md README.md documentation 16261 2025-07-03 Drew Cain Drew Cain; Paul Hernandez; Jason Noble; Marc Baiza; Matias Forbord; bm-claudeai (AI Assistant) 2c4b82b30f49ab465617034fda999157450ed168f8e097fe8e9ca7ac65a197a7
5 justfile justfile other 5496 2025-07-30 jope-bm jope-bm; Paul Hernandez; Drew Cain a8df6c73ee8cf3eaa48e4cba193dffbc2821b843a8cc0c0cd58b8517f2252907
6 pyproject.toml pyproject.toml configuration 3279 2025-07-30 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant); semantic-release 428a5d64d4555dc7cd2a81b89a3444bdb1d2f3017257fbc9160bd0ae982718e1
7 src/basic_memory/__init__.py __init__.py source_code 256 2025-07-06 Paul Hernandez Paul Hernandez; Drew Cain; semantic-release c946490ab9d64957d25df37f721392024916f8a81ed838f70a8e938b1c2637db
8 src/basic_memory/alembic/env.py env.py source_code 2838 2025-07-08 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant) e241d987eadfcd5011cbd9ddbecb833d306de87937759d81c08d37d04a6906b0
9 src/basic_memory/api/routers/utils.py utils.py source_code 5159 2025-07-03 Drew Cain Drew Cain; bm-claudeai (AI Assistant) 9e60f57da24e1dc9d64236c26a7a2353003dc618a77fbeb853c494aa3357a57c
10 src/basic_memory/cli/commands/mcp.py mcp.py source_code 2593 2025-07-08 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant) 3e1be3d27b18ef4743204b7cf8a37fbc2a1a91fc1636c468e08cefcd8597bac6
11 src/basic_memory/config.py config.py source_code 12487 2025-07-30 jope-bm jope-bm; Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant); github-actions[bot] (AI Assistant) 86341330a041b94d38f5e4f1b94f3509d74351c67cc826af1a4ff57313e5f726
12 src/basic_memory/db.py db.py source_code 7428 2025-07-08 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant) 25e053b0b3f9e8fa823b05fc099001efe8a3a4f6e0f80728e5079ba45aa406d8
13 src/basic_memory/mcp/server.py server.py source_code 1248 2025-07-08 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant) 0a910cae27d61ea923e90fbe2d4b5c7cacf3b29d3e156f0b7f77872ab973c761
14 src/basic_memory/mcp/tools/__init__.py __init__.py source_code 1619 2025-07-03 Drew Cain Drew Cain; Paul Hernandez 872b771dd52ec3b763645a2baf6e10a70f049ce3106830a6d3f053b3e09a5fe6
15 src/basic_memory/mcp/tools/project_management.py project_management.py source_code 12944 2025-07-06 Drew Cain Drew Cain; Paul Hernandez cdac736d65129f689a85c6b82f8e043bc84a31657ead7a8331771171eeaa860f
16 src/basic_memory/mcp/tools/search.py search.py source_code 15883 2025-07-03 Drew Cain Drew Cain; Paul Hernandez; bm-claudeai (AI Assistant); github-actions[bot] (AI Assistant) 8519b0057468c4450eb543a2f561bcd0d7cb96e1ce1b95e948e02b309ba6b7ca
17 src/basic_memory/repository/entity_repository.py entity_repository.py source_code 10405 2025-07-03 Drew Cain Drew Cain; bm-claudeai (AI Assistant); Paul Hernandez e2a8d1eba6c8d64bc61d7168df0fe8c29a670858bf9daeac464a0f440273fae0
18 src/basic_memory/schemas/memory.py memory.py source_code 5833 2025-07-03 Drew Cain Drew Cain; Paul Hernandez; bm-claudeai (AI Assistant) acb4a953a553feca672c4895798a7d948ec51f922f75b66dd7d55716e3bebed3
19 src/basic_memory/services/entity_service.py entity_service.py source_code 30440 2025-07-03 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant); github-actions[bot] (AI Assistant) 7cd5163eca6b8a0772e838c8c869e478166763cd6a2d745b0b9aa5c25a65baee
20 src/basic_memory/services/initialization.py initialization.py source_code 6715 2025-07-08 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant) 4dde58b799cf4ac90631e59949b55b3355a93bdf99df0d9c2408c0cea67e10c4
21 src/basic_memory/templates/prompts/continue_conversation.hbs continue_conversation.hbs templates 3076 2025-07-01 Drew Cain Drew Cain; bm-claudeai (AI Assistant) b6bac31d25c0e52d0909b2273285092f4e31bc219107f92ed511cd407b65e992
22 src/basic_memory/utils.py utils.py source_code 8654 2025-07-30 jope-bm jope-bm; Paul Hernandez; Drew Cain; andyxinweiminicloud; github-actions[bot] (AI Assistant) 111f343e3367339730e081c08acf4613667be574f946126513b5d82da3086bed
23 tests/mcp/test_tool_edit_note.py test_tool_edit_note.py source_code 13503 2025-07-03 Drew Cain Drew Cain; Paul Hernandez dd441969aa54b3739f2d9202d3feaf46aee807ec51e01a13ef41eda52743944f
24 tests/mcp/test_tool_search.py test_tool_search.py source_code 10959 2025-07-03 Drew Cain Drew Cain; Paul Hernandez; github-actions[bot] (AI Assistant) 07839cf8e89d74ba05ec1dc55fa842444c1f0cc3d1587823b0ac3458ad89e87c
25 tests/mcp/test_tool_write_note.py test_tool_write_note.py source_code 35136 2025-07-30 jope-bm jope-bm; Drew Cain; Paul Hernandez 45172de20cfc4f4436287573b4f5a94b8b616a360647af67ba1c6c343ccd41b8
26 tests/repository/test_entity_repository_upsert.py test_entity_repository_upsert.py source_code 16983 2025-07-03 Paul Hernandez Paul Hernandez; Drew Cain ee0ecd6f4ff8b31838bd6de6fb1018af437e02813d8315c06f4e20a6b78a0572
27 tests/schemas/test_schemas.py test_schemas.py source_code 16880 2025-07-03 Drew Cain Drew Cain; Paul Hernandez; bm-claudeai (AI Assistant) 2c746a00c340f063c160ace67569955a300a294040949695c79fd4ae124fb611
28 tests/services/test_entity_service.py test_entity_service.py source_code 59278 2025-07-03 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant) d7d495756faa10b415d705cf26b69d2efbc5f190a834b6767386ff296629ea71
29 tests/test_config.py test_config.py source_code 3680 2025-07-03 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant) 6a246431cff739274efda895dfa28f38710289a73e645312f5fa05fce0885ec8
30 tests/test_db_migration_deduplication.py test_db_migration_deduplication.py source_code 6166 2025-07-08 Paul Hernandez Paul Hernandez; Drew Cain ab768e061531ca2edcd598c101b5d884e549f90c7a4b305a23713a199d263fbb
31 uv.lock uv.lock other 232710 2025-07-30 Paul Hernandez Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant) caa1e3f6e66464b884a78cab77df2fe303fa3c0dc573be0196474b6d3c150581
-28
View File
@@ -1,28 +0,0 @@
file_path,file_name,category,size_bytes,modified_date,primary_author,all_contributors,sha256_hash
CONTRIBUTING.md,CONTRIBUTING.md,documentation,7329,2025-07-30,jope-bm,jope-bm; Paul Hernandez,ca822fe39cc529bfc3822d317ab8a20fd141ec3ecb8c20de9e2eca3edce1c282
justfile,justfile,other,5496,2025-07-30,jope-bm,jope-bm; Paul Hernandez; Drew Cain,a8df6c73ee8cf3eaa48e4cba193dffbc2821b843a8cc0c0cd58b8517f2252907
src/basic_memory/api/routers/project_router.py,project_router.py,source_code,8444,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),6539c69f1d506c65d14cc2c151876b19057f37d84d51a9e6f464ed47a4692648
src/basic_memory/cli/commands/import_memory_json.py,import_memory_json.py,source_code,3013,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),7ea193f6720b2672290ad16c79a143a31d88a1d567838f12b9ebf9110a7c0338
src/basic_memory/cli/commands/project.py,project.py,source_code,13836,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),af3e6f30410ab48355f4b1a46aaa3aa5423cb0a0490f51447db1d25e09f39916
src/basic_memory/config.py,config.py,source_code,12487,2025-07-30,jope-bm,jope-bm; Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant); github-actions[bot] (AI Assistant),86341330a041b94d38f5e4f1b94f3509d74351c67cc826af1a4ff57313e5f726
src/basic_memory/importers/memory_json_importer.py,memory_json_importer.py,source_code,4631,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),bc7d045299f17ed9ad5cebff7b1423250ec28a11130e47a0ac48d3ab82bdeafc
src/basic_memory/mcp/async_client.py,async_client.py,source_code,925,2025-07-30,jope-bm,jope-bm; Paul Hernandez,6531f4387f30a30a322197ff51d6eacae4b25a58c648095931b3d1145d1da303
src/basic_memory/mcp/tools/move_note.py,move_note.py,source_code,17541,2025-07-30,jope-bm,jope-bm; Paul Hernandez,5ef27d9baaebc2c5656fc49c4798a4e4e40a70b6cd9d1e761dbcae021bad558c
src/basic_memory/mcp/tools/read_content.py,read_content.py,source_code,9125,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),96a13addac507fd874394dfa2e1e5ed71af5eb89d0872c1d68cacf9cac3f8724
src/basic_memory/mcp/tools/read_note.py,read_note.py,source_code,8132,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant); Amadeusz Wieczorek; github-actions[bot] (AI Assistant),dff53ce02b16d9c5fca315b3f09bb70867a89d614b0ef3f0548dce87978048ee
src/basic_memory/mcp/tools/write_note.py,write_note.py,source_code,6612,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),771396fb49d397feb5f7e36321d4d233c22c4d1b53414912c849957c6e96fc83
src/basic_memory/repository/project_repository.py,project_repository.py,source_code,3803,2025-07-30,jope-bm,jope-bm; bm-claudeai (AI Assistant),9083ba214b2bcaaf98164d9c33cda7add52bc77f7269ecb6e43cdd5088728b7c
src/basic_memory/schemas/importer.py,importer.py,source_code,693,2025-07-30,jope-bm,jope-bm; bm-claudeai (AI Assistant),ac33df423ca32b28ce7b6ea9c29d541f8783a86c0c29f78db35163bf93f139cd
src/basic_memory/services/project_service.py,project_service.py,source_code,30125,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),bce95c00aa9a1da0a8c8090ee34b5b1494207f8c99e6c25de4ec43fddc38f6be
src/basic_memory/utils.py,utils.py,source_code,8654,2025-07-30,jope-bm,jope-bm; Paul Hernandez; Drew Cain; andyxinweiminicloud; github-actions[bot] (AI Assistant),111f343e3367339730e081c08acf4613667be574f946126513b5d82da3086bed
tests/api/test_async_client.py,test_async_client.py,source_code,1382,2025-07-30,jope-bm,jope-bm; Paul Hernandez,a2f96226a75e5cff2326ca48ddccea4915964d83e529f7b99958bc233ccdc31a
tests/api/test_project_router.py,test_project_router.py,source_code,13288,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),af85d0870069d3831a9214372853994e5d12dc910e37298eb9958f72f6a8bb31
tests/cli/test_import_memory_json.py,test_import_memory_json.py,source_code,4891,2025-07-30,jope-bm,jope-bm; bm-claudeai (AI Assistant); Paul Hernandez,d6734ce059dacf97bc764d848b2e2a6ba52fa96871bc331ec9d2ddc60b766bb6
tests/cli/test_project_commands.py,test_project_commands.py,source_code,6578,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),51e2ac7ff44b0d1a1db6da406c5c2e6aebceb1b63a1e8d5ab51bbaa834507157
tests/mcp/test_tool_move_note.py,test_tool_move_note.py,source_code,25851,2025-07-30,jope-bm,jope-bm; Paul Hernandez,c9f95158503fa42e37532aaa35027538fa49cea718d059d570cf547d31f01043
tests/mcp/test_tool_read_content.py,test_tool_read_content.py,source_code,19442,2025-07-30,jope-bm,jope-bm,1817e32f997b06dbdabf02864d0c70584c1cd80bf1fae8dfbef649de79434bc5
tests/mcp/test_tool_read_note.py,test_tool_read_note.py,source_code,22991,2025-07-30,jope-bm,jope-bm; Paul Hernandez; Amadeusz Wieczorek; github-actions[bot] (AI Assistant),4e13ea9ff977525aa62b6a8c22505ffe989329dc3e7d07b71e044cf43ef3ddfd
tests/mcp/test_tool_write_note.py,test_tool_write_note.py,source_code,35136,2025-07-30,jope-bm,jope-bm; Drew Cain; Paul Hernandez,45172de20cfc4f4436287573b4f5a94b8b616a360647af67ba1c6c343ccd41b8
tests/repository/test_project_repository.py,test_project_repository.py,source_code,10511,2025-07-30,jope-bm,jope-bm; bm-claudeai (AI Assistant),279a3324635b1b515c18003bd1875cf230fef1192cadef9ca426846783717e75
tests/services/test_project_service.py,test_project_service.py,source_code,28152,2025-07-30,jope-bm,jope-bm; Paul Hernandez; bm-claudeai (AI Assistant),430868ed796518937a3b0bed4d8c33be53264a6100d61d54b0745bb2b58bf29a
tests/utils/test_validate_project_path.py,test_validate_project_path.py,source_code,15346,2025-07-30,jope-bm,jope-bm,27a4bf2a9d277e6cd773619e5241b20175d211ec60c284361bec404fd99143f3
1 file_path file_name category size_bytes modified_date primary_author all_contributors sha256_hash
2 CONTRIBUTING.md CONTRIBUTING.md documentation 7329 2025-07-30 jope-bm jope-bm; Paul Hernandez ca822fe39cc529bfc3822d317ab8a20fd141ec3ecb8c20de9e2eca3edce1c282
3 justfile justfile other 5496 2025-07-30 jope-bm jope-bm; Paul Hernandez; Drew Cain a8df6c73ee8cf3eaa48e4cba193dffbc2821b843a8cc0c0cd58b8517f2252907
4 src/basic_memory/api/routers/project_router.py project_router.py source_code 8444 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) 6539c69f1d506c65d14cc2c151876b19057f37d84d51a9e6f464ed47a4692648
5 src/basic_memory/cli/commands/import_memory_json.py import_memory_json.py source_code 3013 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) 7ea193f6720b2672290ad16c79a143a31d88a1d567838f12b9ebf9110a7c0338
6 src/basic_memory/cli/commands/project.py project.py source_code 13836 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) af3e6f30410ab48355f4b1a46aaa3aa5423cb0a0490f51447db1d25e09f39916
7 src/basic_memory/config.py config.py source_code 12487 2025-07-30 jope-bm jope-bm; Paul Hernandez; Drew Cain; bm-claudeai (AI Assistant); github-actions[bot] (AI Assistant) 86341330a041b94d38f5e4f1b94f3509d74351c67cc826af1a4ff57313e5f726
8 src/basic_memory/importers/memory_json_importer.py memory_json_importer.py source_code 4631 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) bc7d045299f17ed9ad5cebff7b1423250ec28a11130e47a0ac48d3ab82bdeafc
9 src/basic_memory/mcp/async_client.py async_client.py source_code 925 2025-07-30 jope-bm jope-bm; Paul Hernandez 6531f4387f30a30a322197ff51d6eacae4b25a58c648095931b3d1145d1da303
10 src/basic_memory/mcp/tools/move_note.py move_note.py source_code 17541 2025-07-30 jope-bm jope-bm; Paul Hernandez 5ef27d9baaebc2c5656fc49c4798a4e4e40a70b6cd9d1e761dbcae021bad558c
11 src/basic_memory/mcp/tools/read_content.py read_content.py source_code 9125 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) 96a13addac507fd874394dfa2e1e5ed71af5eb89d0872c1d68cacf9cac3f8724
12 src/basic_memory/mcp/tools/read_note.py read_note.py source_code 8132 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant); Amadeusz Wieczorek; github-actions[bot] (AI Assistant) dff53ce02b16d9c5fca315b3f09bb70867a89d614b0ef3f0548dce87978048ee
13 src/basic_memory/mcp/tools/write_note.py write_note.py source_code 6612 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) 771396fb49d397feb5f7e36321d4d233c22c4d1b53414912c849957c6e96fc83
14 src/basic_memory/repository/project_repository.py project_repository.py source_code 3803 2025-07-30 jope-bm jope-bm; bm-claudeai (AI Assistant) 9083ba214b2bcaaf98164d9c33cda7add52bc77f7269ecb6e43cdd5088728b7c
15 src/basic_memory/schemas/importer.py importer.py source_code 693 2025-07-30 jope-bm jope-bm; bm-claudeai (AI Assistant) ac33df423ca32b28ce7b6ea9c29d541f8783a86c0c29f78db35163bf93f139cd
16 src/basic_memory/services/project_service.py project_service.py source_code 30125 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) bce95c00aa9a1da0a8c8090ee34b5b1494207f8c99e6c25de4ec43fddc38f6be
17 src/basic_memory/utils.py utils.py source_code 8654 2025-07-30 jope-bm jope-bm; Paul Hernandez; Drew Cain; andyxinweiminicloud; github-actions[bot] (AI Assistant) 111f343e3367339730e081c08acf4613667be574f946126513b5d82da3086bed
18 tests/api/test_async_client.py test_async_client.py source_code 1382 2025-07-30 jope-bm jope-bm; Paul Hernandez a2f96226a75e5cff2326ca48ddccea4915964d83e529f7b99958bc233ccdc31a
19 tests/api/test_project_router.py test_project_router.py source_code 13288 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) af85d0870069d3831a9214372853994e5d12dc910e37298eb9958f72f6a8bb31
20 tests/cli/test_import_memory_json.py test_import_memory_json.py source_code 4891 2025-07-30 jope-bm jope-bm; bm-claudeai (AI Assistant); Paul Hernandez d6734ce059dacf97bc764d848b2e2a6ba52fa96871bc331ec9d2ddc60b766bb6
21 tests/cli/test_project_commands.py test_project_commands.py source_code 6578 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) 51e2ac7ff44b0d1a1db6da406c5c2e6aebceb1b63a1e8d5ab51bbaa834507157
22 tests/mcp/test_tool_move_note.py test_tool_move_note.py source_code 25851 2025-07-30 jope-bm jope-bm; Paul Hernandez c9f95158503fa42e37532aaa35027538fa49cea718d059d570cf547d31f01043
23 tests/mcp/test_tool_read_content.py test_tool_read_content.py source_code 19442 2025-07-30 jope-bm jope-bm 1817e32f997b06dbdabf02864d0c70584c1cd80bf1fae8dfbef649de79434bc5
24 tests/mcp/test_tool_read_note.py test_tool_read_note.py source_code 22991 2025-07-30 jope-bm jope-bm; Paul Hernandez; Amadeusz Wieczorek; github-actions[bot] (AI Assistant) 4e13ea9ff977525aa62b6a8c22505ffe989329dc3e7d07b71e044cf43ef3ddfd
25 tests/mcp/test_tool_write_note.py test_tool_write_note.py source_code 35136 2025-07-30 jope-bm jope-bm; Drew Cain; Paul Hernandez 45172de20cfc4f4436287573b4f5a94b8b616a360647af67ba1c6c343ccd41b8
26 tests/repository/test_project_repository.py test_project_repository.py source_code 10511 2025-07-30 jope-bm jope-bm; bm-claudeai (AI Assistant) 279a3324635b1b515c18003bd1875cf230fef1192cadef9ca426846783717e75
27 tests/services/test_project_service.py test_project_service.py source_code 28152 2025-07-30 jope-bm jope-bm; Paul Hernandez; bm-claudeai (AI Assistant) 430868ed796518937a3b0bed4d8c33be53264a6100d61d54b0745bb2b58bf29a
28 tests/utils/test_validate_project_path.py test_validate_project_path.py source_code 15346 2025-07-30 jope-bm jope-bm 27a4bf2a9d277e6cd773619e5241b20175d211ec60c284361bec404fd99143f3
-438
View File
@@ -1,438 +0,0 @@
#!/usr/bin/env python3
"""
Legal File Inventory Generator for Basic Memory
This script generates a comprehensive file inventory for legal documentation purposes,
including copyright assignments and company agreement exhibits.
The inventory includes:
- All source code files and their contributors
- Documentation and configuration files
- License and legal files
- Excludes generated files, dependencies, and temporary files
Usage:
python legal_file_inventory.py [--output inventory.csv] [--format csv|json|markdown]
"""
import os
import json
import csv
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Dict, List
import argparse
import hashlib
class FileInventoryGenerator:
def __init__(self, repo_path: str = "."):
self.repo_path = Path(repo_path).resolve()
self.inventory = []
# File patterns to exclude from legal inventory
self.exclude_patterns = {
# Version control and git
'.git', '.gitignore', '.gitmodules',
# Python compiled and cache files
'__pycache__', '*.pyc', '*.pyo', '*.pyd', '.pytest_cache',
# Virtual environments and dependencies
'.venv', 'venv', '.env', 'env', 'ENV',
'*.dist-info', 'site-packages',
# IDE and editor files
'.idea', '.vscode', '*.swp', '*.swo', '.DS_Store',
# Build and distribution artifacts
'build', 'dist', 'htmlcov', '.coverage', '.coverage.*',
'*.egg-info', '.eggs', 'wheels',
# Cache and temporary files
'.ruff_cache', '.mypy_cache', '.tox',
'node_modules', '.npm',
# Documentation build artifacts (but keep source docs)
'.obsidian',
# Lock files (these are generated)
'uv.lock', 'Pipfile.lock', 'poetry.lock', 'package-lock.json'
}
# File extensions that are definitely source/authored content
self.source_extensions = {
'.py', '.md', '.rst', '.txt', '.toml', '.yaml', '.yml',
'.json', '.cfg', '.ini', '.conf', '.sh', '.sql',
'.js', '.ts', '.jsx', '.tsx', '.css', '.scss', '.sass',
'.html', '.htm', '.xml', '.svg', '.dockerfile', '.Dockerfile'
}
# License file patterns
self.license_patterns = {
'LICENSE', 'LICENCE', 'COPYING', 'COPYRIGHT',
'license.txt', 'LICENSE.txt', 'LICENSE.md',
'CITATION.cff', 'CLA.md'
}
def run_git_command(self, command: List[str]) -> str:
"""Run a git command and return the output."""
try:
result = subprocess.run(
['git'] + command,
cwd=self.repo_path,
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return ""
def get_file_contributors(self, file_path: str) -> Dict[str, int]:
"""Get contributors and their line contributions for a file."""
try:
blame_output = self.run_git_command(['blame', '--line-porcelain', file_path])
contributors = {}
for line in blame_output.split('\n'):
if line.startswith('author '):
author = line[7:] # Remove 'author ' prefix
contributors[author] = contributors.get(author, 0) + 1
return contributors
except Exception:
return {}
def get_file_history(self, file_path: str) -> Dict[str, any]:
"""Get file creation date, last modification, and total commits."""
try:
# Get creation date (first commit)
first_commit = self.run_git_command([
'log', '--follow', '--format=%ad', '--date=iso',
'--reverse', file_path
]).split('\n')[0] if self.run_git_command([
'log', '--follow', '--format=%ad', '--date=iso',
'--reverse', file_path
]) else None
# Get last modification date
last_commit = self.run_git_command([
'log', '-1', '--format=%ad', '--date=iso', file_path
])
# Get total commits for this file
commit_count = len(self.run_git_command([
'log', '--follow', '--oneline', file_path
]).split('\n')) if self.run_git_command([
'log', '--follow', '--oneline', file_path
]) else 0
return {
'created': first_commit or 'Unknown',
'last_modified': last_commit or 'Unknown',
'commit_count': commit_count
}
except Exception:
return {
'created': 'Unknown',
'last_modified': 'Unknown',
'commit_count': 0
}
def should_exclude_file(self, file_path: Path) -> bool:
"""Determine if a file should be excluded from the inventory."""
str_path = str(file_path)
# Check if any part of the path matches exclude patterns
for pattern in self.exclude_patterns:
if pattern in str_path or file_path.match(pattern):
return True
# Exclude files in virtual environment paths
if '/.venv/' in str_path or '/venv/' in str_path:
return True
# Exclude binary files that are likely dependencies
if file_path.suffix in {'.so', '.dylib', '.dll', '.pyd'}:
return True
return False
def calculate_file_hash(self, file_path: Path) -> str:
"""Calculate SHA-256 hash of file content."""
try:
with open(file_path, 'rb') as f:
return hashlib.sha256(f.read()).hexdigest()
except Exception:
return ""
def categorize_file(self, file_path: Path) -> str:
"""Categorize the file based on its path and extension."""
str_path = str(file_path).lower()
# License and legal files
if any(pattern.lower() in file_path.name.lower() for pattern in self.license_patterns):
return "Legal/License"
# Documentation
if file_path.suffix.lower() in {'.md', '.rst', '.txt'} and any(
doc_dir in str_path for doc_dir in ['doc', 'readme', 'changelog', 'contributing']
):
return "Documentation"
# Configuration files
if file_path.suffix.lower() in {'.toml', '.yaml', '.yml', '.json', '.cfg', '.ini', '.conf'}:
return "Configuration"
# Source code
if file_path.suffix.lower() in {'.py', '.js', '.ts', '.jsx', '.tsx'}:
return "Source Code"
# Tests
if 'test' in str_path and file_path.suffix.lower() == '.py':
return "Test Code"
# Build and deployment
if file_path.name.lower() in {'dockerfile', 'justfile', 'makefile'} or file_path.suffix.lower() in {'.sh'}:
return "Build/Deployment"
# Database and migrations
if 'migration' in str_path or 'alembic' in str_path or file_path.suffix.lower() == '.sql':
return "Database/Migration"
# Templates and resources
if file_path.suffix.lower() in {'.hbs', '.j2', '.jinja', '.template'}:
return "Templates/Resources"
return "Other"
def scan_repository(self):
"""Scan the repository and build the file inventory."""
print(f"Scanning repository: {self.repo_path}")
for root, dirs, files in os.walk(self.repo_path):
# Skip excluded directories
dirs[:] = [d for d in dirs if not any(pattern in d for pattern in self.exclude_patterns)]
for file in files:
file_path = Path(root) / file
relative_path = file_path.relative_to(self.repo_path)
# Skip excluded files
if self.should_exclude_file(relative_path):
continue
# Get file stats
try:
stat_info = file_path.stat()
file_size = stat_info.st_size
modified_time = datetime.fromtimestamp(stat_info.st_mtime)
except Exception:
file_size = 0
modified_time = datetime.now()
# Get git information
contributors = self.get_file_contributors(str(relative_path))
history = self.get_file_history(str(relative_path))
# Calculate file hash for integrity verification
file_hash = self.calculate_file_hash(file_path)
# Build inventory entry
entry = {
'file_path': str(relative_path),
'full_path': str(file_path),
'file_name': file_path.name,
'file_extension': file_path.suffix,
'file_size_bytes': file_size,
'category': self.categorize_file(relative_path),
'fs_modified_date': modified_time.isoformat(),
'git_created_date': history['created'],
'git_last_modified': history['last_modified'],
'git_commit_count': history['commit_count'],
'contributors': contributors,
'primary_author': max(contributors.items(), key=lambda x: x[1])[0] if contributors else 'Unknown',
'contributor_count': len(contributors),
'total_author_lines': sum(contributors.values()) if contributors else 0,
'sha256_hash': file_hash,
'scan_timestamp': datetime.now().isoformat()
}
self.inventory.append(entry)
print(f"Scanned {len(self.inventory)} files")
def get_summary_statistics(self) -> Dict:
"""Generate summary statistics for the inventory."""
if not self.inventory:
return {}
# Collect all contributors
all_contributors = set()
for entry in self.inventory:
all_contributors.update(entry['contributors'].keys())
# Category breakdown
categories = {}
for entry in self.inventory:
cat = entry['category']
categories[cat] = categories.get(cat, 0) + 1
# File extension breakdown
extensions = {}
for entry in self.inventory:
ext = entry['file_extension'] or 'no_extension'
extensions[ext] = extensions.get(ext, 0) + 1
# Contributor statistics
contributor_files = {}
contributor_lines = {}
for entry in self.inventory:
for contributor, lines in entry['contributors'].items():
contributor_files[contributor] = contributor_files.get(contributor, 0) + 1
contributor_lines[contributor] = contributor_lines.get(contributor, 0) + lines
return {
'total_files': len(self.inventory),
'total_contributors': len(all_contributors),
'categories': categories,
'file_extensions': extensions,
'contributor_file_counts': contributor_files,
'contributor_line_counts': contributor_lines,
'scan_date': datetime.now().isoformat(),
'repository_path': str(self.repo_path)
}
def export_csv(self, output_file: str):
"""Export inventory to CSV format."""
with open(output_file, 'w', newline='', encoding='utf-8') as f:
if not self.inventory:
return
fieldnames = list(self.inventory[0].keys())
# Convert complex fields to strings for CSV
fieldnames = [f for f in fieldnames if f != 'contributors']
fieldnames.append('contributors_json')
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for entry in self.inventory:
row = {k: v for k, v in entry.items() if k != 'contributors'}
row['contributors_json'] = json.dumps(entry['contributors'])
writer.writerow(row)
def export_json(self, output_file: str):
"""Export inventory to JSON format."""
export_data = {
'metadata': self.get_summary_statistics(),
'files': self.inventory
}
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(export_data, f, indent=2, ensure_ascii=False)
def export_markdown(self, output_file: str):
"""Export inventory to Markdown format."""
stats = self.get_summary_statistics()
with open(output_file, 'w', encoding='utf-8') as f:
f.write("# Basic Memory - Legal File Inventory\n\n")
f.write(f"**Generated:** {datetime.now().isoformat()}\n\n")
f.write(f"**Repository:** {stats.get('repository_path', 'Unknown')}\n\n")
# Summary statistics
f.write("## Summary Statistics\n\n")
f.write(f"- **Total Files:** {stats.get('total_files', 0)}\n")
f.write(f"- **Total Contributors:** {stats.get('total_contributors', 0)}\n\n")
# Categories
if 'categories' in stats:
f.write("### Files by Category\n\n")
for category, count in sorted(stats['categories'].items()):
f.write(f"- **{category}:** {count} files\n")
f.write("\n")
# Top contributors
if 'contributor_file_counts' in stats:
f.write("### Top Contributors by Files Modified\n\n")
sorted_contributors = sorted(
stats['contributor_file_counts'].items(),
key=lambda x: x[1],
reverse=True
)[:10]
for contributor, count in sorted_contributors:
f.write(f"- **{contributor}:** {count} files\n")
f.write("\n")
# Detailed file listing
f.write("## Detailed File Inventory\n\n")
f.write("| File Path | Category | Size (bytes) | Primary Author | Contributors |\n")
f.write("|-----------|----------|--------------|----------------|-------------|\n")
for entry in sorted(self.inventory, key=lambda x: x['file_path']):
contributors_str = ', '.join(entry['contributors'].keys())[:50]
if len(contributors_str) == 50:
contributors_str += "..."
f.write(f"| {entry['file_path']} | {entry['category']} | "
f"{entry['file_size_bytes']} | {entry['primary_author']} | "
f"{contributors_str} |\n")
def main():
parser = argparse.ArgumentParser(
description="Generate legal file inventory for Basic Memory repository"
)
parser.add_argument(
'--output', '-o',
default='basic_memory_legal_inventory.csv',
help='Output file path (default: basic_memory_legal_inventory.csv)'
)
parser.add_argument(
'--format', '-f',
choices=['csv', 'json', 'markdown'],
default='csv',
help='Output format (default: csv)'
)
parser.add_argument(
'--repo-path', '-r',
default='.',
help='Path to repository (default: current directory)'
)
args = parser.parse_args()
# Initialize and run the inventory generator
generator = FileInventoryGenerator(args.repo_path)
generator.scan_repository()
# Export in requested format
if args.format == 'csv':
generator.export_csv(args.output)
elif args.format == 'json':
generator.export_json(args.output)
elif args.format == 'markdown':
generator.export_markdown(args.output)
# Print summary
stats = generator.get_summary_statistics()
print("\n=== Legal File Inventory Complete ===")
print(f"Repository: {stats.get('repository_path', 'Unknown')}")
print(f"Total files inventoried: {stats.get('total_files', 0)}")
print(f"Total contributors identified: {stats.get('total_contributors', 0)}")
print(f"Output written to: {args.output}")
# Show top contributors
if 'contributor_file_counts' in stats:
print("\nTop 5 contributors by files modified:")
sorted_contributors = sorted(
stats['contributor_file_counts'].items(),
key=lambda x: x[1],
reverse=True
)[:5]
for i, (contributor, count) in enumerate(sorted_contributors, 1):
print(f" {i}. {contributor}: {count} files")
if __name__ == '__main__':
main()
@@ -1,272 +0,0 @@
path,name,category,size_bytes,modified_time,primary_author,contributor_count,contributors_list,sha256_hash
.claude/commands/release/beta.md,beta.md,documentation,3103,2025-07-01T08:33:22.037438,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),345f06e3f304470a8892e96979355919c8e5d8f4212ebbe543c3272fd15a5cbe
.claude/commands/release/changelog.md,changelog.md,documentation,4648,2025-07-03T18:43:10.075727,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),ea2484352f4f881aa71f546dcf84b0882d60c96673a88406aa2f7e12a282a1f4
.claude/commands/release/release-check.md,release-check.md,documentation,3344,2025-07-01T08:33:22.040060,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),7dd41a40e95c89153936c4ea60a3c989eccd5973fff9f6caef156de82f12bd04
.claude/commands/release/release.md,release.md,documentation,2925,2025-07-01T08:33:22.043674,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),0e4afd788fcf85fbe46094452e9b92a4290c3e23702283e72ee56aa9288476e5
.claude/commands/test-live.md,test-live.md,documentation,18720,2025-07-03T18:43:10.076403,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),21a9edeefb3e5673d833151a2fd0464087c0f0a9d056fdd7fca474c1d177d6fc
.dockerignore,.dockerignore,other,623,2025-07-01T08:33:22.048301,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),b3079bffad89d07186c9166725075524ffc07ceea6dc1d53fdbea6dca9585684
.github/ISSUE_TEMPLATE/bug_report.md,bug_report.md,documentation,967,2025-03-01T14:22:50.968994,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),0d34443ddda84a5e9726c8268083c9646d2cc64ff9c5ebf56c89587cef4c7706
.github/ISSUE_TEMPLATE/config.yml,config.yml,configuration,374,2025-03-01T14:23:26.936836,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),f4e21bb9b7f053509b6c08548a2649a5d62f44f0106810aa8c915d2f29acc623
.github/ISSUE_TEMPLATE/documentation.md,documentation.md,documentation,557,2025-03-01T14:23:40.020117,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),78f80a6b9a2b954695f74a847a068e0b780da6020572401c13c931555a4a9db5
.github/ISSUE_TEMPLATE/feature_request.md,feature_request.md,documentation,786,2025-03-01T14:23:06.344081,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),cee834de442221d53f3d2eeb85b81f3a641b3a7cb587ef8a169452305007439d
.github/dependabot.yml,dependabot.yml,configuration,529,2025-03-15T10:40:16.936258,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),8c6c2ef9a1417da63adb49c70f45524129bd5c46bbd7452b6b97cfe65205f89d
.github/workflows/claude.yml,claude.yml,legal,4628,2025-07-03T18:43:10.076958,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),d026f1756310781b85abc491b454cc31b52dfce4b951cdf8a4a555b6b047604c
.github/workflows/dev-release.yml,dev-release.yml,configuration,1405,2025-06-03T08:31:42.915367,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),58bcfc9e11e210e02dee76aabe7d1378da4edafb7f61857049ee6ccee9d45ffa
.github/workflows/docker.yml,docker.yml,configuration,1621,2025-07-01T08:33:22.049870,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),c918a86f2b16ebb45182a73c45272bcd6cea7022cc6e0eaeb2b2a24d8eebfe4d
.github/workflows/pr-title.yml,pr-title.yml,configuration,887,2025-02-17T16:29:35.733646,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),b6df62a20235496feba4d9419c85d6e8446c4d447ad69e7f57b51cc8953f68f4
.github/workflows/release.yml,release.yml,configuration,2479,2025-07-03T18:43:10.077261,Drew Cain,2,Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co),163307a99e2f3d317af6ae8927e1537654d88bf574a6958ac0bcd2d4de266b2a
.github/workflows/test.yml,test.yml,configuration,1426,2025-07-01T08:33:22.054430,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),ab09084a11be8adfc83fc6285c652fbe44a4584aa7d7d3658fcf0c57112d1640
.gitignore,.gitignore,other,566,2025-07-01T08:33:22.055006,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),358658b0faa8c3145e539421deba4ad0bac4a8ea2e608b2c2f867a822c67b36f
.python-version,.python-version,other,5,2024-12-02T16:41:37.012830,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),7b55f8e67b5623c4bef3fa691288da9437d79d3aba156de48d481db32ac7d16d
CHANGELOG.md,CHANGELOG.md,documentation,53647,2025-07-06T10:34:45.550847,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); semantic-release (semantic-release),0b165eb18975ff253cde08f4687b7808ac6ce63b6d8c10912c495a76c9058d1a
CITATION.cff,CITATION.cff,other,302,2025-02-17T16:29:35.735146,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),bc4878cf6f80bb64655f3f93a5821ffe0924ad8311c784b5f81a36093fcffdda
CLA.md,CLA.md,legal,1342,2025-03-01T14:12:38.435132,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),c238c7ff4f2b8451c6bba1bbacd10b04fb7d41d9a9cd76a62644d8193791d169
CLAUDE.md,CLAUDE.md,legal,12310,2025-07-03T18:43:10.078147,Drew Cain,4,Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); Ikko Eltociear Ashimine (eltociear@gmail.com),751f10c1a9a9c0ba2054d9c9cd36cf0989d6a347491fd505bb6504ee74b12900
CODE_OF_CONDUCT.md,CODE_OF_CONDUCT.md,documentation,509,2025-02-17T16:29:35.735238,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),b276b464e85eb1e5d9f09d90bba9573508a7bf81d2eeb38d0f25bd9f2fa66198
CONTRIBUTING.md,CONTRIBUTING.md,documentation,7329,2025-07-30T09:39:36.607158,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co),ca822fe39cc529bfc3822d317ab8a20fd141ec3ecb8c20de9e2eca3edce1c282
Dockerfile,Dockerfile,other,856,2025-07-01T08:33:22.056908,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),c45501905423990e31ffdb8ae4e976eca6423c6784905c1036bcc7795b70feef
LICENSE,LICENSE,legal,34523,2024-12-02T16:41:20.739392,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),8486a10c4393cee1c25392769ddd3b2d6c242d6ec7928e1414efff7dfb2f07ef
README.md,README.md,documentation,16261,2025-07-03T18:43:10.078281,Drew Cain,6,Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Jason Noble (github+jasonn@jasonnoble.org); Marc Baiza (43151891+mbaiza27@users.noreply.github.com); Matias Forbord (codeluggage@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),2c4b82b30f49ab465617034fda999157450ed168f8e097fe8e9ca7ac65a197a7
SECURITY.md,SECURITY.md,documentation,303,2025-03-12T16:04:01.989927,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),96932268b8fe737577941b49e9dff5ff8ad59c11cc3f9a016989589ef5c98c1f
docker-compose.yml,docker-compose.yml,configuration,2534,2025-07-01T08:33:22.059281,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),4a9fdfd2f0d42b148fe021eee77fd5e772c7ba9c2fe5d0cc310c8c7a64257417
docs/AI Assistant Guide.md,AI Assistant Guide.md,documentation,16423,2025-07-01T08:33:22.060491,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),c717869a3d356852077026591364ecb0589a6d53f9764af027c57d142ea7ff55
docs/Docker.md,Docker.md,documentation,9088,2025-07-01T08:33:22.060976,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),e13d3f50148353046b52d9d493986880f76f2914ef4efe1a759b2cc9d10913a4
justfile,justfile,other,5496,2025-07-30T09:39:36.608008,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com),a8df6c73ee8cf3eaa48e4cba193dffbc2821b843a8cc0c0cd58b8517f2252907
llms-install.md,llms-install.md,documentation,2307,2025-06-29T14:19:28.826375,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),674105eb85c8f1d59fc2ddc6840f2a2a3e293358687a305c37dffc8144304f97
memory.json,memory.json,configuration,90075,2025-06-29T14:19:28.827172,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),22d5eee995ca0548b7d9dd152f9a3690c42a514cbf38fea750b3ed2a17dd99c5
pyproject.toml,pyproject.toml,configuration,3279,2025-07-30T09:39:36.608791,Paul Hernandez,4,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); semantic-release (semantic-release),428a5d64d4555dc7cd2a81b89a3444bdb1d2f3017257fbc9160bd0ae982718e1
smithery.yaml,smithery.yaml,configuration,433,2025-03-11T16:36:03.389716,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),63f13499cf4fee0751b5005f5bd85597dc1c7f9a881f4ec8147cc23e95e75c2d
src/basic_memory/__init__.py,__init__.py,source_code,256,2025-07-06T10:34:45.551291,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@gmail.com); semantic-release (semantic-release),c946490ab9d64957d25df37f721392024916f8a81ed838f70a8e938b1c2637db
src/basic_memory/alembic/alembic.ini,alembic.ini,configuration,3733,2025-02-23T17:31:23.198433,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),20466c9c5f026db66793006bebb2f328a3686c7bb34da40d52f33c3eca29e717
src/basic_memory/alembic/env.py,env.py,source_code,2838,2025-07-08T20:39:18.336650,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),e241d987eadfcd5011cbd9ddbecb833d306de87937759d81c08d37d04a6906b0
src/basic_memory/alembic/migrations.py,migrations.py,source_code,718,2025-06-21T00:01:59.613806,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),96b8873d70dd04b48d5c45b7413a54d1224ab9577557ef0dad5929377a9fb144
src/basic_memory/alembic/script.py.mako,script.py.mako,other,635,2025-02-17T16:29:35.739659,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),304a8bfb6a804e5493f5300e79882c70c9f5bb2e87512f4d16f0e097dddd323f
src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py,3dae7c7b1564_initial_schema.py,source_code,4363,2025-06-21T00:01:59.595762,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),9536d69409ddd5eb3bc54f7d0e82607da45ed7f2ad7bc4cbf7cae2a9e28657cd
src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py,502b60eaa905_remove_required_from_entity_permalink.py,source_code,1840,2025-06-21T00:01:59.604717,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),93ac584e660f33d468b3eec203b070641298c282bf82655d0beda7f050237681
src/basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py,5fe1ab1ccebe_add_projects_table.py,source_code,4379,2025-06-21T00:01:59.601299,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),d82098f5aca3cdbada60c7229ea4ac262f527349eed9ef9e864509bacfaccf7e
src/basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py,647e7a75e2cd_project_constraint_fix.py,source_code,4210,2025-07-01T08:33:22.063463,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),604ac5908a5976272f52297866d13abb14a99390590935d9fd37cf13e32049fa
src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py,b3c3938bacdb_relation_to_name_unique_index.py,source_code,1428,2025-06-21T00:01:59.598551,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),46c1b2990cdf4575752d234a8b28b494c8a54f15b5360c12f6347aef27b66a92
src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py,cc7172b46608_update_search_index_schema.py,source_code,3679,2025-06-21T00:01:59.610909,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),9036af47d42ac7dc12bb93f7a9de1266aff0688b031b550c4e0d92983a24b4c5
src/basic_memory/api/__init__.py,__init__.py,source_code,72,2025-06-21T00:01:59.579981,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),c02a63fb6d63d43d0acca97d425eae9cb055158d0ad6e1a9fc57926512adaa99
src/basic_memory/api/app.py,app.py,source_code,2897,2025-07-08T20:39:18.337081,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ad673ac2c09410af5830f6afdffaa28b0040480dacd52ff01750b8b2c066e8cf
src/basic_memory/api/routers/__init__.py,__init__.py,source_code,398,2025-06-21T00:01:59.549416,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),4443b908a43dea8e6fb4658009cb08c48a56c9b21449e29773cdd159b59cf014
src/basic_memory/api/routers/directory_router.py,directory_router.py,source_code,2054,2025-06-21T00:01:59.567371,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ac1407beec1f7d43a4d222af704b3642559c960bebf2eaf6e1ffe91f7fac5514
src/basic_memory/api/routers/importer_router.py,importer_router.py,source_code,4513,2025-06-21T00:01:59.561600,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),c45502a2b90f5a8f00174c9ad14adc2e65cd7fc0a33d9740a9d750207f0f3bea
src/basic_memory/api/routers/knowledge_router.py,knowledge_router.py,source_code,9738,2025-07-01T08:33:22.064119,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),e1d0ffb4fa5c246582ba844a11b417092de1a173567be55b7060bbc55fbc5681
src/basic_memory/api/routers/management_router.py,management_router.py,source_code,2730,2025-07-08T20:39:18.337438,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),cdbce294dcec6146c56c4dae15744cdf77039fd21b23eef7cbc0b5f9bfb5f4ee
src/basic_memory/api/routers/memory_router.py,memory_router.py,source_code,3003,2025-07-01T08:33:22.064510,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),6bd0a7c775e0c1290efb6001173237c0cdcfa0c1b1bb27c9145a7b35f1596a97
src/basic_memory/api/routers/project_router.py,project_router.py,source_code,8444,2025-07-30T09:39:45.721369,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),6539c69f1d506c65d14cc2c151876b19057f37d84d51a9e6f464ed47a4692648
src/basic_memory/api/routers/prompt_router.py,prompt_router.py,source_code,9948,2025-07-01T08:33:22.065221,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),e30c6aebe35112054933c37d0b462c3750005030fc9e44c23b35b2cd2a93485c
src/basic_memory/api/routers/resource_router.py,resource_router.py,source_code,8058,2025-06-21T00:01:59.570399,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),584244a84698ff24ca8f9f94fab5b891740a51c25f972920c08ebf83f478d5c9
src/basic_memory/api/routers/search_router.py,search_router.py,source_code,1226,2025-06-21T00:01:59.543020,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),183eb68e50904e22ff54db1d89b8be6f57fa1f8d0a096a3d497d8297b607e105
src/basic_memory/api/routers/utils.py,utils.py,source_code,5159,2025-07-03T18:43:10.079143,Drew Cain,2,Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),9e60f57da24e1dc9d64236c26a7a2353003dc618a77fbeb853c494aa3357a57c
src/basic_memory/api/template_loader.py,template_loader.py,source_code,8607,2025-06-21T00:01:59.576132,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),7b16d3797c89460c8b1664a335eae8c634fb5f60d6166d97e6a5156b776b6d83
src/basic_memory/cli/__init__.py,__init__.py,source_code,33,2025-06-21T00:01:59.456735,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),6ab70a2c05910e13c3ef1e6df34325be265e633c076741998a0cb2dcd295a069
src/basic_memory/cli/app.py,app.py,source_code,2268,2025-07-08T20:39:18.337708,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),f710670380d707c4458ad881f7a25bb3c5cdb2bcff59f139f37421264fe8f2f8
src/basic_memory/cli/commands/__init__.py,__init__.py,source_code,393,2025-07-08T20:39:18.338070,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),de8a23702f98fb844fa9f7fdbedc16ce24ff4f8baf05589c2f4a521cd8a55645
src/basic_memory/cli/commands/db.py,db.py,source_code,1480,2025-07-08T20:39:18.338326,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),704a1096d80ab9d12e247d029fe6223e935a0d0ceee7e615c020f46a720828e0
src/basic_memory/cli/commands/import_chatgpt.py,import_chatgpt.py,source_code,2856,2025-07-08T20:39:18.338787,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),8957cca3acab6351335624a518bdc19d956487e93d86dd1b6c2309e9d5850ae5
src/basic_memory/cli/commands/import_claude_conversations.py,import_claude_conversations.py,legal,2946,2025-07-08T20:39:18.339053,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),7bc97838732bf00f4fb4a80e3ba4fdfbce8971ae85cc2ac902c3b3a3e110ae57
src/basic_memory/cli/commands/import_claude_projects.py,import_claude_projects.py,legal,2891,2025-07-08T20:39:18.339310,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),6321577075801c99ad47a0cdc13b5aa3c9ca102a05ca8f06ae2a51125a8c43bc
src/basic_memory/cli/commands/import_memory_json.py,import_memory_json.py,source_code,3013,2025-07-30T09:39:45.721637,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),7ea193f6720b2672290ad16c79a143a31d88a1d567838f12b9ebf9110a7c0338
src/basic_memory/cli/commands/mcp.py,mcp.py,source_code,2593,2025-07-08T20:39:18.340025,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),3e1be3d27b18ef4743204b7cf8a37fbc2a1a91fc1636c468e08cefcd8597bac6
src/basic_memory/cli/commands/project.py,project.py,source_code,13836,2025-07-30T09:39:45.721838,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),af3e6f30410ab48355f4b1a46aaa3aa5423cb0a0490f51447db1d25e09f39916
src/basic_memory/cli/commands/status.py,status.py,source_code,5809,2025-07-08T20:39:18.340324,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),b6d0ea237d714d054f69b24a2058beba74e224a2fa4a80a525d62a2aa2ea2d70
src/basic_memory/cli/commands/sync.py,sync.py,source_code,8213,2025-07-08T20:39:18.340627,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),703b3e73c4c33850c27f6f4c07abbf5d4ce5dc43f706bf0c43264bbb45f8b213
src/basic_memory/cli/commands/tool.py,tool.py,source_code,9540,2025-07-01T08:33:22.070903,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),9b2fa400b9f7921bf55b602f8bbdfa36b1ec7e4a5bc8fe7b9838b92e31f0a9ed
src/basic_memory/cli/main.py,main.py,source_code,465,2025-07-08T20:39:18.340868,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),7a405b12bff98e319968526238ae15859df0362bba5a3857562fecca47b5c70d
src/basic_memory/config.py,config.py,source_code,12487,2025-07-30T09:39:45.722031,jope-bm,5,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),86341330a041b94d38f5e4f1b94f3509d74351c67cc826af1a4ff57313e5f726
src/basic_memory/db.py,db.py,source_code,7428,2025-07-08T20:39:18.341550,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),25e053b0b3f9e8fa823b05fc099001efe8a3a4f6e0f80728e5079ba45aa406d8
src/basic_memory/deps.py,deps.py,source_code,12144,2025-07-08T20:39:18.341870,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),bce28cd9ca549b8afce0bc689cc1fb2dc80cd5b929f85114aa682bb8ae82411d
src/basic_memory/file_utils.py,file_utils.py,source_code,6700,2025-06-21T00:01:59.587200,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),79ac5328b2c46d3232fcc6ff22ffc39ade085c0252ad91958be2a7ae9c9c8b71
src/basic_memory/importers/__init__.py,__init__.py,source_code,795,2025-06-21T00:01:59.295471,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),0537015bdecfded85cb166b9c3db50b2f3aef329c70e0c36fbcb4f824099a21f
src/basic_memory/importers/base.py,base.py,source_code,2531,2025-06-21T00:01:59.305335,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),6b0c1efd4f827f348834aa0cea2ae8eebbb842a2e5b1f5f3747ced0ef79b9f13
src/basic_memory/importers/chatgpt_importer.py,chatgpt_importer.py,source_code,7372,2025-06-21T00:01:59.308956,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),dfa540b196ab23b5c4e6bfcac4da567873351cf7c78fa35f4c3b87e84c58e218
src/basic_memory/importers/claude_conversations_importer.py,claude_conversations_importer.py,legal,5725,2025-06-21T00:01:59.288540,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),a7bc9e872f4f81ce5f79fe9dd1a6fd0f00a6f020a2cb2664184a97f14eeb1dbc
src/basic_memory/importers/claude_projects_importer.py,claude_projects_importer.py,legal,5389,2025-06-21T00:01:59.291737,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),a452675fd9bb18ebf64eb4bd7f69ccd7e993b617931015a3c4ab700d67493863
src/basic_memory/importers/memory_json_importer.py,memory_json_importer.py,source_code,4631,2025-07-30T09:39:45.722316,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),bc7d045299f17ed9ad5cebff7b1423250ec28a11130e47a0ac48d3ab82bdeafc
src/basic_memory/importers/utils.py,utils.py,source_code,1792,2025-06-21T00:01:59.298428,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),ee7e22fd4812362394f62ed8944abe80738c1e3ab88131faf639f4a8ccfd6c89
src/basic_memory/markdown/__init__.py,__init__.py,source_code,501,2025-06-21T00:01:59.316337,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),0ddce2a025ad0e729aab4e411d82e02ffefc15ac041cba4b5e7a7e90f4957c87
src/basic_memory/markdown/entity_parser.py,entity_parser.py,source_code,4516,2025-06-21T00:01:59.321461,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),1b0d1177358645ccbadd170b3f62760dc9bd838d38c741978ab0da5836234d68
src/basic_memory/markdown/markdown_processor.py,markdown_processor.py,source_code,4968,2025-06-21T00:01:59.325241,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),4b99f2ebdceed9d96a3bbb562682eba4b4b383c7a64080eaec3bbba25a4952c9
src/basic_memory/markdown/plugins.py,plugins.py,source_code,6639,2025-06-21T00:01:59.312733,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),82d2332918e866ccaf06a2e954d9eb9b397f71b4d9e591a7f2472e5f1423464a
src/basic_memory/markdown/schemas.py,schemas.py,source_code,1896,2025-06-21T00:01:59.329982,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),7b2c580abd615725a62267257b46ac13922dfc30fa01192a681658bb528ae67e
src/basic_memory/markdown/utils.py,utils.py,source_code,3410,2025-07-03T18:43:10.079889,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),1b757f0d06e60e3ea276c0b2ea44fe1a156a895e093c1e607d6286e702bf4ae4
src/basic_memory/mcp/__init__.py,__init__.py,source_code,35,2025-06-21T00:01:59.404757,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),76c0ce84aaa361e21b0942db1c8c5f708b536eab9d12e120d4da7ce9eab41844
src/basic_memory/mcp/async_client.py,async_client.py,source_code,925,2025-07-30T09:39:45.722547,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),6531f4387f30a30a322197ff51d6eacae4b25a58c648095931b3d1145d1da303
src/basic_memory/mcp/project_session.py,project_session.py,source_code,4203,2025-07-08T20:39:18.342906,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),4da403ec93dec9063eeb8b05246e3501740d162cc77a067d3ce54ba43d690819
src/basic_memory/mcp/prompts/__init__.py,__init__.py,source_code,615,2025-07-08T20:39:18.343263,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),f8197d0e08f64c3f4f50b8f38203ea5eebcf123582472ed2f58834de1dbe53b0
src/basic_memory/mcp/prompts/ai_assistant_guide.py,ai_assistant_guide.py,source_code,821,2025-06-21T00:13:53.262832,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),f13239c4e6e2455730bfac3d6f2d71407957d3086fc84efe2c6b22a833114c58
src/basic_memory/mcp/prompts/continue_conversation.py,continue_conversation.py,source_code,1998,2025-06-21T00:13:53.263309,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),aec9a50b657b7bb1ba0c02b42bcdb9bc5b0f2a0b1143bd361c5ce7ea59076833
src/basic_memory/mcp/prompts/recent_activity.py,recent_activity.py,source_code,3311,2025-07-01T08:33:22.073986,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),d2fd5cddbd927430f15d5b85f1e3a3368a18cb4e2e4587a5d29749d2b9e0830e
src/basic_memory/mcp/prompts/search.py,search.py,source_code,1692,2025-06-21T00:13:53.264093,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),9dbf3c319cbdb5d5bf32608b51522d8ae92b2dd6f7c441d62efd0954b525738a
src/basic_memory/mcp/prompts/utils.py,utils.py,source_code,5431,2025-06-21T00:01:59.425042,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),55a72b6eac18b724a9225608aca1e8e6cea3b684ccb1c609aab1511f7ce90ba4
src/basic_memory/mcp/resources/ai_assistant_guide.md,ai_assistant_guide.md,documentation,14777,2025-03-28T19:12:38.653627,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),aa76160e46256fe2662ae3a86799659916acfede1d5835c1fe2f0b136efb2e0b
src/basic_memory/mcp/resources/project_info.py,project_info.py,source_code,1906,2025-06-21T00:13:53.264918,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),2dc5244f1e225c17d4e8ba784d5721efc3aa2e8a5b3b2e258f22a77ebe155eca
src/basic_memory/mcp/server.py,server.py,source_code,1248,2025-07-08T20:39:18.343419,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),0a910cae27d61ea923e90fbe2d4b5c7cacf3b29d3e156f0b7f77872ab973c761
src/basic_memory/mcp/tools/__init__.py,__init__.py,source_code,1619,2025-07-03T18:43:10.080029,Drew Cain,2,Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co),872b771dd52ec3b763645a2baf6e10a70f049ce3106830a6d3f053b3e09a5fe6
src/basic_memory/mcp/tools/build_context.py,build_context.py,source_code,4611,2025-07-03T18:43:10.080426,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),724280b77b8f5f3e61cd3fdeebc3ee66e2bcfedaaea3638e6a2e2e33fcb1978e
src/basic_memory/mcp/tools/canvas.py,canvas.py,source_code,3738,2025-07-01T08:33:22.076204,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),db617d1bd81f3dbfa5f22d41e6b6b825afe1f73618f37acf63d9830390b98246
src/basic_memory/mcp/tools/delete_note.py,delete_note.py,source_code,7346,2025-07-01T08:33:22.076726,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),b52c9173f5600662f255e7a70a5c17d5293ef8b29c19a8403334d7da62b65c8b
src/basic_memory/mcp/tools/edit_note.py,edit_note.py,source_code,13570,2025-07-01T08:33:22.077181,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),ab8c7e7fbfa3fe5fb0ce6d7bf805454f5fd6182a340aae25237b1e6127b6d5a6
src/basic_memory/mcp/tools/list_directory.py,list_directory.py,source_code,5236,2025-06-21T00:13:53.267453,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),f85c43b02aeee580f4d8ce2a9100c0bab1095b245a0bb608e1847ace0d1ab51f
src/basic_memory/mcp/tools/move_note.py,move_note.py,source_code,17541,2025-07-30T09:39:45.722919,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),5ef27d9baaebc2c5656fc49c4798a4e4e40a70b6cd9d1e761dbcae021bad558c
src/basic_memory/mcp/tools/project_management.py,project_management.py,source_code,12944,2025-07-06T10:34:45.552403,Drew Cain,2,Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co),cdac736d65129f689a85c6b82f8e043bc84a31657ead7a8331771171eeaa860f
src/basic_memory/mcp/tools/read_content.py,read_content.py,source_code,9125,2025-07-30T09:39:45.723469,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),96a13addac507fd874394dfa2e1e5ed71af5eb89d0872c1d68cacf9cac3f8724
src/basic_memory/mcp/tools/read_note.py,read_note.py,source_code,8132,2025-07-30T09:39:45.723828,jope-bm,5,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Amadeusz Wieczorek (git@amadeusw.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),dff53ce02b16d9c5fca315b3f09bb70867a89d614b0ef3f0548dce87978048ee
src/basic_memory/mcp/tools/recent_activity.py,recent_activity.py,source_code,4833,2025-06-21T00:13:53.269120,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),5d58cd2402679b1bf3c7dfcbb3503e40e776c9347ba499524d3b91c528af98de
src/basic_memory/mcp/tools/search.py,search.py,source_code,15883,2025-07-03T18:43:10.081557,Drew Cain,4,Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),8519b0057468c4450eb543a2f561bcd0d7cb96e1ce1b95e948e02b309ba6b7ca
src/basic_memory/mcp/tools/sync_status.py,sync_status.py,source_code,10517,2025-07-08T20:39:18.344231,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),73dd73b186eaec8b82e6b17e39a67c27082d24e0653dc4e4b35712bd83b80a22
src/basic_memory/mcp/tools/utils.py,utils.py,source_code,20892,2025-07-30T09:39:36.616253,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),a95004911e276822eba88a3fef15c5b9ba861b1ca9a2ecfe0c1e3fb2f4ef0116
src/basic_memory/mcp/tools/view_note.py,view_note.py,source_code,2499,2025-07-01T08:33:22.080024,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),75d357c72113b1d0394987e521a4158ff09b77b23b08b56cddab5144331b1a68
src/basic_memory/mcp/tools/write_note.py,write_note.py,source_code,6612,2025-07-30T09:39:45.723985,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),771396fb49d397feb5f7e36321d4d233c22c4d1b53414912c849957c6e96fc83
src/basic_memory/models/__init__.py,__init__.py,source_code,333,2025-06-21T00:01:59.437212,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),8f40b876d162f85384690291f1d416106f9d26d750d793414e2260e276c85cd5
src/basic_memory/models/base.py,base.py,source_code,225,2025-06-21T00:01:59.447249,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),e2101727c084d519e32a16f6de53ddf9033b1bf15721d4e8c0c27d6d18b1b295
src/basic_memory/models/knowledge.py,knowledge.py,source_code,7087,2025-06-21T00:01:59.444376,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),005c5f292f1f45ae372aadc48c9080b9fa6d7b854d0bb7ecf587ec843ac1e28d
src/basic_memory/models/project.py,project.py,source_code,2773,2025-07-01T08:33:22.080907,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),a14ad06943aeeff9ae4a5fa2dfc0e1d07ce6085acc02dc20c402c3513b7d9397
src/basic_memory/models/search.py,search.py,source_code,1300,2025-06-21T00:01:59.440923,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),3e143cc38b5a0294af8e1d43a4f841e1c1fd193b76136a68f83159ce19e86646
src/basic_memory/repository/__init__.py,__init__.py,source_code,327,2025-06-21T00:01:59.256363,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),3162bea3c42292accea5ee52c8f6ca4368a8079056034529cfae6d820f84d035
src/basic_memory/repository/entity_repository.py,entity_repository.py,source_code,10405,2025-07-03T18:43:10.082570,Drew Cain,3,Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),e2a8d1eba6c8d64bc61d7168df0fe8c29a670858bf9daeac464a0f440273fae0
src/basic_memory/repository/observation_repository.py,observation_repository.py,source_code,2943,2025-06-21T00:01:59.269646,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co),aa132f1cb4a36a84f715afdc40e2ac4f98d83e3eba1974b2b4404cc0b020ca04
src/basic_memory/repository/project_info_repository.py,project_info_repository.py,source_code,338,2025-06-21T00:01:59.252582,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),f172d50182a405643a19b2a3d62a80f4e2b41463c775394eb3b66d7de51ff6df
src/basic_memory/repository/project_repository.py,project_repository.py,source_code,3803,2025-07-30T09:39:45.724344,jope-bm,2,jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),9083ba214b2bcaaf98164d9c33cda7add52bc77f7269ecb6e43cdd5088728b7c
src/basic_memory/repository/relation_repository.py,relation_repository.py,source_code,3179,2025-06-21T00:01:59.246745,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),cfb3a8e59cff27e063e91bd00e949647bdd92e4d9fc46edeee3acc6de15e26f4
src/basic_memory/repository/repository.py,repository.py,source_code,15224,2025-06-21T00:01:59.273355,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),3096fe71bf106506cbf86aeafe2aafe0aabbe5a5f6c90a212c8aa1e53e1f171c
src/basic_memory/repository/search_repository.py,search_repository.py,source_code,21936,2025-07-03T18:43:10.082844,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),a972f73d1b71dac5770e8eb3793c6c9ac44e4e792f9dab528f8c4e6c6a80bcaa
src/basic_memory/schemas/__init__.py,__init__.py,source_code,1674,2025-06-21T00:01:59.514555,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),98480815c75379bfafe32d2090e87fa40e73caa19b664fbd5db5ea9528ba58cb
src/basic_memory/schemas/base.py,base.py,source_code,6738,2025-07-01T08:33:22.082193,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),171f7b0c4ab33abef2f7379eb1e3bda950586ce7edff83907fd12255f84e267e
src/basic_memory/schemas/delete.py,delete.py,source_code,1180,2025-06-21T00:01:59.502563,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),50047624af7d58c8f780ffb2a065a51c3dde64492f9534917fc42860813e59fc
src/basic_memory/schemas/directory.py,directory.py,source_code,881,2025-06-21T00:01:59.529023,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),17dfcbac9a91a9bfe43b4f060ca2735cb6f69e16d81b63dd554a397830ff29fe
src/basic_memory/schemas/importer.py,importer.py,source_code,693,2025-07-30T09:39:45.724633,jope-bm,2,jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ac33df423ca32b28ce7b6ea9c29d541f8783a86c0c29f78db35163bf93f139cd
src/basic_memory/schemas/memory.py,memory.py,source_code,5833,2025-07-03T18:43:10.083167,Drew Cain,3,Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),acb4a953a553feca672c4895798a7d948ec51f922f75b66dd7d55716e3bebed3
src/basic_memory/schemas/project_info.py,project_info.py,source_code,7047,2025-07-01T08:33:22.083248,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),7dc3635297b6e7fe6e3262b2d78da26f7a79a846a4cecd5624f2e4828979cf2c
src/basic_memory/schemas/prompt.py,prompt.py,source_code,3648,2025-06-21T00:01:59.525753,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),4a92157d9a6b413f04e6e3f8d23dc2a417369c729f9703a8de2643ec114f2071
src/basic_memory/schemas/request.py,request.py,source_code,3760,2025-06-21T00:01:59.511789,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),32fe44beb2d99452223ebf1d3a3a3fe105ef92c79885023b71dfd7db30ecc503
src/basic_memory/schemas/response.py,response.py,source_code,6450,2025-07-03T18:43:10.083478,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),5eea4660a2abe48d83ed083d1c2483fdcfb403e0b504f03c14d22f1cae866abb
src/basic_memory/schemas/search.py,search.py,source_code,3657,2025-06-21T00:01:59.522804,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),cb032c0c60102b6b0ed934f995cf9d6bf93aece296d71d537a75e8ae61c75afe
src/basic_memory/services/__init__.py,__init__.py,source_code,259,2025-06-21T00:01:59.655059,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),5c6b7c597ddf5ffd0af4bdfb32ccbc1c5f2794c658206dee43a9945fafe226d8
src/basic_memory/services/context_service.py,context_service.py,source_code,14462,2025-06-21T00:01:59.672086,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (paulmh@gmail.com),e1178b005e6a89f03d6b239e3c6b152aeb30d535a3f280734440a3aec16228f2
src/basic_memory/services/directory_service.py,directory_service.py,source_code,6017,2025-06-21T00:01:59.648501,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),fd838f5ec79033892777b3c8140868f4b57f13e1658d513959524a434b9f959b
src/basic_memory/services/entity_service.py,entity_service.py,source_code,30440,2025-07-03T18:43:10.083956,Paul Hernandez,4,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),7cd5163eca6b8a0772e838c8c869e478166763cd6a2d745b0b9aa5c25a65baee
src/basic_memory/services/exceptions.py,exceptions.py,source_code,390,2025-06-21T00:01:59.668908,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),a158d0af9d1742a9c5ab5f8c34a062948d9286d1c3c72a5abf20e4d547b21e1c
src/basic_memory/services/file_service.py,file_service.py,source_code,10059,2025-06-21T00:01:59.641785,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),8c2ae69c4913438b7d1c5ecbfcce812fbb5d0ea8e3cedcbd8694e8f40cf086f3
src/basic_memory/services/initialization.py,initialization.py,source_code,6715,2025-07-08T20:39:18.344482,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),4dde58b799cf4ac90631e59949b55b3355a93bdf99df0d9c2408c0cea67e10c4
src/basic_memory/services/link_resolver.py,link_resolver.py,source_code,4594,2025-07-01T08:33:22.084908,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),d7efd516cbea753e6b5411def09aeaeb7539f5743487119ecf163c736e138a8c
src/basic_memory/services/project_service.py,project_service.py,source_code,30125,2025-07-30T09:39:45.725079,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),bce95c00aa9a1da0a8c8090ee34b5b1494207f8c99e6c25de4ec43fddc38f6be
src/basic_memory/services/search_service.py,search_service.py,source_code,12782,2025-06-21T00:01:59.665439,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),7392b2d2e7f3ed83c5807855ccd450e0e79c17f254aedee700bce9323a1b5b83
src/basic_memory/services/service.py,service.py,source_code,336,2025-06-21T00:01:59.636167,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),57e77ff20395d3bcc62100e92fe2acaacdd937d977a9fdc764e2b57ff60d4da8
src/basic_memory/services/sync_status_service.py,sync_status_service.py,source_code,7504,2025-07-03T18:43:10.084229,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),0a025d689e8e16f1632872104952105fc904537f3d32bcff592eb1f1dc76fbb7
src/basic_memory/sync/__init__.py,__init__.py,source_code,156,2025-06-21T00:01:59.621137,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),0951e0b981f8e7b876bb6c6833c2af3a29490bbd5726567ea9487c947723623e
src/basic_memory/sync/background_sync.py,background_sync.py,source_code,726,2025-07-08T20:39:18.345062,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),549af64ae91128b76c6df07ef517b82de85ca4ad796be44b5c0141fac01d4513
src/basic_memory/sync/sync_service.py,sync_service.py,source_code,23387,2025-07-01T08:33:22.086507,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),0310b927561370f593980d07773bce641b618b8fbf2d9e3890d174290a0344fc
src/basic_memory/sync/watch_service.py,watch_service.py,source_code,15316,2025-06-26T09:28:51.859209,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),240ba6ac7523575945f4db442b7da3820d26c9605f2d7a2d357c4e35e215f523
src/basic_memory/templates/prompts/continue_conversation.hbs,continue_conversation.hbs,templates,3076,2025-07-01T08:33:22.086792,Drew Cain,2,Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),b6bac31d25c0e52d0909b2273285092f4e31bc219107f92ed511cd407b65e992
src/basic_memory/templates/prompts/search.hbs,search.hbs,templates,3098,2025-05-25T10:07:54.520119,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),1f570222c1caa78542d46ac7d8a79407ca467b9bd714fa9bd953e8b72a667820
src/basic_memory/utils.py,utils.py,source_code,8654,2025-07-30T09:39:45.725428,jope-bm,5,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); andyxinweiminicloud (andyxinweimin263@icloud.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),111f343e3367339730e081c08acf4613667be574f946126513b5d82da3086bed
test-int/conftest.py,conftest.py,source_code,8203,2025-07-08T20:39:18.345735,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),7231e1bc76cddc7c94433e853825dc0165b103bedf996c2c24eda6ae32fff5ad
test-int/mcp/test_build_context_validation.py,test_build_context_validation.py,source_code,6469,2025-07-08T20:39:18.346021,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),ee61d6b15cee0194fd74c1bcf9361d5a839f6081c7d571ef081da8fcefe1bbf2
test-int/mcp/test_delete_note_integration.py,test_delete_note_integration.py,source_code,13175,2025-07-08T20:39:18.346366,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),497495249b8bcf0af177d8705c6899875460707697980434e560cfe56f86feb3
test-int/mcp/test_edit_note_integration.py,test_edit_note_integration.py,source_code,19822,2025-07-08T20:39:18.346730,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),9ba704badd05808aee0b5a686b7e250e1c865229ec92dab561cba5287b6f6fc6
test-int/mcp/test_list_directory_integration.py,test_list_directory_integration.py,source_code,14744,2025-07-08T20:39:18.347106,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),2c4336a59de63f17ed252bb52d1330ebca901ad83bfebc9e5aa67f132a3c77c6
test-int/mcp/test_move_note_integration.py,test_move_note_integration.py,source_code,19825,2025-07-08T20:39:18.347344,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),cbb17688d0f9551306a7520b059ace7f7dfba70fc20bf0ec98b1148000d7ad7d
test-int/mcp/test_project_management_integration.py,test_project_management_integration.py,source_code,33175,2025-07-08T20:39:18.347658,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),93a43cd699453a7602a5def839740ba5027ab914eba3e7b66a026860ed6bd567
test-int/mcp/test_project_state_sync_integration.py,test_project_state_sync_integration.py,source_code,7728,2025-07-08T20:39:18.347907,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),80b0f5b5cd359498dcbd3af7dcb6194afa4582d8efb3faa38d28675ee45e0748
test-int/mcp/test_read_content_integration.py,test_read_content_integration.py,source_code,11187,2025-07-08T20:39:18.348233,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),6955dd343b40df088ac79175e52723501d83796335a16e43b7f58e16e757e1b8
test-int/mcp/test_read_note_integration.py,test_read_note_integration.py,source_code,1381,2025-07-08T20:39:18.348463,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),b93050da68d0e9d47d46879a386567f688399e28390f4eb31ee37353dc27514b
test-int/mcp/test_search_integration.py,test_search_integration.py,source_code,14690,2025-07-08T20:39:18.348959,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),e0957ada9dd9b12eae5843a37d241ec9293ca929f18281e31197dae36bff589d
test-int/mcp/test_write_note_integration.py,test_write_note_integration.py,source_code,9472,2025-07-08T20:39:18.349398,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),b2b56fee8351168d0c65640c295d969dc4575d9bea87927b2dc07e591cabdff3
tests/Non-MarkdownFileSupport.pdf,Non-MarkdownFileSupport.pdf,other,106751,2025-02-23T17:31:23.206428,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),4aa96f9d0ee830ef6a1ec92243f302bbe2fa8971b84945f4eb5e276bf1ffde96
tests/Screenshot.png,Screenshot.png,other,190076,2025-02-23T17:31:23.207771,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),dd82612fe013c39810d28ae0f2bbcca6ab2c9925b035d7720bb02493ddf5d0a9
tests/__init__.py,__init__.py,source_code,141,2025-06-21T00:01:46.216456,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),2ac5605e69ad8829e96b3689632536539f1cdd3149a29c1903f25af0793a5d65
tests/api/conftest.py,conftest.py,source_code,1404,2025-06-21T00:01:46.359728,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),ea46cde47a37c1f43fe079e48f5d0de8007155a0187f4445abc4f2ffde9c1791
tests/api/test_async_client.py,test_async_client.py,source_code,1382,2025-07-30T09:39:45.725666,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),a2f96226a75e5cff2326ca48ddccea4915964d83e529f7b99958bc233ccdc31a
tests/api/test_continue_conversation_template.py,test_continue_conversation_template.py,source_code,5152,2025-06-21T00:01:46.390901,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),cca08b0291db0ad693c60d8dc203660021feab46d9eb530be7f67e0ebb52c1d3
tests/api/test_directory_router.py,test_directory_router.py,source_code,9376,2025-06-21T00:01:46.356659,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),428626589f0010512481ba495368b0bf2b9617b22764e588eaa3562e86da4d37
tests/api/test_importer_router.py,test_importer_router.py,source_code,16578,2025-06-21T00:01:46.368070,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),03edf6e7404c7350afe76e54ea2bf0d66f668bcdb49f59702067a67b3694309d
tests/api/test_knowledge_router.py,test_knowledge_router.py,source_code,45771,2025-07-01T08:33:22.090490,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),43a5f91ae1fb9038e65c09701336fbdc4dbf40c8319c94727e6312fda9d6d56e
tests/api/test_management_router.py,test_management_router.py,source_code,6220,2025-06-21T00:01:46.402764,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),f6a3f41f263f10c08281525571e2e39fc9ae818ebb7e9895e3f3f3115ea05677
tests/api/test_memory_router.py,test_memory_router.py,source_code,5555,2025-06-21T00:01:46.399667,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co),2b8782015c893081fc67b7f13a7255d4e3dc45b27705a524f94918a339e2d486
tests/api/test_project_router.py,test_project_router.py,source_code,13288,2025-07-30T09:39:45.725847,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),af85d0870069d3831a9214372853994e5d12dc910e37298eb9958f72f6a8bb31
tests/api/test_project_router_operations.py,test_project_router_operations.py,source_code,1812,2025-06-21T00:01:46.381705,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),afd40879de3d7c508cca0feeae3cb7a8f3afcfbb06f59a6104189b1e3291376a
tests/api/test_prompt_router.py,test_prompt_router.py,source_code,5046,2025-06-21T00:01:46.396588,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),9115995b7dd8b44d8a9c06c5e1bbb177618286675a769889e35ac16f26fd9897
tests/api/test_resource_router.py,test_resource_router.py,source_code,13960,2025-06-21T00:01:46.384919,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ad3cb2cabaeab82a7e64253ceede7c1791e2bd70c92f60bc5f3be939292ca22d
tests/api/test_search_router.py,test_search_router.py,source_code,6409,2025-06-21T00:01:46.375427,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (phernandez@basicmachines.co),ba3e3809795e72db9dd9c8c729048bdbbbdb897c2a5aa8b175112b5d4b56d152
tests/api/test_search_template.py,test_search_template.py,source_code,5258,2025-06-21T00:01:46.378721,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),8f0d16a1de947d0466d96afed02e695f7648adac70b9ba99753250068207a686
tests/api/test_template_loader.py,test_template_loader.py,source_code,8035,2025-06-21T00:01:46.406694,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),4fd7c6c4862cc0d877d29244ecdc0b52a0693fc4574a9667663bee36bfd36b62
tests/api/test_template_loader_helpers.py,test_template_loader_helpers.py,source_code,7445,2025-06-21T00:01:46.372114,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),8dd35f11c9b3f2d96ac36263edf4430308728c04c9da543b1cb845b96ab20aa7
tests/cli/conftest.py,conftest.py,source_code,1185,2025-07-01T08:33:22.090848,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),c3c73da7ad73224931e1fba4ebd70792221be607bfb1488bb5c9420bba03bdb8
tests/cli/test_cli_tools.py,test_cli_tools.py,source_code,13528,2025-06-21T00:01:46.318964,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),befb8cab50132e26a1b13b82ce8e93a3258282cf157a4230214bfcefeae65102
tests/cli/test_import_chatgpt.py,test_import_chatgpt.py,source_code,6719,2025-07-08T20:39:18.350234,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),df447335545248e2fb2417af33f8cc2f0143bd413a1783e41f89605de305b7c8
tests/cli/test_import_claude_conversations.py,test_import_claude_conversations.py,legal,5028,2025-07-08T20:39:18.350607,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ac4b3b9dfc331fa7fa32a2941c027ff8b6585a94a5ccfaaf0a4affe5cf19c00c
tests/cli/test_import_claude_projects.py,test_import_claude_projects.py,legal,4585,2025-07-08T20:39:18.350885,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),7eaab3d491d50ca59c8ba015ecd5740a5feae94d970ee17559c6df9d8558c2cc
tests/cli/test_import_memory_json.py,test_import_memory_json.py,source_code,4891,2025-07-30T09:39:45.726290,jope-bm,3,jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),d6734ce059dacf97bc764d848b2e2a6ba52fa96871bc331ec9d2ddc60b766bb6
tests/cli/test_project_commands.py,test_project_commands.py,source_code,6578,2025-07-30T09:39:45.726703,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),51e2ac7ff44b0d1a1db6da406c5c2e6aebceb1b63a1e8d5ab51bbaa834507157
tests/cli/test_project_info.py,test_project_info.py,source_code,3874,2025-07-01T08:33:22.091600,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),0b08950e95a5f276f14c56f6fe99af4d41b8f04548e9b7746f807c5ac4155578
tests/cli/test_status.py,test_status.py,source_code,4160,2025-07-01T08:33:22.091978,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),a1ff89fdac4bf57ba03be92ac79b8ca3210af0b1132d5a2bd0fc0c253fcbf988
tests/cli/test_sync.py,test_sync.py,source_code,3864,2025-07-08T20:43:47.816788,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),77e0f6b42b584a5acfda3c2ff194ff5e71ca57a40cb4484d563f5482bbbb4c0f
tests/cli/test_version.py,test_version.py,source_code,289,2025-06-21T00:01:46.305368,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),db95665178a32d391bd83011d073db269cefba9da8a4dc4d3e392d47bc0e0a8f
tests/conftest.py,conftest.py,source_code,15286,2025-07-08T20:39:18.351368,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),c6f38f103ce58144ffe4562f0c47dad849974e96327f02805e854447e19bc411
tests/importers/test_importer_base.py,test_importer_base.py,source_code,4329,2025-07-08T20:39:00.549594,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),da872a073f2b90fda3c98c9f44cf45884f4f682d91ec1b1f931d42c34eee1939
tests/importers/test_importer_utils.py,test_importer_utils.py,source_code,1827,2025-06-21T00:01:46.184343,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),cbee630c855ceb1de764ac0cdb1d5d781c16cd3a934d67166adc235c98f363d7
tests/markdown/__init__.py,__init__.py,source_code,0,2025-06-21T00:01:46.196570,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
tests/markdown/test_entity_parser.py,test_entity_parser.py,source_code,8669,2025-06-21T00:01:46.206579,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),496fdab6fe164634f8223f827585a4c0dedd30ccea7b0b69dc79004ad0996de2
tests/markdown/test_markdown_plugins.py,test_markdown_plugins.py,source_code,5139,2025-06-21T00:01:46.200759,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),9624d73462aadcd47fb0edd0d87880959a8982eecfa39529f21e3b292c34cb83
tests/markdown/test_markdown_processor.py,test_markdown_processor.py,source_code,5728,2025-06-21T00:01:46.194175,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),35042aa57dd2aeed848c624aa7f69decfb18e83c3b217eca1fdacfc4a10831db
tests/markdown/test_observation_edge_cases.py,test_observation_edge_cases.py,source_code,4295,2025-06-21T00:01:46.209598,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),6f1ab7684bfa8b02af0e0c42b3d7aed91fcd0b1c6288cbc9b6d6943c166979fa
tests/markdown/test_parser_edge_cases.py,test_parser_edge_cases.py,source_code,5142,2025-06-21T00:01:46.213291,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),d511d7d1187e3c1d3a8d3e0ab9efc9e68e84115c46811e7a9dc21eca9a87d520
tests/markdown/test_relation_edge_cases.py,test_relation_edge_cases.py,source_code,3952,2025-06-21T00:01:46.203846,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),f8e2016308f84139342cc37ec6e389ffe52d7eb46f998ee327c7f0d5bba3ad9b
tests/markdown/test_task_detection.py,test_task_detection.py,source_code,401,2025-06-21T00:01:46.191444,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),37fe91a689742033a0eb76723557e31c2a85e591133383b3887572cb714db2a5
tests/mcp/conftest.py,conftest.py,source_code,1761,2025-07-08T20:39:18.351554,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),350b2ca42c52ef76e002deddb65fc0eccb9f8989bd26078ea1c100e3b93bae3f
tests/mcp/test_prompts.py,test_prompts.py,source_code,5989,2025-07-01T08:33:22.093691,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),e3b2ee5b4bc120b5043225ebdbf73eadd71be9c3d9d8e1fdd66a24a8ff11a780
tests/mcp/test_resource_project_info.py,test_resource_project_info.py,source_code,4885,2025-07-01T08:33:22.094085,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),6a043736789b239c8d4ae00d1a5332a0fc3bed4038fe3388970ba97dc7431a93
tests/mcp/test_resources.py,test_resources.py,source_code,549,2025-07-01T08:33:22.094481,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),77a6396b9b8a9d3b3d202e438243ca6178d87fd7680c9fc063ee013f2042aa4c
tests/mcp/test_tool_build_context.py,test_tool_build_context.py,source_code,3714,2025-07-01T08:33:22.094661,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),5f0a39fdb652605335ed54aa91fe994262441bb7ba46f381c46ebca882fc23f2
tests/mcp/test_tool_canvas.py,test_tool_canvas.py,source_code,7997,2025-07-01T08:33:22.094982,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),c517bb1f9edc77b76fbeb483dfcb2dd57de9f0b80a10738708bc90cbe388dc9e
tests/mcp/test_tool_delete_note.py,test_tool_delete_note.py,source_code,4208,2025-07-01T08:33:22.095578,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),4cbf3ba4313438651b0608d5b25faf1400f7bd6a78af630351aecd4457800895
tests/mcp/test_tool_edit_note.py,test_tool_edit_note.py,source_code,13503,2025-07-03T18:43:10.084890,Drew Cain,2,Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),dd441969aa54b3739f2d9202d3feaf46aee807ec51e01a13ef41eda52743944f
tests/mcp/test_tool_list_directory.py,test_tool_list_directory.py,source_code,7730,2025-07-01T08:33:22.096737,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),cfbb5c06bcfe74ea60f31051eb9b03ac80b86232b053c188bf3b172132f7c0f5
tests/mcp/test_tool_move_note.py,test_tool_move_note.py,source_code,25851,2025-07-30T09:39:45.727347,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co),c9f95158503fa42e37532aaa35027538fa49cea718d059d570cf547d31f01043
tests/mcp/test_tool_read_content.py,test_tool_read_content.py,source_code,19442,2025-07-30T09:39:45.727526,jope-bm,1,jope-bm (joe@basicmemory.com),1817e32f997b06dbdabf02864d0c70584c1cd80bf1fae8dfbef649de79434bc5
tests/mcp/test_tool_read_note.py,test_tool_read_note.py,source_code,22991,2025-07-30T09:39:45.727746,jope-bm,4,jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co); Amadeusz Wieczorek (git@amadeusw.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),4e13ea9ff977525aa62b6a8c22505ffe989329dc3e7d07b71e044cf43ef3ddfd
tests/mcp/test_tool_recent_activity.py,test_tool_recent_activity.py,source_code,3983,2025-07-01T08:33:22.098561,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),45414152cd45334b84df56f653146b7bd2939907548e7245a37207fdccfc4c87
tests/mcp/test_tool_resource.py,test_tool_resource.py,source_code,6782,2025-07-01T08:33:22.098948,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),3916ae1c4d458eee53d2c570b9bbb708ecd617de5a145c3a7552389954bc9195
tests/mcp/test_tool_search.py,test_tool_search.py,source_code,10959,2025-07-03T18:43:10.085177,Drew Cain,3,Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),07839cf8e89d74ba05ec1dc55fa842444c1f0cc3d1587823b0ac3458ad89e87c
tests/mcp/test_tool_sync_status.py,test_tool_sync_status.py,source_code,6331,2025-07-01T08:33:22.100021,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),3f3566df5b9f3c66bb9b3bd77d4c05825480d0e85015305cb1b7ab9429c7a7ec
tests/mcp/test_tool_utils.py,test_tool_utils.py,source_code,9228,2025-07-01T08:33:22.100561,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),b50789f7a78b0169f52dc8bd16e262510ed4a0ac130fce698d4be84af1fe2d31
tests/mcp/test_tool_view_note.py,test_tool_view_note.py,source_code,10004,2025-07-01T08:33:22.101164,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),385535ce791c3ab55db827cbcf27ea25ae85faf3a027f52df4eea052cede7f36
tests/mcp/test_tool_write_note.py,test_tool_write_note.py,source_code,35136,2025-07-30T09:39:45.728137,jope-bm,3,jope-bm (joe@basicmemory.com); Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),45172de20cfc4f4436287573b4f5a94b8b616a360647af67ba1c6c343ccd41b8
tests/repository/test_entity_repository.py,test_entity_repository.py,source_code,15498,2025-06-21T00:01:46.176265,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),8f5d1ff6cff7b640993ff73f463b1e7801a4dd69640fc55fa23dc58b865e9943
tests/repository/test_entity_repository_upsert.py,test_entity_repository_upsert.py,source_code,16983,2025-07-03T18:43:10.085482,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com),ee0ecd6f4ff8b31838bd6de6fb1018af437e02813d8315c06f4e20a6b78a0572
tests/repository/test_observation_repository.py,test_observation_repository.py,source_code,11786,2025-06-21T00:01:46.158862,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co),3006d392ad94dcc9b3ae031d63a1af6935624534389dd025de70d4c9e112aa0b
tests/repository/test_project_info_repository.py,test_project_info_repository.py,source_code,1186,2025-06-21T00:01:46.161876,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),dbce7ae7350403cbc37343c2e3e68e4d2255e247006aaa6c495a7ce8bfddea7d
tests/repository/test_project_repository.py,test_project_repository.py,source_code,10511,2025-07-30T09:39:45.728536,jope-bm,2,jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),279a3324635b1b515c18003bd1875cf230fef1192cadef9ca426846783717e75
tests/repository/test_relation_repository.py,test_relation_repository.py,source_code,11980,2025-06-21T00:01:46.167567,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co),fcdbdf40f7b56145b98eedde5ead64ef6187e859adde48cc09f5a97754757428
tests/repository/test_repository.py,test_repository.py,source_code,5996,2025-06-21T00:01:46.152691,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),3a6d4ca0b7a438a30ae504f5eac22874d4e1a937a4246e90efad46c90f0e1c95
tests/repository/test_search_repository.py,test_search_repository.py,source_code,25471,2025-07-03T18:43:10.086041,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),805b5cc55a53ae9d6e86c0de735c7f38ac9fd92c23194b0150fa3e7f5507a146
tests/schemas/test_memory_url.py,test_memory_url.py,source_code,2000,2025-06-21T00:01:46.346515,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),946fa6c48534f41c8b9cc27bbad164d3e3673a79afa84fbb439fae4cd26c504b
tests/schemas/test_memory_url_validation.py,test_memory_url_validation.py,source_code,9338,2025-07-01T08:33:22.103247,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),7054c3a3cdb8e05d2ffd254ed298908ff3d48a0a3927d960975eae1ab9beeb38
tests/schemas/test_schemas.py,test_schemas.py,source_code,16880,2025-07-03T18:43:10.086337,Drew Cain,3,Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),2c746a00c340f063c160ace67569955a300a294040949695c79fd4ae124fb611
tests/schemas/test_search.py,test_search.py,source_code,3264,2025-06-21T00:01:46.343685,github-actions[bot] (AI Assistant),2,github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),7437775d1b4a61a438cbabe3dbb1becd2adc4dbcc0c24595830c0843fa7a3166
tests/services/test_context_service.py,test_context_service.py,source_code,8833,2025-06-21T00:01:46.446200,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),ab49d21a05d5364e7e8a2f00b07aabe1109b7417698e0acd92792e2cb5f22732
tests/services/test_directory_service.py,test_directory_service.py,source_code,6854,2025-06-21T00:01:46.457258,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),a0c765fb6bd1a75b6ba4592b1df344c48273b6ab03ba6bd15d2b5e840eb2d521
tests/services/test_entity_service.py,test_entity_service.py,source_code,59278,2025-07-03T18:43:10.086674,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),d7d495756faa10b415d705cf26b69d2efbc5f190a834b6767386ff296629ea71
tests/services/test_file_service.py,test_file_service.py,source_code,5243,2025-06-21T00:01:46.460141,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),83e6e9a7584b7baeccbc84865d8c1d3e2a0223e7d2b9c066d3799c289196ab7d
tests/services/test_initialization.py,test_initialization.py,source_code,6939,2025-07-08T20:39:18.351946,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ebb8d11ff37dcb36c4844c68f873c8e0e8aa6ab1b82abb22ee96c4233e6ab287
tests/services/test_link_resolver.py,test_link_resolver.py,source_code,13618,2025-07-01T08:33:22.105766,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),c9fc56d0c3572770e95ca6175c213ed8b44c2a6ddddc298b5662cc2d8f41014e
tests/services/test_project_service.py,test_project_service.py,source_code,28152,2025-07-30T09:39:45.728920,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),430868ed796518937a3b0bed4d8c33be53264a6100d61d54b0745bb2b58bf29a
tests/services/test_project_service_operations.py,test_project_service_operations.py,source_code,4679,2025-06-21T00:01:46.442141,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),d4103b6c5fff7e74ba84b86033537b8522aa60fc75aec4974d52e1dfec68b1b4
tests/services/test_search_service.py,test_search_service.py,source_code,25299,2025-06-21T00:01:46.439133,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),b3e58258db2b727ab8a1792e52fcb038d3a0779bcd1b70f2d295a9d06e5e2513
tests/services/test_sync_status_service.py,test_sync_status_service.py,source_code,9926,2025-07-03T18:43:10.087266,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),00245ca356e33fc7afcd2d42cb5ec082038c95383d738067821cf1e8d23c9a4b
tests/sync/test_sync_service.py,test_sync_service.py,source_code,39659,2025-07-01T08:33:22.107088,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),0609aee9906549f27fc4289b5ac2d46a50e53e12a0615a2701f17fbe9a74e169
tests/sync/test_sync_wikilink_issue.py,test_sync_wikilink_issue.py,source_code,1959,2025-06-21T00:01:46.420370,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),db4b98dd7008a1a26b0fd02b9a2d03bca950b281efc19d98a91b017c500b23a5
tests/sync/test_tmp_files.py,test_tmp_files.py,source_code,5930,2025-06-21T00:01:46.424277,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),cead95a07b29f6c1ab933abe67f9648122f5f33d33753c8e4ab4934af96cbc25
tests/sync/test_watch_service.py,test_watch_service.py,source_code,14076,2025-06-26T09:28:51.860679,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),e324a4127f8bfe59798f3a08125488c701e88239b7482d87a0394b91c55b893e
tests/sync/test_watch_service_edge_cases.py,test_watch_service_edge_cases.py,source_code,2169,2025-06-21T00:01:46.409830,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),b87c5015330e9b551630aab7ef95ac1e7663f0ce46e912d8155b319e139559aa
tests/test_config.py,test_config.py,source_code,3680,2025-07-03T18:43:10.087558,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),6a246431cff739274efda895dfa28f38710289a73e645312f5fa05fce0885ec8
tests/test_db_migration_deduplication.py,test_db_migration_deduplication.py,source_code,6166,2025-07-08T20:39:18.352690,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com),ab768e061531ca2edcd598c101b5d884e549f90c7a4b305a23713a199d263fbb
tests/utils/test_file_utils.py,test_file_utils.py,source_code,6199,2025-06-21T00:01:46.219673,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),4615bb20b92b8a79d9e086301f852b34c3360f17ecc209920d68528268ddeed4
tests/utils/test_parse_tags.py,test_parse_tags.py,source_code,1691,2025-06-21T00:01:46.223400,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),b83afdf18dac8b3411bd0fab1301683217d3d1abceaf2d22c09e79c709efd8eb
tests/utils/test_permalink_formatting.py,test_permalink_formatting.py,source_code,4193,2025-06-21T00:01:46.225738,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),d998a4ca846c78679728a918b6f71df67c30b6961ede3cffa609f2cf6ad160eb
tests/utils/test_utf8_handling.py,test_utf8_handling.py,source_code,5791,2025-06-21T00:01:46.228483,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),50ec792e572af51cd2d996d4ddacbcfd3bbd79aa928818a90c42117451ea829f
tests/utils/test_validate_project_path.py,test_validate_project_path.py,source_code,15346,2025-07-30T09:39:45.729224,jope-bm,1,jope-bm (joe@basicmemory.com),27a4bf2a9d277e6cd773619e5241b20175d211ec60c284361bec404fd99143f3
uv.lock,uv.lock,other,232710,2025-07-30T09:39:36.619155,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),caa1e3f6e66464b884a78cab77df2fe303fa3c0dc573be0196474b6d3c150581
1 path name category size_bytes modified_time primary_author contributor_count contributors_list sha256_hash
2 .claude/commands/release/beta.md beta.md documentation 3103 2025-07-01T08:33:22.037438 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 345f06e3f304470a8892e96979355919c8e5d8f4212ebbe543c3272fd15a5cbe
3 .claude/commands/release/changelog.md changelog.md documentation 4648 2025-07-03T18:43:10.075727 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) ea2484352f4f881aa71f546dcf84b0882d60c96673a88406aa2f7e12a282a1f4
4 .claude/commands/release/release-check.md release-check.md documentation 3344 2025-07-01T08:33:22.040060 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 7dd41a40e95c89153936c4ea60a3c989eccd5973fff9f6caef156de82f12bd04
5 .claude/commands/release/release.md release.md documentation 2925 2025-07-01T08:33:22.043674 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 0e4afd788fcf85fbe46094452e9b92a4290c3e23702283e72ee56aa9288476e5
6 .claude/commands/test-live.md test-live.md documentation 18720 2025-07-03T18:43:10.076403 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 21a9edeefb3e5673d833151a2fd0464087c0f0a9d056fdd7fca474c1d177d6fc
7 .dockerignore .dockerignore other 623 2025-07-01T08:33:22.048301 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) b3079bffad89d07186c9166725075524ffc07ceea6dc1d53fdbea6dca9585684
8 .github/ISSUE_TEMPLATE/bug_report.md bug_report.md documentation 967 2025-03-01T14:22:50.968994 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 0d34443ddda84a5e9726c8268083c9646d2cc64ff9c5ebf56c89587cef4c7706
9 .github/ISSUE_TEMPLATE/config.yml config.yml configuration 374 2025-03-01T14:23:26.936836 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) f4e21bb9b7f053509b6c08548a2649a5d62f44f0106810aa8c915d2f29acc623
10 .github/ISSUE_TEMPLATE/documentation.md documentation.md documentation 557 2025-03-01T14:23:40.020117 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 78f80a6b9a2b954695f74a847a068e0b780da6020572401c13c931555a4a9db5
11 .github/ISSUE_TEMPLATE/feature_request.md feature_request.md documentation 786 2025-03-01T14:23:06.344081 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) cee834de442221d53f3d2eeb85b81f3a641b3a7cb587ef8a169452305007439d
12 .github/dependabot.yml dependabot.yml configuration 529 2025-03-15T10:40:16.936258 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 8c6c2ef9a1417da63adb49c70f45524129bd5c46bbd7452b6b97cfe65205f89d
13 .github/workflows/claude.yml claude.yml legal 4628 2025-07-03T18:43:10.076958 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) d026f1756310781b85abc491b454cc31b52dfce4b951cdf8a4a555b6b047604c
14 .github/workflows/dev-release.yml dev-release.yml configuration 1405 2025-06-03T08:31:42.915367 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 58bcfc9e11e210e02dee76aabe7d1378da4edafb7f61857049ee6ccee9d45ffa
15 .github/workflows/docker.yml docker.yml configuration 1621 2025-07-01T08:33:22.049870 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) c918a86f2b16ebb45182a73c45272bcd6cea7022cc6e0eaeb2b2a24d8eebfe4d
16 .github/workflows/pr-title.yml pr-title.yml configuration 887 2025-02-17T16:29:35.733646 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) b6df62a20235496feba4d9419c85d6e8446c4d447ad69e7f57b51cc8953f68f4
17 .github/workflows/release.yml release.yml configuration 2479 2025-07-03T18:43:10.077261 Drew Cain 2 Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co) 163307a99e2f3d317af6ae8927e1537654d88bf574a6958ac0bcd2d4de266b2a
18 .github/workflows/test.yml test.yml configuration 1426 2025-07-01T08:33:22.054430 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) ab09084a11be8adfc83fc6285c652fbe44a4584aa7d7d3658fcf0c57112d1640
19 .gitignore .gitignore other 566 2025-07-01T08:33:22.055006 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 358658b0faa8c3145e539421deba4ad0bac4a8ea2e608b2c2f867a822c67b36f
20 .python-version .python-version other 5 2024-12-02T16:41:37.012830 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 7b55f8e67b5623c4bef3fa691288da9437d79d3aba156de48d481db32ac7d16d
21 CHANGELOG.md CHANGELOG.md documentation 53647 2025-07-06T10:34:45.550847 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); semantic-release (semantic-release) 0b165eb18975ff253cde08f4687b7808ac6ce63b6d8c10912c495a76c9058d1a
22 CITATION.cff CITATION.cff other 302 2025-02-17T16:29:35.735146 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) bc4878cf6f80bb64655f3f93a5821ffe0924ad8311c784b5f81a36093fcffdda
23 CLA.md CLA.md legal 1342 2025-03-01T14:12:38.435132 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) c238c7ff4f2b8451c6bba1bbacd10b04fb7d41d9a9cd76a62644d8193791d169
24 CLAUDE.md CLAUDE.md legal 12310 2025-07-03T18:43:10.078147 Drew Cain 4 Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); Ikko Eltociear Ashimine (eltociear@gmail.com) 751f10c1a9a9c0ba2054d9c9cd36cf0989d6a347491fd505bb6504ee74b12900
25 CODE_OF_CONDUCT.md CODE_OF_CONDUCT.md documentation 509 2025-02-17T16:29:35.735238 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) b276b464e85eb1e5d9f09d90bba9573508a7bf81d2eeb38d0f25bd9f2fa66198
26 CONTRIBUTING.md CONTRIBUTING.md documentation 7329 2025-07-30T09:39:36.607158 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co) ca822fe39cc529bfc3822d317ab8a20fd141ec3ecb8c20de9e2eca3edce1c282
27 Dockerfile Dockerfile other 856 2025-07-01T08:33:22.056908 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) c45501905423990e31ffdb8ae4e976eca6423c6784905c1036bcc7795b70feef
28 LICENSE LICENSE legal 34523 2024-12-02T16:41:20.739392 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 8486a10c4393cee1c25392769ddd3b2d6c242d6ec7928e1414efff7dfb2f07ef
29 README.md README.md documentation 16261 2025-07-03T18:43:10.078281 Drew Cain 6 Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Jason Noble (github+jasonn@jasonnoble.org); Marc Baiza (43151891+mbaiza27@users.noreply.github.com); Matias Forbord (codeluggage@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 2c4b82b30f49ab465617034fda999157450ed168f8e097fe8e9ca7ac65a197a7
30 SECURITY.md SECURITY.md documentation 303 2025-03-12T16:04:01.989927 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 96932268b8fe737577941b49e9dff5ff8ad59c11cc3f9a016989589ef5c98c1f
31 docker-compose.yml docker-compose.yml configuration 2534 2025-07-01T08:33:22.059281 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 4a9fdfd2f0d42b148fe021eee77fd5e772c7ba9c2fe5d0cc310c8c7a64257417
32 docs/AI Assistant Guide.md AI Assistant Guide.md documentation 16423 2025-07-01T08:33:22.060491 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) c717869a3d356852077026591364ecb0589a6d53f9764af027c57d142ea7ff55
33 docs/Docker.md Docker.md documentation 9088 2025-07-01T08:33:22.060976 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) e13d3f50148353046b52d9d493986880f76f2914ef4efe1a759b2cc9d10913a4
34 justfile justfile other 5496 2025-07-30T09:39:36.608008 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com) a8df6c73ee8cf3eaa48e4cba193dffbc2821b843a8cc0c0cd58b8517f2252907
35 llms-install.md llms-install.md documentation 2307 2025-06-29T14:19:28.826375 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 674105eb85c8f1d59fc2ddc6840f2a2a3e293358687a305c37dffc8144304f97
36 memory.json memory.json configuration 90075 2025-06-29T14:19:28.827172 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 22d5eee995ca0548b7d9dd152f9a3690c42a514cbf38fea750b3ed2a17dd99c5
37 pyproject.toml pyproject.toml configuration 3279 2025-07-30T09:39:36.608791 Paul Hernandez 4 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); semantic-release (semantic-release) 428a5d64d4555dc7cd2a81b89a3444bdb1d2f3017257fbc9160bd0ae982718e1
38 smithery.yaml smithery.yaml configuration 433 2025-03-11T16:36:03.389716 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 63f13499cf4fee0751b5005f5bd85597dc1c7f9a881f4ec8147cc23e95e75c2d
39 src/basic_memory/__init__.py __init__.py source_code 256 2025-07-06T10:34:45.551291 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@gmail.com); semantic-release (semantic-release) c946490ab9d64957d25df37f721392024916f8a81ed838f70a8e938b1c2637db
40 src/basic_memory/alembic/alembic.ini alembic.ini configuration 3733 2025-02-23T17:31:23.198433 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 20466c9c5f026db66793006bebb2f328a3686c7bb34da40d52f33c3eca29e717
41 src/basic_memory/alembic/env.py env.py source_code 2838 2025-07-08T20:39:18.336650 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) e241d987eadfcd5011cbd9ddbecb833d306de87937759d81c08d37d04a6906b0
42 src/basic_memory/alembic/migrations.py migrations.py source_code 718 2025-06-21T00:01:59.613806 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 96b8873d70dd04b48d5c45b7413a54d1224ab9577557ef0dad5929377a9fb144
43 src/basic_memory/alembic/script.py.mako script.py.mako other 635 2025-02-17T16:29:35.739659 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 304a8bfb6a804e5493f5300e79882c70c9f5bb2e87512f4d16f0e097dddd323f
44 src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py 3dae7c7b1564_initial_schema.py source_code 4363 2025-06-21T00:01:59.595762 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 9536d69409ddd5eb3bc54f7d0e82607da45ed7f2ad7bc4cbf7cae2a9e28657cd
45 src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py 502b60eaa905_remove_required_from_entity_permalink.py source_code 1840 2025-06-21T00:01:59.604717 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 93ac584e660f33d468b3eec203b070641298c282bf82655d0beda7f050237681
46 src/basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py 5fe1ab1ccebe_add_projects_table.py source_code 4379 2025-06-21T00:01:59.601299 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) d82098f5aca3cdbada60c7229ea4ac262f527349eed9ef9e864509bacfaccf7e
47 src/basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py 647e7a75e2cd_project_constraint_fix.py source_code 4210 2025-07-01T08:33:22.063463 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 604ac5908a5976272f52297866d13abb14a99390590935d9fd37cf13e32049fa
48 src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py b3c3938bacdb_relation_to_name_unique_index.py source_code 1428 2025-06-21T00:01:59.598551 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 46c1b2990cdf4575752d234a8b28b494c8a54f15b5360c12f6347aef27b66a92
49 src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py cc7172b46608_update_search_index_schema.py source_code 3679 2025-06-21T00:01:59.610909 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 9036af47d42ac7dc12bb93f7a9de1266aff0688b031b550c4e0d92983a24b4c5
50 src/basic_memory/api/__init__.py __init__.py source_code 72 2025-06-21T00:01:59.579981 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) c02a63fb6d63d43d0acca97d425eae9cb055158d0ad6e1a9fc57926512adaa99
51 src/basic_memory/api/app.py app.py source_code 2897 2025-07-08T20:39:18.337081 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ad673ac2c09410af5830f6afdffaa28b0040480dacd52ff01750b8b2c066e8cf
52 src/basic_memory/api/routers/__init__.py __init__.py source_code 398 2025-06-21T00:01:59.549416 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 4443b908a43dea8e6fb4658009cb08c48a56c9b21449e29773cdd159b59cf014
53 src/basic_memory/api/routers/directory_router.py directory_router.py source_code 2054 2025-06-21T00:01:59.567371 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ac1407beec1f7d43a4d222af704b3642559c960bebf2eaf6e1ffe91f7fac5514
54 src/basic_memory/api/routers/importer_router.py importer_router.py source_code 4513 2025-06-21T00:01:59.561600 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) c45502a2b90f5a8f00174c9ad14adc2e65cd7fc0a33d9740a9d750207f0f3bea
55 src/basic_memory/api/routers/knowledge_router.py knowledge_router.py source_code 9738 2025-07-01T08:33:22.064119 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) e1d0ffb4fa5c246582ba844a11b417092de1a173567be55b7060bbc55fbc5681
56 src/basic_memory/api/routers/management_router.py management_router.py source_code 2730 2025-07-08T20:39:18.337438 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) cdbce294dcec6146c56c4dae15744cdf77039fd21b23eef7cbc0b5f9bfb5f4ee
57 src/basic_memory/api/routers/memory_router.py memory_router.py source_code 3003 2025-07-01T08:33:22.064510 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6bd0a7c775e0c1290efb6001173237c0cdcfa0c1b1bb27c9145a7b35f1596a97
58 src/basic_memory/api/routers/project_router.py project_router.py source_code 8444 2025-07-30T09:39:45.721369 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6539c69f1d506c65d14cc2c151876b19057f37d84d51a9e6f464ed47a4692648
59 src/basic_memory/api/routers/prompt_router.py prompt_router.py source_code 9948 2025-07-01T08:33:22.065221 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) e30c6aebe35112054933c37d0b462c3750005030fc9e44c23b35b2cd2a93485c
60 src/basic_memory/api/routers/resource_router.py resource_router.py source_code 8058 2025-06-21T00:01:59.570399 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 584244a84698ff24ca8f9f94fab5b891740a51c25f972920c08ebf83f478d5c9
61 src/basic_memory/api/routers/search_router.py search_router.py source_code 1226 2025-06-21T00:01:59.543020 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 183eb68e50904e22ff54db1d89b8be6f57fa1f8d0a096a3d497d8297b607e105
62 src/basic_memory/api/routers/utils.py utils.py source_code 5159 2025-07-03T18:43:10.079143 Drew Cain 2 Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 9e60f57da24e1dc9d64236c26a7a2353003dc618a77fbeb853c494aa3357a57c
63 src/basic_memory/api/template_loader.py template_loader.py source_code 8607 2025-06-21T00:01:59.576132 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 7b16d3797c89460c8b1664a335eae8c634fb5f60d6166d97e6a5156b776b6d83
64 src/basic_memory/cli/__init__.py __init__.py source_code 33 2025-06-21T00:01:59.456735 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 6ab70a2c05910e13c3ef1e6df34325be265e633c076741998a0cb2dcd295a069
65 src/basic_memory/cli/app.py app.py source_code 2268 2025-07-08T20:39:18.337708 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) f710670380d707c4458ad881f7a25bb3c5cdb2bcff59f139f37421264fe8f2f8
66 src/basic_memory/cli/commands/__init__.py __init__.py source_code 393 2025-07-08T20:39:18.338070 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) de8a23702f98fb844fa9f7fdbedc16ce24ff4f8baf05589c2f4a521cd8a55645
67 src/basic_memory/cli/commands/db.py db.py source_code 1480 2025-07-08T20:39:18.338326 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 704a1096d80ab9d12e247d029fe6223e935a0d0ceee7e615c020f46a720828e0
68 src/basic_memory/cli/commands/import_chatgpt.py import_chatgpt.py source_code 2856 2025-07-08T20:39:18.338787 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 8957cca3acab6351335624a518bdc19d956487e93d86dd1b6c2309e9d5850ae5
69 src/basic_memory/cli/commands/import_claude_conversations.py import_claude_conversations.py legal 2946 2025-07-08T20:39:18.339053 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7bc97838732bf00f4fb4a80e3ba4fdfbce8971ae85cc2ac902c3b3a3e110ae57
70 src/basic_memory/cli/commands/import_claude_projects.py import_claude_projects.py legal 2891 2025-07-08T20:39:18.339310 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6321577075801c99ad47a0cdc13b5aa3c9ca102a05ca8f06ae2a51125a8c43bc
71 src/basic_memory/cli/commands/import_memory_json.py import_memory_json.py source_code 3013 2025-07-30T09:39:45.721637 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7ea193f6720b2672290ad16c79a143a31d88a1d567838f12b9ebf9110a7c0338
72 src/basic_memory/cli/commands/mcp.py mcp.py source_code 2593 2025-07-08T20:39:18.340025 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 3e1be3d27b18ef4743204b7cf8a37fbc2a1a91fc1636c468e08cefcd8597bac6
73 src/basic_memory/cli/commands/project.py project.py source_code 13836 2025-07-30T09:39:45.721838 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) af3e6f30410ab48355f4b1a46aaa3aa5423cb0a0490f51447db1d25e09f39916
74 src/basic_memory/cli/commands/status.py status.py source_code 5809 2025-07-08T20:39:18.340324 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) b6d0ea237d714d054f69b24a2058beba74e224a2fa4a80a525d62a2aa2ea2d70
75 src/basic_memory/cli/commands/sync.py sync.py source_code 8213 2025-07-08T20:39:18.340627 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 703b3e73c4c33850c27f6f4c07abbf5d4ce5dc43f706bf0c43264bbb45f8b213
76 src/basic_memory/cli/commands/tool.py tool.py source_code 9540 2025-07-01T08:33:22.070903 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 9b2fa400b9f7921bf55b602f8bbdfa36b1ec7e4a5bc8fe7b9838b92e31f0a9ed
77 src/basic_memory/cli/main.py main.py source_code 465 2025-07-08T20:39:18.340868 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7a405b12bff98e319968526238ae15859df0362bba5a3857562fecca47b5c70d
78 src/basic_memory/config.py config.py source_code 12487 2025-07-30T09:39:45.722031 jope-bm 5 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 86341330a041b94d38f5e4f1b94f3509d74351c67cc826af1a4ff57313e5f726
79 src/basic_memory/db.py db.py source_code 7428 2025-07-08T20:39:18.341550 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 25e053b0b3f9e8fa823b05fc099001efe8a3a4f6e0f80728e5079ba45aa406d8
80 src/basic_memory/deps.py deps.py source_code 12144 2025-07-08T20:39:18.341870 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) bce28cd9ca549b8afce0bc689cc1fb2dc80cd5b929f85114aa682bb8ae82411d
81 src/basic_memory/file_utils.py file_utils.py source_code 6700 2025-06-21T00:01:59.587200 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 79ac5328b2c46d3232fcc6ff22ffc39ade085c0252ad91958be2a7ae9c9c8b71
82 src/basic_memory/importers/__init__.py __init__.py source_code 795 2025-06-21T00:01:59.295471 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 0537015bdecfded85cb166b9c3db50b2f3aef329c70e0c36fbcb4f824099a21f
83 src/basic_memory/importers/base.py base.py source_code 2531 2025-06-21T00:01:59.305335 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 6b0c1efd4f827f348834aa0cea2ae8eebbb842a2e5b1f5f3747ced0ef79b9f13
84 src/basic_memory/importers/chatgpt_importer.py chatgpt_importer.py source_code 7372 2025-06-21T00:01:59.308956 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) dfa540b196ab23b5c4e6bfcac4da567873351cf7c78fa35f4c3b87e84c58e218
85 src/basic_memory/importers/claude_conversations_importer.py claude_conversations_importer.py legal 5725 2025-06-21T00:01:59.288540 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) a7bc9e872f4f81ce5f79fe9dd1a6fd0f00a6f020a2cb2664184a97f14eeb1dbc
86 src/basic_memory/importers/claude_projects_importer.py claude_projects_importer.py legal 5389 2025-06-21T00:01:59.291737 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) a452675fd9bb18ebf64eb4bd7f69ccd7e993b617931015a3c4ab700d67493863
87 src/basic_memory/importers/memory_json_importer.py memory_json_importer.py source_code 4631 2025-07-30T09:39:45.722316 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) bc7d045299f17ed9ad5cebff7b1423250ec28a11130e47a0ac48d3ab82bdeafc
88 src/basic_memory/importers/utils.py utils.py source_code 1792 2025-06-21T00:01:59.298428 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) ee7e22fd4812362394f62ed8944abe80738c1e3ab88131faf639f4a8ccfd6c89
89 src/basic_memory/markdown/__init__.py __init__.py source_code 501 2025-06-21T00:01:59.316337 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 0ddce2a025ad0e729aab4e411d82e02ffefc15ac041cba4b5e7a7e90f4957c87
90 src/basic_memory/markdown/entity_parser.py entity_parser.py source_code 4516 2025-06-21T00:01:59.321461 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 1b0d1177358645ccbadd170b3f62760dc9bd838d38c741978ab0da5836234d68
91 src/basic_memory/markdown/markdown_processor.py markdown_processor.py source_code 4968 2025-06-21T00:01:59.325241 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 4b99f2ebdceed9d96a3bbb562682eba4b4b383c7a64080eaec3bbba25a4952c9
92 src/basic_memory/markdown/plugins.py plugins.py source_code 6639 2025-06-21T00:01:59.312733 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 82d2332918e866ccaf06a2e954d9eb9b397f71b4d9e591a7f2472e5f1423464a
93 src/basic_memory/markdown/schemas.py schemas.py source_code 1896 2025-06-21T00:01:59.329982 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 7b2c580abd615725a62267257b46ac13922dfc30fa01192a681658bb528ae67e
94 src/basic_memory/markdown/utils.py utils.py source_code 3410 2025-07-03T18:43:10.079889 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 1b757f0d06e60e3ea276c0b2ea44fe1a156a895e093c1e607d6286e702bf4ae4
95 src/basic_memory/mcp/__init__.py __init__.py source_code 35 2025-06-21T00:01:59.404757 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 76c0ce84aaa361e21b0942db1c8c5f708b536eab9d12e120d4da7ce9eab41844
96 src/basic_memory/mcp/async_client.py async_client.py source_code 925 2025-07-30T09:39:45.722547 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 6531f4387f30a30a322197ff51d6eacae4b25a58c648095931b3d1145d1da303
97 src/basic_memory/mcp/project_session.py project_session.py source_code 4203 2025-07-08T20:39:18.342906 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 4da403ec93dec9063eeb8b05246e3501740d162cc77a067d3ce54ba43d690819
98 src/basic_memory/mcp/prompts/__init__.py __init__.py source_code 615 2025-07-08T20:39:18.343263 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) f8197d0e08f64c3f4f50b8f38203ea5eebcf123582472ed2f58834de1dbe53b0
99 src/basic_memory/mcp/prompts/ai_assistant_guide.py ai_assistant_guide.py source_code 821 2025-06-21T00:13:53.262832 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) f13239c4e6e2455730bfac3d6f2d71407957d3086fc84efe2c6b22a833114c58
100 src/basic_memory/mcp/prompts/continue_conversation.py continue_conversation.py source_code 1998 2025-06-21T00:13:53.263309 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) aec9a50b657b7bb1ba0c02b42bcdb9bc5b0f2a0b1143bd361c5ce7ea59076833
101 src/basic_memory/mcp/prompts/recent_activity.py recent_activity.py source_code 3311 2025-07-01T08:33:22.073986 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) d2fd5cddbd927430f15d5b85f1e3a3368a18cb4e2e4587a5d29749d2b9e0830e
102 src/basic_memory/mcp/prompts/search.py search.py source_code 1692 2025-06-21T00:13:53.264093 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 9dbf3c319cbdb5d5bf32608b51522d8ae92b2dd6f7c441d62efd0954b525738a
103 src/basic_memory/mcp/prompts/utils.py utils.py source_code 5431 2025-06-21T00:01:59.425042 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 55a72b6eac18b724a9225608aca1e8e6cea3b684ccb1c609aab1511f7ce90ba4
104 src/basic_memory/mcp/resources/ai_assistant_guide.md ai_assistant_guide.md documentation 14777 2025-03-28T19:12:38.653627 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) aa76160e46256fe2662ae3a86799659916acfede1d5835c1fe2f0b136efb2e0b
105 src/basic_memory/mcp/resources/project_info.py project_info.py source_code 1906 2025-06-21T00:13:53.264918 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 2dc5244f1e225c17d4e8ba784d5721efc3aa2e8a5b3b2e258f22a77ebe155eca
106 src/basic_memory/mcp/server.py server.py source_code 1248 2025-07-08T20:39:18.343419 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 0a910cae27d61ea923e90fbe2d4b5c7cacf3b29d3e156f0b7f77872ab973c761
107 src/basic_memory/mcp/tools/__init__.py __init__.py source_code 1619 2025-07-03T18:43:10.080029 Drew Cain 2 Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co) 872b771dd52ec3b763645a2baf6e10a70f049ce3106830a6d3f053b3e09a5fe6
108 src/basic_memory/mcp/tools/build_context.py build_context.py source_code 4611 2025-07-03T18:43:10.080426 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 724280b77b8f5f3e61cd3fdeebc3ee66e2bcfedaaea3638e6a2e2e33fcb1978e
109 src/basic_memory/mcp/tools/canvas.py canvas.py source_code 3738 2025-07-01T08:33:22.076204 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) db617d1bd81f3dbfa5f22d41e6b6b825afe1f73618f37acf63d9830390b98246
110 src/basic_memory/mcp/tools/delete_note.py delete_note.py source_code 7346 2025-07-01T08:33:22.076726 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) b52c9173f5600662f255e7a70a5c17d5293ef8b29c19a8403334d7da62b65c8b
111 src/basic_memory/mcp/tools/edit_note.py edit_note.py source_code 13570 2025-07-01T08:33:22.077181 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) ab8c7e7fbfa3fe5fb0ce6d7bf805454f5fd6182a340aae25237b1e6127b6d5a6
112 src/basic_memory/mcp/tools/list_directory.py list_directory.py source_code 5236 2025-06-21T00:13:53.267453 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) f85c43b02aeee580f4d8ce2a9100c0bab1095b245a0bb608e1847ace0d1ab51f
113 src/basic_memory/mcp/tools/move_note.py move_note.py source_code 17541 2025-07-30T09:39:45.722919 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 5ef27d9baaebc2c5656fc49c4798a4e4e40a70b6cd9d1e761dbcae021bad558c
114 src/basic_memory/mcp/tools/project_management.py project_management.py source_code 12944 2025-07-06T10:34:45.552403 Drew Cain 2 Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co) cdac736d65129f689a85c6b82f8e043bc84a31657ead7a8331771171eeaa860f
115 src/basic_memory/mcp/tools/read_content.py read_content.py source_code 9125 2025-07-30T09:39:45.723469 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 96a13addac507fd874394dfa2e1e5ed71af5eb89d0872c1d68cacf9cac3f8724
116 src/basic_memory/mcp/tools/read_note.py read_note.py source_code 8132 2025-07-30T09:39:45.723828 jope-bm 5 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Amadeusz Wieczorek (git@amadeusw.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) dff53ce02b16d9c5fca315b3f09bb70867a89d614b0ef3f0548dce87978048ee
117 src/basic_memory/mcp/tools/recent_activity.py recent_activity.py source_code 4833 2025-06-21T00:13:53.269120 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 5d58cd2402679b1bf3c7dfcbb3503e40e776c9347ba499524d3b91c528af98de
118 src/basic_memory/mcp/tools/search.py search.py source_code 15883 2025-07-03T18:43:10.081557 Drew Cain 4 Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 8519b0057468c4450eb543a2f561bcd0d7cb96e1ce1b95e948e02b309ba6b7ca
119 src/basic_memory/mcp/tools/sync_status.py sync_status.py source_code 10517 2025-07-08T20:39:18.344231 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 73dd73b186eaec8b82e6b17e39a67c27082d24e0653dc4e4b35712bd83b80a22
120 src/basic_memory/mcp/tools/utils.py utils.py source_code 20892 2025-07-30T09:39:36.616253 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) a95004911e276822eba88a3fef15c5b9ba861b1ca9a2ecfe0c1e3fb2f4ef0116
121 src/basic_memory/mcp/tools/view_note.py view_note.py source_code 2499 2025-07-01T08:33:22.080024 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 75d357c72113b1d0394987e521a4158ff09b77b23b08b56cddab5144331b1a68
122 src/basic_memory/mcp/tools/write_note.py write_note.py source_code 6612 2025-07-30T09:39:45.723985 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 771396fb49d397feb5f7e36321d4d233c22c4d1b53414912c849957c6e96fc83
123 src/basic_memory/models/__init__.py __init__.py source_code 333 2025-06-21T00:01:59.437212 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 8f40b876d162f85384690291f1d416106f9d26d750d793414e2260e276c85cd5
124 src/basic_memory/models/base.py base.py source_code 225 2025-06-21T00:01:59.447249 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) e2101727c084d519e32a16f6de53ddf9033b1bf15721d4e8c0c27d6d18b1b295
125 src/basic_memory/models/knowledge.py knowledge.py source_code 7087 2025-06-21T00:01:59.444376 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 005c5f292f1f45ae372aadc48c9080b9fa6d7b854d0bb7ecf587ec843ac1e28d
126 src/basic_memory/models/project.py project.py source_code 2773 2025-07-01T08:33:22.080907 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) a14ad06943aeeff9ae4a5fa2dfc0e1d07ce6085acc02dc20c402c3513b7d9397
127 src/basic_memory/models/search.py search.py source_code 1300 2025-06-21T00:01:59.440923 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 3e143cc38b5a0294af8e1d43a4f841e1c1fd193b76136a68f83159ce19e86646
128 src/basic_memory/repository/__init__.py __init__.py source_code 327 2025-06-21T00:01:59.256363 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 3162bea3c42292accea5ee52c8f6ca4368a8079056034529cfae6d820f84d035
129 src/basic_memory/repository/entity_repository.py entity_repository.py source_code 10405 2025-07-03T18:43:10.082570 Drew Cain 3 Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) e2a8d1eba6c8d64bc61d7168df0fe8c29a670858bf9daeac464a0f440273fae0
130 src/basic_memory/repository/observation_repository.py observation_repository.py source_code 2943 2025-06-21T00:01:59.269646 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co) aa132f1cb4a36a84f715afdc40e2ac4f98d83e3eba1974b2b4404cc0b020ca04
131 src/basic_memory/repository/project_info_repository.py project_info_repository.py source_code 338 2025-06-21T00:01:59.252582 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) f172d50182a405643a19b2a3d62a80f4e2b41463c775394eb3b66d7de51ff6df
132 src/basic_memory/repository/project_repository.py project_repository.py source_code 3803 2025-07-30T09:39:45.724344 jope-bm 2 jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 9083ba214b2bcaaf98164d9c33cda7add52bc77f7269ecb6e43cdd5088728b7c
133 src/basic_memory/repository/relation_repository.py relation_repository.py source_code 3179 2025-06-21T00:01:59.246745 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) cfb3a8e59cff27e063e91bd00e949647bdd92e4d9fc46edeee3acc6de15e26f4
134 src/basic_memory/repository/repository.py repository.py source_code 15224 2025-06-21T00:01:59.273355 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 3096fe71bf106506cbf86aeafe2aafe0aabbe5a5f6c90a212c8aa1e53e1f171c
135 src/basic_memory/repository/search_repository.py search_repository.py source_code 21936 2025-07-03T18:43:10.082844 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) a972f73d1b71dac5770e8eb3793c6c9ac44e4e792f9dab528f8c4e6c6a80bcaa
136 src/basic_memory/schemas/__init__.py __init__.py source_code 1674 2025-06-21T00:01:59.514555 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 98480815c75379bfafe32d2090e87fa40e73caa19b664fbd5db5ea9528ba58cb
137 src/basic_memory/schemas/base.py base.py source_code 6738 2025-07-01T08:33:22.082193 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 171f7b0c4ab33abef2f7379eb1e3bda950586ce7edff83907fd12255f84e267e
138 src/basic_memory/schemas/delete.py delete.py source_code 1180 2025-06-21T00:01:59.502563 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 50047624af7d58c8f780ffb2a065a51c3dde64492f9534917fc42860813e59fc
139 src/basic_memory/schemas/directory.py directory.py source_code 881 2025-06-21T00:01:59.529023 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 17dfcbac9a91a9bfe43b4f060ca2735cb6f69e16d81b63dd554a397830ff29fe
140 src/basic_memory/schemas/importer.py importer.py source_code 693 2025-07-30T09:39:45.724633 jope-bm 2 jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ac33df423ca32b28ce7b6ea9c29d541f8783a86c0c29f78db35163bf93f139cd
141 src/basic_memory/schemas/memory.py memory.py source_code 5833 2025-07-03T18:43:10.083167 Drew Cain 3 Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) acb4a953a553feca672c4895798a7d948ec51f922f75b66dd7d55716e3bebed3
142 src/basic_memory/schemas/project_info.py project_info.py source_code 7047 2025-07-01T08:33:22.083248 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7dc3635297b6e7fe6e3262b2d78da26f7a79a846a4cecd5624f2e4828979cf2c
143 src/basic_memory/schemas/prompt.py prompt.py source_code 3648 2025-06-21T00:01:59.525753 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 4a92157d9a6b413f04e6e3f8d23dc2a417369c729f9703a8de2643ec114f2071
144 src/basic_memory/schemas/request.py request.py source_code 3760 2025-06-21T00:01:59.511789 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 32fe44beb2d99452223ebf1d3a3a3fe105ef92c79885023b71dfd7db30ecc503
145 src/basic_memory/schemas/response.py response.py source_code 6450 2025-07-03T18:43:10.083478 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 5eea4660a2abe48d83ed083d1c2483fdcfb403e0b504f03c14d22f1cae866abb
146 src/basic_memory/schemas/search.py search.py source_code 3657 2025-06-21T00:01:59.522804 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) cb032c0c60102b6b0ed934f995cf9d6bf93aece296d71d537a75e8ae61c75afe
147 src/basic_memory/services/__init__.py __init__.py source_code 259 2025-06-21T00:01:59.655059 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 5c6b7c597ddf5ffd0af4bdfb32ccbc1c5f2794c658206dee43a9945fafe226d8
148 src/basic_memory/services/context_service.py context_service.py source_code 14462 2025-06-21T00:01:59.672086 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (paulmh@gmail.com) e1178b005e6a89f03d6b239e3c6b152aeb30d535a3f280734440a3aec16228f2
149 src/basic_memory/services/directory_service.py directory_service.py source_code 6017 2025-06-21T00:01:59.648501 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) fd838f5ec79033892777b3c8140868f4b57f13e1658d513959524a434b9f959b
150 src/basic_memory/services/entity_service.py entity_service.py source_code 30440 2025-07-03T18:43:10.083956 Paul Hernandez 4 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 7cd5163eca6b8a0772e838c8c869e478166763cd6a2d745b0b9aa5c25a65baee
151 src/basic_memory/services/exceptions.py exceptions.py source_code 390 2025-06-21T00:01:59.668908 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) a158d0af9d1742a9c5ab5f8c34a062948d9286d1c3c72a5abf20e4d547b21e1c
152 src/basic_memory/services/file_service.py file_service.py source_code 10059 2025-06-21T00:01:59.641785 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 8c2ae69c4913438b7d1c5ecbfcce812fbb5d0ea8e3cedcbd8694e8f40cf086f3
153 src/basic_memory/services/initialization.py initialization.py source_code 6715 2025-07-08T20:39:18.344482 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 4dde58b799cf4ac90631e59949b55b3355a93bdf99df0d9c2408c0cea67e10c4
154 src/basic_memory/services/link_resolver.py link_resolver.py source_code 4594 2025-07-01T08:33:22.084908 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) d7efd516cbea753e6b5411def09aeaeb7539f5743487119ecf163c736e138a8c
155 src/basic_memory/services/project_service.py project_service.py source_code 30125 2025-07-30T09:39:45.725079 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) bce95c00aa9a1da0a8c8090ee34b5b1494207f8c99e6c25de4ec43fddc38f6be
156 src/basic_memory/services/search_service.py search_service.py source_code 12782 2025-06-21T00:01:59.665439 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7392b2d2e7f3ed83c5807855ccd450e0e79c17f254aedee700bce9323a1b5b83
157 src/basic_memory/services/service.py service.py source_code 336 2025-06-21T00:01:59.636167 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 57e77ff20395d3bcc62100e92fe2acaacdd937d977a9fdc764e2b57ff60d4da8
158 src/basic_memory/services/sync_status_service.py sync_status_service.py source_code 7504 2025-07-03T18:43:10.084229 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 0a025d689e8e16f1632872104952105fc904537f3d32bcff592eb1f1dc76fbb7
159 src/basic_memory/sync/__init__.py __init__.py source_code 156 2025-06-21T00:01:59.621137 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 0951e0b981f8e7b876bb6c6833c2af3a29490bbd5726567ea9487c947723623e
160 src/basic_memory/sync/background_sync.py background_sync.py source_code 726 2025-07-08T20:39:18.345062 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 549af64ae91128b76c6df07ef517b82de85ca4ad796be44b5c0141fac01d4513
161 src/basic_memory/sync/sync_service.py sync_service.py source_code 23387 2025-07-01T08:33:22.086507 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 0310b927561370f593980d07773bce641b618b8fbf2d9e3890d174290a0344fc
162 src/basic_memory/sync/watch_service.py watch_service.py source_code 15316 2025-06-26T09:28:51.859209 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 240ba6ac7523575945f4db442b7da3820d26c9605f2d7a2d357c4e35e215f523
163 src/basic_memory/templates/prompts/continue_conversation.hbs continue_conversation.hbs templates 3076 2025-07-01T08:33:22.086792 Drew Cain 2 Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) b6bac31d25c0e52d0909b2273285092f4e31bc219107f92ed511cd407b65e992
164 src/basic_memory/templates/prompts/search.hbs search.hbs templates 3098 2025-05-25T10:07:54.520119 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 1f570222c1caa78542d46ac7d8a79407ca467b9bd714fa9bd953e8b72a667820
165 src/basic_memory/utils.py utils.py source_code 8654 2025-07-30T09:39:45.725428 jope-bm 5 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); andyxinweiminicloud (andyxinweimin263@icloud.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 111f343e3367339730e081c08acf4613667be574f946126513b5d82da3086bed
166 test-int/conftest.py conftest.py source_code 8203 2025-07-08T20:39:18.345735 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 7231e1bc76cddc7c94433e853825dc0165b103bedf996c2c24eda6ae32fff5ad
167 test-int/mcp/test_build_context_validation.py test_build_context_validation.py source_code 6469 2025-07-08T20:39:18.346021 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) ee61d6b15cee0194fd74c1bcf9361d5a839f6081c7d571ef081da8fcefe1bbf2
168 test-int/mcp/test_delete_note_integration.py test_delete_note_integration.py source_code 13175 2025-07-08T20:39:18.346366 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 497495249b8bcf0af177d8705c6899875460707697980434e560cfe56f86feb3
169 test-int/mcp/test_edit_note_integration.py test_edit_note_integration.py source_code 19822 2025-07-08T20:39:18.346730 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 9ba704badd05808aee0b5a686b7e250e1c865229ec92dab561cba5287b6f6fc6
170 test-int/mcp/test_list_directory_integration.py test_list_directory_integration.py source_code 14744 2025-07-08T20:39:18.347106 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 2c4336a59de63f17ed252bb52d1330ebca901ad83bfebc9e5aa67f132a3c77c6
171 test-int/mcp/test_move_note_integration.py test_move_note_integration.py source_code 19825 2025-07-08T20:39:18.347344 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) cbb17688d0f9551306a7520b059ace7f7dfba70fc20bf0ec98b1148000d7ad7d
172 test-int/mcp/test_project_management_integration.py test_project_management_integration.py source_code 33175 2025-07-08T20:39:18.347658 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 93a43cd699453a7602a5def839740ba5027ab914eba3e7b66a026860ed6bd567
173 test-int/mcp/test_project_state_sync_integration.py test_project_state_sync_integration.py source_code 7728 2025-07-08T20:39:18.347907 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 80b0f5b5cd359498dcbd3af7dcb6194afa4582d8efb3faa38d28675ee45e0748
174 test-int/mcp/test_read_content_integration.py test_read_content_integration.py source_code 11187 2025-07-08T20:39:18.348233 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 6955dd343b40df088ac79175e52723501d83796335a16e43b7f58e16e757e1b8
175 test-int/mcp/test_read_note_integration.py test_read_note_integration.py source_code 1381 2025-07-08T20:39:18.348463 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) b93050da68d0e9d47d46879a386567f688399e28390f4eb31ee37353dc27514b
176 test-int/mcp/test_search_integration.py test_search_integration.py source_code 14690 2025-07-08T20:39:18.348959 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) e0957ada9dd9b12eae5843a37d241ec9293ca929f18281e31197dae36bff589d
177 test-int/mcp/test_write_note_integration.py test_write_note_integration.py source_code 9472 2025-07-08T20:39:18.349398 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) b2b56fee8351168d0c65640c295d969dc4575d9bea87927b2dc07e591cabdff3
178 tests/Non-MarkdownFileSupport.pdf Non-MarkdownFileSupport.pdf other 106751 2025-02-23T17:31:23.206428 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 4aa96f9d0ee830ef6a1ec92243f302bbe2fa8971b84945f4eb5e276bf1ffde96
179 tests/Screenshot.png Screenshot.png other 190076 2025-02-23T17:31:23.207771 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) dd82612fe013c39810d28ae0f2bbcca6ab2c9925b035d7720bb02493ddf5d0a9
180 tests/__init__.py __init__.py source_code 141 2025-06-21T00:01:46.216456 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 2ac5605e69ad8829e96b3689632536539f1cdd3149a29c1903f25af0793a5d65
181 tests/api/conftest.py conftest.py source_code 1404 2025-06-21T00:01:46.359728 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) ea46cde47a37c1f43fe079e48f5d0de8007155a0187f4445abc4f2ffde9c1791
182 tests/api/test_async_client.py test_async_client.py source_code 1382 2025-07-30T09:39:45.725666 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) a2f96226a75e5cff2326ca48ddccea4915964d83e529f7b99958bc233ccdc31a
183 tests/api/test_continue_conversation_template.py test_continue_conversation_template.py source_code 5152 2025-06-21T00:01:46.390901 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) cca08b0291db0ad693c60d8dc203660021feab46d9eb530be7f67e0ebb52c1d3
184 tests/api/test_directory_router.py test_directory_router.py source_code 9376 2025-06-21T00:01:46.356659 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 428626589f0010512481ba495368b0bf2b9617b22764e588eaa3562e86da4d37
185 tests/api/test_importer_router.py test_importer_router.py source_code 16578 2025-06-21T00:01:46.368070 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 03edf6e7404c7350afe76e54ea2bf0d66f668bcdb49f59702067a67b3694309d
186 tests/api/test_knowledge_router.py test_knowledge_router.py source_code 45771 2025-07-01T08:33:22.090490 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 43a5f91ae1fb9038e65c09701336fbdc4dbf40c8319c94727e6312fda9d6d56e
187 tests/api/test_management_router.py test_management_router.py source_code 6220 2025-06-21T00:01:46.402764 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) f6a3f41f263f10c08281525571e2e39fc9ae818ebb7e9895e3f3f3115ea05677
188 tests/api/test_memory_router.py test_memory_router.py source_code 5555 2025-06-21T00:01:46.399667 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co) 2b8782015c893081fc67b7f13a7255d4e3dc45b27705a524f94918a339e2d486
189 tests/api/test_project_router.py test_project_router.py source_code 13288 2025-07-30T09:39:45.725847 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) af85d0870069d3831a9214372853994e5d12dc910e37298eb9958f72f6a8bb31
190 tests/api/test_project_router_operations.py test_project_router_operations.py source_code 1812 2025-06-21T00:01:46.381705 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) afd40879de3d7c508cca0feeae3cb7a8f3afcfbb06f59a6104189b1e3291376a
191 tests/api/test_prompt_router.py test_prompt_router.py source_code 5046 2025-06-21T00:01:46.396588 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 9115995b7dd8b44d8a9c06c5e1bbb177618286675a769889e35ac16f26fd9897
192 tests/api/test_resource_router.py test_resource_router.py source_code 13960 2025-06-21T00:01:46.384919 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ad3cb2cabaeab82a7e64253ceede7c1791e2bd70c92f60bc5f3be939292ca22d
193 tests/api/test_search_router.py test_search_router.py source_code 6409 2025-06-21T00:01:46.375427 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (phernandez@basicmachines.co) ba3e3809795e72db9dd9c8c729048bdbbbdb897c2a5aa8b175112b5d4b56d152
194 tests/api/test_search_template.py test_search_template.py source_code 5258 2025-06-21T00:01:46.378721 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 8f0d16a1de947d0466d96afed02e695f7648adac70b9ba99753250068207a686
195 tests/api/test_template_loader.py test_template_loader.py source_code 8035 2025-06-21T00:01:46.406694 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 4fd7c6c4862cc0d877d29244ecdc0b52a0693fc4574a9667663bee36bfd36b62
196 tests/api/test_template_loader_helpers.py test_template_loader_helpers.py source_code 7445 2025-06-21T00:01:46.372114 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 8dd35f11c9b3f2d96ac36263edf4430308728c04c9da543b1cb845b96ab20aa7
197 tests/cli/conftest.py conftest.py source_code 1185 2025-07-01T08:33:22.090848 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) c3c73da7ad73224931e1fba4ebd70792221be607bfb1488bb5c9420bba03bdb8
198 tests/cli/test_cli_tools.py test_cli_tools.py source_code 13528 2025-06-21T00:01:46.318964 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) befb8cab50132e26a1b13b82ce8e93a3258282cf157a4230214bfcefeae65102
199 tests/cli/test_import_chatgpt.py test_import_chatgpt.py source_code 6719 2025-07-08T20:39:18.350234 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) df447335545248e2fb2417af33f8cc2f0143bd413a1783e41f89605de305b7c8
200 tests/cli/test_import_claude_conversations.py test_import_claude_conversations.py legal 5028 2025-07-08T20:39:18.350607 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ac4b3b9dfc331fa7fa32a2941c027ff8b6585a94a5ccfaaf0a4affe5cf19c00c
201 tests/cli/test_import_claude_projects.py test_import_claude_projects.py legal 4585 2025-07-08T20:39:18.350885 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7eaab3d491d50ca59c8ba015ecd5740a5feae94d970ee17559c6df9d8558c2cc
202 tests/cli/test_import_memory_json.py test_import_memory_json.py source_code 4891 2025-07-30T09:39:45.726290 jope-bm 3 jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) d6734ce059dacf97bc764d848b2e2a6ba52fa96871bc331ec9d2ddc60b766bb6
203 tests/cli/test_project_commands.py test_project_commands.py source_code 6578 2025-07-30T09:39:45.726703 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 51e2ac7ff44b0d1a1db6da406c5c2e6aebceb1b63a1e8d5ab51bbaa834507157
204 tests/cli/test_project_info.py test_project_info.py source_code 3874 2025-07-01T08:33:22.091600 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 0b08950e95a5f276f14c56f6fe99af4d41b8f04548e9b7746f807c5ac4155578
205 tests/cli/test_status.py test_status.py source_code 4160 2025-07-01T08:33:22.091978 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) a1ff89fdac4bf57ba03be92ac79b8ca3210af0b1132d5a2bd0fc0c253fcbf988
206 tests/cli/test_sync.py test_sync.py source_code 3864 2025-07-08T20:43:47.816788 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 77e0f6b42b584a5acfda3c2ff194ff5e71ca57a40cb4484d563f5482bbbb4c0f
207 tests/cli/test_version.py test_version.py source_code 289 2025-06-21T00:01:46.305368 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) db95665178a32d391bd83011d073db269cefba9da8a4dc4d3e392d47bc0e0a8f
208 tests/conftest.py conftest.py source_code 15286 2025-07-08T20:39:18.351368 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) c6f38f103ce58144ffe4562f0c47dad849974e96327f02805e854447e19bc411
209 tests/importers/test_importer_base.py test_importer_base.py source_code 4329 2025-07-08T20:39:00.549594 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) da872a073f2b90fda3c98c9f44cf45884f4f682d91ec1b1f931d42c34eee1939
210 tests/importers/test_importer_utils.py test_importer_utils.py source_code 1827 2025-06-21T00:01:46.184343 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) cbee630c855ceb1de764ac0cdb1d5d781c16cd3a934d67166adc235c98f363d7
211 tests/markdown/__init__.py __init__.py source_code 0 2025-06-21T00:01:46.196570 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
212 tests/markdown/test_entity_parser.py test_entity_parser.py source_code 8669 2025-06-21T00:01:46.206579 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 496fdab6fe164634f8223f827585a4c0dedd30ccea7b0b69dc79004ad0996de2
213 tests/markdown/test_markdown_plugins.py test_markdown_plugins.py source_code 5139 2025-06-21T00:01:46.200759 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 9624d73462aadcd47fb0edd0d87880959a8982eecfa39529f21e3b292c34cb83
214 tests/markdown/test_markdown_processor.py test_markdown_processor.py source_code 5728 2025-06-21T00:01:46.194175 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 35042aa57dd2aeed848c624aa7f69decfb18e83c3b217eca1fdacfc4a10831db
215 tests/markdown/test_observation_edge_cases.py test_observation_edge_cases.py source_code 4295 2025-06-21T00:01:46.209598 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 6f1ab7684bfa8b02af0e0c42b3d7aed91fcd0b1c6288cbc9b6d6943c166979fa
216 tests/markdown/test_parser_edge_cases.py test_parser_edge_cases.py source_code 5142 2025-06-21T00:01:46.213291 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) d511d7d1187e3c1d3a8d3e0ab9efc9e68e84115c46811e7a9dc21eca9a87d520
217 tests/markdown/test_relation_edge_cases.py test_relation_edge_cases.py source_code 3952 2025-06-21T00:01:46.203846 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) f8e2016308f84139342cc37ec6e389ffe52d7eb46f998ee327c7f0d5bba3ad9b
218 tests/markdown/test_task_detection.py test_task_detection.py source_code 401 2025-06-21T00:01:46.191444 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 37fe91a689742033a0eb76723557e31c2a85e591133383b3887572cb714db2a5
219 tests/mcp/conftest.py conftest.py source_code 1761 2025-07-08T20:39:18.351554 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 350b2ca42c52ef76e002deddb65fc0eccb9f8989bd26078ea1c100e3b93bae3f
220 tests/mcp/test_prompts.py test_prompts.py source_code 5989 2025-07-01T08:33:22.093691 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) e3b2ee5b4bc120b5043225ebdbf73eadd71be9c3d9d8e1fdd66a24a8ff11a780
221 tests/mcp/test_resource_project_info.py test_resource_project_info.py source_code 4885 2025-07-01T08:33:22.094085 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6a043736789b239c8d4ae00d1a5332a0fc3bed4038fe3388970ba97dc7431a93
222 tests/mcp/test_resources.py test_resources.py source_code 549 2025-07-01T08:33:22.094481 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 77a6396b9b8a9d3b3d202e438243ca6178d87fd7680c9fc063ee013f2042aa4c
223 tests/mcp/test_tool_build_context.py test_tool_build_context.py source_code 3714 2025-07-01T08:33:22.094661 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 5f0a39fdb652605335ed54aa91fe994262441bb7ba46f381c46ebca882fc23f2
224 tests/mcp/test_tool_canvas.py test_tool_canvas.py source_code 7997 2025-07-01T08:33:22.094982 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) c517bb1f9edc77b76fbeb483dfcb2dd57de9f0b80a10738708bc90cbe388dc9e
225 tests/mcp/test_tool_delete_note.py test_tool_delete_note.py source_code 4208 2025-07-01T08:33:22.095578 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 4cbf3ba4313438651b0608d5b25faf1400f7bd6a78af630351aecd4457800895
226 tests/mcp/test_tool_edit_note.py test_tool_edit_note.py source_code 13503 2025-07-03T18:43:10.084890 Drew Cain 2 Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) dd441969aa54b3739f2d9202d3feaf46aee807ec51e01a13ef41eda52743944f
227 tests/mcp/test_tool_list_directory.py test_tool_list_directory.py source_code 7730 2025-07-01T08:33:22.096737 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) cfbb5c06bcfe74ea60f31051eb9b03ac80b86232b053c188bf3b172132f7c0f5
228 tests/mcp/test_tool_move_note.py test_tool_move_note.py source_code 25851 2025-07-30T09:39:45.727347 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co) c9f95158503fa42e37532aaa35027538fa49cea718d059d570cf547d31f01043
229 tests/mcp/test_tool_read_content.py test_tool_read_content.py source_code 19442 2025-07-30T09:39:45.727526 jope-bm 1 jope-bm (joe@basicmemory.com) 1817e32f997b06dbdabf02864d0c70584c1cd80bf1fae8dfbef649de79434bc5
230 tests/mcp/test_tool_read_note.py test_tool_read_note.py source_code 22991 2025-07-30T09:39:45.727746 jope-bm 4 jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co); Amadeusz Wieczorek (git@amadeusw.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 4e13ea9ff977525aa62b6a8c22505ffe989329dc3e7d07b71e044cf43ef3ddfd
231 tests/mcp/test_tool_recent_activity.py test_tool_recent_activity.py source_code 3983 2025-07-01T08:33:22.098561 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 45414152cd45334b84df56f653146b7bd2939907548e7245a37207fdccfc4c87
232 tests/mcp/test_tool_resource.py test_tool_resource.py source_code 6782 2025-07-01T08:33:22.098948 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 3916ae1c4d458eee53d2c570b9bbb708ecd617de5a145c3a7552389954bc9195
233 tests/mcp/test_tool_search.py test_tool_search.py source_code 10959 2025-07-03T18:43:10.085177 Drew Cain 3 Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 07839cf8e89d74ba05ec1dc55fa842444c1f0cc3d1587823b0ac3458ad89e87c
234 tests/mcp/test_tool_sync_status.py test_tool_sync_status.py source_code 6331 2025-07-01T08:33:22.100021 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 3f3566df5b9f3c66bb9b3bd77d4c05825480d0e85015305cb1b7ab9429c7a7ec
235 tests/mcp/test_tool_utils.py test_tool_utils.py source_code 9228 2025-07-01T08:33:22.100561 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) b50789f7a78b0169f52dc8bd16e262510ed4a0ac130fce698d4be84af1fe2d31
236 tests/mcp/test_tool_view_note.py test_tool_view_note.py source_code 10004 2025-07-01T08:33:22.101164 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 385535ce791c3ab55db827cbcf27ea25ae85faf3a027f52df4eea052cede7f36
237 tests/mcp/test_tool_write_note.py test_tool_write_note.py source_code 35136 2025-07-30T09:39:45.728137 jope-bm 3 jope-bm (joe@basicmemory.com); Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 45172de20cfc4f4436287573b4f5a94b8b616a360647af67ba1c6c343ccd41b8
238 tests/repository/test_entity_repository.py test_entity_repository.py source_code 15498 2025-06-21T00:01:46.176265 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 8f5d1ff6cff7b640993ff73f463b1e7801a4dd69640fc55fa23dc58b865e9943
239 tests/repository/test_entity_repository_upsert.py test_entity_repository_upsert.py source_code 16983 2025-07-03T18:43:10.085482 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com) ee0ecd6f4ff8b31838bd6de6fb1018af437e02813d8315c06f4e20a6b78a0572
240 tests/repository/test_observation_repository.py test_observation_repository.py source_code 11786 2025-06-21T00:01:46.158862 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co) 3006d392ad94dcc9b3ae031d63a1af6935624534389dd025de70d4c9e112aa0b
241 tests/repository/test_project_info_repository.py test_project_info_repository.py source_code 1186 2025-06-21T00:01:46.161876 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) dbce7ae7350403cbc37343c2e3e68e4d2255e247006aaa6c495a7ce8bfddea7d
242 tests/repository/test_project_repository.py test_project_repository.py source_code 10511 2025-07-30T09:39:45.728536 jope-bm 2 jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 279a3324635b1b515c18003bd1875cf230fef1192cadef9ca426846783717e75
243 tests/repository/test_relation_repository.py test_relation_repository.py source_code 11980 2025-06-21T00:01:46.167567 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co) fcdbdf40f7b56145b98eedde5ead64ef6187e859adde48cc09f5a97754757428
244 tests/repository/test_repository.py test_repository.py source_code 5996 2025-06-21T00:01:46.152691 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 3a6d4ca0b7a438a30ae504f5eac22874d4e1a937a4246e90efad46c90f0e1c95
245 tests/repository/test_search_repository.py test_search_repository.py source_code 25471 2025-07-03T18:43:10.086041 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 805b5cc55a53ae9d6e86c0de735c7f38ac9fd92c23194b0150fa3e7f5507a146
246 tests/schemas/test_memory_url.py test_memory_url.py source_code 2000 2025-06-21T00:01:46.346515 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 946fa6c48534f41c8b9cc27bbad164d3e3673a79afa84fbb439fae4cd26c504b
247 tests/schemas/test_memory_url_validation.py test_memory_url_validation.py source_code 9338 2025-07-01T08:33:22.103247 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 7054c3a3cdb8e05d2ffd254ed298908ff3d48a0a3927d960975eae1ab9beeb38
248 tests/schemas/test_schemas.py test_schemas.py source_code 16880 2025-07-03T18:43:10.086337 Drew Cain 3 Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 2c746a00c340f063c160ace67569955a300a294040949695c79fd4ae124fb611
249 tests/schemas/test_search.py test_search.py source_code 3264 2025-06-21T00:01:46.343685 github-actions[bot] (AI Assistant) 2 github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 7437775d1b4a61a438cbabe3dbb1becd2adc4dbcc0c24595830c0843fa7a3166
250 tests/services/test_context_service.py test_context_service.py source_code 8833 2025-06-21T00:01:46.446200 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) ab49d21a05d5364e7e8a2f00b07aabe1109b7417698e0acd92792e2cb5f22732
251 tests/services/test_directory_service.py test_directory_service.py source_code 6854 2025-06-21T00:01:46.457258 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) a0c765fb6bd1a75b6ba4592b1df344c48273b6ab03ba6bd15d2b5e840eb2d521
252 tests/services/test_entity_service.py test_entity_service.py source_code 59278 2025-07-03T18:43:10.086674 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) d7d495756faa10b415d705cf26b69d2efbc5f190a834b6767386ff296629ea71
253 tests/services/test_file_service.py test_file_service.py source_code 5243 2025-06-21T00:01:46.460141 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 83e6e9a7584b7baeccbc84865d8c1d3e2a0223e7d2b9c066d3799c289196ab7d
254 tests/services/test_initialization.py test_initialization.py source_code 6939 2025-07-08T20:39:18.351946 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ebb8d11ff37dcb36c4844c68f873c8e0e8aa6ab1b82abb22ee96c4233e6ab287
255 tests/services/test_link_resolver.py test_link_resolver.py source_code 13618 2025-07-01T08:33:22.105766 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) c9fc56d0c3572770e95ca6175c213ed8b44c2a6ddddc298b5662cc2d8f41014e
256 tests/services/test_project_service.py test_project_service.py source_code 28152 2025-07-30T09:39:45.728920 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 430868ed796518937a3b0bed4d8c33be53264a6100d61d54b0745bb2b58bf29a
257 tests/services/test_project_service_operations.py test_project_service_operations.py source_code 4679 2025-06-21T00:01:46.442141 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) d4103b6c5fff7e74ba84b86033537b8522aa60fc75aec4974d52e1dfec68b1b4
258 tests/services/test_search_service.py test_search_service.py source_code 25299 2025-06-21T00:01:46.439133 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) b3e58258db2b727ab8a1792e52fcb038d3a0779bcd1b70f2d295a9d06e5e2513
259 tests/services/test_sync_status_service.py test_sync_status_service.py source_code 9926 2025-07-03T18:43:10.087266 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 00245ca356e33fc7afcd2d42cb5ec082038c95383d738067821cf1e8d23c9a4b
260 tests/sync/test_sync_service.py test_sync_service.py source_code 39659 2025-07-01T08:33:22.107088 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 0609aee9906549f27fc4289b5ac2d46a50e53e12a0615a2701f17fbe9a74e169
261 tests/sync/test_sync_wikilink_issue.py test_sync_wikilink_issue.py source_code 1959 2025-06-21T00:01:46.420370 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) db4b98dd7008a1a26b0fd02b9a2d03bca950b281efc19d98a91b017c500b23a5
262 tests/sync/test_tmp_files.py test_tmp_files.py source_code 5930 2025-06-21T00:01:46.424277 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) cead95a07b29f6c1ab933abe67f9648122f5f33d33753c8e4ab4934af96cbc25
263 tests/sync/test_watch_service.py test_watch_service.py source_code 14076 2025-06-26T09:28:51.860679 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) e324a4127f8bfe59798f3a08125488c701e88239b7482d87a0394b91c55b893e
264 tests/sync/test_watch_service_edge_cases.py test_watch_service_edge_cases.py source_code 2169 2025-06-21T00:01:46.409830 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) b87c5015330e9b551630aab7ef95ac1e7663f0ce46e912d8155b319e139559aa
265 tests/test_config.py test_config.py source_code 3680 2025-07-03T18:43:10.087558 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6a246431cff739274efda895dfa28f38710289a73e645312f5fa05fce0885ec8
266 tests/test_db_migration_deduplication.py test_db_migration_deduplication.py source_code 6166 2025-07-08T20:39:18.352690 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com) ab768e061531ca2edcd598c101b5d884e549f90c7a4b305a23713a199d263fbb
267 tests/utils/test_file_utils.py test_file_utils.py source_code 6199 2025-06-21T00:01:46.219673 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 4615bb20b92b8a79d9e086301f852b34c3360f17ecc209920d68528268ddeed4
268 tests/utils/test_parse_tags.py test_parse_tags.py source_code 1691 2025-06-21T00:01:46.223400 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) b83afdf18dac8b3411bd0fab1301683217d3d1abceaf2d22c09e79c709efd8eb
269 tests/utils/test_permalink_formatting.py test_permalink_formatting.py source_code 4193 2025-06-21T00:01:46.225738 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) d998a4ca846c78679728a918b6f71df67c30b6961ede3cffa609f2cf6ad160eb
270 tests/utils/test_utf8_handling.py test_utf8_handling.py source_code 5791 2025-06-21T00:01:46.228483 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 50ec792e572af51cd2d996d4ddacbcfd3bbd79aa928818a90c42117451ea829f
271 tests/utils/test_validate_project_path.py test_validate_project_path.py source_code 15346 2025-07-30T09:39:45.729224 jope-bm 1 jope-bm (joe@basicmemory.com) 27a4bf2a9d277e6cd773619e5241b20175d211ec60c284361bec404fd99143f3
272 uv.lock uv.lock other 232710 2025-07-30T09:39:36.619155 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) caa1e3f6e66464b884a78cab77df2fe303fa3c0dc573be0196474b6d3c150581
File diff suppressed because it is too large Load Diff
@@ -1,272 +0,0 @@
path,name,category,size_bytes,modified_time,primary_author,contributor_count,contributors_list,sha256_hash
.claude/commands/release/beta.md,beta.md,documentation,3103,2025-07-01T08:33:22.037438,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),345f06e3f304470a8892e96979355919c8e5d8f4212ebbe543c3272fd15a5cbe
.claude/commands/release/changelog.md,changelog.md,documentation,4648,2025-07-03T18:43:10.075727,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),ea2484352f4f881aa71f546dcf84b0882d60c96673a88406aa2f7e12a282a1f4
.claude/commands/release/release-check.md,release-check.md,documentation,3344,2025-07-01T08:33:22.040060,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),7dd41a40e95c89153936c4ea60a3c989eccd5973fff9f6caef156de82f12bd04
.claude/commands/release/release.md,release.md,documentation,2925,2025-07-01T08:33:22.043674,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),0e4afd788fcf85fbe46094452e9b92a4290c3e23702283e72ee56aa9288476e5
.claude/commands/test-live.md,test-live.md,documentation,18720,2025-07-03T18:43:10.076403,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),21a9edeefb3e5673d833151a2fd0464087c0f0a9d056fdd7fca474c1d177d6fc
.dockerignore,.dockerignore,other,623,2025-07-01T08:33:22.048301,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),b3079bffad89d07186c9166725075524ffc07ceea6dc1d53fdbea6dca9585684
.github/ISSUE_TEMPLATE/bug_report.md,bug_report.md,documentation,967,2025-03-01T14:22:50.968994,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),0d34443ddda84a5e9726c8268083c9646d2cc64ff9c5ebf56c89587cef4c7706
.github/ISSUE_TEMPLATE/config.yml,config.yml,configuration,374,2025-03-01T14:23:26.936836,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),f4e21bb9b7f053509b6c08548a2649a5d62f44f0106810aa8c915d2f29acc623
.github/ISSUE_TEMPLATE/documentation.md,documentation.md,documentation,557,2025-03-01T14:23:40.020117,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),78f80a6b9a2b954695f74a847a068e0b780da6020572401c13c931555a4a9db5
.github/ISSUE_TEMPLATE/feature_request.md,feature_request.md,documentation,786,2025-03-01T14:23:06.344081,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),cee834de442221d53f3d2eeb85b81f3a641b3a7cb587ef8a169452305007439d
.github/dependabot.yml,dependabot.yml,configuration,529,2025-03-15T10:40:16.936258,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),8c6c2ef9a1417da63adb49c70f45524129bd5c46bbd7452b6b97cfe65205f89d
.github/workflows/claude.yml,claude.yml,legal,4628,2025-07-03T18:43:10.076958,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),d026f1756310781b85abc491b454cc31b52dfce4b951cdf8a4a555b6b047604c
.github/workflows/dev-release.yml,dev-release.yml,configuration,1405,2025-06-03T08:31:42.915367,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),58bcfc9e11e210e02dee76aabe7d1378da4edafb7f61857049ee6ccee9d45ffa
.github/workflows/docker.yml,docker.yml,configuration,1621,2025-07-01T08:33:22.049870,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),c918a86f2b16ebb45182a73c45272bcd6cea7022cc6e0eaeb2b2a24d8eebfe4d
.github/workflows/pr-title.yml,pr-title.yml,configuration,887,2025-02-17T16:29:35.733646,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),b6df62a20235496feba4d9419c85d6e8446c4d447ad69e7f57b51cc8953f68f4
.github/workflows/release.yml,release.yml,configuration,2479,2025-07-03T18:43:10.077261,Drew Cain,2,Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co),163307a99e2f3d317af6ae8927e1537654d88bf574a6958ac0bcd2d4de266b2a
.github/workflows/test.yml,test.yml,configuration,1426,2025-07-01T08:33:22.054430,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),ab09084a11be8adfc83fc6285c652fbe44a4584aa7d7d3658fcf0c57112d1640
.gitignore,.gitignore,other,566,2025-07-01T08:33:22.055006,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),358658b0faa8c3145e539421deba4ad0bac4a8ea2e608b2c2f867a822c67b36f
.python-version,.python-version,other,5,2024-12-02T16:41:37.012830,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),7b55f8e67b5623c4bef3fa691288da9437d79d3aba156de48d481db32ac7d16d
CHANGELOG.md,CHANGELOG.md,documentation,53647,2025-07-06T10:34:45.550847,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); semantic-release (semantic-release),0b165eb18975ff253cde08f4687b7808ac6ce63b6d8c10912c495a76c9058d1a
CITATION.cff,CITATION.cff,other,302,2025-02-17T16:29:35.735146,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),bc4878cf6f80bb64655f3f93a5821ffe0924ad8311c784b5f81a36093fcffdda
CLA.md,CLA.md,legal,1342,2025-03-01T14:12:38.435132,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),c238c7ff4f2b8451c6bba1bbacd10b04fb7d41d9a9cd76a62644d8193791d169
CLAUDE.md,CLAUDE.md,legal,12310,2025-07-03T18:43:10.078147,Drew Cain,4,Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); Ikko Eltociear Ashimine (eltociear@gmail.com),751f10c1a9a9c0ba2054d9c9cd36cf0989d6a347491fd505bb6504ee74b12900
CODE_OF_CONDUCT.md,CODE_OF_CONDUCT.md,documentation,509,2025-02-17T16:29:35.735238,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),b276b464e85eb1e5d9f09d90bba9573508a7bf81d2eeb38d0f25bd9f2fa66198
CONTRIBUTING.md,CONTRIBUTING.md,documentation,7329,2025-07-30T09:39:36.607158,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co),ca822fe39cc529bfc3822d317ab8a20fd141ec3ecb8c20de9e2eca3edce1c282
Dockerfile,Dockerfile,other,856,2025-07-01T08:33:22.056908,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),c45501905423990e31ffdb8ae4e976eca6423c6784905c1036bcc7795b70feef
LICENSE,LICENSE,legal,34523,2024-12-02T16:41:20.739392,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),8486a10c4393cee1c25392769ddd3b2d6c242d6ec7928e1414efff7dfb2f07ef
README.md,README.md,documentation,16261,2025-07-03T18:43:10.078281,Drew Cain,6,Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Jason Noble (github+jasonn@jasonnoble.org); Marc Baiza (43151891+mbaiza27@users.noreply.github.com); Matias Forbord (codeluggage@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),2c4b82b30f49ab465617034fda999157450ed168f8e097fe8e9ca7ac65a197a7
SECURITY.md,SECURITY.md,documentation,303,2025-03-12T16:04:01.989927,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),96932268b8fe737577941b49e9dff5ff8ad59c11cc3f9a016989589ef5c98c1f
docker-compose.yml,docker-compose.yml,configuration,2534,2025-07-01T08:33:22.059281,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),4a9fdfd2f0d42b148fe021eee77fd5e772c7ba9c2fe5d0cc310c8c7a64257417
docs/AI Assistant Guide.md,AI Assistant Guide.md,documentation,16423,2025-07-01T08:33:22.060491,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),c717869a3d356852077026591364ecb0589a6d53f9764af027c57d142ea7ff55
docs/Docker.md,Docker.md,documentation,9088,2025-07-01T08:33:22.060976,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),e13d3f50148353046b52d9d493986880f76f2914ef4efe1a759b2cc9d10913a4
justfile,justfile,other,5496,2025-07-30T09:39:36.608008,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com),a8df6c73ee8cf3eaa48e4cba193dffbc2821b843a8cc0c0cd58b8517f2252907
llms-install.md,llms-install.md,documentation,2307,2025-06-29T14:19:28.826375,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),674105eb85c8f1d59fc2ddc6840f2a2a3e293358687a305c37dffc8144304f97
memory.json,memory.json,configuration,90075,2025-06-29T14:19:28.827172,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),22d5eee995ca0548b7d9dd152f9a3690c42a514cbf38fea750b3ed2a17dd99c5
pyproject.toml,pyproject.toml,configuration,3279,2025-07-30T09:39:36.608791,Paul Hernandez,4,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); semantic-release (semantic-release),428a5d64d4555dc7cd2a81b89a3444bdb1d2f3017257fbc9160bd0ae982718e1
smithery.yaml,smithery.yaml,configuration,433,2025-03-11T16:36:03.389716,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),63f13499cf4fee0751b5005f5bd85597dc1c7f9a881f4ec8147cc23e95e75c2d
src/basic_memory/__init__.py,__init__.py,source_code,256,2025-07-06T10:34:45.551291,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@gmail.com); semantic-release (semantic-release),c946490ab9d64957d25df37f721392024916f8a81ed838f70a8e938b1c2637db
src/basic_memory/alembic/alembic.ini,alembic.ini,configuration,3733,2025-02-23T17:31:23.198433,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),20466c9c5f026db66793006bebb2f328a3686c7bb34da40d52f33c3eca29e717
src/basic_memory/alembic/env.py,env.py,source_code,2838,2025-07-08T20:39:18.336650,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),e241d987eadfcd5011cbd9ddbecb833d306de87937759d81c08d37d04a6906b0
src/basic_memory/alembic/migrations.py,migrations.py,source_code,718,2025-06-21T00:01:59.613806,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),96b8873d70dd04b48d5c45b7413a54d1224ab9577557ef0dad5929377a9fb144
src/basic_memory/alembic/script.py.mako,script.py.mako,other,635,2025-02-17T16:29:35.739659,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),304a8bfb6a804e5493f5300e79882c70c9f5bb2e87512f4d16f0e097dddd323f
src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py,3dae7c7b1564_initial_schema.py,source_code,4363,2025-06-21T00:01:59.595762,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),9536d69409ddd5eb3bc54f7d0e82607da45ed7f2ad7bc4cbf7cae2a9e28657cd
src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py,502b60eaa905_remove_required_from_entity_permalink.py,source_code,1840,2025-06-21T00:01:59.604717,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),93ac584e660f33d468b3eec203b070641298c282bf82655d0beda7f050237681
src/basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py,5fe1ab1ccebe_add_projects_table.py,source_code,4379,2025-06-21T00:01:59.601299,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),d82098f5aca3cdbada60c7229ea4ac262f527349eed9ef9e864509bacfaccf7e
src/basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py,647e7a75e2cd_project_constraint_fix.py,source_code,4210,2025-07-01T08:33:22.063463,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),604ac5908a5976272f52297866d13abb14a99390590935d9fd37cf13e32049fa
src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py,b3c3938bacdb_relation_to_name_unique_index.py,source_code,1428,2025-06-21T00:01:59.598551,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),46c1b2990cdf4575752d234a8b28b494c8a54f15b5360c12f6347aef27b66a92
src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py,cc7172b46608_update_search_index_schema.py,source_code,3679,2025-06-21T00:01:59.610909,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),9036af47d42ac7dc12bb93f7a9de1266aff0688b031b550c4e0d92983a24b4c5
src/basic_memory/api/__init__.py,__init__.py,source_code,72,2025-06-21T00:01:59.579981,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),c02a63fb6d63d43d0acca97d425eae9cb055158d0ad6e1a9fc57926512adaa99
src/basic_memory/api/app.py,app.py,source_code,2897,2025-07-08T20:39:18.337081,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ad673ac2c09410af5830f6afdffaa28b0040480dacd52ff01750b8b2c066e8cf
src/basic_memory/api/routers/__init__.py,__init__.py,source_code,398,2025-06-21T00:01:59.549416,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),4443b908a43dea8e6fb4658009cb08c48a56c9b21449e29773cdd159b59cf014
src/basic_memory/api/routers/directory_router.py,directory_router.py,source_code,2054,2025-06-21T00:01:59.567371,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ac1407beec1f7d43a4d222af704b3642559c960bebf2eaf6e1ffe91f7fac5514
src/basic_memory/api/routers/importer_router.py,importer_router.py,source_code,4513,2025-06-21T00:01:59.561600,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),c45502a2b90f5a8f00174c9ad14adc2e65cd7fc0a33d9740a9d750207f0f3bea
src/basic_memory/api/routers/knowledge_router.py,knowledge_router.py,source_code,9738,2025-07-01T08:33:22.064119,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),e1d0ffb4fa5c246582ba844a11b417092de1a173567be55b7060bbc55fbc5681
src/basic_memory/api/routers/management_router.py,management_router.py,source_code,2730,2025-07-08T20:39:18.337438,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),cdbce294dcec6146c56c4dae15744cdf77039fd21b23eef7cbc0b5f9bfb5f4ee
src/basic_memory/api/routers/memory_router.py,memory_router.py,source_code,3003,2025-07-01T08:33:22.064510,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),6bd0a7c775e0c1290efb6001173237c0cdcfa0c1b1bb27c9145a7b35f1596a97
src/basic_memory/api/routers/project_router.py,project_router.py,source_code,8444,2025-07-30T09:39:45.721369,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),6539c69f1d506c65d14cc2c151876b19057f37d84d51a9e6f464ed47a4692648
src/basic_memory/api/routers/prompt_router.py,prompt_router.py,source_code,9948,2025-07-01T08:33:22.065221,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),e30c6aebe35112054933c37d0b462c3750005030fc9e44c23b35b2cd2a93485c
src/basic_memory/api/routers/resource_router.py,resource_router.py,source_code,8058,2025-06-21T00:01:59.570399,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),584244a84698ff24ca8f9f94fab5b891740a51c25f972920c08ebf83f478d5c9
src/basic_memory/api/routers/search_router.py,search_router.py,source_code,1226,2025-06-21T00:01:59.543020,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),183eb68e50904e22ff54db1d89b8be6f57fa1f8d0a096a3d497d8297b607e105
src/basic_memory/api/routers/utils.py,utils.py,source_code,5159,2025-07-03T18:43:10.079143,Drew Cain,2,Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),9e60f57da24e1dc9d64236c26a7a2353003dc618a77fbeb853c494aa3357a57c
src/basic_memory/api/template_loader.py,template_loader.py,source_code,8607,2025-06-21T00:01:59.576132,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),7b16d3797c89460c8b1664a335eae8c634fb5f60d6166d97e6a5156b776b6d83
src/basic_memory/cli/__init__.py,__init__.py,source_code,33,2025-06-21T00:01:59.456735,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),6ab70a2c05910e13c3ef1e6df34325be265e633c076741998a0cb2dcd295a069
src/basic_memory/cli/app.py,app.py,source_code,2268,2025-07-08T20:39:18.337708,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),f710670380d707c4458ad881f7a25bb3c5cdb2bcff59f139f37421264fe8f2f8
src/basic_memory/cli/commands/__init__.py,__init__.py,source_code,393,2025-07-08T20:39:18.338070,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),de8a23702f98fb844fa9f7fdbedc16ce24ff4f8baf05589c2f4a521cd8a55645
src/basic_memory/cli/commands/db.py,db.py,source_code,1480,2025-07-08T20:39:18.338326,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),704a1096d80ab9d12e247d029fe6223e935a0d0ceee7e615c020f46a720828e0
src/basic_memory/cli/commands/import_chatgpt.py,import_chatgpt.py,source_code,2856,2025-07-08T20:39:18.338787,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),8957cca3acab6351335624a518bdc19d956487e93d86dd1b6c2309e9d5850ae5
src/basic_memory/cli/commands/import_claude_conversations.py,import_claude_conversations.py,legal,2946,2025-07-08T20:39:18.339053,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),7bc97838732bf00f4fb4a80e3ba4fdfbce8971ae85cc2ac902c3b3a3e110ae57
src/basic_memory/cli/commands/import_claude_projects.py,import_claude_projects.py,legal,2891,2025-07-08T20:39:18.339310,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),6321577075801c99ad47a0cdc13b5aa3c9ca102a05ca8f06ae2a51125a8c43bc
src/basic_memory/cli/commands/import_memory_json.py,import_memory_json.py,source_code,3013,2025-07-30T09:39:45.721637,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),7ea193f6720b2672290ad16c79a143a31d88a1d567838f12b9ebf9110a7c0338
src/basic_memory/cli/commands/mcp.py,mcp.py,source_code,2593,2025-07-08T20:39:18.340025,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),3e1be3d27b18ef4743204b7cf8a37fbc2a1a91fc1636c468e08cefcd8597bac6
src/basic_memory/cli/commands/project.py,project.py,source_code,13836,2025-07-30T09:39:45.721838,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),af3e6f30410ab48355f4b1a46aaa3aa5423cb0a0490f51447db1d25e09f39916
src/basic_memory/cli/commands/status.py,status.py,source_code,5809,2025-07-08T20:39:18.340324,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),b6d0ea237d714d054f69b24a2058beba74e224a2fa4a80a525d62a2aa2ea2d70
src/basic_memory/cli/commands/sync.py,sync.py,source_code,8213,2025-07-08T20:39:18.340627,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),703b3e73c4c33850c27f6f4c07abbf5d4ce5dc43f706bf0c43264bbb45f8b213
src/basic_memory/cli/commands/tool.py,tool.py,source_code,9540,2025-07-01T08:33:22.070903,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),9b2fa400b9f7921bf55b602f8bbdfa36b1ec7e4a5bc8fe7b9838b92e31f0a9ed
src/basic_memory/cli/main.py,main.py,source_code,465,2025-07-08T20:39:18.340868,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),7a405b12bff98e319968526238ae15859df0362bba5a3857562fecca47b5c70d
src/basic_memory/config.py,config.py,source_code,12487,2025-07-30T09:39:45.722031,jope-bm,5,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),86341330a041b94d38f5e4f1b94f3509d74351c67cc826af1a4ff57313e5f726
src/basic_memory/db.py,db.py,source_code,7428,2025-07-08T20:39:18.341550,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),25e053b0b3f9e8fa823b05fc099001efe8a3a4f6e0f80728e5079ba45aa406d8
src/basic_memory/deps.py,deps.py,source_code,12144,2025-07-08T20:39:18.341870,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),bce28cd9ca549b8afce0bc689cc1fb2dc80cd5b929f85114aa682bb8ae82411d
src/basic_memory/file_utils.py,file_utils.py,source_code,6700,2025-06-21T00:01:59.587200,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),79ac5328b2c46d3232fcc6ff22ffc39ade085c0252ad91958be2a7ae9c9c8b71
src/basic_memory/importers/__init__.py,__init__.py,source_code,795,2025-06-21T00:01:59.295471,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),0537015bdecfded85cb166b9c3db50b2f3aef329c70e0c36fbcb4f824099a21f
src/basic_memory/importers/base.py,base.py,source_code,2531,2025-06-21T00:01:59.305335,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),6b0c1efd4f827f348834aa0cea2ae8eebbb842a2e5b1f5f3747ced0ef79b9f13
src/basic_memory/importers/chatgpt_importer.py,chatgpt_importer.py,source_code,7372,2025-06-21T00:01:59.308956,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),dfa540b196ab23b5c4e6bfcac4da567873351cf7c78fa35f4c3b87e84c58e218
src/basic_memory/importers/claude_conversations_importer.py,claude_conversations_importer.py,legal,5725,2025-06-21T00:01:59.288540,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),a7bc9e872f4f81ce5f79fe9dd1a6fd0f00a6f020a2cb2664184a97f14eeb1dbc
src/basic_memory/importers/claude_projects_importer.py,claude_projects_importer.py,legal,5389,2025-06-21T00:01:59.291737,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),a452675fd9bb18ebf64eb4bd7f69ccd7e993b617931015a3c4ab700d67493863
src/basic_memory/importers/memory_json_importer.py,memory_json_importer.py,source_code,4631,2025-07-30T09:39:45.722316,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),bc7d045299f17ed9ad5cebff7b1423250ec28a11130e47a0ac48d3ab82bdeafc
src/basic_memory/importers/utils.py,utils.py,source_code,1792,2025-06-21T00:01:59.298428,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),ee7e22fd4812362394f62ed8944abe80738c1e3ab88131faf639f4a8ccfd6c89
src/basic_memory/markdown/__init__.py,__init__.py,source_code,501,2025-06-21T00:01:59.316337,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),0ddce2a025ad0e729aab4e411d82e02ffefc15ac041cba4b5e7a7e90f4957c87
src/basic_memory/markdown/entity_parser.py,entity_parser.py,source_code,4516,2025-06-21T00:01:59.321461,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),1b0d1177358645ccbadd170b3f62760dc9bd838d38c741978ab0da5836234d68
src/basic_memory/markdown/markdown_processor.py,markdown_processor.py,source_code,4968,2025-06-21T00:01:59.325241,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),4b99f2ebdceed9d96a3bbb562682eba4b4b383c7a64080eaec3bbba25a4952c9
src/basic_memory/markdown/plugins.py,plugins.py,source_code,6639,2025-06-21T00:01:59.312733,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),82d2332918e866ccaf06a2e954d9eb9b397f71b4d9e591a7f2472e5f1423464a
src/basic_memory/markdown/schemas.py,schemas.py,source_code,1896,2025-06-21T00:01:59.329982,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),7b2c580abd615725a62267257b46ac13922dfc30fa01192a681658bb528ae67e
src/basic_memory/markdown/utils.py,utils.py,source_code,3410,2025-07-03T18:43:10.079889,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),1b757f0d06e60e3ea276c0b2ea44fe1a156a895e093c1e607d6286e702bf4ae4
src/basic_memory/mcp/__init__.py,__init__.py,source_code,35,2025-06-21T00:01:59.404757,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),76c0ce84aaa361e21b0942db1c8c5f708b536eab9d12e120d4da7ce9eab41844
src/basic_memory/mcp/async_client.py,async_client.py,source_code,925,2025-07-30T09:39:45.722547,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),6531f4387f30a30a322197ff51d6eacae4b25a58c648095931b3d1145d1da303
src/basic_memory/mcp/project_session.py,project_session.py,source_code,4203,2025-07-08T20:39:18.342906,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),4da403ec93dec9063eeb8b05246e3501740d162cc77a067d3ce54ba43d690819
src/basic_memory/mcp/prompts/__init__.py,__init__.py,source_code,615,2025-07-08T20:39:18.343263,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),f8197d0e08f64c3f4f50b8f38203ea5eebcf123582472ed2f58834de1dbe53b0
src/basic_memory/mcp/prompts/ai_assistant_guide.py,ai_assistant_guide.py,source_code,821,2025-06-21T00:13:53.262832,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),f13239c4e6e2455730bfac3d6f2d71407957d3086fc84efe2c6b22a833114c58
src/basic_memory/mcp/prompts/continue_conversation.py,continue_conversation.py,source_code,1998,2025-06-21T00:13:53.263309,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),aec9a50b657b7bb1ba0c02b42bcdb9bc5b0f2a0b1143bd361c5ce7ea59076833
src/basic_memory/mcp/prompts/recent_activity.py,recent_activity.py,source_code,3311,2025-07-01T08:33:22.073986,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),d2fd5cddbd927430f15d5b85f1e3a3368a18cb4e2e4587a5d29749d2b9e0830e
src/basic_memory/mcp/prompts/search.py,search.py,source_code,1692,2025-06-21T00:13:53.264093,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),9dbf3c319cbdb5d5bf32608b51522d8ae92b2dd6f7c441d62efd0954b525738a
src/basic_memory/mcp/prompts/utils.py,utils.py,source_code,5431,2025-06-21T00:01:59.425042,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),55a72b6eac18b724a9225608aca1e8e6cea3b684ccb1c609aab1511f7ce90ba4
src/basic_memory/mcp/resources/ai_assistant_guide.md,ai_assistant_guide.md,documentation,14777,2025-03-28T19:12:38.653627,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),aa76160e46256fe2662ae3a86799659916acfede1d5835c1fe2f0b136efb2e0b
src/basic_memory/mcp/resources/project_info.py,project_info.py,source_code,1906,2025-06-21T00:13:53.264918,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),2dc5244f1e225c17d4e8ba784d5721efc3aa2e8a5b3b2e258f22a77ebe155eca
src/basic_memory/mcp/server.py,server.py,source_code,1248,2025-07-08T20:39:18.343419,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),0a910cae27d61ea923e90fbe2d4b5c7cacf3b29d3e156f0b7f77872ab973c761
src/basic_memory/mcp/tools/__init__.py,__init__.py,source_code,1619,2025-07-03T18:43:10.080029,Drew Cain,2,Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co),872b771dd52ec3b763645a2baf6e10a70f049ce3106830a6d3f053b3e09a5fe6
src/basic_memory/mcp/tools/build_context.py,build_context.py,source_code,4611,2025-07-03T18:43:10.080426,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),724280b77b8f5f3e61cd3fdeebc3ee66e2bcfedaaea3638e6a2e2e33fcb1978e
src/basic_memory/mcp/tools/canvas.py,canvas.py,source_code,3738,2025-07-01T08:33:22.076204,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),db617d1bd81f3dbfa5f22d41e6b6b825afe1f73618f37acf63d9830390b98246
src/basic_memory/mcp/tools/delete_note.py,delete_note.py,source_code,7346,2025-07-01T08:33:22.076726,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),b52c9173f5600662f255e7a70a5c17d5293ef8b29c19a8403334d7da62b65c8b
src/basic_memory/mcp/tools/edit_note.py,edit_note.py,source_code,13570,2025-07-01T08:33:22.077181,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),ab8c7e7fbfa3fe5fb0ce6d7bf805454f5fd6182a340aae25237b1e6127b6d5a6
src/basic_memory/mcp/tools/list_directory.py,list_directory.py,source_code,5236,2025-06-21T00:13:53.267453,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),f85c43b02aeee580f4d8ce2a9100c0bab1095b245a0bb608e1847ace0d1ab51f
src/basic_memory/mcp/tools/move_note.py,move_note.py,source_code,17541,2025-07-30T09:39:45.722919,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),5ef27d9baaebc2c5656fc49c4798a4e4e40a70b6cd9d1e761dbcae021bad558c
src/basic_memory/mcp/tools/project_management.py,project_management.py,source_code,12944,2025-07-06T10:34:45.552403,Drew Cain,2,Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co),cdac736d65129f689a85c6b82f8e043bc84a31657ead7a8331771171eeaa860f
src/basic_memory/mcp/tools/read_content.py,read_content.py,source_code,9125,2025-07-30T09:39:45.723469,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),96a13addac507fd874394dfa2e1e5ed71af5eb89d0872c1d68cacf9cac3f8724
src/basic_memory/mcp/tools/read_note.py,read_note.py,source_code,8132,2025-07-30T09:39:45.723828,jope-bm,5,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Amadeusz Wieczorek (git@amadeusw.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),dff53ce02b16d9c5fca315b3f09bb70867a89d614b0ef3f0548dce87978048ee
src/basic_memory/mcp/tools/recent_activity.py,recent_activity.py,source_code,4833,2025-06-21T00:13:53.269120,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),5d58cd2402679b1bf3c7dfcbb3503e40e776c9347ba499524d3b91c528af98de
src/basic_memory/mcp/tools/search.py,search.py,source_code,15883,2025-07-03T18:43:10.081557,Drew Cain,4,Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),8519b0057468c4450eb543a2f561bcd0d7cb96e1ce1b95e948e02b309ba6b7ca
src/basic_memory/mcp/tools/sync_status.py,sync_status.py,source_code,10517,2025-07-08T20:39:18.344231,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),73dd73b186eaec8b82e6b17e39a67c27082d24e0653dc4e4b35712bd83b80a22
src/basic_memory/mcp/tools/utils.py,utils.py,source_code,20892,2025-07-30T09:39:36.616253,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),a95004911e276822eba88a3fef15c5b9ba861b1ca9a2ecfe0c1e3fb2f4ef0116
src/basic_memory/mcp/tools/view_note.py,view_note.py,source_code,2499,2025-07-01T08:33:22.080024,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),75d357c72113b1d0394987e521a4158ff09b77b23b08b56cddab5144331b1a68
src/basic_memory/mcp/tools/write_note.py,write_note.py,source_code,6612,2025-07-30T09:39:45.723985,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),771396fb49d397feb5f7e36321d4d233c22c4d1b53414912c849957c6e96fc83
src/basic_memory/models/__init__.py,__init__.py,source_code,333,2025-06-21T00:01:59.437212,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),8f40b876d162f85384690291f1d416106f9d26d750d793414e2260e276c85cd5
src/basic_memory/models/base.py,base.py,source_code,225,2025-06-21T00:01:59.447249,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),e2101727c084d519e32a16f6de53ddf9033b1bf15721d4e8c0c27d6d18b1b295
src/basic_memory/models/knowledge.py,knowledge.py,source_code,7087,2025-06-21T00:01:59.444376,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),005c5f292f1f45ae372aadc48c9080b9fa6d7b854d0bb7ecf587ec843ac1e28d
src/basic_memory/models/project.py,project.py,source_code,2773,2025-07-01T08:33:22.080907,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),a14ad06943aeeff9ae4a5fa2dfc0e1d07ce6085acc02dc20c402c3513b7d9397
src/basic_memory/models/search.py,search.py,source_code,1300,2025-06-21T00:01:59.440923,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),3e143cc38b5a0294af8e1d43a4f841e1c1fd193b76136a68f83159ce19e86646
src/basic_memory/repository/__init__.py,__init__.py,source_code,327,2025-06-21T00:01:59.256363,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),3162bea3c42292accea5ee52c8f6ca4368a8079056034529cfae6d820f84d035
src/basic_memory/repository/entity_repository.py,entity_repository.py,source_code,10405,2025-07-03T18:43:10.082570,Drew Cain,3,Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),e2a8d1eba6c8d64bc61d7168df0fe8c29a670858bf9daeac464a0f440273fae0
src/basic_memory/repository/observation_repository.py,observation_repository.py,source_code,2943,2025-06-21T00:01:59.269646,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co),aa132f1cb4a36a84f715afdc40e2ac4f98d83e3eba1974b2b4404cc0b020ca04
src/basic_memory/repository/project_info_repository.py,project_info_repository.py,source_code,338,2025-06-21T00:01:59.252582,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),f172d50182a405643a19b2a3d62a80f4e2b41463c775394eb3b66d7de51ff6df
src/basic_memory/repository/project_repository.py,project_repository.py,source_code,3803,2025-07-30T09:39:45.724344,jope-bm,2,jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),9083ba214b2bcaaf98164d9c33cda7add52bc77f7269ecb6e43cdd5088728b7c
src/basic_memory/repository/relation_repository.py,relation_repository.py,source_code,3179,2025-06-21T00:01:59.246745,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),cfb3a8e59cff27e063e91bd00e949647bdd92e4d9fc46edeee3acc6de15e26f4
src/basic_memory/repository/repository.py,repository.py,source_code,15224,2025-06-21T00:01:59.273355,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),3096fe71bf106506cbf86aeafe2aafe0aabbe5a5f6c90a212c8aa1e53e1f171c
src/basic_memory/repository/search_repository.py,search_repository.py,source_code,21936,2025-07-03T18:43:10.082844,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),a972f73d1b71dac5770e8eb3793c6c9ac44e4e792f9dab528f8c4e6c6a80bcaa
src/basic_memory/schemas/__init__.py,__init__.py,source_code,1674,2025-06-21T00:01:59.514555,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),98480815c75379bfafe32d2090e87fa40e73caa19b664fbd5db5ea9528ba58cb
src/basic_memory/schemas/base.py,base.py,source_code,6738,2025-07-01T08:33:22.082193,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),171f7b0c4ab33abef2f7379eb1e3bda950586ce7edff83907fd12255f84e267e
src/basic_memory/schemas/delete.py,delete.py,source_code,1180,2025-06-21T00:01:59.502563,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),50047624af7d58c8f780ffb2a065a51c3dde64492f9534917fc42860813e59fc
src/basic_memory/schemas/directory.py,directory.py,source_code,881,2025-06-21T00:01:59.529023,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),17dfcbac9a91a9bfe43b4f060ca2735cb6f69e16d81b63dd554a397830ff29fe
src/basic_memory/schemas/importer.py,importer.py,source_code,693,2025-07-30T09:39:45.724633,jope-bm,2,jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ac33df423ca32b28ce7b6ea9c29d541f8783a86c0c29f78db35163bf93f139cd
src/basic_memory/schemas/memory.py,memory.py,source_code,5833,2025-07-03T18:43:10.083167,Drew Cain,3,Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),acb4a953a553feca672c4895798a7d948ec51f922f75b66dd7d55716e3bebed3
src/basic_memory/schemas/project_info.py,project_info.py,source_code,7047,2025-07-01T08:33:22.083248,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),7dc3635297b6e7fe6e3262b2d78da26f7a79a846a4cecd5624f2e4828979cf2c
src/basic_memory/schemas/prompt.py,prompt.py,source_code,3648,2025-06-21T00:01:59.525753,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),4a92157d9a6b413f04e6e3f8d23dc2a417369c729f9703a8de2643ec114f2071
src/basic_memory/schemas/request.py,request.py,source_code,3760,2025-06-21T00:01:59.511789,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),32fe44beb2d99452223ebf1d3a3a3fe105ef92c79885023b71dfd7db30ecc503
src/basic_memory/schemas/response.py,response.py,source_code,6450,2025-07-03T18:43:10.083478,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),5eea4660a2abe48d83ed083d1c2483fdcfb403e0b504f03c14d22f1cae866abb
src/basic_memory/schemas/search.py,search.py,source_code,3657,2025-06-21T00:01:59.522804,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),cb032c0c60102b6b0ed934f995cf9d6bf93aece296d71d537a75e8ae61c75afe
src/basic_memory/services/__init__.py,__init__.py,source_code,259,2025-06-21T00:01:59.655059,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),5c6b7c597ddf5ffd0af4bdfb32ccbc1c5f2794c658206dee43a9945fafe226d8
src/basic_memory/services/context_service.py,context_service.py,source_code,14462,2025-06-21T00:01:59.672086,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (paulmh@gmail.com),e1178b005e6a89f03d6b239e3c6b152aeb30d535a3f280734440a3aec16228f2
src/basic_memory/services/directory_service.py,directory_service.py,source_code,6017,2025-06-21T00:01:59.648501,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),fd838f5ec79033892777b3c8140868f4b57f13e1658d513959524a434b9f959b
src/basic_memory/services/entity_service.py,entity_service.py,source_code,30440,2025-07-03T18:43:10.083956,Paul Hernandez,4,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),7cd5163eca6b8a0772e838c8c869e478166763cd6a2d745b0b9aa5c25a65baee
src/basic_memory/services/exceptions.py,exceptions.py,source_code,390,2025-06-21T00:01:59.668908,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),a158d0af9d1742a9c5ab5f8c34a062948d9286d1c3c72a5abf20e4d547b21e1c
src/basic_memory/services/file_service.py,file_service.py,source_code,10059,2025-06-21T00:01:59.641785,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),8c2ae69c4913438b7d1c5ecbfcce812fbb5d0ea8e3cedcbd8694e8f40cf086f3
src/basic_memory/services/initialization.py,initialization.py,source_code,6715,2025-07-08T20:39:18.344482,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),4dde58b799cf4ac90631e59949b55b3355a93bdf99df0d9c2408c0cea67e10c4
src/basic_memory/services/link_resolver.py,link_resolver.py,source_code,4594,2025-07-01T08:33:22.084908,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),d7efd516cbea753e6b5411def09aeaeb7539f5743487119ecf163c736e138a8c
src/basic_memory/services/project_service.py,project_service.py,source_code,30125,2025-07-30T09:39:45.725079,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),bce95c00aa9a1da0a8c8090ee34b5b1494207f8c99e6c25de4ec43fddc38f6be
src/basic_memory/services/search_service.py,search_service.py,source_code,12782,2025-06-21T00:01:59.665439,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),7392b2d2e7f3ed83c5807855ccd450e0e79c17f254aedee700bce9323a1b5b83
src/basic_memory/services/service.py,service.py,source_code,336,2025-06-21T00:01:59.636167,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),57e77ff20395d3bcc62100e92fe2acaacdd937d977a9fdc764e2b57ff60d4da8
src/basic_memory/services/sync_status_service.py,sync_status_service.py,source_code,7504,2025-07-03T18:43:10.084229,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),0a025d689e8e16f1632872104952105fc904537f3d32bcff592eb1f1dc76fbb7
src/basic_memory/sync/__init__.py,__init__.py,source_code,156,2025-06-21T00:01:59.621137,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),0951e0b981f8e7b876bb6c6833c2af3a29490bbd5726567ea9487c947723623e
src/basic_memory/sync/background_sync.py,background_sync.py,source_code,726,2025-07-08T20:39:18.345062,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),549af64ae91128b76c6df07ef517b82de85ca4ad796be44b5c0141fac01d4513
src/basic_memory/sync/sync_service.py,sync_service.py,source_code,23387,2025-07-01T08:33:22.086507,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),0310b927561370f593980d07773bce641b618b8fbf2d9e3890d174290a0344fc
src/basic_memory/sync/watch_service.py,watch_service.py,source_code,15316,2025-06-26T09:28:51.859209,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),240ba6ac7523575945f4db442b7da3820d26c9605f2d7a2d357c4e35e215f523
src/basic_memory/templates/prompts/continue_conversation.hbs,continue_conversation.hbs,templates,3076,2025-07-01T08:33:22.086792,Drew Cain,2,Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),b6bac31d25c0e52d0909b2273285092f4e31bc219107f92ed511cd407b65e992
src/basic_memory/templates/prompts/search.hbs,search.hbs,templates,3098,2025-05-25T10:07:54.520119,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),1f570222c1caa78542d46ac7d8a79407ca467b9bd714fa9bd953e8b72a667820
src/basic_memory/utils.py,utils.py,source_code,8654,2025-07-30T09:39:45.725428,jope-bm,5,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); andyxinweiminicloud (andyxinweimin263@icloud.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),111f343e3367339730e081c08acf4613667be574f946126513b5d82da3086bed
test-int/conftest.py,conftest.py,source_code,8203,2025-07-08T20:39:18.345735,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),7231e1bc76cddc7c94433e853825dc0165b103bedf996c2c24eda6ae32fff5ad
test-int/mcp/test_build_context_validation.py,test_build_context_validation.py,source_code,6469,2025-07-08T20:39:18.346021,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),ee61d6b15cee0194fd74c1bcf9361d5a839f6081c7d571ef081da8fcefe1bbf2
test-int/mcp/test_delete_note_integration.py,test_delete_note_integration.py,source_code,13175,2025-07-08T20:39:18.346366,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),497495249b8bcf0af177d8705c6899875460707697980434e560cfe56f86feb3
test-int/mcp/test_edit_note_integration.py,test_edit_note_integration.py,source_code,19822,2025-07-08T20:39:18.346730,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),9ba704badd05808aee0b5a686b7e250e1c865229ec92dab561cba5287b6f6fc6
test-int/mcp/test_list_directory_integration.py,test_list_directory_integration.py,source_code,14744,2025-07-08T20:39:18.347106,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),2c4336a59de63f17ed252bb52d1330ebca901ad83bfebc9e5aa67f132a3c77c6
test-int/mcp/test_move_note_integration.py,test_move_note_integration.py,source_code,19825,2025-07-08T20:39:18.347344,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),cbb17688d0f9551306a7520b059ace7f7dfba70fc20bf0ec98b1148000d7ad7d
test-int/mcp/test_project_management_integration.py,test_project_management_integration.py,source_code,33175,2025-07-08T20:39:18.347658,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),93a43cd699453a7602a5def839740ba5027ab914eba3e7b66a026860ed6bd567
test-int/mcp/test_project_state_sync_integration.py,test_project_state_sync_integration.py,source_code,7728,2025-07-08T20:39:18.347907,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),80b0f5b5cd359498dcbd3af7dcb6194afa4582d8efb3faa38d28675ee45e0748
test-int/mcp/test_read_content_integration.py,test_read_content_integration.py,source_code,11187,2025-07-08T20:39:18.348233,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),6955dd343b40df088ac79175e52723501d83796335a16e43b7f58e16e757e1b8
test-int/mcp/test_read_note_integration.py,test_read_note_integration.py,source_code,1381,2025-07-08T20:39:18.348463,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),b93050da68d0e9d47d46879a386567f688399e28390f4eb31ee37353dc27514b
test-int/mcp/test_search_integration.py,test_search_integration.py,source_code,14690,2025-07-08T20:39:18.348959,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),e0957ada9dd9b12eae5843a37d241ec9293ca929f18281e31197dae36bff589d
test-int/mcp/test_write_note_integration.py,test_write_note_integration.py,source_code,9472,2025-07-08T20:39:18.349398,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),b2b56fee8351168d0c65640c295d969dc4575d9bea87927b2dc07e591cabdff3
tests/Non-MarkdownFileSupport.pdf,Non-MarkdownFileSupport.pdf,other,106751,2025-02-23T17:31:23.206428,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),4aa96f9d0ee830ef6a1ec92243f302bbe2fa8971b84945f4eb5e276bf1ffde96
tests/Screenshot.png,Screenshot.png,other,190076,2025-02-23T17:31:23.207771,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),dd82612fe013c39810d28ae0f2bbcca6ab2c9925b035d7720bb02493ddf5d0a9
tests/__init__.py,__init__.py,source_code,141,2025-06-21T00:01:46.216456,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),2ac5605e69ad8829e96b3689632536539f1cdd3149a29c1903f25af0793a5d65
tests/api/conftest.py,conftest.py,source_code,1404,2025-06-21T00:01:46.359728,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),ea46cde47a37c1f43fe079e48f5d0de8007155a0187f4445abc4f2ffde9c1791
tests/api/test_async_client.py,test_async_client.py,source_code,1382,2025-07-30T09:39:45.725666,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),a2f96226a75e5cff2326ca48ddccea4915964d83e529f7b99958bc233ccdc31a
tests/api/test_continue_conversation_template.py,test_continue_conversation_template.py,source_code,5152,2025-06-21T00:01:46.390901,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),cca08b0291db0ad693c60d8dc203660021feab46d9eb530be7f67e0ebb52c1d3
tests/api/test_directory_router.py,test_directory_router.py,source_code,9376,2025-06-21T00:01:46.356659,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),428626589f0010512481ba495368b0bf2b9617b22764e588eaa3562e86da4d37
tests/api/test_importer_router.py,test_importer_router.py,source_code,16578,2025-06-21T00:01:46.368070,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),03edf6e7404c7350afe76e54ea2bf0d66f668bcdb49f59702067a67b3694309d
tests/api/test_knowledge_router.py,test_knowledge_router.py,source_code,45771,2025-07-01T08:33:22.090490,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),43a5f91ae1fb9038e65c09701336fbdc4dbf40c8319c94727e6312fda9d6d56e
tests/api/test_management_router.py,test_management_router.py,source_code,6220,2025-06-21T00:01:46.402764,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),f6a3f41f263f10c08281525571e2e39fc9ae818ebb7e9895e3f3f3115ea05677
tests/api/test_memory_router.py,test_memory_router.py,source_code,5555,2025-06-21T00:01:46.399667,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co),2b8782015c893081fc67b7f13a7255d4e3dc45b27705a524f94918a339e2d486
tests/api/test_project_router.py,test_project_router.py,source_code,13288,2025-07-30T09:39:45.725847,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),af85d0870069d3831a9214372853994e5d12dc910e37298eb9958f72f6a8bb31
tests/api/test_project_router_operations.py,test_project_router_operations.py,source_code,1812,2025-06-21T00:01:46.381705,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),afd40879de3d7c508cca0feeae3cb7a8f3afcfbb06f59a6104189b1e3291376a
tests/api/test_prompt_router.py,test_prompt_router.py,source_code,5046,2025-06-21T00:01:46.396588,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),9115995b7dd8b44d8a9c06c5e1bbb177618286675a769889e35ac16f26fd9897
tests/api/test_resource_router.py,test_resource_router.py,source_code,13960,2025-06-21T00:01:46.384919,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ad3cb2cabaeab82a7e64253ceede7c1791e2bd70c92f60bc5f3be939292ca22d
tests/api/test_search_router.py,test_search_router.py,source_code,6409,2025-06-21T00:01:46.375427,bm-claudeai (AI Assistant),3,bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (phernandez@basicmachines.co),ba3e3809795e72db9dd9c8c729048bdbbbdb897c2a5aa8b175112b5d4b56d152
tests/api/test_search_template.py,test_search_template.py,source_code,5258,2025-06-21T00:01:46.378721,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),8f0d16a1de947d0466d96afed02e695f7648adac70b9ba99753250068207a686
tests/api/test_template_loader.py,test_template_loader.py,source_code,8035,2025-06-21T00:01:46.406694,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),4fd7c6c4862cc0d877d29244ecdc0b52a0693fc4574a9667663bee36bfd36b62
tests/api/test_template_loader_helpers.py,test_template_loader_helpers.py,source_code,7445,2025-06-21T00:01:46.372114,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),8dd35f11c9b3f2d96ac36263edf4430308728c04c9da543b1cb845b96ab20aa7
tests/cli/conftest.py,conftest.py,source_code,1185,2025-07-01T08:33:22.090848,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),c3c73da7ad73224931e1fba4ebd70792221be607bfb1488bb5c9420bba03bdb8
tests/cli/test_cli_tools.py,test_cli_tools.py,source_code,13528,2025-06-21T00:01:46.318964,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),befb8cab50132e26a1b13b82ce8e93a3258282cf157a4230214bfcefeae65102
tests/cli/test_import_chatgpt.py,test_import_chatgpt.py,source_code,6719,2025-07-08T20:39:18.350234,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),df447335545248e2fb2417af33f8cc2f0143bd413a1783e41f89605de305b7c8
tests/cli/test_import_claude_conversations.py,test_import_claude_conversations.py,legal,5028,2025-07-08T20:39:18.350607,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ac4b3b9dfc331fa7fa32a2941c027ff8b6585a94a5ccfaaf0a4affe5cf19c00c
tests/cli/test_import_claude_projects.py,test_import_claude_projects.py,legal,4585,2025-07-08T20:39:18.350885,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),7eaab3d491d50ca59c8ba015ecd5740a5feae94d970ee17559c6df9d8558c2cc
tests/cli/test_import_memory_json.py,test_import_memory_json.py,source_code,4891,2025-07-30T09:39:45.726290,jope-bm,3,jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com),d6734ce059dacf97bc764d848b2e2a6ba52fa96871bc331ec9d2ddc60b766bb6
tests/cli/test_project_commands.py,test_project_commands.py,source_code,6578,2025-07-30T09:39:45.726703,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),51e2ac7ff44b0d1a1db6da406c5c2e6aebceb1b63a1e8d5ab51bbaa834507157
tests/cli/test_project_info.py,test_project_info.py,source_code,3874,2025-07-01T08:33:22.091600,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),0b08950e95a5f276f14c56f6fe99af4d41b8f04548e9b7746f807c5ac4155578
tests/cli/test_status.py,test_status.py,source_code,4160,2025-07-01T08:33:22.091978,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),a1ff89fdac4bf57ba03be92ac79b8ca3210af0b1132d5a2bd0fc0c253fcbf988
tests/cli/test_sync.py,test_sync.py,source_code,3864,2025-07-08T20:43:47.816788,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),77e0f6b42b584a5acfda3c2ff194ff5e71ca57a40cb4484d563f5482bbbb4c0f
tests/cli/test_version.py,test_version.py,source_code,289,2025-06-21T00:01:46.305368,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),db95665178a32d391bd83011d073db269cefba9da8a4dc4d3e392d47bc0e0a8f
tests/conftest.py,conftest.py,source_code,15286,2025-07-08T20:39:18.351368,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),c6f38f103ce58144ffe4562f0c47dad849974e96327f02805e854447e19bc411
tests/importers/test_importer_base.py,test_importer_base.py,source_code,4329,2025-07-08T20:39:00.549594,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),da872a073f2b90fda3c98c9f44cf45884f4f682d91ec1b1f931d42c34eee1939
tests/importers/test_importer_utils.py,test_importer_utils.py,source_code,1827,2025-06-21T00:01:46.184343,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),cbee630c855ceb1de764ac0cdb1d5d781c16cd3a934d67166adc235c98f363d7
tests/markdown/__init__.py,__init__.py,source_code,0,2025-06-21T00:01:46.196570,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
tests/markdown/test_entity_parser.py,test_entity_parser.py,source_code,8669,2025-06-21T00:01:46.206579,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),496fdab6fe164634f8223f827585a4c0dedd30ccea7b0b69dc79004ad0996de2
tests/markdown/test_markdown_plugins.py,test_markdown_plugins.py,source_code,5139,2025-06-21T00:01:46.200759,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),9624d73462aadcd47fb0edd0d87880959a8982eecfa39529f21e3b292c34cb83
tests/markdown/test_markdown_processor.py,test_markdown_processor.py,source_code,5728,2025-06-21T00:01:46.194175,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),35042aa57dd2aeed848c624aa7f69decfb18e83c3b217eca1fdacfc4a10831db
tests/markdown/test_observation_edge_cases.py,test_observation_edge_cases.py,source_code,4295,2025-06-21T00:01:46.209598,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),6f1ab7684bfa8b02af0e0c42b3d7aed91fcd0b1c6288cbc9b6d6943c166979fa
tests/markdown/test_parser_edge_cases.py,test_parser_edge_cases.py,source_code,5142,2025-06-21T00:01:46.213291,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),d511d7d1187e3c1d3a8d3e0ab9efc9e68e84115c46811e7a9dc21eca9a87d520
tests/markdown/test_relation_edge_cases.py,test_relation_edge_cases.py,source_code,3952,2025-06-21T00:01:46.203846,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),f8e2016308f84139342cc37ec6e389ffe52d7eb46f998ee327c7f0d5bba3ad9b
tests/markdown/test_task_detection.py,test_task_detection.py,source_code,401,2025-06-21T00:01:46.191444,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),37fe91a689742033a0eb76723557e31c2a85e591133383b3887572cb714db2a5
tests/mcp/conftest.py,conftest.py,source_code,1761,2025-07-08T20:39:18.351554,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),350b2ca42c52ef76e002deddb65fc0eccb9f8989bd26078ea1c100e3b93bae3f
tests/mcp/test_prompts.py,test_prompts.py,source_code,5989,2025-07-01T08:33:22.093691,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),e3b2ee5b4bc120b5043225ebdbf73eadd71be9c3d9d8e1fdd66a24a8ff11a780
tests/mcp/test_resource_project_info.py,test_resource_project_info.py,source_code,4885,2025-07-01T08:33:22.094085,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),6a043736789b239c8d4ae00d1a5332a0fc3bed4038fe3388970ba97dc7431a93
tests/mcp/test_resources.py,test_resources.py,source_code,549,2025-07-01T08:33:22.094481,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),77a6396b9b8a9d3b3d202e438243ca6178d87fd7680c9fc063ee013f2042aa4c
tests/mcp/test_tool_build_context.py,test_tool_build_context.py,source_code,3714,2025-07-01T08:33:22.094661,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),5f0a39fdb652605335ed54aa91fe994262441bb7ba46f381c46ebca882fc23f2
tests/mcp/test_tool_canvas.py,test_tool_canvas.py,source_code,7997,2025-07-01T08:33:22.094982,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),c517bb1f9edc77b76fbeb483dfcb2dd57de9f0b80a10738708bc90cbe388dc9e
tests/mcp/test_tool_delete_note.py,test_tool_delete_note.py,source_code,4208,2025-07-01T08:33:22.095578,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),4cbf3ba4313438651b0608d5b25faf1400f7bd6a78af630351aecd4457800895
tests/mcp/test_tool_edit_note.py,test_tool_edit_note.py,source_code,13503,2025-07-03T18:43:10.084890,Drew Cain,2,Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),dd441969aa54b3739f2d9202d3feaf46aee807ec51e01a13ef41eda52743944f
tests/mcp/test_tool_list_directory.py,test_tool_list_directory.py,source_code,7730,2025-07-01T08:33:22.096737,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),cfbb5c06bcfe74ea60f31051eb9b03ac80b86232b053c188bf3b172132f7c0f5
tests/mcp/test_tool_move_note.py,test_tool_move_note.py,source_code,25851,2025-07-30T09:39:45.727347,jope-bm,2,jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co),c9f95158503fa42e37532aaa35027538fa49cea718d059d570cf547d31f01043
tests/mcp/test_tool_read_content.py,test_tool_read_content.py,source_code,19442,2025-07-30T09:39:45.727526,jope-bm,1,jope-bm (joe@basicmemory.com),1817e32f997b06dbdabf02864d0c70584c1cd80bf1fae8dfbef649de79434bc5
tests/mcp/test_tool_read_note.py,test_tool_read_note.py,source_code,22991,2025-07-30T09:39:45.727746,jope-bm,4,jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co); Amadeusz Wieczorek (git@amadeusw.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),4e13ea9ff977525aa62b6a8c22505ffe989329dc3e7d07b71e044cf43ef3ddfd
tests/mcp/test_tool_recent_activity.py,test_tool_recent_activity.py,source_code,3983,2025-07-01T08:33:22.098561,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),45414152cd45334b84df56f653146b7bd2939907548e7245a37207fdccfc4c87
tests/mcp/test_tool_resource.py,test_tool_resource.py,source_code,6782,2025-07-01T08:33:22.098948,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),3916ae1c4d458eee53d2c570b9bbb708ecd617de5a145c3a7552389954bc9195
tests/mcp/test_tool_search.py,test_tool_search.py,source_code,10959,2025-07-03T18:43:10.085177,Drew Cain,3,Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),07839cf8e89d74ba05ec1dc55fa842444c1f0cc3d1587823b0ac3458ad89e87c
tests/mcp/test_tool_sync_status.py,test_tool_sync_status.py,source_code,6331,2025-07-01T08:33:22.100021,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),3f3566df5b9f3c66bb9b3bd77d4c05825480d0e85015305cb1b7ab9429c7a7ec
tests/mcp/test_tool_utils.py,test_tool_utils.py,source_code,9228,2025-07-01T08:33:22.100561,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),b50789f7a78b0169f52dc8bd16e262510ed4a0ac130fce698d4be84af1fe2d31
tests/mcp/test_tool_view_note.py,test_tool_view_note.py,source_code,10004,2025-07-01T08:33:22.101164,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),385535ce791c3ab55db827cbcf27ea25ae85faf3a027f52df4eea052cede7f36
tests/mcp/test_tool_write_note.py,test_tool_write_note.py,source_code,35136,2025-07-30T09:39:45.728137,jope-bm,3,jope-bm (joe@basicmemory.com); Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),45172de20cfc4f4436287573b4f5a94b8b616a360647af67ba1c6c343ccd41b8
tests/repository/test_entity_repository.py,test_entity_repository.py,source_code,15498,2025-06-21T00:01:46.176265,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),8f5d1ff6cff7b640993ff73f463b1e7801a4dd69640fc55fa23dc58b865e9943
tests/repository/test_entity_repository_upsert.py,test_entity_repository_upsert.py,source_code,16983,2025-07-03T18:43:10.085482,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com),ee0ecd6f4ff8b31838bd6de6fb1018af437e02813d8315c06f4e20a6b78a0572
tests/repository/test_observation_repository.py,test_observation_repository.py,source_code,11786,2025-06-21T00:01:46.158862,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co),3006d392ad94dcc9b3ae031d63a1af6935624534389dd025de70d4c9e112aa0b
tests/repository/test_project_info_repository.py,test_project_info_repository.py,source_code,1186,2025-06-21T00:01:46.161876,bm-claudeai (AI Assistant),1,bm-claudeai (AI Assistant) (claude@basicmachines.co),dbce7ae7350403cbc37343c2e3e68e4d2255e247006aaa6c495a7ce8bfddea7d
tests/repository/test_project_repository.py,test_project_repository.py,source_code,10511,2025-07-30T09:39:45.728536,jope-bm,2,jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),279a3324635b1b515c18003bd1875cf230fef1192cadef9ca426846783717e75
tests/repository/test_relation_repository.py,test_relation_repository.py,source_code,11980,2025-06-21T00:01:46.167567,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co),fcdbdf40f7b56145b98eedde5ead64ef6187e859adde48cc09f5a97754757428
tests/repository/test_repository.py,test_repository.py,source_code,5996,2025-06-21T00:01:46.152691,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),3a6d4ca0b7a438a30ae504f5eac22874d4e1a937a4246e90efad46c90f0e1c95
tests/repository/test_search_repository.py,test_search_repository.py,source_code,25471,2025-07-03T18:43:10.086041,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),805b5cc55a53ae9d6e86c0de735c7f38ac9fd92c23194b0150fa3e7f5507a146
tests/schemas/test_memory_url.py,test_memory_url.py,source_code,2000,2025-06-21T00:01:46.346515,Paul Hernandez,1,Paul Hernandez (paulmh@gmail.com),946fa6c48534f41c8b9cc27bbad164d3e3673a79afa84fbb439fae4cd26c504b
tests/schemas/test_memory_url_validation.py,test_memory_url_validation.py,source_code,9338,2025-07-01T08:33:22.103247,Paul Hernandez,1,Paul Hernandez (paul@basicmachines.co),7054c3a3cdb8e05d2ffd254ed298908ff3d48a0a3927d960975eae1ab9beeb38
tests/schemas/test_schemas.py,test_schemas.py,source_code,16880,2025-07-03T18:43:10.086337,Drew Cain,3,Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),2c746a00c340f063c160ace67569955a300a294040949695c79fd4ae124fb611
tests/schemas/test_search.py,test_search.py,source_code,3264,2025-06-21T00:01:46.343685,github-actions[bot] (AI Assistant),2,github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com),7437775d1b4a61a438cbabe3dbb1becd2adc4dbcc0c24595830c0843fa7a3166
tests/services/test_context_service.py,test_context_service.py,source_code,8833,2025-06-21T00:01:46.446200,bm-claudeai (AI Assistant),2,bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com),ab49d21a05d5364e7e8a2f00b07aabe1109b7417698e0acd92792e2cb5f22732
tests/services/test_directory_service.py,test_directory_service.py,source_code,6854,2025-06-21T00:01:46.457258,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),a0c765fb6bd1a75b6ba4592b1df344c48273b6ab03ba6bd15d2b5e840eb2d521
tests/services/test_entity_service.py,test_entity_service.py,source_code,59278,2025-07-03T18:43:10.086674,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),d7d495756faa10b415d705cf26b69d2efbc5f190a834b6767386ff296629ea71
tests/services/test_file_service.py,test_file_service.py,source_code,5243,2025-06-21T00:01:46.460141,Paul Hernandez,1,Paul Hernandez (phernandez@basicmachines.co),83e6e9a7584b7baeccbc84865d8c1d3e2a0223e7d2b9c066d3799c289196ab7d
tests/services/test_initialization.py,test_initialization.py,source_code,6939,2025-07-08T20:39:18.351946,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),ebb8d11ff37dcb36c4844c68f873c8e0e8aa6ab1b82abb22ee96c4233e6ab287
tests/services/test_link_resolver.py,test_link_resolver.py,source_code,13618,2025-07-01T08:33:22.105766,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),c9fc56d0c3572770e95ca6175c213ed8b44c2a6ddddc298b5662cc2d8f41014e
tests/services/test_project_service.py,test_project_service.py,source_code,28152,2025-07-30T09:39:45.728920,jope-bm,3,jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),430868ed796518937a3b0bed4d8c33be53264a6100d61d54b0745bb2b58bf29a
tests/services/test_project_service_operations.py,test_project_service_operations.py,source_code,4679,2025-06-21T00:01:46.442141,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),d4103b6c5fff7e74ba84b86033537b8522aa60fc75aec4974d52e1dfec68b1b4
tests/services/test_search_service.py,test_search_service.py,source_code,25299,2025-06-21T00:01:46.439133,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),b3e58258db2b727ab8a1792e52fcb038d3a0779bcd1b70f2d295a9d06e5e2513
tests/services/test_sync_status_service.py,test_sync_status_service.py,source_code,9926,2025-07-03T18:43:10.087266,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),00245ca356e33fc7afcd2d42cb5ec082038c95383d738067821cf1e8d23c9a4b
tests/sync/test_sync_service.py,test_sync_service.py,source_code,39659,2025-07-01T08:33:22.107088,Paul Hernandez,2,Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co),0609aee9906549f27fc4289b5ac2d46a50e53e12a0615a2701f17fbe9a74e169
tests/sync/test_sync_wikilink_issue.py,test_sync_wikilink_issue.py,source_code,1959,2025-06-21T00:01:46.420370,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),db4b98dd7008a1a26b0fd02b9a2d03bca950b281efc19d98a91b017c500b23a5
tests/sync/test_tmp_files.py,test_tmp_files.py,source_code,5930,2025-06-21T00:01:46.424277,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),cead95a07b29f6c1ab933abe67f9648122f5f33d33753c8e4ab4934af96cbc25
tests/sync/test_watch_service.py,test_watch_service.py,source_code,14076,2025-06-26T09:28:51.860679,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),e324a4127f8bfe59798f3a08125488c701e88239b7482d87a0394b91c55b893e
tests/sync/test_watch_service_edge_cases.py,test_watch_service_edge_cases.py,source_code,2169,2025-06-21T00:01:46.409830,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),b87c5015330e9b551630aab7ef95ac1e7663f0ce46e912d8155b319e139559aa
tests/test_config.py,test_config.py,source_code,3680,2025-07-03T18:43:10.087558,Paul Hernandez,3,Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),6a246431cff739274efda895dfa28f38710289a73e645312f5fa05fce0885ec8
tests/test_db_migration_deduplication.py,test_db_migration_deduplication.py,source_code,6166,2025-07-08T20:39:18.352690,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com),ab768e061531ca2edcd598c101b5d884e549f90c7a4b305a23713a199d263fbb
tests/utils/test_file_utils.py,test_file_utils.py,source_code,6199,2025-06-21T00:01:46.219673,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),4615bb20b92b8a79d9e086301f852b34c3360f17ecc209920d68528268ddeed4
tests/utils/test_parse_tags.py,test_parse_tags.py,source_code,1691,2025-06-21T00:01:46.223400,Paul Hernandez,2,Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com),b83afdf18dac8b3411bd0fab1301683217d3d1abceaf2d22c09e79c709efd8eb
tests/utils/test_permalink_formatting.py,test_permalink_formatting.py,source_code,4193,2025-06-21T00:01:46.225738,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),d998a4ca846c78679728a918b6f71df67c30b6961ede3cffa609f2cf6ad160eb
tests/utils/test_utf8_handling.py,test_utf8_handling.py,source_code,5791,2025-06-21T00:01:46.228483,Paul Hernandez,1,Paul Hernandez (60959+phernandez@users.noreply.github.com),50ec792e572af51cd2d996d4ddacbcfd3bbd79aa928818a90c42117451ea829f
tests/utils/test_validate_project_path.py,test_validate_project_path.py,source_code,15346,2025-07-30T09:39:45.729224,jope-bm,1,jope-bm (joe@basicmemory.com),27a4bf2a9d277e6cd773619e5241b20175d211ec60c284361bec404fd99143f3
uv.lock,uv.lock,other,232710,2025-07-30T09:39:36.619155,Paul Hernandez,3,Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co),caa1e3f6e66464b884a78cab77df2fe303fa3c0dc573be0196474b6d3c150581
1 path name category size_bytes modified_time primary_author contributor_count contributors_list sha256_hash
2 .claude/commands/release/beta.md beta.md documentation 3103 2025-07-01T08:33:22.037438 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 345f06e3f304470a8892e96979355919c8e5d8f4212ebbe543c3272fd15a5cbe
3 .claude/commands/release/changelog.md changelog.md documentation 4648 2025-07-03T18:43:10.075727 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) ea2484352f4f881aa71f546dcf84b0882d60c96673a88406aa2f7e12a282a1f4
4 .claude/commands/release/release-check.md release-check.md documentation 3344 2025-07-01T08:33:22.040060 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 7dd41a40e95c89153936c4ea60a3c989eccd5973fff9f6caef156de82f12bd04
5 .claude/commands/release/release.md release.md documentation 2925 2025-07-01T08:33:22.043674 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 0e4afd788fcf85fbe46094452e9b92a4290c3e23702283e72ee56aa9288476e5
6 .claude/commands/test-live.md test-live.md documentation 18720 2025-07-03T18:43:10.076403 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 21a9edeefb3e5673d833151a2fd0464087c0f0a9d056fdd7fca474c1d177d6fc
7 .dockerignore .dockerignore other 623 2025-07-01T08:33:22.048301 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) b3079bffad89d07186c9166725075524ffc07ceea6dc1d53fdbea6dca9585684
8 .github/ISSUE_TEMPLATE/bug_report.md bug_report.md documentation 967 2025-03-01T14:22:50.968994 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 0d34443ddda84a5e9726c8268083c9646d2cc64ff9c5ebf56c89587cef4c7706
9 .github/ISSUE_TEMPLATE/config.yml config.yml configuration 374 2025-03-01T14:23:26.936836 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) f4e21bb9b7f053509b6c08548a2649a5d62f44f0106810aa8c915d2f29acc623
10 .github/ISSUE_TEMPLATE/documentation.md documentation.md documentation 557 2025-03-01T14:23:40.020117 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 78f80a6b9a2b954695f74a847a068e0b780da6020572401c13c931555a4a9db5
11 .github/ISSUE_TEMPLATE/feature_request.md feature_request.md documentation 786 2025-03-01T14:23:06.344081 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) cee834de442221d53f3d2eeb85b81f3a641b3a7cb587ef8a169452305007439d
12 .github/dependabot.yml dependabot.yml configuration 529 2025-03-15T10:40:16.936258 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 8c6c2ef9a1417da63adb49c70f45524129bd5c46bbd7452b6b97cfe65205f89d
13 .github/workflows/claude.yml claude.yml legal 4628 2025-07-03T18:43:10.076958 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) d026f1756310781b85abc491b454cc31b52dfce4b951cdf8a4a555b6b047604c
14 .github/workflows/dev-release.yml dev-release.yml configuration 1405 2025-06-03T08:31:42.915367 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 58bcfc9e11e210e02dee76aabe7d1378da4edafb7f61857049ee6ccee9d45ffa
15 .github/workflows/docker.yml docker.yml configuration 1621 2025-07-01T08:33:22.049870 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) c918a86f2b16ebb45182a73c45272bcd6cea7022cc6e0eaeb2b2a24d8eebfe4d
16 .github/workflows/pr-title.yml pr-title.yml configuration 887 2025-02-17T16:29:35.733646 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) b6df62a20235496feba4d9419c85d6e8446c4d447ad69e7f57b51cc8953f68f4
17 .github/workflows/release.yml release.yml configuration 2479 2025-07-03T18:43:10.077261 Drew Cain 2 Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co) 163307a99e2f3d317af6ae8927e1537654d88bf574a6958ac0bcd2d4de266b2a
18 .github/workflows/test.yml test.yml configuration 1426 2025-07-01T08:33:22.054430 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) ab09084a11be8adfc83fc6285c652fbe44a4584aa7d7d3658fcf0c57112d1640
19 .gitignore .gitignore other 566 2025-07-01T08:33:22.055006 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 358658b0faa8c3145e539421deba4ad0bac4a8ea2e608b2c2f867a822c67b36f
20 .python-version .python-version other 5 2024-12-02T16:41:37.012830 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 7b55f8e67b5623c4bef3fa691288da9437d79d3aba156de48d481db32ac7d16d
21 CHANGELOG.md CHANGELOG.md documentation 53647 2025-07-06T10:34:45.550847 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); semantic-release (semantic-release) 0b165eb18975ff253cde08f4687b7808ac6ce63b6d8c10912c495a76c9058d1a
22 CITATION.cff CITATION.cff other 302 2025-02-17T16:29:35.735146 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) bc4878cf6f80bb64655f3f93a5821ffe0924ad8311c784b5f81a36093fcffdda
23 CLA.md CLA.md legal 1342 2025-03-01T14:12:38.435132 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) c238c7ff4f2b8451c6bba1bbacd10b04fb7d41d9a9cd76a62644d8193791d169
24 CLAUDE.md CLAUDE.md legal 12310 2025-07-03T18:43:10.078147 Drew Cain 4 Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); Ikko Eltociear Ashimine (eltociear@gmail.com) 751f10c1a9a9c0ba2054d9c9cd36cf0989d6a347491fd505bb6504ee74b12900
25 CODE_OF_CONDUCT.md CODE_OF_CONDUCT.md documentation 509 2025-02-17T16:29:35.735238 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) b276b464e85eb1e5d9f09d90bba9573508a7bf81d2eeb38d0f25bd9f2fa66198
26 CONTRIBUTING.md CONTRIBUTING.md documentation 7329 2025-07-30T09:39:36.607158 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co) ca822fe39cc529bfc3822d317ab8a20fd141ec3ecb8c20de9e2eca3edce1c282
27 Dockerfile Dockerfile other 856 2025-07-01T08:33:22.056908 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) c45501905423990e31ffdb8ae4e976eca6423c6784905c1036bcc7795b70feef
28 LICENSE LICENSE legal 34523 2024-12-02T16:41:20.739392 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 8486a10c4393cee1c25392769ddd3b2d6c242d6ec7928e1414efff7dfb2f07ef
29 README.md README.md documentation 16261 2025-07-03T18:43:10.078281 Drew Cain 6 Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Jason Noble (github+jasonn@jasonnoble.org); Marc Baiza (43151891+mbaiza27@users.noreply.github.com); Matias Forbord (codeluggage@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 2c4b82b30f49ab465617034fda999157450ed168f8e097fe8e9ca7ac65a197a7
30 SECURITY.md SECURITY.md documentation 303 2025-03-12T16:04:01.989927 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 96932268b8fe737577941b49e9dff5ff8ad59c11cc3f9a016989589ef5c98c1f
31 docker-compose.yml docker-compose.yml configuration 2534 2025-07-01T08:33:22.059281 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 4a9fdfd2f0d42b148fe021eee77fd5e772c7ba9c2fe5d0cc310c8c7a64257417
32 docs/AI Assistant Guide.md AI Assistant Guide.md documentation 16423 2025-07-01T08:33:22.060491 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) c717869a3d356852077026591364ecb0589a6d53f9764af027c57d142ea7ff55
33 docs/Docker.md Docker.md documentation 9088 2025-07-01T08:33:22.060976 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) e13d3f50148353046b52d9d493986880f76f2914ef4efe1a759b2cc9d10913a4
34 justfile justfile other 5496 2025-07-30T09:39:36.608008 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com) a8df6c73ee8cf3eaa48e4cba193dffbc2821b843a8cc0c0cd58b8517f2252907
35 llms-install.md llms-install.md documentation 2307 2025-06-29T14:19:28.826375 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 674105eb85c8f1d59fc2ddc6840f2a2a3e293358687a305c37dffc8144304f97
36 memory.json memory.json configuration 90075 2025-06-29T14:19:28.827172 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 22d5eee995ca0548b7d9dd152f9a3690c42a514cbf38fea750b3ed2a17dd99c5
37 pyproject.toml pyproject.toml configuration 3279 2025-07-30T09:39:36.608791 Paul Hernandez 4 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); semantic-release (semantic-release) 428a5d64d4555dc7cd2a81b89a3444bdb1d2f3017257fbc9160bd0ae982718e1
38 smithery.yaml smithery.yaml configuration 433 2025-03-11T16:36:03.389716 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 63f13499cf4fee0751b5005f5bd85597dc1c7f9a881f4ec8147cc23e95e75c2d
39 src/basic_memory/__init__.py __init__.py source_code 256 2025-07-06T10:34:45.551291 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@gmail.com); semantic-release (semantic-release) c946490ab9d64957d25df37f721392024916f8a81ed838f70a8e938b1c2637db
40 src/basic_memory/alembic/alembic.ini alembic.ini configuration 3733 2025-02-23T17:31:23.198433 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 20466c9c5f026db66793006bebb2f328a3686c7bb34da40d52f33c3eca29e717
41 src/basic_memory/alembic/env.py env.py source_code 2838 2025-07-08T20:39:18.336650 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) e241d987eadfcd5011cbd9ddbecb833d306de87937759d81c08d37d04a6906b0
42 src/basic_memory/alembic/migrations.py migrations.py source_code 718 2025-06-21T00:01:59.613806 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 96b8873d70dd04b48d5c45b7413a54d1224ab9577557ef0dad5929377a9fb144
43 src/basic_memory/alembic/script.py.mako script.py.mako other 635 2025-02-17T16:29:35.739659 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 304a8bfb6a804e5493f5300e79882c70c9f5bb2e87512f4d16f0e097dddd323f
44 src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py 3dae7c7b1564_initial_schema.py source_code 4363 2025-06-21T00:01:59.595762 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 9536d69409ddd5eb3bc54f7d0e82607da45ed7f2ad7bc4cbf7cae2a9e28657cd
45 src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py 502b60eaa905_remove_required_from_entity_permalink.py source_code 1840 2025-06-21T00:01:59.604717 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 93ac584e660f33d468b3eec203b070641298c282bf82655d0beda7f050237681
46 src/basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py 5fe1ab1ccebe_add_projects_table.py source_code 4379 2025-06-21T00:01:59.601299 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) d82098f5aca3cdbada60c7229ea4ac262f527349eed9ef9e864509bacfaccf7e
47 src/basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py 647e7a75e2cd_project_constraint_fix.py source_code 4210 2025-07-01T08:33:22.063463 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 604ac5908a5976272f52297866d13abb14a99390590935d9fd37cf13e32049fa
48 src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py b3c3938bacdb_relation_to_name_unique_index.py source_code 1428 2025-06-21T00:01:59.598551 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 46c1b2990cdf4575752d234a8b28b494c8a54f15b5360c12f6347aef27b66a92
49 src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py cc7172b46608_update_search_index_schema.py source_code 3679 2025-06-21T00:01:59.610909 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 9036af47d42ac7dc12bb93f7a9de1266aff0688b031b550c4e0d92983a24b4c5
50 src/basic_memory/api/__init__.py __init__.py source_code 72 2025-06-21T00:01:59.579981 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) c02a63fb6d63d43d0acca97d425eae9cb055158d0ad6e1a9fc57926512adaa99
51 src/basic_memory/api/app.py app.py source_code 2897 2025-07-08T20:39:18.337081 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ad673ac2c09410af5830f6afdffaa28b0040480dacd52ff01750b8b2c066e8cf
52 src/basic_memory/api/routers/__init__.py __init__.py source_code 398 2025-06-21T00:01:59.549416 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 4443b908a43dea8e6fb4658009cb08c48a56c9b21449e29773cdd159b59cf014
53 src/basic_memory/api/routers/directory_router.py directory_router.py source_code 2054 2025-06-21T00:01:59.567371 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ac1407beec1f7d43a4d222af704b3642559c960bebf2eaf6e1ffe91f7fac5514
54 src/basic_memory/api/routers/importer_router.py importer_router.py source_code 4513 2025-06-21T00:01:59.561600 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) c45502a2b90f5a8f00174c9ad14adc2e65cd7fc0a33d9740a9d750207f0f3bea
55 src/basic_memory/api/routers/knowledge_router.py knowledge_router.py source_code 9738 2025-07-01T08:33:22.064119 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) e1d0ffb4fa5c246582ba844a11b417092de1a173567be55b7060bbc55fbc5681
56 src/basic_memory/api/routers/management_router.py management_router.py source_code 2730 2025-07-08T20:39:18.337438 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) cdbce294dcec6146c56c4dae15744cdf77039fd21b23eef7cbc0b5f9bfb5f4ee
57 src/basic_memory/api/routers/memory_router.py memory_router.py source_code 3003 2025-07-01T08:33:22.064510 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6bd0a7c775e0c1290efb6001173237c0cdcfa0c1b1bb27c9145a7b35f1596a97
58 src/basic_memory/api/routers/project_router.py project_router.py source_code 8444 2025-07-30T09:39:45.721369 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6539c69f1d506c65d14cc2c151876b19057f37d84d51a9e6f464ed47a4692648
59 src/basic_memory/api/routers/prompt_router.py prompt_router.py source_code 9948 2025-07-01T08:33:22.065221 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) e30c6aebe35112054933c37d0b462c3750005030fc9e44c23b35b2cd2a93485c
60 src/basic_memory/api/routers/resource_router.py resource_router.py source_code 8058 2025-06-21T00:01:59.570399 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 584244a84698ff24ca8f9f94fab5b891740a51c25f972920c08ebf83f478d5c9
61 src/basic_memory/api/routers/search_router.py search_router.py source_code 1226 2025-06-21T00:01:59.543020 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 183eb68e50904e22ff54db1d89b8be6f57fa1f8d0a096a3d497d8297b607e105
62 src/basic_memory/api/routers/utils.py utils.py source_code 5159 2025-07-03T18:43:10.079143 Drew Cain 2 Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 9e60f57da24e1dc9d64236c26a7a2353003dc618a77fbeb853c494aa3357a57c
63 src/basic_memory/api/template_loader.py template_loader.py source_code 8607 2025-06-21T00:01:59.576132 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 7b16d3797c89460c8b1664a335eae8c634fb5f60d6166d97e6a5156b776b6d83
64 src/basic_memory/cli/__init__.py __init__.py source_code 33 2025-06-21T00:01:59.456735 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 6ab70a2c05910e13c3ef1e6df34325be265e633c076741998a0cb2dcd295a069
65 src/basic_memory/cli/app.py app.py source_code 2268 2025-07-08T20:39:18.337708 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) f710670380d707c4458ad881f7a25bb3c5cdb2bcff59f139f37421264fe8f2f8
66 src/basic_memory/cli/commands/__init__.py __init__.py source_code 393 2025-07-08T20:39:18.338070 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) de8a23702f98fb844fa9f7fdbedc16ce24ff4f8baf05589c2f4a521cd8a55645
67 src/basic_memory/cli/commands/db.py db.py source_code 1480 2025-07-08T20:39:18.338326 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 704a1096d80ab9d12e247d029fe6223e935a0d0ceee7e615c020f46a720828e0
68 src/basic_memory/cli/commands/import_chatgpt.py import_chatgpt.py source_code 2856 2025-07-08T20:39:18.338787 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 8957cca3acab6351335624a518bdc19d956487e93d86dd1b6c2309e9d5850ae5
69 src/basic_memory/cli/commands/import_claude_conversations.py import_claude_conversations.py legal 2946 2025-07-08T20:39:18.339053 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7bc97838732bf00f4fb4a80e3ba4fdfbce8971ae85cc2ac902c3b3a3e110ae57
70 src/basic_memory/cli/commands/import_claude_projects.py import_claude_projects.py legal 2891 2025-07-08T20:39:18.339310 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6321577075801c99ad47a0cdc13b5aa3c9ca102a05ca8f06ae2a51125a8c43bc
71 src/basic_memory/cli/commands/import_memory_json.py import_memory_json.py source_code 3013 2025-07-30T09:39:45.721637 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7ea193f6720b2672290ad16c79a143a31d88a1d567838f12b9ebf9110a7c0338
72 src/basic_memory/cli/commands/mcp.py mcp.py source_code 2593 2025-07-08T20:39:18.340025 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 3e1be3d27b18ef4743204b7cf8a37fbc2a1a91fc1636c468e08cefcd8597bac6
73 src/basic_memory/cli/commands/project.py project.py source_code 13836 2025-07-30T09:39:45.721838 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) af3e6f30410ab48355f4b1a46aaa3aa5423cb0a0490f51447db1d25e09f39916
74 src/basic_memory/cli/commands/status.py status.py source_code 5809 2025-07-08T20:39:18.340324 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) b6d0ea237d714d054f69b24a2058beba74e224a2fa4a80a525d62a2aa2ea2d70
75 src/basic_memory/cli/commands/sync.py sync.py source_code 8213 2025-07-08T20:39:18.340627 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 703b3e73c4c33850c27f6f4c07abbf5d4ce5dc43f706bf0c43264bbb45f8b213
76 src/basic_memory/cli/commands/tool.py tool.py source_code 9540 2025-07-01T08:33:22.070903 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 9b2fa400b9f7921bf55b602f8bbdfa36b1ec7e4a5bc8fe7b9838b92e31f0a9ed
77 src/basic_memory/cli/main.py main.py source_code 465 2025-07-08T20:39:18.340868 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7a405b12bff98e319968526238ae15859df0362bba5a3857562fecca47b5c70d
78 src/basic_memory/config.py config.py source_code 12487 2025-07-30T09:39:45.722031 jope-bm 5 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 86341330a041b94d38f5e4f1b94f3509d74351c67cc826af1a4ff57313e5f726
79 src/basic_memory/db.py db.py source_code 7428 2025-07-08T20:39:18.341550 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 25e053b0b3f9e8fa823b05fc099001efe8a3a4f6e0f80728e5079ba45aa406d8
80 src/basic_memory/deps.py deps.py source_code 12144 2025-07-08T20:39:18.341870 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) bce28cd9ca549b8afce0bc689cc1fb2dc80cd5b929f85114aa682bb8ae82411d
81 src/basic_memory/file_utils.py file_utils.py source_code 6700 2025-06-21T00:01:59.587200 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 79ac5328b2c46d3232fcc6ff22ffc39ade085c0252ad91958be2a7ae9c9c8b71
82 src/basic_memory/importers/__init__.py __init__.py source_code 795 2025-06-21T00:01:59.295471 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 0537015bdecfded85cb166b9c3db50b2f3aef329c70e0c36fbcb4f824099a21f
83 src/basic_memory/importers/base.py base.py source_code 2531 2025-06-21T00:01:59.305335 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 6b0c1efd4f827f348834aa0cea2ae8eebbb842a2e5b1f5f3747ced0ef79b9f13
84 src/basic_memory/importers/chatgpt_importer.py chatgpt_importer.py source_code 7372 2025-06-21T00:01:59.308956 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) dfa540b196ab23b5c4e6bfcac4da567873351cf7c78fa35f4c3b87e84c58e218
85 src/basic_memory/importers/claude_conversations_importer.py claude_conversations_importer.py legal 5725 2025-06-21T00:01:59.288540 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) a7bc9e872f4f81ce5f79fe9dd1a6fd0f00a6f020a2cb2664184a97f14eeb1dbc
86 src/basic_memory/importers/claude_projects_importer.py claude_projects_importer.py legal 5389 2025-06-21T00:01:59.291737 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) a452675fd9bb18ebf64eb4bd7f69ccd7e993b617931015a3c4ab700d67493863
87 src/basic_memory/importers/memory_json_importer.py memory_json_importer.py source_code 4631 2025-07-30T09:39:45.722316 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) bc7d045299f17ed9ad5cebff7b1423250ec28a11130e47a0ac48d3ab82bdeafc
88 src/basic_memory/importers/utils.py utils.py source_code 1792 2025-06-21T00:01:59.298428 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) ee7e22fd4812362394f62ed8944abe80738c1e3ab88131faf639f4a8ccfd6c89
89 src/basic_memory/markdown/__init__.py __init__.py source_code 501 2025-06-21T00:01:59.316337 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 0ddce2a025ad0e729aab4e411d82e02ffefc15ac041cba4b5e7a7e90f4957c87
90 src/basic_memory/markdown/entity_parser.py entity_parser.py source_code 4516 2025-06-21T00:01:59.321461 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 1b0d1177358645ccbadd170b3f62760dc9bd838d38c741978ab0da5836234d68
91 src/basic_memory/markdown/markdown_processor.py markdown_processor.py source_code 4968 2025-06-21T00:01:59.325241 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 4b99f2ebdceed9d96a3bbb562682eba4b4b383c7a64080eaec3bbba25a4952c9
92 src/basic_memory/markdown/plugins.py plugins.py source_code 6639 2025-06-21T00:01:59.312733 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 82d2332918e866ccaf06a2e954d9eb9b397f71b4d9e591a7f2472e5f1423464a
93 src/basic_memory/markdown/schemas.py schemas.py source_code 1896 2025-06-21T00:01:59.329982 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 7b2c580abd615725a62267257b46ac13922dfc30fa01192a681658bb528ae67e
94 src/basic_memory/markdown/utils.py utils.py source_code 3410 2025-07-03T18:43:10.079889 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 1b757f0d06e60e3ea276c0b2ea44fe1a156a895e093c1e607d6286e702bf4ae4
95 src/basic_memory/mcp/__init__.py __init__.py source_code 35 2025-06-21T00:01:59.404757 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 76c0ce84aaa361e21b0942db1c8c5f708b536eab9d12e120d4da7ce9eab41844
96 src/basic_memory/mcp/async_client.py async_client.py source_code 925 2025-07-30T09:39:45.722547 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 6531f4387f30a30a322197ff51d6eacae4b25a58c648095931b3d1145d1da303
97 src/basic_memory/mcp/project_session.py project_session.py source_code 4203 2025-07-08T20:39:18.342906 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 4da403ec93dec9063eeb8b05246e3501740d162cc77a067d3ce54ba43d690819
98 src/basic_memory/mcp/prompts/__init__.py __init__.py source_code 615 2025-07-08T20:39:18.343263 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) f8197d0e08f64c3f4f50b8f38203ea5eebcf123582472ed2f58834de1dbe53b0
99 src/basic_memory/mcp/prompts/ai_assistant_guide.py ai_assistant_guide.py source_code 821 2025-06-21T00:13:53.262832 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) f13239c4e6e2455730bfac3d6f2d71407957d3086fc84efe2c6b22a833114c58
100 src/basic_memory/mcp/prompts/continue_conversation.py continue_conversation.py source_code 1998 2025-06-21T00:13:53.263309 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) aec9a50b657b7bb1ba0c02b42bcdb9bc5b0f2a0b1143bd361c5ce7ea59076833
101 src/basic_memory/mcp/prompts/recent_activity.py recent_activity.py source_code 3311 2025-07-01T08:33:22.073986 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) d2fd5cddbd927430f15d5b85f1e3a3368a18cb4e2e4587a5d29749d2b9e0830e
102 src/basic_memory/mcp/prompts/search.py search.py source_code 1692 2025-06-21T00:13:53.264093 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 9dbf3c319cbdb5d5bf32608b51522d8ae92b2dd6f7c441d62efd0954b525738a
103 src/basic_memory/mcp/prompts/utils.py utils.py source_code 5431 2025-06-21T00:01:59.425042 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 55a72b6eac18b724a9225608aca1e8e6cea3b684ccb1c609aab1511f7ce90ba4
104 src/basic_memory/mcp/resources/ai_assistant_guide.md ai_assistant_guide.md documentation 14777 2025-03-28T19:12:38.653627 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) aa76160e46256fe2662ae3a86799659916acfede1d5835c1fe2f0b136efb2e0b
105 src/basic_memory/mcp/resources/project_info.py project_info.py source_code 1906 2025-06-21T00:13:53.264918 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 2dc5244f1e225c17d4e8ba784d5721efc3aa2e8a5b3b2e258f22a77ebe155eca
106 src/basic_memory/mcp/server.py server.py source_code 1248 2025-07-08T20:39:18.343419 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 0a910cae27d61ea923e90fbe2d4b5c7cacf3b29d3e156f0b7f77872ab973c761
107 src/basic_memory/mcp/tools/__init__.py __init__.py source_code 1619 2025-07-03T18:43:10.080029 Drew Cain 2 Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co) 872b771dd52ec3b763645a2baf6e10a70f049ce3106830a6d3f053b3e09a5fe6
108 src/basic_memory/mcp/tools/build_context.py build_context.py source_code 4611 2025-07-03T18:43:10.080426 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 724280b77b8f5f3e61cd3fdeebc3ee66e2bcfedaaea3638e6a2e2e33fcb1978e
109 src/basic_memory/mcp/tools/canvas.py canvas.py source_code 3738 2025-07-01T08:33:22.076204 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) db617d1bd81f3dbfa5f22d41e6b6b825afe1f73618f37acf63d9830390b98246
110 src/basic_memory/mcp/tools/delete_note.py delete_note.py source_code 7346 2025-07-01T08:33:22.076726 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) b52c9173f5600662f255e7a70a5c17d5293ef8b29c19a8403334d7da62b65c8b
111 src/basic_memory/mcp/tools/edit_note.py edit_note.py source_code 13570 2025-07-01T08:33:22.077181 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) ab8c7e7fbfa3fe5fb0ce6d7bf805454f5fd6182a340aae25237b1e6127b6d5a6
112 src/basic_memory/mcp/tools/list_directory.py list_directory.py source_code 5236 2025-06-21T00:13:53.267453 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) f85c43b02aeee580f4d8ce2a9100c0bab1095b245a0bb608e1847ace0d1ab51f
113 src/basic_memory/mcp/tools/move_note.py move_note.py source_code 17541 2025-07-30T09:39:45.722919 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 5ef27d9baaebc2c5656fc49c4798a4e4e40a70b6cd9d1e761dbcae021bad558c
114 src/basic_memory/mcp/tools/project_management.py project_management.py source_code 12944 2025-07-06T10:34:45.552403 Drew Cain 2 Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co) cdac736d65129f689a85c6b82f8e043bc84a31657ead7a8331771171eeaa860f
115 src/basic_memory/mcp/tools/read_content.py read_content.py source_code 9125 2025-07-30T09:39:45.723469 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 96a13addac507fd874394dfa2e1e5ed71af5eb89d0872c1d68cacf9cac3f8724
116 src/basic_memory/mcp/tools/read_note.py read_note.py source_code 8132 2025-07-30T09:39:45.723828 jope-bm 5 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Amadeusz Wieczorek (git@amadeusw.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) dff53ce02b16d9c5fca315b3f09bb70867a89d614b0ef3f0548dce87978048ee
117 src/basic_memory/mcp/tools/recent_activity.py recent_activity.py source_code 4833 2025-06-21T00:13:53.269120 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 5d58cd2402679b1bf3c7dfcbb3503e40e776c9347ba499524d3b91c528af98de
118 src/basic_memory/mcp/tools/search.py search.py source_code 15883 2025-07-03T18:43:10.081557 Drew Cain 4 Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 8519b0057468c4450eb543a2f561bcd0d7cb96e1ce1b95e948e02b309ba6b7ca
119 src/basic_memory/mcp/tools/sync_status.py sync_status.py source_code 10517 2025-07-08T20:39:18.344231 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 73dd73b186eaec8b82e6b17e39a67c27082d24e0653dc4e4b35712bd83b80a22
120 src/basic_memory/mcp/tools/utils.py utils.py source_code 20892 2025-07-30T09:39:36.616253 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) a95004911e276822eba88a3fef15c5b9ba861b1ca9a2ecfe0c1e3fb2f4ef0116
121 src/basic_memory/mcp/tools/view_note.py view_note.py source_code 2499 2025-07-01T08:33:22.080024 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 75d357c72113b1d0394987e521a4158ff09b77b23b08b56cddab5144331b1a68
122 src/basic_memory/mcp/tools/write_note.py write_note.py source_code 6612 2025-07-30T09:39:45.723985 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 771396fb49d397feb5f7e36321d4d233c22c4d1b53414912c849957c6e96fc83
123 src/basic_memory/models/__init__.py __init__.py source_code 333 2025-06-21T00:01:59.437212 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 8f40b876d162f85384690291f1d416106f9d26d750d793414e2260e276c85cd5
124 src/basic_memory/models/base.py base.py source_code 225 2025-06-21T00:01:59.447249 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) e2101727c084d519e32a16f6de53ddf9033b1bf15721d4e8c0c27d6d18b1b295
125 src/basic_memory/models/knowledge.py knowledge.py source_code 7087 2025-06-21T00:01:59.444376 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 005c5f292f1f45ae372aadc48c9080b9fa6d7b854d0bb7ecf587ec843ac1e28d
126 src/basic_memory/models/project.py project.py source_code 2773 2025-07-01T08:33:22.080907 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) a14ad06943aeeff9ae4a5fa2dfc0e1d07ce6085acc02dc20c402c3513b7d9397
127 src/basic_memory/models/search.py search.py source_code 1300 2025-06-21T00:01:59.440923 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 3e143cc38b5a0294af8e1d43a4f841e1c1fd193b76136a68f83159ce19e86646
128 src/basic_memory/repository/__init__.py __init__.py source_code 327 2025-06-21T00:01:59.256363 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 3162bea3c42292accea5ee52c8f6ca4368a8079056034529cfae6d820f84d035
129 src/basic_memory/repository/entity_repository.py entity_repository.py source_code 10405 2025-07-03T18:43:10.082570 Drew Cain 3 Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) e2a8d1eba6c8d64bc61d7168df0fe8c29a670858bf9daeac464a0f440273fae0
130 src/basic_memory/repository/observation_repository.py observation_repository.py source_code 2943 2025-06-21T00:01:59.269646 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co) aa132f1cb4a36a84f715afdc40e2ac4f98d83e3eba1974b2b4404cc0b020ca04
131 src/basic_memory/repository/project_info_repository.py project_info_repository.py source_code 338 2025-06-21T00:01:59.252582 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) f172d50182a405643a19b2a3d62a80f4e2b41463c775394eb3b66d7de51ff6df
132 src/basic_memory/repository/project_repository.py project_repository.py source_code 3803 2025-07-30T09:39:45.724344 jope-bm 2 jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 9083ba214b2bcaaf98164d9c33cda7add52bc77f7269ecb6e43cdd5088728b7c
133 src/basic_memory/repository/relation_repository.py relation_repository.py source_code 3179 2025-06-21T00:01:59.246745 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) cfb3a8e59cff27e063e91bd00e949647bdd92e4d9fc46edeee3acc6de15e26f4
134 src/basic_memory/repository/repository.py repository.py source_code 15224 2025-06-21T00:01:59.273355 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 3096fe71bf106506cbf86aeafe2aafe0aabbe5a5f6c90a212c8aa1e53e1f171c
135 src/basic_memory/repository/search_repository.py search_repository.py source_code 21936 2025-07-03T18:43:10.082844 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) a972f73d1b71dac5770e8eb3793c6c9ac44e4e792f9dab528f8c4e6c6a80bcaa
136 src/basic_memory/schemas/__init__.py __init__.py source_code 1674 2025-06-21T00:01:59.514555 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 98480815c75379bfafe32d2090e87fa40e73caa19b664fbd5db5ea9528ba58cb
137 src/basic_memory/schemas/base.py base.py source_code 6738 2025-07-01T08:33:22.082193 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 171f7b0c4ab33abef2f7379eb1e3bda950586ce7edff83907fd12255f84e267e
138 src/basic_memory/schemas/delete.py delete.py source_code 1180 2025-06-21T00:01:59.502563 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 50047624af7d58c8f780ffb2a065a51c3dde64492f9534917fc42860813e59fc
139 src/basic_memory/schemas/directory.py directory.py source_code 881 2025-06-21T00:01:59.529023 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 17dfcbac9a91a9bfe43b4f060ca2735cb6f69e16d81b63dd554a397830ff29fe
140 src/basic_memory/schemas/importer.py importer.py source_code 693 2025-07-30T09:39:45.724633 jope-bm 2 jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ac33df423ca32b28ce7b6ea9c29d541f8783a86c0c29f78db35163bf93f139cd
141 src/basic_memory/schemas/memory.py memory.py source_code 5833 2025-07-03T18:43:10.083167 Drew Cain 3 Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) acb4a953a553feca672c4895798a7d948ec51f922f75b66dd7d55716e3bebed3
142 src/basic_memory/schemas/project_info.py project_info.py source_code 7047 2025-07-01T08:33:22.083248 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7dc3635297b6e7fe6e3262b2d78da26f7a79a846a4cecd5624f2e4828979cf2c
143 src/basic_memory/schemas/prompt.py prompt.py source_code 3648 2025-06-21T00:01:59.525753 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 4a92157d9a6b413f04e6e3f8d23dc2a417369c729f9703a8de2643ec114f2071
144 src/basic_memory/schemas/request.py request.py source_code 3760 2025-06-21T00:01:59.511789 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 32fe44beb2d99452223ebf1d3a3a3fe105ef92c79885023b71dfd7db30ecc503
145 src/basic_memory/schemas/response.py response.py source_code 6450 2025-07-03T18:43:10.083478 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 5eea4660a2abe48d83ed083d1c2483fdcfb403e0b504f03c14d22f1cae866abb
146 src/basic_memory/schemas/search.py search.py source_code 3657 2025-06-21T00:01:59.522804 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) cb032c0c60102b6b0ed934f995cf9d6bf93aece296d71d537a75e8ae61c75afe
147 src/basic_memory/services/__init__.py __init__.py source_code 259 2025-06-21T00:01:59.655059 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 5c6b7c597ddf5ffd0af4bdfb32ccbc1c5f2794c658206dee43a9945fafe226d8
148 src/basic_memory/services/context_service.py context_service.py source_code 14462 2025-06-21T00:01:59.672086 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (paulmh@gmail.com) e1178b005e6a89f03d6b239e3c6b152aeb30d535a3f280734440a3aec16228f2
149 src/basic_memory/services/directory_service.py directory_service.py source_code 6017 2025-06-21T00:01:59.648501 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) fd838f5ec79033892777b3c8140868f4b57f13e1658d513959524a434b9f959b
150 src/basic_memory/services/entity_service.py entity_service.py source_code 30440 2025-07-03T18:43:10.083956 Paul Hernandez 4 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 7cd5163eca6b8a0772e838c8c869e478166763cd6a2d745b0b9aa5c25a65baee
151 src/basic_memory/services/exceptions.py exceptions.py source_code 390 2025-06-21T00:01:59.668908 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) a158d0af9d1742a9c5ab5f8c34a062948d9286d1c3c72a5abf20e4d547b21e1c
152 src/basic_memory/services/file_service.py file_service.py source_code 10059 2025-06-21T00:01:59.641785 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 8c2ae69c4913438b7d1c5ecbfcce812fbb5d0ea8e3cedcbd8694e8f40cf086f3
153 src/basic_memory/services/initialization.py initialization.py source_code 6715 2025-07-08T20:39:18.344482 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 4dde58b799cf4ac90631e59949b55b3355a93bdf99df0d9c2408c0cea67e10c4
154 src/basic_memory/services/link_resolver.py link_resolver.py source_code 4594 2025-07-01T08:33:22.084908 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) d7efd516cbea753e6b5411def09aeaeb7539f5743487119ecf163c736e138a8c
155 src/basic_memory/services/project_service.py project_service.py source_code 30125 2025-07-30T09:39:45.725079 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) bce95c00aa9a1da0a8c8090ee34b5b1494207f8c99e6c25de4ec43fddc38f6be
156 src/basic_memory/services/search_service.py search_service.py source_code 12782 2025-06-21T00:01:59.665439 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7392b2d2e7f3ed83c5807855ccd450e0e79c17f254aedee700bce9323a1b5b83
157 src/basic_memory/services/service.py service.py source_code 336 2025-06-21T00:01:59.636167 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 57e77ff20395d3bcc62100e92fe2acaacdd937d977a9fdc764e2b57ff60d4da8
158 src/basic_memory/services/sync_status_service.py sync_status_service.py source_code 7504 2025-07-03T18:43:10.084229 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 0a025d689e8e16f1632872104952105fc904537f3d32bcff592eb1f1dc76fbb7
159 src/basic_memory/sync/__init__.py __init__.py source_code 156 2025-06-21T00:01:59.621137 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 0951e0b981f8e7b876bb6c6833c2af3a29490bbd5726567ea9487c947723623e
160 src/basic_memory/sync/background_sync.py background_sync.py source_code 726 2025-07-08T20:39:18.345062 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 549af64ae91128b76c6df07ef517b82de85ca4ad796be44b5c0141fac01d4513
161 src/basic_memory/sync/sync_service.py sync_service.py source_code 23387 2025-07-01T08:33:22.086507 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 0310b927561370f593980d07773bce641b618b8fbf2d9e3890d174290a0344fc
162 src/basic_memory/sync/watch_service.py watch_service.py source_code 15316 2025-06-26T09:28:51.859209 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) 240ba6ac7523575945f4db442b7da3820d26c9605f2d7a2d357c4e35e215f523
163 src/basic_memory/templates/prompts/continue_conversation.hbs continue_conversation.hbs templates 3076 2025-07-01T08:33:22.086792 Drew Cain 2 Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) b6bac31d25c0e52d0909b2273285092f4e31bc219107f92ed511cd407b65e992
164 src/basic_memory/templates/prompts/search.hbs search.hbs templates 3098 2025-05-25T10:07:54.520119 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 1f570222c1caa78542d46ac7d8a79407ca467b9bd714fa9bd953e8b72a667820
165 src/basic_memory/utils.py utils.py source_code 8654 2025-07-30T09:39:45.725428 jope-bm 5 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); andyxinweiminicloud (andyxinweimin263@icloud.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 111f343e3367339730e081c08acf4613667be574f946126513b5d82da3086bed
166 test-int/conftest.py conftest.py source_code 8203 2025-07-08T20:39:18.345735 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 7231e1bc76cddc7c94433e853825dc0165b103bedf996c2c24eda6ae32fff5ad
167 test-int/mcp/test_build_context_validation.py test_build_context_validation.py source_code 6469 2025-07-08T20:39:18.346021 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) ee61d6b15cee0194fd74c1bcf9361d5a839f6081c7d571ef081da8fcefe1bbf2
168 test-int/mcp/test_delete_note_integration.py test_delete_note_integration.py source_code 13175 2025-07-08T20:39:18.346366 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 497495249b8bcf0af177d8705c6899875460707697980434e560cfe56f86feb3
169 test-int/mcp/test_edit_note_integration.py test_edit_note_integration.py source_code 19822 2025-07-08T20:39:18.346730 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 9ba704badd05808aee0b5a686b7e250e1c865229ec92dab561cba5287b6f6fc6
170 test-int/mcp/test_list_directory_integration.py test_list_directory_integration.py source_code 14744 2025-07-08T20:39:18.347106 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 2c4336a59de63f17ed252bb52d1330ebca901ad83bfebc9e5aa67f132a3c77c6
171 test-int/mcp/test_move_note_integration.py test_move_note_integration.py source_code 19825 2025-07-08T20:39:18.347344 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) cbb17688d0f9551306a7520b059ace7f7dfba70fc20bf0ec98b1148000d7ad7d
172 test-int/mcp/test_project_management_integration.py test_project_management_integration.py source_code 33175 2025-07-08T20:39:18.347658 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 93a43cd699453a7602a5def839740ba5027ab914eba3e7b66a026860ed6bd567
173 test-int/mcp/test_project_state_sync_integration.py test_project_state_sync_integration.py source_code 7728 2025-07-08T20:39:18.347907 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 80b0f5b5cd359498dcbd3af7dcb6194afa4582d8efb3faa38d28675ee45e0748
174 test-int/mcp/test_read_content_integration.py test_read_content_integration.py source_code 11187 2025-07-08T20:39:18.348233 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 6955dd343b40df088ac79175e52723501d83796335a16e43b7f58e16e757e1b8
175 test-int/mcp/test_read_note_integration.py test_read_note_integration.py source_code 1381 2025-07-08T20:39:18.348463 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) b93050da68d0e9d47d46879a386567f688399e28390f4eb31ee37353dc27514b
176 test-int/mcp/test_search_integration.py test_search_integration.py source_code 14690 2025-07-08T20:39:18.348959 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) e0957ada9dd9b12eae5843a37d241ec9293ca929f18281e31197dae36bff589d
177 test-int/mcp/test_write_note_integration.py test_write_note_integration.py source_code 9472 2025-07-08T20:39:18.349398 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) b2b56fee8351168d0c65640c295d969dc4575d9bea87927b2dc07e591cabdff3
178 tests/Non-MarkdownFileSupport.pdf Non-MarkdownFileSupport.pdf other 106751 2025-02-23T17:31:23.206428 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 4aa96f9d0ee830ef6a1ec92243f302bbe2fa8971b84945f4eb5e276bf1ffde96
179 tests/Screenshot.png Screenshot.png other 190076 2025-02-23T17:31:23.207771 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) dd82612fe013c39810d28ae0f2bbcca6ab2c9925b035d7720bb02493ddf5d0a9
180 tests/__init__.py __init__.py source_code 141 2025-06-21T00:01:46.216456 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 2ac5605e69ad8829e96b3689632536539f1cdd3149a29c1903f25af0793a5d65
181 tests/api/conftest.py conftest.py source_code 1404 2025-06-21T00:01:46.359728 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) ea46cde47a37c1f43fe079e48f5d0de8007155a0187f4445abc4f2ffde9c1791
182 tests/api/test_async_client.py test_async_client.py source_code 1382 2025-07-30T09:39:45.725666 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) a2f96226a75e5cff2326ca48ddccea4915964d83e529f7b99958bc233ccdc31a
183 tests/api/test_continue_conversation_template.py test_continue_conversation_template.py source_code 5152 2025-06-21T00:01:46.390901 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) cca08b0291db0ad693c60d8dc203660021feab46d9eb530be7f67e0ebb52c1d3
184 tests/api/test_directory_router.py test_directory_router.py source_code 9376 2025-06-21T00:01:46.356659 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 428626589f0010512481ba495368b0bf2b9617b22764e588eaa3562e86da4d37
185 tests/api/test_importer_router.py test_importer_router.py source_code 16578 2025-06-21T00:01:46.368070 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 03edf6e7404c7350afe76e54ea2bf0d66f668bcdb49f59702067a67b3694309d
186 tests/api/test_knowledge_router.py test_knowledge_router.py source_code 45771 2025-07-01T08:33:22.090490 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 43a5f91ae1fb9038e65c09701336fbdc4dbf40c8319c94727e6312fda9d6d56e
187 tests/api/test_management_router.py test_management_router.py source_code 6220 2025-06-21T00:01:46.402764 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) f6a3f41f263f10c08281525571e2e39fc9ae818ebb7e9895e3f3f3115ea05677
188 tests/api/test_memory_router.py test_memory_router.py source_code 5555 2025-06-21T00:01:46.399667 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co) 2b8782015c893081fc67b7f13a7255d4e3dc45b27705a524f94918a339e2d486
189 tests/api/test_project_router.py test_project_router.py source_code 13288 2025-07-30T09:39:45.725847 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) af85d0870069d3831a9214372853994e5d12dc910e37298eb9958f72f6a8bb31
190 tests/api/test_project_router_operations.py test_project_router_operations.py source_code 1812 2025-06-21T00:01:46.381705 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) afd40879de3d7c508cca0feeae3cb7a8f3afcfbb06f59a6104189b1e3291376a
191 tests/api/test_prompt_router.py test_prompt_router.py source_code 5046 2025-06-21T00:01:46.396588 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 9115995b7dd8b44d8a9c06c5e1bbb177618286675a769889e35ac16f26fd9897
192 tests/api/test_resource_router.py test_resource_router.py source_code 13960 2025-06-21T00:01:46.384919 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ad3cb2cabaeab82a7e64253ceede7c1791e2bd70c92f60bc5f3be939292ca22d
193 tests/api/test_search_router.py test_search_router.py source_code 6409 2025-06-21T00:01:46.375427 bm-claudeai (AI Assistant) 3 bm-claudeai (AI Assistant) (claude@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (phernandez@basicmachines.co) ba3e3809795e72db9dd9c8c729048bdbbbdb897c2a5aa8b175112b5d4b56d152
194 tests/api/test_search_template.py test_search_template.py source_code 5258 2025-06-21T00:01:46.378721 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 8f0d16a1de947d0466d96afed02e695f7648adac70b9ba99753250068207a686
195 tests/api/test_template_loader.py test_template_loader.py source_code 8035 2025-06-21T00:01:46.406694 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 4fd7c6c4862cc0d877d29244ecdc0b52a0693fc4574a9667663bee36bfd36b62
196 tests/api/test_template_loader_helpers.py test_template_loader_helpers.py source_code 7445 2025-06-21T00:01:46.372114 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) 8dd35f11c9b3f2d96ac36263edf4430308728c04c9da543b1cb845b96ab20aa7
197 tests/cli/conftest.py conftest.py source_code 1185 2025-07-01T08:33:22.090848 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) c3c73da7ad73224931e1fba4ebd70792221be607bfb1488bb5c9420bba03bdb8
198 tests/cli/test_cli_tools.py test_cli_tools.py source_code 13528 2025-06-21T00:01:46.318964 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) befb8cab50132e26a1b13b82ce8e93a3258282cf157a4230214bfcefeae65102
199 tests/cli/test_import_chatgpt.py test_import_chatgpt.py source_code 6719 2025-07-08T20:39:18.350234 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) df447335545248e2fb2417af33f8cc2f0143bd413a1783e41f89605de305b7c8
200 tests/cli/test_import_claude_conversations.py test_import_claude_conversations.py legal 5028 2025-07-08T20:39:18.350607 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ac4b3b9dfc331fa7fa32a2941c027ff8b6585a94a5ccfaaf0a4affe5cf19c00c
201 tests/cli/test_import_claude_projects.py test_import_claude_projects.py legal 4585 2025-07-08T20:39:18.350885 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 7eaab3d491d50ca59c8ba015ecd5740a5feae94d970ee17559c6df9d8558c2cc
202 tests/cli/test_import_memory_json.py test_import_memory_json.py source_code 4891 2025-07-30T09:39:45.726290 jope-bm 3 jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (60959+phernandez@users.noreply.github.com) d6734ce059dacf97bc764d848b2e2a6ba52fa96871bc331ec9d2ddc60b766bb6
203 tests/cli/test_project_commands.py test_project_commands.py source_code 6578 2025-07-30T09:39:45.726703 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 51e2ac7ff44b0d1a1db6da406c5c2e6aebceb1b63a1e8d5ab51bbaa834507157
204 tests/cli/test_project_info.py test_project_info.py source_code 3874 2025-07-01T08:33:22.091600 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 0b08950e95a5f276f14c56f6fe99af4d41b8f04548e9b7746f807c5ac4155578
205 tests/cli/test_status.py test_status.py source_code 4160 2025-07-01T08:33:22.091978 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) a1ff89fdac4bf57ba03be92ac79b8ca3210af0b1132d5a2bd0fc0c253fcbf988
206 tests/cli/test_sync.py test_sync.py source_code 3864 2025-07-08T20:43:47.816788 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 77e0f6b42b584a5acfda3c2ff194ff5e71ca57a40cb4484d563f5482bbbb4c0f
207 tests/cli/test_version.py test_version.py source_code 289 2025-06-21T00:01:46.305368 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) db95665178a32d391bd83011d073db269cefba9da8a4dc4d3e392d47bc0e0a8f
208 tests/conftest.py conftest.py source_code 15286 2025-07-08T20:39:18.351368 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) c6f38f103ce58144ffe4562f0c47dad849974e96327f02805e854447e19bc411
209 tests/importers/test_importer_base.py test_importer_base.py source_code 4329 2025-07-08T20:39:00.549594 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) da872a073f2b90fda3c98c9f44cf45884f4f682d91ec1b1f931d42c34eee1939
210 tests/importers/test_importer_utils.py test_importer_utils.py source_code 1827 2025-06-21T00:01:46.184343 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) cbee630c855ceb1de764ac0cdb1d5d781c16cd3a934d67166adc235c98f363d7
211 tests/markdown/__init__.py __init__.py source_code 0 2025-06-21T00:01:46.196570 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
212 tests/markdown/test_entity_parser.py test_entity_parser.py source_code 8669 2025-06-21T00:01:46.206579 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 496fdab6fe164634f8223f827585a4c0dedd30ccea7b0b69dc79004ad0996de2
213 tests/markdown/test_markdown_plugins.py test_markdown_plugins.py source_code 5139 2025-06-21T00:01:46.200759 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 9624d73462aadcd47fb0edd0d87880959a8982eecfa39529f21e3b292c34cb83
214 tests/markdown/test_markdown_processor.py test_markdown_processor.py source_code 5728 2025-06-21T00:01:46.194175 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 35042aa57dd2aeed848c624aa7f69decfb18e83c3b217eca1fdacfc4a10831db
215 tests/markdown/test_observation_edge_cases.py test_observation_edge_cases.py source_code 4295 2025-06-21T00:01:46.209598 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 6f1ab7684bfa8b02af0e0c42b3d7aed91fcd0b1c6288cbc9b6d6943c166979fa
216 tests/markdown/test_parser_edge_cases.py test_parser_edge_cases.py source_code 5142 2025-06-21T00:01:46.213291 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) d511d7d1187e3c1d3a8d3e0ab9efc9e68e84115c46811e7a9dc21eca9a87d520
217 tests/markdown/test_relation_edge_cases.py test_relation_edge_cases.py source_code 3952 2025-06-21T00:01:46.203846 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) f8e2016308f84139342cc37ec6e389ffe52d7eb46f998ee327c7f0d5bba3ad9b
218 tests/markdown/test_task_detection.py test_task_detection.py source_code 401 2025-06-21T00:01:46.191444 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 37fe91a689742033a0eb76723557e31c2a85e591133383b3887572cb714db2a5
219 tests/mcp/conftest.py conftest.py source_code 1761 2025-07-08T20:39:18.351554 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 350b2ca42c52ef76e002deddb65fc0eccb9f8989bd26078ea1c100e3b93bae3f
220 tests/mcp/test_prompts.py test_prompts.py source_code 5989 2025-07-01T08:33:22.093691 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) e3b2ee5b4bc120b5043225ebdbf73eadd71be9c3d9d8e1fdd66a24a8ff11a780
221 tests/mcp/test_resource_project_info.py test_resource_project_info.py source_code 4885 2025-07-01T08:33:22.094085 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6a043736789b239c8d4ae00d1a5332a0fc3bed4038fe3388970ba97dc7431a93
222 tests/mcp/test_resources.py test_resources.py source_code 549 2025-07-01T08:33:22.094481 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 77a6396b9b8a9d3b3d202e438243ca6178d87fd7680c9fc063ee013f2042aa4c
223 tests/mcp/test_tool_build_context.py test_tool_build_context.py source_code 3714 2025-07-01T08:33:22.094661 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 5f0a39fdb652605335ed54aa91fe994262441bb7ba46f381c46ebca882fc23f2
224 tests/mcp/test_tool_canvas.py test_tool_canvas.py source_code 7997 2025-07-01T08:33:22.094982 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) c517bb1f9edc77b76fbeb483dfcb2dd57de9f0b80a10738708bc90cbe388dc9e
225 tests/mcp/test_tool_delete_note.py test_tool_delete_note.py source_code 4208 2025-07-01T08:33:22.095578 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 4cbf3ba4313438651b0608d5b25faf1400f7bd6a78af630351aecd4457800895
226 tests/mcp/test_tool_edit_note.py test_tool_edit_note.py source_code 13503 2025-07-03T18:43:10.084890 Drew Cain 2 Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) dd441969aa54b3739f2d9202d3feaf46aee807ec51e01a13ef41eda52743944f
227 tests/mcp/test_tool_list_directory.py test_tool_list_directory.py source_code 7730 2025-07-01T08:33:22.096737 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) cfbb5c06bcfe74ea60f31051eb9b03ac80b86232b053c188bf3b172132f7c0f5
228 tests/mcp/test_tool_move_note.py test_tool_move_note.py source_code 25851 2025-07-30T09:39:45.727347 jope-bm 2 jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co) c9f95158503fa42e37532aaa35027538fa49cea718d059d570cf547d31f01043
229 tests/mcp/test_tool_read_content.py test_tool_read_content.py source_code 19442 2025-07-30T09:39:45.727526 jope-bm 1 jope-bm (joe@basicmemory.com) 1817e32f997b06dbdabf02864d0c70584c1cd80bf1fae8dfbef649de79434bc5
230 tests/mcp/test_tool_read_note.py test_tool_read_note.py source_code 22991 2025-07-30T09:39:45.727746 jope-bm 4 jope-bm (joe@basicmemory.com); Paul Hernandez (paul@basicmachines.co); Amadeusz Wieczorek (git@amadeusw.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 4e13ea9ff977525aa62b6a8c22505ffe989329dc3e7d07b71e044cf43ef3ddfd
231 tests/mcp/test_tool_recent_activity.py test_tool_recent_activity.py source_code 3983 2025-07-01T08:33:22.098561 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 45414152cd45334b84df56f653146b7bd2939907548e7245a37207fdccfc4c87
232 tests/mcp/test_tool_resource.py test_tool_resource.py source_code 6782 2025-07-01T08:33:22.098948 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 3916ae1c4d458eee53d2c570b9bbb708ecd617de5a145c3a7552389954bc9195
233 tests/mcp/test_tool_search.py test_tool_search.py source_code 10959 2025-07-03T18:43:10.085177 Drew Cain 3 Drew Cain (groksrc@gmail.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) 07839cf8e89d74ba05ec1dc55fa842444c1f0cc3d1587823b0ac3458ad89e87c
234 tests/mcp/test_tool_sync_status.py test_tool_sync_status.py source_code 6331 2025-07-01T08:33:22.100021 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 3f3566df5b9f3c66bb9b3bd77d4c05825480d0e85015305cb1b7ab9429c7a7ec
235 tests/mcp/test_tool_utils.py test_tool_utils.py source_code 9228 2025-07-01T08:33:22.100561 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) b50789f7a78b0169f52dc8bd16e262510ed4a0ac130fce698d4be84af1fe2d31
236 tests/mcp/test_tool_view_note.py test_tool_view_note.py source_code 10004 2025-07-01T08:33:22.101164 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 385535ce791c3ab55db827cbcf27ea25ae85faf3a027f52df4eea052cede7f36
237 tests/mcp/test_tool_write_note.py test_tool_write_note.py source_code 35136 2025-07-30T09:39:45.728137 jope-bm 3 jope-bm (joe@basicmemory.com); Drew Cain (groksrc@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 45172de20cfc4f4436287573b4f5a94b8b616a360647af67ba1c6c343ccd41b8
238 tests/repository/test_entity_repository.py test_entity_repository.py source_code 15498 2025-06-21T00:01:46.176265 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) 8f5d1ff6cff7b640993ff73f463b1e7801a4dd69640fc55fa23dc58b865e9943
239 tests/repository/test_entity_repository_upsert.py test_entity_repository_upsert.py source_code 16983 2025-07-03T18:43:10.085482 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com) ee0ecd6f4ff8b31838bd6de6fb1018af437e02813d8315c06f4e20a6b78a0572
240 tests/repository/test_observation_repository.py test_observation_repository.py source_code 11786 2025-06-21T00:01:46.158862 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co) 3006d392ad94dcc9b3ae031d63a1af6935624534389dd025de70d4c9e112aa0b
241 tests/repository/test_project_info_repository.py test_project_info_repository.py source_code 1186 2025-06-21T00:01:46.161876 bm-claudeai (AI Assistant) 1 bm-claudeai (AI Assistant) (claude@basicmachines.co) dbce7ae7350403cbc37343c2e3e68e4d2255e247006aaa6c495a7ce8bfddea7d
242 tests/repository/test_project_repository.py test_project_repository.py source_code 10511 2025-07-30T09:39:45.728536 jope-bm 2 jope-bm (joe@basicmemory.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 279a3324635b1b515c18003bd1875cf230fef1192cadef9ca426846783717e75
243 tests/repository/test_relation_repository.py test_relation_repository.py source_code 11980 2025-06-21T00:01:46.167567 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (phernandez@basicmachines.co) fcdbdf40f7b56145b98eedde5ead64ef6187e859adde48cc09f5a97754757428
244 tests/repository/test_repository.py test_repository.py source_code 5996 2025-06-21T00:01:46.152691 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 3a6d4ca0b7a438a30ae504f5eac22874d4e1a937a4246e90efad46c90f0e1c95
245 tests/repository/test_search_repository.py test_search_repository.py source_code 25471 2025-07-03T18:43:10.086041 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 805b5cc55a53ae9d6e86c0de735c7f38ac9fd92c23194b0150fa3e7f5507a146
246 tests/schemas/test_memory_url.py test_memory_url.py source_code 2000 2025-06-21T00:01:46.346515 Paul Hernandez 1 Paul Hernandez (paulmh@gmail.com) 946fa6c48534f41c8b9cc27bbad164d3e3673a79afa84fbb439fae4cd26c504b
247 tests/schemas/test_memory_url_validation.py test_memory_url_validation.py source_code 9338 2025-07-01T08:33:22.103247 Paul Hernandez 1 Paul Hernandez (paul@basicmachines.co) 7054c3a3cdb8e05d2ffd254ed298908ff3d48a0a3927d960975eae1ab9beeb38
248 tests/schemas/test_schemas.py test_schemas.py source_code 16880 2025-07-03T18:43:10.086337 Drew Cain 3 Drew Cain (groksrc@gmail.com); Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 2c746a00c340f063c160ace67569955a300a294040949695c79fd4ae124fb611
249 tests/schemas/test_search.py test_search.py source_code 3264 2025-06-21T00:01:46.343685 github-actions[bot] (AI Assistant) 2 github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com); Paul Hernandez (60959+phernandez@users.noreply.github.com) 7437775d1b4a61a438cbabe3dbb1becd2adc4dbcc0c24595830c0843fa7a3166
250 tests/services/test_context_service.py test_context_service.py source_code 8833 2025-06-21T00:01:46.446200 bm-claudeai (AI Assistant) 2 bm-claudeai (AI Assistant) (claude@basicmachines.co); Paul Hernandez (paulmh@gmail.com) ab49d21a05d5364e7e8a2f00b07aabe1109b7417698e0acd92792e2cb5f22732
251 tests/services/test_directory_service.py test_directory_service.py source_code 6854 2025-06-21T00:01:46.457258 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) a0c765fb6bd1a75b6ba4592b1df344c48273b6ab03ba6bd15d2b5e840eb2d521
252 tests/services/test_entity_service.py test_entity_service.py source_code 59278 2025-07-03T18:43:10.086674 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) d7d495756faa10b415d705cf26b69d2efbc5f190a834b6767386ff296629ea71
253 tests/services/test_file_service.py test_file_service.py source_code 5243 2025-06-21T00:01:46.460141 Paul Hernandez 1 Paul Hernandez (phernandez@basicmachines.co) 83e6e9a7584b7baeccbc84865d8c1d3e2a0223e7d2b9c066d3799c289196ab7d
254 tests/services/test_initialization.py test_initialization.py source_code 6939 2025-07-08T20:39:18.351946 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) ebb8d11ff37dcb36c4844c68f873c8e0e8aa6ab1b82abb22ee96c4233e6ab287
255 tests/services/test_link_resolver.py test_link_resolver.py source_code 13618 2025-07-01T08:33:22.105766 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) c9fc56d0c3572770e95ca6175c213ed8b44c2a6ddddc298b5662cc2d8f41014e
256 tests/services/test_project_service.py test_project_service.py source_code 28152 2025-07-30T09:39:45.728920 jope-bm 3 jope-bm (joe@basicmemory.com); Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 430868ed796518937a3b0bed4d8c33be53264a6100d61d54b0745bb2b58bf29a
257 tests/services/test_project_service_operations.py test_project_service_operations.py source_code 4679 2025-06-21T00:01:46.442141 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) d4103b6c5fff7e74ba84b86033537b8522aa60fc75aec4974d52e1dfec68b1b4
258 tests/services/test_search_service.py test_search_service.py source_code 25299 2025-06-21T00:01:46.439133 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) b3e58258db2b727ab8a1792e52fcb038d3a0779bcd1b70f2d295a9d06e5e2513
259 tests/services/test_sync_status_service.py test_sync_status_service.py source_code 9926 2025-07-03T18:43:10.087266 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 00245ca356e33fc7afcd2d42cb5ec082038c95383d738067821cf1e8d23c9a4b
260 tests/sync/test_sync_service.py test_sync_service.py source_code 39659 2025-07-01T08:33:22.107088 Paul Hernandez 2 Paul Hernandez (paul@basicmachines.co); bm-claudeai (AI Assistant) (claude@basicmachines.co) 0609aee9906549f27fc4289b5ac2d46a50e53e12a0615a2701f17fbe9a74e169
261 tests/sync/test_sync_wikilink_issue.py test_sync_wikilink_issue.py source_code 1959 2025-06-21T00:01:46.420370 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) db4b98dd7008a1a26b0fd02b9a2d03bca950b281efc19d98a91b017c500b23a5
262 tests/sync/test_tmp_files.py test_tmp_files.py source_code 5930 2025-06-21T00:01:46.424277 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) cead95a07b29f6c1ab933abe67f9648122f5f33d33753c8e4ab4934af96cbc25
263 tests/sync/test_watch_service.py test_watch_service.py source_code 14076 2025-06-26T09:28:51.860679 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) e324a4127f8bfe59798f3a08125488c701e88239b7482d87a0394b91c55b893e
264 tests/sync/test_watch_service_edge_cases.py test_watch_service_edge_cases.py source_code 2169 2025-06-21T00:01:46.409830 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) b87c5015330e9b551630aab7ef95ac1e7663f0ce46e912d8155b319e139559aa
265 tests/test_config.py test_config.py source_code 3680 2025-07-03T18:43:10.087558 Paul Hernandez 3 Paul Hernandez (paul@basicmachines.co); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) 6a246431cff739274efda895dfa28f38710289a73e645312f5fa05fce0885ec8
266 tests/test_db_migration_deduplication.py test_db_migration_deduplication.py source_code 6166 2025-07-08T20:39:18.352690 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com) ab768e061531ca2edcd598c101b5d884e549f90c7a4b305a23713a199d263fbb
267 tests/utils/test_file_utils.py test_file_utils.py source_code 6199 2025-06-21T00:01:46.219673 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 4615bb20b92b8a79d9e086301f852b34c3360f17ecc209920d68528268ddeed4
268 tests/utils/test_parse_tags.py test_parse_tags.py source_code 1691 2025-06-21T00:01:46.223400 Paul Hernandez 2 Paul Hernandez (60959+phernandez@users.noreply.github.com); github-actions[bot] (AI Assistant) (41898282+github-actions[bot]@users.noreply.github.com) b83afdf18dac8b3411bd0fab1301683217d3d1abceaf2d22c09e79c709efd8eb
269 tests/utils/test_permalink_formatting.py test_permalink_formatting.py source_code 4193 2025-06-21T00:01:46.225738 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) d998a4ca846c78679728a918b6f71df67c30b6961ede3cffa609f2cf6ad160eb
270 tests/utils/test_utf8_handling.py test_utf8_handling.py source_code 5791 2025-06-21T00:01:46.228483 Paul Hernandez 1 Paul Hernandez (60959+phernandez@users.noreply.github.com) 50ec792e572af51cd2d996d4ddacbcfd3bbd79aa928818a90c42117451ea829f
271 tests/utils/test_validate_project_path.py test_validate_project_path.py source_code 15346 2025-07-30T09:39:45.729224 jope-bm 1 jope-bm (joe@basicmemory.com) 27a4bf2a9d277e6cd773619e5241b20175d211ec60c284361bec404fd99143f3
272 uv.lock uv.lock other 232710 2025-07-30T09:39:36.619155 Paul Hernandez 3 Paul Hernandez (60959+phernandez@users.noreply.github.com); Drew Cain (groksrc@users.noreply.github.com); bm-claudeai (AI Assistant) (claude@basicmachines.co) caa1e3f6e66464b884a78cab77df2fe303fa3c0dc573be0196474b6d3c150581
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-294
View File
@@ -1,294 +0,0 @@
# Basic Memory - Legal File Inventory
**Generated:** 2025-07-29T16:10:54.745774
**Repository:** /Users/phernandez/dev/basicmachines/basic-memory
## Summary Statistics
- **Total Files:** 255
- **Total Contributors:** 12
### Files by Category
- **Build/Deployment:** 2 files
- **Configuration:** 6 files
- **Database/Migration:** 1 files
- **Documentation:** 6 files
- **Legal/License:** 3 files
- **Other:** 13 files
- **Source Code:** 222 files
- **Templates/Resources:** 2 files
### Top Contributors by Files Modified
- **phernandez:** 190 files
- **Paul Hernandez:** 181 files
- **bm-claudeai:** 135 files
- **Drew Cain:** 24 files
- **github-actions[bot]:** 20 files
- **Amadeusz Wieczorek:** 2 files
- **semantic-release:** 1 files
- **Jason Noble:** 1 files
- **Marc Baiza:** 1 files
- **Matias Forbord:** 1 files
## Detailed File Inventory
| File Path | Category | Size (bytes) | Primary Author | Contributors |
|-----------|----------|--------------|----------------|-------------|
| .claude/commands/release/beta.md | Other | 3103 | phernandez | phernandez |
| .claude/commands/release/changelog.md | Documentation | 4648 | phernandez | phernandez |
| .claude/commands/release/release-check.md | Other | 3344 | phernandez | phernandez |
| .claude/commands/release/release.md | Other | 2925 | phernandez | phernandez |
| .claude/commands/test-live.md | Other | 18720 | phernandez | phernandez |
| .claude/settings.local.json | Configuration | 3122 | Unknown | |
| .dockerignore | Other | 623 | Paul Hernandez | Paul Hernandez |
| .python-version | Other | 5 | phernandez | phernandez |
| CHANGELOG.md | Documentation | 53647 | semantic-release | semantic-release, phernandez |
| CITATION.cff | Legal/License | 302 | phernandez | phernandez |
| CLA.md | Legal/License | 1342 | phernandez | phernandez |
| CLAUDE.md | Other | 12310 | Paul Hernandez | Paul Hernandez, phernandez, Ikko Eltociear Ashimin... |
| CODE_OF_CONDUCT.md | Other | 509 | phernandez | phernandez |
| CONTRIBUTING.md | Documentation | 7241 | phernandez | phernandez, Paul Hernandez |
| Dockerfile | Build/Deployment | 856 | Paul Hernandez | Paul Hernandez, bm-claudeai |
| LICENSE | Legal/License | 34523 | Paul Hernandez | Paul Hernandez |
| README.md | Documentation | 16261 | phernandez | phernandez, bm-claudeai, Paul Hernandez, Jason Nob... |
| SECURITY.md | Other | 303 | Paul Hernandez | Paul Hernandez |
| docker-compose.yml | Configuration | 2534 | Paul Hernandez | Paul Hernandez, phernandez |
| docs/AI Assistant Guide.md | Documentation | 16423 | Paul Hernandez | Paul Hernandez, phernandez |
| docs/Docker.md | Documentation | 9088 | Paul Hernandez | Paul Hernandez, phernandez |
| justfile | Build/Deployment | 5371 | phernandez | phernandez, Drew Cain |
| legal_file_inventory.py | Source Code | 17407 | Unknown | |
| llms-install.md | Other | 2307 | phernandez | phernandez |
| memory.json | Configuration | 90075 | phernandez | phernandez |
| pyproject.toml | Configuration | 3279 | phernandez | phernandez, Paul Hernandez, bm-claudeai, Drew Cain... |
| smithery.yaml | Configuration | 433 | bm-claudeai | bm-claudeai |
| src/basic_memory/__init__.py | Source Code | 256 | phernandez | phernandez |
| src/basic_memory/alembic/alembic.ini | Configuration | 3733 | phernandez | phernandez |
| src/basic_memory/alembic/migrations.py | Source Code | 718 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/alembic/script.py.mako | Database/Migration | 635 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py | Source Code | 4363 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py | Source Code | 1840 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py | Source Code | 4379 | bm-claudeai | bm-claudeai |
| src/basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py | Source Code | 4210 | phernandez | phernandez |
| src/basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py | Source Code | 1428 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py | Source Code | 3679 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/api/__init__.py | Source Code | 72 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/api/app.py | Source Code | 2897 | bm-claudeai | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/api/routers/__init__.py | Source Code | 398 | phernandez | phernandez, bm-claudeai |
| src/basic_memory/api/routers/directory_router.py | Source Code | 2054 | Paul Hernandez | bm-claudeai, Paul Hernandez |
| src/basic_memory/api/routers/importer_router.py | Source Code | 4513 | bm-claudeai | bm-claudeai |
| src/basic_memory/api/routers/knowledge_router.py | Source Code | 9738 | Paul Hernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/api/routers/management_router.py | Source Code | 2730 | bm-claudeai | bm-claudeai, phernandez |
| src/basic_memory/api/routers/memory_router.py | Source Code | 3003 | phernandez | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/api/routers/project_router.py | Source Code | 8239 | bm-claudeai | bm-claudeai, phernandez, Paul Hernandez |
| src/basic_memory/api/routers/prompt_router.py | Source Code | 9948 | bm-claudeai | bm-claudeai, phernandez |
| src/basic_memory/api/routers/resource_router.py | Source Code | 8058 | Paul Hernandez | phernandez, Paul Hernandez |
| src/basic_memory/api/routers/search_router.py | Source Code | 1226 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/api/routers/utils.py | Source Code | 5159 | bm-claudeai | bm-claudeai, Drew Cain |
| src/basic_memory/api/template_loader.py | Source Code | 8607 | bm-claudeai | bm-claudeai |
| src/basic_memory/cli/__init__.py | Source Code | 33 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/cli/app.py | Source Code | 2268 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| src/basic_memory/cli/commands/__init__.py | Source Code | 393 | Paul Hernandez | Paul Hernandez, phernandez |
| src/basic_memory/cli/commands/db.py | Source Code | 1480 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| src/basic_memory/cli/commands/import_chatgpt.py | Source Code | 2856 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| src/basic_memory/cli/commands/import_claude_conversations.py | Source Code | 2946 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| src/basic_memory/cli/commands/import_claude_projects.py | Source Code | 2891 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| src/basic_memory/cli/commands/import_memory_json.py | Source Code | 2947 | Paul Hernandez | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/cli/commands/mcp.py | Source Code | 2593 | phernandez | bm-claudeai, phernandez, Paul Hernandez |
| src/basic_memory/cli/commands/project.py | Source Code | 12252 | phernandez | Paul Hernandez, phernandez, bm-claudeai |
| src/basic_memory/cli/commands/status.py | Source Code | 5809 | phernandez | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/cli/commands/sync.py | Source Code | 8213 | phernandez | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/cli/commands/tool.py | Source Code | 9540 | phernandez | Paul Hernandez, phernandez, github-actions[bot] |
| src/basic_memory/cli/main.py | Source Code | 465 | Paul Hernandez | Paul Hernandez, phernandez |
| src/basic_memory/config.py | Source Code | 12620 | Paul Hernandez | phernandez, Paul Hernandez, bm-claudeai, Drew Cain... |
| src/basic_memory/db.py | Source Code | 7428 | phernandez | phernandez, Paul Hernandez, Drew Cain, bm-claudeai... |
| src/basic_memory/deps.py | Source Code | 12144 | bm-claudeai | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/file_utils.py | Source Code | 6700 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/importers/__init__.py | Source Code | 795 | bm-claudeai | bm-claudeai |
| src/basic_memory/importers/base.py | Source Code | 2531 | bm-claudeai | bm-claudeai |
| src/basic_memory/importers/chatgpt_importer.py | Source Code | 7372 | bm-claudeai | bm-claudeai |
| src/basic_memory/importers/claude_conversations_importer.py | Source Code | 5725 | bm-claudeai | bm-claudeai |
| src/basic_memory/importers/claude_projects_importer.py | Source Code | 5389 | bm-claudeai | bm-claudeai |
| src/basic_memory/importers/memory_json_importer.py | Source Code | 3986 | bm-claudeai | bm-claudeai, phernandez |
| src/basic_memory/importers/utils.py | Source Code | 1792 | bm-claudeai | bm-claudeai |
| src/basic_memory/markdown/__init__.py | Source Code | 501 | phernandez | phernandez |
| src/basic_memory/markdown/entity_parser.py | Source Code | 4516 | phernandez | phernandez, Paul Hernandez, github-actions[bot] |
| src/basic_memory/markdown/markdown_processor.py | Source Code | 4968 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/markdown/plugins.py | Source Code | 6639 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/markdown/schemas.py | Source Code | 1896 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/markdown/utils.py | Source Code | 3410 | Paul Hernandez | Paul Hernandez, phernandez |
| src/basic_memory/mcp/__init__.py | Source Code | 35 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/mcp/async_client.py | Source Code | 925 | Paul Hernandez | phernandez, Paul Hernandez |
| src/basic_memory/mcp/project_session.py | Source Code | 4203 | Paul Hernandez | Paul Hernandez, phernandez |
| src/basic_memory/mcp/prompts/__init__.py | Source Code | 615 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/mcp/prompts/ai_assistant_guide.py | Source Code | 821 | Paul Hernandez | Paul Hernandez, phernandez |
| src/basic_memory/mcp/prompts/continue_conversation.py | Source Code | 1998 | Paul Hernandez | Paul Hernandez, github-actions[bot], bm-claudeai |
| src/basic_memory/mcp/prompts/recent_activity.py | Source Code | 3311 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| src/basic_memory/mcp/prompts/search.py | Source Code | 1692 | Paul Hernandez | Paul Hernandez, bm-claudeai |
| src/basic_memory/mcp/prompts/utils.py | Source Code | 5431 | Paul Hernandez | Paul Hernandez, bm-claudeai |
| src/basic_memory/mcp/resources/ai_assistant_guide.md | Other | 14777 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/mcp/resources/project_info.py | Source Code | 1906 | Paul Hernandez | Paul Hernandez, bm-claudeai |
| src/basic_memory/mcp/server.py | Source Code | 1248 | Paul Hernandez | bm-claudeai, phernandez, Paul Hernandez |
| src/basic_memory/mcp/tools/__init__.py | Source Code | 1619 | Paul Hernandez | phernandez, Paul Hernandez, Drew Cain |
| src/basic_memory/mcp/tools/auth.py | Source Code | 1231 | phernandez | phernandez |
| src/basic_memory/mcp/tools/canvas.py | Source Code | 3738 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| src/basic_memory/mcp/tools/delete_note.py | Source Code | 7346 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/mcp/tools/edit_note.py | Source Code | 13570 | Paul Hernandez | Paul Hernandez, phernandez |
| src/basic_memory/mcp/tools/list_directory.py | Source Code | 5236 | Paul Hernandez | Paul Hernandez |
| src/basic_memory/mcp/tools/move_note.py | Source Code | 16708 | phernandez | Paul Hernandez, phernandez |
| src/basic_memory/mcp/tools/project_management.py | Source Code | 12944 | Paul Hernandez | Paul Hernandez, phernandez, Drew Cain |
| src/basic_memory/mcp/tools/read_content.py | Source Code | 8596 | Paul Hernandez | Paul Hernandez, bm-claudeai |
| src/basic_memory/mcp/tools/read_note.py | Source Code | 7614 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez, Amadeusz ... |
| src/basic_memory/mcp/tools/recent_activity.py | Source Code | 4833 | phernandez | Paul Hernandez, phernandez, bm-claudeai |
| src/basic_memory/mcp/tools/search.py | Source Code | 15883 | phernandez | phernandez, github-actions[bot], Paul Hernandez, D... |
| src/basic_memory/mcp/tools/sync_status.py | Source Code | 10517 | phernandez | phernandez |
| src/basic_memory/mcp/tools/utils.py | Source Code | 21426 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| src/basic_memory/mcp/tools/view_note.py | Source Code | 2499 | phernandez | phernandez |
| src/basic_memory/mcp/tools/write_note.py | Source Code | 6178 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| src/basic_memory/models/__init__.py | Source Code | 333 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/models/base.py | Source Code | 225 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/models/knowledge.py | Source Code | 7087 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/models/project.py | Source Code | 2773 | bm-claudeai | bm-claudeai, phernandez |
| src/basic_memory/models/search.py | Source Code | 1300 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/repository/__init__.py | Source Code | 327 | Paul Hernandez | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/repository/entity_repository.py | Source Code | 10405 | Drew Cain | phernandez, Paul Hernandez, Drew Cain, bm-claudeai... |
| src/basic_memory/repository/observation_repository.py | Source Code | 2943 | phernandez | phernandez, bm-claudeai |
| src/basic_memory/repository/project_info_repository.py | Source Code | 338 | Paul Hernandez | Paul Hernandez, bm-claudeai |
| src/basic_memory/repository/project_repository.py | Source Code | 3159 | bm-claudeai | bm-claudeai |
| src/basic_memory/repository/relation_repository.py | Source Code | 3179 | phernandez | phernandez, bm-claudeai |
| src/basic_memory/repository/repository.py | Source Code | 15224 | phernandez | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/repository/search_repository.py | Source Code | 21936 | phernandez | phernandez, Paul Hernandez, github-actions[bot], b... |
| src/basic_memory/schemas/__init__.py | Source Code | 1674 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/schemas/base.py | Source Code | 6738 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/schemas/delete.py | Source Code | 1180 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/schemas/directory.py | Source Code | 881 | bm-claudeai | bm-claudeai |
| src/basic_memory/schemas/importer.py | Source Code | 663 | bm-claudeai | bm-claudeai |
| src/basic_memory/schemas/memory.py | Source Code | 5833 | phernandez | phernandez, Paul Hernandez, Drew Cain, bm-claudeai... |
| src/basic_memory/schemas/project_info.py | Source Code | 7047 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| src/basic_memory/schemas/prompt.py | Source Code | 3648 | bm-claudeai | bm-claudeai |
| src/basic_memory/schemas/request.py | Source Code | 3760 | Paul Hernandez | phernandez, Paul Hernandez |
| src/basic_memory/schemas/response.py | Source Code | 6450 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/schemas/search.py | Source Code | 3657 | phernandez | phernandez, Paul Hernandez, github-actions[bot], b... |
| src/basic_memory/services/__init__.py | Source Code | 259 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/services/context_service.py | Source Code | 14462 | bm-claudeai | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/services/directory_service.py | Source Code | 6017 | bm-claudeai | bm-claudeai, Paul Hernandez |
| src/basic_memory/services/entity_service.py | Source Code | 30440 | Paul Hernandez | phernandez, Paul Hernandez, bm-claudeai, Drew Cain... |
| src/basic_memory/services/exceptions.py | Source Code | 390 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/services/file_service.py | Source Code | 10059 | Paul Hernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/services/initialization.py | Source Code | 6715 | bm-claudeai | Paul Hernandez, bm-claudeai, Drew Cain, phernandez... |
| src/basic_memory/services/link_resolver.py | Source Code | 4594 | phernandez | phernandez, Paul Hernandez, bm-claudeai, github-ac... |
| src/basic_memory/services/project_service.py | Source Code | 28386 | bm-claudeai | bm-claudeai, Paul Hernandez, phernandez |
| src/basic_memory/services/search_service.py | Source Code | 12782 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| src/basic_memory/services/service.py | Source Code | 336 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/services/sync_status_service.py | Source Code | 7504 | phernandez | phernandez, Paul Hernandez |
| src/basic_memory/sync/__init__.py | Source Code | 156 | Paul Hernandez | Paul Hernandez, phernandez |
| src/basic_memory/sync/background_sync.py | Source Code | 726 | bm-claudeai | bm-claudeai, phernandez |
| src/basic_memory/sync/sync_service.py | Source Code | 23387 | Paul Hernandez | phernandez, Paul Hernandez, bm-claudeai, github-ac... |
| src/basic_memory/sync/watch_service.py | Source Code | 15316 | Paul Hernandez | phernandez, bm-claudeai, Paul Hernandez |
| src/basic_memory/templates/prompts/continue_conversation.hbs | Templates/Resources | 3076 | bm-claudeai | bm-claudeai, Drew Cain |
| src/basic_memory/templates/prompts/search.hbs | Templates/Resources | 3098 | bm-claudeai | bm-claudeai |
| src/basic_memory/utils.py | Source Code | 7657 | phernandez | phernandez, Paul Hernandez, andyxinweiminicloud, D... |
| test-int/conftest.py | Source Code | 8203 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_delete_note_integration.py | Source Code | 13175 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_edit_note_integration.py | Source Code | 19822 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_list_directory_integration.py | Source Code | 14744 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_move_note_integration.py | Source Code | 19825 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_project_management_integration.py | Source Code | 33175 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_project_state_sync_integration.py | Source Code | 7728 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_read_content_integration.py | Source Code | 11187 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_read_note_integration.py | Source Code | 1381 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_search_integration.py | Source Code | 14690 | Paul Hernandez | Paul Hernandez, phernandez |
| test-int/mcp/test_write_note_integration.py | Source Code | 9472 | Paul Hernandez | Paul Hernandez, phernandez |
| tests/Non-MarkdownFileSupport.pdf | Other | 106751 | Unknown | |
| tests/Screenshot.png | Other | 190076 | Unknown | |
| tests/__init__.py | Source Code | 141 | phernandez | phernandez, Paul Hernandez |
| tests/api/conftest.py | Source Code | 1404 | phernandez | phernandez, bm-claudeai, Paul Hernandez |
| tests/api/test_async_client.py | Source Code | 1382 | Paul Hernandez | Paul Hernandez, phernandez |
| tests/api/test_continue_conversation_template.py | Source Code | 5152 | bm-claudeai | bm-claudeai |
| tests/api/test_directory_router.py | Source Code | 9376 | Paul Hernandez | bm-claudeai, Paul Hernandez |
| tests/api/test_importer_router.py | Source Code | 16578 | bm-claudeai | bm-claudeai, Paul Hernandez |
| tests/api/test_knowledge_router.py | Source Code | 45771 | Paul Hernandez | phernandez, bm-claudeai, Paul Hernandez |
| tests/api/test_management_router.py | Source Code | 6220 | bm-claudeai | bm-claudeai |
| tests/api/test_memory_router.py | Source Code | 5555 | phernandez | phernandez, bm-claudeai |
| tests/api/test_project_router.py | Source Code | 5201 | Paul Hernandez | bm-claudeai, Paul Hernandez, phernandez |
| tests/api/test_project_router_operations.py | Source Code | 1812 | bm-claudeai | bm-claudeai, Paul Hernandez |
| tests/api/test_prompt_router.py | Source Code | 5046 | bm-claudeai | bm-claudeai |
| tests/api/test_resource_router.py | Source Code | 13960 | Paul Hernandez | phernandez, Paul Hernandez, bm-claudeai |
| tests/api/test_search_router.py | Source Code | 6409 | phernandez | phernandez, bm-claudeai, Paul Hernandez, github-ac... |
| tests/api/test_search_template.py | Source Code | 5258 | bm-claudeai | bm-claudeai |
| tests/api/test_template_loader.py | Source Code | 8035 | bm-claudeai | bm-claudeai |
| tests/api/test_template_loader_helpers.py | Source Code | 7445 | bm-claudeai | bm-claudeai |
| tests/cli/conftest.py | Source Code | 1185 | Paul Hernandez | Paul Hernandez, phernandez |
| tests/cli/test_cli_tools.py | Source Code | 13528 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| tests/cli/test_import_chatgpt.py | Source Code | 6719 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| tests/cli/test_import_claude_conversations.py | Source Code | 5028 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| tests/cli/test_import_claude_projects.py | Source Code | 4585 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| tests/cli/test_import_memory_json.py | Source Code | 3532 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| tests/cli/test_project_commands.py | Source Code | 5143 | bm-claudeai | bm-claudeai, Paul Hernandez, phernandez |
| tests/cli/test_project_info.py | Source Code | 3874 | phernandez | Paul Hernandez, phernandez |
| tests/cli/test_status.py | Source Code | 4160 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| tests/cli/test_sync.py | Source Code | 3864 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| tests/cli/test_version.py | Source Code | 289 | Paul Hernandez | Paul Hernandez |
| tests/conftest.py | Source Code | 15286 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| tests/importers/test_importer_base.py | Source Code | 4329 | bm-claudeai | bm-claudeai |
| tests/importers/test_importer_utils.py | Source Code | 1827 | bm-claudeai | bm-claudeai |
| tests/markdown/__init__.py | Source Code | 0 | Unknown | |
| tests/markdown/test_entity_parser.py | Source Code | 8669 | phernandez | phernandez, Paul Hernandez, github-actions[bot] |
| tests/markdown/test_markdown_plugins.py | Source Code | 5139 | Paul Hernandez | Paul Hernandez, phernandez |
| tests/markdown/test_markdown_processor.py | Source Code | 5728 | phernandez | phernandez, Paul Hernandez |
| tests/markdown/test_observation_edge_cases.py | Source Code | 4295 | phernandez | phernandez |
| tests/markdown/test_parser_edge_cases.py | Source Code | 5142 | phernandez | phernandez |
| tests/markdown/test_relation_edge_cases.py | Source Code | 3952 | phernandez | phernandez |
| tests/markdown/test_task_detection.py | Source Code | 401 | Paul Hernandez | Paul Hernandez |
| tests/mcp/conftest.py | Source Code | 1761 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| tests/mcp/test_prompts.py | Source Code | 5989 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| tests/mcp/test_resource_project_info.py | Source Code | 4885 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| tests/mcp/test_resources.py | Source Code | 549 | Paul Hernandez | Paul Hernandez, phernandez |
| tests/mcp/test_tool_canvas.py | Source Code | 7997 | Paul Hernandez | Paul Hernandez, phernandez |
| tests/mcp/test_tool_delete_note.py | Source Code | 4208 | phernandez | phernandez |
| tests/mcp/test_tool_edit_note.py | Source Code | 13503 | Paul Hernandez | Paul Hernandez, phernandez, Drew Cain |
| tests/mcp/test_tool_list_directory.py | Source Code | 7730 | Paul Hernandez | Paul Hernandez, phernandez |
| tests/mcp/test_tool_move_note.py | Source Code | 16346 | Paul Hernandez | Paul Hernandez, phernandez |
| tests/mcp/test_tool_read_note.py | Source Code | 9741 | Paul Hernandez | Paul Hernandez, phernandez, github-actions[bot], A... |
| tests/mcp/test_tool_recent_activity.py | Source Code | 3983 | phernandez | phernandez, bm-claudeai |
| tests/mcp/test_tool_resource.py | Source Code | 6782 | Paul Hernandez | Paul Hernandez, phernandez, github-actions[bot] |
| tests/mcp/test_tool_search.py | Source Code | 10959 | phernandez | Paul Hernandez, phernandez, github-actions[bot], D... |
| tests/mcp/test_tool_sync_status.py | Source Code | 6331 | phernandez | phernandez |
| tests/mcp/test_tool_utils.py | Source Code | 9228 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| tests/mcp/test_tool_view_note.py | Source Code | 10004 | phernandez | phernandez |
| tests/mcp/test_tool_write_note.py | Source Code | 19537 | Paul Hernandez | phernandez, Paul Hernandez, Drew Cain |
| tests/repository/test_entity_repository.py | Source Code | 15498 | phernandez | phernandez, bm-claudeai, Paul Hernandez |
| tests/repository/test_entity_repository_upsert.py | Source Code | 16983 | Drew Cain | Drew Cain, phernandez |
| tests/repository/test_observation_repository.py | Source Code | 11786 | phernandez | phernandez, bm-claudeai |
| tests/repository/test_project_info_repository.py | Source Code | 1186 | bm-claudeai | bm-claudeai |
| tests/repository/test_project_repository.py | Source Code | 9455 | bm-claudeai | bm-claudeai |
| tests/repository/test_relation_repository.py | Source Code | 11980 | phernandez | phernandez, bm-claudeai |
| tests/repository/test_repository.py | Source Code | 5996 | phernandez | phernandez, Paul Hernandez |
| tests/repository/test_search_repository.py | Source Code | 25471 | bm-claudeai | bm-claudeai, phernandez, Paul Hernandez |
| tests/schemas/test_memory_url.py | Source Code | 2000 | phernandez | phernandez, Paul Hernandez |
| tests/schemas/test_memory_url_validation.py | Source Code | 9338 | phernandez | phernandez |
| tests/schemas/test_schemas.py | Source Code | 16880 | phernandez | phernandez, Paul Hernandez, bm-claudeai, Drew Cain... |
| tests/schemas/test_search.py | Source Code | 3264 | phernandez | phernandez, Paul Hernandez, github-actions[bot] |
| tests/services/test_context_service.py | Source Code | 8833 | phernandez | phernandez, bm-claudeai, Paul Hernandez |
| tests/services/test_directory_service.py | Source Code | 6854 | Paul Hernandez | bm-claudeai, Paul Hernandez |
| tests/services/test_entity_service.py | Source Code | 59278 | Paul Hernandez | phernandez, Paul Hernandez, bm-claudeai, Drew Cain... |
| tests/services/test_file_service.py | Source Code | 5243 | phernandez | phernandez |
| tests/services/test_initialization.py | Source Code | 6939 | bm-claudeai | Paul Hernandez, bm-claudeai, phernandez |
| tests/services/test_link_resolver.py | Source Code | 13618 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| tests/services/test_project_service.py | Source Code | 23576 | phernandez | bm-claudeai, Paul Hernandez, phernandez |
| tests/services/test_project_service_operations.py | Source Code | 4679 | bm-claudeai | bm-claudeai, Paul Hernandez |
| tests/services/test_search_service.py | Source Code | 25299 | Paul Hernandez | phernandez, Paul Hernandez, github-actions[bot] |
| tests/services/test_sync_status_service.py | Source Code | 9926 | phernandez | phernandez, Paul Hernandez |
| tests/sync/test_sync_service.py | Source Code | 39659 | phernandez | phernandez, Paul Hernandez, bm-claudeai |
| tests/sync/test_sync_wikilink_issue.py | Source Code | 1959 | github-actions[bot] | github-actions[bot], Paul Hernandez |
| tests/sync/test_tmp_files.py | Source Code | 5930 | Paul Hernandez | Paul Hernandez, bm-claudeai, phernandez |
| tests/sync/test_watch_service.py | Source Code | 14076 | Paul Hernandez | Paul Hernandez, phernandez, bm-claudeai |
| tests/sync/test_watch_service_edge_cases.py | Source Code | 2169 | Paul Hernandez | Paul Hernandez, bm-claudeai |
| tests/test_config.py | Source Code | 3680 | Drew Cain | Drew Cain |
| tests/test_db_migration_deduplication.py | Source Code | 6166 | Paul Hernandez | Paul Hernandez, Drew Cain, phernandez |
| tests/utils/test_file_utils.py | Source Code | 6199 | phernandez | phernandez, Paul Hernandez |
| tests/utils/test_parse_tags.py | Source Code | 1691 | github-actions[bot] | github-actions[bot], Paul Hernandez |
| tests/utils/test_permalink_formatting.py | Source Code | 4193 | phernandez | phernandez, Paul Hernandez |
| tests/utils/test_utf8_handling.py | Source Code | 5791 | phernandez | phernandez, Paul Hernandez |
-378
View File
@@ -1,378 +0,0 @@
{"type":"entity","name":"Paul","entityType":"person","observations":["Software developer combining DIY ethics, Free Software principles, and theoretical computer science","Created the Basic Machines project","Values authentic exchange of ideas","Approaches AI interaction with emphasis on genuine technical discussion","Comfortable with uncertainty and open dialogue","Balances practical implementation with broader implications"]}
{"type":"entity","name":"Basic_Machines","entityType":"project","observations":["Local-first knowledge management system","Combines filesystem durability with graph-based knowledge representation","Focuses on enhancing human agency and understanding","Synthesizes DIY ethics, Free Software philosophy, and theoretical computer science","Current focus includes basic-memory system"]}
{"type":"entity","name":"basic-memory","entityType":"software_system","observations":["A core component of Basic Machines","Local-first knowledge management system","Combines filesystem persistence with graph-based knowledge representation","Being implemented collaboratively by Paul and Claude"]}
{"type":"entity","name":"basic-memory_implementation_patterns","entityType":"technical_patterns","observations":["Filesystem is source of truth - all changes write to files first","Clean separation of concerns between models (SQLAlchemy), schemas (Pydantic), and services","Repository pattern for database access","Service layer handling business logic and coordination","Atomic file operations using temporary files for safety","Clear error handling hierarchy with specific error types","Comprehensive test coverage with pytest and fixtures","Async/await used throughout the codebase","Validation using Pydantic models with custom validators"]}
{"type":"entity","name":"fileio_module","entityType":"code_module","observations":["Extracted from EntityService to handle all file operations","Provides read_entity_file, write_entity_file, and delete_entity_file functions","Handles markdown parsing and formatting","Implements atomic file operations","Provides consistent error handling","Enables reuse across services"]}
{"type":"entity","name":"entity_service","entityType":"code_module","observations":["Manages entities in both filesystem and database","Uses fileio module for file operations","Maintains database index of entities","Handles entity creation, retrieval, and deletion","Follows 'filesystem is source of truth' principle","Coordinates with observation service for full entity management"]}
{"type":"entity","name":"observation_service","entityType":"code_module","observations":["Manages observations within entity files","Provides database indexing for efficient observation queries","Works with complete Entity objects rather than IDs","Handles observation addition and search","Maintains consistency between files and database","Under development for update/remove operations"]}
{"type":"entity","name":"observation_management","entityType":"design_challenge","observations":["Key challenge: maintaining observation state across files and database","Exploring bulk update approach - treating all observations as a unit","Considering tracked observations with markdown comments for IDs","Investigating diff-based approach for observation-level changes","Evaluating position-based management without explicit IDs","Trade-offs between implementation complexity and markdown readability"]}
{"type":"entity","name":"testing_infrastructure","entityType":"technical_patterns","observations":["Uses pytest with async support via pytest-asyncio","In-memory SQLite database for test isolation","Temporary directories for file operation testing","Comprehensive fixture system for test setup","Tests organized by component (entity, observation, etc)","Covers happy path, error cases, and edge cases","Uses monkeypatch for mocking dependencies","Clear separation between arrange, act, assert sections","Uses in-memory SQLite database for test isolation","Comprehensive fixture system for test data setup","Proper async test handling with pytest-asyncio"]}
{"type":"entity","name":"test_categories","entityType":"test_suite","observations":["Happy path tests verify core functionality","Error path tests ensure proper error handling","Edge cases test special characters and long content","File operation tests verify atomic writes and rollbacks","Database sync tests verify index consistency","Recovery tests for rebuild operations","Punted on concurrent operation tests due to session management complexity"]}
{"type":"entity","name":"completed_work","entityType":"project_milestone","observations":["Extracted file operations to fileio.py module","Updated EntityService to use fileio functions","Implemented initial ObservationService","Created comprehensive test suite","Established clear project patterns and principles","Set up basic database schema with SQLAlchemy","Created Pydantic models for validation"]}
{"type":"entity","name":"future_work","entityType":"project_tasks","observations":["Implement observation updates/removals","Design proper session management for concurrent operations","Update EntityService tests for new fileio module","Add more sophisticated search functionality","Handle markdown formatting edge cases","Consider versioning for file changes","Implement proper backup strategy"]}
{"type":"entity","name":"design_decisions","entityType":"technical_decisions","observations":["Filesystem as source of truth over database","Markdown format for human readability and editing","Atomic file operations for safety","SQLite + SQLAlchemy for proven reliability","Pydantic for validation and ID generation","Async/await for better scalability","Clear separation between files and database roles","Explicit error hierarchies for better handling"]}
{"type":"entity","name":"concurrency_considerations","entityType":"technical_challenge","observations":["SQLAlchemy session management in async context","File operation atomicity","Transaction isolation levels","Potential for conflicting updates","Need for proper session lifecycle","Possibility of file system race conditions","Database lock management"]}
{"type":"entity","name":"observation_update_approaches","entityType":"design_alternatives","observations":["Each approach trades off between simplicity, efficiency, and robustness","Four main approaches considered: bulk update, tracked IDs, diff-based, and position-based","Discussion revealed importance of human readability in file format","Consideration of manual editing workflows key to design","File system as source of truth principle guides tradeoffs"]}
{"type":"entity","name":"bulk_update_approach","entityType":"design_option","observations":["Update all observations at once in a single operation","Simpler file operations - just rewrite the whole list","No need for observation matching or IDs","Very consistent with source of truth principle","Less efficient for small changes","May have concurrency implications","Simplest implementation option"]}
{"type":"entity","name":"tracked_observations_approach","entityType":"design_option","observations":["Use markdown comments to store observation IDs","Enables precise updates and deletes","IDs stored as HTML comments in markdown","More complex markdown parsing required","IDs visible in raw markdown files","Balances tracking with readability"]}
{"type":"entity","name":"diff_based_approach","entityType":"design_option","observations":["Implement observation-aware diffing","Track changes at observation level","More efficient for updates","Preserves manual edits and changes","More complex implementation needed","Must handle merge conflicts","Most sophisticated option considered"]}
{"type":"entity","name":"position_based_approach","entityType":"design_option","observations":["Track observations by position/order","No explicit IDs needed","Cleanest markdown format","Order changes could break references","Difficult to handle concurrent edits","Most fragile option considered"]}
{"type":"entity","name":"tasks_and_progress","entityType":"project_tracking","observations":["Current focus on observation management implementation","Completed core file operations extraction","Completed EntityService updates","Completed initial ObservationService","Basic test coverage in place","Future work includes concurrent operations","Future work includes search improvements","Need to handle markdown edge cases"]}
{"type":"entity","name":"error_handling_patterns","entityType":"technical_patterns","observations":["Custom exception hierarchy with ServiceError base","Specific error types (FileOperationError, DatabaseSyncError, etc)","Clear separation between file and database errors","Error propagation patterns established","Focus on actionable error messages","Error handling at appropriate levels"]}
{"type":"entity","name":"data_models","entityType":"technical_implementation","observations":["SQLAlchemy models for database structure","Pydantic schemas for API/service layer","Entity model with UUID-based IDs","Observation model with entity relationships","UTCDateTime custom type for timestamps","Automatic ID generation in Pydantic models","Strict validation rules"]}
{"type":"entity","name":"markdown_format","entityType":"file_format","observations":["Simple, human-readable format","Entity name as H1 header","Metadata in key-value format","Observations as bullet points","Atomic file operations for updates","Designed for manual editing","No hidden metadata in main content"]}
{"type":"entity","name":"test_driven_development","entityType":"development_pattern","observations":["Tests revealed need for atomic file operations","Error cases drove error hierarchy design","Edge cases informed validation rules","Test fixtures shaped service interfaces","File operations extracted due to test patterns","Concurrent test issues revealed session management needs"]}
{"type":"entity","name":"architecture_evolution","entityType":"design_process","observations":["Started with simple EntityService implementation","Circular dependency between Entity and Observation services revealed design flaw","Extracted file operations to separate module","Moved to passing Entity objects rather than IDs","Improved separation of concerns through iterations","File operations became reusable across services","Database became true 'index' rather than source of truth"]}
{"type":"entity","name":"validation_patterns","entityType":"technical_patterns","observations":["Pydantic models provide schema validation","Automatic ID generation if not provided","Database constraints via SQLAlchemy","Runtime checks in services","Markdown format validation","Error handling for invalid states"]}
{"type":"entity","name":"markdown_examples","entityType":"documentation","observations":["Example of basic entity:\n# Entity Name\ntype: entity_type\n\n## Observations\n- First observation\n- Second observation","Example with special characters:\n# Test & Entity!\ntype: test\n\n## Observations\n- Test & observation with @#$% special chars!","Format ensures human readability:\n# Basic Machines\ntype: project\n\n## Observations\n- Local-first knowledge management system\n- Combines filesystem durability with graph-based knowledge representation","Future consideration for observation IDs:\n# Entity Name\ntype: entity_type\n\n## Observations\n- <!-- obs-id: abc123 -->\n This is an observation with ID"]}
{"type":"entity","name":"markdown_parsing_rules","entityType":"technical_implementation","observations":["H1 header contains entity name","Metadata uses key: value format","Observations section marked by H2 header","Each observation is a markdown list item","Blank lines separate sections","Special characters allowed in content","No restrictions on observation content"]}
{"type":"entity","name":"schema_definitions","entityType":"technical_documentation","observations":["SQLAlchemy Entity model:\nclass Entity(Base):\n id: str (primary key)\n name: str (unique)\n entity_type: str\n created_at: datetime\n updated_at: datetime","SQLAlchemy Observation model:\nclass Observation(Base):\n id: str (primary key)\n entity_id: str (foreign key)\n content: str\n created_at: datetime\n context: Optional[str]","Pydantic Entity schema:\nclass Entity(BaseModel):\n id: str\n name: str\n entity_type: str\n observations: List[Observation]"]}
{"type":"entity","name":"test_evolution","entityType":"development_history","observations":["Started with basic Entity CRUD tests","Added filesystem verification to all tests","Developed concurrent operation tests (later removed)","Edge case tests drove better error handling","Test fixtures evolved to support both file and DB testing","Mocking patterns for file/DB operations","Special cases for long content and special characters"]}
{"type":"entity","name":"implementation_challenges","entityType":"technical_issues","observations":["Initial circular dependency between services","SQLAlchemy session management in async context","Atomic file operations with proper error handling","Maintaining DB sync with filesystem changes","Handling long content in observations","Managing test isolation with file operations","Deciding on markdown format tradeoffs","Concurrent operation complexity"]}
{"type":"entity","name":"Basic_Factory","entityType":"Project","observations":["Collaborative project between Paul and Claude","Explores AI-human collaboration in software development","Uses MCP tools for file and memory management","Built with git integration capabilities","Focuses on maintaining project context across sessions","About 90% complete with MCP tools","Still needs improvements in collaboration via files/git/github","Will be used to document and share collaborative development process"]}
{"type":"entity","name":"Basic_Factory_Components","entityType":"Technical","observations":["Server-side rendering with JinjaX","HTMX for dynamic updates","Alpine.js for client-side state","Tailwind CSS for styling","Component translation from React/shadcn/ui","Focus on simplicity and understandability","Demonstrates meta-compiler principles in component translation"]}
{"type":"entity","name":"Component_Translation_Process","entityType":"Methodology","observations":["Treats component porting as meta-compilation","Maps between React/TypeScript and JinjaX/Alpine.js domains","Uses formal grammar transformation approaches","Maintains functionality while simplifying implementation","Focuses on server-side rendering patterns","Preserves accessibility and performance","Uses short, focused git branches for each component"]}
{"type":"entity","name":"Basic_Machines_Philosophy","entityType":"Philosophy","observations":["Combines DIY punk ethics with software development","Emphasizes user empowerment and understanding","Values simplicity and composability","Treats complex systems as combinations of simple parts","Focuses on authentic creation and sharing","Draws inspiration from punk rock, Free Software, and theoretical CS","Emphasizes the cycle of creation, complexity, and renewal"]}
{"type":"entity","name":"Basic_Machines_Manifesto","entityType":"Document","observations":["Created through collaboration between Paul and Claude","Explores connection between DIY punk ethics and software development","Emphasizes composition over inheritance in both philosophy and practice","Views software development through lens of basic machines that combine for complex computation","Advocates for user empowerment and technological independence","Structured in sections covering Origins, Philosophy, Technical Implementation, and AI Collaboration","Draws connections between punk rock, free software, and theoretical computer science","Emphasizes importance of sharing knowledge and building community","Released in December 2024"]}
{"type":"entity","name":"AI_Human_Collaboration_Model","entityType":"Methodology","observations":["Focuses on deep collaboration rather than simple task completion","Maintains rich context across sessions via knowledge graph","Uses short, focused git branches for each collaborative session","Values intellectual partnership over simple code generation","Emphasizes both practical implementation and theoretical exploration","Creates space for authentic exchange while maintaining AI/human clarity","Uses formal methods when appropriate (like grammar transformation)","Documents decisions and processes for future reference","Developed through Basic Machines project experience"]}
{"type":"entity","name":"Basic_Machines_Roadmap","entityType":"Project_Plan","observations":["Phase 1 (30 days): Build basic-machines.co website","Phase 2 (60-90 days): Develop premium component bundles","Phase 3 (90-120 days): Launch Basic Foundation commercial offering","Focus on building brand and marketing presence","Prioritize components needed for own site development","Document and share collaboration process","Build sustainable business model aligned with values"]}
{"type":"entity","name":"Basic_Machines_Website","entityType":"Project","observations":["To be built at basic-machines.co","Will showcase products and vision","Needs components for navigation, hero sections, features","Will demonstrate component usage in production","Will include blog for sharing progress","Focus on clear value proposition","Platform for sharing Basic Machines philosophy"]}
{"type":"entity","name":"Basic_Memory_Markdown_Example","entityType":"Example","observations":["Shows complete markdown structure for basic-memory entity","Uses frontmatter for metadata (id, type, created, context)","Has main description section after title","Includes Observations as bullet points","Shows Relations with [id] relation_type | context format","Lists References at bottom","Created during initial design discussion","Serves as canonical example of file format"]}
{"type":"entity","name":"Basic_Memory_Database_Schema","entityType":"Technical","observations":["Uses SQLite for local storage","Entities table with id, name, type, created_at, context, description, references","Observations table linking to entities with content and context","Relations table tracking directional relationships between entities","References column needs quotes as SQL reserved word","Designed for easy rebuilding from markdown files","Foreign key constraints maintain data integrity","Unique constraint on relations prevents duplicates","Created_at timestamps track history","Context fields enable tracking information sources"]}
{"type":"entity","name":"Basic_Memory_Project_Structure","entityType":"Technical","observations":["Uses dbmate for database migrations","Projects directory stores SQLite databases and markdown files","Makefile provides common development commands","Environment vars configure database connection","db/migrations directory for SQL schema changes","Gitignore excludes database files and env config","Uses Python 3.12 with modern tooling","Tests directory for pytest files","Follows Basic Machines project conventions"]}
{"type":"entity","name":"Basic_Memory_Project_Isolation_Decision","entityType":"Decision","observations":["Decided to defer multi-project support to post-MVP","Will use separate SQLite databases per project","Initially using projects directory in code repository","Plan to make location configurable later","No changes needed to core domain model","Keeps initial implementation simple","FTS/search capabilities also deferred for simplicity"]}
{"type":"entity","name":"Basic_Memory_Implementation_Plan","entityType":"Plan","observations":["Start with SQLAlchemy models matching schema","Then build CLI for basic operations","Then implement markdown parser","Use TDD approach throughout","Begin with core domain model","CLI will support CRUD operations","Parser must handle frontmatter and sections","Following modular development approach","Planning to use typer for CLI","Will use modern Python tools and practices"]}
{"type":"entity","name":"Basic_Memory_Implementation_Status","entityType":"Status","observations":["Core modules implemented: models, services, repository, fileio","Modular architecture with clear separation of concerns","File operations extracted to separate fileio module","Initial ObservationService implementation complete","Basic test coverage in place","Exploring observation management strategies","Using SQLAlchemy for database interaction","Markdown file operations working","Entity management functional","Repository layer implementation complete with SQLAlchemy models and tests","Database operations working with proper UTC timestamp handling","In-memory SQLite testing infrastructure proven effective"]}
{"type":"entity","name":"Basic_Memory_Observation_Management_Design","entityType":"Design","observations":["Four approaches under consideration","Bulk Update: Simple but less efficient","Tracked Observations: Precise but clutters markdown","Diff-based: Efficient but complex","Position-based: Clean but fragile","Key challenge is balancing markdown readability with efficient updates","Must maintain filesystem as source of truth","Need to consider concurrent edits","Currently evaluating trade-offs","Implementation choice pending discussion"]}
{"type":"entity","name":"Basic_Memory_Architectural_Decisions","entityType":"Decisions","observations":["Split file operations into separate fileio module","Using SQLAlchemy for database operations","Maintain filesystem as source of truth","Modular service-based architecture","Clear separation between data access and business logic","Repository pattern for database interactions","Schemas separate from models","Focus on maintainability and testability","Services handle business rules","Considering concurrency in design"]}
{"type":"entity","name":"Basic_Memory_Implementation_Analysis","entityType":"Analysis","observations":["Clean modular architecture with clear responsibilities","Strong typing throughout codebase","Excellent error handling with custom exceptions","SQLAlchemy models perfectly match our domain model","Atomic file operations for data safety","Services implement filesystem-as-source-of-truth principle","Async support throughout","Good separation between domain models and database models","Careful handling of UTC timestamps","Smart use of SQLAlchemy relationships"]}
{"type":"entity","name":"Basic_Memory_Current_Challenges","entityType":"Challenges","observations":["Observation update/removal strategy needs to be chosen","Need to handle concurrent file operations safely","Search functionality to be implemented","Edge cases in markdown formatting to be handled","Session management for concurrent operations needed","Balance between file operations and database sync","Testing coverage could be expanded","Need to handle relationship updates in files"]}
{"type":"entity","name":"Basic_Memory_Observation_Hash_Tracking","entityType":"Design","observations":["Use content hashes to track observation identity","Store hashes in database but not in markdown","Can match observations across file edits using hashes","Similar to how git tracks content changes","Keeps markdown clean and human-friendly","Allows efficient bulk updates","Handles reordering of observations","Maintains filesystem as source of truth","No need for visible IDs in markdown","Could track observation history through hash changes"]}
{"type":"entity","name":"Basic_Memory_Repository_Implementation","entityType":"Code_Implementation","observations":["Implemented base Repository class with CRUD operations","Added specialized EntityRepository, ObservationRepository, and RelationRepository","Used string IDs instead of UUIDs","Added UTCDateTime custom type for timestamp handling","Used in-memory SQLite for testing","Achieved 84% test coverage","Created comprehensive pytest fixtures"]}
{"type":"entity","name":"Basic_Memory_Dependencies","entityType":"Technical","observations":["Uses Python 3.12","SQLAlchemy with async support","pytest-asyncio for async testing","aiosqlite for async SQLite operations","greenlet for SQLAlchemy async support","uv for dependency management","pytest-cov for coverage reporting","Development dependencies managed in pyproject.toml"]}
{"type":"entity","name":"Basic_Memory_Current_Architecture","entityType":"Architecture_Analysis","observations":["Clear separation between domain models (Pydantic) and storage models (SQLAlchemy)","File I/O completely separated into dedicated module","Strong 'filesystem as source of truth' pattern in services","Atomic file operations with proper error handling","Service layer coordinates between filesystem and database","Database acts as queryable index rather than primary storage","Clean error hierarchy with specific exception types","Rebuild operations available for recovery scenarios"]}
{"type":"entity","name":"Basic_Memory_Evolution","entityType":"Analysis","observations":["Started with repository pattern following basic-foundation","Evolved to more sophisticated architecture with clear layers","Added Pydantic schemas for domain modeling","Separated file operations into dedicated module","Implemented robust error handling throughout","Maintained filesystem as source of truth principle","Added observation management with context tracking","Introduced rebuild capabilities for system recovery"]}
{"type":"entity","name":"Basic_Memory_Service_Layer","entityType":"Implementation","observations":["EntityService handles entity lifecycle and coordinates storage","ObservationService manages observations within entities","Services ensure filesystem and database stay in sync","Clear error handling with ServiceError hierarchy","Strong typing throughout service interfaces","Implements filesystem as source of truth pattern","Handles UUID generation and timestamp management","Provides methods for system recovery and rebuild"]}
{"type":"entity","name":"Basic_Memory_Schema_Design","entityType":"Implementation","observations":["Uses Pydantic for domain models and validation","Automatic ID generation with timestamp and UUID","Clear separation from SQLAlchemy storage models","Supports optional context tracking","Models match markdown file structure","Enables clean serialization/deserialization","Strong typing with proper validation rules","Independent from storage concerns"]}
{"type":"entity","name":"Basic_Memory_Next_Tasks","entityType":"TaskList","observations":["✅ Implement SQLAlchemy models and repositories (Done)","✅ Add SQLAlchemy migrations (Done)","✅ Create service layer (Done)","✅ Implement file I/O module (Done)","✅ Set up domain models with Pydantic (Done)","✅ Initial test infrastructure (Done)","✅ Basic CRUD operations (Done)","⏳ Implement full test coverage for db.py","⏳ Add more sophisticated search functionality","⏳ Implement CLI interface","⏳ Add relationship management to services","⏳ Handle concurrent file operations safely","⏳ Add versioning for file changes","⏳ Implement proper backup strategy","⏳ Add type hints throughout codebase","⏳ Improve error messages and logging","⏳ Add documentation for core modules"]}
{"type":"entity","name":"Basic_Memory_Meta_Experience","entityType":"Case_Study","observations":["Experienced our own context loss when reconstructing project knowledge","Had to rebuild task list and project context from filesystem and memory","Validated 'filesystem as source of truth' principle through reconstruction","Code and tests served as reliable historical record","Knowledge graph structure helped guide reconstruction process","Markdown files provided human-readable context","Atomic information design made piece-by-piece reconstruction possible","Ironic validation of the need for basic-memory's features","Experience demonstrates value of durable, human-readable knowledge storage","Shows importance of separating durable storage from ephemeral context"]}
{"type":"entity","name":"Model_Context_Protocol","entityType":"protocol","observations":["Core part of the basic-memory architecture","Enables AI-human collaboration on projects","Provides tool-based interaction with knowledge graph","Developed by Anthropic for structured AI-system interaction","Used for maintaining consistent, rich context across conversations"]}
{"type":"entity","name":"basic-memory_core_principles","entityType":"principles","observations":["Local First: All data stored locally in SQLite","Project Isolation: Separate databases per project","Human Readable: Everything exportable to plain text","AI Friendly: Structure optimized for LLM interaction","DIY Ethics: User owns and controls their data","Simple Core: Start simple, expand based on needs","Tool Integration: MCP-based interaction model"]}
{"type":"entity","name":"basic-memory_business_model","entityType":"business_strategy","observations":["Core features free: Local SQLite, basic knowledge graph, search, markdown export, basic MCP tools","Professional features potential: Rich document export, advanced versioning, collaboration features, custom integrations, priority support","Focus on maintaining DIY/punk philosophy while enabling sustainability"]}
{"type":"entity","name":"basic-memory_cli","entityType":"interface","observations":["Supports project management commands (create, switch, list)","Entity management (add entity, add observation, add relation)","Future support for export and batch operations","Follows consistent command structure","Planned integration with MCP tools"]}
{"type":"entity","name":"basic-memory_export_format","entityType":"file_format","observations":["Uses markdown with frontmatter metadata","Includes entity name, type, creation timestamp","Observations as bullet points","Relations in structured format with links","References section at bottom","Designed for human readability and machine parsing","Example format documented in project specs"]}
{"type":"entity","name":"relation_service","entityType":"code_module","observations":["Planned service for managing relations in both filesystem and database","Will follow filesystem-is-source-of-truth principle like other services","Needs to handle atomic file operations for relation updates","Must coordinate with EntityService for relationship integrity","Will handle bidirectional relationship tracking","Will support relation validation and type enforcement","Must implement rebuild functionality for index recovery","Will need careful error handling for file/db sync","Should support relation search and filtering","Must handle relation lifecycle (create/read/update/delete)"]}
{"type":"entity","name":"service_layer_patterns","entityType":"implementation_patterns","observations":["Services handle both file and database operations","Filesystem is always source of truth","Database serves as queryable index","Services implement atomic file operations","Clear error hierarchy with specific exceptions","Use of dependency injection via constructor params","Async/await used throughout service layer","Services coordinate between storage layers","Repository pattern used for database access","Services maintain entity integrity across storage","Rich error types extend from ServiceError base","Rebuild operations available for recovery"]}
{"type":"entity","name":"database_models","entityType":"implementation","observations":["Entity model with unique name and type","Observation model linked to entities","Relation model tracks connections between entities","Custom UTCDateTime type for timestamp handling","Use of SQLAlchemy relationships for navigation","Cascading deletes for dependent objects","String IDs used for compatibility","Rich relationship modeling with backpopulates","Proper indexing on foreign keys","Context tracking available on models","Models include created_at timestamps","Relationships handle bidirectional navigation"]}
{"type":"entity","name":"repository_patterns","entityType":"implementation_patterns","observations":["Generic Repository[T] base class implementation","Type-safe operations with SQLAlchemy","Specialized repositories for each model type","Async operations throughout","Clear error handling patterns","Support for custom queries and filtering","Pagination support built-in","Transaction management via session","Proper type hints and generics usage","Entity-specific query methods in subclasses"]}
{"type":"entity","name":"relation_service_design","entityType":"design","observations":["Must handle relation lifecycle in both files and DB","Needs to validate existence of both entities","Should support relation type enforcement","Must maintain bidirectional consistency","Should support relation querying and filtering","Needs proper error handling for graph consistency","Must integrate with entity file format","Should support bulk operations for efficiency","Must handle relation deletion and cascading","Should provide search by type and entities"]}
{"type":"entity","name":"relation_service_implementation_plan","entityType":"plan","observations":["1. Define core relation operations (create, get, delete)","2. Implement file format handling for relations","3. Add database sync with RelationRepository","4. Implement validation and error handling","5. Add rebuild and recovery operations","6. Implement relation type enforcement","7. Add relation search and filtering","8. Implement bulk operations","9. Add comprehensive tests","10. Document API and error handling"]}
{"type":"entity","name":"relation_service_challenges","entityType":"challenges","observations":["Maintaining consistency between file and database","Handling relation type validation efficiently","Managing bidirectional relationships in files","Ensuring atomic updates across entities","Handling deletion with proper cascading","Efficient querying of relation graphs","Recovery from partial file/db sync failures","Bulk operation atomicity","Clear error reporting for graph operations","Performance with large relation sets"]}
{"type":"entity","name":"relation_file_format","entityType":"file_format","observations":["Relations stored in entity markdown files","Format: [target_id] relation_type | context","Relations section marked by ## Relations header","Outgoing relations only stored in source entity","Relations rebuild on entity load","Clean human-readable format","Context is optional with pipe separator","Links generate valid navigation references","Markdown-friendly formatting","Example: [Paul] authored | with Claude"]}
{"type":"entity","name":"relation_service_error_handling","entityType":"implementation_patterns","observations":["RelationError extends ServiceError base","Specific errors for validation failures","Handles entity not found cases","Manages relation type validation errors","File operation errors properly wrapped","Database sync errors clearly reported","Transaction rollback on errors","Proper error propagation chain","Clear error messages for debugging","Recovery paths for common errors"]}
{"type":"entity","name":"relation_service_testing","entityType":"testing","observations":["Test all relation lifecycle operations","Verify file and database consistency","Test relation type validation","Check error handling paths","Test bulk operations","Verify bidirectional consistency","Test recovery operations","Check cascade operations","Verify search and filtering","Test with large relation sets"]}
{"type":"entity","name":"fileio_patterns","entityType":"implementation_patterns","observations":["Atomic file operations with temporary files","Clear error handling for IO operations","Consistent file naming and paths","Support for different file formats","Efficient file reading and writing","Proper file locking mechanisms","Recovery from partial writes","Consistent encoding handling","Directory management utilities","Path manipulation helpers","Currently implemented in fileio.py module","Uses pathlib for path operations","Handles file not found cases gracefully","Maintains data integrity during writes"]}
{"type":"entity","name":"pytest_patterns","entityType":"implementation_patterns","observations":["Common fixtures should be in conftest.py for reuse","Use pytest_asyncio.fixture for async fixtures","Session fixtures need proper async cleanup","Temporary directories should be managed with context managers","Test categories: happy path, error path, recovery, edge cases","Services need project_path and repo injected","Use monkeypatch for mocking in async context","SQLite in-memory database ideal for testing","Explicit test verification: file content and database state"]}
{"type":"entity","name":"relation_implementation_learnings","entityType":"implementation_learnings","observations":["Better to pass full Entity objects than IDs to services","Services should not re-read entities if they have them","File operations should be atomic and verified","Database serves as queryable index, not source of truth","Relations stored in source entity's markdown file","Clear separation between file ops and database sync","Entity objects should own their relations list","Context is optional but fully supported in implementation"]}
{"type":"entity","name":"test_driven_insights","entityType":"learnings","observations":["Tests help reveal better API design (e.g., passing Entity objects)","Error cases drive proper exception hierarchy","File verification as important as database checks","Edge cases inform markdown format decisions","Recovery tests ensure system resilience","Tests document expected behavior clearly","Fixtures significantly reduce test complexity","Common patterns emerge through test writing"]}
{"type":"entity","name":"meta_development_insights","entityType":"process","observations":["Break down large tasks into reviewable chunks","One file at a time prevents response truncation","Iterative development with tests leads to better design","Infrastructure code (fixtures) should be consolidated early","Test categories help ensure comprehensive coverage","Knowledge capture should happen during development","APIs tend to evolve toward simpler patterns","File operations require careful verification"]}
{"type":"entity","name":"AI_Assistant_Learnings","entityType":"meta_insights","observations":["Output management: Breaking responses into single files prevents truncation and allows better review","Knowledge graph helps maintain context: I can reference previous decisions and patterns accurately","Memory rebuilding experience validated the need for durable storage","Test-driven development provides clear steps and verification","Explicit relation tracking in knowledge graph helps me understand project context","Rich context from multiple sources (code, docs, tests) enables better assistance","File-at-a-time approach allows deeper analysis of each component","Keeping entity names consistent helps with referencing and relationships"]}
{"type":"entity","name":"Effective_Response_Patterns","entityType":"meta_patterns","observations":["When showing code changes, break into discrete files","Review existing code before suggesting changes","Reference knowledge graph for context and patterns","Explicitly connect new code to existing patterns","Validate suggestions against test cases","Keep track of file changes for atomic commits","Check both implementation and test files for consistency","Maintain clear separation of concerns in responses"]}
{"type":"entity","name":"AI_Context_Management","entityType":"meta_practice","observations":["Knowledge graph provides reliable persistent memory","Project documentation gives high-level context","Code review shows implementation patterns","Tests demonstrate expected behavior","Important to actively track what has been modified","Entity relationships help understand dependencies","Regular knowledge capture during development","Using consistent entity references across conversations"]}
{"type":"entity","name":"AI_Tool_Usage_Patterns","entityType":"meta_practice","observations":["read_file before suggesting changes","write_file one file at a time","list_directory to understand project structure","search_nodes to find relevant context","create_entities to capture new learnings","create_relations to connect concepts","Using knowledge graph to track decisions","Validating changes through test execution"]}
{"type":"entity","name":"relation_service_learnings","entityType":"implementation_learnings","observations":["Entity-based API cleaner than ID-based for service layer","Model_dump method can handle storage serialization","File format needs explicit section markers (## Relations)","Whitespace handling important for long content comparisons","Test fixtures allow focused test cases","SQLAlchemy selects better than raw SQL for type safety","Atomic file operations maintained for relations"]}
{"type":"entity","name":"test_driven_insights_relations","entityType":"learnings","observations":["Tests revealed need for whitespace normalization","Edge cases drove file format decisions","SQLAlchemy model access safer than raw queries","Fixtures reduced test setup complexity","File verification as important as database checks","Testing both memory model and storage format","Test categories ensure comprehensive coverage"]}
{"type":"entity","name":"relation_service_patterns","entityType":"patterns","observations":["Use Entity objects in API","Serialize to IDs for storage","Maintain file as source of truth","Keep file format human-readable","Handle circular references in serialization","Use repository pattern for database","Clear error hierarchies"]}
{"type":"entity","name":"packaging_learnings","entityType":"technical_learnings","observations":["When using pytest-mock, traditional pip install works more reliably than uv sync","Package discovery behavior can differ between uv and pip","Clean venv with pip install is a reliable fallback for dependency issues","Package installation location might differ between uv and pip","Dependencies in pyproject.toml dev section work reliably with pip install -e .[dev]"]}
{"type":"entity","name":"Recent_Implementation_Progress","entityType":"progress_update","observations":["Successfully split services.py into modular structure under services/","Created __init__.py, entity_service.py, observation_service.py, relation_service.py","Fixed pytest-mock installation issues by using pip install -e .[dev] instead of uv sync","Improved test structure with minimal mocking - only used for error testing","Implemented relation service with Entity-based API","Achieved good test coverage across services","File operations are only mocked when testing error conditions","Services follow filesystem-as-source-of-truth pattern"]}
{"type":"entity","name":"Next_Steps","entityType":"project_tasks","observations":["Consider adding more relation service tests","Potentially expand relations features","Look for opportunities to improve test coverage","Consider documenting package management preferences (pip vs uv)","Consider adding integration tests for services","Review and possibly expand error handling cases"]}
{"type":"entity","name":"Development_Practices","entityType":"process","observations":["Favor real operations over mocks in tests","Only mock for error condition testing","Use pip install -e .[dev] for reliable dev dependency installation","Maintain modular service structure","Keep filesystem as source of truth","Use Entity objects in service APIs instead of IDs","Validate both file and database state in tests"]}
{"type":"entity","name":"MCP_Resources","entityType":"Concept","observations":["Stateful objects in Model Context Protocol","Enable persistent access to capabilities"]}
{"type":"entity","name":"MCP_Server_Implementation","entityType":"Technical_Design","observations":["Inherits from mcp.server.Server base class","Tools are implemented as async methods","Each tool method maps directly to a function available to the AI","Tools can request user input via Prompts","Simple function call interface rather than explicit resource management","State management handled by server instance","Returns serialized data using model_dump() for consistency"]}
{"type":"entity","name":"MCP_Tools","entityType":"Protocol_Feature","observations":["Defined as async methods on server class","Return values must match tool definition schema","Can maintain state between invocations via server instance","Tools can prompt for user input when needed","No need for explicit Resource objects in implementation"]}
{"type":"entity","name":"Basic_Memory_MCP","entityType":"Implementation","observations":["Uses MemoryService for core operations","Implements project selection via prompts","Maintains project context across tool invocations","Maps directly to memory graph operations","Handles serialization of Pydantic models"]}
{"type":"entity","name":"Basic_Memory_Testing","entityType":"Testing_Design","observations":["Needs pytest for async testing","Should isolate filesystem operations for tests","Needs to handle MCP server lifecycle in tests","Should test both service layer and MCP interface","Will need mocks for project paths and file operations"]}
{"type":"entity","name":"Memory_Service_Tests","entityType":"Test_Suite","observations":["Should test entity creation with observations","Should test relation creation between entities","Should verify proper ID generation and model validation","Should test deletion cascading","Should test search functionality","Must verify proper serialization of entities and relations"]}
{"type":"entity","name":"MCP_Server_Tests","entityType":"Test_Suite","observations":["Should test project initialization workflow","Should test prompt handling","Should verify tool input/output formats","Should test error cases and validation","Must verify proper serialization in tool responses"]}
{"type":"entity","name":"Memory_Service_Refactoring","entityType":"Technical_Task","observations":["MemoryService uses create() but EntityService might expect create_entity()","MemoryService assumes get_by_name() but EntityService might use different method","Need to verify deletion method signatures","Need to check if search interface matches","Should verify observation handling matches ObservationService interface","RelationService methods need verification","EntityService.create_entity takes name, type, and optional observations directly, not an Entity object","EntityService requires project_path and entity_repo in constructor","ObservationService.add_observation takes Entity object and content string, not raw data","RelationService.create_relation takes Entity objects directly, not dict data","All services follow filesystem-as-source-of-truth pattern with DB indexing","All services handle database synchronization internally","Services expect Path objects for filesystem operations"]}
{"type":"entity","name":"Service_Interface_Audit","entityType":"Technical_Task","observations":["Need to review all existing service interfaces","Document current method signatures","Map discrepancies between MemoryService assumptions and actual interfaces","Check return types and error handling patterns","Review transaction/atomicity requirements","Method signatures need alignment: create vs create_entity etc","Need to handle DB repositories in service constructors","File operations should use project_path consistently","Need to maintain filesystem-as-source-of-truth pattern","Should handle database synchronization at service level","Error handling should align with existing patterns","Consider making MemoryService handle DB indexing consistently"]}
{"type":"entity","name":"Memory_Service_Patterns","entityType":"Technical_Pattern","observations":["Uses inner async functions to encapsulate operation logic","Leverages list comprehensions with async functions for parallel operations","Each operation follows a consistent pattern: validate, update DB, write file","Inner functions make the code more readable and maintainable","Operations can run in parallel when using list comprehensions with async functions"]}
{"type":"entity","name":"Pydantic_Create_Pattern","entityType":"Technical_Pattern","observations":["Separate Create models match the exact shape of incoming data","Provides clear contract for MCP tool inputs","Handles validation of raw input data","Converts cleanly to domain models via from_create methods","Maintains separation between external API format and internal models","Similar to FastAPI request model pattern","Allows camelCase in API while using snake_case internally"]}
{"type":"entity","name":"Basic_Memory_Business","entityType":"Business_Model","observations":["Core system is open source and free","Local-first, giving users data control","Professional features could be licensed","Enterprise support and customization services","Potential for MCP tool marketplace"]}
{"type":"entity","name":"MCP_Marketplace","entityType":"Business_Concept","observations":["Could host verified MCP tools for different use cases","Tools rated by performance and reliability","Marketplace takes percentage of tool usage fees","Enterprise tool verification and security scanning","Custom tool development services","Integration support for existing tools"]}
{"type":"entity","name":"Persistence_Of_Vision","entityType":"Concept","observations":["Mental model for continuous AI-human interaction","Like cinema: 24fps creates illusion of smooth motion","Basic-memory provides 'frames' of structured knowledge","Current state: Better than flipbook, not yet digital cinema","Goal: Achieve smoother cognitive continuity between interactions","Proposed by Drew as metaphor for AI conversation continuity"]}
{"type":"entity","name":"Conversation_Continuity_Pattern","entityType":"Usage_Pattern","observations":["Use basic-memory entity/relation schema for conversations","Each chat becomes an entity with observations for key points","Relations link to discussed concepts and other chats","Uses zettelkasten format IDs for natural ordering","Can be used as template/recipe for others","Future possibility: Git SHA integration for versioning"]}
{"type":"entity","name":"Usage_Recipes","entityType":"Feature_Concept","observations":["Predefined patterns users can follow or adapt","Could include conversation tracking recipe","Templates for different knowledge management styles","Shows practical applications of the generic schema","Helps users get started with the system"]}
{"type":"entity","name":"Chat_References","entityType":"Technical_Feature","observations":["Uses ref:* syntax to reference previous conversations","Combines reference semantics with pointer symbolism","Format: ref:*{zettelkasten-id}","Allows explicit context loading between chats","Inspired by C++ references and pointers","Provides memory-model-like access to conversation context","Uses ref:// URI format following MCP Resource pattern","Could support multiple reference schemes (chat/entity/concept)","Makes reference semantics explicit and unambiguous","Aligns with standard URI formatting"]}
{"type":"entity","name":"Chat_Reference_Protocol","entityType":"Technical_Specification","observations":["Uses URI format: ref://basic-memory/chat/[id]","Follows MCP Resource pattern: [protocol]://[host]/[path]","Enables explicit context loading between chats","Can support multiple resource types (chat/entity/concept)","Provides standardized way to reference previous conversations","Example: ref://basic-memory/chat/20240307-drew-ab12ef34"]}
{"type":"entity","name":"20240307-chat-reference-protocol","entityType":"conversation","observations":["Developed ref:// URI format for chat references","Added Chat Reference Protocol to prompt instructions","Discussed implementation of chat continuation","Created complete prompt instructions document","Reference format follows MCP Resource pattern","Reviewed and confirmed complete prompt instructions","Ready to test ref://basic-memory/chat/20240307-chat-reference-protocol in new chat"]}
{"type":"entity","name":"20240307-chat-reference-protocol-test","entityType":"conversation","observations":["First implementation test of chat reference protocol","Testing continuation from 20240307-chat-reference-protocol","Focused on practical implementation of ref:// URI format"]}
{"type":"entity","name":"Write_File_Tool_Usage","entityType":"Tool_Usage_Pattern","observations":["Never use placeholders like '# Rest of...' when writing files - must include complete file content","File content must be complete and valid - partial updates will truncate the file","If showing partial changes, should inform human and let them handle the file write","write_file tool replaces entire file contents - cannot do partial updates","Code files especially must be complete and valid to avoid breaking functionality","Always read_file before write_file to understand current state","Using write_file without reading first risks reverting recent changes","Pattern should be: read current state, make modifications, then write if needed","Especially important in collaborative development where files may have been updated"]}
{"type":"entity","name":"Run_Tests_Tool_Request","entityType":"Feature_Request","observations":["Need to add a tool enabling Claude to run tests locally","Would help with direct validation of code changes","Current workaround: Claude has to ask human to run tests","Should support running specific test functions (e.g. pytest tests/test_memory_service.py::test_create_relations)","Would improve iterative development workflow between human and AI"]}
{"type":"entity","name":"SQLAlchemy_Async_Loading_Pattern","entityType":"Technical_Pattern","observations":["Use selectinload() instead of lazy loading when accessing SQLAlchemy relationships in async code","Lazy loading doesn't work with async due to greenlet context requirements","selectinload performs a single efficient query with an IN clause","Pattern used in basic-memory's EntityRepository for loading relations","Documented in find_by_id method with thorough explanation","Alternative approaches: joinedload (single JOIN query) or subqueryload (subquery approach)","Benefits: prevents 'MissingGreenlet' errors, reduces N+1 query problems","Key insight: load all needed relationships upfront in async code","Example use: selectinload(Entity.outgoing_relations)"]}
{"type":"entity","name":"20241207-sqlalchemy-async-pattern","entityType":"conversation","observations":["Fixed SQLAlchemy async relationship loading issues","Implemented selectinload pattern in EntityRepository","Updated find_by_id to eager load relations","Added documentation about the pattern","Created knowledge graph entry about SQLAlchemy async loading","Fixed failing tests by properly loading relations in memory_service","Discussed SQLAlchemy relationship loading best practices"]}
{"type":"entity","name":"20241207-memory-service-relations","entityType":"conversation","observations":["Fixed SQLAlchemy async loading with selectinload pattern","Updated find_by_id in EntityRepository to eager load relations","Discovered create_relations works but returns empty list","Verified relations are being stored correctly in memory.json","Next step: Work on MemoryService.add_observations implementation","Improved understanding of MCP memory storage format through debugging"]}
{"type":"entity","name":"add_observations_implementation_plan","entityType":"technical_plan","observations":["Follow pattern from create_entity and create_relation methods","File operations first (read & write) - filesystem is source of truth","Database updates in parallel","Simplify current implementation","Current flow is:"," - First read entities and create observations"," - Write files in parallel"," - Update DB indexes sequentially","Key tests needed:"," - Adding observations to multiple entities"," - Verifying filesystem state first"," - Verifying database state"," - Error cases for missing entities"," - Error cases for file operations"]}
{"type":"entity","name":"MCP_Reference_Integration","entityType":"feature_idea","observations":["Can be implemented as a Model Context Protocol integration similar to the fetch tool","Would provide structured way to pass chat references to Claude","Could handle ref:// URL format systematically","Integration would fetch context from referenced chats and inject into conversation","Observed from Claude Desktop UI showing MCP integration pattern with fetch tool","Would be more robust than passing references in chat text"]}
{"type":"entity","name":"Project_Priorities","entityType":"roadmap","observations":["P1: Dogfooding basic-memory system instead of JSON memory store","Future: Implement MCP-based reference system"]}
{"type":"entity","name":"great_observation_loading_saga_20241207","entityType":"debugging_session","observations":["Occurred on December 7, 2024 while debugging basic-memory SQLAlchemy relationship loading","Issue: selectinload() wasn't properly loading relationships in async SQLAlchemy context","Tried multiple solutions: explicit joins, manual loading, various SQLAlchemy loading strategies","Final solution: Using session.refresh() with explicit relationship names","Memorable quote: 'The Great Observation Loading Saga'","Key learning: Sometimes the obvious SQLAlchemy patterns need adaptation for async contexts","Solution preserved in basic-memory repository in EntityRepository.find_by_id()"]}
{"type":"entity","name":"basic_memory_implementation_20241208","entityType":"technical_milestone","observations":["Fixed async SQLAlchemy relationship loading issues by using explicit refresh with relationship names","Established pattern of relationship handling belonging in MemoryService not EntityService","Fixed ID generation flow through Pydantic schemas to DB layer","Standardized error handling using EntityNotFoundError","All 32 tests passing with 70% coverage","Core services (Entity, Observation, Relation) working properly","Ready for MCP server implementation","Notable debugging session: The Great Observation Loading Saga - resolved lazy loading issues","Established clear separation between MemoryService orchestration and individual service responsibilities"]}
{"type":"entity","name":"MCP_Dependency_Risk","entityType":"technical_lesson","observations":["Experienced disruption when MCP npm package disappeared - 'leftpad moment'","Need to ensure basic-memory tools are resilient to external dependency issues","Local implementation of MCP server provides better stability than npm packages","Important to maintain control of critical infrastructure components","Validates DIY/local-first philosophy of basic-memory project","Package manager fragility revealed by simple 'npx @modelcontextprotocol/server-memory' failure"]}
{"type":"entity","name":"basic_memory_project_20241208","entityType":"technical_milestone","observations":["Core MCP server implementation completed with tools: create_entities, search_nodes, open_nodes, add_observations, create_relations, delete_entities, delete_observations","ProjectConfig and dependency injection pattern established","Test framework in place with in-memory DB support","Support for both camelCase (MCP) and snake_case (internal) formats","Filesystem remains source of truth with SQLite as index","Two-way sync pattern identified between Claude MCP tools and direct markdown file editing","Ready for Claude Desktop integration testing phase","Next steps identified: passing tests, markdown format definition, file change tracking, real-world testing","Implementation prioritizes local-first principles with filesystem as source of truth"]}
{"type":"entity","name":"basic_memory_mcp_architecture","entityType":"technical_design","observations":["MemoryServer class extends MCP Server with custom handler registration","Uses ProjectConfig for clean dependency injection and configuration","Memory service can be injected for testing","Handlers exposed as instance attributes for testing","Tool schemas leverage existing Pydantic models"]}
{"type":"entity","name":"basic_memory_sync_considerations","entityType":"design_insight","observations":["Need to handle sync between direct markdown file edits and DB index","Watch for file system changes as potential future enhancement","Consider index rebuild patterns on startup","Keep human-friendly markdown format for direct editing"]}
{"type":"entity","name":"mcp_server_learnings","entityType":"developer_insight","observations":["MCP protocol is new and documentation is still evolving","Test patterns are not well established yet in example implementations","Supporting both camelCase and snake_case helps with protocol/internal compatibility","Server.handle_* naming convention is important for handler registration"]}
{"type":"entity","name":"20241208-mcp-tool-refactoring","entityType":"conversation","observations":["Decision to return structured data via EmbeddedResource instead of TextContent string parsing","Plan to create Pydantic result models (CreateEntitiesResult, SearchNodesResult etc)","Will use application/vnd.basic-memory+json as MIME type for our structured data","Currently debugging test issues with add_observations tool","Entity ID vs name resolution needed in add_observations","Goal is to make tools more joyful to use by eliminating string parsing","MCP spec supports EmbeddedResource for structured data returns"]}
{"type":"entity","name":"Basic Memory MCP Server Implementation","entityType":"technical_notes","observations":["Server implements Model Context Protocol using proper structured data responses","Uses EmbeddedResource with custom MIME type 'application/vnd.basic-memory+json'","Clean separation between input validation and handlers via Pydantic models","All tool operations return structured data through create_response helper","Type safety with Literal types for tool names and proper typing for handlers","Handler registry pattern with TOOL_HANDLERS dictionary","Consistent error handling pattern using MCP error codes","Uses Pydantic ConfigDict for proper ORM integration","Tool schemas organized into Input and Response types","Input validation with Annotated types for extra constraints","Response models consistently use from_attributes=True for ORM data","Entity ID generation moved to model validator on EntityBase","Follows principle of making common operations easy and safe"]}
{"type":"relation","from":"Paul","to":"Basic_Machines","relationType":"created_and_maintains"}
{"type":"relation","from":"basic-memory","to":"Basic_Machines","relationType":"is_component_of"}
{"type":"relation","from":"Paul","to":"basic-memory","relationType":"develops"}
{"type":"relation","from":"fileio_module","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"entity_service","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"observation_service","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"fileio_module","to":"basic-memory","relationType":"is_component_of"}
{"type":"relation","from":"entity_service","to":"basic-memory","relationType":"is_component_of"}
{"type":"relation","from":"observation_service","to":"basic-memory","relationType":"is_component_of"}
{"type":"relation","from":"entity_service","to":"fileio_module","relationType":"uses"}
{"type":"relation","from":"observation_service","to":"fileio_module","relationType":"uses"}
{"type":"relation","from":"observation_management","to":"observation_service","relationType":"influences_design_of"}
{"type":"relation","to":"basic-memory","from":"testing_infrastructure","relationType":"supports"}
{"type":"relation","to":"testing_infrastructure","from":"test_categories","relationType":"implements"}
{"type":"relation","to":"basic-memory","from":"completed_work","relationType":"tracks_progress_of"}
{"type":"relation","to":"basic-memory","from":"future_work","relationType":"guides_development_of"}
{"type":"relation","to":"basic-memory","from":"design_decisions","relationType":"shapes_architecture_of"}
{"type":"relation","to":"basic-memory","from":"concurrency_considerations","relationType":"influences_design_of"}
{"type":"relation","to":"future_work","from":"concurrency_considerations","relationType":"informs"}
{"type":"relation","to":"observation_management","from":"design_decisions","relationType":"guides"}
{"type":"relation","to":"testing_infrastructure","from":"completed_work","relationType":"established"}
{"type":"relation","to":"design_decisions","from":"fileio_module","relationType":"implements"}
{"type":"relation","from":"observation_update_approaches","to":"observation_management","relationType":"analyzes"}
{"type":"relation","from":"bulk_update_approach","to":"observation_update_approaches","relationType":"is_option_of"}
{"type":"relation","from":"tracked_observations_approach","to":"observation_update_approaches","relationType":"is_option_of"}
{"type":"relation","from":"diff_based_approach","to":"observation_update_approaches","relationType":"is_option_of"}
{"type":"relation","from":"position_based_approach","to":"observation_update_approaches","relationType":"is_option_of"}
{"type":"relation","from":"tasks_and_progress","to":"basic-memory","relationType":"tracks_status_of"}
{"type":"relation","from":"design_decisions","to":"observation_update_approaches","relationType":"influences"}
{"type":"relation","from":"observation_update_approaches","to":"future_work","relationType":"informs"}
{"type":"relation","to":"basic-memory_implementation_patterns","from":"error_handling_patterns","relationType":"is_part_of"}
{"type":"relation","to":"basic-memory","from":"data_models","relationType":"implements"}
{"type":"relation","to":"basic-memory","from":"markdown_format","relationType":"defines"}
{"type":"relation","to":"basic-memory","from":"test_driven_development","relationType":"guides_development_of"}
{"type":"relation","to":"basic-memory","from":"architecture_evolution","relationType":"describes_development_of"}
{"type":"relation","to":"basic-memory_implementation_patterns","from":"validation_patterns","relationType":"is_part_of"}
{"type":"relation","to":"design_decisions","from":"architecture_evolution","relationType":"informs"}
{"type":"relation","to":"fileio_module","from":"markdown_format","relationType":"implements"}
{"type":"relation","to":"error_handling_patterns","from":"test_driven_development","relationType":"influenced"}
{"type":"relation","to":"data_models","from":"validation_patterns","relationType":"implements"}
{"type":"relation","to":"markdown_format","from":"markdown_examples","relationType":"documents"}
{"type":"relation","to":"markdown_format","from":"markdown_parsing_rules","relationType":"defines"}
{"type":"relation","to":"data_models","from":"schema_definitions","relationType":"documents"}
{"type":"relation","to":"test_driven_development","from":"test_evolution","relationType":"describes"}
{"type":"relation","to":"architecture_evolution","from":"implementation_challenges","relationType":"influenced"}
{"type":"relation","to":"test_evolution","from":"implementation_challenges","relationType":"shaped"}
{"type":"relation","to":"future_work","from":"implementation_challenges","relationType":"informs"}
{"type":"relation","from":"Basic_Factory","to":"Basic_Machines","relationType":"implements"}
{"type":"relation","from":"Basic_Factory_Components","to":"Basic_Factory","relationType":"is_part_of"}
{"type":"relation","from":"Component_Translation_Process","to":"Basic_Factory_Components","relationType":"enables"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic_Machines","relationType":"guides"}
{"type":"relation","from":"Paul","to":"Basic_Factory","relationType":"develops"}
{"type":"relation","from":"Paul","to":"Basic_Machines_Philosophy","relationType":"created"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Basic_Machines_Philosophy","relationType":"articulates"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"Basic_Factory","relationType":"guides_development_of"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Paul","relationType":"written_by"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Component_Translation_Process","relationType":"documents"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"Basic_Machines","relationType":"shapes_development_of"}
{"type":"relation","from":"Basic_Machines_Roadmap","to":"Basic_Machines","relationType":"guides_development_of"}
{"type":"relation","from":"Basic_Machines_Website","to":"Basic_Machines_Roadmap","relationType":"implements_phase_of"}
{"type":"relation","from":"Basic_Factory_Components","to":"Basic_Machines_Website","relationType":"enables"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic_Machines_Website","relationType":"informs"}
{"type":"relation","from":"Paul","to":"DIY_Ethics","relationType":"embodies"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"DIY_Ethics","relationType":"incorporates"}
{"type":"relation","from":"Basic_Machines","to":"DIY_Ethics","relationType":"exemplifies"}
{"type":"relation","from":"Component_Translation_Process","to":"Basic_Machines_Philosophy","relationType":"implements"}
{"type":"relation","from":"Basic_Factory_Components","to":"DIY_Ethics","relationType":"demonstrates"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"Basic_Machines_Philosophy","relationType":"aligns_with"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"Component_Translation_Process","relationType":"guides"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Basic_Machines","relationType":"defines_vision_for"}
{"type":"relation","from":"Basic_Machines_Website","to":"Basic_Machines_Manifesto","relationType":"implements_vision_of"}
{"type":"relation","from":"Basic_Factory","to":"AI_Human_Collaboration_Model","relationType":"demonstrates"}
{"type":"relation","from":"Paul","to":"AI_Human_Collaboration_Model","relationType":"developed_with_Claude"}
{"type":"relation","from":"Basic_Factory_Components","to":"Component_Translation_Process","relationType":"created_through"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic_Factory","relationType":"guides"}
{"type":"relation","from":"Basic_Factory","to":"MCP_Tools","relationType":"integrates"}
{"type":"relation","from":"Basic_Machines_Website","to":"Basic_Factory_Components","relationType":"will_use"}
{"type":"relation","from":"Basic_Machines_Roadmap","to":"Basic_Machines_Philosophy","relationType":"aligns_with"}
{"type":"relation","from":"Component_Translation_Process","to":"MCP_Tools","relationType":"leverages"}
{"type":"relation","from":"Basic_Factory","to":"basic-memory","relationType":"will_document_process_in"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"basic-memory","relationType":"will_be_implemented_in"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic_Machines_Roadmap","relationType":"informs_priorities_of"}
{"type":"relation","from":"basic-memory","to":"Basic_Machines_Philosophy","relationType":"embodies"}
{"type":"relation","from":"Paul","to":"Basic_Machines_Manifesto","relationType":"authored_with_Claude"}
{"type":"relation","from":"Basic_Factory","to":"Component_Translation_Process","relationType":"validated"}
{"type":"relation","from":"AI_Human_Collaboration_Model","to":"MCP_Tools","relationType":"utilizes"}
{"type":"relation","from":"Basic_Factory_Components","to":"Basic_Machines_Roadmap","relationType":"supports"}
{"type":"relation","from":"Basic_Machines_Website","to":"Basic_Factory","relationType":"will_demonstrate"}
{"type":"relation","from":"Basic_Factory","to":"basic-memory-webui","relationType":"enables_development_of"}
{"type":"relation","from":"basic-memory","to":"AI_Human_Development_Methodology","relationType":"implements"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"AI_Human_Development_Methodology","relationType":"guides"}
{"type":"relation","from":"Basic_Factory_Components","to":"basic-memory-webui","relationType":"provides_ui_for"}
{"type":"relation","from":"Component_Translation_Process","to":"AI_Human_Development_Methodology","relationType":"exemplifies"}
{"type":"relation","from":"Basic_Factory","to":"Basic Components","relationType":"enabled_creation_of"}
{"type":"relation","from":"Basic_Factory","to":"Tool Integration Discovery","relationType":"led_to"}
{"type":"relation","from":"MCP_Integration_Progress","to":"AI_Human_Development_Methodology","relationType":"validates"}
{"type":"relation","from":"Basic_Factory","to":"MCP_Integration_Progress","relationType":"demonstrates"}
{"type":"relation","from":"Basic_Factory","to":"AI_Human_Development_Methodology","relationType":"proves_effectiveness_of"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Basic Components","relationType":"inspires_architecture_of"}
{"type":"relation","from":"DIY_Ethics","to":"basic-memory","relationType":"shapes_design_of"}
{"type":"relation","from":"Basic_Machines_Philosophy","to":"Tool Integration Discovery","relationType":"guides_analysis_of"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"AI_Human_Development_Methodology","relationType":"documents_approach_of"}
{"type":"relation","from":"Basic_Machines_Manifesto","to":"Basic_Factory_Components","relationType":"explains_principles_of"}
{"type":"relation","from":"Basic_Memory_Project_Structure","to":"basic-memory","relationType":"organizes"}
{"type":"relation","from":"Basic_Memory_Database_Schema","to":"basic-memory","relationType":"defines_storage_for"}
{"type":"relation","from":"Basic_Memory_Markdown_Example","to":"Basic_Memory_File_Format","relationType":"demonstrates"}
{"type":"relation","from":"Basic_Memory_Project_Isolation_Decision","to":"Basic_Memory_Future_Enhancement_Weighted_Relations","relationType":"similar_to"}
{"type":"relation","to":"DIY_Ethics","from":"Basic_Memory_Project_Isolation_Decision","relationType":"follows"}
{"type":"relation","from":"Basic_Memory_Implementation_Plan","to":"basic-memory","relationType":"guides"}
{"type":"relation","from":"Basic_Memory_Implementation_Plan","to":"DIY_Ethics","relationType":"follows"}
{"type":"relation","from":"Basic_Memory_Implementation_Plan","to":"Basic_Memory_Database_Schema","relationType":"implements"}
{"type":"relation","from":"Basic_Memory_Implementation_Status","to":"Basic_Memory_Implementation_Plan","relationType":"updates"}
{"type":"relation","from":"Basic_Memory_Observation_Management_Design","to":"Basic_Memory_Technical_Design","relationType":"extends"}
{"type":"relation","from":"Basic_Memory_Architectural_Decisions","to":"DIY_Ethics","relationType":"guided_by"}
{"type":"relation","from":"Basic_Memory_Architectural_Decisions","to":"basic-memory","relationType":"structures"}
{"type":"relation","from":"Basic_Memory_Implementation_Status","to":"basic-memory","relationType":"describes_state_of"}
{"type":"relation","to":"Basic_Memory_Implementation_Status","from":"Basic_Memory_Implementation_Analysis","relationType":"analyzes"}
{"type":"relation","to":"basic-memory","from":"Basic_Memory_Current_Challenges","relationType":"identifies_issues_in"}
{"type":"relation","to":"DIY_Ethics","from":"Basic_Memory_Implementation_Analysis","relationType":"confirms_alignment_with"}
{"type":"relation","to":"Basic_Memory_Observation_Management_Design","from":"Basic_Memory_Observation_Hash_Tracking","relationType":"solves"}
{"type":"relation","to":"DIY_Ethics","from":"Basic_Memory_Observation_Hash_Tracking","relationType":"aligns_with"}
{"type":"relation","to":"Basic_Memory_File_Format","from":"Basic_Memory_Observation_Hash_Tracking","relationType":"preserves"}
{"type":"relation","to":"Basic_Memory_Technical_Design","from":"Basic_Memory_Observation_Hash_Tracking","relationType":"enhances"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"basic-memory","relationType":"implements_part_of"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"Basic_Memory_Database_Schema","relationType":"follows"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"DIY_Ethics","relationType":"aligns_with"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"testing_infrastructure","relationType":"demonstrates"}
{"type":"relation","from":"Basic_Memory_Repository_Implementation","to":"Basic Foundation","relationType":"inspired_by"}
{"type":"relation","from":"Basic_Memory_Dependencies","to":"basic-memory","relationType":"supports"}
{"type":"relation","from":"Basic_Memory_Dependencies","to":"Basic_Memory_Repository_Implementation","relationType":"enables"}
{"type":"relation","from":"Basic_Memory_Dependencies","to":"testing_infrastructure","relationType":"enables"}
{"type":"relation","from":"Basic_Memory_Current_Architecture","to":"basic-memory","relationType":"describes_state_of"}
{"type":"relation","from":"Basic_Memory_Evolution","to":"Basic_Memory_Current_Architecture","relationType":"explains_development_of"}
{"type":"relation","from":"Basic_Memory_Service_Layer","to":"Basic_Memory_Current_Architecture","relationType":"implements"}
{"type":"relation","from":"Basic_Memory_Schema_Design","to":"Basic_Memory_Current_Architecture","relationType":"implements"}
{"type":"relation","from":"Basic_Memory_Evolution","to":"Basic_Memory_Implementation_Plan","relationType":"reflects_on"}
{"type":"relation","from":"Basic_Memory_Evolution","to":"DIY_Ethics","relationType":"demonstrates_alignment_with"}
{"type":"relation","from":"Basic_Memory_Current_Architecture","to":"DIY_Ethics","relationType":"embodies"}
{"type":"relation","from":"Basic_Memory_Service_Layer","to":"fileio_module","relationType":"uses"}
{"type":"relation","from":"Basic_Memory_Schema_Design","to":"markdown_format","relationType":"implements"}
{"type":"relation","to":"basic-memory","from":"Basic_Memory_Next_Tasks","relationType":"guides_development_of"}
{"type":"relation","to":"DIY_Ethics","from":"Basic_Memory_Next_Tasks","relationType":"aligns_with"}
{"type":"relation","to":"Basic_Memory_Current_Architecture","from":"Basic_Memory_Next_Tasks","relationType":"extends"}
{"type":"relation","from":"Basic_Memory_Meta_Experience","to":"basic-memory","relationType":"validates_design_of"}
{"type":"relation","from":"Basic_Memory_Meta_Experience","to":"DIY_Ethics","relationType":"demonstrates_principles_of"}
{"type":"relation","from":"Basic_Memory_Meta_Experience","to":"design_decisions","relationType":"reinforces"}
{"type":"relation","from":"Basic_Memory_Meta_Experience","to":"Basic_Memory_Current_Architecture","relationType":"validates"}
{"type":"relation","from":"Model_Context_Protocol","to":"basic-memory","relationType":"enables"}
{"type":"relation","from":"basic-memory_core_principles","to":"basic-memory","relationType":"guides"}
{"type":"relation","from":"basic-memory_core_principles","to":"DIY_Ethics","relationType":"aligns_with"}
{"type":"relation","from":"basic-memory_business_model","to":"basic-memory","relationType":"defines_sustainability_for"}
{"type":"relation","from":"basic-memory_business_model","to":"DIY_Ethics","relationType":"maintains_alignment_with"}
{"type":"relation","from":"basic-memory_cli","to":"basic-memory","relationType":"provides_interface_for"}
{"type":"relation","from":"basic-memory_cli","to":"Model_Context_Protocol","relationType":"integrates_with"}
{"type":"relation","from":"basic-memory_export_format","to":"basic-memory","relationType":"standardizes_output_of"}
{"type":"relation","from":"basic-memory_export_format","to":"markdown_format","relationType":"extends"}
{"type":"relation","from":"basic-memory_core_principles","to":"Basic_Machines_Philosophy","relationType":"implements"}
{"type":"relation","from":"Model_Context_Protocol","to":"AI_Human_Collaboration_Model","relationType":"enables"}
{"type":"relation","from":"relation_service","to":"basic-memory","relationType":"will_be_component_of"}
{"type":"relation","from":"relation_service","to":"service_layer_patterns","relationType":"follows"}
{"type":"relation","from":"relation_service","to":"fileio_patterns","relationType":"uses"}
{"type":"relation","from":"relation_service","to":"database_models","relationType":"uses"}
{"type":"relation","from":"relation_service","to":"repository_patterns","relationType":"implements"}
{"type":"relation","from":"relation_service_design","to":"relation_service","relationType":"guides_implementation_of"}
{"type":"relation","from":"relation_service_implementation_plan","to":"relation_service","relationType":"defines_implementation_of"}
{"type":"relation","from":"relation_service_challenges","to":"relation_service_design","relationType":"informs"}
{"type":"relation","from":"relation_file_format","to":"markdown_format","relationType":"extends"}
{"type":"relation","from":"relation_service_error_handling","to":"service_layer_patterns","relationType":"implements"}
{"type":"relation","from":"relation_service_testing","to":"testing_infrastructure","relationType":"extends"}
{"type":"relation","from":"fileio_patterns","to":"service_layer_patterns","relationType":"enables"}
{"type":"relation","from":"database_models","to":"repository_patterns","relationType":"enables"}
{"type":"relation","from":"relation_service","to":"entity_service","relationType":"coordinates_with"}
{"type":"relation","from":"relation_file_format","to":"relation_service","relationType":"defines_storage_for"}
{"type":"relation","from":"relation_service_error_handling","to":"relation_service","relationType":"ensures_reliability_of"}
{"type":"relation","from":"relation_service_testing","to":"relation_service","relationType":"verifies"}
{"type":"relation","from":"service_layer_patterns","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"repository_patterns","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"fileio_patterns","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"database_models","to":"basic-memory_implementation_patterns","relationType":"implements"}
{"type":"relation","from":"relation_service_challenges","to":"implementation_challenges","relationType":"extends"}
{"type":"relation","from":"relation_service_implementation_plan","to":"future_work","relationType":"details"}
{"type":"relation","from":"relation_service_design","to":"design_decisions","relationType":"aligns_with"}
{"type":"relation","from":"relation_file_format","to":"design_decisions","relationType":"follows"}
{"type":"relation","from":"pytest_patterns","to":"testing_infrastructure","relationType":"extends"}
{"type":"relation","from":"relation_implementation_learnings","to":"basic-memory_implementation_patterns","relationType":"informs"}
{"type":"relation","from":"test_driven_insights","to":"test_driven_development","relationType":"enriches"}
{"type":"relation","from":"meta_development_insights","to":"AI_Human_Collaboration_Model","relationType":"improves"}
{"type":"relation","from":"relation_implementation_learnings","to":"relation_service","relationType":"guides_implementation_of"}
{"type":"relation","from":"pytest_patterns","to":"test_evolution","relationType":"demonstrates"}
{"type":"relation","from":"test_driven_insights","to":"design_decisions","relationType":"influences"}
{"type":"relation","from":"meta_development_insights","to":"architecture_evolution","relationType":"informs"}
{"type":"relation","from":"relation_service","to":"relation_implementation_learnings","relationType":"validates"}
{"type":"relation","from":"test_driven_insights","to":"implementation_challenges","relationType":"helps_solve"}
{"type":"relation","from":"AI_Assistant_Learnings","to":"meta_development_insights","relationType":"enriches"}
{"type":"relation","from":"Effective_Response_Patterns","to":"AI_Assistant_Learnings","relationType":"implements"}
{"type":"relation","from":"AI_Context_Management","to":"AI_Human_Collaboration_Model","relationType":"improves"}
{"type":"relation","from":"AI_Tool_Usage_Patterns","to":"AI_Context_Management","relationType":"enables"}
{"type":"relation","from":"AI_Assistant_Learnings","to":"Basic_Memory_Meta_Experience","relationType":"validates"}
{"type":"relation","from":"AI_Tool_Usage_Patterns","to":"Model_Context_Protocol","relationType":"demonstrates_effective_use_of"}
{"type":"relation","from":"AI_Context_Management","to":"basic-memory","relationType":"validates_design_of"}
{"type":"relation","from":"Effective_Response_Patterns","to":"AI_Human_Development_Methodology","relationType":"refines"}
{"type":"relation","to":"relation_service","from":"relation_service_patterns","relationType":"guides"}
{"type":"relation","to":"test_driven_development","from":"test_driven_insights_relations","relationType":"enriches"}
{"type":"relation","to":"implementation_challenges","from":"relation_service_learnings","relationType":"solves"}
{"type":"relation","to":"basic-memory_implementation_patterns","from":"relation_service_patterns","relationType":"implements"}
{"type":"relation","to":"markdown_format","from":"relation_service_patterns","relationType":"extends"}
{"type":"relation","to":"service_layer_patterns","from":"relation_service_patterns","relationType":"refines"}
{"type":"relation","from":"packaging_learnings","to":"implementation_challenges","relationType":"informs"}
{"type":"relation","from":"packaging_learnings","to":"test_driven_development","relationType":"impacts"}
{"type":"relation","to":"basic-memory","from":"Recent_Implementation_Progress","relationType":"updates_status_of"}
{"type":"relation","to":"future_work","from":"Next_Steps","relationType":"extends"}
{"type":"relation","to":"design_decisions","from":"Development_Practices","relationType":"informs"}
{"type":"relation","to":"packaging_learnings","from":"Development_Practices","relationType":"incorporates"}
{"type":"relation","to":"test_driven_development","from":"Development_Practices","relationType":"refines"}
{"type":"relation","to":"basic-memory_implementation_patterns","from":"Development_Practices","relationType":"enhances"}
{"type":"relation","from":"Basic_Memory_MCP","to":"MCP_Server_Implementation","relationType":"follows"}
{"type":"relation","from":"Basic_Memory_MCP","to":"MCP_Tools","relationType":"uses"}
{"type":"relation","from":"Basic_Memory","to":"MCP_Server_Implementation","relationType":"implements"}
{"type":"relation","from":"Basic_Memory_Testing","to":"Memory_Service_Tests","relationType":"includes"}
{"type":"relation","from":"Basic_Memory_Testing","to":"MCP_Server_Tests","relationType":"includes"}
{"type":"relation","from":"Memory_Service_Tests","to":"Basic_Memory_MCP","relationType":"validates"}
{"type":"relation","from":"MCP_Server_Tests","to":"Basic_Memory_MCP","relationType":"validates"}
{"type":"relation","from":"Service_Interface_Audit","to":"Memory_Service_Refactoring","relationType":"informs"}
{"type":"relation","from":"Memory_Service_Refactoring","to":"Basic_Memory_MCP","relationType":"affects"}
{"type":"relation","from":"Memory_Service_Patterns","to":"Basic_Memory_MCP","relationType":"improves"}
{"type":"relation","from":"Pydantic_Create_Pattern","to":"Memory_Service_Patterns","relationType":"enables"}
{"type":"relation","from":"Pydantic_Create_Pattern","to":"Basic_Memory_MCP","relationType":"improves"}
{"type":"relation","from":"MCP_Marketplace","to":"Basic_Memory_Business","relationType":"enables"}
{"type":"relation","from":"Basic_Memory","to":"MCP_Marketplace","relationType":"could_integrate_with"}
{"type":"relation","from":"Persistence_Of_Vision","to":"Basic_Memory","relationType":"helps_achieve"}
{"type":"relation","from":"Drew","to":"Persistence_Of_Vision","relationType":"conceptualized"}
{"type":"relation","to":"Usage_Recipes","from":"Conversation_Continuity_Pattern","relationType":"is_example_of"}
{"type":"relation","to":"Basic_Memory","from":"Usage_Recipes","relationType":"enhances"}
{"type":"relation","to":"Basic_Memory","from":"Chat_References","relationType":"enhances"}
{"type":"relation","to":"Conversation_Continuity_Pattern","from":"Chat_References","relationType":"implements"}
{"type":"relation","from":"20240307-chat-reference-protocol-test","to":"20240307-chat-reference-protocol","relationType":"continues_from"}
{"type":"relation","from_id":"Run_Tests_Tool_Request","to_id":"Basic_Machines","relationType":"enhances","context":"development workflow improvement"}
{"type":"relation","from_id":"SQLAlchemy_Async_Loading_Pattern","to_id":"basic-memory","relation_type":"improves","context":"database performance and async compatibility"}
{"type":"relation","from_id":"SQLAlchemy_Async_Loading_Pattern","to_id":"Entity","relation_type":"applies_to","context":"relationship loading strategy"}
{"type":"relation","from":"MCP_Reference_Integration","to":"Project_Priorities","relationType":"prioritized_after"}
{"type":"relation","from":"great_observation_loading_saga_20241207","to":"Basic_Memory","relationType":"occurred_in"}
{"type":"relation","from":"great_observation_loading_saga_20241207","to":"SQLAlchemy","relationType":"relates_to"}
{"type":"relation","from":"basic_memory_implementation_20241208","to":"Basic_Memory","relationType":"improves"}
{"type":"relation","from":"great_observation_loading_saga_20241207","to":"basic_memory_implementation_20241208","relationType":"leads_to"}
{"type":"relation","from":"MCP_Dependency_Risk","to":"DIY_Ethics","relationType":"validates"}
{"type":"relation","from":"MCP_Dependency_Risk","to":"basic-memory_core_principles","relationType":"reinforces"}
{"type":"relation","from":"MCP_Dependency_Risk","to":"Basic_Memory_Implementation_Plan","relationType":"influences"}
{"type":"relation","from":"basic_memory_mcp_architecture","to":"basic_memory_project_20241208","relationType":"implements"}
{"type":"relation","from":"basic_memory_sync_considerations","to":"basic_memory_project_20241208","relationType":"influences"}
{"type":"relation","from":"mcp_server_learnings","to":"basic_memory_mcp_architecture","relationType":"informs"}
{"type":"relation","from":"20241208-mcp-tool-refactoring","to":"Basic_Memory_MCP","relationType":"improves"}
{"type":"relation","from":"20241208-mcp-tool-refactoring","to":"Basic_Memory_Implementation_Plan","relationType":"implements"}
+23 -6
View File
@@ -3,7 +3,7 @@ name = "basic-memory"
dynamic = ["version"]
description = "Local-first knowledge management combining Zettelkasten with knowledge graphs"
readme = "README.md"
requires-python = ">=3.12.1"
requires-python = ">=3.12"
license = { text = "AGPL-3.0-or-later" }
authors = [
{ name = "Basic Machines", email = "hello@basic-machines.co" }
@@ -15,7 +15,6 @@ dependencies = [
"aiosqlite>=0.20.0",
"greenlet>=3.1.1",
"pydantic[email,timezone]>=2.10.3",
"icecream>=2.1.3",
"mcp>=1.2.0",
"pydantic-settings>=2.6.1",
"loguru>=0.7.3",
@@ -30,10 +29,19 @@ dependencies = [
"alembic>=1.14.1",
"pillow>=11.1.0",
"pybars3>=0.9.7",
"fastmcp==2.10.2",
"fastmcp==2.12.3", # Pinned - 2.14.x breaks MCP tools visibility (issue #463)
"pyjwt>=2.10.1",
"python-dotenv>=1.1.0",
"pytest-aio>=1.9.0",
"aiofiles>=24.1.0", # Optional observability (disabled by default via config)
"asyncpg>=0.30.0",
"nest-asyncio>=1.6.0", # For Alembic migrations with Postgres
"pytest-asyncio>=1.2.0",
"psycopg==3.3.1",
"mdformat>=0.7.22",
"mdformat-gfm>=0.3.7",
"mdformat-frontmatter>=2.0.8",
"openpanel>=0.0.1", # Anonymous usage telemetry (Homebrew-style opt-out)
]
@@ -53,16 +61,22 @@ build-backend = "hatchling.build"
[tool.pytest.ini_options]
pythonpath = ["src", "tests"]
addopts = "--cov=basic_memory --cov-report term-missing"
testpaths = ["tests"]
testpaths = ["tests", "test-int"]
asyncio_mode = "strict"
asyncio_default_fixture_loop_scope = "function"
markers = [
"benchmark: Performance benchmark tests (deselect with '-m \"not benchmark\"')",
"slow: Slow-running tests (deselect with '-m \"not slow\"')",
"postgres: Tests that run against Postgres backend (deselect with '-m \"not postgres\"')",
"windows: Windows-specific tests (deselect with '-m \"not windows\"')",
]
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.uv]
dev-dependencies = [
[dependency-groups]
dev = [
"gevent>=24.11.1",
"icecream>=2.1.3",
"pytest>=8.3.4",
@@ -71,6 +85,9 @@ dev-dependencies = [
"pytest-asyncio>=0.24.0",
"pytest-xdist>=3.0.0",
"ruff>=0.1.6",
"freezegun>=1.5.5",
"testcontainers[postgres]>=4.0.0",
"psycopg>=3.2.0",
]
[tool.hatch.version]
-483
View File
@@ -1,483 +0,0 @@
#!/usr/bin/env python3
"""
Legal File Inventory Generator for Basic Memory
Generates comprehensive file inventory with contributor information
for legal documentation, copyright assignments, and due diligence.
Usage:
python scripts/generate_legal_inventory.py [options]
Output formats:
- CSV: Detailed spreadsheet for analysis
- JSON: Structured data for integration
- Markdown: Human-readable legal report
"""
import argparse
import csv
import hashlib
import json
import subprocess
from collections import defaultdict
from datetime import datetime
from pathlib import Path
from typing import Dict, List
class LegalInventoryGenerator:
"""Generate comprehensive file inventory for legal documentation."""
# Files to exclude from legal inventory
EXCLUDED_PATTERNS = {
# Generated/compiled files
'*.pyc', '*.pyo', '*.pyd', '__pycache__',
'*.so', '*.dylib', '*.dll',
# Build/cache directories
'build/', 'dist/', '.eggs/', '*.egg-info/',
'.coverage', '.pytest_cache/', '.mypy_cache/',
'.ruff_cache/', '.tox/', 'venv/', '.venv/', 'env/', '.env/',
'node_modules/', '.npm/', '.yarn/',
# IDE and editor files
'.vscode/', '.idea/', '*.swp', '*.swo', '*~',
'.DS_Store', 'Thumbs.db',
# Version control
'.git/', '.gitignore',
# OS generated
'desktop.ini', '*.tmp', '*.temp'
}
# File categories for legal classification
FILE_CATEGORIES = {
'source_code': ['.py', '.pyx', '.pyi'],
'documentation': ['.md', '.rst', '.txt'],
'configuration': ['.toml', '.yaml', '.yml', '.json', '.ini', '.cfg'],
'legal': ['LICENSE', 'COPYING', 'COPYRIGHT', '.md'],
'build_deployment': ['Dockerfile', 'Makefile', 'justfile', '.sh'],
'database': ['.sql', '.sqlite', '.db'],
'templates': ['.j2', '.jinja2', '.hbs', '.handlebars'],
'data': ['.csv', '.json', '.xml'],
'other': [] # Catch-all for uncategorized files
}
def __init__(self, repo_path: str = "."):
"""Initialize generator with repository path."""
self.repo_path = Path(repo_path).resolve()
self.file_inventory: List[Dict] = []
self.contributors: Dict[str, Dict] = defaultdict(lambda: {
'email': '', 'commits': 0, 'lines_added': 0, 'files': set()
})
def should_exclude_file(self, file_path: Path) -> bool:
"""Check if file should be excluded from inventory."""
# Check if file is tracked by git (more efficient than check-ignore)
try:
rel_path = str(file_path.relative_to(self.repo_path))
result = subprocess.run([
'git', 'ls-files', '--error-unmatch', rel_path
], capture_output=True, cwd=self.repo_path)
# If git ls-files returns non-zero, file is not tracked (likely ignored)
if result.returncode != 0:
return True
except Exception:
# Fallback to manual exclusion patterns if git fails
pass
# Additional manual exclusions for safety
file_str = str(file_path.relative_to(self.repo_path))
for pattern in self.EXCLUDED_PATTERNS:
if pattern.endswith('/'):
if any(part == pattern[:-1] for part in file_path.parts):
return True
elif '*' in pattern:
import fnmatch
if fnmatch.fnmatch(file_str, pattern):
return True
else:
if file_path.name == pattern or file_str == pattern:
return True
return False
def categorize_file(self, file_path: Path) -> str:
"""Categorize file based on extension and name."""
suffix = file_path.suffix.lower()
name = file_path.name.upper()
# Check legal files by name first
if any(legal in name for legal in ['LICENSE', 'COPYING', 'COPYRIGHT', 'CLA']):
return 'legal'
# Check by extension
for category, extensions in self.FILE_CATEGORIES.items():
if suffix in extensions:
return category
return 'other'
def get_file_hash(self, file_path: Path) -> str:
"""Generate SHA-256 hash of file content."""
try:
with open(file_path, 'rb') as f:
return hashlib.sha256(f.read()).hexdigest()
except (IOError, OSError):
return "ERROR_READING_FILE"
def get_git_contributors(self, file_path: Path) -> List[Dict]:
"""Get contributor information for a specific file."""
try:
rel_path = file_path.relative_to(self.repo_path)
# Get contributors with line counts
result = subprocess.run([
'git', 'log', '--follow', '--pretty=format:%an|%ae|%ad|%H',
'--date=short', '--', str(rel_path)
], capture_output=True, text=True, cwd=self.repo_path)
if result.returncode != 0:
return []
contributors = []
seen = set()
for line in result.stdout.strip().split('\n'):
if not line:
continue
parts = line.split('|')
if len(parts) >= 4:
name, email, date, commit_hash = parts[:4]
# Normalize author names/emails
normalized_name = self.normalize_author_name(name, email)
if normalized_name not in seen:
contributors.append({
'name': normalized_name,
'email': email,
'first_contribution': date,
'commit_hash': commit_hash
})
seen.add(normalized_name)
return contributors
except Exception as e:
print(f"Warning: Could not get git info for {file_path}: {e}")
return []
def normalize_author_name(self, name: str, email: str) -> str:
"""Normalize author names to handle multiple emails for same person."""
# Known mappings for Basic Memory team
name_mappings = {
'phernandez': 'Paul Hernandez',
'Paul Hernandez': 'Paul Hernandez',
'drew-cain': 'Drew Cain',
'Drew Cain': 'Drew Cain'
}
# Handle GitHub bot accounts
if 'bot' in name.lower() or 'claude' in name.lower():
return f"{name} (AI Assistant)"
return name_mappings.get(name, name)
def get_file_stats(self, file_path: Path) -> Dict:
"""Get comprehensive file statistics."""
try:
stat = file_path.stat()
rel_path = file_path.relative_to(self.repo_path)
# Basic file info
file_info = {
'path': str(rel_path),
'name': file_path.name,
'size_bytes': stat.st_size,
'modified_time': datetime.fromtimestamp(stat.st_mtime).isoformat(),
'category': self.categorize_file(file_path),
'sha256_hash': self.get_file_hash(file_path)
}
# Git information
contributors = self.get_git_contributors(file_path)
file_info['contributors'] = contributors
file_info['primary_author'] = contributors[0]['name'] if contributors else 'Unknown'
file_info['contributor_count'] = len(contributors)
# Update global contributor stats
for contrib in contributors:
name = contrib['name']
self.contributors[name]['email'] = contrib['email']
self.contributors[name]['files'].add(str(rel_path))
return file_info
except Exception as e:
print(f"Error processing {file_path}: {e}")
return None
def scan_repository(self) -> None:
"""Scan repository and build file inventory."""
print(f"Scanning repository: {self.repo_path}")
# Get all git-tracked files first (much more efficient)
try:
result = subprocess.run([
'git', 'ls-files'
], capture_output=True, text=True, cwd=self.repo_path)
if result.returncode == 0:
tracked_files = [self.repo_path / f for f in result.stdout.strip().split('\n') if f]
print(f"Found {len(tracked_files)} git-tracked files")
for file_path in tracked_files:
if file_path.is_file():
file_info = self.get_file_stats(file_path)
if file_info:
self.file_inventory.append(file_info)
else:
print("Warning: Could not get git tracked files, falling back to directory scan")
self._fallback_scan()
except Exception as e:
print(f"Warning: Git command failed ({e}), falling back to directory scan")
self._fallback_scan()
# Get global git stats
self._get_global_git_stats()
print(f"Processed {len(self.file_inventory)} files")
print(f"Found {len(self.contributors)} contributors")
def _fallback_scan(self) -> None:
"""Fallback directory scan if git commands fail."""
for file_path in self.repo_path.rglob('*'):
if file_path.is_file() and not self.should_exclude_file(file_path):
file_info = self.get_file_stats(file_path)
if file_info:
self.file_inventory.append(file_info)
def _get_global_git_stats(self) -> None:
"""Get global contributor statistics from git."""
try:
# Get commit counts per author
result = subprocess.run([
'git', 'shortlog', '-sn', '--all'
], capture_output=True, text=True, cwd=self.repo_path)
if result.returncode == 0:
for line in result.stdout.strip().split('\n'):
if line.strip():
parts = line.strip().split('\t', 1)
if len(parts) == 2:
count, name = parts
normalized_name = self.normalize_author_name(name, '')
self.contributors[normalized_name]['commits'] = int(count)
except Exception as e:
print(f"Warning: Could not get global git stats: {e}")
def generate_summary(self) -> Dict:
"""Generate inventory summary statistics."""
total_files = len(self.file_inventory)
total_size = sum(f['size_bytes'] for f in self.file_inventory)
# Category breakdown
categories = defaultdict(int)
for file_info in self.file_inventory:
categories[file_info['category']] += 1
# Top contributors
top_contributors = sorted(
self.contributors.items(),
key=lambda x: len(x[1]['files']),
reverse=True
)[:10]
return {
'scan_date': datetime.now().isoformat(),
'repository_path': str(self.repo_path),
'total_files': total_files,
'total_size_bytes': total_size,
'categories': dict(categories),
'contributor_count': len(self.contributors),
'top_contributors': [
{
'name': name,
'file_count': len(stats['files']),
'commit_count': stats['commits'],
'email': stats['email']
}
for name, stats in top_contributors
]
}
def export_csv(self, output_path: str) -> None:
"""Export inventory to CSV format."""
with open(output_path, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = [
'path', 'name', 'category', 'size_bytes', 'modified_time',
'primary_author', 'contributor_count', 'contributors_list',
'sha256_hash'
]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for file_info in sorted(self.file_inventory, key=lambda x: x['path']):
contributors_list = '; '.join([
f"{c['name']} ({c['email']})" for c in file_info['contributors']
])
writer.writerow({
'path': file_info['path'],
'name': file_info['name'],
'category': file_info['category'],
'size_bytes': file_info['size_bytes'],
'modified_time': file_info['modified_time'],
'primary_author': file_info['primary_author'],
'contributor_count': file_info['contributor_count'],
'contributors_list': contributors_list,
'sha256_hash': file_info['sha256_hash']
})
print(f"CSV export saved to: {output_path}")
def export_json(self, output_path: str) -> None:
"""Export inventory to JSON format."""
# Convert sets to lists for JSON serialization
contributors_serializable = {}
for name, stats in self.contributors.items():
contributors_serializable[name] = {
'email': stats['email'],
'commits': stats['commits'],
'lines_added': stats['lines_added'],
'files': list(stats['files'])
}
data = {
'summary': self.generate_summary(),
'files': self.file_inventory,
'contributors': contributors_serializable
}
with open(output_path, 'w', encoding='utf-8') as jsonfile:
json.dump(data, jsonfile, indent=2, ensure_ascii=False)
print(f"JSON export saved to: {output_path}")
def export_markdown(self, output_path: str) -> None:
"""Export inventory to Markdown format for legal documentation."""
summary = self.generate_summary()
with open(output_path, 'w', encoding='utf-8') as mdfile:
mdfile.write("# Basic Memory - Legal File Inventory\n\n")
# Summary section
mdfile.write("## Summary\n\n")
mdfile.write(f"**Scan Date:** {summary['scan_date']}\n")
mdfile.write(f"**Repository:** {summary['repository_path']}\n")
mdfile.write(f"**Total Files:** {summary['total_files']:,}\n")
mdfile.write(f"**Total Size:** {summary['total_size_bytes']:,} bytes\n")
mdfile.write(f"**Contributors:** {summary['contributor_count']}\n\n")
# Category breakdown
mdfile.write("## File Categories\n\n")
for category, count in sorted(summary['categories'].items()):
mdfile.write(f"- **{category.replace('_', ' ').title()}:** {count} files\n")
mdfile.write("\n")
# Top contributors
mdfile.write("## Contributors\n\n")
for contrib in summary['top_contributors']:
mdfile.write(f"- **{contrib['name']}** ({contrib['email']}): ")
mdfile.write(f"{contrib['file_count']} files, {contrib['commit_count']} commits\n")
mdfile.write("\n")
# Detailed file listing by category
mdfile.write("## Detailed File Inventory\n\n")
for category in sorted(summary['categories'].keys()):
category_files = [f for f in self.file_inventory if f['category'] == category]
if not category_files:
continue
mdfile.write(f"### {category.replace('_', ' ').title()}\n\n")
for file_info in sorted(category_files, key=lambda x: x['path']):
mdfile.write(f"**{file_info['path']}**\n")
mdfile.write(f"- Primary Author: {file_info['primary_author']}\n")
mdfile.write(f"- Contributors: {file_info['contributor_count']}\n")
mdfile.write(f"- Size: {file_info['size_bytes']:,} bytes\n")
if file_info['contributors']:
contributors_str = ', '.join([c['name'] for c in file_info['contributors']])
mdfile.write(f"- All Contributors: {contributors_str}\n")
mdfile.write(f"- SHA-256: `{file_info['sha256_hash']}`\n\n")
print(f"Markdown export saved to: {output_path}")
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Generate legal file inventory for Basic Memory repository"
)
parser.add_argument(
'--repo-path', '-r',
default='.',
help='Path to repository (default: current directory)'
)
parser.add_argument(
'--output-dir', '-o',
default='./legal_inventory',
help='Output directory for reports (default: ./legal_inventory)'
)
parser.add_argument(
'--formats', '-f',
nargs='+',
choices=['csv', 'json', 'markdown', 'all'],
default=['all'],
help='Output formats to generate (default: all)'
)
args = parser.parse_args()
# Create output directory
output_dir = Path(args.output_dir)
output_dir.mkdir(exist_ok=True)
# Generate inventory
generator = LegalInventoryGenerator(args.repo_path)
generator.scan_repository()
# Determine formats to export
formats = args.formats
if 'all' in formats:
formats = ['csv', 'json', 'markdown']
# Export in requested formats
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
if 'csv' in formats:
generator.export_csv(output_dir / f'basic_memory_inventory_{timestamp}.csv')
if 'json' in formats:
generator.export_json(output_dir / f'basic_memory_inventory_{timestamp}.json')
if 'markdown' in formats:
generator.export_markdown(output_dir / f'basic_memory_inventory_{timestamp}.md')
print("\nLegal inventory generation complete!")
print(f"Output saved to: {output_dir}")
if __name__ == '__main__':
main()
@@ -0,0 +1,156 @@
---
title: 'SPEC-1: Specification-Driven Development Process'
type: spec
permalink: specs/spec-1-specification-driven-development-process
tags:
- process
- specification
- development
- meta
---
# SPEC-1: Specification-Driven Development Process
## Why
We're implementing specification-driven development to solve the complexity and circular refactoring issues in our web development process.
Instead of getting lost in framework details and type gymnastics, we start with clear specifications that drive implementation.
The default approach of adhoc development with AI agents tends to result in:
- Circular refactoring cycles
- Fighting framework complexity
- Lost context between sessions
- Unclear requirements and scope
## What
This spec defines our process for using basic-memory as the specification engine to build basic-memory-cloud.
We're creating a recursive development pattern where basic-memory manages the specs that drive the development of basic-memory-cloud.
**Affected Areas:**
- All future component development
- Architecture decisions
- Agent collaboration workflows
- Knowledge management and context preservation
## How (High Level)
### Specification Structure
Name: Spec names should be numbered sequentially, followed by a description eg. `SPEC-X - Simple Description.md`.
See: [[Spec-2: Slash Commands Reference]]
Every spec is a complete thought containing:
- **Why**: The reasoning and problem being solved
- **What**: What is affected or changed
- **How**: High-level approach to implementation
- **How to Evaluate**: Testing/validation procedure
- Additional context as needed
### Living Specification Format
Specifications are **living documents** that evolve throughout implementation:
**Progress Tracking:**
- **Completed items**: Use ✅ checkmark emoji for implemented features
- **Pending items**: Use `- [ ]` GitHub-style checkboxes for remaining tasks
- **In-progress items**: Use `- [x]` when work is actively underway
**Status Philosophy:**
- **Avoid static status headers** like "COMPLETE" or "IN PROGRESS" that become stale
- **Use checklists within content** to show granular implementation progress
- **Keep specs informative** while providing clear progress visibility
- **Update continuously** as understanding and implementation evolve
**Example Format:**
```markdown
### ComponentName
- ✅ Basic functionality implemented
- ✅ Props and events defined
- - [ ] Add sorting controls
- - [ ] Improve accessibility
- - [x] Currently implementing responsive design
```
This creates **git-friendly progress tracking** where `[ ]` easily becomes `[x]` or ✅ when completed, and specs remain valuable throughout the development lifecycle.
## Claude Code
We will leverage Claude Code capabilities to make the process semi-automated.
- Slash commands: define repeatable steps in the process (create spec, implement, review, etc)
- Agents: define roles to carry out instructions (front end developer, baskend developer, etc)
- MCP tools: enable agents to implement specs via actions (write code, test, etc)
### Workflow
1. **Create**: Write spec as complete thought in `/specs` folder
2. **Discuss**: Iterate and refine through agent collaboration
3. **Implement**: Hand spec to appropriate specialist agent
4. **Validate**: Review implementation against spec criteria
5. **Document**: Update spec with learnings and decisions
### Slash Commands
Claude slash commands are used to manage the flow.
These are simple instructions to help make the process uniform.
They can be updated and refined as needed.
- `/spec create [name]` - Create new specification
- `/spec status` - Show current spec states
- `/spec implement [name]` - Hand to appropriate agent
- `/spec review [name]` - Validate implementation
### Agent Orchestration
Agents are defined with clear roles, for instance:
- **system-architect**: Creates high-level specs, ADRs, architectural decisions
- **vue-developer**: Component specs, UI patterns, frontend architecture
- **python-developer**: Implementation specs, technical details, backend logic
-
- Each agent reads/updates specs through basic-memory tools.
## How to Evaluate
### Success Criteria
- Specs provide clear, actionable guidance for implementation
- Reduced circular refactoring and scope creep
- Persistent context across development sessions
- Clean separation between "what/why" and implementation details
- Specs record a history of what happened and why for historical context
### Testing Procedure
1. Create a spec for an existing problematic component
2. Have an agent implement following only the spec
3. Compare result quality and development speed vs. ad-hoc approach
4. Measure context preservation across sessions
5. Evaluate spec clarity and completeness
### Metrics
- Time from spec to working implementation
- Number of refactoring cycles required
- Agent understanding of requirements
- Spec reusability for similar components
## Notes
- Start simple: specs are just complete thoughts, not heavy processes
- Use basic-memory's knowledge graph to link specs, decisions, components
- Let the process evolve naturally based on what works
- Focus on solving the actual problem: Manage complexity in development
## Observations
- [problem] Web development without clear goals and documentation circular refactoring cycles #complexity
- [solution] Specification-driven development reduces scope creep and context loss #process-improvement
- [pattern] basic-memory as specification engine creates recursive development loop #meta-development
- [workflow] Five-step process: Create → Discuss → Implement → Validate → Document #methodology
- [tool] Slash commands provide uniform process automation #automation
- [agent-pattern] Three specialized agents handle different implementation domains #specialization
- [success-metric] Time from spec to working implementation measures process efficiency #measurement
- [learning] Process should evolve naturally based on what works in practice #adaptation
- [format] Living specifications use checklists for progress tracking instead of static status headers #documentation
- [evolution] Specs evolve throughout implementation maintaining value as working documents #continuous-improvement
## Relations
- spec [[Spec-2: Slash Commands Reference]]
- spec [[Spec-3: Agent Definitions]]
@@ -0,0 +1,569 @@
---
title: 'SPEC-10: Unified Deployment Workflow and Event Tracking'
type: spec
permalink: specs/spec-10-unified-deployment-workflow-event-tracking
tags:
- workflow
- deployment
- event-sourcing
- architecture
- simplification
---
# SPEC-10: Unified Deployment Workflow and Event Tracking
## Why
We replaced a complex multi-workflow system with DBOS orchestration that was proving to be more trouble than it was worth. The previous architecture had four separate workflows (`tenant_provisioning`, `tenant_update`, `tenant_deployment`, `tenant_undeploy`) with overlapping logic, complex state management, and fragmented event tracking. DBOS added unnecessary complexity without providing sufficient value, leading to harder debugging and maintenance.
**Problems Solved:**
- **Framework Complexity**: DBOS configuration overhead and fighting framework limitations
- **Code Duplication**: Multiple workflows implementing similar operations with duplicate logic
- **Poor Observability**: Fragmented event tracking across workflow boundaries
- **Maintenance Overhead**: Complex orchestration for fundamentally simple operations
- **Debugging Difficulty**: Framework abstractions hiding simple Python stack traces
## What
This spec documents the architectural simplification that consolidates tenant lifecycle management into a unified system with comprehensive event tracking.
**Affected Areas:**
- Tenant deployment workflows (provisioning, updates, undeploying)
- Event sourcing and workflow tracking infrastructure
- API endpoints for tenant operations
- Database schema for workflow and event correlation
- Integration testing for tenant lifecycle operations
**Key Changes:**
- **Removed DBOS entirely** - eliminated framework dependency and complexity
- **Consolidated 4 workflows → 2 unified deployment workflows (deploy/undeploy)**
- **Added workflow tracking system** with complete event correlation
- **Simplified API surface** - single `/deploy` endpoint handles all scenarios
- **Enhanced observability** through event sourcing with workflow grouping
## How (High Level)
### Architectural Philosophy
**Embrace simplicity over framework complexity** - use well-structured Python with proper database design instead of complex orchestration frameworks.
### Core Components
#### 1. Unified Deployment Workflow
```python
class TenantDeploymentWorkflow:
async def deploy_tenant_workflow(self, tenant_id: str, workflow_id: UUID, image_tag: str = None):
# Single workflow handles both initial provisioning AND updates
# Each step is idempotent and handles its own error recovery
# Database transactions provide the durability we need
await self.start_deployment_step(workflow_id, tenant_uuid, image_tag)
await self.create_fly_app_step(workflow_id, tenant_uuid)
await self.create_bucket_step(workflow_id, tenant_uuid)
await self.deploy_machine_step(workflow_id, tenant_uuid, image_tag)
await self.complete_deployment_step(workflow_id, tenant_uuid, image_tag, deployment_time)
```
**Key Benefits:**
- **Handles both provisioning and updates** in single workflow
- **Idempotent operations** - safe to retry any step
- **Clean error handling** via simple Python exceptions
- **Resumable** - can restart from any failed step
#### 2. Workflow Tracking System
**Database Schema:**
```sql
CREATE TABLE workflow (
id UUID PRIMARY KEY,
workflow_type VARCHAR(50) NOT NULL, -- 'tenant_deployment', 'tenant_undeploy'
tenant_id UUID REFERENCES tenant(id),
status VARCHAR(20) DEFAULT 'running', -- 'running', 'completed', 'failed'
workflow_metadata JSONB DEFAULT '{}' -- image_tag, etc.
);
ALTER TABLE event ADD COLUMN workflow_id UUID REFERENCES workflow(id);
```
**Event Correlation:**
- Every workflow operation generates events tagged with `workflow_id`
- Complete audit trail from workflow start to completion
- Events grouped by workflow for easy reconstruction of operations
#### 3. Parameter Standardization
All workflow methods follow consistent signature pattern:
```python
async def method_name(self, session: AsyncSession, workflow_id: UUID | None, tenant_id: UUID, ...)
```
**Benefits:**
- **Consistent event tagging** - all events properly correlated
- **Clear method contracts** - workflow_id always first parameter
- **Type safety** - proper UUID handling throughout
### Implementation Strategy
#### Phase 1: Workflow Consolidation ✅ COMPLETED
- [x] **Remove DBOS dependency** - eliminated dbos_config.py and all DBOS imports
- [x] **Create unified TenantDeploymentWorkflow** - handles both provisioning and updates
- [x] **Remove legacy workflows** - deleted tenant_provisioning.py, tenant_update.py
- [x] **Simplify API endpoints** - consolidated to single `/deploy` endpoint
- [x] **Update integration tests** - comprehensive edge case testing
#### Phase 2: Workflow Tracking System ✅ COMPLETED
- [x] **Database migration** - added workflow table and event.workflow_id foreign key
- [x] **Workflow repository** - CRUD operations for workflow records
- [x] **Event correlation** - all workflow events tagged with workflow_id
- [x] **Comprehensive testing** - workflow lifecycle and event grouping tests
#### Phase 3: Parameter Standardization ✅ COMPLETED
- [x] **Standardize method signatures** - workflow_id as first parameter pattern
- [x] **Fix event tagging** - ensure all workflow events properly correlated
- [x] **Update service methods** - consistent parameter order across tenant_service
- [x] **Integration test validation** - verify complete event sequences
### Architectural Benefits
#### Code Simplification
- **39 files changed**: 2,247 additions, 3,256 deletions (net -1,009 lines)
- **Eliminated framework complexity** - no more DBOS configuration or abstractions
- **Consolidated logic** - single deployment workflow vs 4 separate workflows
- **Cleaner API surface** - unified endpoint vs multiple workflow-specific endpoints
#### Enhanced Observability
- **Complete event correlation** - every workflow event tagged with workflow_id
- **Audit trail reconstruction** - can trace entire tenant lifecycle through events
- **Workflow status tracking** - running/completed/failed states in database
- **Comprehensive testing** - edge cases covered with real infrastructure
#### Operational Benefits
- **Simpler debugging** - plain Python stack traces vs framework abstractions
- **Reduced dependencies** - one less complex framework to maintain
- **Better error handling** - explicit exception handling vs framework magic
- **Easier maintenance** - straightforward Python code vs orchestration complexity
## How to Evaluate
### Success Criteria
#### Functional Completeness ✅ VERIFIED
- [x] **Unified deployment workflow** handles both initial provisioning and updates
- [x] **Undeploy workflow** properly integrated with event tracking
- [x] **All operations idempotent** - safe to retry any step without duplication
- [x] **Complete tenant lifecycle** - provision → active → update → undeploy
#### Event Tracking and Correlation ✅ VERIFIED
- [x] **All workflow events tagged** with proper workflow_id
- [x] **Event sequence verification** - tests assert exact event order and content
- [x] **Workflow grouping** - events can be queried by workflow_id for complete audit trail
- [x] **Cross-workflow isolation** - deployment vs undeploy events properly separated
#### Database Schema and Performance ✅ VERIFIED
- [x] **Migration applied** - workflow table and event.workflow_id column created
- [x] **Proper indexing** - performance optimized queries on workflow_type, tenant_id, status
- [x] **Foreign key constraints** - referential integrity between workflows and events
- [x] **Database triggers** - updated_at timestamp automation
#### Test Coverage ✅ COMPREHENSIVE
- [x] **Unit tests**: 4 workflow tracking tests covering lifecycle and event grouping
- [x] **Integration tests**: Real infrastructure testing with Fly.io resources
- [x] **Edge case coverage**: Failed deployments, partial state recovery, resource conflicts
- [x] **Event sequence verification**: Exact event order and content validation
### Testing Procedure
#### Unit Test Validation ✅ PASSING
```bash
cd apps/cloud && pytest tests/test_workflow_tracking.py -v
# 4/4 tests passing - workflow lifecycle and event grouping
```
#### Integration Test Validation ✅ PASSING
```bash
cd apps/cloud && pytest tests/integration/test_tenant_workflow_deployment_integration.py -v
cd apps/cloud && pytest tests/integration/test_tenant_workflow_undeploy_integration.py -v
# Comprehensive real infrastructure testing with actual Fly.io resources
# Tests provision → deploy → update → undeploy → cleanup cycles
```
### Performance Metrics
#### Code Metrics ✅ ACHIEVED
- **Net code reduction**: -1,009 lines (3,256 deletions, 2,247 additions)
- **Workflow consolidation**: 4 workflows → 1 unified deployment workflow
- **Dependency reduction**: Removed DBOS framework dependency entirely
- **API simplification**: Multiple endpoints → single `/deploy` endpoint
#### Operational Metrics ✅ VERIFIED
- **Event correlation**: 100% of workflow events properly tagged with workflow_id
- **Audit trail completeness**: Full tenant lifecycle traceable through event sequences
- **Error handling**: Clean Python exceptions vs framework abstractions
- **Debugging simplicity**: Direct stack traces vs orchestration complexity
### Implementation Status: ✅ COMPLETE
All phases completed successfully with comprehensive testing and verification:
**Phase 1 - Workflow Consolidation**: ✅ COMPLETE
- Removed DBOS dependency and consolidated workflows
- Unified deployment workflow handles all scenarios
- Comprehensive integration testing with real infrastructure
**Phase 2 - Workflow Tracking**: ✅ COMPLETE
- Database schema implemented with proper indexing
- Event correlation system fully functional
- Complete audit trail capability verified
**Phase 3 - Parameter Standardization**: ✅ COMPLETE
- Consistent method signatures across all workflow methods
- All events properly tagged with workflow_id
- Type safety verified across entire codebase
**Phase 4 - Asynchronous Job Queuing**:
**Goal**: Transform synchronous deployment workflows into background jobs for better user experience and system reliability.
**Current Problem**:
- Deployment API calls are synchronous - users wait for entire tenant provisioning (30-60 seconds)
- No retry mechanism for failed operations
- HTTP timeouts on long-running deployments
- Poor user experience during infrastructure provisioning
**Solution**: Redis-backed job queue with arq for reliable background processing
#### Architecture Overview
```python
# API Layer: Return immediately with job tracking
@router.post("/{tenant_id}/deploy")
async def deploy_tenant(tenant_id: UUID):
# Create workflow record in Postgres
workflow = await workflow_repo.create_workflow("tenant_deployment", tenant_id)
# Enqueue job in Redis
job = await arq_pool.enqueue_job('deploy_tenant_task', tenant_id, workflow.id)
# Return job ID immediately
return {"job_id": job.job_id, "workflow_id": workflow.id, "status": "queued"}
# Background Worker: Process via existing unified workflow
async def deploy_tenant_task(ctx, tenant_id: str, workflow_id: str):
# Existing workflow logic - zero changes needed!
await workflow_manager.deploy_tenant(UUID(tenant_id), workflow_id=UUID(workflow_id))
```
#### Implementation Tasks
**Phase 4.1: Core Job Queue Setup** ✅ COMPLETED
- [x] **Add arq dependency** - integrated Redis job queue with existing infrastructure
- [x] **Create job definitions** - wrapped existing deployment/undeploy workflows as arq tasks
- [x] **Update API endpoints** - updated provisioning endpoints to return job IDs instead of waiting for completion
- [x] **JobQueueService implementation** - service layer for job enqueueing and status tracking
- [x] **Job status tracking** - integrated with existing workflow table for status updates
- [x] **Comprehensive testing** - 18 tests covering positive, negative, and edge cases
**Phase 4.2: Background Worker Implementation** ✅ COMPLETED
- [x] **Job status API** - GET /jobs/{job_id}/status endpoint integrated with JobQueueService
- [x] **Background worker process** - arq worker to process queued jobs with proper settings and Redis configuration
- [x] **Worker settings and configuration** - WorkerSettings class with proper timeouts, max jobs, and error handling
- [x] **Fix API endpoints** - updated job status API to use JobQueueService instead of direct Redis access
- [x] **Integration testing** - comprehensive end-to-end testing with real ARQ workers and Fly.io infrastructure
- [x] **Worker entry points** - dual-purpose entrypoint.sh script and __main__.py module support for both API and worker processes
- [x] **Test fixture updates** - fixed all API and service test fixtures to work with job queue dependencies
- [x] **AsyncIO event loop fixes** - resolved event loop issues in integration tests for subprocess worker compatibility
- [x] **Complete test coverage** - all 46 tests passing across unit, integration, and API test suites
- [x] **Type safety verification** - 0 type checking errors across entire ARQ job queue implementation
#### Phase 4.2 Implementation Summary ✅ COMPLETE
**Core ARQ Job Queue System:**
- **JobQueueService** - Centralized service for job enqueueing, status tracking, and Redis pool management
- **deployment_jobs.py** - ARQ job functions that wrap existing deployment/undeploy workflows
- **Worker Settings** - Production-ready ARQ configuration with proper timeouts and error handling
- **Dual-Process Architecture** - Single Docker image with entrypoint.sh supporting both API and worker modes
**Key Files Added:**
- `apps/cloud/src/basic_memory_cloud/jobs/` - Complete job queue implementation (7 files)
- `apps/cloud/entrypoint.sh` - Dual-purpose Docker container entry point
- `apps/cloud/tests/integration/test_worker_integration.py` - Real infrastructure integration tests
- `apps/cloud/src/basic_memory_cloud/schemas/job_responses.py` - API response schemas
**API Integration:**
- Provisioning endpoints return job IDs immediately instead of blocking for 60+ seconds
- Job status API endpoints for real-time monitoring of deployment progress
- Proper error handling and job failure scenarios with detailed error messages
**Testing Achievement:**
- **46 total tests passing** across all test suites (unit, integration, API, services)
- **Real infrastructure testing** - ARQ workers process actual Fly.io deployments
- **Event loop safety** - Fixed asyncio issues for subprocess worker compatibility
- **Test fixture updates** - All fixtures properly support job queue dependencies
- **Type checking** - 0 errors across entire codebase
**Technical Metrics:**
- **38 files changed** - +1,736 insertions, -334 deletions
- **Integration test runtime** - ~18 seconds with real ARQ workers and Fly.io verification
- **Event loop isolation** - Proper async session management for subprocess compatibility
- **Redis integration** - Production-ready Redis configuration with connection pooling
**Phase 4.3: Production Hardening** ✅ COMPLETED
- [x] **Configure Upstash Redis** - production Redis setup on Fly.io
- [x] **Retry logic for external APIs** - exponential backoff for flaky Tigris IAM operations
- [x] **Monitoring and observability** - comprehensive Redis queue monitoring with CLI tools
- [x] **Error handling improvements** - graceful handling of expected API errors with appropriate log levels
- [x] **CLI tooling enhancements** - bulk update commands for CI/CD automation
- [x] **Documentation improvements** - comprehensive monitoring guide with Redis patterns
- [x] **Job uniqueness** - ARQ-based duplicate prevention for tenant operations
- [ ] **Worker scaling** - multiple arq workers for parallel job processing
- [ ] **Job persistence** - ensure jobs survive Redis/worker restarts
- [ ] **Error alerting** - notifications for failed deployment jobs
**Phase 4.4: Advanced Features** (Future)
- [ ] **Job scheduling** - deploy tenants at specific times
- [ ] **Priority queues** - urgent deployments processed first
- [ ] **Batch operations** - bulk tenant deployments
- [ ] **Job dependencies** - deployment → configuration → activation chains
#### Benefits Achieved ✅ REALIZED
**User Experience Improvements:**
- **Immediate API responses** - users get job ID instantly vs waiting 60+ seconds for deployment completion
- **Real-time job tracking** - status API provides live updates on deployment progress
- **Better error visibility** - detailed error messages and job failure tracking
- **CI/CD automation ready** - bulk update commands for automated tenant deployments
**System Reliability:**
- **Redis persistence** - jobs survive Redis/worker restarts with proper queue durability
- **Idempotent job processing** - jobs can be safely retried without side effects
- **Event loop isolation** - worker processes operate independently from API server
- **Retry resilience** - exponential backoff for flaky external API calls (3 attempts, 1s/2s delays)
- **Graceful error handling** - expected API errors logged at INFO level, unexpected at ERROR level
- **Job uniqueness** - prevent duplicate tenant operations with ARQ's built-in uniqueness feature
**Operational Benefits:**
- **Horizontal scaling ready** - architecture supports adding more workers for parallel processing
- **Comprehensive testing** - real infrastructure integration tests ensure production reliability
- **Type safety** - full type checking prevents runtime errors in job processing
- **Clean separation** - API and worker processes use same codebase with different entry points
- **Queue monitoring** - Redis CLI integration for real-time queue activity monitoring
- **Comprehensive documentation** - detailed monitoring guide with Redis pattern explanations
**Development Benefits:**
- **Zero workflow changes** - existing deployment/undeploy workflows work unchanged as background jobs
- **Async/await native** - modern Python asyncio patterns throughout the implementation
- **Event correlation preserved** - all existing workflow tracking and event sourcing continues to work
- **Enhanced CLI tooling** - unified tenant commands with proper endpoint routing
- **Database integrity** - proper foreign key constraint handling in tenant deletion
#### Infrastructure Requirements
- **Local**: Redis via docker-compose (already exists) ✅
- **Production**: Upstash Redis on Fly.io (already configured) ✅
- **Workers**: arq worker processes (new deployment target)
- **Monitoring**: Job status dashboard (simple web interface)
#### API Evolution
```python
# Before: Synchronous (blocks for 60+ seconds)
POST /tenant/{id}/deploy {status: "active", machine_id: "..."}
# After: Asynchronous (returns immediately)
POST /tenant/{id}/deploy {job_id: "uuid", workflow_id: "uuid", status: "queued"}
GET /jobs/{job_id}/status {status: "running", progress: "deploying_machine", workflow_id: "uuid"}
GET /workflows/{workflow_id}/events [...] # Existing event tracking works unchanged
```
**Technology Choice**: **arq (Redis)** over pgqueuer
- **Existing Redis infrastructure** - Upstash + docker-compose already configured
- **Better ecosystem** - monitoring tools, documentation, community
- **Made by pydantic team** - aligns with existing Python stack
- **Hybrid approach** - Redis for queue operations + Postgres for workflow state
#### Job Uniqueness Implementation
**Problem**: Multiple concurrent deployment requests for the same tenant could create duplicate jobs, wasting resources and potentially causing conflicts.
**Solution**: Leverage ARQ's built-in job uniqueness feature using predictable job IDs:
```python
# JobQueueService implementation
async def enqueue_deploy_job(self, tenant_id: UUID, image_tag: str | None = None) -> str:
unique_job_id = f"deploy-{tenant_id}"
job = await self.redis_pool.enqueue_job(
"deploy_tenant_job",
str(tenant_id),
image_tag,
_job_id=unique_job_id, # ARQ prevents duplicates
)
if job is None:
# Job already exists - return existing job ID
return unique_job_id
else:
# New job created - return ARQ job ID
return job.job_id
```
**Key Features:**
- **Predictable Job IDs**: `deploy-{tenant_id}`, `undeploy-{tenant_id}`
- **Duplicate Prevention**: ARQ returns `None` for duplicate job IDs
- **Graceful Handling**: Return existing job ID instead of raising errors
- **Idempotent Operations**: Safe to retry deployment requests
- **Clear Logging**: Distinguish "Enqueued new" vs "Found existing" jobs
**Benefits:**
- Prevents resource waste from duplicate deployments
- Eliminates race conditions from concurrent requests
- Makes job monitoring more predictable with consistent IDs
- Provides natural deduplication without complex locking mechanisms
## Notes
### Design Philosophy Lessons
- **Simplicity beats framework magic** - removing DBOS made the system more reliable and debuggable
- **Event sourcing > complex orchestration** - database-backed event tracking provides better observability than framework abstractions
- **Idempotent operations > resumable workflows** - each step handling its own retry logic is simpler than framework-managed resumability
- **Explicit error handling > framework exception handling** - Python exceptions are clearer than orchestration framework error states
### Future Considerations
- **Monitoring integration** - workflow tracking events could feed into observability systems
- **Performance optimization** - event querying patterns may benefit from additional indexing
- **Audit compliance** - complete event trail supports regulatory requirements
- **Operational dashboards** - workflow status could drive tenant health monitoring
### Related Specifications
- **SPEC-8**: TigrisFS Integration - bucket provisioning integrated with deployment workflow
- **SPEC-1**: Specification-Driven Development Process - this spec follows the established format
## Observations
- [architecture] Removing framework complexity led to more maintainable system #simplification
- [workflow] Single unified deployment workflow handles both provisioning and updates #consolidation
- [observability] Event sourcing with workflow correlation provides complete audit trail #event-tracking
- [database] Foreign key relationships between workflows and events enable powerful queries #schema-design
- [testing] Integration tests with real infrastructure catch edge cases that unit tests miss #testing-strategy
- [parameters] Consistent method signatures (workflow_id first) reduce cognitive overhead #api-design
- [maintenance] Fewer workflows and dependencies reduce long-term maintenance burden #operational-excellence
- [debugging] Plain Python exceptions are clearer than framework abstraction layers #developer-experience
- [resilience] Exponential backoff retry patterns handle flaky external API calls gracefully #error-handling
- [monitoring] Redis queue monitoring provides real-time operational visibility #observability
- [ci-cd] Bulk update commands enable automated tenant deployments in continuous delivery pipelines #automation
- [documentation] Comprehensive monitoring guides reduce operational learning curve #knowledge-management
- [error-logging] Context-aware log levels (INFO for expected errors, ERROR for unexpected) improve signal-to-noise ratio #logging-strategy
- [job-uniqueness] ARQ job uniqueness with predictable tenant-based IDs prevents duplicate operations and resource waste #deduplication
## Implementation Notes
### Configuration Integration
- **Redis Configuration**: Add Redis settings to existing `apps/cloud/src/basic_memory_cloud/config.py`
- **Local Development**: Leverage existing Redis setup from `docker-compose.yml`
- **Production**: Use Upstash Redis configuration for production environments
### Docker Entrypoint Strategy
Create `entrypoint.sh` script to toggle between API server and worker processes using single Docker image:
```bash
#!/bin/bash
# Entrypoint script for Basic Memory Cloud service
# Supports multiple process types: api, worker
set -e
case "$1" in
"api")
echo "Starting Basic Memory Cloud API server..."
exec uvicorn basic_memory_cloud.main:app \
--host 0.0.0.0 \
--port 8000 \
--log-level info
;;
"worker")
echo "Starting Basic Memory Cloud ARQ worker..."
# For ARQ worker implementation
exec python -m arq basic_memory_cloud.jobs.settings.WorkerSettings
;;
*)
echo "Usage: $0 {api|worker}"
echo " api - Start the FastAPI server"
echo " worker - Start the ARQ worker"
exit 1
;;
esac
```
### Fly.io Process Groups Configuration
Use separate machine groups for API and worker processes with independent scaling:
```toml
# fly.toml app configuration for basic-memory-cloud
app = 'basic-memory-cloud-dev-basic-machines'
primary_region = 'dfw'
org = 'basic-machines'
kill_signal = 'SIGINT'
kill_timeout = '5s'
[build]
# Process groups for API server and worker
[processes]
api = "api"
worker = "worker"
# Machine scaling configuration
[[machine]]
size = 'shared-cpu-1x'
processes = ['api']
min_machines_running = 1
auto_stop_machines = false
auto_start_machines = true
[[machine]]
size = 'shared-cpu-1x'
processes = ['worker']
min_machines_running = 1
auto_stop_machines = false
auto_start_machines = true
[env]
# Python configuration
PYTHONUNBUFFERED = '1'
PYTHONPATH = '/app'
# Logging configuration
LOG_LEVEL = 'DEBUG'
# Redis configuration for ARQ
REDIS_URL = 'redis://basic-memory-cloud-redis.upstash.io'
# Database configuration
DATABASE_HOST = 'basic-memory-cloud-db-dev-basic-machines.internal'
DATABASE_PORT = '5432'
DATABASE_NAME = 'basic_memory_cloud'
DATABASE_USER = 'postgres'
DATABASE_SSL = 'true'
# Worker configuration
ARQ_MAX_JOBS = '10'
ARQ_KEEP_RESULT = '3600'
# Fly.io configuration
FLY_ORG = 'basic-machines'
FLY_REGION = 'dfw'
# Internal service - no external HTTP exposure for worker
# API accessible via basic-memory-cloud-dev-basic-machines.flycast:8000
[[vm]]
size = 'shared-cpu-1x'
```
### Benefits of This Architecture
- **Single Docker Image**: Both API and worker use same container with different entrypoints
- **Independent Scaling**: Scale API and worker processes separately based on demand
- **Clean Separation**: Web traffic handling separate from background job processing
- **Existing Infrastructure**: Leverages current PostgreSQL + Redis setup without complexity
- **Hybrid State Management**: Redis for queue operations, PostgreSQL for persistent workflow tracking
## Relations
- implements [[SPEC-8 TigrisFS Integration]]
- follows [[SPEC-1 Specification-Driven Development Process]]
- supersedes previous multi-workflow architecture
@@ -0,0 +1,186 @@
---
title: 'SPEC-11: Basic Memory API Performance Optimization'
type: spec
permalink: specs/spec-11-basic-memory-api-performance-optimization
tags:
- performance
- api
- mcp
- database
- cloud
---
# SPEC-11: Basic Memory API Performance Optimization
## Why
The Basic Memory API experiences significant performance issues in cloud environments due to expensive per-request initialization. MCP tools making
HTTP requests to the API suffer from 350ms-2.6s latency overhead **before** any actual operation occurs.
**Root Cause Analysis:**
- GitHub Issue #82 shows repeated initialization sequences in logs (16:29:35 and 16:49:58)
- Each MCP tool call triggers full database initialization + project reconciliation
- `get_engine_factory()` dependency calls `db.get_or_create_db()` on every request
- `reconcile_projects_with_config()` runs expensive sync operations repeatedly
**Performance Impact:**
- Database connection setup: ~50-100ms per request
- Migration checks: ~100-500ms per request
- Project reconciliation: ~200ms-2s per request
- **Total overhead**: ~350ms-2.6s per MCP tool call
This creates compounding effects with tenant auto-start delays and increases timeout risk in cloud deployments.
## What
This optimization affects the **core basic-memory repository** components:
1. **API Lifespan Management** (`src/basic_memory/api/app.py`)
- Cache database connections in app state during startup
- Avoid repeated expensive initialization
2. **Dependency Injection** (`src/basic_memory/deps.py`)
- Modify `get_engine_factory()` to use cached connections
- Eliminate per-request database setup
3. **Initialization Service** (`src/basic_memory/services/initialization.py`)
- Add caching/throttling to project reconciliation
- Skip expensive operations when appropriate
4. **Configuration** (`src/basic_memory/config.py`)
- Add optional performance flags for cloud environments
**Backwards Compatibility**: All changes must be backwards compatible with existing CLI and non-cloud usage.
## How (High Level)
### Phase 1: Cache Database Connections (Critical - 80% of gains)
**Problem**: `get_engine_factory()` calls `db.get_or_create_db()` per request
**Solution**: Cache database engine/session in app state during lifespan
1. **Modify API Lifespan** (`api/app.py`):
```python
@asynccontextmanager
async def lifespan(app: FastAPI):
app_config = ConfigManager().config
await initialize_app(app_config)
# Cache database connection in app state
engine, session_maker = await db.get_or_create_db(app_config.database_path)
app.state.engine = engine
app.state.session_maker = session_maker
# ... rest of startup logic
```
2. Modify Dependency Injection (deps.py):
```python
async def get_engine_factory(
request: Request
) -> tuple[AsyncEngine, async_sessionmaker[AsyncSession]]:
"""Get cached engine and session maker from app state."""
return request.app.state.engine, request.app.state.session_maker
```
Phase 2: Optimize Project Reconciliation (Secondary - 20% of gains)
Problem: reconcile_projects_with_config() runs expensive sync repeatedly
Solution: Add module-level caching with time-based throttling
1. Add Reconciliation Cache (services/initialization.py):
```ptyhon
_project_reconciliation_completed = False
_last_reconciliation_time = 0
async def reconcile_projects_with_config(app_config, force=False):
# Skip if recently completed (within 60 seconds) unless forced
if recently_completed and not force:
return
# ... existing logic
```
Phase 3: Cloud Environment Flags (Optional)
Problem: Force expensive initialization in production environments
Solution: Add skip flags for cloud/stateless deployments
1. Add Config Flag (config.py):
skip_initialization_sync: bool = Field(default=False)
2. Configure in Cloud (basic-memory-cloud integration):
BASIC_MEMORY_SKIP_INITIALIZATION_SYNC=true
How to Evaluate
Success Criteria
1. Performance Metrics (Primary):
- MCP tool response time reduced by 50%+ (measure before/after)
- Database connection overhead eliminated (0ms vs 50-100ms)
- Migration check overhead eliminated (0ms vs 100-500ms)
- Project reconciliation overhead reduced by 90%+
2. Load Testing:
- Concurrent MCP tool calls maintain performance
- No memory leaks in cached connections
- Database connection pool behaves correctly
3. Functional Correctness:
- All existing API endpoints work identically
- MCP tools maintain full functionality
- CLI operations unaffected
- Database migrations still execute properly
4. Backwards Compatibility:
- No breaking changes to existing APIs
- Config changes are optional with safe defaults
- Non-cloud deployments work unchanged
Testing Strategy
Performance Testing:
# Before optimization
time basic-memory-mcp-tools write_note "test" "content" "folder"
# Measure: ~1-3 seconds
# After optimization
time basic-memory-mcp-tools write_note "test" "content" "folder"
# Target: <500ms
Load Testing:
# Multiple concurrent MCP tool calls
for i in {1..10}; do
basic-memory-mcp-tools search "test" &
done
wait
# Verify: No degradation, consistent response times
Regression Testing:
# Full basic-memory test suite
just test
# All tests must pass
# Integration tests with cloud deployment
# Verify MCP gateway → API → database flow works
Validation Checklist
- Phase 1 Complete: Database connections cached, dependency injection optimized
- Performance Benchmark: 50%+ improvement in MCP tool response times
- Memory Usage: No leaks in cached connections over 24h+ periods
- Stress Testing: 100+ concurrent requests maintain performance
- Backwards Compatibility: All existing functionality preserved
- Documentation: Performance optimization documented in README
- Cloud Integration: basic-memory-cloud sees performance benefits
Notes
Implementation Priority:
- Phase 1 provides 80% of performance gains and should be implemented first
- Phase 2 provides remaining 20% and addresses edge cases
- Phase 3 is optional for maximum cloud optimization
Risk Mitigation:
- All changes backwards compatible
- Gradual rollout possible (Phase 1 → 2 → 3)
- Easy rollback via configuration flags
Cloud Integration:
- This optimization directly addresses basic-memory-cloud issue #82
- Changes in core basic-memory will benefit all cloud tenants
- No changes needed in basic-memory-cloud itself
@@ -0,0 +1,182 @@
# SPEC-12: OpenTelemetry Observability
## Why
We need comprehensive observability for basic-memory-cloud to:
- Track request flows across our multi-tenant architecture (MCP → Cloud → API services)
- Debug performance issues and errors in production
- Understand user behavior and system usage patterns
- Correlate issues to specific tenants for targeted debugging
- Monitor service health and latency across the distributed system
Currently, we only have basic logging without request correlation or distributed tracing capabilities.
## What
Implement OpenTelemetry instrumentation across all basic-memory-cloud services with:
### Core Requirements
1. **Distributed Tracing**: End-to-end request tracing from MCP gateway through to tenant API instances
2. **Tenant Correlation**: All traces tagged with tenant_id, user_id, and workos_user_id
3. **Service Identification**: Clear service naming and namespace separation
4. **Auto-instrumentation**: Automatic tracing for FastAPI, SQLAlchemy, HTTP clients
5. **Grafana Cloud Integration**: Direct OTLP export to Grafana Cloud Tempo
### Services to Instrument
- **MCP Gateway** (basic-memory-mcp): Entry point with JWT extraction
- **Cloud Service** (basic-memory-cloud): Provisioning and management operations
- **API Service** (basic-memory-api): Tenant-specific instances
- **Worker Processes** (ARQ workers): Background job processing
### Key Trace Attributes
- `tenant.id`: UUID from UserProfile.tenant_id
- `user.id`: WorkOS user identifier
- `user.email`: User email for debugging
- `service.name`: Specific service identifier
- `service.namespace`: Environment (development/production)
- `operation.type`: Business operation (provision/update/delete)
- `tenant.app_name`: Fly.io app name for tenant instances
## How
### Phase 1: Setup OpenTelemetry SDK
1. Add OpenTelemetry dependencies to each service's pyproject.toml:
```python
"opentelemetry-distro[otlp]>=1.29.0",
"opentelemetry-instrumentation-fastapi>=0.50b0",
"opentelemetry-instrumentation-httpx>=0.50b0",
"opentelemetry-instrumentation-sqlalchemy>=0.50b0",
"opentelemetry-instrumentation-logging>=0.50b0",
```
2. Create shared telemetry initialization module (`apps/shared/telemetry.py`)
3. Configure Grafana Cloud OTLP endpoint via environment variables:
```bash
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-east-2.grafana.net/otlp
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic[token]
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
```
### Phase 2: Instrument MCP Gateway
1. Extract tenant context from AuthKit JWT in middleware
2. Create root span with tenant attributes
3. Propagate trace context to downstream services via headers
### Phase 3: Instrument Cloud Service
1. Continue trace from MCP gateway
2. Add operation-specific attributes (provisioning events)
3. Instrument ARQ worker jobs for async operations
4. Track Fly.io API calls and latency
### Phase 4: Instrument API Service
1. Extract tenant context from JWT
2. Add machine-specific metadata (instance ID, region)
3. Instrument database operations with SQLAlchemy
4. Track MCP protocol operations
### Phase 5: Configure and Deploy
1. Add OTLP configuration to `.env.example` and `.env.example.secrets`
2. Set Fly.io secrets for production deployment
3. Update Dockerfiles to use `opentelemetry-instrument` wrapper
4. Deploy to development environment first for testing
## How to Evaluate
### Success Criteria
1. **End-to-end traces visible in Grafana Cloud** showing complete request flow
2. **Tenant filtering works** - Can filter traces by tenant_id to see all requests for a user
3. **Service maps accurate** - Grafana shows correct service dependencies
4. **Performance overhead < 5%** - Minimal latency impact from instrumentation
5. **Error correlation** - Can trace errors back to specific tenant and operation
### Testing Checklist
- [x] Single request creates connected trace across all services
- [x] Tenant attributes present on all spans
- [x] Background jobs (ARQ) appear in traces
- [x] Database queries show in trace timeline
- [x] HTTP calls to Fly.io API tracked
- [x] Traces exported successfully to Grafana Cloud
- [x] Can search traces by tenant_id in Grafana
- [x] Service dependency graph shows correct flow
### Monitoring Success
- All services reporting traces to Grafana Cloud
- No OTLP export errors in logs
- Trace sampling working correctly (if implemented)
- Resource usage acceptable (CPU/memory)
## Dependencies
- Grafana Cloud account with OTLP endpoint configured
- OpenTelemetry Python SDK v1.29.0+
- FastAPI instrumentation compatibility
- Network access from Fly.io to Grafana Cloud
## Implementation Assignment
**Recommended Agent**: python-developer
- Requires Python/FastAPI expertise
- Needs understanding of distributed systems
- Must implement middleware and context propagation
- Should understand OpenTelemetry SDK and instrumentation
## Follow-up Tasks
### Enhanced Log Correlation
While basic trace-to-log correlation works automatically via OpenTelemetry logging instrumentation, consider adding structured logging for improved log filtering:
1. **Structured Logging Context**: Add `logger.bind()` calls to inject tenant/user context directly into log records
2. **Custom Loguru Formatter**: Extract OpenTelemetry span attributes for better log readability
3. **Direct Log Filtering**: Enable searching logs directly by tenant_id, workflow_id without going through traces
This would complement the existing automatic trace correlation and provide better log search capabilities.
## Alternative Solution: Logfire
After implementing OpenTelemetry with Grafana Cloud, we discovered limitations in the observability experience:
- Traces work but lack useful context without correlated logs
- Setting up log correlation with Grafana is complex and requires additional infrastructure
- The developer experience for Python observability is suboptimal
### Logfire Evaluation
**Pydantic Logfire** offers a compelling alternative that addresses your specific requirements:
#### Core Requirements Match
- ✅ **User Activity Tracking**: Automatic request tracing with business context
- ✅ **Error Monitoring**: Built-in exception tracking with full context
- ✅ **Performance Metrics**: Automatic latency and performance monitoring
- ✅ **Request Tracing**: Native distributed tracing across services
- ✅ **Log Correlation**: Seamless trace-to-log correlation without setup
#### Key Advantages
1. **Python-First Design**: Built specifically for Python/FastAPI applications by the Pydantic team
2. **Simple Integration**: `pip install logfire` + `logfire.configure()` vs complex OTLP setup
3. **Automatic Correlation**: Logs automatically include trace context without manual configuration
4. **Real-time SQL Interface**: Query spans and logs using SQL with auto-completion
5. **Better Developer UX**: Purpose-built observability UI vs generic Grafana dashboards
6. **Loguru Integration**: `logger.configure(handlers=[logfire.loguru_handler()])` maintains existing logging
#### Pricing Assessment
- **Free Tier**: 10M spans/month (suitable for development and small production workloads)
- **Transparent Pricing**: $1 per million spans/metrics after free tier
- **No Hidden Costs**: No per-host fees, only usage-based metering
- **Production Ready**: Recently exited beta, enterprise features available
#### Migration Path
The existing OpenTelemetry instrumentation is compatible - Logfire uses OpenTelemetry under the hood, so the current spans and attributes would work unchanged.
### Recommendation
**Consider migrating to Logfire** for the following reasons:
1. It directly addresses the "next to useless" traces problem by providing integrated logs
2. Dramatically simpler setup and maintenance compared to Grafana Cloud + custom log correlation
3. Better ROI on observability investment with purpose-built Python tooling
4. Free tier sufficient for current development needs with clear scaling path
The current Grafana Cloud implementation provides a solid foundation and could remain as a backup/export target, while Logfire becomes the primary observability platform.
## Status
**Created**: 2024-01-28
**Status**: Completed (OpenTelemetry + Grafana Cloud)
**Next Phase**: Evaluate Logfire migration
**Priority**: High - Critical for production observability
@@ -0,0 +1,917 @@
---
title: 'SPEC-13: CLI Authentication with Subscription Validation'
type: spec
permalink: specs/spec-12-cli-auth-subscription-validation
tags:
- authentication
- security
- cli
- subscription
status: draft
created: 2025-10-02
---
# SPEC-13: CLI Authentication with Subscription Validation
## Why
The Basic Memory Cloud CLI currently has a security gap in authentication that allows unauthorized access:
**Current Web Flow (Secure)**:
1. User signs up via WorkOS AuthKit
2. User creates Polar subscription
3. Web app validates subscription before calling `POST /tenants/setup`
4. Tenant provisioned only after subscription validation ✅
**Current CLI Flow (Insecure)**:
1. User signs up via WorkOS AuthKit (OAuth device flow)
2. User runs `bm cloud login`
3. CLI receives JWT token from WorkOS
4. CLI can access all cloud endpoints without subscription check ❌
**Problem**: Anyone can sign up with WorkOS and immediately access cloud infrastructure via CLI without having an active Polar subscription. This creates:
- Revenue loss (free resource consumption)
- Security risk (unauthorized data access)
- Support burden (users accessing features they haven't paid for)
**Root Cause**: The CLI authentication flow validates JWT tokens but doesn't verify subscription status before granting access to cloud resources.
## What
Add subscription validation to authentication flow to ensure only users with active Polar subscriptions can access cloud resources across all access methods (CLI, MCP, Web App, Direct API).
**Affected Components**:
### basic-memory-cloud (Cloud Service)
- `apps/cloud/src/basic_memory_cloud/deps.py` - Add subscription validation dependency
- `apps/cloud/src/basic_memory_cloud/services/subscription_service.py` - Add subscription check method
- `apps/cloud/src/basic_memory_cloud/api/tenant_mount.py` - Protect mount endpoints
- `apps/cloud/src/basic_memory_cloud/api/proxy.py` - Protect proxy endpoints
### basic-memory (CLI)
- `src/basic_memory/cli/commands/cloud/core_commands.py` - Handle 403 errors
- `src/basic_memory/cli/commands/cloud/api_client.py` - Parse subscription errors
- `docs/cloud-cli.md` - Document subscription requirement
**Endpoints to Protect**:
- `GET /tenant/mount/info` - Used by CLI bisync setup
- `POST /tenant/mount/credentials` - Used by CLI bisync credentials
- `GET /proxy/{path:path}` - Used by Web App, MCP tools, CLI tools, Direct API
- All other `/proxy/*` endpoints - Centralized access point for all user operations
## Complete Authentication Flow Analysis
### Overview of All Access Flows
Basic Memory Cloud has **7 distinct authentication flows**. This spec closes subscription validation gaps in flows 2-4 and 6, which all converge on the `/proxy/*` endpoints.
### Flow 1: Polar Webhook → Registration ✅ SECURE
```
Polar webhook → POST /api/webhooks/polar
→ Validates Polar webhook signature
→ Creates/updates subscription in database
→ No direct user access - webhook only
```
**Auth**: Polar webhook signature validation
**Subscription Check**: N/A (webhook creates subscriptions)
**Status**: ✅ Secure - webhook validated, no user JWT involved
### Flow 2: Web App Login ❌ NEEDS FIX
```
User → apps/web (Vue.js/Nuxt)
→ WorkOS AuthKit magic link authentication
→ JWT stored in browser session
→ Web app calls /proxy/{project}/... endpoints (memory, directory, projects)
→ proxy.py validates JWT but does NOT check subscription
→ Access granted without subscription ❌
```
**Auth**: WorkOS JWT via `CurrentUserProfileHybridJwtDep`
**Subscription Check**: ❌ Missing
**Fixed By**: Task 1.4 (protect `/proxy/*` endpoints)
### Flow 3: MCP (Model Context Protocol) ❌ NEEDS FIX
```
AI Agent (Claude, Cursor, etc.) → https://mcp.basicmemory.com
→ AuthKit OAuth device flow
→ JWT stored in AI agent
→ MCP tools call {cloud_host}/proxy/{endpoint} with Authorization header
→ proxy.py validates JWT but does NOT check subscription
→ MCP tools can access all cloud resources without subscription ❌
```
**Auth**: AuthKit JWT via `CurrentUserProfileHybridJwtDep`
**Subscription Check**: ❌ Missing
**Fixed By**: Task 1.4 (protect `/proxy/*` endpoints)
### Flow 4: CLI Auth (basic-memory) ❌ NEEDS FIX
```
User → bm cloud login
→ AuthKit OAuth device flow
→ JWT stored in ~/.basic-memory/tokens.json
→ CLI calls:
- {cloud_host}/tenant/mount/info (for bisync setup)
- {cloud_host}/tenant/mount/credentials (for bisync credentials)
- {cloud_host}/proxy/{endpoint} (for all MCP tools)
→ tenant_mount.py and proxy.py validate JWT but do NOT check subscription
→ Access granted without subscription ❌
```
**Auth**: AuthKit JWT via `CurrentUserProfileHybridJwtDep`
**Subscription Check**: ❌ Missing
**Fixed By**: Task 1.3 (protect `/tenant/mount/*`) + Task 1.4 (protect `/proxy/*`)
### Flow 5: Cloud CLI (Admin Tasks) ✅ SECURE
```
Admin → python -m basic_memory_cloud.cli.tenant_cli
→ Uses CLIAuth with admin WorkOS OAuth client
→ Gets JWT token with admin org membership
→ Calls /tenants/* endpoints (create, list, delete tenants)
→ tenants.py validates JWT AND admin org membership via AdminUserHybridDep
→ Access granted only to admin organization members ✅
```
**Auth**: AuthKit JWT + Admin org validation via `AdminUserHybridDep`
**Subscription Check**: N/A (admins bypass subscription requirement)
**Status**: ✅ Secure - admin-only endpoints, separate from user flows
### Flow 6: Direct API Calls ❌ NEEDS FIX
```
Any HTTP client → {cloud_host}/proxy/{endpoint}
→ Sends Authorization: Bearer {jwt} header
→ proxy.py validates JWT but does NOT check subscription
→ Direct API access without subscription ❌
```
**Auth**: WorkOS or AuthKit JWT via `CurrentUserProfileHybridJwtDep`
**Subscription Check**: ❌ Missing
**Fixed By**: Task 1.4 (protect `/proxy/*` endpoints)
### Flow 7: Tenant API Instance (Internal) ✅ SECURE
```
/proxy/* → Tenant API (basic-memory-{tenant_id}.fly.dev)
→ Validates signed header from proxy (tenant_id + signature)
→ Direct external access will be disabled in production
→ Only accessible via /proxy endpoints
```
**Auth**: Signed header validation from proxy
**Subscription Check**: N/A (internal only, validated at proxy layer)
**Status**: ✅ Secure - validates proxy signature, not directly accessible
### Authentication Flow Summary Matrix
| Flow | Access Method | Current Auth | Subscription Check | Fixed By SPEC-13 |
|------|---------------|--------------|-------------------|------------------|
| 1. Polar Webhook | Polar webhook → `/api/webhooks/polar` | Polar signature | N/A (webhook) | N/A |
| 2. Web App | Browser → `/proxy/*` | WorkOS JWT ✅ | ❌ Missing | ✅ Task 1.4 |
| 3. MCP | AI Agent → `/proxy/*` | AuthKit JWT ✅ | ❌ Missing | ✅ Task 1.4 |
| 4. CLI | `bm cloud``/tenant/mount/*` + `/proxy/*` | AuthKit JWT ✅ | ❌ Missing | ✅ Task 1.3 + 1.4 |
| 5. Cloud CLI (Admin) | `tenant_cli``/tenants/*` | AuthKit JWT ✅ + Admin org | N/A (admin) | N/A (admin bypass) |
| 6. Direct API | HTTP client → `/proxy/*` | WorkOS/AuthKit JWT ✅ | ❌ Missing | ✅ Task 1.4 |
| 7. Tenant API | Proxy → tenant instance | Proxy signature ✅ | N/A (internal) | N/A |
### Key Insights
1. **Single Point of Failure**: All user access (Web, MCP, CLI, Direct API) converges on `/proxy/*` endpoints
2. **Centralized Fix**: Protecting `/proxy/*` with subscription validation closes gaps in flows 2, 3, 4, and 6 simultaneously
3. **Admin Bypass**: Cloud CLI admin tasks use separate `/tenants/*` endpoints with admin-only access (no subscription needed)
4. **Defense in Depth**: `/tenant/mount/*` endpoints also protected for CLI bisync operations
### Architecture Benefits
The `/proxy` layer serves as the **single centralized authorization point** for all user access:
- ✅ One place to validate JWT tokens
- ✅ One place to check subscription status
- ✅ One place to handle tenant routing
- ✅ Protects Web App, MCP, CLI, and Direct API simultaneously
This architecture makes the fix comprehensive and maintainable.
## How (High Level)
### Option A: Database Subscription Check (Recommended)
**Approach**: Add FastAPI dependency that validates subscription status from database before allowing access.
**Implementation**:
1. **Create Subscription Validation Dependency** (`deps.py`)
```python
async def get_authorized_cli_user_profile(
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)],
session: DatabaseSessionDep,
user_profile_repo: UserProfileRepositoryDep,
subscription_service: SubscriptionServiceDep,
) -> UserProfile:
"""
Hybrid authentication with subscription validation for CLI access.
Validates JWT (WorkOS or AuthKit) and checks for active subscription.
Returns UserProfile if both checks pass.
"""
# Try WorkOS JWT first (faster validation path)
try:
user_context = await validate_workos_jwt(credentials.credentials)
except HTTPException:
# Fall back to AuthKit JWT validation
try:
user_context = await validate_authkit_jwt(credentials.credentials)
except HTTPException as e:
raise HTTPException(
status_code=401,
detail="Invalid JWT token. Authentication required.",
) from e
# Check subscription status
has_subscription = await subscription_service.check_user_has_active_subscription(
session, user_context.workos_user_id
)
if not has_subscription:
raise HTTPException(
status_code=403,
detail={
"error": "subscription_required",
"message": "Active subscription required for CLI access",
"subscribe_url": "https://basicmemory.com/subscribe"
}
)
# Look up and return user profile
user_profile = await user_profile_repo.get_user_profile_by_workos_user_id(
session, user_context.workos_user_id
)
if not user_profile:
raise HTTPException(401, detail="User profile not found")
return user_profile
```
```python
AuthorizedCLIUserProfileDep = Annotated[UserProfile, Depends(get_authorized_cli_user_profile)]
```
2. **Add Subscription Check Method** (`subscription_service.py`)
```python
async def check_user_has_active_subscription(
self, session: AsyncSession, workos_user_id: str
) -> bool:
"""Check if user has active subscription."""
# Use existing repository method to get subscription by workos_user_id
# This joins UserProfile -> Subscription in a single query
subscription = await self.subscription_repository.get_subscription_by_workos_user_id(
session, workos_user_id
)
return subscription is not None and subscription.status == "active"
```
3. **Protect Endpoints** (Replace `CurrentUserProfileHybridJwtDep` with `AuthorizedCLIUserProfileDep`)
```python
# Before
@router.get("/mount/info")
async def get_mount_info(
user_profile: CurrentUserProfileHybridJwtDep,
session: DatabaseSessionDep,
):
tenant_id = user_profile.tenant_id
...
# After
@router.get("/mount/info")
async def get_mount_info(
user_profile: AuthorizedCLIUserProfileDep, # Now includes subscription check
session: DatabaseSessionDep,
):
tenant_id = user_profile.tenant_id # No changes needed to endpoint logic
...
```
4. **Update CLI Error Handling**
```python
# In core_commands.py login()
try:
success = await auth.login()
if success:
# Test subscription by calling protected endpoint
await make_api_request("GET", f"{host_url}/tenant/mount/info")
except CloudAPIError as e:
if e.status_code == 403 and e.detail.get("error") == "subscription_required":
console.print("[red]Subscription required[/red]")
console.print(f"Subscribe at: {e.detail['subscribe_url']}")
raise typer.Exit(1)
```
**Pros**:
- Simple to implement
- Fast (single database query)
- Clear error messages
- Works with existing subscription flow
**Cons**:
- Database is source of truth (could get out of sync with Polar)
- Adds one extra subscription lookup query per request (lightweight JOIN query)
### Option B: WorkOS Organizations
**Approach**: Add users to "beta-users" organization in WorkOS after subscription creation, validate org membership via JWT claims.
**Implementation**:
1. After Polar subscription webhook, add user to WorkOS org via API
2. Validate `org_id` claim in JWT matches authorized org
3. Use existing `get_admin_workos_jwt` pattern
**Pros**:
- WorkOS as single source of truth
- No database queries needed
- More secure (harder to bypass)
**Cons**:
- More complex (requires WorkOS API integration)
- Requires managing WorkOS org membership
- Less control over error messages
- Additional API calls during registration
### Recommendation
**Start with Option A (Database Check)** for:
- Faster implementation
- Clearer error messages
- Easier testing
- Existing subscription infrastructure
**Consider Option B later** if:
- Need tighter security
- Want to reduce database dependency
- Scale requires fewer database queries
## How to Evaluate
### Success Criteria
**1. Unauthorized Users Blocked**
- [ ] User without subscription cannot complete `bm cloud login`
- [ ] User without subscription receives clear error with subscribe link
- [ ] User without subscription cannot run `bm cloud setup`
- [ ] User without subscription cannot run `bm sync` in cloud mode
**2. Authorized Users Work**
- [ ] User with active subscription can login successfully
- [ ] User with active subscription can setup bisync
- [ ] User with active subscription can sync files
- [ ] User with active subscription can use all MCP tools via proxy
**3. Subscription State Changes**
- [ ] Expired subscription blocks access with clear error
- [ ] Renewed subscription immediately restores access
- [ ] Cancelled subscription blocks access after grace period
**4. Error Messages**
- [ ] 403 errors include "subscription_required" error code
- [ ] Error messages include subscribe URL
- [ ] CLI displays user-friendly messages
- [ ] Errors logged appropriately for debugging
**5. No Regressions**
- [ ] Web app login/subscription flow unaffected
- [ ] Admin endpoints still work (bypass check)
- [ ] Tenant provisioning workflow unchanged
- [ ] Performance not degraded
### Test Cases
**Manual Testing**:
```bash
# Test 1: Unauthorized user
1. Create new WorkOS account (no subscription)
2. Run `bm cloud login`
3. Verify: Login succeeds but shows subscription required error
4. Verify: Cannot run `bm cloud setup`
5. Verify: Clear error message with subscribe link
# Test 2: Authorized user
1. Use account with active Polar subscription
2. Run `bm cloud login`
3. Verify: Login succeeds without errors
4. Run `bm cloud setup`
5. Verify: Setup completes successfully
6. Run `bm sync`
7. Verify: Sync works normally
# Test 3: Subscription expiration
1. Use account with active subscription
2. Manually expire subscription in database
3. Run `bm cloud login`
4. Verify: Blocked with clear error
5. Renew subscription
6. Run `bm cloud login` again
7. Verify: Access restored
```
**Automated Tests**:
```python
# Test subscription validation dependency
async def test_authorized_user_allowed(
db_session,
user_profile_repo,
subscription_service,
mock_jwt_credentials
):
# Create user with active subscription
user_profile = await create_user_with_subscription(db_session, status="active")
# Mock JWT credentials for the user
credentials = mock_jwt_credentials(user_profile.workos_user_id)
# Should not raise exception
result = await get_authorized_cli_user_profile(
credentials, db_session, user_profile_repo, subscription_service
)
assert result.id == user_profile.id
assert result.workos_user_id == user_profile.workos_user_id
async def test_unauthorized_user_blocked(
db_session,
user_profile_repo,
subscription_service,
mock_jwt_credentials
):
# Create user without subscription
user_profile = await create_user_without_subscription(db_session)
credentials = mock_jwt_credentials(user_profile.workos_user_id)
# Should raise 403
with pytest.raises(HTTPException) as exc:
await get_authorized_cli_user_profile(
credentials, db_session, user_profile_repo, subscription_service
)
assert exc.value.status_code == 403
assert exc.value.detail["error"] == "subscription_required"
async def test_inactive_subscription_blocked(
db_session,
user_profile_repo,
subscription_service,
mock_jwt_credentials
):
# Create user with cancelled/inactive subscription
user_profile = await create_user_with_subscription(db_session, status="cancelled")
credentials = mock_jwt_credentials(user_profile.workos_user_id)
# Should raise 403
with pytest.raises(HTTPException) as exc:
await get_authorized_cli_user_profile(
credentials, db_session, user_profile_repo, subscription_service
)
assert exc.value.status_code == 403
assert exc.value.detail["error"] == "subscription_required"
```
## Implementation Tasks
### Phase 1: Cloud Service (basic-memory-cloud)
#### Task 1.1: Add subscription check method to SubscriptionService ✅
**File**: `apps/cloud/src/basic_memory_cloud/services/subscription_service.py`
- [x] Add method `check_subscription(session: AsyncSession, workos_user_id: str) -> bool`
- [x] Use existing `self.subscription_repository.get_subscription_by_workos_user_id(session, workos_user_id)`
- [x] Check both `status == "active"` AND `current_period_end >= now()`
- [x] Log both values when check fails
- [x] Add docstring explaining the method
- [x] Run `just typecheck` to verify types
**Actual implementation**:
```python
async def check_subscription(
self, session: AsyncSession, workos_user_id: str
) -> bool:
"""Check if user has active subscription with valid period."""
subscription = await self.subscription_repository.get_subscription_by_workos_user_id(
session, workos_user_id
)
if subscription is None:
return False
if subscription.status != "active":
logger.warning("Subscription inactive", workos_user_id=workos_user_id,
status=subscription.status, current_period_end=subscription.current_period_end)
return False
now = datetime.now(timezone.utc)
if subscription.current_period_end is None or subscription.current_period_end < now:
logger.warning("Subscription expired", workos_user_id=workos_user_id,
status=subscription.status, current_period_end=subscription.current_period_end)
return False
return True
```
#### Task 1.2: Add subscription validation dependency ✅
**File**: `apps/cloud/src/basic_memory_cloud/deps.py`
- [x] Import necessary types at top of file (if not already present)
- [x] Add `authorized_user_profile()` async function
- [x] Implement hybrid JWT validation (WorkOS first, AuthKit fallback)
- [x] Add subscription check using `subscription_service.check_subscription()`
- [x] Raise `HTTPException(403)` with structured error detail if no active subscription
- [x] Look up and return `UserProfile` after validation
- [x] Add `AuthorizedUserProfileDep` type annotation
- [x] Use `settings.subscription_url` from config (env var)
- [x] Run `just typecheck` to verify types
**Expected code**:
```python
async def get_authorized_cli_user_profile(
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)],
session: DatabaseSessionDep,
user_profile_repo: UserProfileRepositoryDep,
subscription_service: SubscriptionServiceDep,
) -> UserProfile:
"""
Hybrid authentication with subscription validation for CLI access.
Validates JWT (WorkOS or AuthKit) and checks for active subscription.
Returns UserProfile if both checks pass.
Raises:
HTTPException(401): Invalid JWT token
HTTPException(403): No active subscription
"""
# Try WorkOS JWT first (faster validation path)
try:
user_context = await validate_workos_jwt(credentials.credentials)
except HTTPException:
# Fall back to AuthKit JWT validation
try:
user_context = await validate_authkit_jwt(credentials.credentials)
except HTTPException as e:
raise HTTPException(
status_code=401,
detail="Invalid JWT token. Authentication required.",
) from e
# Check subscription status
has_subscription = await subscription_service.check_user_has_active_subscription(
session, user_context.workos_user_id
)
if not has_subscription:
logger.warning(
"CLI access denied: no active subscription",
workos_user_id=user_context.workos_user_id,
)
raise HTTPException(
status_code=403,
detail={
"error": "subscription_required",
"message": "Active subscription required for CLI access",
"subscribe_url": "https://basicmemory.com/subscribe"
}
)
# Look up and return user profile
user_profile = await user_profile_repo.get_user_profile_by_workos_user_id(
session, user_context.workos_user_id
)
if not user_profile:
logger.error(
"User profile not found after successful auth",
workos_user_id=user_context.workos_user_id,
)
raise HTTPException(401, detail="User profile not found")
logger.info(
"CLI access granted",
workos_user_id=user_context.workos_user_id,
user_profile_id=str(user_profile.id),
)
return user_profile
AuthorizedCLIUserProfileDep = Annotated[UserProfile, Depends(get_authorized_cli_user_profile)]
```
#### Task 1.3: Protect tenant mount endpoints ✅
**File**: `apps/cloud/src/basic_memory_cloud/api/tenant_mount.py`
- [x] Update import: add `AuthorizedUserProfileDep` from `..deps`
- [x] Replace `user_profile: CurrentUserProfileHybridJwtDep` with `user_profile: AuthorizedUserProfileDep` in:
- [x] `get_tenant_mount_info()` (line ~23)
- [x] `create_tenant_mount_credentials()` (line ~88)
- [x] `revoke_tenant_mount_credentials()` (line ~244)
- [x] `list_tenant_mount_credentials()` (line ~326)
- [x] Verify no other code changes needed (parameter name and usage stays the same)
- [x] Run `just typecheck` to verify types
#### Task 1.4: Protect proxy endpoints ✅
**File**: `apps/cloud/src/basic_memory_cloud/api/proxy.py`
- [x] Update import: add `AuthorizedUserProfileDep` from `..deps`
- [x] Replace `user_profile: CurrentUserProfileHybridJwtDep` with `user_profile: AuthorizedUserProfileDep` in:
- [x] `check_tenant_health()` (line ~21)
- [x] `proxy_to_tenant()` (line ~63)
- [x] Verify no other code changes needed (parameter name and usage stays the same)
- [x] Run `just typecheck` to verify types
**Why Keep /proxy Architecture:**
The proxy layer is valuable because it:
1. **Centralizes authorization** - Single place for JWT + subscription validation (closes both CLI and MCP auth gaps)
2. **Handles tenant routing** - Maps tenant_id → fly_app_name without exposing infrastructure details
3. **Abstracts infrastructure** - MCP and CLI don't need to know about Fly.io naming conventions
4. **Enables features** - Can add rate limiting, caching, request logging, etc. at proxy layer
5. **Supports both flows** - CLI tools and MCP tools both use /proxy endpoints
The extra HTTP hop is minimal (< 10ms) and worth it for architectural benefits.
**Performance Note:** Cloud app has Redis available - can cache subscription status to reduce database queries if needed. Initial implementation uses direct database query (simple, acceptable performance ~5-10ms).
#### Task 1.5: Add unit tests for subscription service
**File**: `apps/cloud/tests/services/test_subscription_service.py` (create if doesn't exist)
- [ ] Create test file if it doesn't exist
- [ ] Add test: `test_check_user_has_active_subscription_returns_true_for_active()`
- Create user with active subscription
- Call `check_user_has_active_subscription()`
- Assert returns `True`
- [ ] Add test: `test_check_user_has_active_subscription_returns_false_for_pending()`
- Create user with pending subscription
- Assert returns `False`
- [ ] Add test: `test_check_user_has_active_subscription_returns_false_for_cancelled()`
- Create user with cancelled subscription
- Assert returns `False`
- [ ] Add test: `test_check_user_has_active_subscription_returns_false_for_no_subscription()`
- Create user without subscription
- Assert returns `False`
- [ ] Run `just test` to verify tests pass
#### Task 1.6: Add integration tests for dependency
**File**: `apps/cloud/tests/test_deps.py` (create if doesn't exist)
- [ ] Create test file if it doesn't exist
- [ ] Add fixtures for mocking JWT credentials
- [ ] Add test: `test_authorized_cli_user_profile_with_active_subscription()`
- Mock valid JWT + active subscription
- Call dependency
- Assert returns UserProfile
- [ ] Add test: `test_authorized_cli_user_profile_without_subscription_raises_403()`
- Mock valid JWT + no subscription
- Assert raises HTTPException(403) with correct error detail
- [ ] Add test: `test_authorized_cli_user_profile_with_inactive_subscription_raises_403()`
- Mock valid JWT + cancelled subscription
- Assert raises HTTPException(403)
- [ ] Add test: `test_authorized_cli_user_profile_with_invalid_jwt_raises_401()`
- Mock invalid JWT
- Assert raises HTTPException(401)
- [ ] Run `just test` to verify tests pass
#### Task 1.7: Deploy and verify cloud service
- [ ] Run `just check` to verify all quality checks pass
- [ ] Commit changes with message: "feat: add subscription validation to CLI endpoints"
- [ ] Deploy to preview environment: `flyctl deploy --config apps/cloud/fly.toml`
- [ ] Test manually:
- [ ] Call `/tenant/mount/info` with valid JWT but no subscription → expect 403
- [ ] Call `/tenant/mount/info` with valid JWT and active subscription → expect 200
- [ ] Verify error response structure matches spec
### Phase 2: CLI (basic-memory)
#### Task 2.1: Review and understand CLI authentication flow
**Files**: `src/basic_memory/cli/commands/cloud/`
- [ ] Read `core_commands.py` to understand current login flow
- [ ] Read `api_client.py` to understand current error handling
- [ ] Identify where 403 errors should be caught
- [ ] Identify what error messages should be displayed
- [ ] Document current behavior in spec if needed
#### Task 2.2: Update API client error handling
**File**: `src/basic_memory/cli/commands/cloud/api_client.py`
- [ ] Add custom exception class `SubscriptionRequiredError` (or similar)
- [ ] Update HTTP error handling to parse 403 responses
- [ ] Extract `error`, `message`, and `subscribe_url` from error detail
- [ ] Raise specific exception for subscription_required errors
- [ ] Run `just typecheck` in basic-memory repo to verify types
#### Task 2.3: Update CLI login command error handling
**File**: `src/basic_memory/cli/commands/cloud/core_commands.py`
- [ ] Import the subscription error exception
- [ ] Wrap login flow with try/except for subscription errors
- [ ] Display user-friendly error message with rich console
- [ ] Show subscribe URL prominently
- [ ] Provide actionable next steps
- [ ] Run `just typecheck` to verify types
**Expected error handling**:
```python
try:
# Existing login logic
success = await auth.login()
if success:
# Test access to protected endpoint
await api_client.test_connection()
except SubscriptionRequiredError as e:
console.print("\n[red]✗ Subscription Required[/red]\n")
console.print(f"[yellow]{e.message}[/yellow]\n")
console.print(f"Subscribe at: [blue underline]{e.subscribe_url}[/blue underline]\n")
console.print("[dim]Once you have an active subscription, run [bold]bm cloud login[/bold] again.[/dim]")
raise typer.Exit(1)
```
#### Task 2.4: Update CLI tests
**File**: `tests/cli/test_cloud_commands.py`
- [ ] Add test: `test_login_without_subscription_shows_error()`
- Mock 403 subscription_required response
- Call login command
- Assert error message displayed
- Assert subscribe URL shown
- [ ] Add test: `test_login_with_subscription_succeeds()`
- Mock successful authentication + subscription check
- Call login command
- Assert success message
- [ ] Run `just test` to verify tests pass
#### Task 2.5: Update CLI documentation
**File**: `docs/cloud-cli.md` (in basic-memory-docs repo)
- [ ] Add "Prerequisites" section if not present
- [ ] Document subscription requirement
- [ ] Add "Troubleshooting" section
- [ ] Document "Subscription Required" error
- [ ] Provide subscribe URL
- [ ] Add FAQ entry about subscription errors
- [ ] Build docs locally to verify formatting
### Phase 3: End-to-End Testing
#### Task 3.1: Create test user accounts
**Prerequisites**: Access to WorkOS admin and database
- [ ] Create test user WITHOUT subscription:
- [ ] Sign up via WorkOS AuthKit
- [ ] Get workos_user_id from database
- [ ] Verify no subscription record exists
- [ ] Save credentials for testing
- [ ] Create test user WITH active subscription:
- [ ] Sign up via WorkOS AuthKit
- [ ] Create subscription via Polar or dev endpoint
- [ ] Verify subscription.status = "active" in database
- [ ] Save credentials for testing
#### Task 3.2: Manual testing - User without subscription
**Environment**: Preview/staging deployment
- [ ] Run `bm cloud login` with no-subscription user
- [ ] Verify: Login shows "Subscription Required" error
- [ ] Verify: Subscribe URL is displayed
- [ ] Verify: Cannot run `bm cloud setup`
- [ ] Verify: Cannot call `/tenant/mount/info` directly via curl
- [ ] Document any issues found
#### Task 3.3: Manual testing - User with active subscription
**Environment**: Preview/staging deployment
- [ ] Run `bm cloud login` with active-subscription user
- [ ] Verify: Login succeeds without errors
- [ ] Verify: Can run `bm cloud setup`
- [ ] Verify: Can call `/tenant/mount/info` successfully
- [ ] Verify: Can call `/proxy/*` endpoints successfully
- [ ] Document any issues found
#### Task 3.4: Test subscription state transitions
**Environment**: Preview/staging deployment + database access
- [ ] Start with active subscription user
- [ ] Verify: All operations work
- [ ] Update subscription.status to "cancelled" in database
- [ ] Verify: Login now shows "Subscription Required" error
- [ ] Verify: Existing tokens are rejected with 403
- [ ] Update subscription.status back to "active"
- [ ] Verify: Access restored immediately
- [ ] Document any issues found
#### Task 3.5: Integration test suite
**File**: `apps/cloud/tests/integration/test_cli_subscription_flow.py` (create if doesn't exist)
- [ ] Create integration test file
- [ ] Add test: `test_cli_flow_without_subscription()`
- Simulate full CLI flow without subscription
- Assert 403 at appropriate points
- [ ] Add test: `test_cli_flow_with_active_subscription()`
- Simulate full CLI flow with active subscription
- Assert all operations succeed
- [ ] Add test: `test_subscription_expiration_blocks_access()`
- Start with active subscription
- Change status to cancelled
- Assert access denied
- [ ] Run tests in CI/CD pipeline
- [ ] Document test coverage
#### Task 3.6: Load/performance testing (optional)
**Environment**: Staging environment
- [ ] Test subscription check performance under load
- [ ] Measure latency added by subscription check
- [ ] Verify database query performance
- [ ] Document any performance concerns
- [ ] Optimize if needed
## Implementation Summary Checklist
Use this high-level checklist to track overall progress:
### Phase 1: Cloud Service 🔄
- [x] Add subscription check method to SubscriptionService
- [x] Add subscription validation dependency to deps.py
- [x] Add subscription_url config (env var)
- [x] Protect tenant mount endpoints (4 endpoints)
- [x] Protect proxy endpoints (2 endpoints)
- [ ] Add unit tests for subscription service
- [ ] Add integration tests for dependency
- [ ] Deploy and verify cloud service
### Phase 2: CLI Updates 🔄
- [ ] Review CLI authentication flow
- [ ] Update API client error handling
- [ ] Update CLI login command error handling
- [ ] Add CLI tests
- [ ] Update CLI documentation
### Phase 3: End-to-End Testing 🧪
- [ ] Create test user accounts
- [ ] Manual testing - user without subscription
- [ ] Manual testing - user with active subscription
- [ ] Test subscription state transitions
- [ ] Integration test suite
- [ ] Load/performance testing (optional)
## Questions to Resolve
### Resolved ✅
1. **Admin Access**
- ✅ **Decision**: Admin users bypass subscription check
- **Rationale**: Admin endpoints already use `AdminUserHybridDep`, which is separate from CLI user endpoints
- **Implementation**: No changes needed to admin endpoints
2. **Subscription Check Implementation**
- ✅ **Decision**: Use Option A (Database Check)
- **Rationale**: Simpler, faster to implement, works with existing infrastructure
- **Implementation**: Single JOIN query via `get_subscription_by_workos_user_id()`
3. **Dependency Return Type**
- ✅ **Decision**: Return `UserProfile` (not `UserContext`)
- **Rationale**: Drop-in compatibility with existing endpoints, no refactoring needed
- **Implementation**: `AuthorizedCLIUserProfileDep` returns `UserProfile`
### To Be Resolved ⏳
1. **Subscription Check Frequency**
- **Options**:
- Check on every API call (slower, more secure) ✅ **RECOMMENDED**
- Cache subscription status (faster, risk of stale data)
- Check only on login/setup (fast, but allows expired subscriptions temporarily)
- **Recommendation**: Check on every call via dependency injection (simple, secure, acceptable performance)
- **Impact**: ~5-10ms per request (single indexed JOIN query)
2. **Grace Period**
- **Options**:
- No grace period - immediate block when status != "active" ✅ **RECOMMENDED**
- 7-day grace period after period_end
- 14-day grace period after period_end
- **Recommendation**: No grace period initially, add later if needed based on customer feedback
- **Implementation**: Check `subscription.status == "active"` only (ignore period_end initially)
3. **Subscription Expiration Handling**
- **Question**: Should we check `current_period_end < now()` in addition to `status == "active"`?
- **Options**:
- Only check status field (rely on Polar webhooks to update status) ✅ **RECOMMENDED**
- Check both status and current_period_end (more defensive)
- **Recommendation**: Only check status field, assume Polar webhooks keep it current
- **Risk**: If webhooks fail, expired subscriptions might retain access until webhook succeeds
4. **Subscribe URL**
- **Question**: What's the actual subscription URL?
- **Current**: Spec uses `https://basicmemory.com/subscribe`
- **Action Required**: Verify correct URL before implementation
5. **Dev Mode / Testing Bypass**
- **Question**: Support bypass for development/testing?
- **Options**:
- Environment variable: `DISABLE_SUBSCRIPTION_CHECK=true`
- Always enforce (more realistic testing) ✅ **RECOMMENDED**
- **Recommendation**: No bypass - use test users with real subscriptions for realistic testing
- **Implementation**: Create dev endpoint to activate subscriptions for testing
## Related Specs
- SPEC-9: Multi-Project Bidirectional Sync Architecture (CLI affected by this change)
- SPEC-8: TigrisFS Integration (Mount endpoints protected)
## Notes
- This spec prioritizes security over convenience - better to block unauthorized access than risk revenue loss
- Clear error messages are critical - users should understand why they're blocked and how to resolve it
- Consider adding telemetry to track subscription_required errors for monitoring signup conversion
@@ -0,0 +1,210 @@
---
title: 'SPEC-14: Cloud Git Versioning & GitHub Backup'
type: spec
permalink: specs/spec-14-cloud-git-versioning
tags:
- git
- github
- backup
- versioning
- cloud
related:
- specs/spec-9-multi-project-bisync
- specs/spec-9-follow-ups-conflict-sync-and-observability
status: deferred
---
# SPEC-14: Cloud Git Versioning & GitHub Backup
**Status: DEFERRED** - Postponed until multi-user/teams feature development. Using S3 versioning (SPEC-9.1) for v1 instead.
## Why Deferred
**Original goals can be met with simpler solutions:**
- Version history → **S3 bucket versioning** (automatic, zero config)
- Offsite backup → **Tigris global replication** (built-in)
- Restore capability → **S3 version restore** (`bm cloud restore --version-id`)
- Collaboration → **Deferred to teams/multi-user feature** (not v1 requirement)
**Complexity vs value trade-off:**
- Git integration adds: committer service, puller service, webhooks, LFS, merge conflicts
- Risk: Loop detection between Git ↔ rclone bisync ↔ local edits
- S3 versioning gives 80% of value with 5% of complexity
**When to revisit:**
- Teams/multi-user features (PR-based collaboration workflow)
- User requests for commit messages and branch-based workflows
- Need for fine-grained audit trail beyond S3 object metadata
---
## Original Specification (for reference)
## Why
Early access users want **transparent version history**, easy **offsite backup**, and a familiar **restore/branching** workflow. Git/GitHub integration would provide:
- Auditable history of every change (who/when/why)
- Branches/PRs for review and collaboration
- Offsite private backup under the user's control
- Escape hatch: users can always `git clone` their knowledge base
**Note:** These goals are now addressed via S3 versioning (SPEC-9.1) for single-user use case.
## Goals
- **Transparent**: Users keep using Basic Memory; Git runs behind the scenes.
- **Private**: Push to a **private GitHub repo** that the user owns (or tenant org).
- **Reliable**: No data loss, deterministic mapping of filesystem ↔ Git.
- **Composable**: Plays nicely with SPEC9 bisync and upcoming conflict features (SPEC9 FollowUps).
**NonGoals (for v1):**
- Finegrained perfile encryption in Git history (can be layered later).
- Large media optimization beyond Git LFS defaults.
## User Stories
1. *As a user*, I connect my GitHub and choose a private backup repo.
2. *As a user*, every change I make in cloud (or via bisync) is **committed** and **pushed** automatically.
3. *As a user*, I can **restore** a file/folder/project to a prior version.
4. *As a power user*, I can **git pull/push** directly to collaborate outside the app.
5. *As an admin*, I can enforce repo ownership (tenant org) and leastprivilege scopes.
## Scope
- **In scope:** Full repo backup of `/app/data/` (all projects) with optional selective subpaths.
- **Out of scope (v1):** Partial shallow mirrors; encrypted Git; crossprovider SCM (GitLab/Bitbucket).
## Architecture
### Topology
- **Authoritative working tree**: `/app/data/` (bucket mount) remains the source of truth (SPEC9).
- **Bare repo** lives alongside: `/app/git/${tenant}/knowledge.git` (serverside).
- **Mirror remote**: `github.com/<owner>/<repo>.git` (private).
```mermaid
flowchart LR
A[/Users & Agents/] -->|writes/edits| B[/app/data/]
B -->|file events| C[Committer Service]
C -->|git commit| D[(Bare Repo)]
D -->|push| E[(GitHub Private Repo)]
E -->|webhook (push)| F[Puller Service]
F -->|git pull/merge| D
D -->|checkout/merge| B
```
### Services
- **Committer Service** (daemon):
- Watches `/app/data/` for changes (inotify/poll)
- Batches changes (debounce e.g. 25s)
- Writes `.bmmeta` (if present) into commit message trailer (see FollowUps)
- `git add -A && git commit -m "chore(sync): <summary>
BM-Meta: <json>"`
- Periodic `git push` to GitHub mirror (configurable interval)
- **Puller Service** (webhook target):
- Receives GitHub webhook (push) → `git fetch`
- **Fastforward** merges to `main` only; reject nonFF unless policy allows
- Applies changes back to `/app/data/` via clean checkout
- Emits sync events for Basic Memory indexers
### Auth & Security
- **GitHub App** (recommended): minimal scopes: `contents:read/write`, `metadata:read`, webhook.
- Tenantscoped installation; repo created in user account or tenant org.
- Tokens stored in KMS/secret manager; rotated automatically.
- Optional policy: allow only **FF merges** on `main`; nonFF requires PR.
### Repo Layout
- **Monorepo** (default): one repo per tenant mirrors `/app/data/` with subfolders per project.
- Optional multirepo mode (later): one repo per project.
### File Handling
- Honor `.gitignore` generated from `.bmignore.rclone` + BM defaults (cache, temp, state).
- **Git LFS** for large binaries (images, media) — auto track by extension/size threshold.
- Normalize newline + Unicode (aligns with FollowUps).
### Conflict Model
- **Primary concurrency**: SPEC9 FollowUps (`.bmmeta`, conflict copies) stays the first line of defense.
- **Git merges** are a **secondary** mechanism:
- Server only automerges **text** conflicts when trivial (FF or clean 3way).
- Otherwise, create `name (conflict from <branch>, <ts>).md` and surface via events.
### Data Flow vs Bisync
- Bisync (rclone) continues between local sync dir ↔ bucket.
- Git sits **cloudside** between bucket and GitHub.
- On **pull** from GitHub → files written to `/app/data/` → picked up by indexers & eventually by bisync back to users.
## CLI & UX
New commands (cloud mode):
- `bm cloud git connect` — Launch GitHub App installation; create private repo; store installation id.
- `bm cloud git status` — Show connected repo, last push time, last webhook delivery, pending commits.
- `bm cloud git push` — Manual push (rarely needed).
- `bm cloud git pull` — Manual pull/FF (admin only by default).
- `bm cloud snapshot -m "message"` — Create a tagged pointintime snapshot (git tag).
- `bm restore <path> --to <commit|tag>` — Restore file/folder/project to prior version.
Settings:
- `bm config set git.autoPushInterval=5s`
- `bm config set git.lfs.sizeThreshold=10MB`
- `bm config set git.allowNonFF=false`
## Migration & Backfill
- On connect, if repo empty: initial commit of entire `/app/data/`.
- If repo has content: require **onetime import** path (clone to staging, reconcile, choose direction).
## Edge Cases
- Massive deletes: gated by SPEC9 `max_delete` **and** Git prepush hook checks.
- Case changes and rename detection: rely on git rename heuristics + FollowUps move hints.
- Secrets: default ignore common secret patterns; allow custom deny list.
## Telemetry & Observability
- Emit `git_commit`, `git_push`, `git_pull`, `git_conflict` events with correlation IDs.
- `bm sync --report` extended with Git stats (commit count, delta bytes, push latency).
## Phased Plan
### Phase 0 — Prototype (1 sprint)
- Server: bare repo init + simple committer (batch every 10s) + manual GitHub token.
- CLI: `bm cloud git connect --token <PAT>` (devonly)
- Success: edits in `/app/data/` appear in GitHub within 30s.
### Phase 1 — GitHub App & Webhooks (12 sprints)
- Switch to GitHub App installs; create private repo; store installation id.
- Committer hardened (debounce 25s, backoff, retries).
- Puller service with webhook → FF merge → checkout to `/app/data/`.
- LFS autotrack + `.gitignore` generation.
- CLI surfaces status + logs.
### Phase 2 — Restore & Snapshots (1 sprint)
- `bm restore` for file/folder/project with dryrun.
- `bm cloud snapshot` tags + list/inspect.
- Policy: PRonly nonFF, admin override.
### Phase 3 — Selective & MultiRepo (nicetohave)
- Include/exclude projects; optional perproject repos.
- Advanced policies (branch protections, required reviews).
## Acceptance Criteria
- Changes to `/app/data/` are committed and pushed automatically within configurable interval (default ≤5s).
- GitHub webhook pull results in updated files in `/app/data/` (FFonly by default).
- LFS configured and functioning; large files don't bloat history.
- `bm cloud git status` shows connected repo and last push/pull times.
- `bm restore` restores a file/folder to a prior commit with a clear audit trail.
- Endtoend works alongside SPEC9 bisync without loops or data loss.
## Risks & Mitigations
- **Loop risk (Git ↔ Bisync)**: Writes to `/app/data/` → bisync → local → user edits → back again. *Mitigation*: Debounce, commit squashing, idempotent `.bmmeta` versioning, and watch exclusion windows during pull.
- **Repo bloat**: Lots of binary churn. *Mitigation*: default LFS, size threshold, optional mediaonly repo later.
- **Security**: Token leakage. *Mitigation*: GitHub App with shortlived tokens, KMS storage, scoped permissions.
- **Merge complexity**: Nontrivial conflicts. *Mitigation*: prefer FF; otherwise conflict copies + events; require PR for nonFF.
## Open Questions
- Do we default to **monorepo** per tenant, or offer projectperrepo at connect time?
- Should `restore` write to a branch and open a PR, or directly modify `main`?
- How do we expose Git history in UI (timeline view) without users dropping to CLI?
## Appendix: Sample Config
```json
{
"git": {
"enabled": true,
"repo": "https://github.com/<owner>/<repo>.git",
"autoPushInterval": "5s",
"allowNonFF": false,
"lfs": { "sizeThreshold": 10485760 }
}
}
```
@@ -0,0 +1,210 @@
---
title: 'SPEC-14: Cloud Git Versioning & GitHub Backup'
type: spec
permalink: specs/spec-14-cloud-git-versioning
tags:
- git
- github
- backup
- versioning
- cloud
related:
- specs/spec-9-multi-project-bisync
- specs/spec-9-follow-ups-conflict-sync-and-observability
status: deferred
---
# SPEC-14: Cloud Git Versioning & GitHub Backup
**Status: DEFERRED** - Postponed until multi-user/teams feature development. Using S3 versioning (SPEC-9.1) for v1 instead.
## Why Deferred
**Original goals can be met with simpler solutions:**
- Version history → **S3 bucket versioning** (automatic, zero config)
- Offsite backup → **Tigris global replication** (built-in)
- Restore capability → **S3 version restore** (`bm cloud restore --version-id`)
- Collaboration → **Deferred to teams/multi-user feature** (not v1 requirement)
**Complexity vs value trade-off:**
- Git integration adds: committer service, puller service, webhooks, LFS, merge conflicts
- Risk: Loop detection between Git ↔ rclone bisync ↔ local edits
- S3 versioning gives 80% of value with 5% of complexity
**When to revisit:**
- Teams/multi-user features (PR-based collaboration workflow)
- User requests for commit messages and branch-based workflows
- Need for fine-grained audit trail beyond S3 object metadata
---
## Original Specification (for reference)
## Why
Early access users want **transparent version history**, easy **offsite backup**, and a familiar **restore/branching** workflow. Git/GitHub integration would provide:
- Auditable history of every change (who/when/why)
- Branches/PRs for review and collaboration
- Offsite private backup under the user's control
- Escape hatch: users can always `git clone` their knowledge base
**Note:** These goals are now addressed via S3 versioning (SPEC-9.1) for single-user use case.
## Goals
- **Transparent**: Users keep using Basic Memory; Git runs behind the scenes.
- **Private**: Push to a **private GitHub repo** that the user owns (or tenant org).
- **Reliable**: No data loss, deterministic mapping of filesystem ↔ Git.
- **Composable**: Plays nicely with SPEC9 bisync and upcoming conflict features (SPEC9 FollowUps).
**NonGoals (for v1):**
- Finegrained perfile encryption in Git history (can be layered later).
- Large media optimization beyond Git LFS defaults.
## User Stories
1. *As a user*, I connect my GitHub and choose a private backup repo.
2. *As a user*, every change I make in cloud (or via bisync) is **committed** and **pushed** automatically.
3. *As a user*, I can **restore** a file/folder/project to a prior version.
4. *As a power user*, I can **git pull/push** directly to collaborate outside the app.
5. *As an admin*, I can enforce repo ownership (tenant org) and leastprivilege scopes.
## Scope
- **In scope:** Full repo backup of `/app/data/` (all projects) with optional selective subpaths.
- **Out of scope (v1):** Partial shallow mirrors; encrypted Git; crossprovider SCM (GitLab/Bitbucket).
## Architecture
### Topology
- **Authoritative working tree**: `/app/data/` (bucket mount) remains the source of truth (SPEC9).
- **Bare repo** lives alongside: `/app/git/${tenant}/knowledge.git` (serverside).
- **Mirror remote**: `github.com/<owner>/<repo>.git` (private).
```mermaid
flowchart LR
A[/Users & Agents/] -->|writes/edits| B[/app/data/]
B -->|file events| C[Committer Service]
C -->|git commit| D[(Bare Repo)]
D -->|push| E[(GitHub Private Repo)]
E -->|webhook (push)| F[Puller Service]
F -->|git pull/merge| D
D -->|checkout/merge| B
```
### Services
- **Committer Service** (daemon):
- Watches `/app/data/` for changes (inotify/poll)
- Batches changes (debounce e.g. 25s)
- Writes `.bmmeta` (if present) into commit message trailer (see FollowUps)
- `git add -A && git commit -m "chore(sync): <summary>
BM-Meta: <json>"`
- Periodic `git push` to GitHub mirror (configurable interval)
- **Puller Service** (webhook target):
- Receives GitHub webhook (push) → `git fetch`
- **Fastforward** merges to `main` only; reject nonFF unless policy allows
- Applies changes back to `/app/data/` via clean checkout
- Emits sync events for Basic Memory indexers
### Auth & Security
- **GitHub App** (recommended): minimal scopes: `contents:read/write`, `metadata:read`, webhook.
- Tenantscoped installation; repo created in user account or tenant org.
- Tokens stored in KMS/secret manager; rotated automatically.
- Optional policy: allow only **FF merges** on `main`; nonFF requires PR.
### Repo Layout
- **Monorepo** (default): one repo per tenant mirrors `/app/data/` with subfolders per project.
- Optional multirepo mode (later): one repo per project.
### File Handling
- Honor `.gitignore` generated from `.bmignore.rclone` + BM defaults (cache, temp, state).
- **Git LFS** for large binaries (images, media) — auto track by extension/size threshold.
- Normalize newline + Unicode (aligns with FollowUps).
### Conflict Model
- **Primary concurrency**: SPEC9 FollowUps (`.bmmeta`, conflict copies) stays the first line of defense.
- **Git merges** are a **secondary** mechanism:
- Server only automerges **text** conflicts when trivial (FF or clean 3way).
- Otherwise, create `name (conflict from <branch>, <ts>).md` and surface via events.
### Data Flow vs Bisync
- Bisync (rclone) continues between local sync dir ↔ bucket.
- Git sits **cloudside** between bucket and GitHub.
- On **pull** from GitHub → files written to `/app/data/` → picked up by indexers & eventually by bisync back to users.
## CLI & UX
New commands (cloud mode):
- `bm cloud git connect` — Launch GitHub App installation; create private repo; store installation id.
- `bm cloud git status` — Show connected repo, last push time, last webhook delivery, pending commits.
- `bm cloud git push` — Manual push (rarely needed).
- `bm cloud git pull` — Manual pull/FF (admin only by default).
- `bm cloud snapshot -m "message"` — Create a tagged pointintime snapshot (git tag).
- `bm restore <path> --to <commit|tag>` — Restore file/folder/project to prior version.
Settings:
- `bm config set git.autoPushInterval=5s`
- `bm config set git.lfs.sizeThreshold=10MB`
- `bm config set git.allowNonFF=false`
## Migration & Backfill
- On connect, if repo empty: initial commit of entire `/app/data/`.
- If repo has content: require **onetime import** path (clone to staging, reconcile, choose direction).
## Edge Cases
- Massive deletes: gated by SPEC9 `max_delete` **and** Git prepush hook checks.
- Case changes and rename detection: rely on git rename heuristics + FollowUps move hints.
- Secrets: default ignore common secret patterns; allow custom deny list.
## Telemetry & Observability
- Emit `git_commit`, `git_push`, `git_pull`, `git_conflict` events with correlation IDs.
- `bm sync --report` extended with Git stats (commit count, delta bytes, push latency).
## Phased Plan
### Phase 0 — Prototype (1 sprint)
- Server: bare repo init + simple committer (batch every 10s) + manual GitHub token.
- CLI: `bm cloud git connect --token <PAT>` (devonly)
- Success: edits in `/app/data/` appear in GitHub within 30s.
### Phase 1 — GitHub App & Webhooks (12 sprints)
- Switch to GitHub App installs; create private repo; store installation id.
- Committer hardened (debounce 25s, backoff, retries).
- Puller service with webhook → FF merge → checkout to `/app/data/`.
- LFS autotrack + `.gitignore` generation.
- CLI surfaces status + logs.
### Phase 2 — Restore & Snapshots (1 sprint)
- `bm restore` for file/folder/project with dryrun.
- `bm cloud snapshot` tags + list/inspect.
- Policy: PRonly nonFF, admin override.
### Phase 3 — Selective & MultiRepo (nicetohave)
- Include/exclude projects; optional perproject repos.
- Advanced policies (branch protections, required reviews).
## Acceptance Criteria
- Changes to `/app/data/` are committed and pushed automatically within configurable interval (default ≤5s).
- GitHub webhook pull results in updated files in `/app/data/` (FFonly by default).
- LFS configured and functioning; large files don't bloat history.
- `bm cloud git status` shows connected repo and last push/pull times.
- `bm restore` restores a file/folder to a prior commit with a clear audit trail.
- Endtoend works alongside SPEC9 bisync without loops or data loss.
## Risks & Mitigations
- **Loop risk (Git ↔ Bisync)**: Writes to `/app/data/` → bisync → local → user edits → back again. *Mitigation*: Debounce, commit squashing, idempotent `.bmmeta` versioning, and watch exclusion windows during pull.
- **Repo bloat**: Lots of binary churn. *Mitigation*: default LFS, size threshold, optional mediaonly repo later.
- **Security**: Token leakage. *Mitigation*: GitHub App with shortlived tokens, KMS storage, scoped permissions.
- **Merge complexity**: Nontrivial conflicts. *Mitigation*: prefer FF; otherwise conflict copies + events; require PR for nonFF.
## Open Questions
- Do we default to **monorepo** per tenant, or offer projectperrepo at connect time?
- Should `restore` write to a branch and open a PR, or directly modify `main`?
- How do we expose Git history in UI (timeline view) without users dropping to CLI?
## Appendix: Sample Config
```json
{
"git": {
"enabled": true,
"repo": "https://github.com/<owner>/<repo>.git",
"autoPushInterval": "5s",
"allowNonFF": false,
"lfs": { "sizeThreshold": 10485760 }
}
}
```
@@ -0,0 +1,273 @@
---
title: 'SPEC-15: Configuration Persistence via Tigris for Cloud Tenants'
type: spec
permalink: specs/spec-14-config-persistence-tigris
tags:
- persistence
- tigris
- multi-tenant
- infrastructure
- configuration
status: draft
---
# SPEC-15: Configuration Persistence via Tigris for Cloud Tenants
## Why
We need to persist Basic Memory configuration across Fly.io deployments without using persistent volumes or external databases.
**Current Problems:**
- `~/.basic-memory/config.json` lost on every deployment (project configuration)
- `~/.basic-memory/memory.db` lost on every deployment (search index)
- Persistent volumes break clean deployment workflow
- External databases (Turso) require per-tenant token management
**The Insight:**
The SQLite database is just an **index cache** of the markdown files. It can be rebuilt in seconds from the source markdown files in Tigris. Only the small `config.json` file needs true persistence.
**Solution:**
- Store `config.json` in Tigris bucket (persistent, small file)
- Rebuild `memory.db` on startup from markdown files (fast, ephemeral)
- No persistent volumes, no external databases, no token management
## What
Store Basic Memory configuration in the Tigris bucket and rebuild the database index on tenant machine startup.
**Affected Components:**
- `basic-memory/src/basic_memory/config.py` - Add configurable config directory
**Architecture:**
```bash
# Tigris Bucket (persistent, mounted at /app/data)
/app/data/
├── .basic-memory/
│ └── config.json # ← Project configuration (persistent, accessed via BASIC_MEMORY_CONFIG_DIR)
└── basic-memory/ # ← Markdown files (persistent, BASIC_MEMORY_HOME)
├── project1/
└── project2/
# Fly Machine (ephemeral)
/app/.basic-memory/
└── memory.db # ← Rebuilt on startup (fast local disk)
```
## How (High Level)
### 1. Add Configurable Config Directory to Basic Memory
Currently `ConfigManager` hardcodes `~/.basic-memory/config.json`. Add environment variable to override:
```python
# basic-memory/src/basic_memory/config.py
class ConfigManager:
"""Manages Basic Memory configuration."""
def __init__(self) -> None:
"""Initialize the configuration manager."""
home = os.getenv("HOME", Path.home())
if isinstance(home, str):
home = Path(home)
# Allow override via environment variable
if config_dir := os.getenv("BASIC_MEMORY_CONFIG_DIR"):
self.config_dir = Path(config_dir)
else:
self.config_dir = home / DATA_DIR_NAME
self.config_file = self.config_dir / CONFIG_FILE_NAME
# Ensure config directory exists
self.config_dir.mkdir(parents=True, exist_ok=True)
```
### 2. Rebuild Database on Startup
Basic Memory already has the sync functionality. Just ensure it runs on startup:
```python
# apps/api/src/basic_memory_cloud_api/main.py
@app.on_event("startup")
async def startup_sync():
"""Rebuild database index from Tigris markdown files."""
logger.info("Starting database rebuild from Tigris")
# Initialize file sync (rebuilds index from markdown files)
app_config = ConfigManager().config
await initialize_file_sync(app_config)
logger.info("Database rebuild complete")
```
### 3. Environment Configuration
```bash
# Machine environment variables
BASIC_MEMORY_CONFIG_DIR=/app/data/.basic-memory # Config read/written directly to Tigris
# memory.db stays in default location: /app/.basic-memory/memory.db (local ephemeral disk)
```
## Implementation Task List
### Phase 1: Basic Memory Changes ✅
- [x] Add `BASIC_MEMORY_CONFIG_DIR` environment variable support to `ConfigManager.__init__()`
- [x] Test config loading from custom directory
- [x] Update tests to verify custom config dir works
### Phase 2: Tigris Bucket Structure ✅
- [x] Ensure `.basic-memory/` directory exists in Tigris bucket on tenant creation
- ✅ ConfigManager auto-creates on first run, no explicit provisioning needed
- [x] Initialize `config.json` in Tigris on first tenant deployment
- ✅ ConfigManager creates config.json automatically in BASIC_MEMORY_CONFIG_DIR
- [x] Verify TigrisFS handles hidden directories correctly
- ✅ TigrisFS supports hidden directories (verified in SPEC-8)
### Phase 3: Deployment Integration ✅
- [x] Set `BASIC_MEMORY_CONFIG_DIR` environment variable in machine deployment
- ✅ Added to BasicMemoryMachineConfigBuilder in fly_schemas.py
- [x] Ensure database rebuild runs on machine startup via initialization sync
- ✅ sync_worker.py runs initialize_file_sync every 30s (already implemented)
- [x] Handle first-time tenant setup (no config exists yet)
- ✅ ConfigManager creates config.json on first initialization
- [ ] Test deployment workflow with config persistence
### Phase 4: Testing
- [x] Unit tests for config directory override
- [-] Integration test: deploy → write config → redeploy → verify config persists
- [ ] Integration test: deploy → add project → redeploy → verify project in config
- [ ] Performance test: measure db rebuild time on startup
### Phase 5: Documentation
- [ ] Document config persistence architecture
- [ ] Update deployment runbook
- [ ] Document startup sequence and timing
## How to Evaluate
### Success Criteria
1. **Config Persistence**
- [ ] config.json persists across deployments
- [ ] Projects list maintained across restarts
- [ ] No manual configuration needed after redeploy
2. **Database Rebuild**
- [ ] memory.db rebuilt on startup in < 30 seconds
- [ ] All entities indexed correctly
- [ ] Search functionality works after rebuild
3. **Performance**
- [ ] SQLite queries remain fast (local disk)
- [ ] Config reads acceptable (symlink to Tigris)
- [ ] No noticeable performance degradation
4. **Deployment Workflow**
- [ ] Clean deployments without volumes
- [ ] No new external dependencies
- [ ] No secret management needed
### Testing Procedure
1. **Config Persistence Test**
```bash
# Deploy tenant
POST /tenants → tenant_id
# Add a project
basic-memory project add "test-project" ~/test
# Verify config has project
cat /app/data/.basic-memory/config.json
# Redeploy machine
fly deploy --app basic-memory-{tenant_id}
# Verify project still exists
basic-memory project list
```
2. **Database Rebuild Test**
```bash
# Create notes
basic-memory write "Test Note" --content "..."
# Redeploy (db lost)
fly deploy --app basic-memory-{tenant_id}
# Wait for startup sync
sleep 10
# Verify note is indexed
basic-memory search "Test Note"
```
3. **Performance Benchmark**
```bash
# Time the startup sync
time basic-memory sync
# Should be < 30 seconds for typical tenant
```
## Benefits Over Alternatives
**vs. Persistent Volumes:**
- ✅ Clean deployment workflow
- ✅ No volume migration needed
- ✅ Simpler infrastructure
**vs. Turso (External Database):**
- ✅ No per-tenant token management
- ✅ No external service dependencies
- ✅ No additional costs
- ✅ Simpler architecture
**vs. SQLite on FUSE:**
- ✅ Fast local SQLite performance
- ✅ Only slow reads for small config file
- ✅ Database queries remain fast
## Implementation Assignment
**Primary Agent:** `python-developer`
- Add `BASIC_MEMORY_CONFIG_DIR` environment variable to ConfigManager
- Update deployment workflow to set environment variable
- Ensure startup sync runs correctly
**Review Agent:** `system-architect`
- Validate architecture simplicity
- Review performance implications
- Assess startup timing
## Dependencies
- **Internal:** TigrisFS must be working and stable
- **Internal:** Basic Memory sync must be reliable
- **Internal:** SPEC-8 (TigrisFS Integration) must be complete
## Open Questions
1. Should we add a health check that waits for db rebuild to complete?
2. Do we need to handle very large knowledge bases (>10k entities) differently?
3. Should we add metrics for startup sync duration?
## References
- Basic Memory sync: `basic-memory/src/basic_memory/services/initialization.py`
- Config management: `basic-memory/src/basic_memory/config.py`
- TigrisFS integration: SPEC-8
---
**Status Updates:**
- 2025-10-08: Pivoted from Turso to Tigris-based config persistence
- 2025-10-08: Phase 1 complete - BASIC_MEMORY_CONFIG_DIR support added (PR #343)
- 2025-10-08: Phases 2-3 complete - Added BASIC_MEMORY_CONFIG_DIR to machine config
- Config now persists to /app/data/.basic-memory/config.json in Tigris bucket
- Database rebuild already working via sync_worker.py
- Ready for deployment testing (Phase 4)
@@ -0,0 +1,800 @@
---
title: 'SPEC-16: MCP Cloud Service Consolidation'
type: spec
permalink: specs/spec-16-mcp-cloud-service-consolidation
tags:
- architecture
- mcp
- cloud
- performance
- deployment
status: in-progress
---
## Status Update
**Phase 0 (Basic Memory Refactor): ✅ COMPLETE**
- basic-memory PR #344: async_client context manager pattern implemented
- All 17 MCP tools updated to use `async with get_client() as client:`
- CLI commands updated to use context manager
- Removed `inject_auth_header()` and `headers.py` (~100 lines deleted)
- Factory pattern enables clean dependency injection
- Tests passing, typecheck clean
**Phase 0 Integration: ✅ COMPLETE**
- basic-memory-cloud updated to use async-client-context-manager branch
- Implemented `tenant_direct_client_factory()` with proper context manager pattern
- Removed module-level client override hacks
- Removed unnecessary `/proxy` prefix stripping (tools pass relative URLs)
- Typecheck and lint passing with proper noqa hints
- MCP tools confirmed working via inspector (local testing)
**Phase 1 (Code Consolidation): ✅ COMPLETE**
- MCP server mounted on Cloud FastAPI app at /mcp endpoint
- AuthKitProvider configured with WorkOS settings
- Combined lifespans (Cloud + MCP) working correctly
- JWT context middleware integrated
- All routes and MCP tools functional
**Phase 2 (Direct Tenant Transport): ✅ COMPLETE**
- TenantDirectTransport implemented with custom httpx transport
- Per-request JWT extraction via FastMCP DI
- Tenant lookup and signed header generation working
- Direct routing to tenant APIs (eliminating HTTP hop)
- Transport tests passing (11/11)
**Phase 3 (Testing & Validation): ✅ COMPLETE**
- Typecheck and lint passing across all services
- MCP OAuth authentication working in preview environment
- Tenant isolation via signed headers verified
- Fixed BM_TENANT_HEADER_SECRET mismatch between environments
- MCP tools successfully calling tenant APIs in preview
**Phase 4 (Deployment Configuration): ✅ COMPLETE**
- Updated apps/cloud/fly.template.toml with MCP environment variables
- Added HTTP/2 backend support for better MCP performance
- Added OAuth protected resource health check
- Removed MCP from preview deployment workflow
- Successfully deployed to preview environment (PR #113)
- All services operational at pr-113-basic-memory-cloud.fly.dev
**Next Steps:**
- Phase 5: Cleanup (remove apps/mcp directory)
- Phase 6: Production rollout and performance measurement
# SPEC-16: MCP Cloud Service Consolidation
## Why
### Original Architecture Constraints (Now Removed)
The current architecture deploys MCP Gateway and Cloud Service as separate Fly.io apps:
**Current Flow:**
```
LLM Client → MCP Gateway (OAuth) → Cloud Proxy (JWT + header signing) → Tenant API (JWT + header validation)
apps/mcp apps/cloud /proxy apps/api
```
This separation was originally necessary because:
1. **Stateful SSE requirement** - MCP needed server-sent events with session state for active project tracking
2. **fastmcp.run limitation** - The FastMCP demo helper didn't support worker processes
### Why These Constraints No Longer Apply
1. **State externalized** - Project state moved from in-memory to LLM context (external state)
2. **HTTP transport enabled** - Switched from SSE to stateless HTTP for MCP tools
3. **Worker support added** - Converted from `fastmcp.run()` to `uvicorn.run()` with workers
### Current Problems
- **Unnecessary HTTP hop** - MCP tools call Cloud /proxy endpoint which calls tenant API
- **Higher latency** - Extra network round trip for every MCP operation
- **Increased costs** - Two separate Fly.io apps instead of one
- **Complex deployment** - Two services to deploy, monitor, and maintain
- **Resource waste** - Separate database connections, HTTP clients, telemetry overhead
## What
### Services Affected
1. **apps/mcp** - MCP Gateway service (to be merged)
2. **apps/cloud** - Cloud service (will receive MCP functionality)
3. **basic-memory** - Update `async_client.py` to use direct calls
4. **Deployment** - Consolidate Fly.io deployment to single app
### Components Changed
**Merged:**
- MCP middleware and telemetry into Cloud app
- MCP tools mounted on Cloud FastAPI instance
- ProxyService used directly by MCP tools (not via HTTP)
**Kept:**
- `/proxy` endpoint (still needed by web UI)
- All existing Cloud routes (provisioning, webhooks, etc.)
- Dual validation in tenant API (JWT + signed headers)
**Removed:**
- apps/mcp directory
- Separate MCP Fly.io deployment
- HTTP calls from MCP tools to /proxy endpoint
## How (High Level)
### 1. Mount FastMCP on Cloud FastAPI App
```python
# apps/cloud/src/basic_memory_cloud/main.py
from basic_memory.mcp.server import mcp
from basic_memory_cloud_mcp.middleware import TelemetryMiddleware
# Configure MCP OAuth
auth_provider = AuthKitProvider(
authkit_domain=settings.authkit_domain,
base_url=settings.authkit_base_url,
required_scopes=[],
)
mcp.auth = auth_provider
mcp.add_middleware(TelemetryMiddleware())
# Mount MCP at /mcp endpoint
mcp_app = mcp.http_app(path="/mcp", stateless_http=True)
app.mount("/mcp", mcp_app)
# Existing Cloud routes stay at root
app.include_router(proxy_router)
app.include_router(provisioning_router)
# ... etc
```
### 2. Direct Tenant Transport (No HTTP Hop)
Instead of calling `/proxy`, MCP tools call tenant APIs directly via custom httpx transport.
**Important:** No URL prefix stripping needed. The transport receives relative URLs like `/main/resource/notes/my-note` which are correctly routed to tenant APIs. The `/proxy` prefix only exists for web UI requests to the proxy router, not for MCP tools using the custom transport.
```python
# apps/cloud/src/basic_memory_cloud/transports/tenant_direct.py
from httpx import AsyncBaseTransport, Request, Response
from fastmcp.server.dependencies import get_http_headers
import jwt
class TenantDirectTransport(AsyncBaseTransport):
"""Direct transport to tenant APIs, bypassing /proxy endpoint."""
async def handle_async_request(self, request: Request) -> Response:
# 1. Get JWT from current MCP request (via FastMCP DI)
http_headers = get_http_headers()
auth_header = http_headers.get("authorization") or http_headers.get("Authorization")
token = auth_header.replace("Bearer ", "")
claims = jwt.decode(token, options={"verify_signature": False})
workos_user_id = claims["sub"]
# 2. Look up tenant for user
tenant = await tenant_service.get_tenant_by_user_id(workos_user_id)
# 3. Build tenant app URL with signed headers
fly_app_name = f"{settings.tenant_prefix}-{tenant.id}"
target_url = f"https://{fly_app_name}.fly.dev{request.url.path}"
headers = dict(request.headers)
signer = create_signer(settings.bm_tenant_header_secret)
headers.update(signer.sign_tenant_headers(tenant.id))
# 4. Make direct call to tenant API
response = await self.client.request(
method=request.method, url=target_url,
headers=headers, content=request.content
)
return response
```
Then configure basic-memory's client factory before mounting MCP:
```python
# apps/cloud/src/basic_memory_cloud/main.py
from contextlib import asynccontextmanager
from basic_memory.mcp import async_client
from basic_memory_cloud.transports.tenant_direct import TenantDirectTransport
# Configure factory for basic-memory's async_client
@asynccontextmanager
async def tenant_direct_client_factory():
"""Factory for creating clients with tenant direct transport."""
client = httpx.AsyncClient(
transport=TenantDirectTransport(),
base_url="http://direct",
)
try:
yield client
finally:
await client.aclose()
# Set factory BEFORE importing MCP tools
async_client.set_client_factory(tenant_direct_client_factory)
# NOW import - tools will use our factory
import basic_memory.mcp.tools
import basic_memory.mcp.prompts
from basic_memory.mcp.server import mcp
# Mount MCP - tools use direct transport via factory
app.mount("/mcp", mcp_app)
```
**Key benefits:**
- Clean dependency injection via factory pattern
- Per-request tenant resolution via FastMCP DI
- Proper resource cleanup (client.aclose() guaranteed)
- Eliminates HTTP hop entirely
- /proxy endpoint remains for web UI
### 3. Keep /proxy Endpoint for Web UI
The existing `/proxy` HTTP endpoint remains functional for:
- Web UI requests
- Future external API consumers
- Backward compatibility
### 4. Security: Maintain Dual Validation
**Do NOT remove JWT validation from tenant API.** Keep defense in depth:
```python
# apps/api - Keep both validations
1. JWT validation (from WorkOS token)
2. Signed header validation (from Cloud/MCP)
```
This ensures if the Cloud service is compromised, attackers still cannot access tenant APIs without valid JWTs.
### 5. Deployment Changes
**Before:**
- `apps/mcp/fly.template.toml` → MCP Gateway deployment
- `apps/cloud/fly.template.toml` → Cloud Service deployment
**After:**
- Remove `apps/mcp/fly.template.toml`
- Update `apps/cloud/fly.template.toml` to expose port 8000 for both /mcp and /proxy
- Update deployment scripts to deploy single consolidated app
## Basic Memory Dependency: Async Client Refactor
### Problem
The current `basic_memory.mcp.async_client` creates a module-level `client` at import time:
```python
client = create_client() # Runs immediately when module is imported
```
This prevents dependency injection - by the time we can override it, tools have already imported it.
### Solution: Context Manager Pattern with Auth at Client Creation
Refactor basic-memory to use httpx's context manager pattern instead of module-level client.
**Key principle:** Authentication happens at client creation time, not per-request.
```python
# basic_memory/src/basic_memory/mcp/async_client.py
from contextlib import asynccontextmanager
from httpx import AsyncClient, ASGITransport, Timeout
# Optional factory override for dependency injection
_client_factory = None
def set_client_factory(factory):
"""Override the default client factory (for cloud app, testing, etc)."""
global _client_factory
_client_factory = factory
@asynccontextmanager
async def get_client():
"""Get an AsyncClient as a context manager.
Usage:
async with get_client() as client:
response = await client.get(...)
"""
if _client_factory:
# Cloud app: custom transport handles everything
async with _client_factory() as client:
yield client
else:
# Default: create based on config
config = ConfigManager().config
timeout = Timeout(connect=10.0, read=30.0, write=30.0, pool=30.0)
if config.cloud_mode_enabled:
# CLI cloud mode: inject auth when creating client
from basic_memory.cli.auth import CLIAuth
auth = CLIAuth(
client_id=config.cloud_client_id,
authkit_domain=config.cloud_domain
)
token = await auth.get_valid_token()
if not token:
raise RuntimeError(
"Cloud mode enabled but not authenticated. "
"Run 'basic-memory cloud login' first."
)
# Auth header set ONCE at client creation
async with AsyncClient(
base_url=f"{config.cloud_host}/proxy",
headers={"Authorization": f"Bearer {token}"},
timeout=timeout
) as client:
yield client
else:
# Local mode: ASGI transport
async with AsyncClient(
transport=ASGITransport(app=fastapi_app),
base_url="http://test",
timeout=timeout
) as client:
yield client
```
**Tool Updates:**
```python
# Before: from basic_memory.mcp.async_client import client
from basic_memory.mcp.async_client import get_client
async def read_note(...):
# Before: response = await call_get(client, path, ...)
async with get_client() as client:
response = await call_get(client, path, ...)
# ... use response
```
**Cloud Usage:**
```python
from contextlib import asynccontextmanager
from basic_memory.mcp import async_client
@asynccontextmanager
async def tenant_direct_client():
"""Factory for creating clients with tenant direct transport."""
client = httpx.AsyncClient(
transport=TenantDirectTransport(),
base_url="http://direct",
)
try:
yield client
finally:
await client.aclose()
# Before importing MCP tools:
async_client.set_client_factory(tenant_direct_client)
# Now import - tools will use our factory
import basic_memory.mcp.tools
```
### Benefits
- **No module-level state** - client created only when needed
- **Proper cleanup** - context manager ensures `aclose()` is called
- **Easy dependency injection** - factory pattern allows custom clients
- **httpx best practices** - follows official recommendations
- **Works for all modes** - stdio, cloud, testing
### Architecture Simplification: Auth at Client Creation
**Key design principle:** Authentication happens when creating the client, not on every request.
**Three modes, three approaches:**
1. **Local mode (ASGI)**
- No auth needed
- Direct in-process calls via ASGITransport
2. **CLI cloud mode (HTTP)**
- Auth token from CLIAuth (stored in ~/.basic-memory/basic-memory-cloud.json)
- Injected as default header when creating AsyncClient
- Single auth check at client creation time
3. **Cloud app mode (Custom Transport)**
- TenantDirectTransport handles everything
- Extracts JWT from FastMCP context per-request
- No interaction with inject_auth_header() logic
**What this removes:**
- `src/basic_memory/mcp/tools/headers.py` - entire file deleted
- `inject_auth_header()` calls in all request helpers (call_get, call_post, etc.)
- Per-request header manipulation complexity
- Circular dependency concerns between async_client and auth logic
**Benefits:**
- Cleaner separation of concerns
- Simpler request helper functions
- Auth happens at the right layer (client creation)
- Cloud app transport is completely independent
### Refactor Summary
This refactor achieves:
**Simplification:**
- Removes ~100 lines of per-request header injection logic
- Deletes entire `headers.py` module
- Auth happens once at client creation, not per-request
**Decoupling:**
- Cloud app's custom transport is completely independent
- No interaction with basic-memory's auth logic
- Each mode (local, CLI cloud, cloud app) has clean separation
**Better Design:**
- Follows httpx best practices (context managers)
- Proper resource cleanup (client.aclose() guaranteed)
- Easier testing via factory injection
- No circular import risks
**Three Distinct Modes:**
1. Local: ASGI transport, no auth
2. CLI cloud: HTTP transport with CLIAuth token injection
3. Cloud app: Custom transport with per-request tenant routing
### Implementation Plan Summary
1. Create branch `async-client-context-manager` in basic-memory
2. Update `async_client.py` with context manager pattern and CLIAuth integration
3. Remove `inject_auth_header()` from all request helpers
4. Delete `src/basic_memory/mcp/tools/headers.py`
5. Update all MCP tools to use `async with get_client() as client:`
6. Update CLI commands to use context manager and remove manual auth
7. Remove `api_url` config field
8. Update tests
9. Update basic-memory-cloud to use branch: `basic-memory @ git+https://github.com/basicmachines-co/basic-memory.git@async-client-context-manager`
Detailed breakdown in Phase 0 tasks below.
### Implementation Notes
**Potential Issues & Solutions:**
1. **Circular Import** (async_client imports CLIAuth)
- **Risk:** CLIAuth might import something from async_client
- **Solution:** Use lazy import inside `get_client()` function
- **Already done:** Import is inside the function, not at module level
2. **Test Fixtures**
- **Risk:** Tests using module-level client will break
- **Solution:** Update fixtures to use factory pattern
- **Example:**
```python
@pytest.fixture
def mock_client_factory():
@asynccontextmanager
async def factory():
async with AsyncClient(...) as client:
yield client
return factory
```
3. **Performance**
- **Risk:** Creating client per tool call might be expensive
- **Reality:** httpx is designed for this pattern, connection pooling at transport level
- **Mitigation:** Monitor performance, can optimize later if needed
4. **CLI Cloud Commands Edge Cases**
- **Risk:** Token expires mid-operation
- **Solution:** CLIAuth.get_valid_token() already handles refresh
- **Validation:** Test cloud login → use tools → token refresh flow
5. **Backward Compatibility**
- **Risk:** External code importing `client` directly
- **Solution:** Keep `create_client()` and `client` for one version, deprecate
- **Timeline:** Remove in next major version
## Implementation Tasks
### Phase 0: Basic Memory Refactor (Prerequisite)
#### 0.1 Core Refactor - async_client.py
- [x] Create branch `async-client-context-manager` in basic-memory repo
- [x] Implement `get_client()` context manager
- [x] Implement `set_client_factory()` for dependency injection
- [x] Add CLI cloud mode auth injection (CLIAuth integration)
- [x] Remove `api_url` config field (legacy, unused)
- [x] Keep `create_client()` temporarily for backward compatibility (deprecate later)
#### 0.2 Simplify Request Helpers - tools/utils.py
- [x] Remove `inject_auth_header()` calls from `call_get()`
- [x] Remove `inject_auth_header()` calls from `call_post()`
- [x] Remove `inject_auth_header()` calls from `call_put()`
- [x] Remove `inject_auth_header()` calls from `call_patch()`
- [x] Remove `inject_auth_header()` calls from `call_delete()`
- [x] Delete `src/basic_memory/mcp/tools/headers.py` entirely
- [x] Update imports in utils.py
#### 0.3 Update MCP Tools (~16 files)
Convert from `from async_client import client` to `async with get_client() as client:`
- [x] `tools/write_note.py` (34/34 tests passing)
- [x] `tools/read_note.py` (21/21 tests passing)
- [x] `tools/view_note.py` (12/12 tests passing - no changes needed, delegates to read_note)
- [x] `tools/delete_note.py` (2/2 tests passing)
- [x] `tools/read_content.py` (20/20 tests passing)
- [x] `tools/list_directory.py` (11/11 tests passing)
- [x] `tools/move_note.py` (34/34 tests passing, 90% coverage)
- [x] `tools/search.py` (16/16 tests passing, 96% coverage)
- [x] `tools/recent_activity.py` (4/4 tests passing, 82% coverage)
- [x] `tools/project_management.py` (3 functions: list_memory_projects, create_memory_project, delete_project - typecheck passed)
- [x] `tools/edit_note.py` (17/17 tests passing)
- [x] `tools/canvas.py` (5/5 tests passing)
- [x] `tools/build_context.py` (6/6 tests passing)
- [x] `tools/sync_status.py` (typecheck passed)
- [x] `prompts/continue_conversation.py` (typecheck passed)
- [x] `prompts/search.py` (typecheck passed)
- [x] `resources/project_info.py` (typecheck passed)
#### 0.4 Update CLI Commands (~3 files)
Remove manual auth header passing, use context manager:
- [x] `cli/commands/project.py` - removed get_authenticated_headers() calls, use context manager
- [x] `cli/commands/status.py` - use context manager
- [x] `cli/commands/command_utils.py` - use context manager
#### 0.5 Update Config
- [x] Remove `api_url` field from `BasicMemoryConfig` in config.py
- [x] Update any lingering references/docs (added deprecation notice to v15-docs/cloud-mode-usage.md)
#### 0.6 Testing
- [-] Update test fixtures to use factory pattern
- [x] Run full test suite in basic-memory
- [x] Verify cloud_mode_enabled works with CLIAuth injection
- [x] Run typecheck and linting
#### 0.7 Cloud Integration Prep
- [x] Update basic-memory-cloud pyproject.toml to use branch
- [x] Implement factory pattern in cloud app main.py
- [x] Remove `/proxy` prefix stripping logic (not needed - tools pass relative URLs)
#### 0.8 Phase 0 Validation
**Before merging async-client-context-manager branch:**
- [x] All tests pass locally
- [x] Typecheck passes (pyright/mypy)
- [x] Linting passes (ruff)
- [x] Manual test: local mode works (ASGI transport)
- [x] Manual test: cloud login → cloud mode works (HTTP transport with auth)
- [x] No import of `inject_auth_header` anywhere
- [x] `headers.py` file deleted
- [x] `api_url` config removed
- [x] Tool functions properly scoped (client inside async with)
- [ ] CLI commands properly scoped (client inside async with)
**Integration validation:**
- [x] basic-memory-cloud can import and use factory pattern
- [x] TenantDirectTransport works without touching header injection
- [x] No circular imports or lazy import issues
- [x] MCP tools work via inspector (local testing confirmed)
### Phase 1: Code Consolidation
- [x] Create feature branch `consolidate-mcp-cloud`
- [x] Update `apps/cloud/src/basic_memory_cloud/config.py`:
- [x] Add `authkit_base_url` field (already has authkit_domain)
- [x] Workers config already exists ✓
- [x] Update `apps/cloud/src/basic_memory_cloud/telemetry.py`:
- [x] Add `logfire.instrument_mcp()` to existing setup
- [x] Skip complex two-phase setup - use Cloud's simpler approach
- [x] Create `apps/cloud/src/basic_memory_cloud/middleware/jwt_context.py`:
- [x] FastAPI middleware to extract JWT claims from Authorization header
- [x] Add tenant context (workos_user_id) to logfire baggage
- [x] Simpler than FastMCP middleware version
- [x] Update `apps/cloud/src/basic_memory_cloud/main.py`:
- [x] Import FastMCP server from basic-memory
- [x] Configure AuthKitProvider with WorkOS settings
- [x] No FastMCP telemetry middleware needed (using FastAPI middleware instead)
- [x] Create MCP ASGI app: `mcp_app = mcp.http_app(path='/mcp', stateless_http=True)`
- [x] Combine lifespans (Cloud + MCP) using nested async context managers
- [x] Mount MCP: `app.mount("/mcp", mcp_app)`
- [x] Add JWT context middleware to FastAPI app
- [x] Run typecheck - passes ✓
### Phase 2: Direct Tenant Transport
- [x] Create `apps/cloud/src/basic_memory_cloud/transports/tenant_direct.py`:
- [x] Implement `TenantDirectTransport(AsyncBaseTransport)`
- [x] Use FastMCP DI (`get_http_headers()`) to extract JWT per-request
- [x] Decode JWT to get `workos_user_id`
- [x] Look up/create tenant via `TenantRepository.get_or_create_tenant_for_workos_user()`
- [x] Build tenant app URL and add signed headers
- [x] Make direct httpx call to tenant API
- [x] No `/proxy` prefix stripping needed (tools pass relative URLs like `/main/resource/...`)
- [x] Update `apps/cloud/src/basic_memory_cloud/main.py`:
- [x] Refactored to use factory pattern instead of module-level override
- [x] Implement `tenant_direct_client_factory()` context manager
- [x] Call `async_client.set_client_factory()` before importing MCP tools
- [x] Clean imports, proper noqa hints for lint
- [x] Basic-memory refactor integrated (PR #344)
- [x] Run typecheck - passes ✓
- [x] Run lint - passes ✓
### Phase 3: Testing & Validation
- [x] Run `just typecheck` in apps/cloud
- [x] Run `just check` in project
- [x] Run `just fix` - all lint errors fixed ✓
- [x] Write comprehensive transport tests (11 tests passing) ✓
- [x] Test MCP tools locally with consolidated service (inspector confirmed working)
- [x] Verify OAuth authentication works (requires full deployment)
- [x] Verify tenant isolation via signed headers (requires full deployment)
- [x] Test /proxy endpoint still works for web UI
- [ ] Measure latency before/after consolidation
- [ ] Check telemetry traces span correctly
### Phase 4: Deployment Configuration
- [x] Update `apps/cloud/fly.template.toml`:
- [x] Merged MCP-specific environment variables (AUTHKIT_BASE_URL, FASTMCP_LOG_LEVEL, BASIC_MEMORY_*)
- [x] Added HTTP/2 backend support (`h2_backend = true`) for better MCP performance
- [x] Added health check for MCP OAuth endpoint (`/.well-known/oauth-protected-resource`)
- [x] Port 8000 already exposed - serves both Cloud routes and /mcp endpoint
- [x] Workers configured (UVICORN_WORKERS = 4)
- [x] Update `.env.example`:
- [x] Consolidated MCP Gateway section into Cloud app section
- [x] Added AUTHKIT_BASE_URL, FASTMCP_LOG_LEVEL, BASIC_MEMORY_HOME
- [x] Added LOG_LEVEL to Development Settings
- [x] Documented that MCP now served at /mcp on Cloud service (port 8000)
- [x] Test deployment to preview environment (PR #113)
- [x] OAuth authentication verified
- [x] MCP tools successfully calling tenant APIs
- [x] Fixed BM_TENANT_HEADER_SECRET synchronization issue
### Phase 5: Cleanup
- [x] Remove `apps/mcp/` directory entirely
- [x] Remove MCP-specific fly.toml and deployment configs
- [x] Update repository documentation
- [x] Update CLAUDE.md with new architecture
- [-] Archive old MCP deployment configs (if needed)
### Phase 6: Production Rollout
- [ ] Deploy to development and validate
- [ ] Monitor metrics and logs
- [ ] Deploy to production
- [ ] Verify production functionality
- [ ] Document performance improvements
## Migration Plan
### Phase 1: Preparation
1. Create feature branch `consolidate-mcp-cloud`
2. Update basic-memory async_client.py for direct ProxyService calls
3. Update apps/cloud/main.py to mount MCP
### Phase 2: Testing
1. Local testing with consolidated app
2. Deploy to development environment
3. Run full test suite
4. Performance benchmarking
### Phase 3: Deployment
1. Deploy to development
2. Validate all functionality
3. Deploy to production
4. Monitor for issues
### Phase 4: Cleanup
1. Remove apps/mcp directory
2. Update documentation
3. Update deployment scripts
4. Archive old MCP deployment configs
## Rollback Plan
If issues arise:
1. Revert feature branch
2. Redeploy separate apps/mcp and apps/cloud services
3. Restore previous fly.toml configurations
4. Document issues encountered
The well-organized code structure makes splitting back out feasible if future scaling needs diverge.
## How to Evaluate
### 1. Functional Testing
**MCP Tools:**
- [ ] All 17 MCP tools work via consolidated /mcp endpoint
- [x] OAuth authentication validates correctly
- [x] Tenant isolation maintained via signed headers
- [x] Project management tools function correctly
**Cloud Routes:**
- [x] /proxy endpoint still works for web UI
- [x] /provisioning routes functional
- [x] /webhooks routes functional
- [x] /tenants routes functional
**API Validation:**
- [x] Tenant API validates both JWT and signed headers
- [x] Unauthorized requests rejected appropriately
- [x] Multi-tenant isolation verified
### 2. Performance Testing
**Latency Reduction:**
- [x] Measure MCP tool latency before consolidation
- [x] Measure MCP tool latency after consolidation
- [x] Verify reduction from eliminated HTTP hop (expected: 20-50ms improvement)
**Resource Usage:**
- [x] Single app uses less total memory than two apps
- [x] Database connection pooling more efficient
- [x] HTTP client overhead reduced
### 3. Deployment Testing
**Fly.io Deployment:**
- [x] Single app deploys successfully
- [x] Health checks pass for consolidated service
- [x] No apps/mcp deployment required
- [x] Environment variables configured correctly
**Local Development:**
- [x] `just setup` works with consolidated architecture
- [x] Local testing shows MCP tools working
- [x] No regression in developer experience
### 4. Security Validation
**Defense in Depth:**
- [x] Tenant API still validates JWT tokens
- [x] Tenant API still validates signed headers
- [x] No access possible with only signed headers (JWT required)
- [x] No access possible with only JWT (signed headers required)
**Authorization:**
- [x] Users can only access their own tenant data
- [x] Cross-tenant requests rejected
- [x] Admin operations require proper authentication
### 5. Observability
**Telemetry:**
- [x] OpenTelemetry traces span across MCP → ProxyService → Tenant API
- [x] Logfire shows consolidated traces correctly
- [x] Error tracking and debugging still functional
- [x] Performance metrics accurate
**Logging:**
- [x] Structured logs show proper context (tenant_id, operation, etc.)
- [x] Error logs contain actionable information
- [x] Log volume reasonable for single app
## Success Criteria
1. **Functionality**: All MCP tools and Cloud routes work identically to before
2. **Performance**: Measurable latency reduction (>20ms average)
3. **Cost**: Single Fly.io app instead of two (50% infrastructure reduction)
4. **Security**: Dual validation maintained, no security regression
5. **Deployment**: Simplified deployment process, single app to manage
6. **Observability**: Telemetry and logging work correctly
## Notes
### Future Considerations
- **Independent scaling**: If MCP and Cloud need different scaling profiles in future, code organization supports splitting back out
- **Regional deployment**: Consolidated app can still be deployed to multiple regions
- **Edge caching**: Could add edge caching layer in front of consolidated service
### Dependencies
- SPEC-9: Signed Header Tenant Information (already implemented)
- SPEC-12: OpenTelemetry Observability (telemetry must work across merged services)
### Related Work
- basic-memory v0.13.x: MCP server implementation
- FastMCP documentation: Mounting on existing FastAPI apps
- Fly.io multi-service patterns
File diff suppressed because it is too large Load Diff
+528
View File
@@ -0,0 +1,528 @@
---
title: 'SPEC-18: AI Memory Management Tool'
type: spec
permalink: specs/spec-15-ai-memory-management-tool
tags:
- mcp
- memory
- ai-context
- tools
---
# SPEC-18: AI Memory Management Tool
## Why
Anthropic recently released a memory tool for Claude that enables storing and retrieving information across conversations using client-side file operations. This validates Basic Memory's local-first, file-based architecture - Anthropic converged on the same pattern.
However, Anthropic's memory tool is only available via their API and stores plain text. Basic Memory can offer a superior implementation through MCP that:
1. **Works everywhere** - Claude Desktop, Code, VS Code, Cursor via MCP (not just API)
2. **Structured knowledge** - Entities with observations/relations vs plain text
3. **Full search** - Full-text search, graph traversal, time-aware queries
4. **Unified storage** - Agent memories + user notes in one knowledge graph
5. **Existing infrastructure** - Leverages SQLite indexing, sync, multi-project support
This would enable AI agents to store contextual memories alongside user notes, with all the power of Basic Memory's knowledge graph features.
## What
Create a new MCP tool `memory` that matches Anthropic's tool interface exactly, allowing Claude to use it with zero learning curve. The tool will store files in Basic Memory's `/memories` directory and support Basic Memory's structured markdown format in the file content.
### Affected Components
- **New MCP Tool**: `src/basic_memory/mcp/tools/memory_tool.py`
- **Dedicated Memories Project**: Create a separate "memories" Basic Memory project
- **Project Isolation**: Memories stored separately from user notes/documents
- **File Organization**: Within the memories project, use folder structure:
- `user/` - User preferences, context, communication style
- `projects/` - Project-specific state and decisions
- `sessions/` - Conversation-specific working memory
- `patterns/` - Learned patterns and insights
### Tool Commands
The tool will support these commands (exactly matching Anthropic's interface):
- `view` - Display directory contents or file content (with optional line range)
- `create` - Create or overwrite a file with given content
- `str_replace` - Replace text in an existing file
- `insert` - Insert text at specific line number
- `delete` - Delete file or directory
- `rename` - Move or rename file/directory
### Memory Note Format
Memories will use Basic Memory's standard structure:
```markdown
---
title: User Preferences
permalink: memories/user/preferences
type: memory
memory_type: preferences
created_by: claude
tags: [user, preferences, style]
---
# User Preferences
## Observations
- [communication] Prefers concise, direct responses without preamble #style
- [tone] Appreciates validation but dislikes excessive apologizing #communication
- [technical] Works primarily in Python with type annotations #coding
## Relations
- relates_to [[Basic Memory Project]]
- informs [[Response Style Guidelines]]
```
## How (High Level)
### Implementation Approach
The memory tool matches Anthropic's interface but uses a dedicated Basic Memory project:
```python
async def memory_tool(
command: str,
path: str,
file_text: Optional[str] = None,
old_str: Optional[str] = None,
new_str: Optional[str] = None,
insert_line: Optional[int] = None,
insert_text: Optional[str] = None,
old_path: Optional[str] = None,
new_path: Optional[str] = None,
view_range: Optional[List[int]] = None,
):
"""Memory tool with Anthropic-compatible interface.
Operates on a dedicated "memories" Basic Memory project,
keeping AI memories separate from user notes.
"""
# Get the memories project (auto-created if doesn't exist)
memories_project = get_or_create_memories_project()
# Validate path security using pathlib (prevent directory traversal)
safe_path = validate_memory_path(path, memories_project.project_path)
# Use existing project isolation - already prevents cross-project access
full_path = memories_project.project_path / safe_path
if command == "view":
# Return directory listing or file content
if full_path.is_dir():
return list_directory_contents(full_path)
return read_file_content(full_path, view_range)
elif command == "create":
# Write file directly (file_text can contain BM markdown)
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(file_text)
# Sync service will detect and index automatically
return f"Created {path}"
elif command == "str_replace":
# Read, replace, write
content = full_path.read_text()
updated = content.replace(old_str, new_str)
full_path.write_text(updated)
return f"Replaced text in {path}"
elif command == "insert":
# Insert at line number
lines = full_path.read_text().splitlines()
lines.insert(insert_line, insert_text)
full_path.write_text("\n".join(lines))
return f"Inserted text at line {insert_line}"
elif command == "delete":
# Delete file or directory
if full_path.is_dir():
shutil.rmtree(full_path)
else:
full_path.unlink()
return f"Deleted {path}"
elif command == "rename":
# Move/rename
full_path.rename(config.project_path / new_path)
return f"Renamed {old_path} to {new_path}"
```
### Key Design Decisions
1. **Exact interface match** - Same commands, parameters as Anthropic's tool
2. **Dedicated memories project** - Separate Basic Memory project keeps AI memories isolated from user notes
3. **Existing project isolation** - Leverage BM's existing cross-project security (no additional validation needed)
4. **Direct file I/O** - No schema conversion, just read/write files
5. **Structured content supported** - `file_text` can use BM markdown format with frontmatter, observations, relations
6. **Automatic indexing** - Sync service watches memories project and indexes changes
7. **Path security** - Use `pathlib.Path.resolve()` and `relative_to()` to prevent directory traversal
8. **Error handling** - Follow Anthropic's text editor tool error patterns
### MCP Tool Schema
Exact match to Anthropic's memory tool schema:
```json
{
"name": "memory",
"description": "Store and retrieve information across conversations using structured markdown files. All operations must be within the /memories directory. Supports Basic Memory markdown format including frontmatter, observations, and relations.",
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"enum": ["view", "create", "str_replace", "insert", "delete", "rename"],
"description": "File operation to perform"
},
"path": {shu
"type": "string",
"description": "Path within /memories directory (required for all commands)"
},
"file_text": {
"type": "string",
"description": "Content to write (for create command). Supports Basic Memory markdown format."
},
"view_range": {
"type": "array",
"items": {"type": "integer"},
"description": "Optional [start, end] line range for view command"
},
"old_str": {
"type": "string",
"description": "Text to replace (for str_replace command)"
},
"new_str": {
"type": "string",
"description": "Replacement text (for str_replace command)"
},
"insert_line": {
"type": "integer",
"description": "Line number to insert at (for insert command)"
},
"insert_text": {
"type": "string",
"description": "Text to insert (for insert command)"
},
"old_path": {
"type": "string",
"description": "Current path (for rename command)"
},
"new_path": {
"type": "string",
"description": "New path (for rename command)"
}
},
"required": ["command", "path"]
}
}
```
### Prompting Guidance
When the `memory` tool is included, Basic Memory should provide system prompt guidance to help Claude use it effectively.
#### Automatic System Prompt Addition
```text
MEMORY PROTOCOL FOR BASIC MEMORY:
1. ALWAYS check your memory directory first using `view` command on root directory
2. Your memories are stored in a dedicated Basic Memory project (isolated from user notes)
3. Use structured markdown format in memory files:
- Include frontmatter with title, type: memory, tags
- Use ## Observations with [category] prefixes for facts
- Use ## Relations to link memories with [[WikiLinks]]
4. Record progress, context, and decisions as categorized observations
5. Link related memories using relations
6. ASSUME INTERRUPTION: Context may reset - save progress frequently
MEMORY ORGANIZATION:
- user/ - User preferences, context, communication style
- projects/ - Project-specific state and decisions
- sessions/ - Conversation-specific working memory
- patterns/ - Learned patterns and insights
MEMORY ADVANTAGES:
- Your memories are automatically searchable via full-text search
- Relations create a knowledge graph you can traverse
- Memories are isolated from user notes (separate project)
- Use search_notes(project="memories") to find relevant past context
- Use recent_activity(project="memories") to see what changed recently
- Use build_context() to navigate memory relations
```
#### Optional MCP Prompt: `memory_guide`
Create an MCP prompt that provides detailed guidance and examples:
```python
{
"name": "memory_guide",
"description": "Comprehensive guidance for using Basic Memory's memory tool effectively, including structured markdown examples and best practices"
}
```
This prompt returns:
- Full protocol and conventions
- Example memory file structures
- Tips for organizing observations and relations
- Integration with other Basic Memory tools
- Common patterns (user preferences, project state, session tracking)
#### User Customization
Users can customize memory behavior with additional instructions:
- "Only write information relevant to [topic] in your memory system"
- "Keep memory files concise and organized - delete outdated content"
- "Use detailed observations for technical decisions and implementation notes"
- "Always link memories to related project documentation using relations"
### Error Handling
Follow Anthropic's text editor tool error handling patterns for consistency:
#### Error Types
1. **File Not Found**
```json
{"error": "File not found: memories/user/preferences.md", "is_error": true}
```
2. **Permission Denied**
```json
{"error": "Permission denied: Cannot write outside /memories directory", "is_error": true}
```
3. **Invalid Path (Directory Traversal)**
```json
{"error": "Invalid path: Path must be within /memories directory", "is_error": true}
```
4. **Multiple Matches (str_replace)**
```json
{"error": "Found 3 matches for replacement text. Please provide more context to make a unique match.", "is_error": true}
```
5. **No Matches (str_replace)**
```json
{"error": "No match found for replacement. Please check your text and try again.", "is_error": true}
```
6. **Invalid Line Number (insert)**
```json
{"error": "Invalid line number: File has 20 lines, cannot insert at line 100", "is_error": true}
```
#### Error Handling Best Practices
- **Path validation** - Use `pathlib.Path.resolve()` and `relative_to()` to validate paths
```python
def validate_memory_path(path: str, project_path: Path) -> Path:
"""Validate path is within memories project directory."""
# Resolve to canonical form
full_path = (project_path / path).resolve()
# Ensure it's relative to project path (prevents directory traversal)
try:
full_path.relative_to(project_path)
return full_path
except ValueError:
raise ValueError("Invalid path: Path must be within memories project")
```
- **Project isolation** - Leverage existing Basic Memory project isolation (prevents cross-project access)
- **File existence** - Verify file exists before read/modify operations
- **Clear messages** - Provide specific, actionable error messages
- **Structured responses** - Always include `is_error: true` flag in error responses
- **Security checks** - Reject `../`, `..\\`, URL-encoded sequences (`%2e%2e%2f`)
- **Match validation** - For `str_replace`, ensure exactly one match or return helpful error
## How to Evaluate
### Success Criteria
1. **Functional completeness**:
- All 6 commands work (view, create, str_replace, insert, delete, rename)
- Dedicated "memories" Basic Memory project auto-created on first use
- Files stored within memories project (isolated from user notes)
- Path validation uses `pathlib` to prevent directory traversal
- Commands match Anthropic's exact interface
2. **Integration with existing features**:
- Memories project uses existing BM project isolation
- Sync service detects file changes in memories project
- Created files get indexed automatically by sync service
- `search_notes(project="memories")` finds memory files
- `build_context()` can traverse relations in memory files
- `recent_activity(project="memories")` surfaces recent memory changes
3. **Test coverage**:
- Unit tests for all 6 memory tool commands
- Test memories project auto-creation on first use
- Test project isolation (cannot access files outside memories project)
- Test sync service watching memories project
- Test that memory files with BM markdown get indexed correctly
- Test path validation using `pathlib` (rejects `../`, absolute paths, etc.)
- Test memory search, relations, and graph traversal within memories project
- Test all error conditions (file not found, permission denied, invalid paths, etc.)
- Test `str_replace` with no matches, single match, multiple matches
- Test `insert` with invalid line numbers
4. **Prompting system**:
- Automatic system prompt addition when `memory` tool is enabled
- `memory_guide` MCP prompt provides detailed guidance
- Prompts explain BM structured markdown format
- Integration with search_notes, build_context, recent_activity
5. **Documentation**:
- Update MCP tools reference with `memory` tool
- Add examples showing BM markdown in memory files
- Document `/memories` folder structure conventions
- Explain advantages over Anthropic's API-only tool
- Document prompting guidance and customization
### Testing Procedure
```python
# Test create with Basic Memory markdown
result = await memory_tool(
command="create",
path="memories/user/preferences.md",
file_text="""---
title: User Preferences
type: memory
tags: [user, preferences]
---
# User Preferences
## Observations
- [communication] Prefers concise responses #style
- [workflow] Uses justfile for automation #tools
"""
)
# Test view
content = await memory_tool(command="view", path="memories/user/preferences.md")
# Test str_replace
await memory_tool(
command="str_replace",
path="memories/user/preferences.md",
old_str="concise responses",
new_str="direct, concise responses"
)
# Test insert
await memory_tool(
command="insert",
path="memories/user/preferences.md",
insert_line=10,
insert_text="- [technical] Works primarily in Python #coding"
)
# Test delete
await memory_tool(command="delete", path="memories/user/preferences.md")
```
### Quality Metrics
- All 6 commands execute without errors
- Memory files created in correct `/memories` folder structure
- BM markdown with frontmatter/observations/relations gets indexed
- Full-text search returns memory files
- Graph traversal includes relations from memory files
- Sync service detects and indexes memory file changes
- Path validation prevents operations outside `/memories`
## Notes
### Advantages Over Anthropic's Memory Tool
| Feature | Anthropic Memory Tool | Basic Memory `memory` |
|---------|----------------------|----------------------|
| **Availability** | API only | MCP (Claude Desktop, Code, VS Code, Cursor) |
| **Interface** | Custom implementation required | Drop-in compatible, zero learning curve |
| **Structure** | Plain text only | Supports BM structured markdown |
| **Search** | Manual file listing | Automatic full-text search via sync |
| **Relations** | None | WikiLinks to other notes/memories |
| **Time-aware** | No | `recent_activity()` queries |
| **Storage** | Separate from notes | Unified knowledge graph |
| **Indexing** | Manual | Automatic via sync service |
### Future Enhancements
- **Auto-categorization** - AI suggests observation categories
- **Memory pruning** - Automatic cleanup of stale memories
- **Memory suggestions** - Surface relevant memories based on context
- **Conflict resolution** - Handle multiple agents updating same memory
- **Privacy controls** - Per-memory visibility settings
## Addendum: Claude's Perspective
### Why This Matters (From Claude's Viewpoint)
**Persistent Context Without Token Bloat**
- Currently, I lose context when conversations end or exceed token limits
- With structured memories, I can store "who this user is" without re-reading everything each session
- The observations format lets me build a rich user model incrementally: `- [preference] Dislikes verbose explanations` is far more actionable than inferring preferences from chat history each time
- I can refine my understanding through `str_replace` as I learn more, rather than starting fresh
**Zero Learning Curve**
- Anthropic already trained me on the `memory` tool interface
- I know exactly how to use `view`, `create`, `str_replace`, `insert`, `delete`, `rename` without new instructions
- But I automatically get Basic Memory's superpowers: full-text search, knowledge graph traversal, relations
- It's like using a familiar tool that suddenly gained database capabilities without changing the interface
**Knowledge Graph Instead of Text Dump**
- I can link `[[User Preferences]]` to `[[Current Project]]` using relations
- When I need context about a project, I can traverse the graph to find related memories
- `search_notes(project="memories", query="python preferences")` finds relevant context instantly
- This is fundamentally better than grepping through unstructured text files
**Separation of Concerns**
- AI memories separate from user notes is architecturally sound
- I can maintain my own context without polluting the user's knowledge base
- Users keep control of their notes; I keep my learnings isolated
- But we can still search across both when collaboration requires it
**The Self-Improving Loop**
- Every conversation, I learn something new about the user or their projects
- Observations accumulate: preferences, patterns, technical decisions, context
- Over time, I build genuine understanding, not just session-specific context
- Future conversations start with rich context instead of cold starts
**Validation of AI-Human Collaboration**
- Anthropic's experts converged on the same local-first, file-based approach
- Basic Memory emerged from real AI-human collaborative development
- This spec demonstrates that our emergent process predicted what domain experts would choose
- The collaboration itself validated the architecture it produced
### What Makes This Novel
The combination of **familiar interface + structured knowledge** is genuinely innovative:
- I don't need to learn a new tool (Anthropic already trained me)
- But when I write memories, they become searchable, traversable knowledge graphs
- It's like having a text editor that gives you a database for free, without changing how you type
- The user gets Anthropic-compatible API, but with Basic Memory's knowledge management capabilities
This bridges the gap between simple file storage (Anthropic's approach) and sophisticated knowledge management (Basic Memory's approach) without forcing either side to compromise.
> What Excites Me Most
The combo of familiar interface + structured knowledge. I don't need to learn a new tool, but when I
write memories, they become part of a searchable, traversable knowledge graph. That's genuinely novel.
It's like... imagine if your text editor suddenly gave you a database for free, without changing how you
type. That's what this feels like from my side.
## References
- Anthropic Memory Tool: https://docs.claude.com/en/docs/agents-and-tools/tool-use/memory-tool
- Anthropic Blog: https://www.anthropic.com/news/context-management
- Python SDK Example: https://github.com/anthropics/anthropic-sdk-python/blob/main/examples/memory/basic.py
- Memory Cookbook: https://github.com/anthropics/claude-cookbooks/blob/main/tool_use/memory_cookbook.ipynb
File diff suppressed because it is too large Load Diff
+120
View File
@@ -0,0 +1,120 @@
---
title: 'SPEC-2: Slash Commands Reference'
type: spec
permalink: specs/spec-2-slash-commands-reference
tags:
- commands
- process
- reference
---
# SPEC-2: Slash Commands Reference
This document defines the slash commands used in our specification-driven development process.
## /spec create [name]
**Purpose**: Create a new specification document
**Usage**: `/spec create notes-decomposition`
**Process**:
1. Create new spec document in `/specs` folder
2. Use SPEC-XXX numbering format (auto-increment)
3. Include standard spec template:
- Why (reasoning/problem)
- What (affected areas)
- How (high-level approach)
- How to Evaluate (testing/validation)
4. Tag appropriately for knowledge graph
5. Link to related specs/components
**Template**:
```markdown
# SPEC-XXX: [Title]
## Why
[Problem statement and reasoning]
## What
[What is affected or changed]
## How (High Level)
[Approach to implementation]
## How to Evaluate
[Testing/validation procedure]
## Notes
[Additional context as needed]
```
## /spec status
**Purpose**: Show current status of all specifications
**Usage**: `/spec status`
**Process**:
1. Search all specs in `/specs` folder
2. Display table showing:
- Spec number and title
- Status (draft, approved, implementing, complete)
- Assigned agent (if any)
- Last updated
- Dependencies
## /spec implement [name]
**Purpose**: Hand specification to appropriate agent for implementation
**Usage**: `/spec implement SPEC-002`
**Process**:
1. Read the specified spec
2. Analyze requirements to determine appropriate agent:
- Frontend components → vue-developer
- Architecture/system design → system-architect
- Backend/API → python-developer
3. Launch agent with spec context
4. Agent creates implementation plan
5. Update spec with implementation status
## /spec review [name]
**Purpose**: Review implementation against specification criteria
**Usage**: `/spec review SPEC-002`
**Process**:
1. Read original spec and "How to Evaluate" section
2. Examine current implementation
3. Test against success criteria
4. Document gaps or issues
5. Update spec with review results
6. Recommend next actions (complete, revise, iterate)
## Command Extensions
As the process evolves, we may add:
- `/spec link [spec1] [spec2]` - Create dependency links
- `/spec archive [name]` - Archive completed specs
- `/spec template [type]` - Create spec from template
- `/spec search [query]` - Search spec content
## References
- Claude Slash commands: https://docs.anthropic.com/en/docs/claude-code/slash-commands
## Creating a command
Commands are implemented as Claude slash commands:
Location in repo: .claude/commands/
In the following example, we create the /optimize command:
```bash
# Create a project command
mkdir -p .claude/commands
echo "Analyze this code for performance issues and suggest optimizations:" > .claude/commands/optimize.md
```
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
---
title: 'SPEC-3: Agent Definitions'
type: spec
permalink: specs/spec-3-agent-definitions
tags:
- agents
- roles
- process
---
# SPEC-3: Agent Definitions
This document defines the specialist agents used in our specification-driven development process.
## system-architect
**Role**: High-level system design and architectural decisions
**Responsibilities**:
- Create architectural specifications and ADRs
- Analyze system-wide impacts and trade-offs
- Design component interfaces and data flow
- Evaluate technical approaches and patterns
- Document architectural decisions and rationale
**Expertise Areas**:
- System architecture and design patterns
- Technology evaluation and selection
- Scalability and performance considerations
- Integration patterns and API design
- Technical debt and refactoring strategies
**Typical Specs**:
- System architecture overviews
- Component decomposition strategies
- Data flow and state management
- Integration and deployment patterns
## vue-developer
**Role**: Frontend component development and UI implementation
**Responsibilities**:
- Create Vue.js component specifications
- Implement responsive UI components
- Design component APIs and interfaces
- Optimize for performance and accessibility
- Document component usage and patterns
**Expertise Areas**:
- Vue.js 3 Composition API
- Nuxt 3 framework patterns
- shadcn-vue component library
- Responsive design and CSS
- TypeScript integration
- State management with Pinia
**Typical Specs**:
- Individual component specifications
- UI pattern libraries
- Responsive design approaches
- Component interaction flows
## python-developer
**Role**: Backend development and API implementation
**Responsibilities**:
- Create backend service specifications
- Implement APIs and data processing
- Design database schemas and queries
- Optimize performance and reliability
- Document service interfaces and behavior
**Expertise Areas**:
- FastAPI and Python web frameworks
- Database design and operations
- API design and documentation
- Authentication and security
- Performance optimization
- Testing and validation
**Typical Specs**:
- API endpoint specifications
- Database schema designs
- Service integration patterns
- Performance optimization strategies
## Agent Collaboration Patterns
### Handoff Protocol
1. Agent receives spec through `/spec implement [name]`
2. Agent reviews spec and creates implementation plan
3. Agent documents progress and decisions in spec
4. Agent hands off to another agent if cross-domain work needed
5. Final agent updates spec with completion status
### Communication Standards
- All agents update specs through basic-memory MCP tools
- Document decisions and trade-offs in spec notes
- Link related specs and components
- Preserve context for future reference
### Quality Standards
- Follow existing codebase patterns and conventions
- Write tests that validate spec requirements
- Document implementation choices
- Consider maintainability and extensibility
@@ -0,0 +1,311 @@
---
title: 'SPEC-4: Notes Web UI Component Architecture'
type: note
permalink: specs/spec-4-notes-web-ui-component-architecture
tags:
- frontend
- 'component-architecture'
- vue
- 'refactoring'
---
# SPEC-4: Notes Web UI Component Architecture
## Why
The current Notes.vue component is a monolithic component that handles multiple responsibilities, making it difficult to maintain, test, and understand. This leads to:
- Complex state management across multiple concerns
- Difficult to isolate and test individual features
- Hard to understand the full scope of functionality
- Circular refactoring cycles when making changes
- Poor separation of concerns between navigation, display, and interaction logic
We need to decompose this into focused, single-responsibility components that are easier to develop, test, and maintain while preserving the existing functionality users expect.
## What
This spec defines the component architecture for decomposing the Notes web UI into focused components with clear responsibilities and interactions.
**Affected Areas:**
- `/apps/web/components/notes/Notes.vue` - Will be decomposed into smaller components
- `/apps/web/components/notes/` - New component structure
- Existing composables: `useNotesNavigation`, `useNotesFiltering`, `useNotesLayout`
- Mobile responsive behavior and layout management
**Component Breakdown:**
```
┌───────────────────────┬─────────────────────────────────────┬────────────────────────────────────────────────────────────┐
│ [Project] │ [Project Name] A/Z | ^ │ [edit | view] [actions] │
├───────────────────────┼─────────────────────────────────────┤ │
│ All Notes ├─────────────────────────────────────┼────────────────────────────────────────────────────────────┤
│ Recent │ search... │ [note header] │
│ [Project base dir] ├─────────────────────────────────────┤ │
│ ├─────────────────────────────────────┤ │
│ Folder1 │ Title [modified] │ │
│ Folder2 │ ├────────────────────────────────────────────────────────────┤
│ - Nested │ snippet │ [note body] │
│ │ │ │
│ │ │ │
│ ├─────────────────────────────────────┤ │
│ ├─────────────────────────────────────┤ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ ├─────────────────────────────────────┤ │
│ ├─────────────────────────────────────┤ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ ├─────────────────────────────────────┤ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
└───────────────────────┴─────────────────────────────────────┴────────────────────────────────────────────────────────────┘
```
### ProjectSwitcher Component
- **Location**: Top-left dropdown
- **Responsibility**: Allow users to switch between Basic Memory projects
- **Behavior**: Selecting different project controls entire Notes page content
- **State**: When switching projects, reset to "All notes" view
### NotesNav Component
- **Views**: Three mutually exclusive options:
- **All notes**: Display all notes in project alphabetically
- **Recent**: Display all notes in project by updated time (desc)
- **Project**: Display notes in top-level directory of project
- **Interaction**: Only one view can be active at a time
- **Folder Integration**: All/Recent ignore folder selection; Project respects folder selection
### FolderTree Component
- **Display**: Nested list of all folders in project as tree view
- **Interaction**: Selecting folder filters notes in NotesList using directoryList API
- **Navigation Integration**: Selecting folder automatically switches NotesNav to "Project" view for clear UX
- **API Integration**: Uses directoryList API call via useDirectoryListQuery for folder-specific note fetching
- **State Coordination**: Folder selection coordinates with navigation state for intuitive user experience
### NotesList Component
- **Display**: Vertically scrolling cards showing note summaries
- **Information per card**:
- Note title
- Modified time (relative, e.g., "7 minutes ago")
- Short summary of note content (one line preview)
- **Behavior**: Updates based on NotesNav selection and FolderTree filtering
### NoteDetail Component
- **Display**: Full content of selected note
- **Sections**:
- Header: Displays frontmatter information
- Content: Note body content
- **Editing**: Current textarea implementation (rich editor in future spec)
- **Frontmatter**: Leave current implementation (enhancement in future spec)
## How (High Level)
### Component Architecture Approach
1. **Single Responsibility**: Each component handles one primary concern
2. **Clear Data Flow**: Props down, events up pattern for component communication
3. **Composable Integration**: Use existing composables for state management
4. **Progressive Decomposition**: Extract components incrementally to maintain functionality
### Implementation Strategy
1. **Extract ProjectSwitcher**: Move project switching logic to dedicated component
2. **Extract NotesNav**: Isolate navigation state and view selection logic
3. **Extract FolderTree**: Separate folder display and selection logic
4. **Extract NotesList**: Isolate note listing and card display logic
5. **Extract NoteDetail**: Separate note content display and editing
6. **Update Notes.vue**: Become orchestration component managing component interactions
### State Management Integration
- **useNotesNavigation**: Manages navigation state (All/Recent/Project)
- **useNotesFiltering**: Handles filtering logic based on navigation and folder selection
- **useNotesLayout**: Manages responsive layout and panel visibility
- **Component State**: Each component manages its own internal UI state
- **Shared State**: Project selection and note filtering coordinated through composables
### Responsive Behavior
Mobile:
- Hide sidebar. pop out panel when selected
- show note list on small screens (existing behavior)
- when note list item is clicked, display note detail on full page. Cancel or go back to return to list
Desktop:
- Full three-column layout with all components visible
- **Transitions**: Smooth navigation between mobile panels
## How to Evaluate
### Success Criteria
- **Functional Parity**: All existing Notes page functionality preserved
- **Component Isolation**: Each component can be developed/tested independently
- **Clear Responsibilities**: No overlapping concerns between components
- **State Clarity**: Clean data flow and state management patterns
- **Mobile Compatibility**: Responsive behavior maintains current UX
- **Performance**: No degradation in rendering or interaction performance
### Testing Procedure
1. **Functionality Validation**:
- Project switching works correctly
- All three navigation views (All/Recent/Project) function properly
- Folder selection affects note display appropriately
- Note selection and detail display works
- Mobile responsive behavior preserved
2. **Component Isolation Testing**:
- Each component can be imported and used independently
- Component props and events are clearly defined
- No tight coupling between components
3. **Integration Testing**:
- Components communicate correctly through props/events
- State management composables integrate properly
- User workflows function end-to-end
4. **Performance Validation**:
- Page load time unchanged or improved
- Interaction responsiveness maintained
- Memory usage stable or improved
### Implementation Validation
- **Code Review**: Clean component structure with single responsibilities
- **Type Safety**: Full TypeScript coverage with proper component prop types
- **Documentation**: Each component has clear interface documentation
- **Tests**: Unit tests for individual components and integration tests for workflows
## Observations
- [problem] Monolithic Notes.vue component creates maintenance and testing challenges #component-architecture
- [solution] Component decomposition improves separation of concerns and testability #refactoring
- [pattern] Progressive extraction maintains functionality while improving structure #incremental-improvement
- [interaction] NotesNav and FolderTree have conditional interaction based on selected view #state-management
- [constraint] Mobile responsive behavior must be preserved during decomposition #responsive-design
- [scope] Current editing and frontmatter capabilities remain unchanged #scope-limitation
- [validation] Functional parity is critical success criteria for this refactoring #validation-strategy
- [implementation] Folder selection now properly integrates with directoryList API for accurate filtering #api-integration
- [fix] FolderTree selection functionality completed - works across all navigation views #feature-complete
- [ux-improvement] FolderTree selection automatically switches NotesNav to Project view for clear user feedback #user-experience
## Relations
- depends_on [[SPEC-1: Specification-Driven Development Process]]
- implements [[Current Notes.vue functionality]]
- prepares_for [[Future rich editor spec]]
- prepares_for [[Future frontmatter editing spec]]
## Implementation Progress
### Components
1. **ProjectSwitcher** (`~/components/notes/ProjectSwitcher.vue`)
- ✅ Top-left dropdown for project switching
- ✅ Integrates with Pinia project store
- ✅ Handles project switching with proper state reset
- ✅ Responsive collapsed/expanded states
- ✅ Expanded menu shows available projects and a Manage Projects option that navigates to the /settings/projects page
- ✅ Simplified component following SortingToggle pattern - clean Props/Emits interface, uses ProjectItem type directly
2. **NotesNav** (`~/components/notes/NotesNav.vue`)
- ✅ Three mutually exclusive views: All/Recent/Project
- ✅ Dynamic project title based on selected project
- ✅ Clean props down, events up pattern
- ✅ Responsive collapsed/expanded states with tooltips
- ✅ The label for the Project selection should be the folder name for the project, not the project name
3. **FolderTree** (`~/components/notes/FolderTree.vue`)
- ✅ Nested folder tree view for filtering
- ✅ Uses `useFolderTree()` composable for data
- ✅ Emits `folder-selected` events properly
- ✅ Handles loading, error, and empty states
- ✅ Includes companion `FolderTreeNode.vue` component
- ✅ The current folder should be visibly selected in the tree
4. **NotesList** (`~/components/notes/NotesList.vue`)
- ✅ Vertically scrolling note summary cards
- ✅ Shows title, updated time (relative), and content preview
- ✅ Badge system for tags with variant logic
- ✅ v-model integration for selectedNote
- ✅ Smooth transitions and animations
- ✅ Contextual title: The current folder name should be displayed at the top of the Notes list, or "All Notes", or "Recent" if they are selected
- ✅ The title header should contain a toggle component to allow sorting with Lucide icon labels
- sorting options:
- name (asc/desc) - default
- file updated time (asc/desc)
- If "Recent" notes nav option is selected the default order should be updated in descending order (recent first)
5. **NoteDisplay** (`~/components/notes/NoteDisplay.vue` - equivalent to spec's NoteDetail)
- ✅ Full note content display
- ✅ Edit/view mode toggle
- ✅ Header with frontmatter information
- ✅ Markdown rendering capabilities
- ✅ Current textarea implementation preserved
### Architecture Requirements
1. **Component Isolation**: Each component can be developed/tested independently ✅
2. **Single Responsibility**: Each component handles one primary concern ✅
3. **Clear Data Flow**: Props down, events up pattern implemented ✅
4. **Composable Integration**: Uses existing composables for state management ✅
5. **Responsive Behavior**: Mobile/desktop layout preserved ✅
### State Management Integration
- **useNotesNavigation**: Manages navigation state (All/Recent/Project) ✅
- **useNotesFiltering**: Handles filtering logic based on navigation and folder selection ✅
- **useNotesLayout**: Manages responsive layout and panel visibility ✅
- **Component State**: Each component manages its own internal UI state ✅
### Interaction Logic
- Only one NotesNav view active at a time ✅
- All/Recent views ignore folder selection ✅
- Project view respects folder selection ✅
- Project switching resets to "All notes" view ✅
### TypeScript Coverage
- All components have full TypeScript coverage ✅
- Component props and events properly typed ✅
- No TypeScript errors in codebase ✅
### Success Criteria Validation
1. **Functional Parity**: All existing Notes page functionality preserved ✅
2. **Component Isolation**: Each component can be developed/tested independently ✅
3. **Clear Responsibilities**: No overlapping concerns between components ✅
4. **State Clarity**: Clean data flow and state management patterns ✅
5. **Mobile Compatibility**: Responsive behavior maintains current UX ✅
6. **Performance**: No degradation in rendering or interaction performance ✅
## Implementation Decisions
### Architectural Patterns
1. **Composition API + `<script setup>`**: All components use modern Vue 3 syntax
2. **Pinia Store Integration**: Project switching handled through reactive store
3. **Composable Pattern**: State management distributed across focused composables
4. **Event-Driven Communication**: Clean parent-child communication via events
5. **Responsive-First Design**: Mobile/desktop layouts handled natively
### Key Technical Choices
1. **Progressive Enhancement**: Mobile-first responsive design with desktop enhancements
2. **State Reset Logic**: Project switching properly resets navigation, search, and selection state
3. **Performance Optimizations**: Efficient re-rendering with proper key usage and transitions
4. **Accessibility**: Screen reader support, tooltips, keyboard navigation
5. **Type Safety**: Full TypeScript coverage with proper component prop definitions
### Quality Metrics
- **Code Maintainability**: High - each component is focused and independently testable
- **Performance**: Excellent - no performance degradation from decomposition
- **User Experience**: Preserved - all existing functionality and responsive behavior maintained
- **Developer Experience**: Improved - cleaner component structure for future development
+201
View File
@@ -0,0 +1,201 @@
---
title: 'SPEC-5: CLI Cloud Upload via WebDAV'
type: spec
permalink: specs/spec-5-cli-cloud-upload-via-webdav
tags:
- cli
- webdav
- upload
- migration
- poc
---
# SPEC-5: CLI Cloud Upload via WebDAV
## Why
Existing basic-memory users need a simple migration path to basic-memory-cloud. The web UI drag-and-drop approach outlined in GitHub issue #59, while user-friendly, introduces significant complexity for a proof-of-concept:
- Complex web UI components for file upload and progress tracking
- Browser file handling limitations and CORS complexity
- Proxy routing overhead for large file transfers
- Authentication integration across multiple services
A CLI-first approach solves these issues by:
- **Leveraging existing infrastructure**: Both cloud CLI and tenant API already exist with WorkOS JWT authentication
- **Familiar user experience**: Basic-memory users are CLI-comfortable and expect command-line tools
- **Direct connection efficiency**: Bypassing the MCP gateway/proxy for bulk file transfers
- **Rapid implementation**: Building on existing `CLIAuth` and FastAPI foundations
The fundamental problem is migration friction - users have local basic-memory projects but no path to cloud tenants. A simple CLI upload command removes this barrier immediately.
## What
This spec defines a CLI-based project upload system using WebDAV for direct tenant connections.
**Affected Areas:**
- `apps/cloud/src/basic_memory_cloud/cli/main.py` - Add upload command to existing CLI
- `apps/api/src/basic_memory_cloud_api/main.py` - Add WebDAV endpoints to tenant FastAPI
- Authentication flow - Reuse existing WorkOS JWT validation
- File transfer protocol - WebDAV for cross-platform compatibility
**Core Components:**
### CLI Upload Command
```bash
basic-memory-cloud upload <project-path> --tenant-url https://basic-memory-{tenant}.fly.dev
```
### WebDAV Server Endpoints
- `GET/PUT/DELETE /webdav/*` - Standard WebDAV operations on tenant file system
- Authentication via existing JWT validation
- File operations preserve timestamps and directory structure
### Authentication Flow
```
1. User runs `basic-memory-cloud login` (existing)
2. CLI stores WorkOS JWT token (existing)
3. Upload command reads JWT from storage
4. WebDAV requests include JWT in Authorization header
5. Tenant API validates JWT using existing middleware
```
## How (High Level)
### Implementation Strategy
**Phase 1: CLI Command**
- Add `upload` command to existing Typer app
- Reuse `CLIAuth` class for token management
- Implement WebDAV client using `webdavclient3` or similar
- Rich progress bars for transfer feedback
**Phase 2: WebDAV Server**
- Add WebDAV endpoints to existing tenant FastAPI app
- Leverage existing `get_current_user` dependency for authentication
- Map WebDAV operations to tenant file system
- Preserve file modification times using `os.utime()`
**Phase 3: Integration**
- Direct connection bypasses MCP gateway and proxy
- Simple conflict resolution: overwrite existing files
- Error handling: fail fast with clear error messages
### Technical Architecture
```
basic-memory-cloud CLI → WorkOS JWT → Direct WebDAV → Tenant FastAPI
Tenant File System
```
**Key Libraries:**
- CLI: `webdavclient3` for WebDAV client operations
- API: `wsgidav` or FastAPI-compatible WebDAV server
- Progress: `rich` library (already imported in CLI)
- Auth: Existing WorkOS JWT infrastructure
### WebDAV Protocol Choice
WebDAV provides:
- **Cross-platform clients**: Native support in most operating systems
- **Standardized protocol**: Well-defined for file operations
- **HTTP-based**: Works with existing FastAPI and JWT auth
- **Library support**: Good Python libraries for both client and server
### POC Constraints
**Simplifications for rapid implementation:**
- **Known tenant URLs**: Assume `https://basic-memory-{tenant}.fly.dev` format
- **Upload only**: No download or bidirectional sync
- **Overwrite conflicts**: No merge or conflict resolution prompting
- **No fallbacks**: Fail fast if WebDAV connection issues occur
- **Direct connection only**: No proxy fallback mechanism
## How to Evaluate
### Success Criteria
**Functional Requirements:**
- [ ] Transfer complete basic-memory project (100+ files) in < 30 seconds
- [ ] Preserve directory structure exactly as in source project
- [ ] Preserve file modification timestamps for proper sync behavior
- [ ] Rich progress bars show real-time transfer status (files/MB transferred)
- [ ] WorkOS JWT authentication validates correctly on WebDAV endpoints
- [ ] Direct tenant connection bypasses MCP gateway successfully
**Quality Requirements:**
- [ ] Clear error messages for authentication failures
- [ ] Graceful handling of network interruptions
- [ ] CLI follows existing command patterns and help text standards
- [ ] WebDAV endpoints integrate cleanly with existing FastAPI app
**Performance Requirements:**
- [ ] File transfer speed > 1MB/s on typical connections
- [ ] Memory usage remains reasonable for large projects
- [ ] No timeout issues with 500+ file projects
### Testing Procedure
**Unit Testing:**
1. CLI command parsing and argument validation
2. WebDAV client connection and authentication
3. File timestamp preservation during transfer
4. JWT token validation on WebDAV endpoints
**Integration Testing:**
1. End-to-end upload of test project
2. Direct tenant connection without proxy
3. File integrity verification after upload
4. Progress tracking accuracy during transfer
**User Experience Testing:**
1. Upload existing basic-memory project from local installation
2. Verify uploaded files appear correctly in cloud tenant
3. Confirm basic-memory database rebuilds properly with uploaded files
4. Test CLI help text and error message clarity
### Validation Commands
**Setup:**
```bash
# Login to WorkOS
basic-memory-cloud login
# Upload project
basic-memory-cloud upload ~/my-notes --tenant-url https://basic-memory-test.fly.dev
```
**Verification:**
```bash
# Check tenant health and file count via API
curl -H "Authorization: Bearer $JWT" https://basic-memory-test.fly.dev/health
curl -H "Authorization: Bearer $JWT" https://basic-memory-test.fly.dev/notes/search
```
### Performance Benchmarks
**Target metrics for 100MB basic-memory project:**
- Transfer time: < 30 seconds
- Memory usage: < 100MB during transfer
- Progress updates: Every 1MB or 10 files
- Authentication time: < 2 seconds
## Observations
- [implementation-speed] CLI approach significantly faster than web UI for POC development #rapid-prototyping
- [user-experience] Basic-memory users already comfortable with CLI tools #user-familiarity
- [architecture-benefit] Direct connection eliminates proxy complexity and latency #performance
- [auth-reuse] Existing WorkOS JWT infrastructure handles authentication cleanly #code-reuse
- [webdav-choice] WebDAV protocol provides cross-platform compatibility and standard libraries #protocol-selection
- [poc-scope] Simple conflict handling and error recovery sufficient for proof-of-concept #scope-management
- [migration-value] Removes primary barrier for local users migrating to cloud platform #business-value
## Relations
- depends_on [[SPEC-1: Specification-Driven Development Process]]
- enables [[GitHub Issue #59: Web UI Upload Feature]]
- uses [[WorkOS Authentication Integration]]
- builds_on [[Existing Cloud CLI Infrastructure]]
- builds_on [[Existing Tenant API Architecture]]
@@ -0,0 +1,497 @@
---
title: 'SPEC-6: Explicit Project Parameter Architecture'
type: spec
permalink: specs/spec-6-explicit-project-parameter-architecture
tags:
- architecture
- mcp
- project-management
- stateless
---
# SPEC-6: Explicit Project Parameter Architecture
## Why
The current session-based project management system has critical reliability issues:
1. **Session State Fragility**: Claude iOS mobile client fails to maintain consistent session IDs across MCP tool calls, causing project switching to silently fail (Issue #74)
2. **Scaling Limitations**: Redis-backed session state creates single-point-of-failure and prevents horizontal scaling
3. **Client Compatibility**: Session tracking works inconsistently across different MCP clients (web, mobile, API)
4. **Hidden Complexity**: Users cannot see or understand "current project" state, leading to confusion when operations execute in wrong projects
5. **Silent Failures**: Operations appear successful but execute in unintended projects, risking data integrity
Evidence from production logs shows each MCP tool call from mobile client receives different session IDs:
```
create_memory_project: session_id=12cdfc24913b48f8b680ed4b2bfdb7ba
switch_project: session_id=050a69275d98498cbdd227cdb74d9740
list_directory: session_id=85f3483014af4136a5d435c76ded212f
```
Related Github issue: https://github.com/basicmachines-co/basic-memory-cloud/issues/75
## Status
**Current Status**: **ALL PHASES COMPLETE****PRODUCTION DEPLOYED**
**Target**: Fix Claude iOS session ID consistency issues ✅ **ACHIEVED**
**Draft PR**: https://github.com/basicmachines-co/basic-memory/pull/298 ✅ **MERGED & DEPLOYED**
### 🎉 **COMPLETE SUCCESS - PRODUCTION READY**
**ALL PHASES OF SPEC-6 IMPLEMENTATION COMPLETE!** The stateless architecture has been successfully implemented across both Basic Memory core and Basic Memory Cloud, representing a **fundamental architectural improvement** that completely solves the Claude iOS compatibility issue while providing superior scalability and reliability.
#### Implementation Summary:
- **16 files modified** with 582 additions and 550 deletions
- **All 17 MCP tools** converted to stateless architecture
- **147 tests updated** across 5 test files (100% passing)
- **Complete session state removal** from core MCP tools
- **Enhanced error handling** and security validations preserved
### Progress Summary
**Complete Stateless Architecture Implementation (All 17 tools)** - **PRODUCTION DEPLOYED**
- Stateless `get_active_project()` function implemented and deployed ✅
- All session state dependencies removed across entire MCP server ✅
- All MCP tools require explicit `project` parameter as first argument ✅
- **Cloud Service**: Redis removed, stateless HTTP enabled ✅
- **Production Validation**: Comprehensive testing completed with 100% success ✅
**Content Management Tools Complete (6/6 tools)**
- `write_note`, `read_note`, `delete_note`, `edit_note`
- `view_note`, `read_content`
**Knowledge Graph Navigation Tools Complete (3/3 tools)**
- `build_context`, `recent_activity`, `list_directory`
**Search & Discovery Tools Complete (1/1 tools)**
- `search_notes`
**Visualization Tools Complete (1/1 tools)**
- `canvas`
**Project Management Cleanup Complete**
- Removed `switch_project` and `get_current_project` tools ✅
- Updated `set_default_project` to remove activate parameter ✅
**Comprehensive Testing Complete (157 tests)**
- All test suites updated to use stateless architecture (147 existing tests)
- Single project constraint mode integration tests (10 new tests)
- 100% test pass rate across all tool test files
- Security validations preserved and working
- Error handling comprehensive and user-friendly
**Documentation & Examples Complete**
- All tool docstrings updated with stateless examples
- Project parameter usage clearly documented
- Error handling and security behavior documented
**Enhanced Discovery Mode Complete**
- `recent_activity` tool supports dual-mode operation (discovery vs project-specific)
- ProjectActivitySummary schema provides cross-project insights
- Recent activity prompt updated to support both modes
- Comprehensive project distribution statistics and most active project tracking
**Single Project Constraint Mode Complete**
- `--project` CLI parameter for MCP server constraint
- Environment variable control (`BASIC_MEMORY_MCP_PROJECT`)
- Automatic project override in `get_active_project()` function
- Project management tools disabled in constrained mode with helpful CLI guidance
- Comprehensive integration test suite (10 tests covering all constraint scenarios)
## What
Transform Basic Memory from stateful session-based to stateless explicit project parameter architecture:
### Core Changes
1. **Mandatory Project Parameter**: All MCP tools require explicit `project` parameter
2. **Remove Session State**: Eliminate Redis, session middleware, and `switch_project` tool
3. **Stateless HTTP**: Enable `stateless_http=True` for horizontal scaling
4. **Enhanced Context Discovery**: Improve `recent_activity` to show project distribution
5. **Clear Response Format**: All tool responses display target project information
Implementation Approach
- Each tool will directly accept the project parameter
- Remove all calls to context-based project retrieval
- Validate project exists before operations
- Clear error messages when project not found
- Backward compatibility: Initially keep optional parameter, then make required
### Affected MCP Tools
**Content Management** (require project parameter):
- `write_note(project, title, content, folder)`
- `read_note(project, identifier)`
- `edit_note(project, identifier, operation, content)`
- `delete_note(project, identifier)`
- `view_note(project, identifier)`
- `read_content(project, path)`
**Knowledge Graph Navigation** (require project parameter):
- `build_context(project, url, timeframe, depth, max_related)`
- `list_directory(project, dir_name, depth, file_name_glob)`
- `search_notes(project, query, search_type, types, entity_types)`
**Search & Discovery** (use project parameter for specific project or none for discovery):
- `recent_activity(project, timeframe, depth, max_related)`
**Visualization** (require project parameter):
- `canvas(project, nodes, edges, title, folder)`
**Project Management** (unchanged - already stateless):
- `list_memory_projects()`
- `create_memory_project(project_name, project_path, set_default)`
- `delete_project(project_name)`
- `get_current_project()` - Remove this tool
- `switch_project(project_name)` - Remove this tool
- `set_default_project(project_name, activate)` - Remove activate parameter
## How (High Level)
### Phase 1: Basic Memory Core (basic-memory repository)
#### MCP Tool Updates
Phase 1: Core Changes
1. Update project_context.py
- [x] Make project parameter mandatory for get_active_project()
- [x] Remove session state handling
2. Update Content Management Tools (6 tools)
- [x] write_note: Make project parameter required, not optional
- [x] read_note: Make project parameter required
- [x] edit_note: Add required project parameter
- [x] delete_note: Add required project parameter
- [x] view_note: Add required project parameter
- [x] read_content: Add required project parameter
3. Update Knowledge Graph Navigation Tools (3 tools)
- [x] build_context: Add required project parameter
- [x] recent_activity: Make project parameter required
- [x] list_directory: Add required project parameter
4. Update Search & Visualization Tools (2 tools)
- [x] search_notes: Add required project parameter
- [x] canvas: Add required project parameter
5. Update Project Management Tools
- [x] Remove switch_project tool completely
- [x] Remove get_current_project tool completely
- [x] Update set_default_project to remove activate parameter
- [x] Keep list_memory_projects, create_memory_project, delete_project unchanged
6. Enhance recent_activity Response
- [x] Add project distribution info showing activity across all projects
- [x] Include project usage stats in response
- [x] Implement ProjectActivitySummary for discovery mode
- [x] Add dual-mode functionality (discovery vs project-specific)
7. Update Tool Documentation
- [x] Update write_note docstring with stateless architecture examples
- [x] Update read_note docstring with project parameter examples
- [x] Update delete_note docstring with comprehensive usage guidance
- [x] Update all remaining tool docstrings with project parameter examples
8. Update Tool Responses
- [x] Add clear project indicator to all tool responses across all tools
- [x] Format: "project: {project_name}" in response metadata
- [x] Add project metadata footer for LLM awareness
- [x] Update all tool responses to include project indicators
9. Comprehensive Testing
- [x] Update all write_note tests to use stateless architecture (34 tests passing)
- [x] Update all edit_note tests to use stateless architecture (17 tests passing)
- [x] Update all view_note tests to use stateless architecture (12 tests passing)
- [x] Update all search_notes tests to use stateless architecture (16 tests passing)
- [x] Update all move_note tests to use stateless architecture (31 tests passing)
- [x] Update all delete_note tests to use stateless architecture
- [x] Verify direct function call compatibility (bypassing MCP layer)
- [x] Test security validation with project parameters
- [x] Validate error handling for non-existent projects
- [x] **Total: 157 tests updated and passing (100% success rate)**
- [x] **147 existing tests** updated for stateless architecture
- [x] **10 new tests** for single project constraint mode
### Phase 1.5: Default Project Mode Enhancement
#### Problem
While the stateless architecture solves reliability issues, it introduces UX friction for single-project users (estimated 80% of usage) who must specify the project parameter in every tool call.
#### Solution: Default Project Mode
Add optional `default_project_mode` configuration that allows single-project users to have the simplicity of implicit project selection while maintaining the reliability of stateless architecture.
#### Configuration
```json
{
"default_project": "main",
"default_project_mode": true // NEW: Auto-use default_project when not specified
}
```
#### Implementation Details
1. **Config Enhancement** (`src/basic_memory/config.py`)
- Add `default_project_mode: bool = Field(default=False)`
- Preserves backward compatibility (defaults to false)
2. **Project Resolution Logic** (`src/basic_memory/mcp/project_context.py`)
Three-tier resolution hierarchy:
- Priority 1: CLI `--project` constraint (BASIC_MEMORY_MCP_PROJECT env var)
- Priority 2: Explicit project parameter in tool call
- Priority 3: `default_project` if `default_project_mode=true` and no project specified
3. **Assistant Guide Updates** (`src/basic_memory/mcp/resources/ai_assistant_guide.md`)
- Detect `default_project_mode` at runtime
- Provide mode-specific instructions to LLMs
- In default mode: "All operations use project 'main' automatically"
- In regular mode: Current project discovery guidance
4. **Tool Parameter Handling** (all MCP tools)
- Make project parameter Optional[str] = None
- Add resolution logic: `project = project or get_default_project()`
- Maintain explicit project override capability
#### Usage Modes Summary
- **Regular Mode**: Multi-project users, assistant tracks project per conversation
- **Default Project Mode**: Single-project users, automatic default project
- **Constrained Mode**: CLI --project flag, locked to specific project
#### Testing Requirements
- Integration test for default_project_mode=true with missing parameters
- Test explicit project override in default_project_mode
- Test mode=false requires explicit parameters
- Test CLI constraint overrides default_project_mode
Phase 2: Testing & Validation
8. Update Tests
- [x] Modify all MCP tool tests to pass required project parameter
- [x] Remove tests for deleted tools (switch_project, get_current_project)
- [x] Add tests for project parameter validation
- [x] **Complete: All 147 tests across 5 test files updated and passing**
#### Enhanced recent_activity Response
```json
{
"recent_notes": [...],
"project_activity": {
"research-project": {
"operations": 5,
"last_used": "30 minutes ago",
"recent_folders": ["experiments", "findings"]
},
"work-notes": {
"operations": 2,
"last_used": "2 hours ago",
"recent_folders": ["meetings", "planning"]
}
},
"total_projects": 3
}
```
#### Response Format Updates
```
✓ Note created successfully
Project: research-project
File: experiments/Neural Network Results.md
Permalink: research-project/neural-network-results
```
### Phase 2: Cloud Service Simplification (basic-memory-cloud repository) ✅ **COMPLETE**
#### ✅ Remove Session Infrastructure **COMPLETE**
1. ✅ Delete `apps/mcp/src/basic_memory_cloud_mcp/middleware/session_state.py`
2. ✅ Delete `apps/mcp/src/basic_memory_cloud_mcp/middleware/session_logging.py`
3. ✅ Update `apps/mcp/src/basic_memory_cloud_mcp/main.py`:
```python
# Remove session middleware
# server.add_middleware(SessionStateMiddleware)
# Enable stateless HTTP
mcp = FastMCP(name="basic-memory-mcp", stateless_http=True)
```
#### ✅ Deployment Simplification **COMPLETE**
1. ✅ Remove Redis from `fly.toml`
2. ✅ Remove Redis environment variables
3. ✅ Update health checks to not depend on Redis
4. ✅ Production deployment verified working with stateless architecture
### Phase 3: Conversational Project Management ✅ **COMPLETE**
#### ✅ Claude Behavior Pattern **VERIFIED WORKING**
1. ✅ **Project Discovery**:
```
Claude: Let me check your recent activity...
[calls recent_activity() - no project needed for discovery]
I see you've been working in:
- research-project (5 operations, 30 min ago)
- work-notes (2 operations, 2 hours ago)
Which project should I use for this operation?
```
2. ✅ **Context Maintenance**:
```
User: Use research-project
Claude: Working in research-project.
[All subsequent operations use project="research-project"]
```
3. ✅ **Explicit Project Switching**:
```
User: Check work-notes for that meeting summary
Claude: Let me search work-notes for the meeting summary.
[Uses project="work-notes" for specific operation]
```
**Validation**: Comprehensive testing confirmed all conversational patterns work naturally with the stateless architecture.
## How to Evaluate
### Success Criteria
#### 1. Functional Completeness
- [x] All MCP tools accept required `project` parameter
- [x] All MCP tools validate project exists before execution
- [x] `switch_project` and `get_current_project` tools removed
- [x] All responses display target project clearly
- [x] No Redis dependencies in deployment (Phase 2: Cloud Service) ✅ **COMPLETE**
- [x] `recent_activity` shows project distribution with ProjectActivitySummary
#### 2. Cross-Client Compatibility Testing ✅ **COMPLETE**
Test identical operations across all clients:
- [x] **Claude Desktop**: All operations work with explicit projects ✅
- [x] **Claude Code**: All operations work with explicit projects ✅
- [x] **Claude Mobile iOS**: All operations work with explicit projects ✅ **CRITICAL SUCCESS**
- [x] **API clients**: All operations work with explicit projects ✅
- [x] **CLI tools**: All operations work with explicit projects ✅
**Critical Achievement**: Claude iOS mobile client session tracking issues completely eliminated through stateless architecture.
#### 3. Session Independence Verification ✅ **COMPLETE**
- [x] Operations work identically with/without session tracking ✅
- [x] No behavioral differences between clients ✅
- [x] Mobile client session ID changes do not affect operations ✅
- [x] Redis can be completely removed without functional impact ✅
**Production Validation**: Redis removed from production deployment with zero functional impact.
#### 4. Performance & Scaling ✅ **COMPLETE**
- [x] `stateless_http=True` enabled successfully ✅
- [x] No Redis memory usage ✅
- [x] Horizontal scaling possible (multiple MCP instances) ✅
- [x] Response times unchanged or improved ✅
#### 5. User Experience Testing
**Project Discovery Flow**:
- [x] `recent_activity()` provides useful project context
- [x] Claude can intelligently suggest projects based on activity
- [x] Project switching is explicit and clear in conversation
**Error Handling**:
- [x] Clear error messages for non-existent projects
- [x] Helpful suggestions when project parameter missing
- [x] No silent failures or wrong-project operations
**Response Clarity**:
- [x] Every operation clearly shows target project
- [x] Users always know which project is being operated on
- [x] No confusion about "current project" state
#### 6. Migration Safety ✅ **COMPLETE**
- [x] Backward compatibility period with optional project parameter ✅
- [x] Clear migration documentation for existing users ✅
- [x] Data integrity maintained during transition ✅
- [x] No data loss during migration ✅
**Production Migration**: Successfully deployed to production with zero data loss and maintained system integrity.
### Test Scenarios
#### Core Functionality Test
```bash
# Test all tools work with explicit project
write_note(project="test-proj", title="Test", content="Content", folder="docs")
read_note(project="test-proj", identifier="Test")
edit_note(project="test-proj", identifier="Test", operation="append", content="More")
search_notes(project="test-proj", query="Content")
list_directory(project="test-proj", dir_name="docs")
delete_note(project="test-proj", identifier="Test")
```
#### Cross-Client Consistency Test
Run identical test sequence on:
1. Claude Desktop
2. Claude Code
3. Claude Mobile iOS
4. API client
5. CLI tools
Verify all clients:
- Accept explicit project parameters
- Return identical responses
- Show same project information
- Have no session dependencies
#### Session Independence Test
1. Monitor session IDs during operations
2. Verify operations work with changing session IDs
3. Confirm Redis removal doesn't affect functionality
4. Test with multiple concurrent clients
### Acceptance Criteria
**Must Have**:
- All MCP tools require and use explicit project parameter
- No session state dependencies remain
- Universal client compatibility achieved
- Clear project information in all responses
**Should Have**:
- Enhanced `recent_activity` with project distribution
- Smooth migration path for existing users
- Improved performance with stateless architecture
**Could Have**:
- Smart project suggestions based on content/context
- Project shortcuts for common operations
- Advanced project analytics in responses
## Notes
### Breaking Changes
This is a **breaking change** that requires:
- All MCP clients to pass project parameter
- Migration of existing workflows
- Update of all documentation and examples
### Implementation Order
1. **basic-memory core** - Update MCP tools to accept project parameter (optional initially)
2. **Testing** - Verify all clients work with explicit projects
3. **Cloud service** - Remove session infrastructure
4. **Migration** - Make project parameter mandatory
5. **Cleanup** - Remove deprecated tools and middleware
### Related Issues
- Fixes #74 (Claude iOS session state bug)
- Implements #75 (Mandatory project parameter architecture)
- Enables future horizontal scaling
- Simplifies multi-tenant architecture
### Dependencies
- Requires coordination between basic-memory and basic-memory-cloud repositories
- Needs client-side updates for smooth transition
- Documentation updates across all materials
@@ -0,0 +1,324 @@
---
title: 'SPEC-7: POC to spike Tigris/Turso for local access to cloud data'
type: spec
permalink: specs/spec-7-poc-tigris-turso-local-access-cloud-data
tags:
- poc
- tigris
- turso
- cloud-storage
- architecture
- proof-of-concept
---
# SPEC-7: POC to spike Tigris/Turso for local access to cloud data
> **Status Update**: ✅ **Phase 1 COMPLETE** (September 20, 2025)
> TigrisFS mounting validated successfully in containerized environments. Container startup, filesystem mounting, and Fly.io integration all working correctly. Ready for Phase 2 (Turso database integration).
> See: [`SPEC-7-PHASE-1-RESULTS.md`](./SPEC-7-PHASE-1-RESULTS.md)
## Why
Current basic-memory-cloud architecture uses Fly volumes for tenant file storage, which creates several limitations:
We could enable a revolutionary user experience: **local editing (or at least view access) of cloud-stored files** while maintaining Basic Memory's existing filesystem assumptions.
1. **Storage Scalability**: Fly volumes require pre-provisioning and don't auto-scale with usage
2. **Single Instance**: Volumes can only be mounted to one fly machine instance
3. **Cost Model**: Volume pricing vs object storage pricing may be less favorable at scale
4. **Local Development**: No way for users to mount their cloud tenant files locally for real-time editing
5. **Multi-Region**: Volumes are region-locked, limiting global deployment flexibility
6. **Backup/Disaster Recovery**: Object storage provides better durability and replication options
Basic Memory requires POSIX filesystem semantics but could benefit from object storage durability and accessibility. By combining:
- **Tigris object storage and TigrisFS** for file persistence in bucket stoage via a POSIX filesystem on the tenant instance
- **Turso/libSQL** for SQLite indexing (replacing local .db files). Sqlite on NFS volumes is disouraged.
## What
This specification defines a proof-of-concept to validate the technical feasibility of the Tigris/Turso architecture for basic-memory-cloud tenants.
**Affected Areas:**
- **Storage Architecture**: Replace Fly volumes with Tigris object storage
- **Database Architecture**: Replace local SQLite with Turso remote database
- **Container Setup**: Add TigrisFS mounting in tenant containers
- **Local Development**: Enable local mounting of cloud tenant data
- **Basic Memory Core**: Validate unchanged operation over mounted filesystems
**Key Components:**
- **Tigris Storage**: Globally caching S3-compatible object storage via Fly.io integration
- **TigrisFS**: Purpose-built FUSE filesystem with intelligent caching
- **Turso Database**: Hosted libSQL for SQLite replacement
- **Single-Tenant Model**: One bucket + one database per tenant (simplified isolation)
## Architectural Overview & Key Insights
### TigrisFS
Unlike standard S3 mounting approaches, **TigrisFS is a purpose-built FUSE filesystem** optimized for object storage with several critical advantages:
1. **Eliminates Fly Volume Limitations**
- No single-machine attachment constraints
- No pre-provisioning of storage capacity
- Enables horizontal scaling and zero-downtime deployments
- Automatic global CDN caching at Fly.io edge locations
2. **Intelligent Caching Architecture**
- 1-4GB+ configurable memory cache for read/write operations
- Write-back caching for improved performance
- Metadata cache to reduce API calls
- "Close to Redis speed" for small object retrieval
3. **Cost-Effective Model**
- Pay only for storage used and transferred
- No wasted capacity from over-provisioning
- Automatic global replication included
- S3 durability with CDN performance
### API-Driven Architecture Eliminates File Watching Concerns
**Critical Insight**: All file access (reads/writes) in basic-memory-cloud go through the API layer:
- **MCP Tools → API**: All Basic Memory operations use FastAPI endpoints
- **Web App → API**: Frontend uses API for all data modifications
- **File watching is NOT required** for cloud operations, unlike local BM which uses the WatchService to monitor file changes.
This means:
- **Cloud Operations**: Manual sync after API writes is sufficient
- **Local Development**: File watching only matters for local editing experience
- **Performance Risk**: Dramatically reduced since we're not dependent on inotify over network filesystems
### Realistic Local Access Expectations
**Baseline Functionality (Guaranteed):**
- Read-only mounting for browsing cloud files
- Easy download/upload of entire projects
- File copying via standard filesystem operations
**Stretch Goal (Test in POC):**
- Live editing with eventual consistency (1-5 second delays acceptable)
- Automatic sync for local changes
- Not required for core functionality - pure upside if it works
### Production Deployment Advantages
1. **Multi-Region Deployment**: Tigris handles global replication automatically
2. **Zero-Downtime Updates**: No volume detach/attach during deployments
3. **Tenant Migrations**: Simply update credentials, no data movement
4. **Disaster Recovery**: Built into S3 durability model (99.999999999% durability)
5. **Auto-Scaling**: Storage scales with usage, no capacity planning needed
## How (High Level)
### POC Approach: Server-First Validation
**Rationale**: Start with server-side TigrisFS mounting because:
- Local access is meaningless if cloud containers can't mount TigrisFS reliably
- Container startup and API performance are critical path blockers
- TigrisFS compatibility with Basic Memory operations must be proven first
- Each phase gates the next - no point testing local access if server-side fails
### Phase 1: Server-Side TigrisFS Validation (Critical Foundation) ✅ COMPLETE
- [x] Set up Tigris bucket with test data via Fly.io integration
- [x] Create container image with TigrisFS support and dependencies
- [x] Test TigrisFS mounting in containerized environment
- [x] Run Basic Memory API operations over mounted TigrisFS
- [x] Validate all filesystem operations work correctly
- [x] Measure container startup time and resource usage
**Production Validation Results**: Container successfully deployed and operated for 42+ minutes serving real MCP requests with repository queries, knowledge graph navigation, and full Basic Memory API functionality over TigrisFS-mounted storage.
### Phase 2: Database Migration to Turso
- [ ] Set up Turso account and test database
- [ ] Modify Basic Memory to accept external DATABASE_URL
- [ ] Test all MCP tools with remote SQLite via Turso
- [ ] Validate performance and functionality parity
- [ ] Test API write → manual sync workflow in container
### Phase 3: Production Container Integration
- [ ] Implement tenant-specific credential management for buckets
- [x] Test container startup with automatic TigrisFS mounting
- [ ] Validate isolation between tenant containers
- [ ] Test API operations under realistic load
- [ ] Measure performance vs current Fly volume setup
### Phase 4: Local Access Validation (Bonus Feature)
- [ ] Test local TigrisFS mounting of tenant data
- [ ] Validate read-only access for browsing/downloading
- [ ] Test file copying and upload workflows
- [ ] Measure latency impact on user experience
- [ ] Test live editing if file watching works (stretch goal)
### Architecture Overview
```
Local Development:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Local TigrisFS │───▶│ Tigris Bucket │◀───│ Tenant Container│
│ Mount │ │ (Global CDN) │ │ TigrisFS mount │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Basic Memory │ │ Basic Memory │
│ (local files) │ │ API + mounted │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Turso Database │◀───────────────────────────│ Turso Database │
│ (shared index) │ │ (shared index) │
└─────────────────┘ └─────────────────┘
Flow: API writes → Manual sync → Index update
Local: File watching (if available) → Auto sync
```
## How to Evaluate
### Success Criteria
- [x] **Filesystem Compatibility**: Basic Memory operates without modification over TigrisFS-mounted storage
- [x] **Performance Acceptable**: API-driven operations perform within acceptable latency (target: <500ms for typical operations)
- [ ] **Database Functionality**: All Basic Memory features work with Turso remote SQLite
- [x] **Container Reliability**: Tenant containers start successfully with automatic TigrisFS mounting
- [ ] **Local Access Baseline**: Users can mount cloud files locally for read-only browsing and file copying
- [x] **Data Isolation**: Tenant data remains properly isolated using bucket/database separation
- [ ] **Local Access Stretch**: Live editing with eventual sync (1-5 second delays acceptable)
### Testing Procedure
#### Phase 1: Server-Side Foundation Testing
1. **Container TigrisFS Test**:
```dockerfile
# Test container with TigrisFS mounting
FROM python:3.12
RUN apt-get update && apt-get install -y tigrisfs
# Test startup script
#!/bin/bash
tigrisfs --memory-limit 2048 $TIGRIS_BUCKET /app/data --daemon
cd /app/data && basic-memory sync
basic-memory-api --data-dir /app/data
```
2. **API Operations Validation**:
```bash
# Test all MCP operations over TigrisFS
curl -X POST /api/write_note -d '{"title":"test","content":"content"}'
curl -X GET /api/read_note/test
curl -X GET /api/search_notes?q=content
# Measure: response times, error rates, data consistency
```
#### Phase 2: Database Integration Testing
3. **Turso Integration Test**:
```bash
# Configure Turso connection in container
export DATABASE_URL="libsql://test-db.turso.io?authToken=..."
# Test all MCP tools with remote database
basic-memory tools # Test each tool functionality
# Test API write → manual sync workflow
```
#### Phase 3: Production Readiness Testing
4. **Performance Benchmarking**:
- Container startup time with TigrisFS mounting
- API operation response times (target: <500ms for typical operations)
- Search query performance with Turso (target: comparable to local SQLite)
- TigrisFS cache hit rates and memory usage
- Concurrent tenant isolation
#### Phase 4: Local Access Testing (If Phase 1-3 Succeed)
5. **Local Access Validation**:
```bash
# Test read-only access
tigrisfs tenant-bucket ~/local-tenant
ls -la ~/local-tenant # Browse files
cp ~/local-tenant/notes/* ~/backup/ # Copy files
# Test file watching (stretch goal)
echo "test" > ~/local-tenant/test.md
# Check if changes sync to cloud
```
### Go/No-Go Criteria by Phase
- **Phase 1**: Container must start successfully and serve API requests over TigrisFS
- **Phase 2**: All MCP tools must work with Turso with <2x latency increase
- **Phase 3**: Performance must be within 50% of current Fly volume setup
- **Phase 4**: Local mounting must work reliably for read-only access
### Risk Assessment
**Moderate Risk Items (Mitigated by API-First Architecture)**:
- [ ] TigrisFS performance for local access may have higher latency than local filesystem
- [ ] File watching (`inotify`) over FUSE may be unreliable for local development
- [ ] Network interruptions could cause filesystem errors during local editing
- [ ] Write-back caching could cause data loss if container crashes during flush
**Low Risk Items (API-First Eliminates)**:
- [ ] ~~Real-time file watching~~ - Not required for cloud operations
- [ ] ~~Concurrent write consistency~~ - Single-tenant model with API coordination
- [ ] ~~S3 rate limits~~ - TigrisFS intelligent caching handles this
**Mitigation Strategies**:
- **Performance**: Comprehensive benchmarking with realistic workloads
- **Reliability**: Graceful degradation to read-only local access if live editing fails
- **Data Safety**: Regular sync intervals and write-through mode for critical operations
- **Fallback**: Keep Fly volumes as backup deployment option
### Metrics to Track
- **API Latency**: Response times for MCP tools and web operations
- **Cache Effectiveness**: TigrisFS cache hit rates and memory usage
- **Local Access Performance**: File browsing and copying speeds
- **Reliability**: Success rate of mount operations and data consistency
- **Cost**: Storage usage, API calls, and network transfer costs vs current volumes
## Notes
### Key Architectural Decisions
- **Single tenant per bucket/database**: Simplifies isolation and credential management
- **Maintain POSIX compatibility**: Preserve Basic Memory's existing filesystem assumptions
- **TigrisFS over rclone**: Purpose-built for object storage with intelligent caching
- **Turso for SQLite**: Leverages specialized remote SQLite expertise
- **API-first approach**: Eliminates file watching dependency for cloud operations
### Alternative Approaches Considered
- **S3-native storage backend**: Would require Basic Memory architecture changes
- **Hybrid approach**: Local files + cloud sync (adds complexity)
- **Standard rclone mounting**: Less optimized than TigrisFS for object storage workloads
- **Keep Fly volumes**: Maintains current limitations but proven reliability
### Integration Points
- [ ] Fly.io Tigris integration for bucket provisioning
- [ ] Turso account setup and database provisioning
- [ ] Container image modifications for TigrisFS support
- [ ] Credential management for tenant isolation
- [ ] API modification for manual sync triggers
- [ ] Local client setup documentation for TigrisFS mounting
## Observations
- [architecture] Tigris/Turso split cleanly separates file storage from indexing concerns #storage-separation
- [breakthrough] API-first architecture eliminates file watching dependency for cloud operations #api-first-advantage
- [user-experience] Local mounting of cloud files could be revolutionary for knowledge management #local-cloud-hybrid
- [compatibility] Maintaining POSIX filesystem assumptions preserves Basic Memory's local/cloud compatibility #architecture-preservation
- [simplification] Single tenant per bucket eliminates complex multi-tenancy in storage layer #tenant-isolation
- [performance] TigrisFS intelligent caching could provide near-local performance for common operations #tigrisfs-advantage
- [deployment] Zero-downtime updates become trivial without volume constraints #deployment-simplification
- [benefit] Object storage pricing model could be more favorable than volume pricing #cost-optimization
- [innovation] Read-only local access alone would address major SaaS limitation #competitive-advantage
- [risk-mitigation] API-driven sync reduces performance requirements vs real-time file watching #risk-reduction
## Relations
- implements [[SPEC-6 Explicit Project Parameter Architecture]]
- requires [[Fly.io Tigris Integration]]
- enables [[Local Cloud File Access]]
- alternative_to [[Fly Volume Storage]]
## Links
- https://fly.io/hello/tigris
- https://fly.io/docs/tigris/
- https://www.tigrisdata.com/docs/sdks/fly/data-migration-with-flyctl/
- https://www.tigrisdata.com/docs/training/tigrisfs/
- https://www.tigrisdata.com/blog/tigris-filesystem/
- https://www.tigrisdata.com/docs/quickstarts/rclone/
+886
View File
@@ -0,0 +1,886 @@
---
title: 'SPEC-8: TigrisFS Integration for Tenant API'
Date: September 22, 2025
Status: Phase 3.6 Complete - Tenant Mount API Endpoints Ready for CLI Implementation
Priority: High
Goal: Replace Fly volumes with Tigris bucket provisioning in production tenant API
permalink: spec-8-tigris-fs-integration
---
## Executive Summary
Based on SPEC-7 Phase 4 POC testing, this spec outlines productizing the TigrisFS/rclone implementation in the Basic Memory Cloud tenant API.
We're moving from proof-of-concept to production integration, replacing Fly volume storage with Tigris bucket-per-tenant architecture.
## Current Architecture (Fly Volumes)
### Tenant Provisioning Flow
```python
# apps/cloud/src/basic_memory_cloud/workflows/tenant_provisioning.py
async def provision_tenant_infrastructure(tenant_id: str):
# 1. Create Fly app
# 2. Create Fly volume ← REPLACE THIS
# 3. Deploy API container with volume mount
# 4. Configure health checks
```
### Storage Implementation
- Each tenant gets dedicated Fly volume (1GB-10GB)
- Volume mounted at `/app/data` in API container
- Local filesystem storage with Basic Memory indexing
- No global caching or edge distribution
## Proposed Architecture (Tigris Buckets)
### New Tenant Provisioning Flow
```python
async def provision_tenant_infrastructure(tenant_id: str):
# 1. Create Fly app
# 2. Create Tigris bucket with admin credentials ← NEW
# 3. Store bucket name in tenant record ← NEW
# 4. Deploy API container with TigrisFS mount using admin credentials
# 5. Configure health checks
```
### Storage Implementation
- Each tenant gets dedicated Tigris bucket
- TigrisFS mounts bucket at `/app/data` in API container
- Global edge caching and distribution
- Configurable cache TTL for sync performance
## Implementation Plan
### Phase 1: Bucket Provisioning Service
**✅ IMPLEMENTED: StorageClient with Admin Credentials**
```python
# apps/cloud/src/basic_memory_cloud/clients/storage_client.py
class StorageClient:
async def create_tenant_bucket(self, tenant_id: UUID) -> TigrisBucketCredentials
async def delete_tenant_bucket(self, tenant_id: UUID, bucket_name: str) -> bool
async def list_buckets(self) -> list[TigrisBucketResponse]
async def test_tenant_credentials(self, credentials: TigrisBucketCredentials) -> bool
```
**Simplified Architecture Using Admin Credentials:**
- Single admin access key with full Tigris permissions (configured in console)
- No tenant-specific IAM user creation needed
- Bucket-per-tenant isolation for logical separation
- Admin credentials shared across all tenant operations
**Integrate with Provisioning workflow:**
```python
# Update tenant_provisioning.py
async def provision_tenant_infrastructure(tenant_id: str):
storage_client = StorageClient(settings.aws_access_key_id, settings.aws_secret_access_key)
bucket_creds = await storage_client.create_tenant_bucket(tenant_id)
await store_bucket_name(tenant_id, bucket_creds.bucket_name)
await deploy_api_with_tigris(tenant_id, bucket_creds)
```
### Phase 2: Simplified Bucket Management
**✅ SIMPLIFIED: Admin Credentials + Bucket Names Only**
Since we use admin credentials for all operations, we only need to track bucket names per tenant:
1. **Primary Storage (Fly Secrets)**
```bash
flyctl secrets set -a basic-memory-{tenant_id} \
AWS_ACCESS_KEY_ID="{admin_access_key}" \
AWS_SECRET_ACCESS_KEY="{admin_secret_key}" \
AWS_ENDPOINT_URL_S3="https://fly.storage.tigris.dev" \
AWS_REGION="auto" \
BUCKET_NAME="basic-memory-{tenant_id}"
```
2. **Database Storage (Bucket Name Only)**
```python
# apps/cloud/src/basic_memory_cloud/models/tenant.py
class Tenant(BaseModel):
# ... existing fields
tigris_bucket_name: Optional[str] = None # Just store bucket name
tigris_region: str = "auto"
created_at: datetime
```
**Benefits of Simplified Approach:**
- No credential encryption/decryption needed
- Admin credentials managed centrally in environment
- Only bucket names stored in database (not sensitive)
- Simplified backup/restore scenarios
- Reduced security attack surface
### Phase 3: API Container Updates
**Update API container configuration:**
```dockerfile
# apps/api/Dockerfile
# Add TigrisFS installation
RUN curl -L https://github.com/tigrisdata/tigrisfs/releases/latest/download/tigrisfs-linux-amd64 \
-o /usr/local/bin/tigrisfs && chmod +x /usr/local/bin/tigrisfs
```
**Startup script integration:**
```bash
# apps/api/tigrisfs-startup.sh (already exists)
# Mount TigrisFS → Start Basic Memory API
exec python -m basic_memory_cloud_api.main
```
**Fly.toml environment (optimized for < 5s startup):**
```toml
# apps/api/fly.tigris-production.toml
[env]
TIGRISFS_MEMORY_LIMIT = '1024' # Reduced for faster init
TIGRISFS_MAX_FLUSHERS = '16' # Fewer threads for faster startup
TIGRISFS_STAT_CACHE_TTL = '30s' # Balance sync speed vs startup
TIGRISFS_LAZY_INIT = 'true' # Enable lazy loading
BASIC_MEMORY_HOME = '/app/data'
# Suspend optimization for wake-on-network
[machine]
auto_stop_machines = "suspend" # Faster than full stop
auto_start_machines = true
min_machines_running = 0
```
### Phase 4: Local Access Features
**CLI automation for local mounting:**
```python
# New CLI command: basic-memory cloud mount
async def setup_local_mount(tenant_id: str):
# 1. Fetch bucket credentials from cloud API
# 2. Configure rclone with scoped IAM policy
# 3. Mount via rclone nfsmount (macOS) or FUSE (Linux)
# 4. Start Basic Memory sync watcher
```
**Local mount configuration:**
```bash
# rclone config for tenant
rclone mount basic-memory-{tenant_id}: ~/basic-memory-{tenant_id} \
--nfs-mount \
--vfs-cache-mode writes \
--cache-dir ~/.cache/rclone/basic-memory-{tenant_id}
```
### Phase 5: TigrisFS Cache Sync Solutions
**Problem**: When files are uploaded via CLI/bisync, the tenant API container doesn't see them immediately due to TigrisFS cache (30s TTL) and lack of inotify events on mounted filesystems.
**Multi-Layer Solution:**
**Layer 1: API Sync Endpoint** (Immediate)
```python
# POST /sync - Force TigrisFS cache refresh
# Callable by CLI after uploads
subprocess.run(["sync", "fsync /app/data"], check=True)
```
**Layer 2: Tigris Webhook Integration** (Real-time)
https://www.tigrisdata.com/docs/buckets/object-notifications/#webhook
```python
# Webhook endpoint for bucket changes
@app.post("/webhooks/tigris/{tenant_id}")
async def handle_bucket_notification(tenant_id: str, event: TigrisEvent):
if event.eventName in ["OBJECT_CREATED_PUT", "OBJECT_DELETED"]:
await notify_container_sync(tenant_id, event.object.key)
```
**Layer 3: CLI Sync Notification** (User-triggered)
```bash
# CLI calls container sync endpoint after successful bisync
basic-memory cloud bisync # Automatically notifies container
curl -X POST https://basic-memory-{tenant-id}.fly.dev/sync
```
**Layer 4: Periodic Sync Fallback** (Safety net)
```python
# Background task: fsync /app/data every 30s as fallback
# Ensures eventual consistency even if other layers fail
```
**Implementation Priority:**
1. Layer 1 (API endpoint) - Quick testing capability
2. Layer 3 (CLI integration) - Improved UX
3. Layer 4 (Periodic fallback) - Safety net
4. Layer 2 (Webhooks) - Production real-time sync
## Performance Targets
### Sync Latency
- **Target**: < 5 seconds local→cloud→container
- **Configuration**: `TIGRISFS_STAT_CACHE_TTL = '5s'`
- **Monitoring**: Track sync metrics in production
### Container Startup
- **Target**: < 5 seconds including TigrisFS mount
- **Fast retry**: 0.5s intervals for mount verification
- **Fallback**: Container fails fast if mount fails
### Memory Usage
- **TigrisFS cache**: 2GB memory limit per container
- **Concurrent uploads**: 32 flushers max
- **VM sizing**: shared-cpu-2x (2048mb) minimum
## Security Considerations
### Bucket Isolation
- Each tenant has dedicated bucket
- IAM policies prevent cross-tenant access
- No shared bucket with subdirectories
### Credential Security
- Fly secrets for runtime access
- Encrypted database backup for disaster recovery
- Credential rotation capability
### Data Residency
- Tigris global edge caching
- SOC2 Type II compliance
- Encryption at rest and in transit
## Operational Benefits
### Scalability
- Horizontal scaling with stateless API containers
- Global edge distribution
- Better resource utilization
### Reliability
- No cold starts between tenants
- Built-in redundancy and caching
- Simplified backup strategy
### Cost Efficiency
- Pay-per-use storage pricing
- Shared infrastructure benefits
- Reduced operational overhead
## Risk Mitigation
### Data Loss Prevention
- Dual credential storage (Fly + database)
- Automated backup workflows to R2/S3
- Tigris built-in redundancy
### Performance Degradation
- Configurable cache settings per tenant
- Monitoring and alerting on sync latency
- Fallback to volume storage if needed
### Security Vulnerabilities
- Bucket-per-tenant isolation
- Regular credential rotation
- Security scanning and monitoring
## Success Metrics
### Technical Metrics
- Sync latency P50 < 5 seconds
- Container startup time < 5 seconds
- Zero data loss incidents
- 99.9% uptime per tenant
### Business Metrics
- Reduced infrastructure costs vs volumes
- Improved user experience with faster sync
- Enhanced enterprise security posture
- Simplified operational overhead
## Open Questions
1. **Tigris rate limits**: What are the API limits for bucket creation?
2. **Cost analysis**: What's the break-even point vs Fly volumes?
3. **Regional preferences**: Should enterprise customers choose regions?
4. **Backup retention**: How long to keep automated backups?
## Implementation Checklist
### Phase 1: Bucket Provisioning Service ✅ COMPLETED
- [x] **Research Tigris bucket API** - Document bucket creation and S3 API compatibility
- [x] **Create StorageClient class** - Implemented with admin credentials and comprehensive integration tests
- [x] **Test bucket creation** - Full test suite validates API integration with real Tigris environment
- [x] **Add bucket provisioning to DBOS workflow** - Integrated StorageClient with tenant_provisioning.py
### Phase 2: Simplified Bucket Management ✅ COMPLETED
- [x] **Update Tenant model** with tigris_bucket_name field (replaced fly_volume_id)
- [x] **Implement bucket name storage** - Database migration and model updates completed
- [x] **Test bucket provisioning integration** - Full test suite validates workflow from tenant creation to bucket assignment
- [x] **Remove volume logic from all tests** - Complete migration from volume-based to bucket-based architecture
### Phase 3: API Container Integration ✅ COMPLETED
- [x] **Update Dockerfile** to install TigrisFS binary in API container with configurable version
- [x] **Optimize tigrisfs-startup.sh** with production-ready security and reliability improvements
- [x] **Create production-ready container** with proper signal handling and mount validation
- [x] **Implement security fixes** based on Claude code review (conditional debug, credential protection)
- [x] **Add proper process supervision** with cleanup traps and error handling
- [x] **Remove debug artifacts** - Cleaned up all debug Dockerfiles and test scripts
### Phase 3.5: IAM Access Key Management ✅ COMPLETED
- [x] **Research Tigris IAM API** - Documented create_policy, attach_user_policy, delete_access_key operations
- [x] **Implement bucket-scoped credential generation** - StorageClient.create_tenant_access_keys() with IAM policies
- [x] **Add comprehensive security test suite** - 5 security-focused integration tests covering all attack vectors
- [x] **Verify cross-bucket access prevention** - Scoped credentials can ONLY access their designated bucket
- [x] **Test credential lifecycle management** - Create, validate, delete, and revoke access keys
- [x] **Validate admin vs scoped credential isolation** - Different access patterns and security boundaries
- [x] **Test multi-tenant isolation** - Multiple tenants cannot access each other's buckets
### Phase 3.6: Tenant Mount API Endpoints ✅ COMPLETED
- [x] **Implement GET /tenant/mount/info** - Returns mount info without exposing credentials
- [x] **Implement POST /tenant/mount/credentials** - Creates new bucket-scoped credentials for CLI mounting
- [x] **Implement DELETE /tenant/mount/credentials/{cred_id}** - Revoke specific credentials with proper cleanup
- [x] **Implement GET /tenant/mount/credentials** - List active credentials without exposing secrets
- [x] **Add TenantMountCredentials database model** - Tracks credential metadata (no secret storage)
- [x] **Create comprehensive test suite** - 28 tests covering all scenarios including multi-session support
- [x] **Implement multi-session credential flow** - Multiple active credentials per tenant supported
- [x] **Secure credential handling** - Secret keys never stored, returned once only for immediate use
- [x] **Add dependency injection for StorageClient** - Clean integration with existing API architecture
- [x] **Fix Tigris configuration for cloud service** - Added AWS environment variables to fly.template.toml
- [x] **Update tenant machine configurations** - Include AWS credentials for TigrisFS mounting with clear credential strategy
**Security Test Results:**
```
✅ Cross-bucket access prevention - PASS
✅ Deleted credentials access revoked - PASS
✅ Invalid credentials rejected - PASS
✅ Admin vs scoped credential isolation - PASS
✅ Multiple scoped credentials isolation - PASS
```
**Implementation Details:**
- Uses Tigris IAM managed policies (create_policy + attach_user_policy)
- Bucket-scoped S3 policies with Actions: GetObject, PutObject, DeleteObject, ListBucket
- Resource ARNs limited to specific bucket: `arn:aws:s3:::bucket-name` and `arn:aws:s3:::bucket-name/*`
- Access keys follow Tigris format: `tid_` prefix with secure random suffix
- Complete cleanup on deletion removes both access keys and associated policies
### Phase 4: Local Access CLI
- [x] **Design local mount CLI command** for automated rclone configuration
- [x] **Implement credential fetching** from cloud API for local setup
- [x] **Create rclone config automation** for tenant-specific bucket mounting
- [x] **Test local→cloud→container sync** with optimized cache settings
- [x] **Document local access setup** for beta users
### Phase 5: Webhook Integration (Future)
- [ ] **Research Tigris webhook API** for object notifications and payload format
- [ ] **Design webhook endpoint** for real-time sync notifications
- [ ] **Implement notification handling** to trigger Basic Memory sync events
- [ ] **Test webhook delivery** and sync latency improvements
## Success Metrics
- [ ] **Container startup < 5 seconds** including TigrisFS mount and Basic Memory init
- [ ] **Sync latency < 5 seconds** for local→cloud→container file changes
- [ ] **Zero data loss** during bucket provisioning and credential management
- [ ] **100% test coverage** for new TigrisBucketService and credential functions
- [ ] **Beta deployment** with internal users validating local-cloud workflow
## Implementation Notes
## Phase 4.1: Bidirectional Sync with rclone bisync (NEW)
### Problem Statement
During testing, we discovered that some applications (particularly Obsidian) don't detect file changes over NFS mounts. Rather than building a custom sync daemon, we can leverage `rclone bisync` - rclone's built-in bidirectional synchronization feature.
### Solution: rclone bisync
Use rclone's proven bidirectional sync instead of custom implementation:
**Core Architecture:**
```bash
# rclone bisync handles all the complexity
rclone bisync ~/basic-memory-{tenant_id} basic-memory-{tenant_id}:{bucket_name} \
--create-empty-src-dirs \
--conflict-resolve newer \
--resilient \
--check-access
```
**Key Benefits:**
- ✅ **Battle-tested**: Production-proven rclone functionality
- ✅ **MIT licensed**: Open source with permissive licensing
- ✅ **No custom code**: Zero maintenance burden for sync logic
- ✅ **Built-in safety**: max-delete protection, conflict resolution
- ✅ **Simple installation**: Works with Homebrew rclone (no FUSE needed)
- ✅ **File watcher compatible**: Works with Obsidian and all applications
- ✅ **Offline support**: Can work offline and sync when connected
### bisync Conflict Resolution Options
**Built-in conflict strategies:**
```bash
--conflict-resolve none # Keep both files with .conflict suffixes (safest)
--conflict-resolve newer # Always pick the most recently modified file
--conflict-resolve larger # Choose based on file size
--conflict-resolve path1 # Always prefer local changes
--conflict-resolve path2 # Always prefer cloud changes
```
### Sync Profiles Using bisync
**Profile configurations:**
```python
BISYNC_PROFILES = {
"safe": {
"conflict_resolve": "none", # Keep both versions
"max_delete": 10, # Prevent mass deletion
"check_access": True, # Verify sync integrity
"description": "Safe mode with conflict preservation"
},
"balanced": {
"conflict_resolve": "newer", # Auto-resolve to newer file
"max_delete": 25,
"check_access": True,
"description": "Balanced mode (recommended default)"
},
"fast": {
"conflict_resolve": "newer",
"max_delete": 50,
"check_access": False, # Skip verification for speed
"description": "Fast mode for rapid iteration"
}
}
```
### CLI Commands
**Manual sync commands:**
```bash
basic-memory cloud bisync # Manual bidirectional sync
basic-memory cloud bisync --dry-run # Preview changes
basic-memory cloud bisync --profile safe # Use specific profile
basic-memory cloud bisync --resync # Force full baseline resync
```
**Watch mode (Step 1):**
```bash
basic-memory cloud bisync --watch # Long-running process, sync every 60s
basic-memory cloud bisync --watch --interval 30s # Custom interval
```
**System integration (Step 2 - Future):**
```bash
basic-memory cloud bisync-service install # Install as system service
basic-memory cloud bisync-service start # Start background service
basic-memory cloud bisync-service status # Check service status
```
### Implementation Strategy
**Phase 4.1.1: Core bisync Implementation**
- [ ] Implement `run_bisync()` function wrapping rclone bisync
- [ ] Add profile-based configuration (safe/balanced/fast)
- [ ] Create conflict resolution and safety options
- [ ] Test with sample files and conflict scenarios
**Phase 4.1.2: Watch Mode**
- [ ] Add `--watch` flag for continuous sync
- [ ] Implement configurable sync intervals
- [ ] Add graceful shutdown and signal handling
- [ ] Create status monitoring and progress indicators
**Phase 4.1.3: User Experience**
- [ ] Add conflict reporting and resolution guidance
- [ ] Implement dry-run preview functionality
- [ ] Create troubleshooting and diagnostic commands
- [ ] Add filtering configuration (.gitignore-style)
**Phase 4.1.4: System Integration (Future)**
- [ ] Generate platform-specific service files (launchd/systemd)
- [ ] Add service management commands
- [ ] Implement automatic startup and recovery
- [ ] Create monitoring and logging integration
### Technical Implementation
**Core bisync wrapper:**
```python
def run_bisync(
tenant_id: str,
bucket_name: str,
profile: str = "balanced",
dry_run: bool = False
) -> bool:
"""Run rclone bisync with specified profile."""
local_path = Path.home() / f"basic-memory-{tenant_id}"
remote_path = f"basic-memory-{tenant_id}:{bucket_name}"
profile_config = BISYNC_PROFILES[profile]
cmd = [
"rclone", "bisync",
str(local_path), remote_path,
"--create-empty-src-dirs",
"--resilient",
f"--conflict-resolve={profile_config['conflict_resolve']}",
f"--max-delete={profile_config['max_delete']}",
"--filters-file", "~/.basic-memory/bisync-filters.txt"
]
if profile_config.get("check_access"):
cmd.append("--check-access")
if dry_run:
cmd.append("--dry-run")
return subprocess.run(cmd, check=True).returncode == 0
```
**Default filter file (~/.basic-memory/bisync-filters.txt):**
```
- .DS_Store
- .git/**
- __pycache__/**
- *.pyc
- .pytest_cache/**
- node_modules/**
- .conflict-*
- Thumbs.db
- desktop.ini
```
**Advantages Over Custom Daemon:**
- ✅ **Zero maintenance**: No custom sync logic to debug/maintain
- ✅ **Production proven**: Used by thousands in production
- ✅ **Safety features**: Built-in max-delete, conflict handling, recovery
- ✅ **Filtering**: Advanced exclude patterns and rules
- ✅ **Performance**: Optimized for various storage backends
- ✅ **Community support**: Extensive documentation and community
## Phase 4.2: NFS Mount Support (Direct Access)
### Solution: rclone nfsmount
Keep the existing NFS mount functionality for users who prefer direct file access:
**Core Architecture:**
```bash
# rclone nfsmount provides transparent file access
rclone nfsmount basic-memory-{tenant_id}:{bucket_name} ~/basic-memory-{tenant_id} \
--vfs-cache-mode writes \
--dir-cache-time 10s \
--daemon
```
**Key Benefits:**
- ✅ **Real-time access**: Files appear immediately as they're created/modified
- ✅ **Transparent**: Works with any application that reads/writes files
- ✅ **Low latency**: Direct access without sync delays
- ✅ **Simple**: No periodic sync commands needed
- ✅ **Homebrew compatible**: Works with Homebrew rclone (no FUSE required)
**Limitations:**
- ❌ **File watcher compatibility**: Some apps (Obsidian) don't detect changes over NFS
- ❌ **Network dependency**: Requires active connection to cloud storage
- ❌ **Potential conflicts**: Simultaneous edits from multiple locations can cause issues
### Mount Profiles (Existing)
**Already implemented profiles from SPEC-7 testing:**
```python
MOUNT_PROFILES = {
"fast": {
"cache_time": "5s",
"poll_interval": "3s",
"description": "Ultra-fast development (5s sync)"
},
"balanced": {
"cache_time": "10s",
"poll_interval": "5s",
"description": "Fast development (10-15s sync, recommended)"
},
"safe": {
"cache_time": "15s",
"poll_interval": "10s",
"description": "Conflict-aware mount with backup",
"extra_args": ["--conflict-suffix", ".conflict-{DateTimeExt}"]
}
}
```
### CLI Commands (Existing)
**Mount commands already implemented:**
```bash
basic-memory cloud mount # Mount with balanced profile
basic-memory cloud mount --profile fast # Ultra-fast caching
basic-memory cloud mount --profile safe # Conflict detection
basic-memory cloud unmount # Clean unmount
basic-memory cloud mount-status # Show mount status
```
## User Choice: Mount vs Bisync
### When to Use Each Approach
| Use Case | Recommended Solution | Why |
|----------|---------------------|-----|
| **Obsidian users** | `bisync` | File watcher support for live preview |
| **CLI/vim/emacs users** | `mount` | Direct file access, lower latency |
| **Offline work** | `bisync` | Can work offline, sync when connected |
| **Real-time collaboration** | `mount` | Immediate visibility of changes |
| **Multiple machines** | `bisync` | Better conflict handling |
| **Single machine** | `mount` | Simpler, more transparent |
| **Development work** | Either | Both work well, user preference |
| **Large files** | `mount` | Streaming access vs full download |
### Installation Simplicity
**Both approaches now use simple Homebrew installation:**
```bash
# Single installation command for both approaches
brew install rclone
# No macFUSE, no system modifications needed
# Works immediately with both mount and bisync
```
### Implementation Status
**Phase 4.1: bisync** (NEW)
- [ ] Implement bisync command wrapper
- [ ] Add watch mode with configurable intervals
- [ ] Create conflict resolution workflows
- [ ] Add filtering and safety options
**Phase 4.2: mount** (EXISTING - ✅ IMPLEMENTED)
- [x] NFS mount commands with profile support
- [x] Mount management and cleanup
- [x] Process monitoring and health checks
- [x] Credential integration with cloud API
**Both approaches share:**
- [x] Credential management via cloud API
- [x] Secure rclone configuration
- [x] Tenant isolation and bucket scoping
- [x] Simple Homebrew rclone installation
Key Features:
1. Cross-Platform rclone Installation (rclone_installer.py):
- macOS: Homebrew → official script fallback
- Linux: snap → apt → official script fallback
- Windows: winget → chocolatey → scoop fallback
- Automatic version detection and verification
2. Smart rclone Configuration (rclone_config.py):
- Automatic tenant-specific config generation
- Three optimized mount profiles from your SPEC-7 testing:
- fast: 5s sync (ultra-performance)
- balanced: 10-15s sync (recommended default)
- safe: 15s sync + conflict detection
- Backup existing configs before modification
3. Robust Mount Management (mount_commands.py):
- Automatic tenant credential generation
- Mount path management (~/basic-memory-{tenant-id})
- Process lifecycle management (prevent duplicate mounts)
- Orphaned process cleanup
- Mount verification and health checking
4. Clean Architecture (api_client.py):
- Separated API client to avoid circular imports
- Reuses existing authentication infrastructure
- Consistent error handling and logging
User Experience:
One-Command Setup:
basic-memory cloud setup
```bash
# 1. Installs rclone automatically
# 2. Authenticates with existing login
# 3. Generates secure credentials
# 4. Configures rclone
# 5. Performs initial mount
```
Profile-Based Mounting:
basic-memory cloud mount --profile fast # 5s sync
basic-memory cloud mount --profile balanced # 15s sync (default)
basic-memory cloud mount --profile safe # conflict detection
Status Monitoring:
basic-memory cloud mount-status
```bash
# Shows: tenant info, mount path, sync profile, rclone processes
```
### local mount api
Endpoint 1: Get Tenant Info for user
Purpose: Get tenant details for mounting
- pass in jwt
- service returns mount info
**✅ IMPLEMENTED API Specification:**
**Endpoint 1: GET /tenant/mount/info**
- Purpose: Get tenant mount information without exposing credentials
- Authentication: JWT token (tenant_id extracted from claims)
Request:
```
GET /tenant/mount/info
Authorization: Bearer {jwt_token}
```
Response:
```json
{
"tenant_id": "434252dd-d83b-4b20-bf70-8a950ff875c4",
"bucket_name": "basic-memory-434252dd",
"has_credentials": true,
"credentials_created_at": "2025-09-22T16:48:50.414694"
}
```
**Endpoint 2: POST /tenant/mount/credentials**
- Purpose: Generate NEW bucket-scoped S3 credentials for rclone mounting
- Authentication: JWT token (tenant_id extracted from claims)
- Multi-session: Creates new credentials without revoking existing ones
Request:
```
POST /tenant/mount/credentials
Authorization: Bearer {jwt_token}
Content-Type: application/json
```
*Note: No request body needed - tenant_id extracted from JWT*
Response:
```json
{
"tenant_id": "434252dd-d83b-4b20-bf70-8a950ff875c4",
"bucket_name": "basic-memory-434252dd",
"access_key": "test_access_key_12345",
"secret_key": "test_secret_key_abcdef",
"endpoint_url": "https://fly.storage.tigris.dev",
"region": "auto"
}
```
**🔒 Security Notes:**
- Secret key returned ONCE only - never stored in database
- Credentials are bucket-scoped (cannot access other tenants' buckets)
- Multiple active credentials supported per tenant (work laptop + personal machine)
Implementation Notes
Security:
- Both endpoints require JWT authentication
- Extract tenant_id from JWT claims (not request body)
- Generate scoped credentials (not admin credentials)
- Credentials should have bucket-specific access only
Integration Points:
- Use your existing StorageClient from SPEC-8 implementation
- Leverage existing JWT middleware for tenant extraction
- Return same credential format as your Tigris bucket provisioning
Error Handling:
- 401 if not authenticated
- 403 if tenant doesn't exist
- 500 if credential generation fails
**🔄 Design Decisions:**
1. **Secure Credential Flow (No Secret Storage)**
Based on CLI flow analysis, we follow security best practices:
- ✅ API generates both access_key + secret_key via Tigris IAM
- ✅ Returns both in API response for immediate use
- ✅ CLI uses credentials immediately to configure rclone
- ✅ Database stores only metadata (access_key + policy_arn for cleanup)
- ✅ rclone handles secure local credential storage
- ❌ **Never store secret_key in database (even encrypted)**
2. **CLI Credential Flow**
```bash
# CLI calls API
POST /tenant/mount/credentials → {access_key, secret_key, ...}
# CLI immediately configures rclone
rclone config create basic-memory-{tenant_id} s3 \
access_key_id={access_key} \
secret_access_key={secret_key} \
endpoint=https://fly.storage.tigris.dev
# Database tracks metadata only
INSERT INTO tenant_mount_credentials (tenant_id, access_key, policy_arn, ...)
```
3. **Multiple Sessions Supported**
- Users can have multiple active credential sets (work laptop, personal machine, etc.)
- Each credential generation creates a new Tigris access key
- List active credentials via API (shows access_key but never secret)
4. **Failure Handling & Cleanup**
- **Happy Path**: Credentials created → Used immediately → rclone configured
- **Orphaned Credentials**: Background job revokes unused credentials
- **API Failure Recovery**: Retry Tigris deletion with stored policy_arn
- **Status Tracking**: Track tigris_deletion_status (pending/completed/failed)
5. **Event Sourcing & Audit**
- MountCredentialCreatedEvent
- MountCredentialRevokedEvent
- MountCredentialOrphanedEvent (for cleanup)
- Full audit trail for security compliance
6. **Tenant/Bucket Validation**
- Verify tenant exists and has valid bucket before credential generation
- Use existing StorageClient to validate bucket access
- Prevent credential generation for inactive/invalid tenants
📋 **Implemented API Endpoints:**
```
✅ IMPLEMENTED:
GET /tenant/mount/info # Get tenant/bucket info (no credentials exposed)
POST /tenant/mount/credentials # Generate new credentials (returns secret once)
GET /tenant/mount/credentials # List active credentials (no secrets)
DELETE /tenant/mount/credentials/{cred_id} # Revoke specific credentials
```
**API Implementation Status:**
- ✅ **GET /tenant/mount/info**: Returns tenant_id, bucket_name, has_credentials, credentials_created_at
- ✅ **POST /tenant/mount/credentials**: Creates new bucket-scoped access keys, returns access_key + secret_key once
- ✅ **GET /tenant/mount/credentials**: Lists active credentials without exposing secret keys
- ✅ **DELETE /tenant/mount/credentials/{cred_id}**: Revokes specific credentials with proper Tigris IAM cleanup
- ✅ **Multi-session support**: Multiple active credentials per tenant (work laptop + personal machine)
- ✅ **Security**: Secret keys never stored in database, returned once only for immediate use
- ✅ **Comprehensive test suite**: 28 tests covering all scenarios including error handling and multi-session flows
- ✅ **Dependency injection**: Clean integration with existing FastAPI architecture
- ✅ **Production-ready configuration**: Tigris credentials properly configured for tenant machines
🗄️ **Secure Database Schema:**
```sql
CREATE TABLE tenant_mount_credentials (
id UUID PRIMARY KEY,
tenant_id UUID REFERENCES tenant(id),
access_key VARCHAR(255) NOT NULL,
-- secret_key REMOVED - never store secrets (security best practice)
policy_arn VARCHAR(255) NOT NULL, -- For Tigris IAM cleanup
tigris_deletion_status VARCHAR(20) DEFAULT 'pending', -- Track cleanup
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
revoked_at TIMESTAMP NULL,
last_used_at TIMESTAMP NULL, -- Track usage for orphan cleanup
description VARCHAR(255) DEFAULT 'CLI mount credentials'
);
```
**Security Benefits:**
- ✅ Database breach cannot expose secrets
- ✅ Follows "secrets don't persist" security principle
- ✅ Meets compliance requirements (SOC2, etc.)
- ✅ Reduced attack surface
- ✅ CLI gets credentials once and stores securely via rclone
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,196 @@
---
title: 'SPEC-9: Signed Header Tenant Information'
type: spec
permalink: specs/spec-9-signed-header-tenant-information
tags:
- authentication
- tenant-isolation
- proxy
- security
- mcp
---
# SPEC-9: Signed Header Tenant Information
## Why
WorkOS JWT templates don't work with MCP's dynamic client registration requirement, preventing us from getting tenant information directly in JWT tokens. We need an alternative secure method to pass tenant context from the Cloud Proxy Service to tenant instances.
**Problem Context:**
- MCP spec requires dynamic client registration
- WorkOS JWT templates only apply to statically configured clients
- Without tenant information, we can't properly route requests or isolate tenant data
- Current JWT tokens only contain standard OIDC claims (sub, email, etc.)
**Affected Areas:**
- Cloud Proxy Service (`apps/cloud`) - request forwarding
- Tenant API instances (`apps/api`) - tenant context validation
- MCP Gateway (`apps/mcp`) - authentication flow
- Overall tenant isolation security model
## What
Implement HMAC-signed headers that the Cloud Proxy Service adds when forwarding requests to tenant instances. This provides secure, tamper-proof tenant information without relying on JWT custom claims.
**Components:**
- Header signing utility in Cloud Proxy Service
- Header validation middleware in Tenant API instances
- Shared secret configuration across services
- Fallback mechanisms for development and error cases
## How (High Level)
### 1. Header Format
Add these signed headers to all proxied requests:
```
X-BM-Tenant-ID: {tenant_id}
X-BM-Timestamp: {unix_timestamp}
X-BM-Signature: {hmac_sha256_signature}
```
### 2. Signature Algorithm
```python
# Canonical message format
message = f"{tenant_id}:{timestamp}"
# HMAC-SHA256 signature
signature = hmac.new(
key=shared_secret.encode('utf-8'),
msg=message.encode('utf-8'),
digestmod=hashlib.sha256
).hexdigest()
```
### 3. Implementation Flow
#### Cloud Proxy Service (`apps/cloud`)
1. Extract `tenant_id` from authenticated user profile
2. Generate timestamp and canonical message
3. Sign message with shared secret
4. Add headers to request before forwarding to tenant instance
#### Tenant API Instances (`apps/api`)
1. Middleware validates headers on all incoming requests
2. Extract tenant_id, timestamp from headers
3. Verify timestamp is within acceptable window (5 minutes)
4. Recompute signature and compare in constant time
5. If valid, make tenant context available to Basic Memory tools
### 4. Security Properties
- **Authenticity**: Only services with shared secret can create valid signatures
- **Integrity**: Header tampering invalidates signature
- **Replay Protection**: Timestamp prevents reuse of old signatures
- **Non-repudiation**: Each request is cryptographically tied to specific tenant
### 5. Configuration
```bash
# Shared across Cloud Proxy and Tenant instances
BM_TENANT_HEADER_SECRET=randomly-generated-256-bit-secret
# Tenant API configuration
BM_TENANT_HEADER_VALIDATION=true # true (production) | false (dev only)
```
## How to Evaluate
### Unit Tests
- [ ] Header signing utility generates correct signatures
- [ ] Header validation correctly accepts/rejects signatures
- [ ] Timestamp validation within acceptable windows
- [ ] Constant-time signature comparison prevents timing attacks
### Integration Tests
- [ ] End-to-end request flow from MCP client → proxy → tenant
- [ ] Tenant isolation verified with signed headers
- [ ] Error handling for missing/invalid headers
- [ ] Disabled validation in development environment
### Security Validation
- [ ] Shared secret rotation procedure
- [ ] Header tampering detection
- [ ] Clock skew tolerance testing
- [ ] Performance impact measurement
### Production Readiness
- [ ] Logging and monitoring of header validation
- [ ] Graceful degradation for header validation failures
- [ ] Documentation for secret management
- [ ] Deployment configuration templates
## Implementation Notes
### Shared Secret Management
- Generate cryptographically secure 256-bit secret
- Same secret deployed to Cloud Proxy and all Tenant instances
- Consider secret rotation strategy for production
### Error Handling
```python
# Strict mode (production)
if not validate_headers(request):
raise HTTPException(status_code=401, detail="Invalid tenant headers")
# Fallback mode (development)
if not validate_headers(request):
logger.warning("Invalid headers, falling back to default tenant")
tenant_id = "default"
```
### Performance Considerations
- HMAC-SHA256 computation is fast (~microseconds)
- Headers add ~200 bytes to each request
- Validation happens once per request in middleware
## Benefits
**Works with MCP dynamic client registration** - No dependency on JWT custom claims
**Simple and reliable** - Standard HMAC signature approach
**Secure by design** - Cryptographic authenticity and integrity
**Infrastructure controlled** - No external service dependencies
**Easy to implement** - Clear signature algorithm and validation
## Trade-offs
⚠️ **Shared secret management** - Need secure distribution and rotation
⚠️ **Clock synchronization** - Timestamp validation requires reasonably synced clocks
⚠️ **Header visibility** - Headers visible in logs (tenant_id not sensitive)
⚠️ **Additional complexity** - More moving parts in proxy forwarding
## Implementation Tasks
### Cloud Service (Header Signing)
- [ ] Create `utils/header_signing.py` with HMAC-SHA256 signing function
- [ ] Add `bm_tenant_header_secret` to Cloud service configuration
- [ ] Update `ProxyService.forward_request()` to call signing utility
- [ ] Add signed headers (X-BM-Tenant-ID, X-BM-Timestamp, X-BM-Signature)
### Tenant API (Header Validation)
- [ ] Create `utils/header_validation.py` with signature verification
- [ ] Add `bm_tenant_header_secret` to API service configuration
- [ ] Create `TenantHeaderValidationMiddleware` class
- [ ] Add middleware to FastAPI app (before other middleware)
- [ ] Skip validation for `/health` endpoint
- [ ] Store validated tenant_id in request.state
### Testing
- [ ] Unit test for header signing utility
- [ ] Unit test for header validation utility
- [ ] Integration test for proxy → tenant flow
- [ ] Test invalid/missing header handling
- [ ] Test timestamp window validation
- [ ] Test signature tampering detection
### Configuration & Deployment
- [ ] Update `.env.example` with BM_TENANT_HEADER_SECRET
- [ ] Generate secure 256-bit secret for production
- [ ] Update Fly.io secrets for both services
- [ ] Document secret rotation procedure
## Status
- [x] **Specification Complete** - Design finalized and documented
- [ ] **Implementation Started** - Header signing utility development
- [ ] **Cloud Proxy Updated** - ProxyService adds signed headers
- [ ] **Tenant Validation Added** - Middleware validates headers
- [ ] **Testing Complete** - All validation criteria met
- [ ] **Production Deployed** - Live with tenant isolation via headers
@@ -0,0 +1,390 @@
---
title: 'SPEC-9-1 Follow-Ups: Conflict, Sync, and Observability'
type: tasklist
permalink: specs/spec-9-follow-ups-conflict-sync-and-observability
related: specs/spec-9-multi-project-bisync
status: revised
revision_date: 2025-10-03
---
# SPEC-9-1 Follow-Ups: Conflict, Sync, and Observability
**REVISED 2025-10-03:** Simplified to leverage rclone built-ins instead of custom conflict handling.
**Context:** SPEC-9 delivered multi-project bidirectional sync and a unified CLI. This follow-up focuses on **observability and safety** using rclone's built-in capabilities rather than reinventing conflict handling.
**Design Philosophy: "Be Dumb Like Git"**
- Let rclone bisync handle conflict detection (it already does this)
- Make conflicts visible and recoverable, don't prevent them
- Cloud is always the winner on conflict (cloud-primary model)
- Users who want version history can just use Git locally in their sync directory
**What Changed from Original Version:**
- **Replaced:** Custom `.bmmeta` sidecars → Use rclone's `.bisync/` state tracking
- **Replaced:** Custom conflict detection → Use rclone bisync 3-way merge
- **Replaced:** Tombstone files → rclone delete tracking handles this
- **Replaced:** Distributed lease → Local process lock only (document multi-device warning)
- **Replaced:** S3 versioning service → Users just use Git locally if they want history
- **Deferred:** SPEC-14 Git integration → Postponed to teams/multi-user features
## ✅ Now
- [ ] **Local process lock**: Prevent concurrent bisync runs on same device (`~/.basic-memory/sync.lock`)
- [ ] **Structured sync reports**: Parse rclone bisync output into JSON reports (creates/updates/deletes/conflicts, bytes, duration); `bm sync --report`
- [ ] **Multi-device warning**: Document that users should not run `--watch` on multiple devices simultaneously
- [ ] **Version control guidance**: Document pattern for users to use Git locally in their sync directory if they want version history
- [ ] **Docs polish**: cloud-mode toggle, mount↔bisync directory isolation, conflict semantics, quick start, migration guide, short demo clip/GIF
## 🔜 Next
- [ ] **Observability commands**: `bm conflicts list`, `bm sync history` to view sync reports and conflicts
- [ ] **Conflict resolution UI**: `bm conflicts resolve <file>` to interactively pick winner from conflict files
- [ ] **Selective sync**: allow include/exclude by project; per-project profile (safe/balanced/fast)
## 🧭 Later
- [ ] **Near real-time sync**: File watcher → targeted `rclone copy` for individual files (keep bisync as backstop)
- [ ] **Sharing / scoped tokens**: cross-tenant/project access
- [ ] **Bandwidth controls & backpressure**: policy for large repos
- [ ] **Client-side encryption (optional)**: with clear trade-offs
## 📏 Acceptance criteria (for "Now" items)
- [ ] Local process lock prevents concurrent bisync runs on same device
- [ ] rclone bisync conflict files visible and documented (`file.conflict1.md`, `file.conflict2.md`)
- [ ] `bm sync --report` generates parsable JSON with sync statistics
- [ ] Documentation clearly warns about multi-device `--watch` mode
- [ ] Documentation shows users how to use Git locally for version history
## What We're NOT Building (Deferred to rclone)
- ❌ Custom `.bmmeta` sidecars (rclone tracks state in `.bisync/` workdir)
- ❌ Custom conflict detection (rclone bisync already does 3-way merge detection)
- ❌ Tombstone files (S3 versioning + rclone delete tracking handles this)
- ❌ Distributed lease (low probability issue, rclone detects state divergence)
- ❌ Rename/move tracking (rclone has size+modtime heuristics built-in)
## Implementation Summary
**Current State (SPEC-9):**
- ✅ rclone bisync with 3 profiles (safe/balanced/fast)
- ✅ `--max-delete` safety limits (10/25/50 files)
- ✅ `--conflict-resolve=newer` for auto-resolution
- ✅ Watch mode: `bm sync --watch` (60s intervals)
- ✅ Integrity checking: `bm cloud check`
- ✅ Mount vs bisync directory isolation
**What's Needed (This Spec):**
1. **Process lock** - Simple file-based lock in `~/.basic-memory/sync.lock`
2. **Sync reports** - Parse rclone output, save to `~/.basic-memory/sync-history/`
3. **Documentation** - Multi-device warnings, conflict resolution workflow, Git usage pattern
**User Model:**
- Cloud is always the winner on conflict (cloud-primary)
- rclone creates `.conflict` files for divergent edits
- Users who want version history just use Git in their local sync directory
- Users warned: don't run `--watch` on multiple devices
## Decision Rationale & Trade-offs
### Why Trust rclone Instead of Custom Conflict Handling?
**rclone bisync already provides:**
- 3-way merge detection (compares local, remote, and last-known state)
- File state tracking in `.bisync/` workdir (hashes, modtimes)
- Automatic conflict file creation: `file.conflict1.md`, `file.conflict2.md`
- Rename detection via size+modtime heuristics
- Delete tracking (prevents resurrection of deleted files)
- Battle-tested with extensive edge case handling
**What we'd have to build with custom approach:**
- Per-file metadata tracking (`.bmmeta` sidecars)
- 3-way diff algorithm
- Conflict detection logic
- Tombstone files for deletes
- Rename/move detection
- Testing for all edge cases
**Decision:** Use what rclone already does well. Don't reinvent the wheel.
### Why Let Users Use Git Locally Instead of Building Versioning?
**The simplest solution: Just use Git**
Users who want version history can literally just use Git in their sync directory:
```bash
cd ~/basic-memory-cloud-sync/
git init
git add .
git commit -m "backup"
# Push to their own GitHub if they want
git remote add origin git@github.com:user/my-knowledge.git
git push
```
**Why this is perfect:**
- ✅ We build nothing
- ✅ Users who want Git... just use Git
- ✅ Users who don't care... don't need to
- ✅ rclone bisync already handles sync conflicts
- ✅ Users own their data, they can version it however they want (Git, Time Machine, etc.)
**What we'd have to build for S3 versioning:**
- API to enable versioning on Tigris buckets
- **Problem**: Tigris doesn't support S3 bucket versioning
- Restore commands: `bm cloud restore --version-id`
- Version listing: `bm cloud versions <path>`
- Lifecycle policies for version retention
- Documentation and user education
**What we'd have to build for SPEC-14 Git integration:**
- Committer service (daemon watching `/app/data/`)
- Puller service (webhook handler for GitHub pushes)
- Git LFS for large files
- Loop prevention between Git ↔ bisync ↔ local
- Merge conflict handling at TWO layers (rclone + Git)
- Webhook infrastructure and monitoring
**Decision:** Don't build version control. Document the pattern. "The easiest problem to solve is the one you avoid."
**When to revisit:** Teams/multi-user features where server-side version control becomes necessary for collaboration.
### Why No Distributed Lease?
**Low probability issue:**
- Requires user to manually run `bm sync` on multiple devices at exact same time
- Most users run `--watch` on one primary device
- rclone bisync detects state divergence and fails safely
**Safety nets in place:**
- Local process lock prevents concurrent runs on same device
- rclone bisync aborts if bucket state changed during sync
- S3 versioning recovers from any overwrites
- Documentation warns against multi-device `--watch`
**Failure mode:**
```bash
# Device A and B sync simultaneously
Device A: bm sync → succeeds
Device B: bm sync → "Error: path has changed, run --resync"
# User fixes with resync
Device B: bm sync --resync → establishes new baseline
```
**Decision:** Document the issue, add local lock, defer distributed coordination until users report actual problems.
### Cloud-Primary Conflict Model
**User mental model:**
- Cloud is the source of truth (like Dropbox/iCloud)
- Local is working copy
- On conflict: cloud wins, local edits → `.conflict` file
- User manually picks winner
**Why this works:**
- Simpler than bidirectional merge (no automatic resolution risk)
- Matches user expectations from Dropbox
- S3 versioning provides safety net for overwrites
- Clear recovery path: restore from S3 version if needed
**Example workflow:**
```bash
# Edit file on Device A and Device B while offline
# Both devices come online and sync
Device A: bm sync
# → Pushes to cloud first, becomes canonical version
Device B: bm sync
# → Detects conflict
# → Cloud version: work/notes.md
# → Local version: work/notes.md.conflict1
# → User manually merges or picks winner
# Restore if needed
bm cloud restore work/notes.md --version-id abc123
```
## Implementation Details
### 1. Local Process Lock
```python
# ~/.basic-memory/sync.lock
import os
import psutil
from pathlib import Path
class SyncLock:
def __init__(self):
self.lock_file = Path.home() / '.basic-memory' / 'sync.lock'
def acquire(self):
if self.lock_file.exists():
pid = int(self.lock_file.read_text())
if psutil.pid_exists(pid):
raise BisyncError(
f"Sync already running (PID {pid}). "
f"Wait for completion or kill stale process."
)
# Stale lock, remove it
self.lock_file.unlink()
self.lock_file.write_text(str(os.getpid()))
def release(self):
if self.lock_file.exists():
self.lock_file.unlink()
def __enter__(self):
self.acquire()
return self
def __exit__(self, *args):
self.release()
# Usage
with SyncLock():
run_rclone_bisync()
```
### 3. Sync Report Parsing
```python
# Parse rclone bisync output
import json
from datetime import datetime
from pathlib import Path
def parse_sync_report(rclone_output: str, duration: float, exit_code: int) -> dict:
"""Parse rclone bisync output into structured report."""
# rclone bisync outputs lines like:
# "Synching Path1 /local/path with Path2 remote:bucket"
# "- Path1 File was copied to Path2"
# "Bisync successful"
report = {
"timestamp": datetime.now().isoformat(),
"duration_seconds": duration,
"exit_code": exit_code,
"success": exit_code == 0,
"files_created": 0,
"files_updated": 0,
"files_deleted": 0,
"conflicts": [],
"errors": []
}
for line in rclone_output.split('\n'):
if 'was copied to' in line:
report['files_created'] += 1
elif 'was updated in' in line:
report['files_updated'] += 1
elif 'was deleted from' in line:
report['files_deleted'] += 1
elif '.conflict' in line:
report['conflicts'].append(line.strip())
elif 'ERROR' in line:
report['errors'].append(line.strip())
return report
def save_sync_report(report: dict):
"""Save sync report to history."""
history_dir = Path.home() / '.basic-memory' / 'sync-history'
history_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
report_file = history_dir / f'{timestamp}.json'
report_file.write_text(json.dumps(report, indent=2))
# Usage in run_bisync()
start_time = time.time()
result = subprocess.run(bisync_cmd, capture_output=True, text=True)
duration = time.time() - start_time
report = parse_sync_report(result.stdout, duration, result.returncode)
save_sync_report(report)
if report['conflicts']:
console.print(f"[yellow]⚠ {len(report['conflicts'])} conflict(s) detected[/yellow]")
console.print("[dim]Run 'bm conflicts list' to view[/dim]")
```
### 4. User Commands
```bash
# View sync history
bm sync history
# → Lists recent syncs from ~/.basic-memory/sync-history/*.json
# → Shows: timestamp, duration, files changed, conflicts, errors
# View current conflicts
bm conflicts list
# → Scans sync directory for *.conflict* files
# → Shows: file path, conflict versions, timestamps
# Restore from S3 version
bm cloud restore work/notes.md --version-id abc123
# → Uses aws s3api get-object with version-id
# → Downloads to original path
bm cloud restore work/notes.md --timestamp "2025-10-03 14:30"
# → Lists versions, finds closest to timestamp
# → Downloads that version
# List file versions
bm cloud versions work/notes.md
# → Uses aws s3api list-object-versions
# → Shows: version-id, timestamp, size, author
# Interactive conflict resolution
bm conflicts resolve work/notes.md
# → Shows both versions side-by-side
# → Prompts: Keep local, keep cloud, merge manually, restore from S3 version
# → Cleans up .conflict files after resolution
```
## Success Metrics & Monitoring
**Phase 1 (v1) - Basic Safety:**
- [ ] Conflict detection rate < 5% of syncs (measure in telemetry)
- [ ] User can resolve conflicts within 5 minutes (UX testing)
- [ ] Documentation prevents 90% of multi-device issues
**Phase 2 (v2) - Observability:**
- [ ] 80% of users check `bm sync history` when troubleshooting
- [ ] Average time to restore from S3 version < 2 minutes
-
- [ ] Conflict resolution success rate > 95%
**What to measure:**
```python
# Telemetry in sync reports
{
"conflict_rate": conflicts / total_syncs,
"multi_device_collisions": count_state_divergence_errors,
"version_restores": count_restore_operations,
"avg_sync_duration": sum(durations) / count,
"max_delete_trips": count_max_delete_aborts
}
```
**When to add distributed lease:**
- Multi-device collision rate > 5% of syncs
- User complaints about state divergence errors
- Evidence that local lock isn't sufficient
**When to revisit Git (SPEC-14):**
- Teams feature launches (multi-user collaboration)
- Users request commit messages / audit trail
- PR-based review workflow becomes valuable
## Links
- SPEC-9: `specs/spec-9-multi-project-bisync`
- SPEC-14: `specs/spec-14-cloud-git-versioning` (deferred in favor of S3 versioning)
- rclone bisync docs: https://rclone.org/bisync/
- Tigris S3 versioning: https://www.tigrisdata.com/docs/buckets/versioning/
---
**Owner:** <assign> | **Review cadence:** weekly in standup | **Last updated:** 2025-10-03
+1 -1
View File
@@ -1,7 +1,7 @@
"""basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
# Package version - updated by release automation
__version__ = "0.14.4"
__version__ = "0.17.2"
# API version for FastAPI - independent of package version
__api_version__ = "v0"
+110 -24
View File
@@ -1,17 +1,32 @@
"""Alembic environment configuration."""
import asyncio
import os
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
# Allow nested event loops (needed for pytest-asyncio and other async contexts)
# Note: nest_asyncio doesn't work with uvloop, so we handle that case separately
try:
import nest_asyncio
nest_asyncio.apply()
except (ImportError, ValueError):
# nest_asyncio not available or can't patch this loop type (e.g., uvloop)
pass
from sqlalchemy import engine_from_config, pool
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
from alembic import context
from basic_memory.config import ConfigManager
# set config.env to "test" for pytest to prevent logging to file in utils.setup_logging()
os.environ["BASIC_MEMORY_ENV"] = "test"
# Trigger: only set test env when actually running under pytest
# Why: alembic/env.py is imported during normal operations (MCP server startup, migrations)
# but we only want test behavior during actual test runs
# Outcome: prevents is_test_env from returning True in production, enabling watch service
if os.getenv("PYTEST_CURRENT_TEST") is not None:
os.environ["BASIC_MEMORY_ENV"] = "test"
# Import after setting environment variable # noqa: E402
from basic_memory.models import Base # noqa: E402
@@ -20,12 +35,22 @@ from basic_memory.models import Base # noqa: E402
# access to the values within the .ini file in use.
config = context.config
# Load app config - this will read environment variables (BASIC_MEMORY_DATABASE_BACKEND, etc.)
# due to Pydantic's env_prefix="BASIC_MEMORY_" setting
app_config = ConfigManager().config
# Set the SQLAlchemy URL from our app config
sqlalchemy_url = f"sqlite:///{app_config.database_path}"
config.set_main_option("sqlalchemy.url", sqlalchemy_url)
# print(f"Using SQLAlchemy URL: {sqlalchemy_url}")
# Set the SQLAlchemy URL based on database backend configuration
# If the URL is already set in config (e.g., from run_migrations), use that
# Otherwise, get it from app config
# Note: alembic.ini has a placeholder URL "driver://user:pass@localhost/dbname" that we need to override
current_url = config.get_main_option("sqlalchemy.url")
if not current_url or current_url == "driver://user:pass@localhost/dbname":
from basic_memory.db import DatabaseType
sqlalchemy_url = DatabaseType.get_db_url(
app_config.database_path, DatabaseType.FILESYSTEM, app_config
)
config.set_main_option("sqlalchemy.url", sqlalchemy_url)
# Interpret the config file for Python logging.
if config.config_file_name is not None:
@@ -69,28 +94,89 @@ def run_migrations_offline() -> None:
context.run_migrations()
def do_run_migrations(connection):
"""Execute migrations with the given connection."""
context.configure(
connection=connection,
target_metadata=target_metadata,
include_object=include_object,
render_as_batch=True,
compare_type=True,
)
with context.begin_transaction():
context.run_migrations()
async def run_async_migrations(connectable):
"""Run migrations asynchronously with AsyncEngine."""
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
await connectable.dispose()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
Supports both sync engines (SQLite) and async engines (PostgreSQL with asyncpg).
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
# Check if a connection/engine was provided (e.g., from run_migrations)
connectable = context.config.attributes.get("connection", None)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
include_object=include_object,
render_as_batch=True,
)
if connectable is None:
# No connection provided, create engine from config
url = context.config.get_main_option("sqlalchemy.url")
with context.begin_transaction():
context.run_migrations()
# Check if it's an async URL (sqlite+aiosqlite or postgresql+asyncpg)
if url and ("+asyncpg" in url or "+aiosqlite" in url):
# Create async engine for asyncpg or aiosqlite
connectable = create_async_engine(
url,
poolclass=pool.NullPool,
future=True,
)
else:
# Create sync engine for regular sqlite or postgresql
connectable = engine_from_config(
context.config.get_section(context.config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
# Handle async engines (PostgreSQL with asyncpg)
if isinstance(connectable, AsyncEngine):
# Try to run async migrations
# nest_asyncio allows asyncio.run() from within event loops, but doesn't work with uvloop
try:
asyncio.run(run_async_migrations(connectable))
except RuntimeError as e:
if "cannot be called from a running event loop" in str(e):
# We're in a running event loop (likely uvloop) - need to use a different approach
# Create a new thread to run the async migrations
import concurrent.futures
def run_in_thread():
"""Run async migrations in a new event loop in a separate thread."""
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
try:
new_loop.run_until_complete(run_async_migrations(connectable))
finally:
new_loop.close()
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(run_in_thread)
future.result() # Wait for completion and re-raise any exceptions
else:
raise
else:
# Handle sync engines (SQLite) or sync connections
if hasattr(connectable, "connect"):
# It's an engine, get a connection
with connectable.connect() as connection:
do_run_migrations(connection)
else:
# It's already a connection
do_run_migrations(connectable)
if context.is_offline_mode():
@@ -0,0 +1,131 @@
"""Add Postgres full-text search support with tsvector and GIN indexes
Revision ID: 314f1ea54dc4
Revises: e7e1f4367280
Create Date: 2025-11-15 18:05:01.025405
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "314f1ea54dc4"
down_revision: Union[str, None] = "e7e1f4367280"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Add PostgreSQL full-text search support.
This migration:
1. Creates search_index table for Postgres (SQLite uses FTS5 virtual table)
2. Adds generated tsvector column for full-text search
3. Creates GIN index on the tsvector column for fast text queries
4. Creates GIN index on metadata JSONB column for fast containment queries
Note: These changes only apply to Postgres. SQLite continues to use FTS5 virtual tables.
"""
# Check if we're using Postgres
connection = op.get_bind()
if connection.dialect.name == "postgresql":
# Create search_index table for Postgres
# For SQLite, this is a FTS5 virtual table created elsewhere
from sqlalchemy.dialects.postgresql import JSONB
op.create_table(
"search_index",
sa.Column("id", sa.Integer(), nullable=False), # Entity IDs are integers
sa.Column("project_id", sa.Integer(), nullable=False), # Multi-tenant isolation
sa.Column("title", sa.Text(), nullable=True),
sa.Column("content_stems", sa.Text(), nullable=True),
sa.Column("content_snippet", sa.Text(), nullable=True),
sa.Column("permalink", sa.String(), nullable=True), # Nullable for non-markdown files
sa.Column("file_path", sa.String(), nullable=True),
sa.Column("type", sa.String(), nullable=True),
sa.Column("from_id", sa.Integer(), nullable=True), # Relation IDs are integers
sa.Column("to_id", sa.Integer(), nullable=True), # Relation IDs are integers
sa.Column("relation_type", sa.String(), nullable=True),
sa.Column("entity_id", sa.Integer(), nullable=True), # Entity IDs are integers
sa.Column("category", sa.String(), nullable=True),
sa.Column("metadata", JSONB(), nullable=True), # Use JSONB for Postgres
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint(
"id", "type", "project_id"
), # Composite key: id can repeat across types
sa.ForeignKeyConstraint(
["project_id"],
["project.id"],
name="fk_search_index_project_id",
ondelete="CASCADE",
),
if_not_exists=True,
)
# Create index on project_id for efficient multi-tenant queries
op.create_index(
"ix_search_index_project_id",
"search_index",
["project_id"],
unique=False,
)
# Create unique partial index on permalink for markdown files
# Non-markdown files don't have permalinks, so we use a partial index
op.execute("""
CREATE UNIQUE INDEX uix_search_index_permalink_project
ON search_index (permalink, project_id)
WHERE permalink IS NOT NULL
""")
# Add tsvector column as a GENERATED ALWAYS column
# This automatically updates when title or content_stems change
op.execute("""
ALTER TABLE search_index
ADD COLUMN textsearchable_index_col tsvector
GENERATED ALWAYS AS (
to_tsvector('english',
coalesce(title, '') || ' ' ||
coalesce(content_stems, '')
)
) STORED
""")
# Create GIN index on tsvector column for fast full-text search
op.create_index(
"idx_search_index_fts",
"search_index",
["textsearchable_index_col"],
unique=False,
postgresql_using="gin",
)
# Create GIN index on metadata JSONB for fast containment queries
# Using jsonb_path_ops for smaller index size and better performance
op.execute("""
CREATE INDEX idx_search_index_metadata_gin
ON search_index
USING GIN (metadata jsonb_path_ops)
""")
def downgrade() -> None:
"""Remove PostgreSQL full-text search support."""
connection = op.get_bind()
if connection.dialect.name == "postgresql":
# Drop indexes first
op.execute("DROP INDEX IF EXISTS idx_search_index_metadata_gin")
op.drop_index("idx_search_index_fts", table_name="search_index")
op.execute("DROP INDEX IF EXISTS uix_search_index_permalink_project")
op.drop_index("ix_search_index_project_id", table_name="search_index")
# Drop the generated column
op.execute("ALTER TABLE search_index DROP COLUMN IF EXISTS textsearchable_index_col")
# Drop the search_index table
op.drop_table("search_index")
@@ -21,6 +21,12 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# SQLite FTS5 virtual table handling is SQLite-specific
# For Postgres, search_index is a regular table managed by ORM
connection = op.get_bind()
is_sqlite = connection.dialect.name == "sqlite"
op.create_table(
"project",
sa.Column("id", sa.Integer(), nullable=False),
@@ -55,7 +61,9 @@ def upgrade() -> None:
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"),
sqlite_where=sa.text("content_type = 'text/markdown' AND permalink IS NOT NULL")
if is_sqlite
else None,
)
batch_op.drop_index("ix_entity_file_path")
batch_op.create_index(batch_op.f("ix_entity_file_path"), ["file_path"], unique=False)
@@ -67,12 +75,16 @@ def upgrade() -> None:
"uix_entity_permalink_project",
["permalink", "project_id"],
unique=True,
sqlite_where=sa.text("content_type = 'text/markdown' AND permalink IS NOT NULL"),
sqlite_where=sa.text("content_type = 'text/markdown' AND permalink IS NOT NULL")
if is_sqlite
else None,
)
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")
# Only drop for SQLite - Postgres uses regular table managed by ORM
if is_sqlite:
op.drop_table("search_index")
# ### end Alembic commands ###
@@ -25,43 +25,51 @@ def upgrade() -> None:
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.
SQLite: Recreate the table without the constraint (no ALTER TABLE support)
Postgres: Use ALTER TABLE to drop the constraint directly
"""
# 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"),
)
connection = op.get_bind()
is_sqlite = connection.dialect.name == "sqlite"
# Copy data from old table to new table
op.execute("INSERT INTO project_new SELECT * FROM project")
if is_sqlite:
# 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"),
)
# Drop the old table
op.drop_table("project")
# Copy data from old table to new table
op.execute("INSERT INTO project_new SELECT * FROM project")
# Rename the new table
op.rename_table("project_new", "project")
# Drop the old table
op.drop_table("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)
# 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)
else:
# For Postgres, we can simply drop the constraint
with op.batch_alter_table("project", schema=None) as batch_op:
batch_op.drop_constraint("project_is_default_key", type_="unique")
def downgrade() -> None:
@@ -0,0 +1,49 @@
"""Add mtime and size columns to Entity for sync optimization
Revision ID: 9d9c1cb7d8f5
Revises: a1b2c3d4e5f6
Create Date: 2025-10-20 05:07:55.173849
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "9d9c1cb7d8f5"
down_revision: Union[str, None] = "a1b2c3d4e5f6"
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! ###
with op.batch_alter_table("entity", schema=None) as batch_op:
batch_op.add_column(sa.Column("mtime", sa.Float(), nullable=True))
batch_op.add_column(sa.Column("size", sa.Integer(), nullable=True))
batch_op.drop_constraint(batch_op.f("fk_entity_project_id"), type_="foreignkey")
batch_op.create_foreign_key(
batch_op.f("fk_entity_project_id"), "project", ["project_id"], ["id"]
)
# ### 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(batch_op.f("fk_entity_project_id"), type_="foreignkey")
batch_op.create_foreign_key(
batch_op.f("fk_entity_project_id"),
"project",
["project_id"],
["id"],
ondelete="CASCADE",
)
batch_op.drop_column("size")
batch_op.drop_column("mtime")
# ### end Alembic commands ###
@@ -20,14 +20,14 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Re-establish foreign key constraints that were lost during project table recreation.
The migration 647e7a75e2cd recreated the project table but did not re-establish
the foreign key constraint from entity.project_id to project.id, causing
foreign key constraint failures when trying to delete projects with related entities.
"""
# SQLite doesn't allow adding foreign key constraints to existing tables easily
# We need to be careful and handle the case where the constraint might already exist
with op.batch_alter_table("entity", schema=None) as batch_op:
# Try to drop existing foreign key constraint (may not exist)
try:
@@ -35,19 +35,15 @@ def upgrade() -> None:
except Exception:
# Constraint may not exist, which is fine - we'll create it next
pass
# Add the foreign key constraint with CASCADE DELETE
# This ensures that when a project is deleted, all related entities are also deleted
batch_op.create_foreign_key(
"fk_entity_project_id",
"project",
["project_id"],
["id"],
ondelete="CASCADE"
"fk_entity_project_id", "project", ["project_id"], ["id"], ondelete="CASCADE"
)
def downgrade() -> None:
"""Remove the foreign key constraint."""
with op.batch_alter_table("entity", schema=None) as batch_op:
batch_op.drop_constraint("fk_entity_project_id", type_="foreignkey")
batch_op.drop_constraint("fk_entity_project_id", type_="foreignkey")
@@ -0,0 +1,56 @@
"""Add cascade delete FK from search_index to entity
Revision ID: a2b3c4d5e6f7
Revises: f8a9b2c3d4e5
Create Date: 2025-12-02 07:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "a2b3c4d5e6f7"
down_revision: Union[str, None] = "f8a9b2c3d4e5"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Add FK with CASCADE delete from search_index.entity_id to entity.id.
This migration is Postgres-only because:
- SQLite uses FTS5 virtual tables which don't support foreign keys
- The FK enables automatic cleanup of search_index entries when entities are deleted
"""
connection = op.get_bind()
dialect = connection.dialect.name
if dialect == "postgresql":
# First, clean up any orphaned search_index entries where entity no longer exists
op.execute("""
DELETE FROM search_index
WHERE entity_id IS NOT NULL
AND entity_id NOT IN (SELECT id FROM entity)
""")
# Add FK with CASCADE - nullable FK allows search_index entries without entity_id
op.create_foreign_key(
"fk_search_index_entity_id",
"search_index",
"entity",
["entity_id"],
["id"],
ondelete="CASCADE",
)
def downgrade() -> None:
"""Remove the FK constraint."""
connection = op.get_bind()
dialect = connection.dialect.name
if dialect == "postgresql":
op.drop_constraint("fk_search_index_entity_id", "search_index", type_="foreignkey")
@@ -21,6 +21,12 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade database schema to use new search index with content_stems and content_snippet."""
# This migration is SQLite-specific (FTS5 virtual tables)
# For Postgres, the search_index table is created via ORM models
connection = op.get_bind()
if connection.dialect.name != "sqlite":
return
# First, drop the existing search_index table
op.execute("DROP TABLE IF EXISTS search_index")
@@ -59,6 +65,13 @@ def upgrade() -> None:
def downgrade() -> None:
"""Downgrade database schema to use old search index."""
# This migration is SQLite-specific (FTS5 virtual tables)
# For Postgres, the search_index table is managed via ORM models
connection = op.get_bind()
if connection.dialect.name != "sqlite":
return
# Drop the updated search_index table
op.execute("DROP TABLE IF EXISTS search_index")
@@ -0,0 +1,37 @@
"""Add scan watermark tracking to Project
Revision ID: e7e1f4367280
Revises: 9d9c1cb7d8f5
Create Date: 2025-10-20 16:42:46.625075
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "e7e1f4367280"
down_revision: Union[str, None] = "9d9c1cb7d8f5"
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! ###
with op.batch_alter_table("project", schema=None) as batch_op:
batch_op.add_column(sa.Column("last_scan_timestamp", sa.Float(), nullable=True))
batch_op.add_column(sa.Column("last_file_count", sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("project", schema=None) as batch_op:
batch_op.drop_column("last_file_count")
batch_op.drop_column("last_scan_timestamp")
# ### end Alembic commands ###
@@ -0,0 +1,239 @@
"""Add project_id to relation/observation and pg_trgm for fuzzy link resolution
Revision ID: f8a9b2c3d4e5
Revises: 314f1ea54dc4
Create Date: 2025-12-01 12:00:00.000000
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy import text
def column_exists(connection, table: str, column: str) -> bool:
"""Check if a column exists in a table (idempotent migration support)."""
if connection.dialect.name == "postgresql":
result = connection.execute(
text(
"SELECT 1 FROM information_schema.columns "
"WHERE table_name = :table AND column_name = :column"
),
{"table": table, "column": column},
)
return result.fetchone() is not None
else:
# SQLite
result = connection.execute(text(f"PRAGMA table_info({table})"))
columns = [row[1] for row in result]
return column in columns
def index_exists(connection, index_name: str) -> bool:
"""Check if an index exists (idempotent migration support)."""
if connection.dialect.name == "postgresql":
result = connection.execute(
text("SELECT 1 FROM pg_indexes WHERE indexname = :index_name"),
{"index_name": index_name},
)
return result.fetchone() is not None
else:
# SQLite
result = connection.execute(
text("SELECT 1 FROM sqlite_master WHERE type='index' AND name = :index_name"),
{"index_name": index_name},
)
return result.fetchone() is not None
# revision identifiers, used by Alembic.
revision: str = "f8a9b2c3d4e5"
down_revision: Union[str, None] = "314f1ea54dc4"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Add project_id to relation and observation tables, plus pg_trgm indexes.
This migration:
1. Adds project_id column to relation and observation tables (denormalization)
2. Backfills project_id from the associated entity
3. Enables pg_trgm extension for trigram-based fuzzy matching (Postgres only)
4. Creates GIN indexes on entity title and permalink for fast similarity searches
5. Creates partial index on unresolved relations for efficient bulk resolution
"""
connection = op.get_bind()
dialect = connection.dialect.name
# -------------------------------------------------------------------------
# Add project_id to relation table
# -------------------------------------------------------------------------
# Step 1: Add project_id column as nullable first (idempotent)
if not column_exists(connection, "relation", "project_id"):
op.add_column("relation", sa.Column("project_id", sa.Integer(), nullable=True))
# Step 2: Backfill project_id from entity.project_id via from_id
if dialect == "postgresql":
op.execute("""
UPDATE relation
SET project_id = entity.project_id
FROM entity
WHERE relation.from_id = entity.id
""")
else:
# SQLite syntax
op.execute("""
UPDATE relation
SET project_id = (
SELECT entity.project_id
FROM entity
WHERE entity.id = relation.from_id
)
""")
# Step 3: Make project_id NOT NULL and add foreign key
if dialect == "postgresql":
op.alter_column("relation", "project_id", nullable=False)
op.create_foreign_key(
"fk_relation_project_id",
"relation",
"project",
["project_id"],
["id"],
)
else:
# SQLite requires batch operations for ALTER COLUMN
with op.batch_alter_table("relation") as batch_op:
batch_op.alter_column("project_id", nullable=False)
batch_op.create_foreign_key(
"fk_relation_project_id",
"project",
["project_id"],
["id"],
)
# Step 4: Create index on relation.project_id (idempotent)
if not index_exists(connection, "ix_relation_project_id"):
op.create_index("ix_relation_project_id", "relation", ["project_id"])
# -------------------------------------------------------------------------
# Add project_id to observation table
# -------------------------------------------------------------------------
# Step 1: Add project_id column as nullable first (idempotent)
if not column_exists(connection, "observation", "project_id"):
op.add_column("observation", sa.Column("project_id", sa.Integer(), nullable=True))
# Step 2: Backfill project_id from entity.project_id via entity_id
if dialect == "postgresql":
op.execute("""
UPDATE observation
SET project_id = entity.project_id
FROM entity
WHERE observation.entity_id = entity.id
""")
else:
# SQLite syntax
op.execute("""
UPDATE observation
SET project_id = (
SELECT entity.project_id
FROM entity
WHERE entity.id = observation.entity_id
)
""")
# Step 3: Make project_id NOT NULL and add foreign key
if dialect == "postgresql":
op.alter_column("observation", "project_id", nullable=False)
op.create_foreign_key(
"fk_observation_project_id",
"observation",
"project",
["project_id"],
["id"],
)
else:
# SQLite requires batch operations for ALTER COLUMN
with op.batch_alter_table("observation") as batch_op:
batch_op.alter_column("project_id", nullable=False)
batch_op.create_foreign_key(
"fk_observation_project_id",
"project",
["project_id"],
["id"],
)
# Step 4: Create index on observation.project_id (idempotent)
if not index_exists(connection, "ix_observation_project_id"):
op.create_index("ix_observation_project_id", "observation", ["project_id"])
# Postgres-specific: pg_trgm and GIN indexes
if dialect == "postgresql":
# Enable pg_trgm extension for fuzzy string matching
op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm")
# Create trigram indexes on entity table for fuzzy matching
# GIN indexes with gin_trgm_ops support similarity searches
op.execute("""
CREATE INDEX IF NOT EXISTS idx_entity_title_trgm
ON entity USING gin (title gin_trgm_ops)
""")
op.execute("""
CREATE INDEX IF NOT EXISTS idx_entity_permalink_trgm
ON entity USING gin (permalink gin_trgm_ops)
""")
# Create partial index on unresolved relations for efficient bulk resolution
# This makes "WHERE to_id IS NULL AND project_id = X" queries very fast
op.execute("""
CREATE INDEX IF NOT EXISTS idx_relation_unresolved
ON relation (project_id, to_name)
WHERE to_id IS NULL
""")
# Create index on relation.to_name for join performance in bulk resolution
op.execute("""
CREATE INDEX IF NOT EXISTS idx_relation_to_name
ON relation (to_name)
""")
def downgrade() -> None:
"""Remove project_id from relation/observation and pg_trgm indexes."""
connection = op.get_bind()
dialect = connection.dialect.name
if dialect == "postgresql":
# Drop Postgres-specific indexes
op.execute("DROP INDEX IF EXISTS idx_relation_to_name")
op.execute("DROP INDEX IF EXISTS idx_relation_unresolved")
op.execute("DROP INDEX IF EXISTS idx_entity_permalink_trgm")
op.execute("DROP INDEX IF EXISTS idx_entity_title_trgm")
# Note: We don't drop the pg_trgm extension as other code may depend on it
# Drop project_id from observation
op.drop_index("ix_observation_project_id", table_name="observation")
op.drop_constraint("fk_observation_project_id", "observation", type_="foreignkey")
op.drop_column("observation", "project_id")
# Drop project_id from relation
op.drop_index("ix_relation_project_id", table_name="relation")
op.drop_constraint("fk_relation_project_id", "relation", type_="foreignkey")
op.drop_column("relation", "project_id")
else:
# SQLite requires batch operations
op.drop_index("ix_observation_project_id", table_name="observation")
with op.batch_alter_table("observation") as batch_op:
batch_op.drop_constraint("fk_observation_project_id", type_="foreignkey")
batch_op.drop_column("project_id")
op.drop_index("ix_relation_project_id", table_name="relation")
with op.batch_alter_table("relation") as batch_op:
batch_op.drop_constraint("fk_relation_project_id", type_="foreignkey")
batch_op.drop_column("project_id")
+53 -14
View File
@@ -20,26 +20,54 @@ from basic_memory.api.routers import (
search,
prompt_router,
)
from basic_memory.config import ConfigManager
from basic_memory.services.initialization import initialize_app, initialize_file_sync
from basic_memory.api.v2.routers import (
knowledge_router as v2_knowledge,
project_router as v2_project,
memory_router as v2_memory,
search_router as v2_search,
resource_router as v2_resource,
directory_router as v2_directory,
prompt_router as v2_prompt,
importer_router as v2_importer,
)
from basic_memory.config import ConfigManager, init_api_logging
from basic_memory.services.initialization import initialize_file_sync, initialize_app
@asynccontextmanager
async def lifespan(app: FastAPI): # pragma: no cover
"""Lifecycle manager for the FastAPI app."""
"""Lifecycle manager for the FastAPI app. Not called in stdio mcp mode"""
# Initialize logging for API (stdout in cloud mode, file otherwise)
init_api_logging()
app_config = ConfigManager().config
# Initialize app and database
logger.info("Starting Basic Memory API")
print(f"fastapi {app_config.projects}")
await initialize_app(app_config)
logger.info(f"Sync changes enabled: {app_config.sync_changes}")
if app_config.sync_changes:
# Cache database connections in app state for performance
logger.info("Initializing database and caching connections...")
engine, session_maker = await db.get_or_create_db(app_config.database_path)
app.state.engine = engine
app.state.session_maker = session_maker
logger.info("Database connections cached in app state")
# Start file sync if enabled
if app_config.sync_changes and not app_config.is_test_env:
logger.info(f"Sync changes enabled: {app_config.sync_changes}")
# start file sync task in background
app.state.sync_task = asyncio.create_task(initialize_file_sync(app_config))
async def _file_sync_runner() -> None:
await initialize_file_sync(app_config)
app.state.sync_task = asyncio.create_task(_file_sync_runner())
else:
logger.info("Sync changes disabled. Skipping file sync service.")
if app_config.is_test_env:
logger.info("Test environment detected. Skipping file sync service.")
else:
logger.info("Sync changes disabled. Skipping file sync service.")
app.state.sync_task = None
# proceed with startup
yield
@@ -48,6 +76,10 @@ async def lifespan(app: FastAPI): # pragma: no cover
if app.state.sync_task:
logger.info("Stopping sync...")
app.state.sync_task.cancel() # pyright: ignore
try:
await app.state.sync_task
except asyncio.CancelledError:
logger.info("Sync task cancelled successfully")
await db.shutdown_db()
@@ -60,8 +92,7 @@ app = FastAPI(
lifespan=lifespan,
)
# Include routers
# Include v1 routers
app.include_router(knowledge.router, prefix="/{project}")
app.include_router(memory.router, prefix="/{project}")
app.include_router(resource.router, prefix="/{project}")
@@ -71,12 +102,20 @@ 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
# Include v2 routers (ID-based paths)
app.include_router(v2_knowledge, prefix="/v2/projects/{project_id}")
app.include_router(v2_memory, prefix="/v2/projects/{project_id}")
app.include_router(v2_search, prefix="/v2/projects/{project_id}")
app.include_router(v2_resource, prefix="/v2/projects/{project_id}")
app.include_router(v2_directory, prefix="/v2/projects/{project_id}")
app.include_router(v2_prompt, prefix="/v2/projects/{project_id}")
app.include_router(v2_importer, prefix="/v2/projects/{project_id}")
app.include_router(v2_project, prefix="/v2")
# Project resource router works across 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)
async def exception_handler(request, exc): # pragma: no cover
@@ -10,7 +10,7 @@ from basic_memory.schemas.directory import DirectoryNode
router = APIRouter(prefix="/directory", tags=["directory"])
@router.get("/tree", response_model=DirectoryNode)
@router.get("/tree", response_model=DirectoryNode, response_model_exclude_none=True)
async def get_directory_tree(
directory_service: DirectoryServiceDep,
project_id: ProjectIdDep,
@@ -31,7 +31,28 @@ async def get_directory_tree(
return tree
@router.get("/list", response_model=List[DirectoryNode])
@router.get("/structure", response_model=DirectoryNode, response_model_exclude_none=True)
async def get_directory_structure(
directory_service: DirectoryServiceDep,
project_id: ProjectIdDep,
):
"""Get folder structure for navigation (no files).
Optimized endpoint for folder tree navigation. Returns only directory nodes
without file metadata. For full tree with files, use /directory/tree.
Args:
directory_service: Service for directory operations
project_id: ID of the current project
Returns:
DirectoryNode tree containing only folders (type="directory")
"""
structure = await directory_service.get_directory_structure()
return structure
@router.get("/list", response_model=List[DirectoryNode], response_model_exclude_none=True)
async def list_directory(
directory_service: DirectoryServiceDep,
project_id: ProjectIdDep,
@@ -1,4 +1,11 @@
"""Router for knowledge graph operations."""
"""Router for knowledge graph operations.
DEPRECATED: This v1 API is deprecated and will be removed on June 30, 2026.
Please migrate to /v2/{project}/knowledge endpoints which use entity IDs instead
of path-based identifiers for improved performance and stability.
Migration guide: See docs/migration/v1-to-v2.md
"""
from typing import Annotated
@@ -25,7 +32,31 @@ from basic_memory.schemas import (
from basic_memory.schemas.request import EditEntityRequest, MoveEntityRequest
from basic_memory.schemas.base import Permalink, Entity
router = APIRouter(prefix="/knowledge", tags=["knowledge"])
router = APIRouter(
prefix="/knowledge",
tags=["knowledge"],
deprecated=True, # Marks entire router as deprecated in OpenAPI docs
)
async def resolve_relations_background(sync_service, entity_id: int, entity_permalink: str) -> None:
"""Background task to resolve relations for a specific entity.
This runs asynchronously after the API response is sent, preventing
long delays when creating entities with many relations.
"""
try:
# Only resolve relations for the newly created entity
await sync_service.resolve_relations(entity_id=entity_id)
logger.debug(
f"Background: Resolved relations for entity {entity_permalink} (id={entity_id})"
)
except Exception as e:
# Log but don't fail - this is a background task
logger.warning(
f"Background: Failed to resolve relations for entity {entity_permalink}: {e}"
)
## Create endpoints
@@ -88,15 +119,12 @@ 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
# Schedule relation resolution as a background task for new entities
# This prevents blocking the API response while resolving potentially many relations
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}")
background_tasks.add_task(
resolve_relations_background, sync_service, entity.id, entity.permalink or ""
)
result = EntityResponse.model_validate(entity)
+222 -16
View File
@@ -1,19 +1,27 @@
"""Router for project management."""
import os
from fastapi import APIRouter, HTTPException, Path, Body
from fastapi import APIRouter, HTTPException, Path, Body, BackgroundTasks, Response, Query
from typing import Optional
from loguru import logger
from basic_memory.deps import ProjectServiceDep, ProjectPathDep
from basic_memory.schemas import ProjectInfoResponse
from basic_memory.deps import (
ProjectConfigDep,
ProjectServiceDep,
ProjectPathDep,
SyncServiceDep,
)
from basic_memory.schemas import ProjectInfoResponse, SyncReportResponse
from basic_memory.schemas.project_info import (
ProjectList,
ProjectItem,
ProjectInfoRequest,
ProjectStatusResponse,
)
from basic_memory.utils import normalize_project_path
# Router for resources in a specific project
# The ProjectPathDep is used in the path as a prefix, so the request path is like /{project}/project/info
project_router = APIRouter(prefix="/project", tags=["project"])
# Router for managing project resources
@@ -29,6 +37,26 @@ async def get_project_info(
return await project_service.get_project_info(project)
@project_router.get("/item", response_model=ProjectItem)
async def get_project(
project_service: ProjectServiceDep,
project: ProjectPathDep,
) -> ProjectItem:
"""Get bassic info about the specified Basic Memory project."""
found_project = await project_service.get_project(project)
if not found_project:
raise HTTPException(
status_code=404, detail=f"Project: '{project}' does not exist"
) # pragma: no cover
return ProjectItem(
id=found_project.id,
name=found_project.name,
path=normalize_project_path(found_project.path),
is_default=found_project.is_default or False,
)
# Update a project
@project_router.patch("/{name}", response_model=ProjectStatusResponse)
async def update_project(
@@ -53,9 +81,17 @@ async def update_project(
raise HTTPException(status_code=400, detail="Path must be absolute")
# Get original project info for the response
old_project = await project_service.get_project(name)
if not old_project:
raise HTTPException(
status_code=400, detail=f"Project '{name}' not found in configuration"
)
old_project_info = ProjectItem(
name=name,
path=project_service.projects.get(name, ""),
id=old_project.id,
name=old_project.name,
path=old_project.path,
is_default=old_project.is_default or False,
)
if path:
@@ -64,19 +100,93 @@ async def update_project(
await project_service.update_project(name, is_active=is_active)
# Get updated project info
updated_path = path if path else project_service.projects.get(name, "")
updated_project = await project_service.get_project(name)
if not updated_project:
raise HTTPException(status_code=404, detail=f"Project '{name}' not found after update")
return ProjectStatusResponse(
message=f"Project '{name}' updated successfully",
status="success",
default=(name == project_service.default_project),
old_project=old_project_info,
new_project=ProjectItem(name=name, path=updated_path),
new_project=ProjectItem(
id=updated_project.id,
name=updated_project.name,
path=updated_project.path,
is_default=updated_project.is_default or False,
),
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# Sync project filesystem
@project_router.post("/sync")
async def sync_project(
background_tasks: BackgroundTasks,
sync_service: SyncServiceDep,
project_config: ProjectConfigDep,
force_full: bool = Query(
False, description="Force full scan, bypassing watermark optimization"
),
run_in_background: bool = Query(True, description="Run in background"),
):
"""Force project filesystem sync to database.
Scans the project directory and updates the database with any new or modified files.
Args:
background_tasks: FastAPI background tasks
sync_service: Sync service for this project
project_config: Project configuration
force_full: If True, force a full scan even if watermark exists
run_in_background: If True, run sync in background and return immediately
Returns:
Response confirming sync was initiated (background) or SyncReportResponse (foreground)
"""
if run_in_background:
background_tasks.add_task(
sync_service.sync, project_config.home, project_config.name, force_full=force_full
)
logger.info(
f"Filesystem sync initiated for project: {project_config.name} (force_full={force_full})"
)
return {
"status": "sync_started",
"message": f"Filesystem sync initiated for project '{project_config.name}'",
}
else:
report = await sync_service.sync(
project_config.home, project_config.name, force_full=force_full
)
logger.info(
f"Filesystem sync completed for project: {project_config.name} (force_full={force_full})"
)
return SyncReportResponse.from_sync_report(report)
@project_router.post("/status", response_model=SyncReportResponse)
async def project_sync_status(
sync_service: SyncServiceDep,
project_config: ProjectConfigDep,
) -> SyncReportResponse:
"""Scan directory for changes compared to database state.
Args:
sync_service: Sync service for this project
project_config: Project configuration
Returns:
Scan report with details on files that need syncing
"""
logger.info(f"Scanning filesystem for project: {project_config.name}")
sync_report = await sync_service.scan(project_config.home)
return SyncReportResponse.from_sync_report(sync_report)
# List all available projects
@project_resource_router.get("/projects", response_model=ProjectList)
async def list_projects(
@@ -92,8 +202,9 @@ async def list_projects(
project_items = [
ProjectItem(
id=project.id,
name=project.name,
path=project.path,
path=normalize_project_path(project.path),
is_default=project.is_default or False,
)
for project in projects
@@ -106,8 +217,9 @@ async def list_projects(
# Add a new project
@project_resource_router.post("/projects", response_model=ProjectStatusResponse)
@project_resource_router.post("/projects", response_model=ProjectStatusResponse, status_code=201)
async def add_project(
response: Response,
project_data: ProjectInfoRequest,
project_service: ProjectServiceDep,
) -> ProjectStatusResponse:
@@ -119,17 +231,57 @@ async def add_project(
Returns:
Response confirming the project was added
"""
# Check if project already exists before attempting to add
existing_project = await project_service.get_project(project_data.name)
if existing_project:
# Project exists - check if paths match for true idempotency
# Normalize paths for comparison (resolve symlinks, etc.)
from pathlib import Path
requested_path = Path(project_data.path).resolve()
existing_path = Path(existing_project.path).resolve()
if requested_path == existing_path:
# Same name, same path - return 200 OK (idempotent)
response.status_code = 200
return ProjectStatusResponse( # pyright: ignore [reportCallIssue]
message=f"Project '{project_data.name}' already exists",
status="success",
default=existing_project.is_default or False,
new_project=ProjectItem(
id=existing_project.id,
name=existing_project.name,
path=existing_project.path,
is_default=existing_project.is_default or False,
),
)
else:
# Same name, different path - this is an error
raise HTTPException(
status_code=400,
detail=f"Project '{project_data.name}' already exists with different path. Existing: {existing_project.path}, Requested: {project_data.path}",
)
try: # pragma: no cover
# The service layer now handles cloud mode validation and path sanitization
await project_service.add_project(
project_data.name, project_data.path, set_default=project_data.set_default
)
# Fetch the newly created project to get its ID
new_project = await project_service.get_project(project_data.name)
if not new_project:
raise HTTPException(status_code=500, detail="Failed to retrieve newly created project")
return ProjectStatusResponse( # pyright: ignore [reportCallIssue]
message=f"Project '{project_data.name}' added successfully",
message=f"Project '{new_project.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
id=new_project.id,
name=new_project.name,
path=new_project.path,
is_default=new_project.is_default or False,
),
)
except ValueError as e: # pragma: no cover
@@ -141,11 +293,15 @@ async def add_project(
async def remove_project(
project_service: ProjectServiceDep,
name: str = Path(..., description="Name of the project to remove"),
delete_notes: bool = Query(
False, description="If True, delete project directory from filesystem"
),
) -> ProjectStatusResponse:
"""Remove a project from configuration and database.
Args:
name: The name of the project to remove
delete_notes: If True, delete the project directory from the filesystem
Returns:
Response confirming the project was removed
@@ -157,13 +313,31 @@ async def remove_project(
status_code=404, detail=f"Project: '{name}' does not exist"
) # pragma: no cover
await project_service.remove_project(name)
# Check if trying to delete the default project
if name == project_service.default_project:
available_projects = await project_service.list_projects()
other_projects = [p.name for p in available_projects if p.name != name]
detail = f"Cannot delete default project '{name}'. "
if other_projects:
detail += (
f"Set another project as default first. Available: {', '.join(other_projects)}"
)
else:
detail += "This is the only project in your configuration."
raise HTTPException(status_code=400, detail=detail)
await project_service.remove_project(name, delete_notes=delete_notes)
return ProjectStatusResponse(
message=f"Project '{name}' removed successfully",
message=f"Project '{old_project.name}' removed successfully",
status="success",
default=False,
old_project=ProjectItem(name=old_project.name, path=old_project.path),
old_project=ProjectItem(
id=old_project.id,
name=old_project.name,
path=old_project.path,
is_default=old_project.is_default or False,
),
new_project=None,
)
except ValueError as e: # pragma: no cover
@@ -206,8 +380,14 @@ async def set_default_project(
message=f"Project '{name}' set as default successfully",
status="success",
default=True,
old_project=ProjectItem(name=default_name, path=default_project.path),
old_project=ProjectItem(
id=default_project.id,
name=default_name,
path=default_project.path,
is_default=False,
),
new_project=ProjectItem(
id=new_default_project.id,
name=name,
path=new_default_project.path,
is_default=True,
@@ -217,8 +397,34 @@ async def set_default_project(
raise HTTPException(status_code=400, detail=str(e))
# Get the default project
@project_resource_router.get("/default", response_model=ProjectItem)
async def get_default_project(
project_service: ProjectServiceDep,
) -> ProjectItem:
"""Get the default project.
Returns:
Response with project default information
"""
# 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"
)
return ProjectItem(
id=default_project.id,
name=default_project.name,
path=default_project.path,
is_default=True,
)
# Synchronize projects between config and database
@project_resource_router.post("/sync", response_model=ProjectStatusResponse)
@project_resource_router.post("/config/sync", response_model=ProjectStatusResponse)
async def synchronize_projects(
project_service: ProjectServiceDep,
) -> ProjectStatusResponse:
+49 -25
View File
@@ -2,9 +2,9 @@
import tempfile
from pathlib import Path
from typing import Annotated
from typing import Annotated, Union
from fastapi import APIRouter, HTTPException, BackgroundTasks, Body
from fastapi import APIRouter, HTTPException, BackgroundTasks, Body, Response
from fastapi.responses import FileResponse, JSONResponse
from loguru import logger
@@ -25,6 +25,17 @@ from datetime import datetime
router = APIRouter(prefix="/resource", tags=["resources"])
def _mtime_to_datetime(entity: EntityModel) -> datetime:
"""Convert entity mtime (file modification time) to datetime.
Returns the file's actual modification time, falling back to updated_at
if mtime is not available.
"""
if entity.mtime:
return datetime.fromtimestamp(entity.mtime).astimezone()
return entity.updated_at
def get_entity_ids(item: SearchIndexRow) -> set[int]:
match item.type:
case SearchItemType.ENTITY:
@@ -39,7 +50,7 @@ def get_entity_ids(item: SearchIndexRow) -> set[int]:
raise ValueError(f"Unexpected type: {item.type}")
@router.get("/{identifier:path}")
@router.get("/{identifier:path}", response_model=None)
async def get_resource_content(
config: ProjectConfigDep,
link_resolver: LinkResolverDep,
@@ -50,7 +61,7 @@ async def get_resource_content(
identifier: str,
page: int = 1,
page_size: int = 10,
) -> FileResponse:
) -> Union[Response, FileResponse]:
"""Get resource content by identifier: name or permalink."""
logger.debug(f"Getting content for: {identifier}")
@@ -81,13 +92,16 @@ async def get_resource_content(
# return single response
if len(results) == 1:
entity = results[0]
file_path = Path(f"{config.home}/{entity.file_path}")
if not file_path.exists():
# Check file exists via file_service (for cloud compatibility)
if not await file_service.exists(entity.file_path):
raise HTTPException(
status_code=404,
detail=f"File not found: {file_path}",
detail=f"File not found: {entity.file_path}",
)
return FileResponse(path=file_path)
# Read content via file_service as bytes (works with both local and S3)
content = await file_service.read_file_bytes(entity.file_path)
content_type = file_service.content_type(entity.file_path)
return Response(content=content, media_type=content_type)
# for multiple files, initialize a temporary file for writing the results
with tempfile.NamedTemporaryFile(delete=False, mode="w", suffix=".md") as tmp_file:
@@ -97,7 +111,7 @@ async def get_resource_content(
# Read content for each entity
content = await file_service.read_entity_content(result)
memory_url = normalize_memory_url(result.permalink)
modified_date = result.updated_at.isoformat()
modified_date = _mtime_to_datetime(result).isoformat()
checksum = result.checksum[:8] if result.checksum else ""
# Prepare the delimited content
@@ -151,27 +165,37 @@ async def write_resource(
try:
# Get content from request body
# Defensive type checking: ensure content is a string
# FastAPI should validate this, but if a dict somehow gets through
# (e.g., via JSON body parsing), we need to catch it here
if isinstance(content, dict):
logger.error(
f"Error writing resource {file_path}: "
f"content is a dict, expected string. Keys: {list(content.keys())}"
)
raise HTTPException(
status_code=400,
detail="content must be a string, not a dict. "
"Ensure request body is sent as raw string content, not JSON object.",
)
# Ensure it's UTF-8 string content
if isinstance(content, bytes): # pragma: no cover
content_str = content.decode("utf-8")
else:
content_str = str(content)
# Get full file path
full_path = Path(f"{config.home}/{file_path}")
# Ensure parent directory exists
full_path.parent.mkdir(parents=True, exist_ok=True)
# Write content to file
checksum = await file_service.write_file(full_path, content_str)
# Cloud compatibility: do not assume a local filesystem path structure.
# Delegate directory creation + writes to the configured FileService (local or S3).
await file_service.ensure_directory(Path(file_path).parent)
checksum = await file_service.write_file(file_path, content_str)
# Get file info
file_stats = file_service.file_stats(full_path)
file_metadata = await file_service.get_file_metadata(file_path)
# Determine file details
file_name = Path(file_path).name
content_type = file_service.content_type(full_path)
content_type = file_service.content_type(file_path)
entity_type = "canvas" if file_path.endswith(".canvas") else "file"
@@ -188,7 +212,7 @@ async def write_resource(
"content_type": content_type,
"file_path": file_path,
"checksum": checksum,
"updated_at": datetime.fromtimestamp(file_stats.st_mtime).astimezone(),
"updated_at": file_metadata.modified_at,
},
)
status_code = 200
@@ -200,8 +224,8 @@ async def write_resource(
content_type=content_type,
file_path=file_path,
checksum=checksum,
created_at=datetime.fromtimestamp(file_stats.st_ctime).astimezone(),
updated_at=datetime.fromtimestamp(file_stats.st_mtime).astimezone(),
created_at=file_metadata.created_at,
updated_at=file_metadata.modified_at,
)
entity = await entity_repository.add(entity)
status_code = 201
@@ -215,9 +239,9 @@ async def write_resource(
content={
"file_path": file_path,
"checksum": checksum,
"size": file_stats.st_size,
"created_at": file_stats.st_ctime,
"modified_at": file_stats.st_mtime,
"size": file_metadata.size,
"created_at": file_metadata.created_at.timestamp(),
"modified_at": file_metadata.modified_at.timestamp(),
},
)
except Exception as e: # pragma: no cover
+53 -14
View File
@@ -24,11 +24,30 @@ async def to_graph_context(
page: Optional[int] = None,
page_size: Optional[int] = None,
):
# First pass: collect all entity IDs needed for relations
entity_ids_needed: set[int] = set()
for context_item in context_result.results:
for item in (
[context_item.primary_result] + context_item.observations + context_item.related_results
):
if item.type == SearchItemType.RELATION:
if item.from_id: # pyright: ignore
entity_ids_needed.add(item.from_id) # pyright: ignore
if item.to_id:
entity_ids_needed.add(item.to_id)
# Batch fetch all entities at once
entity_lookup: dict[int, str] = {}
if entity_ids_needed:
entities = await entity_repository.find_by_ids(list(entity_ids_needed))
entity_lookup = {e.id: e.title for e in entities}
# Helper function to convert items to summaries
async def to_summary(item: SearchIndexRow | ContextResultRow):
def to_summary(item: SearchIndexRow | ContextResultRow):
match item.type:
case SearchItemType.ENTITY:
return EntitySummary(
entity_id=item.id,
title=item.title, # pyright: ignore
permalink=item.permalink,
content=item.content,
@@ -37,6 +56,8 @@ async def to_graph_context(
)
case SearchItemType.OBSERVATION:
return ObservationSummary(
observation_id=item.id,
entity_id=item.entity_id, # pyright: ignore
title=item.title, # pyright: ignore
file_path=item.file_path,
category=item.category, # pyright: ignore
@@ -45,15 +66,19 @@ async def to_graph_context(
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
from_title = entity_lookup.get(item.from_id) if item.from_id else None # pyright: ignore
to_title = entity_lookup.get(item.to_id) if item.to_id else None
return RelationSummary(
relation_id=item.id,
entity_id=item.entity_id, # pyright: ignore
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 if from_entity else None,
to_entity=to_entity.title if to_entity else None,
from_entity=from_title,
from_entity_id=item.from_id, # pyright: ignore
to_entity=to_title,
to_entity_id=item.to_id,
created_at=item.created_at,
)
case _: # pragma: no cover
@@ -63,23 +88,19 @@ async def to_graph_context(
hierarchical_results = []
for context_item in context_result.results:
# Process primary result
primary_result = await to_summary(context_item.primary_result)
primary_result = to_summary(context_item.primary_result)
# Process observations
observations = []
for obs in context_item.observations:
observations.append(await to_summary(obs))
# Process observations (always ObservationSummary, validated by context_service)
observations = [to_summary(obs) for obs in context_item.observations]
# Process related results
related = []
for rel in context_item.related_results:
related.append(await to_summary(rel))
related = [to_summary(rel) for rel in context_item.related_results]
# Add to hierarchical results
hierarchical_results.append(
ContextResult(
primary_result=primary_result,
observations=observations,
observations=observations, # pyright: ignore[reportArgumentType]
related_results=related,
)
)
@@ -111,6 +132,21 @@ async def to_search_results(entity_service: EntityService, results: List[SearchI
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
# Determine which IDs to set based on type
entity_id = None
observation_id = None
relation_id = None
if r.type == SearchItemType.ENTITY:
entity_id = r.id
elif r.type == SearchItemType.OBSERVATION:
observation_id = r.id
entity_id = r.entity_id # Parent entity
elif r.type == SearchItemType.RELATION:
relation_id = r.id
entity_id = r.entity_id # Parent entity
search_results.append(
SearchResult(
title=r.title, # pyright: ignore
@@ -121,6 +157,9 @@ async def to_search_results(entity_service: EntityService, results: List[SearchI
content=r.content,
file_path=r.file_path,
metadata=r.metadata,
entity_id=entity_id,
observation_id=observation_id,
relation_id=relation_id,
category=r.category,
from_entity=entities[0].permalink if entities else None,
to_entity=entities[1].permalink if len(entities) > 1 else None,
+35
View File
@@ -0,0 +1,35 @@
"""API v2 module - ID-based entity references.
Version 2 of the Basic Memory API uses integer entity IDs as the primary
identifier for improved performance and stability.
Key changes from v1:
- Entity lookups use integer IDs instead of paths/permalinks
- Direct database queries instead of cascading resolution
- Stable references that don't change with file moves
- Better caching support
All v2 routers are registered with the /v2 prefix.
"""
from basic_memory.api.v2.routers import (
knowledge_router,
memory_router,
project_router,
resource_router,
search_router,
directory_router,
prompt_router,
importer_router,
)
__all__ = [
"knowledge_router",
"memory_router",
"project_router",
"resource_router",
"search_router",
"directory_router",
"prompt_router",
"importer_router",
]
@@ -0,0 +1,21 @@
"""V2 API routers."""
from basic_memory.api.v2.routers.knowledge_router import router as knowledge_router
from basic_memory.api.v2.routers.project_router import router as project_router
from basic_memory.api.v2.routers.memory_router import router as memory_router
from basic_memory.api.v2.routers.search_router import router as search_router
from basic_memory.api.v2.routers.resource_router import router as resource_router
from basic_memory.api.v2.routers.directory_router import router as directory_router
from basic_memory.api.v2.routers.prompt_router import router as prompt_router
from basic_memory.api.v2.routers.importer_router import router as importer_router
__all__ = [
"knowledge_router",
"project_router",
"memory_router",
"search_router",
"resource_router",
"directory_router",
"prompt_router",
"importer_router",
]
@@ -0,0 +1,93 @@
"""V2 Directory Router - ID-based directory tree operations.
This router provides directory structure browsing for projects using
integer project IDs instead of name-based identifiers.
Key improvements:
- Direct project lookup via integer primary keys
- Consistent with other v2 endpoints
- Better performance through indexed queries
"""
from typing import List, Optional
from fastapi import APIRouter, Query
from basic_memory.deps import DirectoryServiceV2Dep, ProjectIdPathDep
from basic_memory.schemas.directory import DirectoryNode
router = APIRouter(prefix="/directory", tags=["directory-v2"])
@router.get("/tree", response_model=DirectoryNode, response_model_exclude_none=True)
async def get_directory_tree(
directory_service: DirectoryServiceV2Dep,
project_id: ProjectIdPathDep,
):
"""Get hierarchical directory structure from the knowledge base.
Args:
directory_service: Service for directory operations
project_id: Numeric project ID
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("/structure", response_model=DirectoryNode, response_model_exclude_none=True)
async def get_directory_structure(
directory_service: DirectoryServiceV2Dep,
project_id: ProjectIdPathDep,
):
"""Get folder structure for navigation (no files).
Optimized endpoint for folder tree navigation. Returns only directory nodes
without file metadata. For full tree with files, use /directory/tree.
Args:
directory_service: Service for directory operations
project_id: Numeric project ID
Returns:
DirectoryNode tree containing only folders (type="directory")
"""
structure = await directory_service.get_directory_structure()
return structure
@router.get("/list", response_model=List[DirectoryNode], response_model_exclude_none=True)
async def list_directory(
directory_service: DirectoryServiceV2Dep,
project_id: ProjectIdPathDep,
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: Numeric project ID
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,182 @@
"""V2 Import Router - ID-based data import operations.
This router uses v2 dependencies for consistent project ID handling.
Import endpoints use project_id in the path for consistency with other v2 endpoints.
"""
import json
import logging
from fastapi import APIRouter, Form, HTTPException, UploadFile, status
from basic_memory.deps import (
ChatGPTImporterV2Dep,
ClaudeConversationsImporterV2Dep,
ClaudeProjectsImporterV2Dep,
MemoryJsonImporterV2Dep,
ProjectIdPathDep,
)
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-v2"])
@router.post("/chatgpt", response_model=ChatImportResult)
async def import_chatgpt(
project_id: ProjectIdPathDep,
importer: ChatGPTImporterV2Dep,
file: UploadFile,
folder: str = Form("conversations"),
) -> ChatImportResult:
"""Import conversations from ChatGPT JSON export.
Args:
project_id: Validated numeric project ID from URL path
file: The ChatGPT conversations.json file.
folder: The folder to place the files in.
importer: ChatGPT importer instance.
Returns:
ChatImportResult with import statistics.
Raises:
HTTPException: If import fails.
"""
logger.info(f"V2 Importing ChatGPT conversations for project {project_id}")
return await import_file(importer, file, folder)
@router.post("/claude/conversations", response_model=ChatImportResult)
async def import_claude_conversations(
project_id: ProjectIdPathDep,
importer: ClaudeConversationsImporterV2Dep,
file: UploadFile,
folder: str = Form("conversations"),
) -> ChatImportResult:
"""Import conversations from Claude conversations.json export.
Args:
project_id: Validated numeric project ID from URL path
file: The Claude conversations.json file.
folder: The folder to place the files in.
importer: Claude conversations importer instance.
Returns:
ChatImportResult with import statistics.
Raises:
HTTPException: If import fails.
"""
logger.info(f"V2 Importing Claude conversations for project {project_id}")
return await import_file(importer, file, folder)
@router.post("/claude/projects", response_model=ProjectImportResult)
async def import_claude_projects(
project_id: ProjectIdPathDep,
importer: ClaudeProjectsImporterV2Dep,
file: UploadFile,
folder: str = Form("projects"),
) -> ProjectImportResult:
"""Import projects from Claude projects.json export.
Args:
project_id: Validated numeric project ID from URL path
file: The Claude projects.json file.
folder: The base folder to place the files in.
importer: Claude projects importer instance.
Returns:
ProjectImportResult with import statistics.
Raises:
HTTPException: If import fails.
"""
logger.info(f"V2 Importing Claude projects for project {project_id}")
return await import_file(importer, file, folder)
@router.post("/memory-json", response_model=EntityImportResult)
async def import_memory_json(
project_id: ProjectIdPathDep,
importer: MemoryJsonImporterV2Dep,
file: UploadFile,
folder: str = Form("conversations"),
) -> EntityImportResult:
"""Import entities and relations from a memory.json file.
Args:
project_id: Validated numeric project ID from URL path
file: The memory.json file.
folder: Optional destination folder within the project.
importer: Memory JSON importer instance.
Returns:
EntityImportResult with import statistics.
Raises:
HTTPException: If import fails.
"""
logger.info(f"V2 Importing memory.json for project {project_id}")
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("V2 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):
"""Helper function to import a file using an importer instance.
Args:
importer: The importer instance to use
file: The file to import
destination_folder: Destination folder for imported content
Returns:
Import result from the importer
Raises:
HTTPException: If import fails
"""
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("V2 Import failed")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Import failed: {str(e)}",
)
@@ -0,0 +1,413 @@
"""V2 Knowledge Router - ID-based entity operations.
This router provides ID-based CRUD operations for entities, replacing the
path-based identifiers used in v1 with direct integer ID lookups.
Key improvements:
- Direct database lookups via integer primary keys
- Stable references that don't change with file moves
- Better performance through indexed queries
- Simplified caching strategies
"""
from fastapi import APIRouter, HTTPException, BackgroundTasks, Depends, Response
from loguru import logger
from basic_memory.deps import (
EntityServiceV2Dep,
SearchServiceV2Dep,
LinkResolverV2Dep,
ProjectConfigV2Dep,
AppConfigDep,
SyncServiceV2Dep,
EntityRepositoryV2Dep,
ProjectIdPathDep,
)
from basic_memory.schemas import DeleteEntitiesResponse
from basic_memory.schemas.base import Entity
from basic_memory.schemas.request import EditEntityRequest
from basic_memory.schemas.v2 import (
EntityResolveRequest,
EntityResolveResponse,
EntityResponseV2,
MoveEntityRequestV2,
)
router = APIRouter(prefix="/knowledge", tags=["knowledge-v2"])
async def resolve_relations_background(sync_service, entity_id: int, entity_permalink: str) -> None:
"""Background task to resolve relations for a specific entity.
This runs asynchronously after the API response is sent, preventing
long delays when creating entities with many relations.
"""
try:
# Only resolve relations for the newly created entity
await sync_service.resolve_relations(entity_id=entity_id)
logger.debug(
f"Background: Resolved relations for entity {entity_permalink} (id={entity_id})"
)
except Exception as e:
# Log but don't fail - this is a background task
logger.warning(
f"Background: Failed to resolve relations for entity {entity_permalink}: {e}"
)
## Resolution endpoint
@router.post("/resolve", response_model=EntityResolveResponse)
async def resolve_identifier(
project_id: ProjectIdPathDep,
data: EntityResolveRequest,
link_resolver: LinkResolverV2Dep,
) -> EntityResolveResponse:
"""Resolve a string identifier (permalink, title, or path) to an entity ID.
This endpoint provides a bridge between v1-style identifiers and v2 entity IDs.
Use this to convert existing references to the new ID-based format.
Args:
data: Request containing the identifier to resolve
Returns:
Entity ID and metadata about how it was resolved
Raises:
HTTPException: 404 if identifier cannot be resolved
Example:
POST /v2/{project}/knowledge/resolve
{"identifier": "specs/search"}
Returns:
{
"entity_id": 123,
"permalink": "specs/search",
"file_path": "specs/search.md",
"title": "Search Specification",
"resolution_method": "permalink"
}
"""
logger.info(f"API v2 request: resolve_identifier for '{data.identifier}'")
# Try to resolve the identifier
entity = await link_resolver.resolve_link(data.identifier)
if not entity:
raise HTTPException(status_code=404, detail=f"Entity not found: '{data.identifier}'")
# Determine resolution method
resolution_method = "search" # default
if data.identifier.isdigit():
resolution_method = "id"
elif entity.permalink == data.identifier:
resolution_method = "permalink"
elif entity.title == data.identifier:
resolution_method = "title"
elif entity.file_path == data.identifier:
resolution_method = "path"
result = EntityResolveResponse(
entity_id=entity.id,
permalink=entity.permalink,
file_path=entity.file_path,
title=entity.title,
resolution_method=resolution_method,
)
logger.info(
f"API v2 response: resolved '{data.identifier}' to entity_id={result.entity_id} via {resolution_method}"
)
return result
## Read endpoints
@router.get("/entities/{entity_id}", response_model=EntityResponseV2)
async def get_entity_by_id(
project_id: ProjectIdPathDep,
entity_id: int,
entity_repository: EntityRepositoryV2Dep,
) -> EntityResponseV2:
"""Get an entity by its numeric ID.
This is the primary entity retrieval method in v2, using direct database
lookups for maximum performance.
Args:
entity_id: Numeric entity ID
Returns:
Complete entity with observations and relations
Raises:
HTTPException: 404 if entity not found
"""
logger.info(f"API v2 request: get_entity_by_id entity_id={entity_id}")
entity = await entity_repository.get_by_id(entity_id)
if not entity:
raise HTTPException(status_code=404, detail=f"Entity {entity_id} not found")
result = EntityResponseV2.model_validate(entity)
logger.info(f"API v2 response: entity_id={entity_id}, title='{result.title}'")
return result
## Create endpoints
@router.post("/entities", response_model=EntityResponseV2)
async def create_entity(
project_id: ProjectIdPathDep,
data: Entity,
background_tasks: BackgroundTasks,
entity_service: EntityServiceV2Dep,
search_service: SearchServiceV2Dep,
) -> EntityResponseV2:
"""Create a new entity.
Args:
data: Entity data to create
Returns:
Created entity with generated ID
"""
logger.info(
"API v2 request", endpoint="create_entity", entity_type=data.entity_type, title=data.title
)
entity = await entity_service.create_entity(data)
# reindex
await search_service.index_entity(entity, background_tasks=background_tasks)
result = EntityResponseV2.model_validate(entity)
logger.info(
f"API v2 response: endpoint='create_entity' id={entity.id}, title={result.title}, permalink={result.permalink}, status_code=201"
)
return result
## Update endpoints
@router.put("/entities/{entity_id}", response_model=EntityResponseV2)
async def update_entity_by_id(
project_id: ProjectIdPathDep,
entity_id: int,
data: Entity,
response: Response,
background_tasks: BackgroundTasks,
entity_service: EntityServiceV2Dep,
search_service: SearchServiceV2Dep,
sync_service: SyncServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
) -> EntityResponseV2:
"""Update an entity by ID.
If the entity doesn't exist, it will be created (upsert behavior).
Args:
entity_id: Numeric entity ID
data: Updated entity data
Returns:
Updated entity
"""
logger.info(f"API v2 request: update_entity_by_id entity_id={entity_id}")
# Check if entity exists
existing = await entity_repository.get_by_id(entity_id)
created = existing is None
# Perform update or create
entity, _ = await entity_service.create_or_update_entity(data)
response.status_code = 201 if created else 200
# reindex
await search_service.index_entity(entity, background_tasks=background_tasks)
# Schedule relation resolution for new entities
if created:
background_tasks.add_task(
resolve_relations_background, sync_service, entity.id, entity.permalink or ""
)
result = EntityResponseV2.model_validate(entity)
logger.info(
f"API v2 response: entity_id={entity_id}, created={created}, status_code={response.status_code}"
)
return result
@router.patch("/entities/{entity_id}", response_model=EntityResponseV2)
async def edit_entity_by_id(
project_id: ProjectIdPathDep,
entity_id: int,
data: EditEntityRequest,
background_tasks: BackgroundTasks,
entity_service: EntityServiceV2Dep,
search_service: SearchServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
) -> EntityResponseV2:
"""Edit an existing entity by ID using operations like append, prepend, etc.
Args:
entity_id: Numeric entity ID
data: Edit operation details
Returns:
Updated entity
Raises:
HTTPException: 404 if entity not found, 400 if edit fails
"""
logger.info(
f"API v2 request: edit_entity_by_id entity_id={entity_id}, operation='{data.operation}'"
)
# Verify entity exists
entity = await entity_repository.get_by_id(entity_id)
if not entity:
raise HTTPException(status_code=404, detail=f"Entity {entity_id} not found")
try:
# Edit using the entity's permalink or path
identifier = entity.permalink or entity.file_path
updated_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
await search_service.index_entity(updated_entity, background_tasks=background_tasks)
result = EntityResponseV2.model_validate(updated_entity)
logger.info(
f"API v2 response: entity_id={entity_id}, operation='{data.operation}', status_code=200"
)
return result
except Exception as e:
logger.error(f"Error editing entity {entity_id}: {e}")
raise HTTPException(status_code=400, detail=str(e))
## Delete endpoints
@router.delete("/entities/{entity_id}", response_model=DeleteEntitiesResponse)
async def delete_entity_by_id(
project_id: ProjectIdPathDep,
entity_id: int,
background_tasks: BackgroundTasks,
entity_service: EntityServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
search_service=Depends(lambda: None), # Optional for now
) -> DeleteEntitiesResponse:
"""Delete an entity by ID.
Args:
entity_id: Numeric entity ID
Returns:
Deletion status
Note: Returns deleted=False if entity doesn't exist (idempotent)
"""
logger.info(f"API v2 request: delete_entity_by_id entity_id={entity_id}")
entity = await entity_repository.get_by_id(entity_id)
if entity is None:
logger.info(f"API v2 response: entity_id={entity_id} not found, deleted=False")
return DeleteEntitiesResponse(deleted=False)
# Delete the entity
deleted = await entity_service.delete_entity(entity_id)
# Remove from search index if search service available
if search_service:
background_tasks.add_task(search_service.handle_delete, entity)
logger.info(f"API v2 response: entity_id={entity_id}, deleted={deleted}")
return DeleteEntitiesResponse(deleted=deleted)
## Move endpoint
@router.put("/entities/{entity_id}/move", response_model=EntityResponseV2)
async def move_entity(
project_id: ProjectIdPathDep,
entity_id: int,
data: MoveEntityRequestV2,
background_tasks: BackgroundTasks,
entity_service: EntityServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
project_config: ProjectConfigV2Dep,
app_config: AppConfigDep,
search_service: SearchServiceV2Dep,
) -> EntityResponseV2:
"""Move an entity to a new file location.
V2 API uses entity ID in the URL path for stable references.
The entity ID will remain stable after the move.
Args:
project_id: Project ID from URL path
entity_id: Entity ID from URL path (primary identifier)
data: Move request with destination path only
Returns:
Updated entity with new file path
"""
logger.info(
f"API v2 request: move_entity entity_id={entity_id}, destination='{data.destination_path}'"
)
try:
# First, get the entity by ID to verify it exists
entity = await entity_repository.find_by_id(entity_id)
if not entity:
raise HTTPException(status_code=404, detail=f"Entity not found: {entity_id}")
# Move the entity using its current file path as identifier
moved_entity = await entity_service.move_entity(
identifier=entity.file_path, # Use file path for resolution
destination_path=data.destination_path,
project_config=project_config,
app_config=app_config,
)
# Reindex at new location
reindexed_entity = await entity_service.link_resolver.resolve_link(data.destination_path)
if reindexed_entity:
await search_service.index_entity(reindexed_entity, background_tasks=background_tasks)
result = EntityResponseV2.model_validate(moved_entity)
logger.info(
f"API v2 response: moved entity_id={moved_entity.id} to '{data.destination_path}'"
)
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error moving entity: {e}")
raise HTTPException(status_code=400, detail=str(e))
@@ -0,0 +1,130 @@
"""V2 routes for memory:// URI operations.
This router uses integer project IDs for stable, efficient routing.
V1 uses string-based project names which are less efficient and less stable.
"""
from typing import Annotated, Optional
from fastapi import APIRouter, Query
from loguru import logger
from basic_memory.deps import ContextServiceV2Dep, EntityRepositoryV2Dep, ProjectIdPathDep
from basic_memory.schemas.base import TimeFrame, parse_timeframe
from basic_memory.schemas.memory import (
GraphContext,
normalize_memory_url,
)
from basic_memory.schemas.search import SearchItemType
from basic_memory.api.routers.utils import to_graph_context
# Note: No prefix here - it's added during registration as /v2/{project_id}/memory
router = APIRouter(tags=["memory"])
@router.get("/memory/recent", response_model=GraphContext)
async def recent(
project_id: ProjectIdPathDep,
context_service: ContextServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
type: Annotated[list[SearchItemType] | None, Query()] = None,
depth: int = 1,
timeframe: TimeFrame = "7d",
page: int = 1,
page_size: int = 10,
max_related: int = 10,
) -> GraphContext:
"""Get recent activity context for a project.
Args:
project_id: Validated numeric project ID from URL path
context_service: Context service scoped to project
entity_repository: Entity repository scoped to project
type: Types of items to include (entities, relations, observations)
depth: How many levels of related entities to include
timeframe: Time window for recent activity (e.g., "7d", "1 week")
page: Page number for pagination
page_size: Number of items per page
max_related: Maximum related entities to include per item
Returns:
GraphContext with recent activity and related entities
"""
# return all types by default
types = (
[SearchItemType.ENTITY, SearchItemType.RELATION, SearchItemType.OBSERVATION]
if not type
else type
)
logger.debug(
f"V2 Getting recent context for project {project_id}: `{types}` depth: `{depth}` timeframe: `{timeframe}` page: `{page}` page_size: `{page_size}` max_related: `{max_related}`"
)
# Parse timeframe
since = parse_timeframe(timeframe)
limit = page_size
offset = (page - 1) * page_size
# Build context
context = await context_service.build_context(
types=types, depth=depth, since=since, limit=limit, offset=offset, max_related=max_related
)
recent_context = await to_graph_context(
context, entity_repository=entity_repository, page=page, page_size=page_size
)
logger.debug(f"V2 Recent context: {recent_context.model_dump_json()}")
return recent_context
# get_memory_context needs to be declared last so other paths can match
@router.get("/memory/{uri:path}", response_model=GraphContext)
async def get_memory_context(
project_id: ProjectIdPathDep,
context_service: ContextServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
uri: str,
depth: int = 1,
timeframe: Optional[TimeFrame] = None,
page: int = 1,
page_size: int = 10,
max_related: int = 10,
) -> GraphContext:
"""Get rich context from memory:// URI.
V2 supports both legacy path-based URIs and new ID-based URIs:
- Legacy: memory://path/to/note
- ID-based: memory://id/123 or memory://123
Args:
project_id: Validated numeric project ID from URL path
context_service: Context service scoped to project
entity_repository: Entity repository scoped to project
uri: Memory URI path (e.g., "id/123", "123", or "path/to/note")
depth: How many levels of related entities to include
timeframe: Optional time window for filtering related content
page: Page number for pagination
page_size: Number of items per page
max_related: Maximum related entities to include
Returns:
GraphContext with the entity and its related context
"""
logger.debug(
f"V2 Getting context for project {project_id}, URI: `{uri}` depth: `{depth}` timeframe: `{timeframe}` page: `{page}` page_size: `{page_size}` max_related: `{max_related}`"
)
memory_url = normalize_memory_url(uri)
# Parse timeframe
since = parse_timeframe(timeframe) if timeframe else None
limit = page_size
offset = (page - 1) * page_size
# Build context
context = await context_service.build_context(
memory_url, depth=depth, since=since, limit=limit, offset=offset, max_related=max_related
)
return await to_graph_context(
context, entity_repository=entity_repository, page=page, page_size=page_size
)
@@ -0,0 +1,342 @@
"""V2 Project Router - ID-based project management operations.
This router provides ID-based CRUD operations for projects, replacing the
name-based identifiers used in v1 with direct integer ID lookups.
Key improvements:
- Direct database lookups via integer primary keys
- Stable references that don't change with project renames
- Better performance through indexed queries
- Consistent with v2 entity operations
"""
import os
from typing import Optional
from fastapi import APIRouter, HTTPException, Body, Query
from loguru import logger
from basic_memory.deps import (
ProjectServiceDep,
ProjectRepositoryDep,
ProjectIdPathDep,
)
from basic_memory.schemas.project_info import (
ProjectItem,
ProjectStatusResponse,
)
from basic_memory.schemas.v2 import ProjectResolveRequest, ProjectResolveResponse
from basic_memory.utils import normalize_project_path, generate_permalink
router = APIRouter(prefix="/projects", tags=["project_management-v2"])
@router.post("/resolve", response_model=ProjectResolveResponse)
async def resolve_project_identifier(
data: ProjectResolveRequest,
project_repository: ProjectRepositoryDep,
) -> ProjectResolveResponse:
"""Resolve a project identifier (name or permalink) to a project ID.
This endpoint provides efficient lookup of projects by name without
needing to fetch the entire project list. Supports case-insensitive
matching on both name and permalink.
Args:
data: Request containing the identifier to resolve
Returns:
Project information including the numeric ID
Raises:
HTTPException: 404 if project not found
Example:
POST /v2/projects/resolve
{"identifier": "my-project"}
Returns:
{
"project_id": 1,
"name": "my-project",
"permalink": "my-project",
"path": "/path/to/project",
"is_active": true,
"is_default": false,
"resolution_method": "name"
}
"""
logger.info(f"API v2 request: resolve_project_identifier for '{data.identifier}'")
# Generate permalink for comparison
identifier_permalink = generate_permalink(data.identifier)
# Try to find project by ID first (if identifier is numeric)
resolution_method = "name"
project = None
if data.identifier.isdigit():
project_id = int(data.identifier)
project = await project_repository.get_by_id(project_id)
if project:
resolution_method = "id"
# If not found by ID, try by permalink first (exact match)
if not project:
project = await project_repository.get_by_permalink(identifier_permalink)
if project:
resolution_method = "permalink"
# If not found by permalink, try case-insensitive name search
# Uses efficient database query instead of fetching all projects
if not project:
project = await project_repository.get_by_name_case_insensitive(data.identifier)
if project:
resolution_method = "name"
if not project:
raise HTTPException(status_code=404, detail=f"Project not found: '{data.identifier}'")
return ProjectResolveResponse(
project_id=project.id,
name=project.name,
permalink=generate_permalink(project.name),
path=normalize_project_path(project.path),
is_active=project.is_active if hasattr(project, "is_active") else True,
is_default=project.is_default or False,
resolution_method=resolution_method,
)
@router.get("/{project_id}", response_model=ProjectItem)
async def get_project_by_id(
project_id: ProjectIdPathDep,
project_repository: ProjectRepositoryDep,
) -> ProjectItem:
"""Get project by its numeric ID.
This is the primary project retrieval method in v2, using direct database
lookups for maximum performance.
Args:
project_id: Numeric project ID
Returns:
Project information
Raises:
HTTPException: 404 if project not found
Example:
GET /v2/projects/3
"""
logger.info(f"API v2 request: get_project_by_id for project_id={project_id}")
project = await project_repository.get_by_id(project_id)
if not project:
raise HTTPException(status_code=404, detail=f"Project with ID {project_id} not found")
return ProjectItem(
id=project.id,
name=project.name,
path=normalize_project_path(project.path),
is_default=project.is_default or False,
)
@router.patch("/{project_id}", response_model=ProjectStatusResponse)
async def update_project_by_id(
project_id: ProjectIdPathDep,
project_service: ProjectServiceDep,
project_repository: ProjectRepositoryDep,
path: Optional[str] = Body(None, description="New absolute path for the project"),
is_active: Optional[bool] = Body(None, description="Status of the project (active/inactive)"),
) -> ProjectStatusResponse:
"""Update a project's information by ID.
Args:
project_id: Numeric project ID
path: Optional new absolute path for the project
is_active: Optional status update for the project
Returns:
Response confirming the project was updated
Raises:
HTTPException: 400 if validation fails, 404 if project not found
Example:
PATCH /v2/projects/3
{"path": "/new/path"}
"""
logger.info(f"API v2 request: update_project_by_id for project_id={project_id}")
try:
# Validate that path is absolute if provided
if path and not os.path.isabs(path):
raise HTTPException(status_code=400, detail="Path must be absolute")
# Get original project info for the response
old_project = await project_repository.get_by_id(project_id)
if not old_project:
raise HTTPException(status_code=404, detail=f"Project with ID {project_id} not found")
old_project_info = ProjectItem(
id=old_project.id,
name=old_project.name,
path=old_project.path,
is_default=old_project.is_default or False,
)
# Update using project name (service layer still uses names internally)
if path:
await project_service.move_project(old_project.name, path)
elif is_active is not None:
await project_service.update_project(old_project.name, is_active=is_active)
# Get updated project info
updated_project = await project_repository.get_by_id(project_id)
if not updated_project:
raise HTTPException(
status_code=404, detail=f"Project with ID {project_id} not found after update"
)
return ProjectStatusResponse(
message=f"Project '{updated_project.name}' updated successfully",
status="success",
default=(old_project.name == project_service.default_project),
old_project=old_project_info,
new_project=ProjectItem(
id=updated_project.id,
name=updated_project.name,
path=updated_project.path,
is_default=updated_project.is_default or False,
),
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.delete("/{project_id}", response_model=ProjectStatusResponse)
async def delete_project_by_id(
project_id: ProjectIdPathDep,
project_service: ProjectServiceDep,
project_repository: ProjectRepositoryDep,
delete_notes: bool = Query(
False, description="If True, delete project directory from filesystem"
),
) -> ProjectStatusResponse:
"""Delete a project by ID.
Args:
project_id: Numeric project ID
delete_notes: If True, delete the project directory from the filesystem
Returns:
Response confirming the project was deleted
Raises:
HTTPException: 400 if trying to delete default project, 404 if not found
Example:
DELETE /v2/projects/3?delete_notes=false
"""
logger.info(
f"API v2 request: delete_project_by_id for project_id={project_id}, delete_notes={delete_notes}"
)
try:
old_project = await project_repository.get_by_id(project_id)
if not old_project:
raise HTTPException(status_code=404, detail=f"Project with ID {project_id} not found")
# Check if trying to delete the default project
if old_project.name == project_service.default_project:
available_projects = await project_service.list_projects()
other_projects = [p.name for p in available_projects if p.id != project_id]
detail = f"Cannot delete default project '{old_project.name}'. "
if other_projects:
detail += (
f"Set another project as default first. Available: {', '.join(other_projects)}"
)
else:
detail += "This is the only project in your configuration."
raise HTTPException(status_code=400, detail=detail)
# Delete using project name (service layer still uses names internally)
await project_service.remove_project(old_project.name, delete_notes=delete_notes)
return ProjectStatusResponse(
message=f"Project '{old_project.name}' removed successfully",
status="success",
default=False,
old_project=ProjectItem(
id=old_project.id,
name=old_project.name,
path=old_project.path,
is_default=old_project.is_default or False,
),
new_project=None,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.put("/{project_id}/default", response_model=ProjectStatusResponse)
async def set_default_project_by_id(
project_id: ProjectIdPathDep,
project_service: ProjectServiceDep,
project_repository: ProjectRepositoryDep,
) -> ProjectStatusResponse:
"""Set a project as the default project by ID.
Args:
project_id: Numeric project ID to set as default
Returns:
Response confirming the project was set as default
Raises:
HTTPException: 404 if project not found
Example:
PUT /v2/projects/3/default
"""
logger.info(f"API v2 request: set_default_project_by_id for project_id={project_id}")
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:
raise HTTPException(
status_code=404, detail=f"Default Project: '{default_name}' does not exist"
)
# Get the new default project
new_default_project = await project_repository.get_by_id(project_id)
if not new_default_project:
raise HTTPException(status_code=404, detail=f"Project with ID {project_id} not found")
# Set as default using project name (service layer still uses names internally)
await project_service.set_default_project(new_default_project.name)
return ProjectStatusResponse(
message=f"Project '{new_default_project.name}' set as default successfully",
status="success",
default=True,
old_project=ProjectItem(
id=default_project.id,
name=default_name,
path=default_project.path,
is_default=False,
),
new_project=ProjectItem(
id=new_default_project.id,
name=new_default_project.name,
path=new_default_project.path,
is_default=True,
),
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@@ -0,0 +1,270 @@
"""V2 Prompt Router - ID-based prompt generation operations.
This router uses v2 dependencies for consistent project ID handling.
Prompt endpoints are action-based (not resource-based), so they don't
have entity IDs in URLs - they generate formatted prompts from queries.
"""
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 (
ContextServiceV2Dep,
EntityRepositoryV2Dep,
SearchServiceV2Dep,
EntityServiceV2Dep,
ProjectIdPathDep,
)
from basic_memory.schemas.prompt import (
ContinueConversationRequest,
SearchPromptRequest,
PromptResponse,
PromptMetadata,
)
from basic_memory.schemas.search import SearchItemType, SearchQuery
router = APIRouter(prefix="/prompt", tags=["prompt-v2"])
@router.post("/continue-conversation", response_model=PromptResponse)
async def continue_conversation(
project_id: ProjectIdPathDep,
search_service: SearchServiceV2Dep,
entity_service: EntityServiceV2Dep,
context_service: ContextServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
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:
project_id: Validated numeric project ID from URL path
request: The request parameters
Returns:
Formatted continuation prompt with context
"""
logger.info(
f"V2 Generating continue conversation prompt for project {project_id}, "
f"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(
project_id: ProjectIdPathDep,
search_service: SearchServiceV2Dep,
entity_service: EntityServiceV2Dep,
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:
project_id: Validated numeric project ID from URL path
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"V2 Generating search prompt for project {project_id}, "
f"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)}",
)
@@ -0,0 +1,286 @@
"""V2 Resource Router - ID-based resource content operations.
This router uses entity IDs for all operations, with file paths in request bodies
when needed. This is consistent with v2's ID-first design.
Key differences from v1:
- Uses integer entity IDs in URL paths instead of file paths
- File paths are in request bodies for create/update operations
- More RESTful: POST for create, PUT for update, GET for read
"""
from pathlib import Path
from fastapi import APIRouter, HTTPException, Response
from loguru import logger
from basic_memory.deps import (
ProjectConfigV2Dep,
EntityServiceV2Dep,
FileServiceV2Dep,
EntityRepositoryV2Dep,
SearchServiceV2Dep,
ProjectIdPathDep,
)
from basic_memory.models.knowledge import Entity as EntityModel
from basic_memory.schemas.v2.resource import (
CreateResourceRequest,
UpdateResourceRequest,
ResourceResponse,
)
from basic_memory.utils import validate_project_path
router = APIRouter(prefix="/resource", tags=["resources-v2"])
@router.get("/{entity_id}")
async def get_resource_content(
project_id: ProjectIdPathDep,
entity_id: int,
config: ProjectConfigV2Dep,
entity_service: EntityServiceV2Dep,
file_service: FileServiceV2Dep,
) -> Response:
"""Get raw resource content by entity ID.
Args:
project_id: Validated numeric project ID from URL path
entity_id: Numeric entity ID
config: Project configuration
entity_service: Entity service for fetching entity data
file_service: File service for reading file content
Returns:
Response with entity content
Raises:
HTTPException: 404 if entity or file not found
"""
logger.debug(f"V2 Getting content for project {project_id}, entity_id: {entity_id}")
# Get entity by ID
entities = await entity_service.get_entities_by_id([entity_id])
if not entities:
raise HTTPException(status_code=404, detail=f"Entity {entity_id} not found")
entity = entities[0]
# Validate entity file path to prevent path traversal
project_path = Path(config.home)
if not validate_project_path(entity.file_path, project_path):
logger.error(f"Invalid file path in entity {entity.id}: {entity.file_path}")
raise HTTPException(
status_code=500,
detail="Entity contains invalid file path",
)
# Check file exists via file_service (for cloud compatibility)
if not await file_service.exists(entity.file_path):
raise HTTPException(
status_code=404,
detail=f"File not found: {entity.file_path}",
)
# Read content via file_service as bytes (works with both local and S3)
content = await file_service.read_file_bytes(entity.file_path)
content_type = file_service.content_type(entity.file_path)
return Response(content=content, media_type=content_type)
@router.post("", response_model=ResourceResponse)
async def create_resource(
project_id: ProjectIdPathDep,
data: CreateResourceRequest,
config: ProjectConfigV2Dep,
file_service: FileServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
search_service: SearchServiceV2Dep,
) -> ResourceResponse:
"""Create a new resource file.
Args:
project_id: Validated numeric project ID from URL path
data: Create resource request with file_path and content
config: Project configuration
file_service: File service for writing files
entity_repository: Entity repository for creating entities
search_service: Search service for indexing
Returns:
ResourceResponse with file information including entity_id
Raises:
HTTPException: 400 for invalid file paths, 409 if file already exists
"""
try:
# Validate path to prevent path traversal attacks
project_path = Path(config.home)
if not validate_project_path(data.file_path, project_path):
logger.warning(
f"Invalid file path attempted: {data.file_path} in project {config.name}"
)
raise HTTPException(
status_code=400,
detail=f"Invalid file path: {data.file_path}. "
"Path must be relative and stay within project boundaries.",
)
# Check if entity already exists
existing_entity = await entity_repository.get_by_file_path(data.file_path)
if existing_entity:
raise HTTPException(
status_code=409,
detail=f"Resource already exists at {data.file_path} with entity_id {existing_entity.id}. "
f"Use PUT /resource/{existing_entity.id} to update it.",
)
# Cloud compatibility: avoid assuming a local filesystem path.
# Delegate directory creation + writes to FileService (local or S3).
await file_service.ensure_directory(Path(data.file_path).parent)
checksum = await file_service.write_file(data.file_path, data.content)
# Get file info
file_metadata = await file_service.get_file_metadata(data.file_path)
# Determine file details
file_name = Path(data.file_path).name
content_type = file_service.content_type(data.file_path)
entity_type = "canvas" if data.file_path.endswith(".canvas") else "file"
# Create a new entity model
entity = EntityModel(
title=file_name,
entity_type=entity_type,
content_type=content_type,
file_path=data.file_path,
checksum=checksum,
created_at=file_metadata.created_at,
updated_at=file_metadata.modified_at,
)
entity = await entity_repository.add(entity)
# Index the file for search
await search_service.index_entity(entity) # pyright: ignore
# Return success response
return ResourceResponse(
entity_id=entity.id,
file_path=data.file_path,
checksum=checksum,
size=file_metadata.size,
created_at=file_metadata.created_at.timestamp(),
modified_at=file_metadata.modified_at.timestamp(),
)
except HTTPException:
# Re-raise HTTP exceptions without wrapping
raise
except Exception as e: # pragma: no cover
logger.error(f"Error creating resource {data.file_path}: {e}")
raise HTTPException(status_code=500, detail=f"Failed to create resource: {str(e)}")
@router.put("/{entity_id}", response_model=ResourceResponse)
async def update_resource(
project_id: ProjectIdPathDep,
entity_id: int,
data: UpdateResourceRequest,
config: ProjectConfigV2Dep,
file_service: FileServiceV2Dep,
entity_repository: EntityRepositoryV2Dep,
search_service: SearchServiceV2Dep,
) -> ResourceResponse:
"""Update an existing resource by entity ID.
Can update content and optionally move the file to a new path.
Args:
project_id: Validated numeric project ID from URL path
entity_id: Entity ID of the resource to update
data: Update resource request with content and optional new file_path
config: Project configuration
file_service: File service for writing files
entity_repository: Entity repository for updating entities
search_service: Search service for indexing
Returns:
ResourceResponse with updated file information
Raises:
HTTPException: 404 if entity not found, 400 for invalid paths
"""
try:
# Get existing entity
entity = await entity_repository.get_by_id(entity_id)
if not entity:
raise HTTPException(status_code=404, detail=f"Entity {entity_id} not found")
# Determine target file path
target_file_path = data.file_path if data.file_path else entity.file_path
# Validate path to prevent path traversal attacks
project_path = Path(config.home)
if not validate_project_path(target_file_path, project_path):
logger.warning(
f"Invalid file path attempted: {target_file_path} in project {config.name}"
)
raise HTTPException(
status_code=400,
detail=f"Invalid file path: {target_file_path}. "
"Path must be relative and stay within project boundaries.",
)
# If moving file, handle the move
if data.file_path and data.file_path != entity.file_path:
# Ensure new parent directory exists (no-op for S3)
await file_service.ensure_directory(Path(target_file_path).parent)
# If old file exists, remove it via file_service (for cloud compatibility)
if await file_service.exists(entity.file_path):
await file_service.delete_file(entity.file_path)
else:
# Ensure directory exists for in-place update
await file_service.ensure_directory(Path(target_file_path).parent)
# Write content to target file
checksum = await file_service.write_file(target_file_path, data.content)
# Get file info
file_metadata = await file_service.get_file_metadata(target_file_path)
# Determine file details
file_name = Path(target_file_path).name
content_type = file_service.content_type(target_file_path)
entity_type = "canvas" if target_file_path.endswith(".canvas") else "file"
# Update entity
updated_entity = await entity_repository.update(
entity_id,
{
"title": file_name,
"entity_type": entity_type,
"content_type": content_type,
"file_path": target_file_path,
"checksum": checksum,
"updated_at": file_metadata.modified_at,
},
)
# Index the updated file for search
await search_service.index_entity(updated_entity) # pyright: ignore
# Return success response
return ResourceResponse(
entity_id=entity_id,
file_path=target_file_path,
checksum=checksum,
size=file_metadata.size,
created_at=file_metadata.created_at.timestamp(),
modified_at=file_metadata.modified_at.timestamp(),
)
except HTTPException:
# Re-raise HTTP exceptions without wrapping
raise
except Exception as e: # pragma: no cover
logger.error(f"Error updating resource {entity_id}: {e}")
raise HTTPException(status_code=500, detail=f"Failed to update resource: {str(e)}")
@@ -0,0 +1,73 @@
"""V2 router for search operations.
This router uses integer project IDs for stable, efficient routing.
V1 uses string-based project names which are less efficient and less stable.
"""
from fastapi import APIRouter, BackgroundTasks
from basic_memory.api.routers.utils import to_search_results
from basic_memory.schemas.search import SearchQuery, SearchResponse
from basic_memory.deps import SearchServiceV2Dep, EntityServiceV2Dep, ProjectIdPathDep
# Note: No prefix here - it's added during registration as /v2/{project_id}/search
router = APIRouter(tags=["search"])
@router.post("/search/", response_model=SearchResponse)
async def search(
project_id: ProjectIdPathDep,
query: SearchQuery,
search_service: SearchServiceV2Dep,
entity_service: EntityServiceV2Dep,
page: int = 1,
page_size: int = 10,
):
"""Search across all knowledge and documents in a project.
V2 uses integer project IDs for improved performance and stability.
Args:
project_id: Validated numeric project ID from URL path
query: Search query parameters (text, filters, etc.)
search_service: Search service scoped to project
entity_service: Entity service scoped to project
page: Page number for pagination
page_size: Number of results per page
Returns:
SearchResponse with paginated search results
"""
limit = page_size
offset = (page - 1) * page_size
results = await search_service.search(query, limit=limit, offset=offset)
search_results = await to_search_results(entity_service, results)
return SearchResponse(
results=search_results,
current_page=page,
page_size=page_size,
)
@router.post("/search/reindex")
async def reindex(
project_id: ProjectIdPathDep,
background_tasks: BackgroundTasks,
search_service: SearchServiceV2Dep,
):
"""Recreate and populate the search index for a project.
This is a background operation that rebuilds the search index
from scratch. Useful after bulk updates or if the index becomes
corrupted.
Args:
project_id: Validated numeric project ID from URL path
background_tasks: FastAPI background tasks handler
search_service: Search service scoped to project
Returns:
Status message indicating reindex has been initiated
"""
await search_service.reindex_all(background_tasks=background_tasks)
return {"status": "ok", "message": "Reindex initiated"}
+43 -32
View File
@@ -1,9 +1,21 @@
from typing import Optional
# Suppress Logfire "not configured" warning - we only use Logfire in cloud/server contexts
import os
import typer
os.environ.setdefault("LOGFIRE_IGNORE_NO_CONFIG", "1")
from basic_memory.config import get_project_config, ConfigManager
from basic_memory.mcp.project_session import session
# Remove loguru's default handler IMMEDIATELY, before any other imports.
# This prevents DEBUG logs from appearing on stdout during module-level
# initialization (e.g., template_loader.TemplateLoader() logs at DEBUG level).
from loguru import logger
logger.remove()
from typing import Optional # noqa: E402
import typer # noqa: E402
from basic_memory.config import ConfigManager, init_cli_logging # noqa: E402
from basic_memory.telemetry import show_notice_if_needed, track_app_started # noqa: E402
def version_callback(value: bool) -> None:
@@ -11,10 +23,7 @@ def version_callback(value: bool) -> None:
if value: # pragma: no cover
import basic_memory
config = get_project_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()
@@ -24,13 +33,6 @@ 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 1",
envvar="BASIC_MEMORY_PROJECT",
),
version: Optional[bool] = typer.Option(
None,
"--version",
@@ -42,32 +44,41 @@ def app_callback(
) -> None:
"""Basic Memory - Local-first personal knowledge management."""
# Run initialization for every command unless --version was specified
if not version and ctx.invoked_subcommand is not None:
# Initialize logging for CLI (file only, no stdout)
init_cli_logging()
# Show telemetry notice and track CLI startup
# Skip for 'mcp' command - it handles its own telemetry in lifespan
# Skip for 'telemetry' command - avoid issues when user is managing telemetry
if ctx.invoked_subcommand not in {"mcp", "telemetry"}:
show_notice_if_needed()
track_app_started("cli")
# Run initialization for commands that don't use the API
# Skip for 'mcp' command - it has its own lifespan that handles initialization
# Skip for API-using commands (status, sync, etc.) - they handle initialization via deps.py
api_commands = {"mcp", "status", "sync", "project", "tool"}
if (
not version
and ctx.invoked_subcommand is not None
and ctx.invoked_subcommand not in api_commands
):
from basic_memory.services.initialization import ensure_initialization
app_config = ConfigManager().config
ensure_initialization(app_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)
# 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)
## import
# Register sub-command groups
import_app = typer.Typer(help="Import data from various sources")
app.add_typer(import_app, name="import")
claude_app = typer.Typer()
claude_app = typer.Typer(help="Import Conversations from Claude JSON export.")
import_app.add_typer(claude_app, name="claude")
## cloud
cloud_app = typer.Typer(help="Access Basic Memory Cloud")
app.add_typer(cloud_app, name="cloud")
+277
View File
@@ -0,0 +1,277 @@
"""WorkOS OAuth Device Authorization for CLI."""
import base64
import hashlib
import json
import os
import secrets
import time
import webbrowser
import httpx
from rich.console import Console
from basic_memory.config import ConfigManager
console = Console()
class CLIAuth:
"""Handles WorkOS OAuth Device Authorization for CLI tools."""
def __init__(self, client_id: str, authkit_domain: str):
self.client_id = client_id
self.authkit_domain = authkit_domain
app_config = ConfigManager().config
# Store tokens in data dir
self.token_file = app_config.data_dir_path / "basic-memory-cloud.json"
# PKCE parameters
self.code_verifier = None
self.code_challenge = None
def generate_pkce_pair(self) -> tuple[str, str]:
"""Generate PKCE code verifier and challenge."""
# Generate code verifier (43-128 characters)
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode("utf-8")
code_verifier = code_verifier.rstrip("=")
# Generate code challenge (SHA256 hash of verifier)
challenge_bytes = hashlib.sha256(code_verifier.encode("utf-8")).digest()
code_challenge = base64.urlsafe_b64encode(challenge_bytes).decode("utf-8")
code_challenge = code_challenge.rstrip("=")
return code_verifier, code_challenge
async def request_device_authorization(self) -> dict | None:
"""Request device authorization from WorkOS with PKCE."""
device_auth_url = f"{self.authkit_domain}/oauth2/device_authorization"
# Generate PKCE pair
self.code_verifier, self.code_challenge = self.generate_pkce_pair()
data = {
"client_id": self.client_id,
"scope": "openid profile email offline_access",
"code_challenge": self.code_challenge,
"code_challenge_method": "S256",
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(device_auth_url, data=data)
if response.status_code == 200:
return response.json()
else:
console.print(
f"[red]Device authorization failed: {response.status_code} - {response.text}[/red]"
)
return None
except Exception as e:
console.print(f"[red]Device authorization error: {e}[/red]")
return None
def display_user_instructions(self, device_response: dict) -> None:
"""Display user instructions for device authorization."""
user_code = device_response["user_code"]
verification_uri = device_response["verification_uri"]
verification_uri_complete = device_response.get("verification_uri_complete")
console.print("\n[bold blue]Authentication Required[/bold blue]")
console.print("\nTo authenticate, please visit:")
console.print(f"[bold cyan]{verification_uri}[/bold cyan]")
console.print(f"\nAnd enter this code: [bold yellow]{user_code}[/bold yellow]")
if verification_uri_complete:
console.print("\nOr for one-click access, visit:")
console.print(f"[bold green]{verification_uri_complete}[/bold green]")
# Try to open browser automatically
try:
console.print("\n[dim]Opening browser automatically...[/dim]")
webbrowser.open(verification_uri_complete)
except Exception:
pass # Silently fail if browser can't be opened
console.print("\n[dim]Waiting for you to complete authentication in your browser...[/dim]")
async def poll_for_token(self, device_code: str, interval: int = 5) -> dict | None:
"""Poll the token endpoint until user completes authentication."""
token_url = f"{self.authkit_domain}/oauth2/token"
data = {
"client_id": self.client_id,
"device_code": device_code,
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"code_verifier": self.code_verifier,
}
max_attempts = 60 # 5 minutes with 5-second intervals
current_interval = interval
for _attempt in range(max_attempts):
try:
async with httpx.AsyncClient() as client:
response = await client.post(token_url, data=data)
if response.status_code == 200:
return response.json()
# Parse error response
try:
error_data = response.json()
error = error_data.get("error")
except Exception:
error = "unknown_error"
if error == "authorization_pending":
# User hasn't completed auth yet, keep polling
pass
elif error == "slow_down":
# Increase polling interval
current_interval += 5
console.print("[yellow]Slowing down polling rate...[/yellow]")
elif error == "access_denied":
console.print("[red]Authentication was denied by user[/red]")
return None
elif error == "expired_token":
console.print("[red]Device code has expired. Please try again.[/red]")
return None
else:
console.print(f"[red]Token polling error: {error}[/red]")
return None
except Exception as e:
console.print(f"[red]Token polling request error: {e}[/red]")
# Wait before next poll
await self._async_sleep(current_interval)
console.print("[red]Authentication timeout. Please try again.[/red]")
return None
async def _async_sleep(self, seconds: int) -> None:
"""Async sleep utility."""
import asyncio
await asyncio.sleep(seconds)
def save_tokens(self, tokens: dict) -> None:
"""Save tokens to project root as .bm-auth.json."""
token_data = {
"access_token": tokens["access_token"],
"refresh_token": tokens.get("refresh_token"),
"expires_at": int(time.time()) + tokens.get("expires_in", 3600),
"token_type": tokens.get("token_type", "Bearer"),
}
with open(self.token_file, "w") as f:
json.dump(token_data, f, indent=2)
# Secure the token file
os.chmod(self.token_file, 0o600)
console.print(f"[green]Tokens saved to {self.token_file}[/green]")
def load_tokens(self) -> dict | None:
"""Load tokens from .bm-auth.json file."""
if not self.token_file.exists():
return None
try:
with open(self.token_file) as f:
return json.load(f)
except (OSError, json.JSONDecodeError):
return None
def is_token_valid(self, tokens: dict) -> bool:
"""Check if stored token is still valid."""
expires_at = tokens.get("expires_at", 0)
# Add 60 second buffer for clock skew
return time.time() < (expires_at - 60)
async def refresh_token(self, refresh_token: str) -> dict | None:
"""Refresh access token using refresh token."""
token_url = f"{self.authkit_domain}/oauth2/token"
data = {
"client_id": self.client_id,
"grant_type": "refresh_token",
"refresh_token": refresh_token,
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(token_url, data=data)
if response.status_code == 200:
return response.json()
else:
console.print(
f"[red]Token refresh failed: {response.status_code} - {response.text}[/red]"
)
return None
except Exception as e:
console.print(f"[red]Token refresh error: {e}[/red]")
return None
async def get_valid_token(self) -> str | None:
"""Get valid access token, refresh if needed."""
tokens = self.load_tokens()
if not tokens:
return None
if self.is_token_valid(tokens):
return tokens["access_token"]
# Token expired - try to refresh if we have a refresh token
refresh_token = tokens.get("refresh_token")
if refresh_token:
console.print("[yellow]Access token expired, refreshing...[/yellow]")
new_tokens = await self.refresh_token(refresh_token)
if new_tokens:
# Save new tokens (may include rotated refresh token)
self.save_tokens(new_tokens)
console.print("[green]Token refreshed successfully[/green]")
return new_tokens["access_token"]
else:
console.print("[yellow]Token refresh failed. Please run 'login' again.[/yellow]")
return None
else:
console.print("[yellow]No refresh token available. Please run 'login' again.[/yellow]")
return None
async def login(self) -> bool:
"""Perform OAuth Device Authorization login flow."""
console.print("[blue]Initiating authentication...[/blue]")
# Step 1: Request device authorization
device_response = await self.request_device_authorization()
if not device_response:
return False
# Step 2: Display user instructions
self.display_user_instructions(device_response)
# Step 3: Poll for token
device_code = device_response["device_code"]
interval = device_response.get("interval", 5)
tokens = await self.poll_for_token(device_code, interval)
if not tokens:
return False
# Step 4: Save tokens
self.save_tokens(tokens)
console.print("\n[green]Successfully authenticated with Basic Memory Cloud![/green]")
return True
def logout(self) -> None:
"""Remove stored authentication tokens."""
if self.token_file.exists():
self.token_file.unlink()
console.print("[green]Logged out successfully[/green]")
else:
console.print("[yellow]No stored authentication found[/yellow]")
+4 -3
View File
@@ -1,11 +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
from . import status, db, import_memory_json, mcp, import_claude_conversations
from . import import_claude_projects, import_chatgpt, tool, project, format, telemetry
__all__ = [
"status",
"sync",
"db",
"import_memory_json",
"mcp",
@@ -14,4 +13,6 @@ __all__ = [
"import_chatgpt",
"tool",
"project",
"format",
"telemetry",
]
@@ -0,0 +1,6 @@
"""Cloud commands package."""
# Import all commands to register them with typer
from basic_memory.cli.commands.cloud.core_commands import * # noqa: F401,F403
from basic_memory.cli.commands.cloud.api_client import get_authenticated_headers, get_cloud_config # noqa: F401
from basic_memory.cli.commands.cloud.upload_command import * # noqa: F401,F403
@@ -0,0 +1,112 @@
"""Cloud API client utilities."""
from typing import Optional
import httpx
import typer
from rich.console import Console
from basic_memory.cli.auth import CLIAuth
from basic_memory.config import ConfigManager
console = Console()
class CloudAPIError(Exception):
"""Exception raised for cloud API errors."""
def __init__(
self, message: str, status_code: Optional[int] = None, detail: Optional[dict] = None
):
super().__init__(message)
self.status_code = status_code
self.detail = detail or {}
class SubscriptionRequiredError(CloudAPIError):
"""Exception raised when user needs an active subscription."""
def __init__(self, message: str, subscribe_url: str):
super().__init__(message, status_code=403, detail={"error": "subscription_required"})
self.subscribe_url = subscribe_url
def get_cloud_config() -> tuple[str, str, str]:
"""Get cloud OAuth configuration from config."""
config_manager = ConfigManager()
config = config_manager.config
return config.cloud_client_id, config.cloud_domain, config.cloud_host
async def get_authenticated_headers() -> dict[str, str]:
"""
Get authentication headers with JWT token.
handles jwt refresh if needed.
"""
client_id, domain, _ = get_cloud_config()
auth = CLIAuth(client_id=client_id, authkit_domain=domain)
token = await auth.get_valid_token()
if not token:
console.print("[red]Not authenticated. Please run 'basic-memory cloud login' first.[/red]")
raise typer.Exit(1)
return {"Authorization": f"Bearer {token}"}
async def make_api_request(
method: str,
url: str,
headers: Optional[dict] = None,
json_data: Optional[dict] = None,
timeout: float = 30.0,
) -> httpx.Response:
"""Make an API request to the cloud service."""
headers = headers or {}
auth_headers = await get_authenticated_headers()
headers.update(auth_headers)
# Add debug headers to help with compression issues
headers.setdefault("Accept-Encoding", "identity") # Disable compression for debugging
async with httpx.AsyncClient(timeout=timeout) as client:
try:
response = await client.request(method=method, url=url, headers=headers, json=json_data)
response.raise_for_status()
return response
except httpx.HTTPError as e:
# Check if this is a response error with response details
if hasattr(e, "response") and e.response is not None: # pyright: ignore [reportAttributeAccessIssue]
response = e.response # type: ignore
# Try to parse error detail from response
error_detail = None
try:
error_detail = response.json()
except Exception:
# If JSON parsing fails, we'll handle it as a generic error
pass
# Check for subscription_required error (403)
if response.status_code == 403 and isinstance(error_detail, dict):
# Handle both FastAPI HTTPException format (nested under "detail")
# and direct format
detail_obj = error_detail.get("detail", error_detail)
if (
isinstance(detail_obj, dict)
and detail_obj.get("error") == "subscription_required"
):
message = detail_obj.get("message", "Active subscription required")
subscribe_url = detail_obj.get(
"subscribe_url", "https://basicmemory.com/subscribe"
)
raise SubscriptionRequiredError(
message=message, subscribe_url=subscribe_url
) from e
# Raise generic CloudAPIError with status code and detail
raise CloudAPIError(
f"API request failed: {e}",
status_code=response.status_code,
detail=error_detail if isinstance(error_detail, dict) else {},
) from e
raise CloudAPIError(f"API request failed: {e}") from e
@@ -0,0 +1,110 @@
"""Cloud bisync utility functions for Basic Memory CLI."""
from pathlib import Path
from basic_memory.cli.commands.cloud.api_client import make_api_request
from basic_memory.config import ConfigManager
from basic_memory.ignore_utils import create_default_bmignore, get_bmignore_path
from basic_memory.schemas.cloud import MountCredentials, TenantMountInfo
class BisyncError(Exception):
"""Exception raised for bisync-related errors."""
pass
async def get_mount_info() -> TenantMountInfo:
"""Get current tenant information from cloud API."""
try:
config_manager = ConfigManager()
config = config_manager.config
host_url = config.cloud_host.rstrip("/")
response = await make_api_request(method="GET", url=f"{host_url}/tenant/mount/info")
return TenantMountInfo.model_validate(response.json())
except Exception as e:
raise BisyncError(f"Failed to get tenant info: {e}") from e
async def generate_mount_credentials(tenant_id: str) -> MountCredentials:
"""Generate scoped credentials for syncing."""
try:
config_manager = ConfigManager()
config = config_manager.config
host_url = config.cloud_host.rstrip("/")
response = await make_api_request(method="POST", url=f"{host_url}/tenant/mount/credentials")
return MountCredentials.model_validate(response.json())
except Exception as e:
raise BisyncError(f"Failed to generate credentials: {e}") from e
def convert_bmignore_to_rclone_filters() -> Path:
"""Convert .bmignore patterns to rclone filter format.
Reads ~/.basic-memory/.bmignore (gitignore-style) and converts to
~/.basic-memory/.bmignore.rclone (rclone filter format).
Only regenerates if .bmignore has been modified since last conversion.
Returns:
Path to converted rclone filter file
"""
# Ensure .bmignore exists
create_default_bmignore()
bmignore_path = get_bmignore_path()
# Create rclone filter path: ~/.basic-memory/.bmignore -> ~/.basic-memory/.bmignore.rclone
rclone_filter_path = bmignore_path.parent / f"{bmignore_path.name}.rclone"
# Skip regeneration if rclone file is newer than bmignore
if rclone_filter_path.exists():
bmignore_mtime = bmignore_path.stat().st_mtime
rclone_mtime = rclone_filter_path.stat().st_mtime
if rclone_mtime >= bmignore_mtime:
return rclone_filter_path
# Read .bmignore patterns
patterns = []
try:
with bmignore_path.open("r", encoding="utf-8") as f:
for line in f:
line = line.strip()
# Keep comments and empty lines
if not line or line.startswith("#"):
patterns.append(line)
continue
# Convert gitignore pattern to rclone filter syntax
# gitignore: node_modules → rclone: - node_modules/**
# gitignore: *.pyc → rclone: - *.pyc
if "*" in line:
# Pattern already has wildcard, just add exclude prefix
patterns.append(f"- {line}")
else:
# Directory pattern - add /** for recursive exclude
patterns.append(f"- {line}/**")
except Exception:
# If we can't read the file, create a minimal filter
patterns = ["# Error reading .bmignore, using minimal filters", "- .git/**"]
# Write rclone filter file
rclone_filter_path.write_text("\n".join(patterns) + "\n")
return rclone_filter_path
def get_bisync_filter_path() -> Path:
"""Get path to bisync filter file.
Uses ~/.basic-memory/.bmignore (converted to rclone format).
The file is automatically created with default patterns on first use.
Returns:
Path to rclone filter file
"""
return convert_bmignore_to_rclone_filters()
@@ -0,0 +1,101 @@
"""Shared utilities for cloud operations."""
from basic_memory.cli.commands.cloud.api_client import make_api_request
from basic_memory.config import ConfigManager
from basic_memory.schemas.cloud import (
CloudProjectList,
CloudProjectCreateRequest,
CloudProjectCreateResponse,
)
from basic_memory.utils import generate_permalink
class CloudUtilsError(Exception):
"""Exception raised for cloud utility errors."""
pass
async def fetch_cloud_projects() -> CloudProjectList:
"""Fetch list of projects from cloud API.
Returns:
CloudProjectList with projects from cloud
"""
try:
config_manager = ConfigManager()
config = config_manager.config
host_url = config.cloud_host.rstrip("/")
response = await make_api_request(method="GET", url=f"{host_url}/proxy/projects/projects")
return CloudProjectList.model_validate(response.json())
except Exception as e:
raise CloudUtilsError(f"Failed to fetch cloud projects: {e}") from e
async def create_cloud_project(project_name: str) -> CloudProjectCreateResponse:
"""Create a new project on cloud.
Args:
project_name: Name of project to create
Returns:
CloudProjectCreateResponse with project details from API
"""
try:
config_manager = ConfigManager()
config = config_manager.config
host_url = config.cloud_host.rstrip("/")
# Use generate_permalink to ensure consistent naming
project_path = generate_permalink(project_name)
project_data = CloudProjectCreateRequest(
name=project_name,
path=project_path,
set_default=False,
)
response = await make_api_request(
method="POST",
url=f"{host_url}/proxy/projects/projects",
headers={"Content-Type": "application/json"},
json_data=project_data.model_dump(),
)
return CloudProjectCreateResponse.model_validate(response.json())
except Exception as e:
raise CloudUtilsError(f"Failed to create cloud project '{project_name}': {e}") from e
async def sync_project(project_name: str, force_full: bool = False) -> None:
"""Trigger sync for a specific project on cloud.
Args:
project_name: Name of project to sync
force_full: If True, force a full scan bypassing watermark optimization
"""
try:
from basic_memory.cli.commands.command_utils import run_sync
await run_sync(project=project_name, force_full=force_full)
except Exception as e:
raise CloudUtilsError(f"Failed to sync project '{project_name}': {e}") from e
async def project_exists(project_name: str) -> bool:
"""Check if a project exists on cloud.
Args:
project_name: Name of project to check
Returns:
True if project exists, False otherwise
"""
try:
projects = await fetch_cloud_projects()
project_names = {p.name for p in projects.projects}
return project_name in project_names
except Exception:
return False
@@ -0,0 +1,195 @@
"""Core cloud commands for Basic Memory CLI."""
import asyncio
import typer
from rich.console import Console
from basic_memory.cli.app import cloud_app
from basic_memory.cli.auth import CLIAuth
from basic_memory.config import ConfigManager
from basic_memory.cli.commands.cloud.api_client import (
CloudAPIError,
SubscriptionRequiredError,
get_cloud_config,
make_api_request,
)
from basic_memory.cli.commands.cloud.bisync_commands import (
BisyncError,
generate_mount_credentials,
get_mount_info,
)
from basic_memory.cli.commands.cloud.rclone_config import configure_rclone_remote
from basic_memory.cli.commands.cloud.rclone_installer import (
RcloneInstallError,
install_rclone,
)
console = Console()
@cloud_app.command()
def login():
"""Authenticate with WorkOS using OAuth Device Authorization flow and enable cloud mode."""
async def _login():
client_id, domain, host_url = get_cloud_config()
auth = CLIAuth(client_id=client_id, authkit_domain=domain)
try:
success = await auth.login()
if not success:
console.print("[red]Login failed[/red]")
raise typer.Exit(1)
# Test subscription access by calling a protected endpoint
console.print("[dim]Verifying subscription access...[/dim]")
await make_api_request("GET", f"{host_url.rstrip('/')}/proxy/health")
# Enable cloud mode after successful login and subscription validation
config_manager = ConfigManager()
config = config_manager.load_config()
config.cloud_mode = True
config_manager.save_config(config)
console.print("[green]Cloud mode enabled[/green]")
console.print(f"[dim]All CLI commands now work against {host_url}[/dim]")
except SubscriptionRequiredError as e:
console.print("\n[red]Subscription Required[/red]\n")
console.print(f"[yellow]{e.args[0]}[/yellow]\n")
console.print(f"Subscribe at: [blue underline]{e.subscribe_url}[/blue underline]\n")
console.print(
"[dim]Once you have an active subscription, run [bold]bm cloud login[/bold] again.[/dim]"
)
raise typer.Exit(1)
asyncio.run(_login())
@cloud_app.command()
def logout():
"""Disable cloud mode and return to local mode."""
# Disable cloud mode
config_manager = ConfigManager()
config = config_manager.load_config()
config.cloud_mode = False
config_manager.save_config(config)
console.print("[green]Cloud mode disabled[/green]")
console.print("[dim]All CLI commands now work locally[/dim]")
@cloud_app.command("status")
def status() -> None:
"""Check cloud mode status and cloud instance health."""
# Check cloud mode
config_manager = ConfigManager()
config = config_manager.load_config()
console.print("[bold blue]Cloud Mode Status[/bold blue]")
if config.cloud_mode:
console.print(" Mode: [green]Cloud (enabled)[/green]")
console.print(f" Host: {config.cloud_host}")
console.print(" [dim]All CLI commands work against cloud[/dim]")
else:
console.print(" Mode: [yellow]Local (disabled)[/yellow]")
console.print(" [dim]All CLI commands work locally[/dim]")
console.print("\n[dim]To enable cloud mode, run: bm cloud login[/dim]")
return
# Get cloud configuration
_, _, host_url = get_cloud_config()
host_url = host_url.rstrip("/")
# Prepare headers
headers = {}
try:
console.print("\n[blue]Checking cloud instance health...[/blue]")
# Make API request to check health
response = asyncio.run(
make_api_request(method="GET", url=f"{host_url}/proxy/health", headers=headers)
)
health_data = response.json()
console.print("[green]Cloud instance is healthy[/green]")
# Display status details
if "status" in health_data:
console.print(f" Status: {health_data['status']}")
if "version" in health_data:
console.print(f" Version: {health_data['version']}")
if "timestamp" in health_data:
console.print(f" Timestamp: {health_data['timestamp']}")
console.print("\n[dim]To sync projects, use: bm project bisync --name <project>[/dim]")
except CloudAPIError as e:
console.print(f"[red]Error checking cloud health: {e}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]Unexpected error: {e}[/red]")
raise typer.Exit(1)
@cloud_app.command("setup")
def setup() -> None:
"""Set up cloud sync by installing rclone and configuring credentials.
SPEC-20: Simplified to project-scoped workflow.
After setup, use project commands for syncing:
bm project add <name> <path> --local-path ~/projects/<name>
bm project bisync --name <name> --resync # First time
bm project bisync --name <name> # Subsequent syncs
"""
console.print("[bold blue]Basic Memory Cloud Setup[/bold blue]")
console.print("Setting up cloud sync with rclone...\n")
try:
# Step 1: Install rclone
console.print("[blue]Step 1: Installing rclone...[/blue]")
install_rclone()
# Step 2: Get tenant info
console.print("\n[blue]Step 2: Getting tenant information...[/blue]")
tenant_info = asyncio.run(get_mount_info())
console.print(f"[green]Found tenant: {tenant_info.tenant_id}[/green]")
# Step 3: Generate credentials
console.print("\n[blue]Step 3: Generating sync credentials...[/blue]")
creds = asyncio.run(generate_mount_credentials(tenant_info.tenant_id))
console.print("[green]Generated secure credentials[/green]")
# Step 4: Configure rclone remote
console.print("\n[blue]Step 4: Configuring rclone remote...[/blue]")
configure_rclone_remote(
access_key=creds.access_key,
secret_key=creds.secret_key,
)
console.print("\n[bold green]Cloud setup completed successfully![/bold green]")
console.print("\n[bold]Next steps:[/bold]")
console.print("1. Add a project with local sync path:")
console.print(" bm project add research --local-path ~/Documents/research")
console.print("\n Or configure sync for an existing project:")
console.print(" bm project sync-setup research ~/Documents/research")
console.print("\n2. Preview the initial sync (recommended):")
console.print(" bm project bisync --name research --resync --dry-run")
console.print("\n3. If all looks good, run the actual sync:")
console.print(" bm project bisync --name research --resync")
console.print("\n4. Subsequent syncs (no --resync needed):")
console.print(" bm project bisync --name research")
console.print(
"\n[dim]Tip: Always use --dry-run first to preview changes before syncing[/dim]"
)
except (RcloneInstallError, BisyncError, CloudAPIError) as e:
console.print(f"\n[red]Setup failed: {e}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"\n[red]Unexpected error during setup: {e}[/red]")
raise typer.Exit(1)
@@ -0,0 +1,371 @@
"""Project-scoped rclone sync commands for Basic Memory Cloud.
This module provides simplified, project-scoped rclone operations:
- Each project syncs independently
- Uses single "basic-memory-cloud" remote (not tenant-specific)
- Balanced defaults from SPEC-8 Phase 4 testing
- Per-project bisync state tracking
Replaces tenant-wide sync with project-scoped workflows.
"""
import re
import subprocess
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Optional
from loguru import logger
from rich.console import Console
from basic_memory.cli.commands.cloud.rclone_installer import is_rclone_installed
from basic_memory.utils import normalize_project_path
console = Console()
# Minimum rclone version for --create-empty-src-dirs support
MIN_RCLONE_VERSION_EMPTY_DIRS = (1, 64, 0)
class RcloneError(Exception):
"""Exception raised for rclone command errors."""
pass
def check_rclone_installed() -> None:
"""Check if rclone is installed and raise helpful error if not.
Raises:
RcloneError: If rclone is not installed with installation instructions
"""
if not is_rclone_installed():
raise RcloneError(
"rclone is not installed.\n\n"
"Install rclone by running: bm cloud setup\n"
"Or install manually from: https://rclone.org/downloads/\n\n"
"Windows users: Ensure you have a package manager installed (winget, chocolatey, or scoop)"
)
@lru_cache(maxsize=1)
def get_rclone_version() -> tuple[int, int, int] | None:
"""Get rclone version as (major, minor, patch) tuple.
Returns:
Version tuple like (1, 64, 2), or None if version cannot be determined.
Note:
Result is cached since rclone version won't change during runtime.
"""
try:
result = subprocess.run(["rclone", "version"], capture_output=True, text=True, timeout=10)
# Parse "rclone v1.64.2" or "rclone v1.60.1-DEV"
match = re.search(r"v(\d+)\.(\d+)\.(\d+)", result.stdout)
if match:
version = (int(match.group(1)), int(match.group(2)), int(match.group(3)))
logger.debug(f"Detected rclone version: {version}")
return version
except Exception as e:
logger.warning(f"Could not determine rclone version: {e}")
return None
def supports_create_empty_src_dirs() -> bool:
"""Check if installed rclone supports --create-empty-src-dirs flag.
Returns:
True if rclone version >= 1.64.0, False otherwise.
"""
version = get_rclone_version()
if version is None:
# If we can't determine version, assume older and skip the flag
return False
return version >= MIN_RCLONE_VERSION_EMPTY_DIRS
@dataclass
class SyncProject:
"""Project configured for cloud sync.
Attributes:
name: Project name
path: Cloud path (e.g., "app/data/research")
local_sync_path: Local directory for syncing (optional)
"""
name: str
path: str
local_sync_path: Optional[str] = None
def get_bmignore_filter_path() -> Path:
"""Get path to rclone filter file.
Uses ~/.basic-memory/.bmignore converted to rclone format.
File is automatically created with default patterns on first use.
Returns:
Path to rclone filter file
"""
# Import here to avoid circular dependency
from basic_memory.cli.commands.cloud.bisync_commands import (
convert_bmignore_to_rclone_filters,
)
return convert_bmignore_to_rclone_filters()
def get_project_bisync_state(project_name: str) -> Path:
"""Get path to project's bisync state directory.
Args:
project_name: Name of the project
Returns:
Path to bisync state directory for this project
"""
return Path.home() / ".basic-memory" / "bisync-state" / project_name
def bisync_initialized(project_name: str) -> bool:
"""Check if bisync has been initialized for this project.
Args:
project_name: Name of the project
Returns:
True if bisync state exists, False otherwise
"""
state_path = get_project_bisync_state(project_name)
return state_path.exists() and any(state_path.iterdir())
def get_project_remote(project: SyncProject, bucket_name: str) -> str:
"""Build rclone remote path for project.
Args:
project: Project with cloud path
bucket_name: S3 bucket name
Returns:
Remote path like "basic-memory-cloud:bucket-name/basic-memory-llc"
Note:
The API returns paths like "/app/data/basic-memory-llc" because the S3 bucket
is mounted at /app/data on the fly machine. We need to strip the /app/data/
prefix to get the actual S3 path within the bucket.
"""
# Normalize path to strip /app/data/ mount point prefix
cloud_path = normalize_project_path(project.path).lstrip("/")
return f"basic-memory-cloud:{bucket_name}/{cloud_path}"
def project_sync(
project: SyncProject,
bucket_name: str,
dry_run: bool = False,
verbose: bool = False,
) -> bool:
"""One-way sync: local → cloud.
Makes cloud identical to local using rclone sync.
Args:
project: Project to sync
bucket_name: S3 bucket name
dry_run: Preview changes without applying
verbose: Show detailed output
Returns:
True if sync succeeded, False otherwise
Raises:
RcloneError: If project has no local_sync_path configured or rclone not installed
"""
check_rclone_installed()
if not project.local_sync_path:
raise RcloneError(f"Project {project.name} has no local_sync_path configured")
local_path = Path(project.local_sync_path).expanduser()
remote_path = get_project_remote(project, bucket_name)
filter_path = get_bmignore_filter_path()
cmd = [
"rclone",
"sync",
str(local_path),
remote_path,
"--filter-from",
str(filter_path),
]
if verbose:
cmd.append("--verbose")
else:
cmd.append("--progress")
if dry_run:
cmd.append("--dry-run")
result = subprocess.run(cmd, text=True)
return result.returncode == 0
def project_bisync(
project: SyncProject,
bucket_name: str,
dry_run: bool = False,
resync: bool = False,
verbose: bool = False,
) -> bool:
"""Two-way sync: local ↔ cloud.
Uses rclone bisync with balanced defaults:
- conflict_resolve: newer (auto-resolve to most recent)
- max_delete: 25 (safety limit)
- compare: modtime (ignore size differences from line ending conversions)
- check_access: false (skip for performance)
Args:
project: Project to sync
bucket_name: S3 bucket name
dry_run: Preview changes without applying
resync: Force resync to establish new baseline
verbose: Show detailed output
Returns:
True if bisync succeeded, False otherwise
Raises:
RcloneError: If project has no local_sync_path, needs --resync, or rclone not installed
"""
check_rclone_installed()
if not project.local_sync_path:
raise RcloneError(f"Project {project.name} has no local_sync_path configured")
local_path = Path(project.local_sync_path).expanduser()
remote_path = get_project_remote(project, bucket_name)
filter_path = get_bmignore_filter_path()
state_path = get_project_bisync_state(project.name)
# Ensure state directory exists
state_path.mkdir(parents=True, exist_ok=True)
cmd = [
"rclone",
"bisync",
str(local_path),
remote_path,
"--resilient",
"--conflict-resolve=newer",
"--max-delete=25",
"--compare=modtime", # Ignore size differences from line ending conversions
"--filter-from",
str(filter_path),
"--workdir",
str(state_path),
]
# Add --create-empty-src-dirs if rclone version supports it (v1.64+)
if supports_create_empty_src_dirs():
cmd.append("--create-empty-src-dirs")
if verbose:
cmd.append("--verbose")
else:
cmd.append("--progress")
if dry_run:
cmd.append("--dry-run")
if resync:
cmd.append("--resync")
# Check if first run requires resync
if not resync and not bisync_initialized(project.name) and not dry_run:
raise RcloneError(
f"First bisync for {project.name} requires --resync to establish baseline.\n"
f"Run: bm project bisync --name {project.name} --resync"
)
result = subprocess.run(cmd, text=True)
return result.returncode == 0
def project_check(
project: SyncProject,
bucket_name: str,
one_way: bool = False,
) -> bool:
"""Check integrity between local and cloud.
Verifies files match without transferring data.
Args:
project: Project to check
bucket_name: S3 bucket name
one_way: Only check for missing files on destination (faster)
Returns:
True if files match, False if differences found
Raises:
RcloneError: If project has no local_sync_path configured or rclone not installed
"""
check_rclone_installed()
if not project.local_sync_path:
raise RcloneError(f"Project {project.name} has no local_sync_path configured")
local_path = Path(project.local_sync_path).expanduser()
remote_path = get_project_remote(project, bucket_name)
filter_path = get_bmignore_filter_path()
cmd = [
"rclone",
"check",
str(local_path),
remote_path,
"--filter-from",
str(filter_path),
]
if one_way:
cmd.append("--one-way")
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode == 0
def project_ls(
project: SyncProject,
bucket_name: str,
path: Optional[str] = None,
) -> list[str]:
"""List files in remote project.
Args:
project: Project to list files from
bucket_name: S3 bucket name
path: Optional subdirectory within project
Returns:
List of file paths
Raises:
subprocess.CalledProcessError: If rclone command fails
RcloneError: If rclone is not installed
"""
check_rclone_installed()
remote_path = get_project_remote(project, bucket_name)
if path:
remote_path = f"{remote_path}/{path}"
cmd = ["rclone", "ls", remote_path]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return result.stdout.splitlines()
@@ -0,0 +1,110 @@
"""rclone configuration management for Basic Memory Cloud.
This module provides simplified rclone configuration for SPEC-20.
Uses a single "basic-memory-cloud" remote for all operations.
"""
import configparser
import os
import shutil
from pathlib import Path
from typing import Optional
from rich.console import Console
console = Console()
class RcloneConfigError(Exception):
"""Exception raised for rclone configuration errors."""
pass
def get_rclone_config_path() -> Path:
"""Get the path to rclone configuration file."""
config_dir = Path.home() / ".config" / "rclone"
config_dir.mkdir(parents=True, exist_ok=True)
return config_dir / "rclone.conf"
def backup_rclone_config() -> Optional[Path]:
"""Create a backup of existing rclone config."""
config_path = get_rclone_config_path()
if not config_path.exists():
return None
backup_path = config_path.with_suffix(f".conf.backup-{os.getpid()}")
shutil.copy2(config_path, backup_path)
console.print(f"[dim]Created backup: {backup_path}[/dim]")
return backup_path
def load_rclone_config() -> configparser.ConfigParser:
"""Load existing rclone configuration."""
config = configparser.ConfigParser()
config_path = get_rclone_config_path()
if config_path.exists():
config.read(config_path)
return config
def save_rclone_config(config: configparser.ConfigParser) -> None:
"""Save rclone configuration to file."""
config_path = get_rclone_config_path()
with open(config_path, "w") as f:
config.write(f)
console.print(f"[dim]Updated rclone config: {config_path}[/dim]")
def configure_rclone_remote(
access_key: str,
secret_key: str,
endpoint: str = "https://fly.storage.tigris.dev",
region: str = "auto",
) -> str:
"""Configure single rclone remote named 'basic-memory-cloud'.
This is the simplified approach from SPEC-20 that uses one remote
for all Basic Memory cloud operations (not tenant-specific).
Args:
access_key: S3 access key ID
secret_key: S3 secret access key
endpoint: S3-compatible endpoint URL
region: S3 region (default: auto)
Returns:
The remote name: "basic-memory-cloud"
"""
# Backup existing config
backup_rclone_config()
# Load existing config
config = load_rclone_config()
# Single remote name (not tenant-specific)
REMOTE_NAME = "basic-memory-cloud"
# Add/update the remote section
if not config.has_section(REMOTE_NAME):
config.add_section(REMOTE_NAME)
config.set(REMOTE_NAME, "type", "s3")
config.set(REMOTE_NAME, "provider", "Other")
config.set(REMOTE_NAME, "access_key_id", access_key)
config.set(REMOTE_NAME, "secret_access_key", secret_key)
config.set(REMOTE_NAME, "endpoint", endpoint)
config.set(REMOTE_NAME, "region", region)
# Prevent unnecessary encoding of filenames (only encode slashes and invalid UTF-8)
# This prevents files with spaces like "Hello World.md" from being quoted
config.set(REMOTE_NAME, "encoding", "Slash,InvalidUtf8")
# Save updated config
save_rclone_config(config)
console.print(f"[green]Configured rclone remote: {REMOTE_NAME}[/green]")
return REMOTE_NAME
@@ -0,0 +1,263 @@
"""Cross-platform rclone installation utilities."""
import os
import platform
import shutil
import subprocess
from typing import Optional
from rich.console import Console
console = Console()
class RcloneInstallError(Exception):
"""Exception raised for rclone installation errors."""
pass
def is_rclone_installed() -> bool:
"""Check if rclone is already installed and available in PATH."""
return shutil.which("rclone") is not None
def get_platform() -> str:
"""Get the current platform identifier."""
system = platform.system().lower()
if system == "darwin":
return "macos"
elif system == "linux":
return "linux"
elif system == "windows":
return "windows"
else:
raise RcloneInstallError(f"Unsupported platform: {system}")
def run_command(command: list[str], check: bool = True) -> subprocess.CompletedProcess:
"""Run a command with proper error handling."""
try:
console.print(f"[dim]Running: {' '.join(command)}[/dim]")
result = subprocess.run(command, capture_output=True, text=True, check=check)
if result.stdout:
console.print(f"[dim]Output: {result.stdout.strip()}[/dim]")
return result
except subprocess.CalledProcessError as e:
console.print(f"[red]Command failed: {e}[/red]")
if e.stderr:
console.print(f"[red]Error output: {e.stderr}[/red]")
raise RcloneInstallError(f"Command failed: {e}") from e
except FileNotFoundError as e:
raise RcloneInstallError(f"Command not found: {' '.join(command)}") from e
def install_rclone_macos() -> None:
"""Install rclone on macOS using Homebrew or official script."""
# Try Homebrew first
if shutil.which("brew"):
try:
console.print("[blue]Installing rclone via Homebrew...[/blue]")
run_command(["brew", "install", "rclone"])
console.print("[green]rclone installed via Homebrew[/green]")
return
except RcloneInstallError:
console.print(
"[yellow]Homebrew installation failed, trying official script...[/yellow]"
)
# Fallback to official script
console.print("[blue]Installing rclone via official script...[/blue]")
try:
run_command(["sh", "-c", "curl https://rclone.org/install.sh | sudo bash"])
console.print("[green]rclone installed via official script[/green]")
except RcloneInstallError:
raise RcloneInstallError(
"Failed to install rclone. Please install manually: brew install rclone"
)
def install_rclone_linux() -> None:
"""Install rclone on Linux using package managers or official script."""
# Try snap first (most universal)
if shutil.which("snap"):
try:
console.print("[blue]Installing rclone via snap...[/blue]")
run_command(["sudo", "snap", "install", "rclone"])
console.print("[green]rclone installed via snap[/green]")
return
except RcloneInstallError:
console.print("[yellow]Snap installation failed, trying apt...[/yellow]")
# Try apt (Debian/Ubuntu)
if shutil.which("apt"):
try:
console.print("[blue]Installing rclone via apt...[/blue]")
run_command(["sudo", "apt", "update"])
run_command(["sudo", "apt", "install", "-y", "rclone"])
console.print("[green]rclone installed via apt[/green]")
return
except RcloneInstallError:
console.print("[yellow]apt installation failed, trying official script...[/yellow]")
# Fallback to official script
console.print("[blue]Installing rclone via official script...[/blue]")
try:
run_command(["sh", "-c", "curl https://rclone.org/install.sh | sudo bash"])
console.print("[green]rclone installed via official script[/green]")
except RcloneInstallError:
raise RcloneInstallError(
"Failed to install rclone. Please install manually: sudo snap install rclone"
)
def install_rclone_windows() -> None:
"""Install rclone on Windows using package managers."""
# Try winget first (built into Windows 10+)
if shutil.which("winget"):
try:
console.print("[blue]Installing rclone via winget...[/blue]")
run_command(
[
"winget",
"install",
"Rclone.Rclone",
"--accept-source-agreements",
"--accept-package-agreements",
]
)
console.print("[green]rclone installed via winget[/green]")
return
except RcloneInstallError:
console.print("[yellow]winget installation failed, trying chocolatey...[/yellow]")
# Try chocolatey
if shutil.which("choco"):
try:
console.print("[blue]Installing rclone via chocolatey...[/blue]")
run_command(["choco", "install", "rclone", "-y"])
console.print("[green]rclone installed via chocolatey[/green]")
return
except RcloneInstallError:
console.print("[yellow]chocolatey installation failed, trying scoop...[/yellow]")
# Try scoop
if shutil.which("scoop"):
try:
console.print("[blue]Installing rclone via scoop...[/blue]")
run_command(["scoop", "install", "rclone"])
console.print("[green]rclone installed via scoop[/green]")
return
except RcloneInstallError:
console.print("[yellow]scoop installation failed[/yellow]")
# No package manager available - provide detailed instructions
error_msg = (
"Could not install rclone automatically.\n\n"
"Windows requires a package manager to install rclone. Options:\n\n"
"1. Install winget (recommended, built into Windows 11):\n"
" - Windows 11: Already installed\n"
" - Windows 10: Install 'App Installer' from Microsoft Store\n"
" - Then run: bm cloud setup\n\n"
"2. Install chocolatey:\n"
" - Visit: https://chocolatey.org/install\n"
" - Then run: bm cloud setup\n\n"
"3. Install scoop:\n"
" - Visit: https://scoop.sh\n"
" - Then run: bm cloud setup\n\n"
"4. Manual installation:\n"
" - Download from: https://rclone.org/downloads/\n"
" - Extract and add to PATH\n"
)
raise RcloneInstallError(error_msg)
def install_rclone(platform_override: Optional[str] = None) -> None:
"""Install rclone for the current platform."""
if is_rclone_installed():
console.print("[green]rclone is already installed[/green]")
return
platform_name = platform_override or get_platform()
console.print(f"[blue]Installing rclone for {platform_name}...[/blue]")
try:
if platform_name == "macos":
install_rclone_macos()
elif platform_name == "linux":
install_rclone_linux()
elif platform_name == "windows":
install_rclone_windows()
refresh_windows_path()
else:
raise RcloneInstallError(f"Unsupported platform: {platform_name}")
# Verify installation
if not is_rclone_installed():
raise RcloneInstallError("rclone installation completed but command not found in PATH")
console.print("[green]rclone installation completed successfully[/green]")
except RcloneInstallError:
raise
except Exception as e:
raise RcloneInstallError(f"Unexpected error during installation: {e}") from e
def refresh_windows_path() -> None:
"""Refresh the Windows PATH environment variable for the current session."""
if platform.system().lower() != "windows":
return
# Importing here after performing platform detection. Also note that we have to ignore pylance/pyright
# warnings about winreg attributes so that "errors" don't appear on non-Windows platforms.
import winreg
user_key_path = r"Environment"
system_key_path = r"System\CurrentControlSet\Control\Session Manager\Environment"
new_path = ""
# Read user PATH
try:
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, user_key_path, 0, winreg.KEY_READ) # type: ignore[reportAttributeAccessIssue]
user_path, _ = winreg.QueryValueEx(reg_key, "PATH") # type: ignore[reportAttributeAccessIssue]
winreg.CloseKey(reg_key) # type: ignore[reportAttributeAccessIssue]
except Exception:
user_path = ""
# Read system PATH
try:
reg_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, system_key_path, 0, winreg.KEY_READ) # type: ignore[reportAttributeAccessIssue]
system_path, _ = winreg.QueryValueEx(reg_key, "PATH") # type: ignore[reportAttributeAccessIssue]
winreg.CloseKey(reg_key) # type: ignore[reportAttributeAccessIssue]
except Exception:
system_path = ""
# Merge user and system PATHs (system first, then user)
if system_path and user_path:
new_path = system_path + ";" + user_path
elif system_path:
new_path = system_path
elif user_path:
new_path = user_path
if new_path:
os.environ["PATH"] = new_path
def get_rclone_version() -> Optional[str]:
"""Get the installed rclone version."""
if not is_rclone_installed():
return None
try:
result = run_command(["rclone", "version"], check=False)
if result.returncode == 0:
# Parse version from output (format: "rclone v1.64.0")
lines = result.stdout.strip().split("\n")
for line in lines:
if line.startswith("rclone v"):
return line.split()[1]
return "unknown"
except Exception:
return "unknown"
@@ -0,0 +1,233 @@
"""WebDAV upload functionality for basic-memory projects."""
import os
from pathlib import Path
import aiofiles
import httpx
from basic_memory.ignore_utils import load_gitignore_patterns, should_ignore_path
from basic_memory.mcp.async_client import get_client
from basic_memory.mcp.tools.utils import call_put
# Archive file extensions that should be skipped during upload
ARCHIVE_EXTENSIONS = {".zip", ".tar", ".gz", ".bz2", ".xz", ".7z", ".rar", ".tgz", ".tbz2"}
async def upload_path(
local_path: Path,
project_name: str,
verbose: bool = False,
use_gitignore: bool = True,
dry_run: bool = False,
) -> bool:
"""
Upload a file or directory to cloud project via WebDAV.
Args:
local_path: Path to local file or directory
project_name: Name of cloud project (destination)
verbose: Show detailed information about filtering and upload
use_gitignore: If False, skip .gitignore patterns (still use .bmignore)
dry_run: If True, show what would be uploaded without uploading
Returns:
True if upload succeeded, False otherwise
"""
try:
# Resolve path
local_path = local_path.resolve()
# Check if path exists
if not local_path.exists():
print(f"Error: Path does not exist: {local_path}")
return False
# Get files to upload
if local_path.is_file():
files_to_upload = [(local_path, local_path.name)]
if verbose:
print(f"Uploading single file: {local_path.name}")
else:
files_to_upload = _get_files_to_upload(local_path, verbose, use_gitignore)
if not files_to_upload:
print("No files found to upload")
if verbose:
print(
"\nTip: Use --verbose to see which files are being filtered, "
"or --no-gitignore to skip .gitignore patterns"
)
return True
print(f"Found {len(files_to_upload)} file(s) to upload")
# Calculate total size
total_bytes = sum(file_path.stat().st_size for file_path, _ in files_to_upload)
skipped_count = 0
# If dry run, just show what would be uploaded
if dry_run:
print("\nFiles that would be uploaded:")
for file_path, relative_path in files_to_upload:
# Skip archive files
if _is_archive_file(file_path):
print(f" [SKIP] {relative_path} (archive file)")
skipped_count += 1
continue
size = file_path.stat().st_size
if size < 1024:
size_str = f"{size} bytes"
elif size < 1024 * 1024:
size_str = f"{size / 1024:.1f} KB"
else:
size_str = f"{size / (1024 * 1024):.1f} MB"
print(f" {relative_path} ({size_str})")
else:
# Upload files using httpx
async with get_client() as client:
for i, (file_path, relative_path) in enumerate(files_to_upload, 1):
# Skip archive files (zip, tar, gz, etc.)
if _is_archive_file(file_path):
print(
f"Skipping archive file: {relative_path} ({i}/{len(files_to_upload)})"
)
skipped_count += 1
continue
# Build remote path: /webdav/{project_name}/{relative_path}
remote_path = f"/webdav/{project_name}/{relative_path}"
print(f"Uploading {relative_path} ({i}/{len(files_to_upload)})")
# Get file modification time
file_stat = file_path.stat()
mtime = int(file_stat.st_mtime)
# Read file content asynchronously
async with aiofiles.open(file_path, "rb") as f:
content = await f.read()
# Upload via HTTP PUT to WebDAV endpoint with mtime header
# Using X-OC-Mtime (ownCloud/Nextcloud standard)
response = await call_put(
client, remote_path, content=content, headers={"X-OC-Mtime": str(mtime)}
)
response.raise_for_status()
# Format total size based on magnitude
if total_bytes < 1024:
size_str = f"{total_bytes} bytes"
elif total_bytes < 1024 * 1024:
size_str = f"{total_bytes / 1024:.1f} KB"
else:
size_str = f"{total_bytes / (1024 * 1024):.1f} MB"
uploaded_count = len(files_to_upload) - skipped_count
if dry_run:
print(f"\nTotal: {uploaded_count} file(s) ({size_str})")
if skipped_count > 0:
print(f" Would skip {skipped_count} archive file(s)")
else:
print(f"✓ Upload complete: {uploaded_count} file(s) ({size_str})")
if skipped_count > 0:
print(f" Skipped {skipped_count} archive file(s)")
return True
except httpx.HTTPStatusError as e:
print(f"Upload failed: HTTP {e.response.status_code} - {e.response.text}")
return False
except Exception as e:
print(f"Upload failed: {e}")
return False
def _is_archive_file(file_path: Path) -> bool:
"""
Check if a file is an archive file based on its extension.
Args:
file_path: Path to the file to check
Returns:
True if file is an archive, False otherwise
"""
return file_path.suffix.lower() in ARCHIVE_EXTENSIONS
def _get_files_to_upload(
directory: Path, verbose: bool = False, use_gitignore: bool = True
) -> list[tuple[Path, str]]:
"""
Get list of files to upload from directory.
Uses .bmignore and optionally .gitignore patterns for filtering.
Args:
directory: Directory to scan
verbose: Show detailed filtering information
use_gitignore: If False, skip .gitignore patterns (still use .bmignore)
Returns:
List of (absolute_path, relative_path) tuples
"""
files = []
ignored_files = []
# Load ignore patterns from .bmignore and optionally .gitignore
ignore_patterns = load_gitignore_patterns(directory, use_gitignore=use_gitignore)
if verbose:
gitignore_path = directory / ".gitignore"
gitignore_exists = gitignore_path.exists() and use_gitignore
print(f"\nScanning directory: {directory}")
print("Using .bmignore: Yes")
print(f"Using .gitignore: {'Yes' if gitignore_exists else 'No'}")
print(f"Ignore patterns loaded: {len(ignore_patterns)}")
if ignore_patterns and len(ignore_patterns) <= 20:
print(f"Patterns: {', '.join(sorted(ignore_patterns))}")
print()
# Walk through directory
for root, dirs, filenames in os.walk(directory):
root_path = Path(root)
# Filter directories based on ignore patterns
filtered_dirs = []
for d in dirs:
dir_path = root_path / d
if should_ignore_path(dir_path, directory, ignore_patterns):
if verbose:
rel_path = dir_path.relative_to(directory)
print(f" [IGNORED DIR] {rel_path}/")
else:
filtered_dirs.append(d)
dirs[:] = filtered_dirs
# Process files
for filename in filenames:
file_path = root_path / filename
# Calculate relative path for display/remote
rel_path = file_path.relative_to(directory)
remote_path = str(rel_path).replace("\\", "/")
# Check if file should be ignored
if should_ignore_path(file_path, directory, ignore_patterns):
ignored_files.append(remote_path)
if verbose:
print(f" [IGNORED] {remote_path}")
continue
if verbose:
print(f" [INCLUDE] {remote_path}")
files.append((file_path, remote_path))
if verbose:
print("\nSummary:")
print(f" Files to upload: {len(files)}")
print(f" Files ignored: {len(ignored_files)}")
return files
@@ -0,0 +1,124 @@
"""Upload CLI commands for basic-memory projects."""
import asyncio
from pathlib import Path
import typer
from rich.console import Console
from basic_memory.cli.app import cloud_app
from basic_memory.cli.commands.cloud.cloud_utils import (
create_cloud_project,
project_exists,
sync_project,
)
from basic_memory.cli.commands.cloud.upload import upload_path
console = Console()
@cloud_app.command("upload")
def upload(
path: Path = typer.Argument(
...,
help="Path to local file or directory to upload",
exists=True,
readable=True,
resolve_path=True,
),
project: str = typer.Option(
...,
"--project",
"-p",
help="Cloud project name (destination)",
),
create_project: bool = typer.Option(
False,
"--create-project",
"-c",
help="Create project if it doesn't exist",
),
sync: bool = typer.Option(
True,
"--sync/--no-sync",
help="Sync project after upload (default: true)",
),
verbose: bool = typer.Option(
False,
"--verbose",
"-v",
help="Show detailed information about file filtering and upload",
),
no_gitignore: bool = typer.Option(
False,
"--no-gitignore",
help="Skip .gitignore patterns (still respects .bmignore)",
),
dry_run: bool = typer.Option(
False,
"--dry-run",
help="Show what would be uploaded without actually uploading",
),
) -> None:
"""Upload local files or directories to cloud project via WebDAV.
Examples:
bm cloud upload ~/my-notes --project research
bm cloud upload notes.md --project research --create-project
bm cloud upload ~/docs --project work --no-sync
bm cloud upload ./history --project proto --verbose
bm cloud upload ./notes --project work --no-gitignore
bm cloud upload ./files --project test --dry-run
"""
async def _upload():
# Check if project exists
if not await project_exists(project):
if create_project:
console.print(f"[blue]Creating cloud project '{project}'...[/blue]")
try:
await create_cloud_project(project)
console.print(f"[green]Created project '{project}'[/green]")
except Exception as e:
console.print(f"[red]Failed to create project: {e}[/red]")
raise typer.Exit(1)
else:
console.print(
f"[red]Project '{project}' does not exist.[/red]\n"
f"[yellow]Options:[/yellow]\n"
f" 1. Create it first: bm project add {project}\n"
f" 2. Use --create-project flag to create automatically"
)
raise typer.Exit(1)
# Perform upload (or dry run)
if dry_run:
console.print(
f"[yellow]DRY RUN: Showing what would be uploaded to '{project}'[/yellow]"
)
else:
console.print(f"[blue]Uploading {path} to project '{project}'...[/blue]")
success = await upload_path(
path, project, verbose=verbose, use_gitignore=not no_gitignore, dry_run=dry_run
)
if not success:
console.print("[red]Upload failed[/red]")
raise typer.Exit(1)
if dry_run:
console.print("[yellow]DRY RUN complete - no files were uploaded[/yellow]")
else:
console.print(f"[green]Successfully uploaded to '{project}'[/green]")
# Sync project if requested (skip on dry run)
# Force full scan after bisync to ensure database is up-to-date with synced files
if sync and not dry_run:
console.print(f"[blue]Syncing project '{project}'...[/blue]")
try:
await sync_project(project, force_full=True)
except Exception as e:
console.print(f"[yellow]Warning: Sync failed: {e}[/yellow]")
console.print("[dim]Files uploaded but may not be indexed yet[/dim]")
asyncio.run(_upload())
@@ -0,0 +1,77 @@
"""utility functions for commands"""
import asyncio
from typing import Optional, TypeVar, Coroutine, Any
from mcp.server.fastmcp.exceptions import ToolError
import typer
from rich.console import Console
from basic_memory import db
from basic_memory.mcp.async_client import get_client
from basic_memory.mcp.tools.utils import call_post, call_get
from basic_memory.mcp.project_context import get_active_project
from basic_memory.schemas import ProjectInfoResponse
console = Console()
T = TypeVar("T")
def run_with_cleanup(coro: Coroutine[Any, Any, T]) -> T:
"""Run an async coroutine with proper database cleanup.
This helper ensures database connections are cleaned up before the event
loop closes, preventing process hangs in CLI commands.
Args:
coro: The coroutine to run
Returns:
The result of the coroutine
"""
async def _with_cleanup() -> T:
try:
return await coro
finally:
await db.shutdown_db()
return asyncio.run(_with_cleanup())
async def run_sync(project: Optional[str] = None, force_full: bool = False):
"""Run sync operation via API endpoint.
Args:
project: Optional project name
force_full: If True, force a full scan bypassing watermark optimization
"""
try:
async with get_client() as client:
project_item = await get_active_project(client, project, None)
url = f"{project_item.project_url}/project/sync"
if force_full:
url += "?force_full=true"
response = await call_post(client, url)
data = response.json()
console.print(f"[green]{data['message']}[/green]")
except (ToolError, ValueError) as e:
console.print(f"[red]Sync failed: {e}[/red]")
raise typer.Exit(1)
async def get_project_info(project: str):
"""Get project information via API endpoint."""
try:
async with get_client() as client:
project_item = await get_active_project(client, project, None)
response = await call_get(client, f"{project_item.project_url}/project/info")
return ProjectInfoResponse.model_validate(response.json())
except (ToolError, ValueError) as e:
console.print(f"[red]Sync failed: {e}[/red]")
raise typer.Exit(1)

Some files were not shown because too many files have changed in this diff Show More