generated from coulomb/repo-seed
session-memory: error-body mining into digest (WP-0006 T01)
build_digest now extracts normalized error fingerprints + samples from failed events (error kind + failing tool_result bodies) into a durable error_snippets list — paths/numbers/uuids/addrs stripped so the same error collapses to one fingerprint with a count; Python traceback header skipped in favour of the real exception line. Durable in Tier 2 (survives Tier 1 eviction). SCHEMA_VERSION -> 2 (re-ingest needed to populate). 7 new tests; suite 95/95 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
74
tests/test_digest_errors.py
Normal file
74
tests/test_digest_errors.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Error-body mining into the digest (WP-0006 T01)."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from session_memory.core.digest import ( # noqa: E402
|
||||
_error_fingerprint,
|
||||
_error_snippets,
|
||||
build_digest,
|
||||
)
|
||||
from session_memory.core.schema import SCHEMA_VERSION, Session, SessionEvent # noqa: E402
|
||||
|
||||
|
||||
def _ev(seq, kind, **kw):
|
||||
return SessionEvent(session_uid="claude:s", seq=seq, kind=kind, **kw)
|
||||
|
||||
|
||||
def test_fingerprint_normalizes_paths_numbers_ids():
|
||||
a = _error_fingerprint("ModuleNotFoundError: No module named 'foo' at /home/x/a.py:42")
|
||||
b = _error_fingerprint("ModuleNotFoundError: No module named 'foo' at /srv/y/b.py:9991")
|
||||
assert a == b # paths + line numbers stripped -> same fingerprint
|
||||
assert "<path>" in a and "<n>" in a
|
||||
|
||||
|
||||
def test_fingerprint_uuid_and_addr():
|
||||
fp = _error_fingerprint("connection 0xDEADBEEF to 1972d1d9-fc35-4912-8126-1fe64cc51425 failed")
|
||||
assert "<addr>" in fp and "<uuid>" in fp
|
||||
|
||||
|
||||
def test_snippets_dedup_and_count():
|
||||
blobs = {"b1": "Traceback...\nValueError: bad thing at /p/x.py:10",
|
||||
"b2": "Traceback...\nValueError: bad thing at /q/y.py:99",
|
||||
"b3": "KeyError: 'id'"}
|
||||
events = [
|
||||
_ev(0, "error", payload_ref="b1"),
|
||||
_ev(1, "error", payload_ref="b2"), # same fingerprint as b1
|
||||
_ev(2, "error", payload_ref="b3"),
|
||||
]
|
||||
snips = _error_snippets(events, blobs)
|
||||
assert len(snips) == 2
|
||||
top = snips[0]
|
||||
assert top["count"] == 2 # the ValueError collapsed
|
||||
assert "ValueError" in top["sample"]
|
||||
|
||||
|
||||
def test_failed_tool_result_mined():
|
||||
blobs = {"b1": "npm ERR! something failed with non-zero exit"}
|
||||
events = [_ev(0, "tool_result", tool="Bash", payload_ref="b1")]
|
||||
snips = _error_snippets(events, blobs)
|
||||
assert len(snips) == 1
|
||||
assert snips[0]["tool"] == "Bash"
|
||||
|
||||
|
||||
def test_clean_tool_result_not_mined():
|
||||
blobs = {"b1": "6 passed in 0.4s"}
|
||||
events = [_ev(0, "tool_result", tool="Bash", payload_ref="b1")]
|
||||
assert _error_snippets(events, blobs) == []
|
||||
|
||||
|
||||
def test_build_digest_includes_error_snippets_and_v2():
|
||||
s = Session(session_uid="claude:s", flavor="claude", native_session_id="s", repo="r")
|
||||
events = [_ev(0, "user_msg"), _ev(1, "error", payload_ref="b1"), _ev(2, "assistant_msg")]
|
||||
d = build_digest(s, events, {"b1": "RuntimeError: kaboom at /a/b.py:3"})
|
||||
assert d["schema_version"] == SCHEMA_VERSION == 2
|
||||
assert d["error_snippets"][0]["count"] == 1
|
||||
assert "RuntimeError" in d["error_snippets"][0]["sample"]
|
||||
|
||||
|
||||
def test_no_errors_empty_list():
|
||||
s = Session(session_uid="claude:s", flavor="claude", native_session_id="s", repo="r")
|
||||
d = build_digest(s, [_ev(0, "user_msg"), _ev(1, "assistant_msg")])
|
||||
assert d["error_snippets"] == []
|
||||
Reference in New Issue
Block a user