daimon-briefing 0.3.1__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/PKG-INFO +1 -1
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/carry.py +40 -7
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/pyproject.toml +1 -1
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_carry.py +70 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/uv.lock +1 -1
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/.gitignore +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/README.md +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/__init__.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/anchor.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/briefing.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/cli.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/config.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/configure.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/harvest.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/hooks.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/llm.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/recall.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/render.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/scoring.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/serializer.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/store.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/teamsync.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/daimon_briefing/transcript.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/skills/daimon-briefing/SKILL.md +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/skills/daimon-end/SKILL.md +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/__init__.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/conftest.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/fixtures/sample_transcript.md +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_anchor.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_briefing.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_claude_hooks.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_cli.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_codex_hooks.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_config.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_configure.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_gemini_hooks.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_harvest.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_hooks.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_isolation.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_llm.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_recall.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_render.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_scoring.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_serializer.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_store.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_teamsync.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_transcript.py +0 -0
- {daimon_briefing-0.3.1 → daimon_briefing-0.3.2}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: daimon-briefing
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Dream-briefing hermes plugin: cognitive checkpoint at session end, 'while you were away' briefing at session start. Slice 1 (local-file, no Honcho).
|
|
5
5
|
Author: Daily-Nerd / Daimon
|
|
6
6
|
License: Apache-2.0
|
|
@@ -9,6 +9,7 @@ caller injects clock and knobs (scar: a default wall-clock anywhere silently
|
|
|
9
9
|
freezes time math under simulation)."""
|
|
10
10
|
|
|
11
11
|
import copy
|
|
12
|
+
from collections import Counter
|
|
12
13
|
|
|
13
14
|
from . import recall, scoring, store
|
|
14
15
|
|
|
@@ -22,16 +23,41 @@ _CARRIED_KINDS = (
|
|
|
22
23
|
|
|
23
24
|
_MIN_SHARED = 3 # shared salient terms for same-item
|
|
24
25
|
_MIN_RATIO = 0.6 # or this fraction of the shorter term list
|
|
26
|
+
_GENERIC_DF = 3 # a term shared by >=3 items of one kind is that kind's
|
|
27
|
+
# vocabulary, not an item's identity. Filtering it out of
|
|
28
|
+
# dedup stops generic overlap (data/field/validation, the
|
|
29
|
+
# #13 live specimen) from forging a false merge. Computed
|
|
30
|
+
# per kind per merge — no static stoplist, so carry stays
|
|
31
|
+
# language-neutral (es i18n just shipped).
|
|
25
32
|
|
|
26
33
|
|
|
27
|
-
def
|
|
34
|
+
def _generic_terms(texts, k: int = _GENERIC_DF) -> frozenset:
|
|
35
|
+
"""Salient terms appearing in >= k DISTINCT texts of one kind — that kind's
|
|
36
|
+
shared vocabulary, which dedup must ignore. Document frequency counts a term
|
|
37
|
+
once per text (set per text), so repetition inside one item can't inflate
|
|
38
|
+
it."""
|
|
39
|
+
df: Counter = Counter()
|
|
40
|
+
for t in texts:
|
|
41
|
+
df.update(set(recall.salient_terms(t)))
|
|
42
|
+
return frozenset(term for term, n in df.items() if n >= k)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _same_item(a_text: str, b_text: str, generic=frozenset()) -> bool:
|
|
28
46
|
"""Term-overlap identity: the serializer rewords constantly (run-01), so
|
|
29
47
|
exact text misses twins. Shared >=3 salient terms, or >=60% of the shorter
|
|
30
|
-
list, means same item
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if
|
|
48
|
+
list, means same item — but only AFTER subtracting `generic` (the kind's
|
|
49
|
+
document-frequent vocabulary), so overlap on common words can't merge
|
|
50
|
+
unrelated items.
|
|
51
|
+
|
|
52
|
+
Floor: if either filtered set has <2 terms, never fuzzy-match. This blocks a
|
|
53
|
+
single surviving shared term from passing the ratio path (1/1 = 1.0). The
|
|
54
|
+
bias is deliberate and asymmetric: a false merge erases a loop and forges
|
|
55
|
+
its birth stamp, while a false non-merge only costs a duplicate item — so
|
|
56
|
+
tie-break toward NOT merging. The exact-text guard still catches identical
|
|
57
|
+
items regardless."""
|
|
58
|
+
a = set(recall.salient_terms(a_text)) - generic
|
|
59
|
+
b = set(recall.salient_terms(b_text)) - generic
|
|
60
|
+
if len(a) < 2 or len(b) < 2:
|
|
35
61
|
return False
|
|
36
62
|
shared = len(a & b)
|
|
37
63
|
return shared >= _MIN_SHARED or shared / min(len(a), len(b)) >= _MIN_RATIO
|
|
@@ -64,6 +90,12 @@ def merge(new_cp: dict, prev_cp: dict | None, now: float,
|
|
|
64
90
|
continue
|
|
65
91
|
prev_items = (prev_cp.get(section) or {}).get(key) or []
|
|
66
92
|
native_texts = {i.get("text") for i in native if isinstance(i, dict)}
|
|
93
|
+
# Generic vocabulary for THIS kind, from the same universe merge iterates
|
|
94
|
+
# (native + prev): terms this common are not identity, so dedup ignores
|
|
95
|
+
# them (#13). Computed once per kind, passed to every _same_item below.
|
|
96
|
+
generic = _generic_terms(
|
|
97
|
+
[str(i.get("text") or "") for i in native if isinstance(i, dict)]
|
|
98
|
+
+ [str(i.get("text") or "") for i in prev_items if isinstance(i, dict)])
|
|
67
99
|
carried = []
|
|
68
100
|
for item in prev_items:
|
|
69
101
|
if not isinstance(item, dict) or not str(item.get("text") or "").strip():
|
|
@@ -72,7 +104,8 @@ def merge(new_cp: dict, prev_cp: dict | None, now: float,
|
|
|
72
104
|
if text in native_texts:
|
|
73
105
|
continue # exact twin already present (idempotency)
|
|
74
106
|
twin = next((n for n in native if isinstance(n, dict)
|
|
75
|
-
and _same_item(text, str(n.get("text") or ""))),
|
|
107
|
+
and _same_item(text, str(n.get("text") or ""), generic)),
|
|
108
|
+
None)
|
|
76
109
|
if twin is not None:
|
|
77
110
|
# Session re-discussed it: the new wording wins, but the item's
|
|
78
111
|
# AGE does not reset (run-01: 8-12 resets/20 cycles killed the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "daimon-briefing"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "Dream-briefing hermes plugin: cognitive checkpoint at session end, 'while you were away' briefing at session start. Slice 1 (local-file, no Honcho)."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -7,6 +7,18 @@ from daimon_briefing import carry
|
|
|
7
7
|
|
|
8
8
|
NOW = 1_760_000_000.0 # arbitrary fixed epoch
|
|
9
9
|
|
|
10
|
+
# Live false-merge specimen (2026-07-02): two UNRELATED items that matched on
|
|
11
|
+
# exactly the generic terms {data, field, validation}. See #13.
|
|
12
|
+
_SPEC_A = ("First external user validation — the core adoption-arc objective "
|
|
13
|
+
"that unblocks _MIN_OVERLAP field data, DAIMON_TEAM validation, and "
|
|
14
|
+
"teammate-noise research questions")
|
|
15
|
+
_SPEC_B = ("Q-STALE + multi-cycle degradation validation — parked on LLM "
|
|
16
|
+
"budget. Need field data: what do 20 serialize cycles do to a "
|
|
17
|
+
"long-lived open loop, and how does that inform decay tuning?")
|
|
18
|
+
# Sibling native item carrying the same generic vocabulary, so {data, field,
|
|
19
|
+
# validation} each reach document-frequency 3 across the kind (A, sibling, B).
|
|
20
|
+
_SPEC_SIB = "extra validation of the data field mapping"
|
|
21
|
+
|
|
10
22
|
|
|
11
23
|
def _iso(days_before_now):
|
|
12
24
|
import datetime as dt
|
|
@@ -135,6 +147,64 @@ def test_same_item_short_texts_never_fuzzy_match():
|
|
|
135
147
|
assert carry._same_item("ok", "ok go") is False
|
|
136
148
|
|
|
137
149
|
|
|
150
|
+
def test_generic_overlap_does_not_false_merge_specimen():
|
|
151
|
+
# Live #13 specimen: fresh native A and unrelated carried B share only the
|
|
152
|
+
# generic terms {data, field, validation}. B must survive as its OWN item;
|
|
153
|
+
# A must NOT inherit B's older birth stamp.
|
|
154
|
+
new = _cp("S-new", 0, questions=[
|
|
155
|
+
_item(_SPEC_A, days=0), _item(_SPEC_SIB, days=1)])
|
|
156
|
+
prev = _cp("S-prev", 1, questions=[_item(_SPEC_B, imp=7, days=45)])
|
|
157
|
+
out = carry.merge(new, prev, NOW)
|
|
158
|
+
qs = out["working_context"]["open_questions"]
|
|
159
|
+
texts = [q["text"] for q in qs]
|
|
160
|
+
assert _SPEC_B in texts # B kept, not erased
|
|
161
|
+
b_item = next(q for q in qs if q["text"] == _SPEC_B)
|
|
162
|
+
assert b_item["carried_from"] == "S-prev"
|
|
163
|
+
a_item = next(q for q in qs if q["text"] == _SPEC_A)
|
|
164
|
+
assert a_item["first_seen"] == _iso(0) # A did NOT inherit B's stamp
|
|
165
|
+
assert "carried_from" not in a_item
|
|
166
|
+
assert len(qs) == 3
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_same_item_generic_filter_is_the_fix_not_a_threshold():
|
|
170
|
+
generic = frozenset({"data", "field", "validation"})
|
|
171
|
+
assert carry._same_item(_SPEC_A, _SPEC_B, generic) is False
|
|
172
|
+
assert carry._same_item(_SPEC_A, _SPEC_B) is True # unfiltered: the bug
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_specific_twin_still_merges_and_inherits_age():
|
|
176
|
+
# Two rewordings sharing SPECIFIC low-DF terms must still match (run-02
|
|
177
|
+
# behavior): the guard filters vocabulary, not identity.
|
|
178
|
+
old = _item("quorint-ledger reconciliation drops entries when upstream "
|
|
179
|
+
"feed pauses", days=45)
|
|
180
|
+
new_twin = _item("quorint-ledger reconciliation still dropping entries on "
|
|
181
|
+
"feed pauses", days=0)
|
|
182
|
+
prev = _cp("S-prev", 1, questions=[
|
|
183
|
+
old, _item("unrelated gavotte pipeline flaking noise", days=3)])
|
|
184
|
+
new = _cp("S-new", 0, questions=[
|
|
185
|
+
new_twin, _item("tervane cache eviction unclear noise", days=1)])
|
|
186
|
+
out = carry.merge(new, prev, NOW)
|
|
187
|
+
qs = out["working_context"]["open_questions"]
|
|
188
|
+
twin = next(q for q in qs if "still dropping" in q["text"])
|
|
189
|
+
assert twin["first_seen"] == _iso(45) # matched -> age inherited
|
|
190
|
+
assert not any("drops entries" in q["text"] for q in qs) # not duplicated
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_post_filter_floor_blocks_single_shared_term():
|
|
194
|
+
generic = frozenset({"data", "field", "validation"})
|
|
195
|
+
a = "data field validation alpha" # filtered -> {alpha}
|
|
196
|
+
b = "data field validation alpha bravo" # filtered -> {alpha, bravo}
|
|
197
|
+
# ratio would be 1/1 = 1.0 >= _MIN_RATIO without the floor; floor blocks it
|
|
198
|
+
assert carry._same_item(a, b, generic) is False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_generic_terms_df_boundary():
|
|
202
|
+
texts = ["zeta omega alpha", "zeta omega beta", "omega gamma delta"]
|
|
203
|
+
generic = carry._generic_terms(texts) # k defaults to _GENERIC_DF (3)
|
|
204
|
+
assert "omega" in generic # 3 distinct texts -> generic
|
|
205
|
+
assert "zeta" not in generic # exactly 2 distinct texts -> not generic
|
|
206
|
+
|
|
207
|
+
|
|
138
208
|
def test_in_call_duplicate_prev_items_carry_once():
|
|
139
209
|
# Two prev items with IDENTICAL text: native_texts must pick up the first
|
|
140
210
|
# one as it's appended, so the second (an exact twin) is skipped too.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|