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>
85 lines
3.8 KiB
Python
85 lines
3.8 KiB
Python
"""The document a rule is EMBEDDED as.
|
|
|
|
This shape is measured, not chosen. Probed across a corpus, the snippet is the
|
|
most discriminative record kind — a 0.153 top-to-second gap against
|
|
0.010-0.023 for everything else — and the cause is its shape: purpose stated
|
|
twice in a short, single-topic document. These cases pin that
|
|
recipe onto rules, and pin the exclusion that matters more than any of it.
|
|
"""
|
|
import pytest
|
|
|
|
from grimoire.services.embeddings import chunk_document, rule_document
|
|
|
|
|
|
def test_the_trigger_appears_twice_which_is_what_makes_a_vector_sharp():
|
|
"""Repetition of purpose + brevity is the measured cause of the snippet's
|
|
separation. The rule document reproduces it exactly: the trigger in the
|
|
title, and again as the body's first line."""
|
|
title, body = rule_document(
|
|
"Release — never without explicit request",
|
|
"Never cut a release without the operator explicitly asking.",
|
|
"before cutting any release",
|
|
)
|
|
assert title == "Release — never without explicit request — before cutting any release"
|
|
assert body.startswith("When to apply: before cutting any release")
|
|
assert "Never cut a release" in body
|
|
|
|
|
|
def test_why_is_never_embedded():
|
|
"""The exclusion this whole design turns on. `why` is dated incident
|
|
narrative — it can run to thousands of characters — and long multi-topic
|
|
prose is what makes dev-logs mutually indistinguishable: the average lands
|
|
on a centroid they all share. Adding it would not give the vector more to
|
|
work with; it would give every rule the SAME thing to work with.
|
|
|
|
rule_document takes no `why` parameter at all, which is the strongest form
|
|
of this guarantee: it cannot be passed in by a caller who means well.
|
|
"""
|
|
import inspect
|
|
|
|
assert "why" not in inspect.signature(rule_document).parameters
|
|
|
|
|
|
def test_a_rule_with_no_trigger_still_embeds_just_less_sharply():
|
|
"""A rule can have no trigger. Degrading to
|
|
title + statement keeps them findable; it does NOT pad the document with
|
|
whatever text is lying around, which would be the tempting fix and the
|
|
wrong one."""
|
|
title, body = rule_document("dev is home", "Work directly on dev.", "")
|
|
assert title == "dev is home"
|
|
assert body == "Work directly on dev."
|
|
assert "When to apply" not in body
|
|
|
|
|
|
def test_an_empty_rule_yields_no_document_and_therefore_no_vector():
|
|
"""Callers gate on falsiness to skip embedding — the same contract
|
|
chunk_document has, so an emptied record stops being findable by its old
|
|
content rather than keeping a stale vector."""
|
|
assert rule_document("", "", "") == (None, None)
|
|
assert chunk_document(*rule_document("", "", "")) == []
|
|
|
|
|
|
def test_a_long_rule_chunks_and_every_chunk_carries_the_trigger():
|
|
"""A ~3,600-character statement across two headed sections.
|
|
The existing chunker splits it at headings and prefixes each chunk with the
|
|
title — which now CONTAINS the trigger, so each half stays anchored to what
|
|
the rule is for. This is why splitting a merged rule costs nothing at
|
|
retrieval time."""
|
|
statement = (
|
|
"## The tags\n\n" + ("Four tags, four jobs. " * 60)
|
|
+ "\n\n## The artifact's own version\n\n" + ("Two version values. " * 60)
|
|
)
|
|
title, body = rule_document("Versioning", statement, "when cutting a release")
|
|
chunks = chunk_document(title, body)
|
|
assert len(chunks) > 1
|
|
assert all("when cutting a release" in chunk for chunk in chunks)
|
|
|
|
|
|
@pytest.mark.parametrize("trigger,expected_title", [
|
|
("before any git push", "dev is home — before any git push"),
|
|
(" before any git push ", "dev is home — before any git push"),
|
|
])
|
|
def test_the_trigger_is_trimmed_before_it_reaches_the_vector(trigger, expected_title):
|
|
title, _ = rule_document("dev is home", "Work on dev.", trigger)
|
|
assert title == expected_title
|