file-observer 1.2.2__tar.gz → 1.2.4__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.
- {file_observer-1.2.2/src/file_observer.egg-info → file_observer-1.2.4}/PKG-INFO +1 -1
- {file_observer-1.2.2 → file_observer-1.2.4}/pyproject.toml +1 -1
- {file_observer-1.2.2 → file_observer-1.2.4}/src/file_observer/scanner.py +35 -19
- {file_observer-1.2.2 → file_observer-1.2.4/src/file_observer.egg-info}/PKG-INFO +1 -1
- {file_observer-1.2.2 → file_observer-1.2.4}/src/file_observer.egg-info/SOURCES.txt +3 -1
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_packaging.py +3 -3
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_unit.py +6 -6
- file_observer-1.2.4/tests/test_v1_2_3.py +41 -0
- file_observer-1.2.4/tests/test_v1_2_4.py +56 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/LICENSE +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/LICENSE-AGPL +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/docs/README.md +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/setup.cfg +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/src/file_observer/__init__.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/src/file_observer.egg-info/dependency_links.txt +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/src/file_observer.egg-info/entry_points.txt +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/src/file_observer.egg-info/requires.txt +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/src/file_observer.egg-info/top_level.txt +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/src/scanner/__init__.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/src/scanner/scanner.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_edge_cases.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_golden.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_integration.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_v1_1.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_v1_2.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_v1_2_1.py +0 -0
- {file_observer-1.2.2 → file_observer-1.2.4}/tests/test_v1_2_2.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: file-observer
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: Not a file watcher — a one-shot, read-only scan that emits a deterministic JSON manifest of what's in a directory before you ingest it.
|
|
5
5
|
Author-email: Russell Pfister <russalo@russalo.com>
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "file-observer"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.4"
|
|
8
8
|
description = "Not a file watcher — a one-shot, read-only scan that emits a deterministic JSON manifest of what's in a directory before you ingest it."
|
|
9
9
|
readme = "docs/README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -71,8 +71,8 @@ except ImportError:
|
|
|
71
71
|
_defusedxml_available = False
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
SCANNER_VERSION = "1.2.
|
|
75
|
-
LOGIC_VERSION = "1.1.
|
|
74
|
+
SCANNER_VERSION = "1.2.4"
|
|
75
|
+
LOGIC_VERSION = "1.1.4"
|
|
76
76
|
SCHEMA_VERSION = "1.2"
|
|
77
77
|
|
|
78
78
|
|
|
@@ -546,21 +546,21 @@ CHATLOG_TOOL = "chatlog_signals"
|
|
|
546
546
|
# any detection regex or extraction algorithm requires bumping METHOD_VERSION
|
|
547
547
|
# and updating the rules definition string.
|
|
548
548
|
CHATLOG_VECTOR_ID = "chatlog"
|
|
549
|
-
CHATLOG_METHOD_VERSION =
|
|
549
|
+
CHATLOG_METHOD_VERSION = 8 # v1.2.4: case-insensitive stop-list; embedded-dialogue parity with prose Rule 1
|
|
550
550
|
CHATLOG_RULES_DEFINITION = (
|
|
551
|
-
"detect:speaker_label_re(distinct>=2,total>=3,recurring>=1,
|
|
551
|
+
"detect:speaker_label_re(distinct>=2,total>=3,recurring>=1,stop_list_ci),"
|
|
552
552
|
"h3_header_re(5+)|section_divider_re(3+)[require speaker_cosignal_distinct>=2],"
|
|
553
553
|
"json_conversation(role_keys{type,role,from,speaker,author}+content_keys{text,value,content,message,body},"
|
|
554
|
-
"line/array/tree,embedded_speaker_labels,require msgs>=3 AND distinct_speakers>=2);"
|
|
555
|
-
"extract:turn_count,speaker_labels(freq>=3,
|
|
554
|
+
"line/array/tree,embedded_speaker_labels(distinct>=2,total>=3,recurring>=1),require msgs>=3 AND distinct_speakers>=2);"
|
|
555
|
+
"extract:turn_count,speaker_labels(freq>=3,stop_list_ci),section_markers,"
|
|
556
556
|
"turn_char_stats,speaker_turn_counts,speaker_turn_chars,alternation,"
|
|
557
557
|
"reference_tokens(at_mentions,wiki_links,code_fence_blocks,url_count),"
|
|
558
558
|
"top_capitalized_tokens(freq>=3,top20),vocabulary_size_estimate;"
|
|
559
|
-
"
|
|
560
|
-
"
|
|
561
|
-
"
|
|
562
|
-
"
|
|
563
|
-
"
|
|
559
|
+
"stop_list_ci:allow,answer,arguments,authorization,bcc,caution,cc,command,commands,"
|
|
560
|
+
"copyright,date,description,disallow,distribution,documentation,error,example,examples,"
|
|
561
|
+
"fixme,format,from,important,license,lines,message,newsgroups,note,options,organization,"
|
|
562
|
+
"parameters,password,path,question,references,result,returns,sender,subject,summary,"
|
|
563
|
+
"synopsis,tip,to,todo,usage,version,warning"
|
|
564
564
|
)
|
|
565
565
|
CHATLOG_STATIC_TUNING = {
|
|
566
566
|
"detection_threshold": 3,
|
|
@@ -624,7 +624,20 @@ CHATLOG_SPEAKER_STOP_LIST: set[str] = {
|
|
|
624
624
|
"Version", "Usage", "Options", "Command", "Commands", "Format", "Synopsis",
|
|
625
625
|
"Description", "Arguments", "Parameters", "Returns", "Summary",
|
|
626
626
|
"Authorization", "Documentation", "License", "Copyright", "Password",
|
|
627
|
+
# v1.2.3: FAQ labels — `Question:`/`Answer:` recur like speakers but aren't
|
|
628
|
+
# (stopgap for the most common recurring-Key:value FP; the real fix is a
|
|
629
|
+
# non-count signal — see scratch/review/v1_2_2_fp_findings.md). Deliberately
|
|
630
|
+
# NOT adding single letters `Q`/`A`: `A:`/`B:` is legitimate anonymized dialogue.
|
|
631
|
+
"Question", "Answer",
|
|
627
632
|
}
|
|
633
|
+
# v1.2.4: match case-INSENSITIVELY. The label regex matches all-caps, so the
|
|
634
|
+
# case-sensitive set left holes (ALL-CAPS `FROM:`/`SUBJECT:` header dumps still
|
|
635
|
+
# false-positived; only FAQ got dual-cased in v1.2.3). Folding once here closes
|
|
636
|
+
# every all-caps hole AND removes the dual-listing maintenance burden — `User`
|
|
637
|
+
# stays a legitimate speaker because it isn't in the set in any case.
|
|
638
|
+
CHATLOG_SPEAKER_STOP_LIST_CF: frozenset[str] = frozenset(
|
|
639
|
+
w.casefold() for w in CHATLOG_SPEAKER_STOP_LIST
|
|
640
|
+
)
|
|
628
641
|
|
|
629
642
|
# v0.9: Reference tokens vector identity constants.
|
|
630
643
|
REFERENCE_TOKENS_VECTOR_ID = "reference_tokens"
|
|
@@ -2544,7 +2557,7 @@ class Scanner:
|
|
|
2544
2557
|
# sweep showed separates transcripts (recurring) from header blocks (one-shot).
|
|
2545
2558
|
speaker_matches = [
|
|
2546
2559
|
m.group(1) for m in CHATLOG_SPEAKER_LABEL_RE.finditer(text)
|
|
2547
|
-
if m.group(1) not in
|
|
2560
|
+
if m.group(1).casefold() not in CHATLOG_SPEAKER_STOP_LIST_CF
|
|
2548
2561
|
]
|
|
2549
2562
|
speaker_counts = Counter(speaker_matches)
|
|
2550
2563
|
if (len(speaker_counts) >= 2 and sum(speaker_counts.values()) >= 3
|
|
@@ -2571,14 +2584,17 @@ class Scanner:
|
|
|
2571
2584
|
@staticmethod
|
|
2572
2585
|
def _string_has_speaker_dialogue(s: Any) -> bool:
|
|
2573
2586
|
"""Dialogue embedded in a JSON string value (e.g. hh-rlhf's
|
|
2574
|
-
'\\n\\nHuman: ...\\n\\nAssistant: ...'):
|
|
2587
|
+
'\\n\\nHuman: ...\\n\\nAssistant: ...'). v1.2.4: applies the SAME predicate
|
|
2588
|
+
as prose Rule 1 — >=2 distinct speakers, >=3 total, >=1 recurring — so the
|
|
2589
|
+
same text isn't detected inside a JSON string yet rejected as prose."""
|
|
2575
2590
|
if not isinstance(s, str) or len(s) < 20:
|
|
2576
2591
|
return False
|
|
2577
|
-
labels =
|
|
2592
|
+
labels = Counter(
|
|
2578
2593
|
m.group(1) for m in CHATLOG_SPEAKER_LABEL_RE.finditer(s)
|
|
2579
|
-
if m.group(1) not in
|
|
2580
|
-
|
|
2581
|
-
return len(labels) >= 3
|
|
2594
|
+
if m.group(1).casefold() not in CHATLOG_SPEAKER_STOP_LIST_CF
|
|
2595
|
+
)
|
|
2596
|
+
return (len(labels) >= 2 and sum(labels.values()) >= 3
|
|
2597
|
+
and any(c >= 2 for c in labels.values()))
|
|
2582
2598
|
|
|
2583
2599
|
def _detect_conversational_json(self, text: str) -> bool:
|
|
2584
2600
|
"""Generalized conversational JSON/JSONL detection (v1.2; tightened v1.2.1).
|
|
@@ -2687,7 +2703,7 @@ class Scanner:
|
|
|
2687
2703
|
(e.g. hh-rlhf's '\\n\\nHuman: ...\\n\\nAssistant: ...')."""
|
|
2688
2704
|
pairs: list[tuple[str, str]] = []
|
|
2689
2705
|
matches = [m for m in CHATLOG_SPEAKER_LABEL_RE.finditer(s)
|
|
2690
|
-
if m.group(1) not in
|
|
2706
|
+
if m.group(1).casefold() not in CHATLOG_SPEAKER_STOP_LIST_CF]
|
|
2691
2707
|
for i, m in enumerate(matches):
|
|
2692
2708
|
end = matches[i + 1].start() if i + 1 < len(matches) else len(s)
|
|
2693
2709
|
pairs.append((m.group(1), s[m.end():end].strip()))
|
|
@@ -2811,7 +2827,7 @@ class Scanner:
|
|
|
2811
2827
|
# v0.9.1: filter stop-list tokens from both detection and turn metrics
|
|
2812
2828
|
raw_label_matches = [
|
|
2813
2829
|
m for m in CHATLOG_SPEAKER_LABEL_RE.finditer(text)
|
|
2814
|
-
if m.group(1) not in
|
|
2830
|
+
if m.group(1).casefold() not in CHATLOG_SPEAKER_STOP_LIST_CF
|
|
2815
2831
|
]
|
|
2816
2832
|
turn_count = len(raw_label_matches)
|
|
2817
2833
|
label_counts = Counter(m.group(1) for m in raw_label_matches)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: file-observer
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: Not a file watcher — a one-shot, read-only scan that emits a deterministic JSON manifest of what's in a directory before you ingest it.
|
|
5
5
|
Author-email: Russell Pfister <russalo@russalo.com>
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -17,7 +17,7 @@ def test_canonical_top_level_api():
|
|
|
17
17
|
|
|
18
18
|
import file_observer
|
|
19
19
|
|
|
20
|
-
assert file_observer.__version__ == "1.2.
|
|
20
|
+
assert file_observer.__version__ == "1.2.4"
|
|
21
21
|
assert "Scanner" in file_observer.__all__
|
|
22
22
|
|
|
23
23
|
|
|
@@ -49,9 +49,9 @@ def test_canonical_submodule_constants_unchanged():
|
|
|
49
49
|
"""Constants and the manifest field they feed stay stable across the 1.0.x → 1.1 line (the 1.0.1 import-package rename left them intact)."""
|
|
50
50
|
from file_observer.scanner import SCANNER_VERSION, LOGIC_VERSION, SCHEMA_VERSION
|
|
51
51
|
|
|
52
|
-
assert SCANNER_VERSION == "1.2.
|
|
52
|
+
assert SCANNER_VERSION == "1.2.4"
|
|
53
53
|
assert SCHEMA_VERSION == "1.2" # additive minor bump (v1.1 corpus-intelligence fields)
|
|
54
|
-
assert LOGIC_VERSION == "1.1.
|
|
54
|
+
assert LOGIC_VERSION == "1.1.4" # v1.2.4: case-insensitive stop-list + embedded-dialogue parity
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def test_legacy_scanner_import_warns():
|
|
@@ -811,8 +811,8 @@ class TestScanContext:
|
|
|
811
811
|
(tmp_path / "a.txt").write_text("hello")
|
|
812
812
|
manifest = Scanner(source_dir=tmp_path).scan()
|
|
813
813
|
ctx = manifest.context
|
|
814
|
-
assert ctx.scanner_version == "1.2.
|
|
815
|
-
assert ctx.logic_version == "1.1.
|
|
814
|
+
assert ctx.scanner_version == "1.2.4"
|
|
815
|
+
assert ctx.logic_version == "1.1.4"
|
|
816
816
|
assert ctx.python_version # non-empty
|
|
817
817
|
assert ctx.platform # non-empty
|
|
818
818
|
|
|
@@ -850,7 +850,7 @@ class TestScanContext:
|
|
|
850
850
|
manifest = Scanner(source_dir=tmp_path).scan()
|
|
851
851
|
data = json_mod.loads(manifest_to_json(manifest))
|
|
852
852
|
assert "context" in data
|
|
853
|
-
assert data["context"]["scanner_version"] == "1.2.
|
|
853
|
+
assert data["context"]["scanner_version"] == "1.2.4"
|
|
854
854
|
|
|
855
855
|
|
|
856
856
|
# ---------------------------------------------------------------------------
|
|
@@ -2042,8 +2042,8 @@ class TestSemanticToolNames:
|
|
|
2042
2042
|
|
|
2043
2043
|
def test_version_is_current(self) -> None:
|
|
2044
2044
|
from file_observer.scanner import SCANNER_VERSION, LOGIC_VERSION
|
|
2045
|
-
assert SCANNER_VERSION == "1.2.
|
|
2046
|
-
assert LOGIC_VERSION == "1.1.
|
|
2045
|
+
assert SCANNER_VERSION == "1.2.4"
|
|
2046
|
+
assert LOGIC_VERSION == "1.1.4"
|
|
2047
2047
|
|
|
2048
2048
|
|
|
2049
2049
|
# ---------------------------------------------------------------------------
|
|
@@ -3530,4 +3530,4 @@ class TestMarkdownReport:
|
|
|
3530
3530
|
from file_observer.scanner import manifest_to_markdown
|
|
3531
3531
|
(tmp_path / "a.txt").write_text("hello")
|
|
3532
3532
|
md = manifest_to_markdown(Scanner(source_dir=tmp_path).scan())
|
|
3533
|
-
assert "1.2.
|
|
3533
|
+
assert "1.2.4" in md
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""v1.2.3 — FAQ false-positive stopgap.
|
|
2
|
+
|
|
3
|
+
Pro + flash both flagged that recurring `Question:`/`Answer:` labels (a FAQ doc)
|
|
4
|
+
satisfy the v1.2.2 prose rule (≥2 distinct, ≥3 total, ≥1 recurring) and
|
|
5
|
+
false-positive as a chatlog. v1.2.3 adds `Question`/`Answer` to the stop-list.
|
|
6
|
+
|
|
7
|
+
This is a STOPGAP for the most common single case — NOT the real fix. The root
|
|
8
|
+
issue (prose `Key:value` is ambiguous with dialogue) needs a non-count signal;
|
|
9
|
+
see scratch/review/v1_2_2_fp_findings.md. We deliberately do NOT stop-list single
|
|
10
|
+
letters like `Q`/`A` — `A:`/`B:` is a legitimate anonymized-dialogue pattern.
|
|
11
|
+
|
|
12
|
+
Falsify-first: the FAQ test FAILS on v1.2.2, passes after the stop-list add.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from file_observer.scanner import Scanner, ScannerConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _is_chatlog(tmp_path, name, content):
|
|
19
|
+
(tmp_path / name).write_text(content)
|
|
20
|
+
return Scanner(source_dir=tmp_path, config=ScannerConfig()).scan().files[0].is_chatlog
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_faq_question_answer_not_flagged(tmp_path):
|
|
24
|
+
c = ("Question: How do I install?\nAnswer: Use pip.\n"
|
|
25
|
+
"Question: Where are the docs?\nAnswer: In docs/.\n")
|
|
26
|
+
assert _is_chatlog(tmp_path, "FAQ.md", c) is False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_faq_allcaps_not_flagged(tmp_path):
|
|
30
|
+
# case-sensitive stop-list + the label regex matches all-caps → ALL-CAPS FAQ
|
|
31
|
+
# style needs explicit coverage (PR #30 review: flash/codex/copilot flagged it).
|
|
32
|
+
c = ("QUESTION: How do I install?\nANSWER: Use pip.\n"
|
|
33
|
+
"QUESTION: Where are the docs?\nANSWER: In docs/.\n")
|
|
34
|
+
assert _is_chatlog(tmp_path, "FAQ.md", c) is False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_anonymized_ab_dialogue_still_detected(tmp_path):
|
|
38
|
+
# `A:`/`B:` anonymized speakers are a LEGIT conversation — must stay detected
|
|
39
|
+
# (guards against over-stop-listing single letters while fixing FAQ).
|
|
40
|
+
c = "A: hey did you see this\nB: yeah just now\nA: what do you think\n"
|
|
41
|
+
assert _is_chatlog(tmp_path, "dialogue.txt", c) is True
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""v1.2.4 — two clean wins from the in-house multi-agent recall review (PR #31).
|
|
2
|
+
|
|
3
|
+
The Claude multi-agent /code-review pass found two real issues the cross-model
|
|
4
|
+
(Gemini) reviews missed, both verified live:
|
|
5
|
+
|
|
6
|
+
F9: the stop-list is case-sensitive, but the case-sensitivity fix was applied
|
|
7
|
+
only to FAQ (Question/QUESTION) — ALL-CAPS email/usenet headers (FROM:/
|
|
8
|
+
SUBJECT:) still false-positived. Fix: match the stop-list case-insensitively
|
|
9
|
+
(also kills the dual-casing maintenance burden).
|
|
10
|
+
F4: `_string_has_speaker_dialogue` (the embedded-JSON-string path) was never
|
|
11
|
+
hardened — still `len(labels) >= 3` with no distinct/recurrence — so the same
|
|
12
|
+
dialogue detected inside a JSON string was rejected as prose. Fix: apply the
|
|
13
|
+
prose Rule 1 predicate (>=2 distinct, >=3 total, >=1 recurring).
|
|
14
|
+
|
|
15
|
+
Falsify-first: the two FP/asymmetry tests FAIL on v1.2.3, pass after the fix; the
|
|
16
|
+
guard tests must stay green throughout. See scratch/review/v1_2_2_fp_findings.md.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from file_observer.scanner import Scanner, ScannerConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _is_chatlog(tmp_path, name, content):
|
|
24
|
+
(tmp_path / name).write_text(content)
|
|
25
|
+
return Scanner(source_dir=tmp_path, config=ScannerConfig()).scan().files[0].is_chatlog
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestF9CaseInsensitiveStopList:
|
|
29
|
+
def test_allcaps_email_headers_not_flagged(self, tmp_path):
|
|
30
|
+
# ALL-CAPS recurring RFC822/usenet header dump — the FP class v1.2.2 targeted,
|
|
31
|
+
# but only title-case From/Subject were stop-listed.
|
|
32
|
+
c = "FROM: alice\nSUBJECT: re hi\nFROM: bob\nSUBJECT: re re hi\n"
|
|
33
|
+
assert _is_chatlog(tmp_path, "thread.txt", c) is False
|
|
34
|
+
|
|
35
|
+
def test_allcaps_faq_still_not_flagged(self, tmp_path):
|
|
36
|
+
c = "QUESTION: how?\nANSWER: like so.\nQUESTION: and then?\nANSWER: done.\n"
|
|
37
|
+
assert _is_chatlog(tmp_path, "FAQ.md", c) is False
|
|
38
|
+
|
|
39
|
+
def test_user_still_a_legit_speaker(self, tmp_path):
|
|
40
|
+
# `User` is intentionally NOT stop-listed; case-insensitive matching must
|
|
41
|
+
# not start suppressing it.
|
|
42
|
+
c = "User: hi there\nBob: hey\nUser: how are you\n"
|
|
43
|
+
assert _is_chatlog(tmp_path, "chat.txt", c) is True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestF4EmbeddedDialogueParity:
|
|
47
|
+
def test_embedded_json_dialogue_requires_recurrence(self, tmp_path):
|
|
48
|
+
# 3 distinct one-shot speakers in a JSON string value: rejected as prose
|
|
49
|
+
# (Rule 1), so must also be rejected here — no container-format asymmetry.
|
|
50
|
+
c = json.dumps({"chosen": "Alice: hi\n\nBob: hello\n\nCarol: hey there friend"})
|
|
51
|
+
assert _is_chatlog(tmp_path, "e.jsonl", c) is False
|
|
52
|
+
|
|
53
|
+
def test_hh_rlhf_embedded_still_detected(self, tmp_path):
|
|
54
|
+
# 2 distinct, recurring (Human x2) — a real embedded conversation, must stay.
|
|
55
|
+
c = json.dumps({"chosen": "\n\nHuman: hi\n\nAssistant: hello\n\nHuman: bye"})
|
|
56
|
+
assert _is_chatlog(tmp_path, "hh.jsonl", c) is True
|
|
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
|