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

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:
2026-10-22 10:50:45 -04:00
co-authored by Claude Opus 5.5
parent e99f1169f7
commit 0f5ef0be63
447 changed files with 3611 additions and 3558 deletions
+65 -65
View File
@@ -28,8 +28,8 @@ def _stored_settings(stored):
in an integration job is worse: the test would pass or fail on whatever the
instance happened to have stored.
"""
from scribe.services import plugin_context as pc
from scribe.services import retrieval_surfaces as rs
from grimoire.services import plugin_context as pc
from grimoire.services import retrieval_surfaces as rs
def _side(uid, k, d=""):
return stored.get(k, d)
@@ -80,7 +80,7 @@ REAL_CODE = '''def debounce(fn, wait=0.25):
@pytest.mark.asyncio
async def test_disabled_returns_empty_and_touches_nothing():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search, listing = AsyncMock(), AsyncMock()
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg(enabled=False))), \
patch.object(pc.snippets_svc, "list_snippets", listing), \
@@ -94,7 +94,7 @@ async def test_disabled_returns_empty_and_touches_nothing():
@pytest.mark.asyncio
async def test_no_path_returns_empty():
"""Nothing to look up by place, and the semantic arm alone isn't this feature."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock()
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc, "semantic_search_notes", search):
@@ -105,7 +105,7 @@ async def test_no_path_returns_empty():
@pytest.mark.asyncio
async def test_nothing_recorded_is_silent():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
patch.object(pc, "semantic_search_notes", AsyncMock(return_value=[])), \
@@ -122,7 +122,7 @@ async def test_snippet_at_this_path_becomes_the_sync_nudge():
the record of the file being edited. It surfaces without a score,
framed as "updating the record is part of this edit", and is reported in
sync_note_ids so the hook feeds the sync dedup channel."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets",
AsyncMock(return_value=([_snippet_item(12, "debounce — rate-limit a callback")], 1))), \
@@ -145,7 +145,7 @@ async def test_reuse_dedup_never_suppresses_the_sync_nudge():
and must not silence "you are editing the recorded
file right now", the one claim that must fire at the moment of change. The
sync class dedups only against its own channel."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock(return_value=[])
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets",
@@ -165,7 +165,7 @@ async def test_sync_dedup_silences_a_sync_nudge_already_shown():
"""Once the session has been told "snippet #12 records this file", every
further edit of the same file stays quiet about it — on every arm: the
directory query and the semantic arm would both re-surface it otherwise."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
async def _listing(uid, **kw):
# Recorded at the exact file, so it matches the file AND dir queries.
@@ -186,7 +186,7 @@ async def test_sync_dedup_silences_a_sync_nudge_already_shown():
@pytest.mark.asyncio
async def test_directory_is_only_consulted_when_the_file_leaves_room():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
calls = []
async def _listing(uid, **kw):
@@ -208,7 +208,7 @@ async def test_directory_is_only_consulted_when_the_file_leaves_room():
@pytest.mark.asyncio
async def test_directory_hits_are_marked_nearby_not_here():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
async def _listing(uid, **kw):
if kw["path"] == "src/lib":
@@ -227,7 +227,7 @@ async def test_directory_hits_are_marked_nearby_not_here():
@pytest.mark.asyncio
async def test_a_failing_location_lookup_does_not_sink_the_hint():
"""The place arm is best-effort — a broken query must not cost the semantic one."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(side_effect=RuntimeError("boom"))), \
patch.object(pc, "semantic_search_notes",
@@ -242,7 +242,7 @@ async def test_a_failing_location_lookup_does_not_sink_the_hint():
@pytest.mark.asyncio
async def test_semantic_arm_is_snippet_only_and_browse_scoped():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock(return_value=[])
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
@@ -263,7 +263,7 @@ async def test_semantic_arm_is_snippet_only_and_browse_scoped():
@pytest.mark.asyncio
async def test_margin_gate_applies_to_the_semantic_arm():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
hits = [(0.80, fake_note(id=11, title="near", user_id=1, note_type="snippet")), (0.74, fake_note(id=22, title="alsoNear", user_id=1, note_type="snippet")), (0.61, fake_note(id=33, title="far", user_id=1, note_type="snippet"))]
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg(top_k=5))), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
@@ -278,7 +278,7 @@ async def test_margin_gate_applies_to_the_semantic_arm():
@pytest.mark.asyncio
async def test_no_code_means_no_semantic_arm():
"""An Edit whose payload we couldn't read still gets the location answer."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock()
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets",
@@ -293,7 +293,7 @@ async def test_no_code_means_no_semantic_arm():
@pytest.mark.asyncio
async def test_place_beats_meaning_and_the_cap_covers_both_arms():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
async def _listing(uid, **kw):
if kw["path"] == "src/x.py":
@@ -315,7 +315,7 @@ async def test_place_beats_meaning_and_the_cap_covers_both_arms():
@pytest.mark.asyncio
async def test_semantic_arm_only_asks_for_the_budget_the_place_arm_left():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock(return_value=[])
async def _listing(uid, **kw):
@@ -348,7 +348,7 @@ async def test_session_dedup_marks_the_reuse_arms_rather_than_silencing_them():
make the marker read as a complete account of what the session has met,
when it would only cover half the lines.
"""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
async def _listing(uid, **kw):
# Recorded at a SIBLING file, so it reaches only the directory query.
@@ -379,7 +379,7 @@ async def test_session_dedup_marks_the_reuse_arms_rather_than_silencing_them():
@pytest.mark.asyncio
async def test_another_users_snippet_is_attributed():
"""An unattributed line reads as your own record."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets",
AsyncMock(return_value=([_snippet_item(12, "theirs", user_id=42)], 1))), \
@@ -393,7 +393,7 @@ async def test_another_users_snippet_is_attributed():
@pytest.mark.asyncio
async def test_telemetry_uses_its_own_source():
"""Separate source is what lets this surface be tuned apart from auto_inject."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
rec = MagicMock()
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
@@ -421,7 +421,7 @@ async def test_sync_surfacing_is_measured_under_its_own_usage_source():
"""The sync class's pull-through rate is its scoreboard — whether
sessions actually update the record when told they're editing it. Folding
it into write_path_place would make that unmeasurable."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
async def _listing(uid, **kw):
if kw["path"] == "src/x.py":
@@ -445,7 +445,7 @@ async def test_sync_surfacing_is_measured_under_its_own_usage_source():
@pytest.mark.asyncio
async def test_config_has_its_own_switch_threshold_and_inherited_budget():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
stored = {pc.WRITEPATH_ENABLED_KEY: "false"}
with _stored_settings(stored):
cfg = await pc.get_writepath_config(1)
@@ -469,7 +469,7 @@ async def test_config_has_its_own_switch_threshold_and_inherited_budget():
async def test_writepath_threshold_is_operator_tunable_and_clamped():
"""It's a product knob, so it has to be settable, and a typo in
the settings box must not be able to open the gate to everything."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
async def _cfg_with(raw):
stored = {pc.WRITEPATH_THRESHOLD_KEY: raw}
@@ -493,7 +493,7 @@ async def test_the_rule_arm_has_its_own_tunable_bar():
against note PROSE, and rules are short imperative technical
English — a more homogeneous corpus whose noise floor sits higher.
"""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
async def _cfg_with(raw):
stored = {pc.RULEHINT_THRESHOLD_KEY: raw}
@@ -513,7 +513,7 @@ async def test_the_two_write_path_bars_are_independent():
"""The split, asserted. Setting one must not move the other — the failure
that would silently undo this step is a config assembler that reads one key
into both fields."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
stored = {pc.WRITEPATH_THRESHOLD_KEY: "0.90", pc.RULEHINT_THRESHOLD_KEY: "0.61"}
with _stored_settings(stored):
@@ -532,7 +532,7 @@ async def test_the_two_act_arms_read_independent_rule_bars():
reads one key into both fields would silently undo that, and the symptom
would be invisible: both arms would simply agree again.
"""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
stored = {pc.RULEHINT_THRESHOLD_KEY: "0.75", pc.TOOLRULE_THRESHOLD_KEY: "0.61"}
with _stored_settings(stored):
@@ -551,7 +551,7 @@ async def test_a_garbage_command_bar_falls_back_to_its_own_default():
that are kept separate, which is the harder failure to see because the arm
keeps working.
"""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
stored = {pc.TOOLRULE_THRESHOLD_KEY: "banana"}
with _stored_settings(stored):
@@ -569,7 +569,7 @@ def test_the_command_bar_defaults_below_the_write_path_bar():
commands retrieve nothing. Tuning either value stays free; inverting the
relationship would mute the command arm.
"""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
assert pc.TOOLRULE_DEFAULT_THRESHOLD < pc.RULEHINT_DEFAULT_THRESHOLD
@@ -587,7 +587,7 @@ def test_the_rule_bar_defaults_above_the_code_bar():
Pinned as an inequality so tuning the value stays free while inverting the
relationship — which would silently drown the arm in noise — does not.
"""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
assert pc.RULEHINT_DEFAULT_THRESHOLD > pc.WRITEPATH_DEFAULT_THRESHOLD
@@ -608,8 +608,8 @@ def test_the_rule_arm_asks_for_a_set_and_lets_the_band_narrow_it():
return several, and that rules — which measured FLATTER than notes, not
sharper — are narrowed harder than the notes menu is.
"""
from scribe.services import plugin_context as pc
from scribe.services import retrieval_pipeline as rp
from grimoire.services import plugin_context as pc
from grimoire.services import retrieval_pipeline as rp
assert pc.RULEHINT_LIMIT > 1
assert 0 < pc._RULEHINT_BAND < rp._NOTE_BAND
@@ -623,7 +623,7 @@ async def test_trivial_payload_skips_the_semantic_arm_entirely():
floor for "some code", not a resemblance. A payload too small to
carry meaning must not reach the embedder at all, and must not cost a
retrieval_logs row either."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search, rec = AsyncMock(return_value=[]), MagicMock()
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
@@ -639,7 +639,7 @@ async def test_trivial_payload_skips_the_semantic_arm_entirely():
async def test_the_floor_counts_substance_not_indentation():
"""Code is indentation-heavy, so raw length would let a deeply-nested
one-liner through on padding alone."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
padded = " " * 400 + "return None" # long, but almost no substance
search = AsyncMock(return_value=[])
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
@@ -655,7 +655,7 @@ async def test_the_floor_counts_substance_not_indentation():
async def test_the_floor_does_not_block_the_place_arm():
"""The floor is a statement about the semantic query, not about the edit. A
snippet recorded at this exact path is prior art however small the change."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock(return_value=[])
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets",
@@ -672,7 +672,7 @@ async def test_the_floor_does_not_block_the_place_arm():
async def test_a_real_helper_clears_the_floor():
"""The floor errs toward keeping recall — anything that plausibly IS a
reusable helper has to get through, or the feature stops working."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock(return_value=[(0.80, fake_note(id=5, title="debounce — …", user_id=1, note_type="snippet"))])
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
@@ -687,7 +687,7 @@ async def test_a_real_helper_clears_the_floor():
def test_the_floor_stays_below_the_smallest_plausible_helper():
"""A guard on the constant itself: raising it far enough to reject a real
one-line helper would silently gut the arm rather than sharpen it."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
smallest_real_helper = 'def slug(t): return re.sub(r"[^a-z0-9]+", "-", t.lower()).strip("-")'
substance = len("".join(smallest_real_helper.split()))
assert pc.WRITEPATH_MIN_CODE_CHARS < substance
@@ -702,7 +702,7 @@ def test_concept_query_prefers_the_name_and_docstring():
title and the body — so a concept query out-scores the code itself (0.823 vs
0.743 against a recorded snippet). The shape mirrors a snippet's own
title."""
from scribe.services.plugin_context import concept_query
from grimoire.services.plugin_context import concept_query
q = concept_query(
'def collapse_into_clusters(edges):\n'
' """Collapse similar-pairs into connected components."""\n'
@@ -714,7 +714,7 @@ def test_concept_query_prefers_the_name_and_docstring():
def test_concept_query_reads_jsdoc_and_the_const_arrow_form():
"""The dominant shape in this repo's frontend isn't `function foo()`."""
from scribe.services.plugin_context import concept_query
from grimoire.services.plugin_context import concept_query
q = concept_query(
"/**\n"
" * Rate-limit a callback so it fires once after the last call.\n"
@@ -728,7 +728,7 @@ def test_concept_query_reads_jsdoc_and_the_const_arrow_form():
def test_concept_query_does_not_let_a_shebang_become_the_description():
"""`#!` matches the leading-comment pattern but says nothing about the code."""
from scribe.services.plugin_context import concept_query
from grimoire.services.plugin_context import concept_query
q = concept_query(
"#!/usr/bin/env bash\n"
"# Resolve the repo remote and url-encode it for the plugin API.\n"
@@ -745,7 +745,7 @@ def test_concept_query_falls_back_when_there_is_no_doc():
a recorded snippet where the full code body scored 0.743 — same separation from the noise
floor (0.113), but below the 0.68 bar, so preferring the bare name would turn
a comfortable hit into a miss. Undocumented code keeps the raw payload."""
from scribe.services.plugin_context import concept_query
from grimoire.services.plugin_context import concept_query
assert concept_query(
"def collapse_into_clusters(edges):\n"
" parent = {}\n"
@@ -754,14 +754,14 @@ def test_concept_query_falls_back_when_there_is_no_doc():
def test_concept_query_falls_back_on_a_doc_that_says_nothing():
from scribe.services.plugin_context import concept_query
from grimoire.services.plugin_context import concept_query
assert concept_query('def f(x):\n """TODO"""\n return x\n') == ""
def test_concept_query_falls_back_when_nothing_parses():
"""A Vue SFC has neither a recognised declaration nor a doc block. Every
unhandled language must degrade to querying by the raw payload."""
from scribe.services.plugin_context import concept_query
from grimoire.services.plugin_context import concept_query
assert concept_query(
'<script setup lang="ts">\n'
"const props = defineProps<{ modelValue: string }>()\n"
@@ -771,7 +771,7 @@ def test_concept_query_falls_back_when_nothing_parses():
def test_concept_query_accepts_a_doc_without_a_declaration():
"""A module docstring is already the concept; it needs no signature."""
from scribe.services.plugin_context import concept_query
from grimoire.services.plugin_context import concept_query
q = concept_query('"""Turn a raw git remote into the project it belongs to."""\nMAPPING = {}\n')
assert q == "Turn a raw git remote into the project it belongs to."
@@ -779,7 +779,7 @@ def test_concept_query_accepts_a_doc_without_a_declaration():
def test_concept_query_caps_what_it_collects():
"""A 40-function Write must not become a wall of signatures, and a long module
docstring must not drown out the declaration."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
many = '"""Utilities."""\n' + "".join(
f"def helper_{i}(a, b):\n return a\n" for i in range(12)
)
@@ -792,7 +792,7 @@ def test_concept_query_caps_what_it_collects():
@pytest.mark.asyncio
async def test_semantic_arm_queries_the_concept_not_the_raw_payload():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock(return_value=[])
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
@@ -809,7 +809,7 @@ async def test_semantic_arm_queries_the_concept_not_the_raw_payload():
async def test_telemetry_records_the_query_actually_sent():
"""retrieval_logs is what the threshold gets tuned from, so it has to hold the
concept query — logging the raw code would make the scores uninterpretable."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
rec = MagicMock()
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
@@ -827,7 +827,7 @@ async def test_the_floor_judges_the_raw_payload_not_the_concept():
RAW payload; the rewrite happens after. A concept query is allowed to be
shorter than the floor — that's the whole point, since the best queries are
short — but a sub-floor payload must stay silent even when it has a docstring."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
# Clears the floor, and its concept is deliberately SHORTER than the floor.
verbose = (
@@ -861,7 +861,7 @@ async def test_the_floor_judges_the_raw_payload_not_the_concept():
# --- cross-language prior art is disclosed, not filtered ----------------------
def test_language_for_path_and_canonicalisation():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
assert pc._language_for_path("src/a/b.ts") == "typescript"
assert pc._language_for_path("src/a/b.py") == "python"
assert pc._language_for_path("ops/Dockerfile") == "dockerfile"
@@ -876,7 +876,7 @@ def test_language_for_path_and_canonicalisation():
def test_foreign_language_only_claims_a_mismatch_it_can_establish():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
assert pc._foreign_language({"language": "python"}, "typescript") == "python"
assert pc._foreign_language({"language": "py"}, "python") == "" # same, aliased
assert pc._foreign_language({"language": ""}, "typescript") == "" # theirs unknown
@@ -888,7 +888,7 @@ def test_foreign_language_only_claims_a_mismatch_it_can_establish():
async def test_a_cross_language_hit_is_labelled_and_explained():
"""The fail state this closes: an unlabelled Python hit offered while writing
TypeScript is either dismissed as irrelevant or pasted into the .ts file."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
note = fake_note(id=7, title="group_pairs — collapse related pairs into groups", user_id=1, note_type="snippet")
note.data = {"language": "python"}
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
@@ -908,7 +908,7 @@ async def test_a_cross_language_hit_is_labelled_and_explained():
async def test_a_same_language_hit_is_not_labelled_and_gets_no_preamble():
"""The common case keeps a clean line; the explanation only appears when
there is something on the menu it explains."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
note = fake_note(id=7, title="debounce — rate-limit a callback", user_id=1, note_type="snippet")
note.data = {"language": "python"}
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
@@ -916,7 +916,7 @@ async def test_a_same_language_hit_is_not_labelled_and_gets_no_preamble():
patch.object(pc, "semantic_search_notes", AsyncMock(return_value=[(0.72, note)])), \
patch.object(pc, "record_retrieval", MagicMock()), \
patch.object(pc, "owner_names_for", AsyncMock(return_value={})):
out = await pc.build_write_path_hint(1, "src/scribe/services/x.py", code=REAL_CODE)
out = await pc.build_write_path_hint(1, "src/grimoire/services/x.py", code=REAL_CODE)
assert "[similar 0.72]" in out["context"]
assert "·" not in out["context"]
assert "shape of a solution" not in out["context"]
@@ -925,7 +925,7 @@ async def test_a_same_language_hit_is_not_labelled_and_gets_no_preamble():
@pytest.mark.asyncio
async def test_an_unknown_target_extension_never_invents_a_mismatch():
"""A wrong "· python" tag is worse than no tag at all."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
note = fake_note(id=7, title="helper — does a thing", user_id=1, note_type="snippet")
note.data = {"language": "python"}
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
@@ -938,7 +938,7 @@ async def test_an_unknown_target_extension_never_invents_a_mismatch():
def test_prior_art_line_keeps_language_and_attribution_together():
from scribe.services.plugin_context import _prior_art_line
from grimoire.services.plugin_context import _prior_art_line
item = {"id": 12, "title": "group_pairs — collapse pairs"}
assert _prior_art_line(item, "similar 0.72", None, "python") == \
'> - #12 [similar 0.72 · python] "group_pairs — collapse pairs"'
@@ -950,7 +950,7 @@ def test_prior_art_line_takes_a_search_hit_whose_snippet_is_its_excerpt():
"""A search hit carries its matched passage as a STRING under `snippet` —
the key a snippet record uses for its field dict. Reading it as the dict
500'd the write-path route on any nameless hit."""
from scribe.services.plugin_context import _prior_art_line
from grimoire.services.plugin_context import _prior_art_line
hit = {"id": 7, "title": "Paging contract", "snippet": "…the cursor is opaque…"}
assert _prior_art_line(hit, "similar 0.61", None) == '> - #7 [similar 0.61] "Paging contract"'
record = {"id": 8, "title": "long composed title", "snippet": {"name": "paginate"}}
@@ -964,7 +964,7 @@ def test_route_reads_every_arg_the_hook_sends():
and the hook is the only caller — a rename on either side is silent."""
import inspect
from scribe.routes import plugin as routes
from grimoire.routes import plugin as routes
# The handler plus the query-scope helper it delegates repo/project_id
# to — the contract is what the ROUTE MODULE reads, wherever it reads it.
src = inspect.getsource(routes.write_path_prior_art) + inspect.getsource(
@@ -1002,7 +1002,7 @@ def test_route_resolves_repo_to_a_project_not_to_a_location_filter():
remote may only reach resolve_project."""
import inspect
from scribe.routes import plugin as routes
from grimoire.routes import plugin as routes
src = inspect.getsource(routes.write_path_prior_art) + inspect.getsource(
routes._project_scope
)
@@ -1284,7 +1284,7 @@ async def test_stamping_needs_named_shapes_and_a_recent_pull():
"""Offered-but-ignored stamps nothing: without a PULLED event there is no
evidence, and without the hook naming shapes there is nothing to stamp.
Neither case may even read the pull stream."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
pulls = AsyncMock(return_value={})
stamp = AsyncMock(return_value=[])
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
@@ -1317,7 +1317,7 @@ async def test_a_pulled_snippet_already_seen_is_evidence_not_menu():
after the search. The part the stamp depends on: the pulled id stays in
the query and its score reaches `resembles`.
"""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
search = AsyncMock(return_value=[(0.91, fake_note(id=7, title="pulled", user_id=1, note_type="snippet")), (0.80, fake_note(id=8, title="fresh", user_id=1, note_type="snippet"))])
stamp = AsyncMock(return_value=[{
"path": "src/x.py", "symbol": "debounce", "kind": "sym",
@@ -1366,7 +1366,7 @@ async def test_a_stamp_renders_even_when_the_hint_is_otherwise_silent():
"""After dedup the common case is an empty hint; the stamp must still run
and still be reported — silence about accounting is how hook rows would
become invisible."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
stamped = [{"path": "web/b.css", "symbol": "btn-primary", "kind": "css",
"snippet_id": 5, "reason": "hook: pulled #5; payload references `btn-primary`"}]
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
@@ -1388,7 +1388,7 @@ async def test_a_stamp_renders_even_when_the_hint_is_otherwise_silent():
@pytest.mark.asyncio
async def test_a_failing_stamp_does_not_sink_the_hint():
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets",
AsyncMock(return_value=([_snippet_item(12, "records me")], 1))), \
@@ -1411,7 +1411,7 @@ def test_route_stamps_only_for_a_caller_allowed_to_write():
shapes through only when the key is write-scoped (or it's a session)."""
import inspect
from scribe.routes import plugin as routes
from grimoire.routes import plugin as routes
src = inspect.getsource(routes.write_path_prior_art)
assert 'request.args.get("shapes"' in src
assert '== "write"' in src
@@ -1520,7 +1520,7 @@ async def test_the_write_time_derive_check_names_a_family_or_a_canon_in_band():
family with no canon, or a canon recorded elsewhere — rendered at the
write even when nothing else does; keyed so the session's exclude
channel silences a family already named."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
found = [
{"symbol": "log-empty", "kind": "css", "key": "name:css:log-empty",
"family": {"group": "name:css:log-empty", "label": ".log-empty", "identical": False,
@@ -1622,7 +1622,7 @@ async def test_the_write_time_divergence_check_is_named_in_band():
"""The hook named a shape at a path whose directory a canon
dominates, and the stamp didn't make it that canon's instance — the hint
must say so at the write, even when nothing else renders."""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
div = [{"symbol": "confirmDanger", "kind": "sym", "canon_snippet_id": 2761,
"instances": 20, "judged": 21}]
check = AsyncMock(return_value=div)
@@ -1711,7 +1711,7 @@ def _search_reporting(score, note):
async def _write_path_row(rec, **kwargs):
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
patch.object(pc, "record_retrieval", rec), \
@@ -1740,7 +1740,7 @@ async def test_a_record_this_arm_withheld_itself_is_not_a_near_miss():
The filter is still there, and an arm that reported a score for it would
be making a false claim about the bar.
"""
from scribe.services import plugin_context as pc
from grimoire.services import plugin_context as pc
rec = MagicMock()
# Recorded at a sibling file → listed by PLACE in this same call, and
# pulled this session → kept in the query as resemblance evidence.