Skip to content

feat: Add display_name and is_private to ProjectItem#574

Open
jope-bm wants to merge 7 commits intomainfrom
feat-project-display-name
Open

feat: Add display_name and is_private to ProjectItem#574
jope-bm wants to merge 7 commits intomainfrom
feat-project-display-name

Conversation

@jope-bm
Copy link
Contributor

@jope-bm jope-bm commented Feb 17, 2026

Summary

  • Adds display_name and is_private optional fields to ProjectItem schema for cloud proxy enrichment
  • Updates list_memory_projects MCP tool to show "My Notes (private-fb83af23)" format when display_name is present
  • Backwards-compatible: fields default to None/False, no impact on non-cloud usage

Context

Basic Memory Cloud is adding per-user private projects ("My Notes") in team workspaces. The cloud proxy enriches the project list API response with display_name and is_private metadata, but these fields were being silently dropped during Pydantic deserialization because ProjectItem didn't declare them.

Changes

File Change
schemas/project_info.py Added display_name: Optional[str] = None and is_private: bool = False to ProjectItem
mcp/tools/project_management.py list_memory_projects uses "display_name (name)" format when display_name is set
tests/schemas/test_schemas.py 6 new tests for ProjectItem schema with cloud-injected fields
tests/mcp/test_tool_project_management.py 2 new tests for display_name formatting in list_memory_projects

Test plan

  • 8 new tests pass
  • All existing project management and schema tests pass
  • CI passes

🤖 Generated with Claude Code

Add optional cloud-injected metadata fields to ProjectItem schema so
the cloud proxy can enrich project list responses with friendly names
and privacy indicators for per-user private projects.

- display_name: Optional friendly name (e.g. "My Notes") shown instead
  of the internal slug (e.g. "private-fb83af23")
- is_private: Boolean flag indicating the project is private to the user

Update list_memory_projects MCP tool to show "display_name (name)"
format when display_name is present, so MCP clients see friendly names
while still knowing the actual project slug to use in API calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Joe P <joe@basicmemory.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fedcbff3b1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


for project in project_list.projects:
result += f"• {project.name}\n"
label = f"{project.display_name} ({project.name})" if project.display_name else project.name

Choose a reason for hiding this comment

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

P1 Badge Keep project labels resolvable by resolve endpoint

When display_name is set, this tool now publishes display_name (name) as the project label, but project resolution only accepts external ID, permalink, or raw name (see /v2/projects/resolve lookup in project_router.py), so using the displayed label (or the display name alone) as a project argument will fail with “Project not found”. For private cloud projects this breaks the normal list→select→use workflow unless callers manually parse the slug back out of the label.

Useful? React with 👍 / 👎.

phernandez and others added 2 commits February 17, 2026 23:26
ConfigManager.add_project() and ProjectService.move_project() unconditionally
called mkdir on the project path. In cloud mode (BASIC_MEMORY_PROJECT_ROOT set),
paths resolve under root (/) which is read-only on macOS, crashing local dev.
Production Fly.io containers have writable /, so this was never caught there.

Skip mkdir when project_root is set since cloud mode uses S3 for file storage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Joe P <joe@basicmemory.com>
Copy link
Member

@phernandez phernandez left a comment

Choose a reason for hiding this comment

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

@claude see my comments about using the FileService instead of Path to create dirs

Path(resolved_path).mkdir(parents=True, exist_ok=True)
# Create the new directory if it doesn't exist (skip in cloud mode where storage is S3)
if not self.config_manager.config.project_root:
Path(resolved_path).mkdir(parents=True, exist_ok=True)
Copy link
Member

Choose a reason for hiding this comment

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

We should use the FileService here (or S3Fileservice in the cloud)

project_path = Path(path)
project_path.mkdir(parents=True, exist_ok=True) # pragma: no cover
if not self.config.project_root:
project_path.mkdir(parents=True, exist_ok=True) # pragma: no cover
Copy link
Member

Choose a reason for hiding this comment

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

also here: We should use the FileService here (or S3Fileservice in the cloud)

@phernandez
Copy link
Member

@claude can you resolve this conflict?

@claude
Copy link
Contributor

claude bot commented Feb 19, 2026

Claude finished @phernandez's task in 13m 22s —— View job


Resolved Review Feedback

All tasks completed:

  • Gather context: read changed files and understand FileService
  • Make markdown_processor Optional in FileService (needed for system-level file service)
  • Update ProjectService.__init__ to accept file_service
  • Move mkdir from ConfigManager.add_project() to ProjectService.add_project() using FileService.ensure_directory()
  • Update move_project() to use FileService.ensure_directory()
  • Update deps/services.py to inject FileService into ProjectService
  • Update tests/conftest.py project_service fixture
  • Update tests/test_config.py to reflect ConfigManager never creates directories
  • Commit and push changes

Summary of Changes

Addressed phernandez's review by replacing direct Path.mkdir() calls with FileService.ensure_directory(), which provides the abstraction layer needed for future cloud (S3) compatibility.

src/basic_memory/services/file_service.py

  • Made markdown_processor optional (default None) — ensure_directory doesn't need it, enabling a lightweight system-level FileService to be created without the full markdown processing stack

src/basic_memory/config.py

  • Removed Path.mkdir() from ConfigManager.add_project() — config manager is now pure config management; directory creation is delegated to ProjectService

src/basic_memory/services/project_service.py

  • Added file_service: Optional[FileService] = None to __init__
  • add_project() and move_project() now call await self.file_service.ensure_directory() in local mode, with fail-fast error if file_service is None

src/basic_memory/deps/services.py

  • get_project_service now creates and injects a system-level FileService (with Path.home() as base path — ensure_directory accepts absolute paths so base path doesn't matter here)

tests/conftest.py

  • Updated project_service fixture to accept and pass file_service

tests/test_config.py

  • Replaced test_add_project_skips_mkdir_when_project_root_set with test_add_project_never_creates_directory — now correctly asserts that ConfigManager.add_project() never creates filesystem directories regardless of mode
    | Branch

github-actions bot and others added 2 commits February 19, 2026 04:44
…eation

Replace direct Path.mkdir() calls with FileService.ensure_directory() to enable
future cloud (S3) compatibility. ConfigManager.add_project() is now pure config
management — directory creation is delegated to ProjectService via FileService.

- Make FileService.markdown_processor Optional (not needed for ensure_directory)
- Add file_service: Optional[FileService] to ProjectService.__init__
- ProjectService.add_project() and move_project() use file_service.ensure_directory()
- deps/services.py injects a system-level FileService into ProjectService
- Update tests/conftest.py project_service fixture to include file_service
- Update test_config.py: test that ConfigManager.add_project() never creates dirs

Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Signed-off-by: phernandez <paul@basicmachines.co>
@jope-bm
Copy link
Contributor Author

jope-bm commented Feb 19, 2026

@claude This check is failing. Can you fix it? https://github.com/basicmachines-co/basic-memory/actions/runs/22169948507/job/64182360857?pr=574

@claude
Copy link
Contributor

claude bot commented Feb 19, 2026

Claude finished @jope-bm's task in 1m 33s —— View job


Fixed Failing Type Check

Root cause: file_service.py:83markdown_processor was made Optional[MarkdownProcessor] (default None) in the previous FileService refactor, but read_entity_content called self.markdown_processor.read_file(...) without a null check. Pyright flagged this as reportOptionalMemberAccess.

Fix: Added a fail-fast guard before the call (src/basic_memory/services/file_service.py:83):

# markdown_processor is required for entity content reads — fail fast if not configured
if self.markdown_processor is None:
    raise ValueError("markdown_processor is required for read_entity_content")

After this check Pyright narrows the type to non-None, resolving the error. This is consistent with the project's fail-fast principle.

  • Gather context and understand the request
  • Check CI logs — type check failure in file_service.py:83
  • Read file_service.py
  • Add fail-fast null guard in read_entity_content
  • Commit and push (32929fe)
    | Branch

github-actions bot and others added 2 commits February 19, 2026 17:20
Pyright flagged `self.markdown_processor.read_file()` as
reportOptionalMemberAccess after markdown_processor was made Optional
in the FileService refactor. Added a fail-fast ValueError if
markdown_processor is None when read_entity_content is called —
consistent with the project's fail-fast principle and allows Pyright
to narrow the type for the subsequent call.

Co-authored-by: jope-bm <jope-bm@users.noreply.github.com>
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
fastembed, openai, and sqlite_vec are optional extras that may not be
installed at typecheck time. Add type: ignore comments so pyright
does not fail on their conditional imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Joe P <joe@basicmemory.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments