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:
@@ -16,7 +16,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from scribe.services.coverage import (
|
||||
from grimoire.services.coverage import (
|
||||
ArchiveScan,
|
||||
class_references,
|
||||
coverage_line,
|
||||
@@ -26,7 +26,7 @@ from scribe.services.coverage import (
|
||||
scannable,
|
||||
shapes_from_archive,
|
||||
)
|
||||
from scribe.services.shape_ledger import location_covers
|
||||
from grimoire.services.shape_ledger import location_covers
|
||||
from tests.helpers import ensure_user, forge_tarball
|
||||
|
||||
PLUGIN = Path(__file__).resolve().parents[1] / "plugin"
|
||||
@@ -339,7 +339,7 @@ def test_type_import_specifiers_are_not_definitions():
|
||||
"""`import { type Foo, bar }` is the same two words as `type Foo =`
|
||||
and defines nothing; only a `type` line with a declaration after the
|
||||
name counts (TS alias, Go/Rust type)."""
|
||||
from scribe.services.coverage import extract_shapes
|
||||
from grimoire.services.coverage import extract_shapes
|
||||
src = (
|
||||
'import { type DesignSystem, fetchDesignSystems } from "@/api/designSystems";\n'
|
||||
'import { type Project } from "./x";\n'
|
||||
@@ -371,13 +371,13 @@ def test_coverage_line_is_evidence_carrying_and_labeled_estimate():
|
||||
|
||||
def test_bind_repo_tool_takes_a_ref():
|
||||
"""The binding names the branch the ledger follows."""
|
||||
from scribe.mcp.server import build_mcp_server
|
||||
from grimoire.mcp.server import build_mcp_server
|
||||
tool = build_mcp_server()._tool_manager.get_tool("bind_repo")
|
||||
assert "ref" in tool.parameters.get("properties", {})
|
||||
|
||||
|
||||
def test_coverage_routes_are_registered():
|
||||
from scribe.app import create_app
|
||||
from grimoire.app import create_app
|
||||
|
||||
rules = {r.rule for r in create_app().url_map.iter_rules()}
|
||||
assert "/api/projects/<int:project_id>/coverage" in rules
|
||||
@@ -390,7 +390,7 @@ def test_coverage_routes_are_registered():
|
||||
def _forge(tar_bytes: bytes):
|
||||
import httpx
|
||||
|
||||
from scribe.services.forge import GiteaForge
|
||||
from grimoire.services.forge import GiteaForge
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
path = request.url.path
|
||||
@@ -409,7 +409,7 @@ def _forge(tar_bytes: bytes):
|
||||
def _selector(tar_bytes: bytes):
|
||||
"""The keyring shape compute_coverage consumes — one owner
|
||||
keyring holding the mocked Gitea adapter."""
|
||||
from scribe.services.forge import ForgeSelector
|
||||
from grimoire.services.forge import ForgeSelector
|
||||
|
||||
return ForgeSelector((_forge(tar_bytes),))
|
||||
|
||||
@@ -417,10 +417,10 @@ def _selector(tar_bytes: bytes):
|
||||
@pytest_asyncio.fixture
|
||||
async def seeded(_dispose_engine):
|
||||
"""User + project + binding + two snippets that cover 2 of TREE's 4 shapes."""
|
||||
from scribe.models import async_session
|
||||
from scribe.models.project import Project
|
||||
from scribe.services import snippets as svc
|
||||
from scribe.services.repo_bindings import set_binding
|
||||
from grimoire.models import async_session
|
||||
from grimoire.models.project import Project
|
||||
from grimoire.services import snippets as svc
|
||||
from grimoire.services.repo_bindings import set_binding
|
||||
|
||||
async with async_session() as s:
|
||||
user = await ensure_user(s, "coverage_itest")
|
||||
@@ -447,7 +447,7 @@ async def seeded(_dispose_engine):
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_coverage_measures_the_tree_exactly_and_caches(seeded):
|
||||
from scribe.services.coverage import (
|
||||
from grimoire.services.coverage import (
|
||||
cached_coverage,
|
||||
compute_coverage,
|
||||
refresh_coverage,
|
||||
@@ -455,8 +455,8 @@ async def test_coverage_measures_the_tree_exactly_and_caches(seeded):
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from scribe.models import async_session
|
||||
from scribe.models.code_shape import CodeShape
|
||||
from grimoire.models import async_session
|
||||
from grimoire.models.code_shape import CodeShape
|
||||
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
selector = _selector(_tarball(TREE))
|
||||
@@ -518,9 +518,9 @@ async def test_ledger_keeps_judgments_and_stamps_vanished_shapes(seeded):
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from scribe.models import async_session
|
||||
from scribe.models.code_shape import CodeShape
|
||||
from scribe.services.coverage import compute_coverage
|
||||
from grimoire.models import async_session
|
||||
from grimoire.models.code_shape import CodeShape
|
||||
from grimoire.services.coverage import compute_coverage
|
||||
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
await compute_coverage(uid, pid, selector=_selector(_tarball(TREE)))
|
||||
@@ -562,9 +562,9 @@ async def test_ledger_keeps_judgments_and_stamps_vanished_shapes(seeded):
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_enter_project_surfaces_the_line_only_once_computed(seeded):
|
||||
from scribe.mcp._context import _user_id_ctx
|
||||
from scribe.mcp.tools.projects import enter_project
|
||||
from scribe.services.coverage import refresh_coverage
|
||||
from grimoire.mcp._context import _user_id_ctx
|
||||
from grimoire.mcp.tools.projects import enter_project
|
||||
from grimoire.services.coverage import refresh_coverage
|
||||
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
token = _user_id_ctx.set(uid)
|
||||
@@ -590,8 +590,8 @@ async def test_enter_project_surfaces_the_line_only_once_computed(seeded):
|
||||
async def test_explicit_refresh_names_its_failures(seeded):
|
||||
"""refresh_for_caller raises fixable errors instead of silence: an agent
|
||||
mid-task must learn WHY nothing measured, and 'None' would strand it."""
|
||||
from scribe.models import async_session
|
||||
from scribe.services.coverage import refresh_for_caller
|
||||
from grimoire.models import async_session
|
||||
from grimoire.services.coverage import refresh_for_caller
|
||||
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
# The owner has no forge connection rows → the error names the fix.
|
||||
@@ -613,7 +613,7 @@ async def test_explicit_refresh_names_its_failures(seeded):
|
||||
async def test_background_seed_is_quiet_without_a_forge(seeded):
|
||||
"""refresh_if_stale must exit silently for a forge-less owner — the
|
||||
baseline install — and treat a fresh cache as nothing-to-do."""
|
||||
from scribe.services.coverage import refresh_coverage, refresh_if_stale
|
||||
from grimoire.services.coverage import refresh_coverage, refresh_if_stale
|
||||
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
# Absent cache + no forge rows: returns without raising, writes nothing.
|
||||
@@ -627,11 +627,11 @@ async def test_background_seed_is_quiet_without_a_forge(seeded):
|
||||
async def test_unservable_binding_measures_nothing(seeded):
|
||||
"""A project bound only to a host the forge doesn't serve returns None —
|
||||
the same silence as no forge at all, never an error."""
|
||||
from scribe.services.coverage import compute_coverage
|
||||
from scribe.services.repo_bindings import set_binding
|
||||
from grimoire.services.coverage import compute_coverage
|
||||
from grimoire.services.repo_bindings import set_binding
|
||||
|
||||
from scribe.models import async_session
|
||||
from scribe.models.project import Project
|
||||
from grimoire.models import async_session
|
||||
from grimoire.models.project import Project
|
||||
|
||||
uid = seeded["uid"]
|
||||
async with async_session() as s:
|
||||
@@ -652,7 +652,7 @@ def test_extract_definitions_fingerprints_each_block():
|
||||
"""The block rule across the language families the extractor knows: a
|
||||
Python def ends at the next top-level statement, a braces/CSS block keeps
|
||||
its closer, and comments/decorators don't move the hash."""
|
||||
from scribe.services.coverage import extract_definitions
|
||||
from grimoire.services.coverage import extract_definitions
|
||||
|
||||
text = (
|
||||
"import os\n\n"
|
||||
@@ -676,7 +676,7 @@ def test_extract_definitions_fingerprints_each_block():
|
||||
changed = {d.name: d for d in extract_definitions(text.replace("x + 1", "x + 2"))}
|
||||
assert changed["a"].body_sha != defs["a"].body_sha
|
||||
# And the identity view is unchanged for the hook mirror.
|
||||
from scribe.services.coverage import extract_shapes
|
||||
from grimoire.services.coverage import extract_shapes
|
||||
assert extract_shapes(text) == [(d.kind, d.name) for d in extract_definitions(text)]
|
||||
# A CSS rule's fingerprint is its declarations: the same body
|
||||
# under another selector is the same shape to the derive grouping.
|
||||
@@ -699,7 +699,7 @@ def test_extract_definitions_fingerprints_each_block():
|
||||
|
||||
|
||||
def test_coverage_line_names_the_proposers_standing():
|
||||
from scribe.services.coverage import coverage_line
|
||||
from grimoire.services.coverage import coverage_line
|
||||
|
||||
base = {
|
||||
"total": 100, "accounted": 10, "unclassified": 90,
|
||||
@@ -736,7 +736,7 @@ def test_coverage_line_shows_standing_work_even_with_nothing_unclassified():
|
||||
still carry derive groups / proposals / divergence — the line names
|
||||
them as `standing:` instead of hiding them behind the todo count, and
|
||||
names the drift since the previous refresh first-copy-first."""
|
||||
from scribe.services.coverage import coverage_line
|
||||
from grimoire.services.coverage import coverage_line
|
||||
|
||||
base = {
|
||||
"total": 4693, "accounted": 4693, "unclassified": 0,
|
||||
@@ -767,7 +767,7 @@ def test_coverage_line_shows_standing_work_even_with_nothing_unclassified():
|
||||
|
||||
|
||||
def test_coverage_line_names_divergence_and_recheck():
|
||||
from scribe.services.coverage import coverage_line
|
||||
from grimoire.services.coverage import coverage_line
|
||||
|
||||
base = {
|
||||
"total": 100, "accounted": 40, "unclassified": 60,
|
||||
@@ -786,7 +786,7 @@ def test_scoped_definitions_are_vue_script_setup_and_scoped_style_only():
|
||||
"""One-offs by construction — every sym in a .vue and every css
|
||||
rule inside <style scoped>; an unscoped <style> block and non-.vue files
|
||||
stay ordinary."""
|
||||
from scribe.services.coverage import extract_definitions, scoped_definitions
|
||||
from grimoire.services.coverage import extract_definitions, scoped_definitions
|
||||
vue = (
|
||||
"<script setup lang=\"ts\">\n"
|
||||
"function load() {\n return 1;\n}\n"
|
||||
@@ -814,7 +814,7 @@ def test_svelte_scopes_by_default_and_its_module_script_stays_importable():
|
||||
component's own unless it opts out, and only <script module> /
|
||||
<script context="module"> can be imported elsewhere. Without this a
|
||||
Svelte project's every `.card` and `.title` sits in the human todo."""
|
||||
from scribe.services.coverage import extract_definitions, scoped_definitions
|
||||
from grimoire.services.coverage import extract_definitions, scoped_definitions
|
||||
svelte = (
|
||||
"<script module lang=\"ts\">\n"
|
||||
"export function stageOf(x: number) {\n return x;\n}\n"
|
||||
@@ -843,7 +843,7 @@ def test_svelte_scopes_by_default_and_its_module_script_stays_importable():
|
||||
def test_the_line_names_the_review_queue_and_the_tool_that_holds_it():
|
||||
"""`stamps_to_review` goes uncalled unless something on arrival says
|
||||
there is anything to review."""
|
||||
from scribe.services.coverage import coverage_line
|
||||
from grimoire.services.coverage import coverage_line
|
||||
base = {"accounted": 10, "total": 10, "counts": {"exempt": 10},
|
||||
"unclassified": 0, "computed_at": "2026-09-30T00:00:00"}
|
||||
assert "stamps_to_review" not in coverage_line(base)
|
||||
@@ -856,8 +856,8 @@ def test_the_line_names_the_review_queue_and_the_tool_that_holds_it():
|
||||
async def test_binding_ref_is_the_branch_the_ledger_follows(seeded):
|
||||
"""A binding that names a ref is read at that ref (not the forge's
|
||||
default branch); "" clears it; None on a re-bind leaves it standing."""
|
||||
from scribe.services.coverage import compute_coverage
|
||||
from scribe.services.repo_bindings import bindings_for_project, set_binding
|
||||
from grimoire.services.coverage import compute_coverage
|
||||
from grimoire.services.repo_bindings import bindings_for_project, set_binding
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
b = await set_binding(uid, "https://git.example.com/alice/widget.git", pid, "dev")
|
||||
assert b.ref == "dev"
|
||||
@@ -889,7 +889,7 @@ async def test_binding_ref_is_the_branch_the_ledger_follows(seeded):
|
||||
])
|
||||
def test_a_file_is_a_unit_when_it_renders_and_nothing_inside_carries_its_name(path, text, unit):
|
||||
"""One structural test, not a framework list."""
|
||||
from scribe.services.coverage import class_references, extract_definitions, is_file_unit
|
||||
from grimoire.services.coverage import class_references, extract_definitions, is_file_unit
|
||||
|
||||
assert is_file_unit(path, text, extract_definitions(text), class_references(path, text)) is unit
|
||||
|
||||
@@ -897,7 +897,7 @@ def test_a_file_is_a_unit_when_it_renders_and_nothing_inside_carries_its_name(pa
|
||||
def test_the_line_says_whether_writers_judged_what_they_wrote():
|
||||
"""The write-time figure — silent until the question has
|
||||
been put, then turns checked, asked, and asks walked past."""
|
||||
from scribe.services.coverage import coverage_line
|
||||
from grimoire.services.coverage import coverage_line
|
||||
base = {"accounted": 10, "total": 10, "counts": {"exempt": 10},
|
||||
"unclassified": 0, "computed_at": "2026-10-01T00:00:00"}
|
||||
assert "written-shape check" not in coverage_line(base)
|
||||
|
||||
Reference in New Issue
Block a user