refactor(notes): a snippet's and lesson's stored title is its name; the trigger joins it only in the embedded document (milestone 427)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / integration (push) Successful in 52s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m35s
CI & Build / Build & push image (push) Successful in 32s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / integration (push) Successful in 52s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m35s
CI & Build / Build & push image (push) Successful in 32s
The title was `subject — trigger` because the stored title WAS the embedded one, and the join is what makes these kinds rank on the situation they apply to (#2485). Every surface that shows a title then showed the trigger too -- menus, lists and search rows ran to kilobytes. - embeddings.document_title(title, note_type, data, body) joins the trigger from `data` (body fallback) at embed time. Idempotent: an un-migrated composed title comes out the same, never doubled. The embed path, the startup backfill and the dedup gate's semantic signal all use it, so the embedded text -- and every vector -- is unchanged. - Writers store the subject: snippet create/update (service, REST, MCP) and lesson_document. Both compose_title helpers are removed. - Readers: dedup takes `data`; the menus strip the embedded title from a passage; list rows project `when_to_use`, which SnippetListView reads. - 0108 rewrites existing rows on an exact `' — ' || <own trigger>` suffix with raw SQL, leaving updated_at alone so the backfill does not re-embed the corpus for identical vectors. Downgrade recomposes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,11 @@ from scribe.services import projects as projects_svc
|
||||
from scribe.services import shape_ledger as shape_ledger_svc
|
||||
from scribe.services import snippets as snippets_svc
|
||||
from scribe.services.access import label_shared_items, owner_names_for
|
||||
from scribe.services.embeddings import semantic_search_notes, semantic_search_rules
|
||||
from scribe.services.embeddings import (
|
||||
document_title,
|
||||
semantic_search_notes,
|
||||
semantic_search_rules,
|
||||
)
|
||||
from scribe.services.lessons import LESSON_NOTE_TYPE
|
||||
from scribe.services.note_usage import record_surfaced
|
||||
from scribe.services.rule_usage import record_rule_surfaced
|
||||
@@ -88,8 +92,10 @@ def _menu_name(title: str | None, note_type: str | None, data=None, body: str |
|
||||
def _menu_passage(title: str | None, chunk_text: str | None, name: str = "") -> str:
|
||||
"""The matched chunk on one line, without the title it was embedded under.
|
||||
|
||||
Every chunk is `title\nsection` (`embeddings.embedding_text`), so the title
|
||||
prefix is stripped exactly. A chunk that WAS only the title — a short
|
||||
Every chunk is `title\nsection` (`embeddings.embedding_text`), and `title`
|
||||
here must be the EMBEDDED one (`embeddings.document_title`) — for a snippet
|
||||
or lesson that is `name — trigger`, not the stored name — so the prefix is
|
||||
stripped exactly. A chunk that WAS only the title — a short
|
||||
record, or the head chunk of one — matched on the title, and for a
|
||||
trigger-keyed kind the part of it the name line no longer shows is the
|
||||
trigger: that is returned, because it is precisely what matched.
|
||||
@@ -1235,7 +1241,10 @@ async def build_autoinject_hint(
|
||||
# queries and so are not in this search's report. No fallback to the
|
||||
# body's opening: on a menu that would be a line of preamble dressed as
|
||||
# a reason, and a reader cannot tell the two apart once indented alike.
|
||||
passage = _menu_passage(note.title, (menu_chunks.get(nid) or {}).get("text"), name)
|
||||
passage = _menu_passage(
|
||||
document_title(note.title, note.note_type, note.data, note.body),
|
||||
(menu_chunks.get(nid) or {}).get("text"), name,
|
||||
)
|
||||
if passage:
|
||||
lines.append(f"> ↳ {passage}")
|
||||
|
||||
@@ -2324,6 +2333,10 @@ async def build_write_path_hint(
|
||||
# (#4364): the line is built from facts, not from
|
||||
# re-reading its own marker.
|
||||
"name": _menu_name(note.title, note.note_type, note.data, note.body),
|
||||
# What its chunks are prefixed with, for stripping.
|
||||
"doc_title": document_title(
|
||||
note.title, note.note_type, note.data, note.body,
|
||||
),
|
||||
"seen": int(note.id) in excluded,
|
||||
# Carried, not re-read off the rendered marker. The
|
||||
# marker is prose assembled for a human and it already
|
||||
@@ -2510,7 +2523,8 @@ async def build_write_path_hint(
|
||||
if item.get("seen"):
|
||||
continue
|
||||
passage = _menu_passage(
|
||||
item.get("title"), (wp_chunks.get(int(item["id"])) or {}).get("text"),
|
||||
item.get("doc_title") or item.get("title"),
|
||||
(wp_chunks.get(int(item["id"])) or {}).get("text"),
|
||||
item.get("name") or "",
|
||||
)
|
||||
if passage:
|
||||
|
||||
Reference in New Issue
Block a user