Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .claude/commands/test-live.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: bm_write, bm_read, bm_search, bm_edit, bm_project(command="list"), bm_project(command="recent"))
- `core` - Essential tools only (Tier 1: bm_write, bm_read, bm_search, bm_edit, bm_project_list(), bm_recent())
- `features` - Core + important workflows (Tier 1 + Tier 2)
- `all` - Comprehensive testing of all tools and scenarios

Expand All @@ -27,12 +27,12 @@ When the user runs `/project:test-live`, execute comprehensive test plan:
2. **bm_read** - Primary knowledge retrieval mechanism
3. **bm_search** - Essential for finding information
4. **bm_edit** - Core content modification capability
5. **bm_project(command="list")** - Project discovery and session guidance
6. **bm_project(command="recent")** - Project discovery mode and activity analysis
5. **bm_project_list()** - Project discovery and session guidance
6. **bm_recent()** - Project discovery mode and activity analysis

### **Tier 2: Important Workflows (Usually Test)**
7. **bm_context** - Conversation continuity via memory:// URLs
8. **bm_project(command="create")** - Essential for project setup
8. **bm_project_create()** - Essential for project setup
9. **bm_move** - Knowledge organization
10. **bm_project_delete** - Project lifecycle management

Expand All @@ -49,9 +49,9 @@ When the user runs `/project:test-live`, execute comprehensive test plan:

## Project Routing Tests

1. Call `bm_project(command="list")`; inspect project names, external IDs, and
1. Call `bm_project_list()`; inspect project names, external IDs, and
workspace grouping. Reuse a returned UUID through `project`.
2. Call `bm_project(command="recent", project=<test-project>, since="7d")`.
2. Call `bm_recent(project=<test-project>, since="7d")`.
Check paging and indexing guidance. Without `project`, it uses the default
project; with no default, it aggregates projects. Empty activity is not proof
of an empty knowledge base.
Expand Down Expand Up @@ -159,12 +159,12 @@ Test essential MCP tools that form the foundation of Basic Memory:
- ✅ Stale `expected_checksum` refuses changes
- ⚠️ Error scenarios (invalid commands)

**5. bm_project(command="list") Tests (Critical):**
**5. bm_project_list() Tests (Critical):**
- Project identities and workspace grouping
- Empty project list and constrained server behavior
- UUIDs reusable through `project`

**6. bm_project(command="recent") Tests (Critical):**
**6. bm_recent() Tests (Critical):**
- Explicit project, default project, and no-default aggregation
- `since`, `items`, and pagination
- Empty activity and never-indexed guidance
Expand All @@ -177,7 +177,7 @@ Test essential MCP tools that form the foundation of Basic Memory:
- ✅ memory:// URL navigation
- ⚠️ Performance with complex relation graphs

**8. bm_project(command="create") Tests (Important):**
**8. bm_project_create() Tests (Important):**
- ✅ Create projects dynamically
- ✅ Set default during creation
- ✅ Path validation and creation
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ agents can pick the right one without trial-and-error:
- **Content:** `bm_write`, `bm_read`, `bm_edit`, `bm_move`, `bm_delete`
- **Search & discovery:** `bm_search`, `bm_list`, `bm_find`
- **Knowledge graph:** `bm_context`
- **Projects:** `bm_project(command="list" | "create" | "recent")`;
- **Projects:** `bm_project_list(| "create" | "recent")`;
`bm_project_delete` is separate and requires server opt-in.
- **Schema:** `bm_schema(command="validate" | "infer" | "diff")`
- **Manual:** `bm_man`
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/scripts/read_load_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,8 +738,8 @@ async def run(args: argparse.Namespace) -> int:
):
await session.initialize()
created = await session.call_tool(
"bm_project",
{"command": "create", "name": project, "path": str(project_dir)},
"bm_project_create",
{"name": project, "path": str(project_dir)},
)
if created.is_error:
raise RuntimeError("could not create benchmark project")
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/scripts/write_load_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,8 @@ async def run(args: argparse.Namespace) -> int:
await session.initialize()

create = await session.call_tool(
"bm_project",
{"command": "create", "name": project, "path": str(project_dir)},
"bm_project_create",
{"name": project, "path": str(project_dir)},
)
if create.model_dump(by_alias=True)["isError"]:
print("ERROR: could not create project", file=sys.stderr)
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/tests/agent_tasks/test_surfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_read_only_surface_excludes_mutation():
assert view.name == BM_SURFACE.name
assert view.config_overrides == BM_SURFACE.config_overrides
assert not set(SHARED_WRITE_TOOLS) & set(view.tool_allowlist)
assert "bm_project" not in view.tool_allowlist # its create command writes
assert "bm_project_create" not in view.tool_allowlist
assert "bm_schema" not in view.tool_allowlist
assert "bm_read" in view.tool_allowlist

Expand Down
32 changes: 17 additions & 15 deletions docs/1.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,10 @@ pass `--json`. Without `--project`, commands use the configured default project.
| `search --type T`, `--entity-type K`, `--after_date D` | `search --note-types T`, `--items K`, `--since D` |
| `search --category C` | `search --categories C` |
| `build-context`, `context URL --timeframe D` | `context IDENTIFIER --since D` |
| `recent-activity`, `recent` | `project recent` |
| `recent --timeframe D --type K --depth N` | `project recent --since D --items K`; depth is fixed at 1 |
| `list-projects`, `projects` | `project list` |
| `list-workspaces`, `workspaces` | `project list` (omits workspaces with no projects), or `bm cloud workspace list` for every workspace |
| `recent-activity`, `recent` | `recent` |
| `recent --timeframe D --type K --depth N` | `recent --since D --items K`; depth is fixed at 1 |
| `list-projects`, `projects` | `project-list` |
| `list-workspaces`, `workspaces` | `project-list` (omits workspaces with no projects), or `bm cloud workspace list` for every workspace |
| `schema-validate`, `schema-infer`, `schema-diff`, `validate`, `infer`, `diff` | `schema validate/infer/diff`; a positional `TYPE` becomes `--note-type TYPE` |
| `validate path/note.md` | `schema validate --identifier path/note.md` |
| `--project-id UUID` | `--project UUID` |
Expand All @@ -179,7 +179,7 @@ JSON result with `--json`.

| Previous shell verb | 1.0 shell verb |
| --- | --- |
| `bm tail` | removed; use `bm tool project recent` |
| `bm tail` | removed; use `bm tool recent` |
| `--plain`, Rich tables, JSON when piped | removed; text result by default, `--json` for JSON |
| `--project-id UUID` | `--project UUID` |
| no `--project` with several projects: refused | the configured default project |
Expand All @@ -205,11 +205,11 @@ The bundled manual page `tools-migration(7)` (`bm man tools-migration`) carries
| `build_context` | `bm_context` |
| `list_directory`, `ls` | `bm_list` for one level; `bm_find` for recursive inventory |
| `find` | `bm_find` |
| `recent_activity` | `bm_project(command="recent")` |
| `tail` | `bm_project(command="recent")`; map `timeframe` to `since` and `lines` to `page_size`. Recent JSON includes `created_at`, not `updated_at`. The `bm tail` shell command is removed; use `bm tool project recent`. |
| `list_memory_projects` | `bm_project(command="list")` |
| `list_workspaces` | `bm_project(command="list")` groups projects by workspace, so it omits a workspace with no projects; `bm cloud workspace list` in a shell lists every workspace |
| `create_memory_project` | `bm_project(command="create", name=..., path=...)` |
| `recent_activity` | `bm_recent` |
| `tail` | `bm_recent`; map `timeframe` to `since` and `lines` to `page_size`. Recent JSON includes `created_at`, not `updated_at`. The `bm tail` shell command is removed; use `bm tool recent`. |
| `list_memory_projects` | `bm_project_list` |
| `list_workspaces` | `bm_project_list` groups projects by workspace, so it omits a workspace with no projects; `bm cloud workspace list` in a shell lists every workspace |
| `create_memory_project` | `bm_project_create(name=..., path=...)` |
| `delete_project` | `bm_project_delete` (opt-in) |
| `schema_validate`, `schema_infer`, `schema_diff` | `bm_schema(command="validate"/"infer"/"diff")` |
| `move_note` | `bm_move` |
Expand All @@ -231,9 +231,11 @@ The primary surface has twelve tools, plus the separately enabled destructive
| `bm_move` | Move a note or directory to `destination`. A trailing slash means move into that directory under the source's existing name; without it, the destination is the new path. |
| `bm_search` | Ranked text, title, permalink, semantic, or hybrid search; metadata filters; project scopes; and matching-line searches. Replaces both note search and the MCP grep workflow. |
| `bm_context` | Build graph context from an exact `identifier`, with bounded depth and time window. Whitespace-only identifiers are refused. |
| `bm_list` | List one directory level in the required project. `/` is that project's root; discover projects with `bm_project(command="list")`. |
| `bm_list` | List one directory level in the required project. `/` is that project's root; discover projects with `bm_project_list`. |
| `bm_find` | Recursive file discovery with path and frontmatter filters. Use this for inventory rather than expecting recursive `bm_list` results. |
| `bm_project` | Consolidate `list`, `create`, and `recent`. Project rows carry workspace metadata; deletion is not a command on this tool. |
| `bm_project_list` | List projects (1.0 shipped this as `bm_project(command="list")`; 1.1 splits it). Project rows carry workspace metadata. |
| `bm_project_create` | Create a project (1.0: `bm_project(command="create")`). |
| `bm_recent` | Recent changes in the required `project` (1.0: `bm_project(command="recent")`). |
| `bm_project_delete` | Separate, opt-in destructive project deletion. Keep it separate in tool permissions and agent allowlists. |
| `bm_schema` | Consolidate schema `validate`, `infer`, and `diff` behind `command`. |
| `bm_man` | Discover, read, or search installed manuals. Supply a page or a query, not both; no project selection is required. |
Expand Down Expand Up @@ -278,7 +280,7 @@ await bm_write(
project="research",
)
await bm_search(filters={"status": "active", "tags": ["planning"]}, project="research")
await bm_project(command="recent", since="7d", project="research", output_format="json")
await bm_recent(since="7d", project="research", output_format="json")
```

### Update callers and installed instructions
Expand All @@ -296,8 +298,8 @@ surface they were measured against and should not be relabeled as 1.0 results.

- Core note, folder, context and schema calls require a nonempty `project`,
including single-project sessions, constrained servers and qualified targets.
Discovery (`bm_project(command="list")`) and bundled manuals need no project.
`bm_project(command="recent")` requires `project` too.
Discovery (`bm_project_list`) and bundled manuals need no project.
`bm_recent` requires `project` too.
- Search requires explicit scope: `project` for one project, `projects` for
selected projects, or `all_projects=True` for all accessible projects. A
project-qualified path or memory URL does not replace explicit search scope.
Expand Down
8 changes: 3 additions & 5 deletions docs/ai-assistant-guide-extended.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ capture authorization; ask before saving when no authorization exists.
Discover projects when the route is unknown or the user asks for their projects:

```python
await bm_project(command="list", output_format="json")
await bm_project_list(output_format="json")
```

Pass a project name, `workspace/project`, or a returned `external_id` UUID through
Expand All @@ -33,7 +33,7 @@ a project for later calls. Note-addressing tools can refuse unqualified identifi
when several projects are available, even if a default is configured.

```python
await bm_project(command="recent", since="7d", project="main")
await bm_recent(since="7d", project="main")
await bm_search(query="authentication", project="main")
await bm_read(identifier="specs/authentication.md", project="main")
```
Expand All @@ -47,9 +47,7 @@ conclusion. The CLI `bm status` reports local indexing status; there is no MCP
Create a project when requested:

```python
await bm_project(
command="create",
name="research",
await bm_project_create(name="research",
path="/Users/name/Documents/research",
set_default=False,
)
Expand Down
4 changes: 2 additions & 2 deletions integrations/hermes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ The plugin starts `bm mcp` lazily: on the first `/bm-*` command or agent session

`Note:` lines list setup the first start will do by itself, such as installing `bm` with `uv`.

The plugin asks the running `bm mcp` whether the configured project exists (`bm_project` list). It never reads Basic Memory's config files, so `BASIC_MEMORY_CONFIG_DIR` and `XDG_CONFIG_HOME` work however `bm` itself resolves them. A missing local project is created at `project_path` on first start. A missing cloud project fails the start with the `bm project add ... --cloud` command to run.
The plugin asks the running `bm mcp` whether the configured project exists (`bm_project_list`). It never reads Basic Memory's config files, so `BASIC_MEMORY_CONFIG_DIR` and `XDG_CONFIG_HOME` work however `bm` itself resolves them. A missing local project is created at `project_path` on first start. A missing cloud project fails the start with the `bm project add ... --cloud` command to run.

### Known issue: `/bm-*` commands may not appear in some Hermes gateway builds

Expand Down Expand Up @@ -172,7 +172,7 @@ To override, write `~/.hermes/basic-memory.json` or run `hermes memory setup bas
}
```

In local mode the plugin creates the BM project on first init if Basic Memory does not list it, through `bm mcp`'s `bm_project` create. In cloud mode it doesn't — you create the cloud-routed project yourself (see below) and the plugin checks that Basic Memory lists it before initializing.
In local mode the plugin creates the BM project on first init if Basic Memory does not list it, through `bm mcp`'s `bm_project_create`. In cloud mode it doesn't — you create the cloud-routed project yourself (see below) and the plugin checks that Basic Memory lists it before initializing.

### Cloud mode

Expand Down
28 changes: 12 additions & 16 deletions integrations/hermes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ def _spawn_thread(target: Callable[[], None], *, name: str) -> threading.Thread:
"bm_context": "bm_context",
"bm_delete": "bm_delete",
"bm_move": "bm_move",
"bm_recent": "bm_project",
"bm_projects": "bm_project",
"bm_workspaces": "bm_project",
"bm_recent": "bm_recent",
"bm_projects": "bm_project_list",
"bm_workspaces": "bm_project_list",
}

# Discovery tools that operate across all projects/workspaces. They don't
Expand Down Expand Up @@ -516,14 +516,14 @@ def _config_path(hermes_home: str) -> Path:
return Path(hermes_home) / "basic-memory.json"


# `source` values in `bm_project list` rows that mean the project is registered
# `source` values in `bm_project_list` rows that mean the project is registered
# in this machine's local config. A local project sharing a permalink with cloud
# projects is attached to one of them and listed as "local+cloud".
_LOCALLY_REGISTERED_SOURCES = frozenset({"local", "local+cloud"})


def _project_listed(projects: list[dict[str, Any]], project: str, *, local: bool) -> bool:
"""True when `bm_project list` rows include `project`.
"""True when `bm_project_list` rows include `project`.

Accepts every spelling Basic Memory routes by: the name, the
workspace-qualified name, or the external id. With `local`, only rows
Expand Down Expand Up @@ -1036,7 +1036,6 @@ def _translate_args(
out["identifier"] = args["identifier"]
out["destination"] = str(args["new_folder"]).rstrip("/") + "/"
elif hermes_tool == "bm_recent":
out["command"] = "recent"
if args.get("timeframe"):
out["since"] = str(args["timeframe"])
if args.get("limit") is not None:
Expand All @@ -1046,7 +1045,6 @@ def _translate_args(
elif hermes_tool in _GLOBAL_TOOLS:
# The agent needs to parse identifiers (UUIDs, workspace slugs) out
# of the response, so request JSON regardless of BM's text default.
out["command"] = "list"
out["output_format"] = "json"
return bm_tool, out

Expand Down Expand Up @@ -1332,7 +1330,7 @@ def _ensure_project(self, actor: _BmMcpActor) -> bool:
"""
try:
listing = json.loads(
actor.call("bm_project", {"command": "list", "output_format": "json"}, timeout=60.0)
actor.call("bm_project_list", {"output_format": "json"}, timeout=60.0)
)
if "error" in listing:
return self._project_check_failed(
Expand All @@ -1355,9 +1353,8 @@ def _ensure_project(self, actor: _BmMcpActor) -> bool:
os.makedirs(self._project_path, exist_ok=True)
created = json.loads(
actor.call(
"bm_project",
"bm_project_create",
{
"command": "create",
"name": self._project,
"path": self._project_path,
"output_format": "json",
Expand Down Expand Up @@ -2237,10 +2234,9 @@ def _bm_recent(raw_args: str) -> str:
return err
try:
raw = p._call(
"bm_project",
"bm_recent",
{
"project": p._project,
"command": "recent",
"since": timeframe,
"page_size": 10,
"output_format": "json",
Expand Down Expand Up @@ -2325,8 +2321,8 @@ def _bm_project(raw_args: str) -> str:
return err
try:
raw = p._call(
"bm_project",
{"command": "list", "output_format": "json"},
"bm_project_list",
{"output_format": "json"},
timeout=15.0,
)
except Exception as e:
Expand Down Expand Up @@ -2368,8 +2364,8 @@ def _bm_workspace(raw_args: str) -> str:
)
try:
raw = p._call(
"bm_project",
{"command": "list", "output_format": "json"},
"bm_project_list",
{"output_format": "json"},
timeout=15.0,
)
except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion integrations/hermes/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The CLI is fine when you genuinely need a feature these wrappers don't expose (r

These are Hermes-native wrappers, with their own argument schemas. For example,
`bm_recent`, `bm_projects`, `bm_workspaces`, `limit`, `folder`, and `operation`
below belong to Hermes. Direct Basic Memory 1.0 MCP tools use `bm_project`,
below belong to Hermes. Direct Basic Memory MCP tools use `bm_project_list`,
`page_size`, `directory`/`identifier`, and `command` instead. Follow the active
Hermes tool schema when adapting examples from the shared memory skills.

Expand Down
Loading
Loading