Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

feat: Multi-Domain Memories (User-Level vs Project-Level Storage)#25

Merged
zircote merged 8 commits into
v1.0.0from
issue-13-multi-domain
Dec 26, 2025
Merged

feat: Multi-Domain Memories (User-Level vs Project-Level Storage)#25
zircote merged 8 commits into
v1.0.0from
issue-13-multi-domain

Conversation

@zircote

@zircote zircote commented Dec 26, 2025

Copy link
Copy Markdown
Owner

Multi-Domain Memories (SPEC-2025-12-25-001)

Implements GitHub Issue #13 - User-level vs project-level memory storage.

Summary

Add support for multi-domain memory storage to distinguish between:

  • User domain: Global, cross-project memories stored in ~/.local/share/memory-plugin/user-memories/
  • Project domain: Repository-scoped memories (existing behavior)

Key Features

  • Domain enum and path configuration
  • Schema migration to v3 with domain column
  • GitOps factory for domain-aware git operations
  • User-memories bare repo initialization
  • Multi-domain capture and recall services
  • Domain markers: [global] inline and ▶ global:namespace ─── blocks
  • SessionStart context includes both domains
  • Optional remote sync for user memories
  • CLI command updates for domain filtering

Implementation Progress

Phase Name Progress Status
1 Foundation 0% pending
2 Storage Layer 0% pending
3 Service Layer 0% pending
4 Hooks Integration 0% pending
5 Sync & Polish 0% pending

Spec Documents

Architecture Decisions

  1. ADR-001: Separate bare git repo for user memories
  2. ADR-002: Project memories override user memories
  3. ADR-003: Team domain deferred to v2
  4. ADR-004: Optional remote auto-sync
  5. ADR-005: Memory ID format with domain prefix
  6. ADR-006: Lazy initialization of user infrastructure
  7. ADR-007: Domain as Enum rather than string

Auto-generated by /claude-spec:implement

@zircote
zircote changed the base branch from main to v1.0.0 December 26, 2025 03:14
zircote and others added 7 commits December 25, 2025 23:10
- Create PROGRESS.md with 24 tasks across 5 phases
- Update README.md with approval and start timestamps
- Update CHANGELOG.md with approval entry

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Task 1.1 complete - Add Domain Enum to Config

Changes:
- Add Domain enum with USER and PROJECT values
- Add get_user_memories_path() for user-memories bare repo location
- Add get_user_index_path() for user-domain SQLite index
- Both functions support optional ensure_exists parameter for lazy dir creation
- Add comprehensive tests for new configuration (17 tests)

All 1851 tests pass, 88% coverage.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add domain awareness to the Memory model for multi-domain storage:
- Add `domain: str` field with default "project" for backward compatibility
- Add `domain_enum` property to get Domain enum value
- Add `is_user_domain` and `is_project_domain` convenience properties
- Add domain properties to MemoryResult for uniform access
- Add 7 tests covering domain field and properties

Uses string storage for SQLite/JSON serialization compatibility, with
enum property for type-safe programmatic access.

Part of: SPEC-2025-12-25-001 (Multi-Domain Memories)
Resolves: Task 1.2

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 1 Foundation complete for multi-domain memories:

Schema Migration (Task 1.3):
- Bump SCHEMA_VERSION to 3
- Add domain TEXT column with 'project' default
- Add idx_memories_domain index
- Implement migration 3 for existing databases

IndexService Domain (Task 1.4):
- Update insert(), insert_batch(), update() for domain field
- Update _row_to_memory() with backward-compatible domain reading

Tests:
- Add TestSchemaMigration class with v2→v3 migration test
- Add fresh database schema test
- Add domain-specific insert tests

Closes phase 1 of SPEC-2025-12-25-001.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…s 2.1-2.4)

## Task 2.1: GitOps Factory for Domain
- Add GitOps.for_domain(domain) class method for domain-specific instances
- Implement domain instance caching with clear_domain_cache() for testing
- USER domain uses user-memories path, PROJECT domain uses repo path

## Task 2.2: Initialize User-Memories Bare Repo
- Add ensure_user_repo_initialized() class method
- Creates bare git repo at ~/.local/share/memory-plugin/user-memories/
- Configures git identity and creates initial empty commit
- Add is_bare_repository() method for verification

## Task 2.3: Add Domain Filter to IndexService Search
- Add optional domain parameter to search_vector()
- Add optional domain parameter to search_text()
- Both methods filter by domain when specified, backward compatible

## Task 2.4: Add Domain Filter to Other Index Methods
- Add domain filter to get_by_spec(), get_by_namespace(), list_recent()
- Add domain filter to count() method
- Update get_stats() to include by_domain breakdown
- Add by_domain field and by_domain_dict property to IndexStats model

Tests: 14 new GitOps tests, 13 new domain filter tests (1885 total, 89% coverage)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Mark Tasks 2.1-2.4 as done in IMPLEMENTATION_PLAN.md
- Update Phase 2 status to 100% done in PROGRESS.md
- Add session notes for Phase 2 completion
- Update current_phase to 2

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Apply 8 high-priority fixes from comprehensive code review:

Security:
- CRIT-001: Add ThreadPoolExecutor timeout (30s/120s) to embedding ops
- SEC-HIGH-001: Add symlink verification before user-memories git init
- SEC-HIGH-002: Add 64KB YAML size limit to prevent billion laughs
- SEC-HIGH-003: Implement PID-based stale lock detection

Performance:
- PERF-HIGH-001: Add pagination (limit/offset) to get_all_ids()
- PERF-HIGH-004: Add composite indexes for common query patterns

Code Quality:
- CRIT-003: Remove unused thread lock, document SQLite WAL concurrency
- QUAL-HIGH-001: Replace 10 broad exception handlers with specific types

Also removes code-review reports from docs/ (not needed in repo).

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@zircote
zircote force-pushed the issue-13-multi-domain branch from d22ecbd to bfd5cc9 Compare December 26, 2025 04:10
Add noqa: ARG005 comments for unused lambda arguments in test mocks.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@zircote
zircote marked this pull request as ready for review December 26, 2025 05:05
Copilot AI review requested due to automatic review settings December 26, 2025 05:05
@zircote
zircote merged commit 3cab1c7 into v1.0.0 Dec 26, 2025
4 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements comprehensive multi-domain memory storage, adding support for:

  • User domain: Global, cross-project memories stored in ~/.local/share/memory-plugin/user-memories/
  • Project domain: Repository-scoped memories (existing behavior)

The implementation spans 5 phases with 24 tasks, adding domain-aware capture, recall, sync, and hooks integration. All tasks are marked complete with 1955 passing tests.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/git_notes_memory/config.py Domain enum, user paths, remote config
src/git_notes_memory/models.py Memory model with domain field
src/git_notes_memory/index.py Schema v3 migration, domain filtering
src/git_notes_memory/git_ops.py Domain factory, user repo init
src/git_notes_memory/capture.py Domain-aware capture, user service
src/git_notes_memory/recall.py Multi-domain search with merge/dedup
src/git_notes_memory/sync.py User memory sync methods
src/git_notes_memory/hooks/ Domain markers, context builder updates
tests/ Comprehensive test coverage additions

The implementation appears thorough and well-tested. No blocking issues identified.

# Initialize bare repository
cmd = ["git", "init", "--bare", str(user_path)]
try:
result = subprocess.run(

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'result' is unnecessary as it is redefined before this value is used.

Suggested change
result = subprocess.run(
subprocess.run(

Copilot uses AI. Check for mistakes.
Comment thread tests/test_capture.py
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test get_user_capture_service returns a CaptureService."""
import git_notes_memory.capture as capture_module

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'git_notes_memory.capture' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_capture.py
) -> None:
"""Test get_user_capture_service returns a CaptureService."""
import git_notes_memory.capture as capture_module
import git_notes_memory.config as config_module

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'git_notes_memory.config' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_capture.py
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test get_user_capture_service returns the same instance."""
import git_notes_memory.capture as capture_module

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'git_notes_memory.capture' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_capture.py
) -> None:
"""Test get_user_capture_service returns the same instance."""
import git_notes_memory.capture as capture_module
import git_notes_memory.config as config_module

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'git_notes_memory.config' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_capture.py
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test get_user_capture_service uses GitOps.for_domain(USER)."""
import git_notes_memory.capture as capture_module

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'git_notes_memory.capture' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_capture.py
) -> None:
"""Test get_user_capture_service uses GitOps.for_domain(USER)."""
import git_notes_memory.capture as capture_module
import git_notes_memory.config as config_module

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'git_notes_memory.config' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_capture.py
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test get_user_capture_service returns service with IndexService."""
import git_notes_memory.capture as capture_module

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'git_notes_memory.capture' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_capture.py
) -> None:
"""Test get_user_capture_service returns service with IndexService."""
import git_notes_memory.capture as capture_module
import git_notes_memory.config as config_module

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'git_notes_memory.config' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
content = os.read(fd, 32).decode("utf-8", errors="ignore").strip()
if content and content.isdigit():
return int(content)
except OSError:

Copilot AI Dec 26, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants