rename(547): the server is grimoire — package, image, DB defaults, backups
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 44s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 44s
Step 2 of the Scribe -> Grimoire rename. - src/scribe -> src/grimoire, and every import, patch target, module path, hypercorn entry point and `python -m grimoire.restore` with it; pyproject, uv.lock and the frontend package are named grimoire. - Image git.fabledsword.com/bvandeusen/fabledgrimoire; CI's Postgres service runs as grimoire / grimoire_test. - Compose files default the DB user, database and (prod) volume to grimoire, each overridable from .env, so an install created as Scribe keeps its data by setting POSTGRES_USER, POSTGRES_DB and PGDATA_VOLUME. - Backups are written as grimoire-<ts>.tar with format grimoire-backup-tar. Archives named scribe-* with the scribe-backup-tar manifest still restore and are still counted at the destination (folder and S3), with tests. - Exports, the MCP auth realm, the ASGI scope key and the persistent logger carry the new name. Kept on purpose: the secret-sealing key label stays "scribe.secrets.v1", because every stored secret was derived under it. Plugin identity (plugin name, hooks, env vars, .scribe marker, skills) is step 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,8 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from scribe.mcp._context import _user_id_ctx
|
||||
from scribe.mcp.tools.search import search
|
||||
from grimoire.mcp._context import _user_id_ctx
|
||||
from grimoire.mcp.tools.search import search
|
||||
from tests.helpers import fake_note
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ async def test_fable_search_returns_repackaged_results():
|
||||
_user_id_ctx.set(7)
|
||||
fake = fake_note(id=1, title="kafka rebalance", is_task=False, body="HPA details", tags=["ops"])
|
||||
with patch(
|
||||
"scribe.mcp.tools.search.semantic_search_notes",
|
||||
"grimoire.mcp.tools.search.semantic_search_notes",
|
||||
AsyncMock(return_value=[(0.93, fake)]),
|
||||
):
|
||||
out = await search(q="kafka")
|
||||
@@ -65,7 +65,7 @@ async def test_search_shows_the_passage_that_matched_not_the_opening():
|
||||
}
|
||||
return [(0.5, fake)]
|
||||
|
||||
with patch("scribe.mcp.tools.search.semantic_search_notes", _search):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_notes", _search):
|
||||
out = await search(q="answer")
|
||||
r = out["results"][0]
|
||||
assert r["excerpt"] == "THE ANSWER IS 04775c3."
|
||||
@@ -83,7 +83,7 @@ async def test_a_result_says_whether_its_excerpt_is_the_match_or_the_opening():
|
||||
_user_id_ctx.set(7)
|
||||
fake = fake_note(id=1, title="t", body="short body")
|
||||
with patch(
|
||||
"scribe.mcp.tools.search.semantic_search_notes",
|
||||
"grimoire.mcp.tools.search.semantic_search_notes",
|
||||
AsyncMock(return_value=[(0.5, fake)]),
|
||||
):
|
||||
out = await search(q="x")
|
||||
@@ -98,7 +98,7 @@ async def test_a_long_excerpt_keeps_both_ends_and_says_how_much_went():
|
||||
body = "OPENING. " + ("m" * 3000) + " CLOSING."
|
||||
fake = fake_note(id=1, title="t", body=body)
|
||||
with patch(
|
||||
"scribe.mcp.tools.search.semantic_search_notes",
|
||||
"grimoire.mcp.tools.search.semantic_search_notes",
|
||||
AsyncMock(return_value=[(0.5, fake)]),
|
||||
):
|
||||
out = await search(q="x")
|
||||
@@ -115,7 +115,7 @@ async def test_a_short_record_arrives_whole_and_unmarked():
|
||||
_user_id_ctx.set(7)
|
||||
fake = fake_note(id=1, title="t", body="all of it")
|
||||
with patch(
|
||||
"scribe.mcp.tools.search.semantic_search_notes",
|
||||
"grimoire.mcp.tools.search.semantic_search_notes",
|
||||
AsyncMock(return_value=[(0.5, fake)]),
|
||||
):
|
||||
out = await search(q="x")
|
||||
@@ -134,7 +134,7 @@ async def test_fable_search_content_type_filters_at_service_layer():
|
||||
that already asks this way."""
|
||||
_user_id_ctx.set(7)
|
||||
mock_search = AsyncMock(return_value=[])
|
||||
with patch("scribe.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
await search(q="x", content_type="task")
|
||||
assert mock_search.call_args.kwargs["is_task"] is True
|
||||
assert mock_search.call_args.kwargs.get("task_kind") is None
|
||||
@@ -173,7 +173,7 @@ async def test_each_specific_kind_reaches_the_engine_filter_it_names(
|
||||
for a snippet must narrow to snippets, not merely to non-tasks."""
|
||||
_user_id_ctx.set(7)
|
||||
mock_search = AsyncMock(return_value=[])
|
||||
with patch("scribe.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
await search(q="x", content_type=content_type)
|
||||
kwargs = mock_search.call_args.kwargs
|
||||
for key, value in expected.items():
|
||||
@@ -183,7 +183,7 @@ async def test_each_specific_kind_reaches_the_engine_filter_it_names(
|
||||
def test_the_vocabulary_is_derived_from_the_facet_table_not_recopied():
|
||||
"""Adding a kind to `_FACETS` is one edit, at this door too. A hand-kept
|
||||
list here lets the agent's search offer fewer kinds than the web's."""
|
||||
from scribe.services.knowledge import FACET_TYPES, content_type_filters
|
||||
from grimoire.services.knowledge import FACET_TYPES, content_type_filters
|
||||
|
||||
for facet in FACET_TYPES:
|
||||
filters = content_type_filters(facet) # raises if a kind is unreachable
|
||||
@@ -203,7 +203,7 @@ async def test_an_unknown_content_type_is_refused_rather_than_returning_nothing(
|
||||
instead of falling through to a filter that matches no row."""
|
||||
_user_id_ctx.set(7)
|
||||
mock_search = AsyncMock(return_value=[])
|
||||
with patch("scribe.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
with pytest.raises(ValueError) as err:
|
||||
await search(q="x", content_type="snippets") # plural typo
|
||||
mock_search.assert_not_awaited()
|
||||
@@ -216,8 +216,8 @@ async def test_an_unknown_content_type_is_refused_rather_than_returning_nothing(
|
||||
async def test_the_docstring_names_every_kind_the_tool_accepts():
|
||||
"""The docstring IS the agent-facing contract — a filter an agent
|
||||
has not been told about is unreachable however well it is wired."""
|
||||
from scribe.mcp.tools.search import search as search_tool
|
||||
from scribe.services.knowledge import FACET_TYPES
|
||||
from grimoire.mcp.tools.search import search as search_tool
|
||||
from grimoire.services.knowledge import FACET_TYPES
|
||||
|
||||
doc = search_tool.__doc__ or ""
|
||||
for facet in FACET_TYPES:
|
||||
@@ -242,7 +242,7 @@ async def test_an_explicit_search_reaches_a_lesson_from_any_project():
|
||||
"""
|
||||
_user_id_ctx.set(7)
|
||||
mock_search = AsyncMock(return_value=[])
|
||||
with patch("scribe.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
await search(q="x", project_id=3)
|
||||
|
||||
assert mock_search.call_args.kwargs["include_global_kinds"] is True
|
||||
@@ -252,7 +252,7 @@ async def test_an_explicit_search_reaches_a_lesson_from_any_project():
|
||||
async def test_fable_search_limit_is_clamped():
|
||||
_user_id_ctx.set(7)
|
||||
mock_search = AsyncMock(return_value=[])
|
||||
with patch("scribe.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_notes", mock_search):
|
||||
await search(q="x", limit=999)
|
||||
assert mock_search.call_args.kwargs["limit"] == 50
|
||||
|
||||
@@ -273,7 +273,7 @@ async def test_rule_search_scopes_to_the_project_it_is_given(project_id, scope):
|
||||
is asking the whole rulebook — unlike a hook, which speaks unasked."""
|
||||
_user_id_ctx.set(7)
|
||||
found = AsyncMock(return_value=[])
|
||||
with patch("scribe.mcp.tools.search.semantic_search_rules", found):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_rules", found):
|
||||
await search(q="release tagging", content_type="rule", project_id=project_id)
|
||||
kwargs = found.await_args.kwargs
|
||||
assert {k: kwargs[k] for k in scope} == scope
|
||||
@@ -281,7 +281,7 @@ async def test_rule_search_scopes_to_the_project_it_is_given(project_id, scope):
|
||||
|
||||
|
||||
def test_a_milestone_is_embedded_by_what_it_is_for_then_its_plan():
|
||||
from scribe.services.embeddings import milestone_document
|
||||
from grimoire.services.embeddings import milestone_document
|
||||
|
||||
assert milestone_document("M3", "metadata providers", "## Goal\nx") == (
|
||||
"M3 — metadata providers", "metadata providers\n\n## Goal\nx")
|
||||
@@ -313,9 +313,9 @@ async def test_milestone_search_is_its_own_shape_and_scopes_to_the_project():
|
||||
}
|
||||
return [(0.81, ms)]
|
||||
|
||||
with patch("scribe.mcp.tools.search.semantic_search_milestones",
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_milestones",
|
||||
_milestone_search), \
|
||||
patch("scribe.services.milestones.get_project_milestone_summary", summary):
|
||||
patch("grimoire.services.milestones.get_project_milestone_summary", summary):
|
||||
out = await search(q="book metadata", content_type="milestone", project_id=30)
|
||||
assert seen["project_id"] == 30
|
||||
assert out["results"] == [{
|
||||
@@ -356,7 +356,7 @@ async def test_system_search_is_its_own_shape_and_carries_the_matched_passage():
|
||||
}
|
||||
return [(0.81, fake)]
|
||||
|
||||
with patch("scribe.mcp.tools.search.semantic_search_systems", _system_search):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_systems", _system_search):
|
||||
out = await search(q="where does retrieval telemetry go?",
|
||||
content_type="system", project_id=2)
|
||||
|
||||
@@ -383,12 +383,12 @@ async def test_system_search_scopes_to_the_project_it_was_given():
|
||||
answer to "where does this belong here?"."""
|
||||
_user_id_ctx.set(7)
|
||||
mock = AsyncMock(return_value=[])
|
||||
with patch("scribe.mcp.tools.search.semantic_search_systems", mock):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_systems", mock):
|
||||
await search(q="x", content_type="system", project_id=30)
|
||||
assert mock.call_args.kwargs["project_id"] == 30
|
||||
|
||||
mock.reset_mock()
|
||||
with patch("scribe.mcp.tools.search.semantic_search_systems", mock):
|
||||
with patch("grimoire.mcp.tools.search.semantic_search_systems", mock):
|
||||
await search(q="x", content_type="system")
|
||||
assert mock.call_args.kwargs["project_id"] is None
|
||||
|
||||
@@ -400,8 +400,8 @@ async def test_a_system_search_does_not_go_through_the_note_search():
|
||||
_user_id_ctx.set(7)
|
||||
notes = AsyncMock(return_value=[])
|
||||
with (
|
||||
patch("scribe.mcp.tools.search.semantic_search_notes", notes),
|
||||
patch("scribe.mcp.tools.search.semantic_search_systems", AsyncMock(return_value=[])),
|
||||
patch("grimoire.mcp.tools.search.semantic_search_notes", notes),
|
||||
patch("grimoire.mcp.tools.search.semantic_search_systems", AsyncMock(return_value=[])),
|
||||
):
|
||||
await search(q="x", content_type="system")
|
||||
notes.assert_not_awaited()
|
||||
|
||||
Reference in New Issue
Block a user