fix(lessons): a lesson's name is one claim on one line (#4797)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m48s
CI & Build / Build & push image (push) Successful in 28s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m48s
CI & Build / Build & push image (push) Successful in 28s
#4797 "A lesson's what takes a whole narrative, and the narrative becomes its name". `what` is the title every listing and menu prints. Nothing enforced its documented "one line", so lessons written with the incident in `what` printed up to ~1,500 characters as a menu line, buried the claim, and diluted the trigger in the embedded title. That was 14 of the 39 lessons on this install. - lessons.require_claim refuses a `what` over WHAT_MAX_CHARS (240) or running over several lines. The refusal says the story goes in `insight`. - Both doors call it before writing: - MCP create_lesson / update_lesson; - REST create / update. An update checks only a NEW name, so a lesson stored with a long one can still take the edit that repairs it. - lessons.claim_line is the display half. _menu_name shows an over-long stored name as its first sentence marked " …", because a door guard does not undo rows already stored and an unrepaired install would keep printing them. - The tool docstrings state the bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -255,6 +255,59 @@ def compose_body(
|
||||
return "\n\n".join(lines)
|
||||
|
||||
|
||||
# A lesson's NAME is its claim, and the claim is one line (#4797). `what` is
|
||||
# the title every listing, menu and search row prints, so a story written there
|
||||
# instead of in `insight` turns each of those lines into a document — the menu
|
||||
# line for one such lesson ran to 1,416 characters, and the claim it existed to
|
||||
# transfer sat somewhere in the middle. It also ranks badly: the embedded title
|
||||
# is `what — when_to_apply`, so a narrative crowds out the trigger.
|
||||
#
|
||||
# 240 is a long sentence. The bound is on what a NAME can be, not on what a
|
||||
# lesson can say — `insight` is the body, unbounded, chunked like any record,
|
||||
# and costs the ranking nothing.
|
||||
WHAT_MAX_CHARS = 240
|
||||
|
||||
|
||||
def require_claim(what: str | None) -> None:
|
||||
"""Refuse a `what` that is not one line — before anything is written.
|
||||
|
||||
A refusal rather than a cut, because a cut name reads as complete: the
|
||||
writer would never learn the story went to the wrong field, and the
|
||||
reader would get its first 240 characters as though they were the claim.
|
||||
"""
|
||||
text = (what or "").strip()
|
||||
multiline = "\n" in text
|
||||
if not multiline and len(text) <= WHAT_MAX_CHARS:
|
||||
return
|
||||
shape = "runs over several lines" if multiline else f"is {len(text)} characters"
|
||||
raise ValueError(
|
||||
f"`what` {shape}; it is the lesson's NAME — one claim, one line, at "
|
||||
f"most {WHAT_MAX_CHARS} characters — and every listing and menu prints "
|
||||
"it whole. Nothing was written. Put the claim in `what`, said as the "
|
||||
"sentence someone should come away with, and the incident that taught "
|
||||
"it in `insight`, which is the body and is not bounded."
|
||||
)
|
||||
|
||||
|
||||
def claim_line(what: str | None) -> str:
|
||||
"""A stored name as one menu line, whatever was stored.
|
||||
|
||||
The display half of `require_claim`, for names written before it existed:
|
||||
a guard at the door does not undo a value already stored, and on an
|
||||
install nobody has repaired, those rows would still print their whole
|
||||
story on every menu. Over the bound, the name's first sentence is shown
|
||||
and marked as cut — the record itself is unchanged, and opening it shows
|
||||
the rest.
|
||||
"""
|
||||
text = " ".join((what or "").split())
|
||||
if len(text) <= WHAT_MAX_CHARS:
|
||||
return text
|
||||
first = re.split(r"(?<=[.!?])\s", text, maxsplit=1)[0]
|
||||
if len(first) > WHAT_MAX_CHARS:
|
||||
first = first[:WHAT_MAX_CHARS].rsplit(" ", 1)[0]
|
||||
return first + " …"
|
||||
|
||||
|
||||
def lesson_document(
|
||||
what: str, when_to_apply: str = "", insight: str = "",
|
||||
learned_from: list[int] | None = None,
|
||||
|
||||
@@ -35,7 +35,7 @@ from scribe.services.embeddings import (
|
||||
semantic_search_rules,
|
||||
)
|
||||
from scribe.services import lesson_rules as lesson_rules_svc
|
||||
from scribe.services.lessons import LESSON_NOTE_TYPE
|
||||
from scribe.services.lessons import LESSON_NOTE_TYPE, claim_line
|
||||
from scribe.services.note_usage import record_surfaced
|
||||
from scribe.services.rule_usage import record_rule_surfaced
|
||||
from scribe.services.supersession import superseded_ids
|
||||
@@ -89,7 +89,8 @@ def _menu_name(title: str | None, note_type: str | None, data=None, body: str |
|
||||
from scribe.services.embeddings import untrigger_title
|
||||
from scribe.services.lessons import lesson_trigger
|
||||
trigger = lesson_trigger(SimpleNamespace(data=data, body=body or ""))
|
||||
return untrigger_title(title, trigger).strip() or title
|
||||
# One line even when the stored name is a story (#4797).
|
||||
return claim_line(untrigger_title(title, trigger).strip() or title)
|
||||
return title
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user