Skip to content

Commit 4788436

Browse files
committed
Merge origin/main into feat-project-display-name
Signed-off-by: phernandez <paul@basicmachines.co>
2 parents bb838c9 + ed82b0c commit 4788436

23 files changed

Lines changed: 1616 additions & 331 deletions

.github/workflows/test.yml

Lines changed: 157 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,70 @@
11
name: Tests
22

3+
concurrency:
4+
group: bm-ci-${{ github.workflow }}-${{ github.repository }}-${{ github.head_ref || github.ref }}
5+
cancel-in-progress: true
6+
37
on:
48
push:
59
branches: [ "main" ]
610
pull_request:
711
branches: [ "main" ]
8-
# pull_request_target runs on the BASE of the PR, not the merge result.
9-
# It has write permissions and access to secrets.
10-
# It's useful for PRs from forks or automated PRs but requires careful use for security reasons.
11-
# See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
12-
pull_request_target:
13-
branches: [ "main" ]
1412

1513
jobs:
16-
test-sqlite:
17-
name: Test SQLite (${{ matrix.os }}, Python ${{ matrix.python-version }})
14+
static-checks:
15+
name: Static Checks (Python 3.12)
16+
timeout-minutes: 20
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
with:
22+
submodules: true
23+
24+
- name: Set up Python 3.12
25+
uses: actions/setup-python@v4
26+
with:
27+
python-version: "3.12"
28+
cache: "pip"
29+
30+
- name: Install uv
31+
run: |
32+
pip install uv
33+
34+
- uses: extractions/setup-just@v3
35+
36+
- name: Create virtual env
37+
run: |
38+
uv venv
39+
40+
- name: Install dependencies
41+
run: |
42+
uv pip install -e ".[dev]"
43+
44+
- name: Run type checks
45+
run: |
46+
just typecheck
47+
48+
- name: Run linting
49+
run: |
50+
just lint
51+
52+
test-sqlite-unit:
53+
name: Test SQLite Unit (${{ matrix.os }}, Python ${{ matrix.python-version }})
1854
timeout-minutes: 30
55+
needs: [static-checks]
1956
strategy:
2057
fail-fast: false
2158
matrix:
22-
os: [ubuntu-latest, windows-latest]
23-
python-version: [ "3.12", "3.13", "3.14" ]
59+
include:
60+
- os: ubuntu-latest
61+
python-version: "3.12"
62+
- os: ubuntu-latest
63+
python-version: "3.13"
64+
- os: ubuntu-latest
65+
python-version: "3.14"
66+
- os: windows-latest
67+
python-version: "3.12"
2468
runs-on: ${{ matrix.os }}
2569

2670
steps:
@@ -46,24 +90,63 @@ jobs:
4690
4791
- name: Install dependencies
4892
run: |
49-
uv pip install -e ".[dev,semantic]"
93+
uv pip install -e ".[dev]"
5094
51-
- name: Run type checks
95+
- name: Run tests (SQLite Unit)
5296
run: |
53-
just typecheck
97+
just test-unit-sqlite
5498
55-
- name: Run linting
99+
test-sqlite-integration:
100+
name: Test SQLite Integration (${{ matrix.os }}, Python ${{ matrix.python-version }})
101+
timeout-minutes: 45
102+
needs: [static-checks]
103+
strategy:
104+
fail-fast: false
105+
matrix:
106+
include:
107+
- os: ubuntu-latest
108+
python-version: "3.12"
109+
- os: ubuntu-latest
110+
python-version: "3.13"
111+
- os: ubuntu-latest
112+
python-version: "3.14"
113+
- os: windows-latest
114+
python-version: "3.12"
115+
runs-on: ${{ matrix.os }}
116+
117+
steps:
118+
- uses: actions/checkout@v4
119+
with:
120+
submodules: true
121+
122+
- name: Set up Python ${{ matrix.python-version }}
123+
uses: actions/setup-python@v4
124+
with:
125+
python-version: ${{ matrix.python-version }}
126+
cache: 'pip'
127+
128+
- name: Install uv
56129
run: |
57-
just lint
130+
pip install uv
58131
59-
- name: Run tests (SQLite)
132+
- uses: extractions/setup-just@v3
133+
134+
- name: Create virtual env
135+
run: |
136+
uv venv
137+
138+
- name: Install dependencies
60139
run: |
61-
uv pip install pytest pytest-cov
62-
just test-sqlite
140+
uv pip install -e ".[dev]"
63141
64-
test-postgres:
65-
name: Test Postgres (Python ${{ matrix.python-version }})
142+
- name: Run tests (SQLite Integration)
143+
run: |
144+
just test-int-sqlite
145+
146+
test-postgres-unit:
147+
name: Test Postgres Unit (Python ${{ matrix.python-version }})
66148
timeout-minutes: 30
149+
needs: [static-checks]
67150
strategy:
68151
fail-fast: false
69152
matrix:
@@ -95,16 +178,57 @@ jobs:
95178
96179
- name: Install dependencies
97180
run: |
98-
uv pip install -e ".[dev,semantic]"
181+
uv pip install -e ".[dev]"
99182
100-
- name: Run tests (Postgres via testcontainers)
183+
- name: Run tests (Postgres Unit)
101184
run: |
102-
uv pip install pytest pytest-cov
103-
just test-postgres
185+
just test-unit-postgres
186+
187+
test-postgres-integration:
188+
name: Test Postgres Integration (Python ${{ matrix.python-version }})
189+
timeout-minutes: 45
190+
needs: [static-checks]
191+
strategy:
192+
fail-fast: false
193+
matrix:
194+
python-version: [ "3.12", "3.13", "3.14" ]
195+
runs-on: ubuntu-latest
196+
197+
# Note: No services section needed - testcontainers handles Postgres in Docker
198+
199+
steps:
200+
- uses: actions/checkout@v4
201+
with:
202+
submodules: true
203+
204+
- name: Set up Python ${{ matrix.python-version }}
205+
uses: actions/setup-python@v4
206+
with:
207+
python-version: ${{ matrix.python-version }}
208+
cache: 'pip'
209+
210+
- name: Install uv
211+
run: |
212+
pip install uv
213+
214+
- uses: extractions/setup-just@v3
215+
216+
- name: Create virtual env
217+
run: |
218+
uv venv
219+
220+
- name: Install dependencies
221+
run: |
222+
uv pip install -e ".[dev]"
223+
224+
- name: Run tests (Postgres Integration)
225+
run: |
226+
just test-int-postgres
104227
105228
test-semantic:
106229
name: Test Semantic (Python 3.12)
107230
timeout-minutes: 45
231+
needs: [static-checks]
108232
runs-on: ubuntu-latest
109233

110234
steps:
@@ -134,12 +258,19 @@ jobs:
134258
135259
- name: Run tests (Semantic)
136260
run: |
137-
uv pip install pytest pytest-cov
138261
just test-semantic
139262
140263
coverage:
141264
name: Coverage Summary (combined, Python 3.12)
142-
timeout-minutes: 30
265+
timeout-minutes: 60
266+
needs:
267+
- static-checks
268+
- test-sqlite-unit
269+
- test-sqlite-integration
270+
- test-postgres-unit
271+
- test-postgres-integration
272+
- test-semantic
273+
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
143274
runs-on: ubuntu-latest
144275

145276
steps:
@@ -169,7 +300,6 @@ jobs:
169300
170301
- name: Run combined coverage (SQLite + Postgres)
171302
run: |
172-
uv pip install pytest pytest-cov
173303
just coverage
174304
175305
- name: Add coverage report to job summary

README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -418,19 +418,19 @@ basic-memory tool edit-note docs/setup --operation append --content $'\n- Added
418418

419419
**Content Management:**
420420
```
421-
write_note(title, content, folder, tags) - Create or update notes
422-
read_note(identifier, page, page_size) - Read notes by title or permalink
421+
write_note(title, content, folder, tags, output_format="text"|"json") - Create or update notes
422+
read_note(identifier, page, page_size, output_format="text"|"json") - Read notes by title or permalink
423423
read_content(path) - Read raw file content (text, images, binaries)
424424
view_note(identifier) - View notes as formatted artifacts
425-
edit_note(identifier, operation, content) - Edit notes incrementally
426-
move_note(identifier, destination_path) - Move notes with database consistency
427-
delete_note(identifier) - Delete notes from knowledge base
425+
edit_note(identifier, operation, content, output_format="text"|"json") - Edit notes incrementally
426+
move_note(identifier, destination_path, output_format="text"|"json") - Move notes with database consistency
427+
delete_note(identifier, output_format="text"|"json") - Delete notes from knowledge base
428428
```
429429

430430
**Knowledge Graph Navigation:**
431431
```
432-
build_context(url, depth, timeframe) - Navigate knowledge graph via memory:// URLs
433-
recent_activity(type, depth, timeframe) - Find recently updated information
432+
build_context(url, depth, timeframe, output_format="json"|"text") - Navigate knowledge graph via memory:// URLs
433+
recent_activity(type, depth, timeframe, output_format="text"|"json") - Find recently updated information
434434
list_directory(dir_name, depth) - Browse directory contents with filtering
435435
```
436436

@@ -443,12 +443,15 @@ search_by_metadata(filters, limit, offset, project) - Structured frontmatter sea
443443

444444
**Project Management:**
445445
```
446-
list_memory_projects() - List all available projects
447-
create_memory_project(project_name, project_path) - Create new projects
446+
list_memory_projects(output_format="text"|"json") - List all available projects
447+
create_memory_project(project_name, project_path, output_format="text"|"json") - Create new projects
448448
get_current_project() - Show current project stats
449449
sync_status() - Check synchronization status
450450
```
451451

452+
`output_format` defaults to `"text"` for these tools, preserving current human-readable responses.
453+
`build_context` defaults to `"json"` and can be switched to `"text"` when compact markdown output is preferred.
454+
452455
**Cloud Discovery (opt-in):**
453456
```
454457
cloud_info() - Show optional Cloud overview and setup guidance

docs/mcp-ui-bakeoff-instructions.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,26 @@ Manual check:
8383

8484
---
8585

86-
### 2) ASCII / ANSI TUI Output
86+
### 2) Text / JSON Output Modes
8787

8888
Tools:
89-
- `search_notes(output_format="ascii" | "ansi")`
90-
- `read_note(output_format="ascii" | "ansi")`
89+
- `search_notes(output_format="text" | "json")`
90+
- `read_note(output_format="text" | "json")`
91+
- `write_note(output_format="text" | "json")`
92+
- `edit_note(output_format="text" | "json")`
93+
- `recent_activity(output_format="text" | "json")`
94+
- `list_memory_projects(output_format="text" | "json")`
95+
- `create_memory_project(output_format="text" | "json")`
96+
- `delete_note(output_format="text" | "json")`
97+
- `move_note(output_format="text" | "json")`
98+
- `build_context(output_format="json" | "text")`
9199

92100
Expect:
93-
- ASCII table for search, header + content preview for note.
94-
- ANSI variants include color escape codes.
101+
- `text` mode preserves existing human-readable responses.
102+
- `json` mode returns structured dict/list payloads for machine-readable clients.
95103

96104
Automated:
97-
- `uv run pytest test-int/mcp/test_output_format_ascii_integration.py`
105+
- `uv run pytest test-int/mcp/test_output_format_json_integration.py`
98106

99107
---
100108

@@ -125,6 +133,6 @@ Fill in after running:
125133

126134
- Tool‑UI (React): __
127135
- MCP‑UI SDK (embedded): __
128-
- ASCII/ANSI: __
136+
- Text/JSON modes: __
129137

130138
Decision + rationale: __

docs/post-v0.18.0-test-plan.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ Key finding: **FastEmbed (384-d local ONNX) matches or exceeds OpenAI (1536-d) q
180180
### Existing coverage anchor points
181181

182182
- `tests/mcp/test_tool_contracts.py`
183-
- `test-int/mcp/test_output_format_ascii_integration.py`
183+
- `test-int/mcp/test_output_format_json_integration.py`
184184
- `test-int/mcp/test_ui_sdk_integration.py`
185185

186186
### Gaps to close — DONE
@@ -288,7 +288,7 @@ Run after automated tests pass.
288288
- Routing: verify success/failure paths with and without API key.
289289
- Permalink routing: read/write/search notes across projects with colliding titles.
290290
- Permalink routing: verify memory URL routing correctness.
291-
- UI/TUI: call `search_notes` and `read_note` with UI variants and `output_format=ascii|ansi`.
291+
- UI/TUI: call `search_notes` and `read_note` with UI variants and `output_format=text|json`.
292292
- UI/TUI: verify payload/resource format and metadata completeness.
293293

294294
## Implementation Backlog (Ordered)

src/basic_memory/cli/commands/tool.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,14 @@ async def _read_note_json(
160160
search_type="title",
161161
project=project_name,
162162
workspace=workspace,
163+
output_format="json",
163164
)
164-
if title_results and hasattr(title_results, "results") and title_results.results:
165-
result = title_results.results[0]
166-
if result.permalink:
167-
entity_id = await knowledge_client.resolve_entity(result.permalink)
165+
results = title_results.get("results", []) if isinstance(title_results, dict) else []
166+
if results:
167+
result = results[0]
168+
permalink = result.get("permalink")
169+
if permalink:
170+
entity_id = await knowledge_client.resolve_entity(permalink)
168171

169172
if entity_id is None:
170173
raise ValueError(f"Could not find note matching: {identifier}")
@@ -635,10 +638,13 @@ def build_context(
635638
page=page,
636639
page_size=page_size,
637640
max_related=max_related,
641+
output_format="text" if format == "text" else "json",
638642
)
639643
)
640-
# build_context now returns a slimmed dict (already serializable)
641-
print(json.dumps(result, indent=2, ensure_ascii=True, default=str))
644+
if format == "json":
645+
print(json.dumps(result, indent=2, ensure_ascii=True, default=str))
646+
else:
647+
print(result)
642648
except ValueError as e:
643649
typer.echo(f"Error: {e}", err=True)
644650
raise typer.Exit(1)

0 commit comments

Comments
 (0)