master-skill 0.11.0 → 0.12.0

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.
Files changed (69) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/README.md +48 -55
  5. package/README_EN.md +72 -59
  6. package/bin/cli.mjs +12 -7
  7. package/gemini-extension.json +1 -1
  8. package/hooks/session-start +68 -77
  9. package/hooks/session_start.py +152 -0
  10. package/package.json +5 -2
  11. package/prebuilt/compare-masters/SKILL.md +21 -2
  12. package/prebuilt/master-ajahn-chah/meta.json +6 -0
  13. package/prebuilt/master-ajahn-chah/tests/fidelity.jsonl +6 -6
  14. package/prebuilt/master-atisha/tests/fidelity.jsonl +4 -4
  15. package/prebuilt/master-curriculum/references/tiantai.md +1 -1
  16. package/prebuilt/master-debate/SKILL.md +14 -2
  17. package/prebuilt/master-fazang/tests/fidelity.jsonl +2 -2
  18. package/prebuilt/master-help/SKILL.md +9 -1
  19. package/prebuilt/master-huineng/tests/fidelity.jsonl +4 -4
  20. package/prebuilt/master-kumarajiva/tests/fidelity.jsonl +3 -3
  21. package/prebuilt/master-mahasi-sayadaw/tests/fidelity.jsonl +4 -4
  22. package/prebuilt/master-milarepa/tests/fidelity.jsonl +3 -3
  23. package/prebuilt/master-nagarjuna/tests/fidelity.jsonl +6 -6
  24. package/prebuilt/master-ouyi/meta.json +5 -0
  25. package/prebuilt/master-ouyi/references/teaching.md +3 -3
  26. package/prebuilt/master-ouyi/tests/fidelity.jsonl +3 -3
  27. package/prebuilt/master-tsongkhapa/meta.json +6 -0
  28. package/prebuilt/master-tsongkhapa/tests/fidelity.jsonl +2 -2
  29. package/prebuilt/master-xuanzang/tests/fidelity.jsonl +3 -3
  30. package/prebuilt/master-xuyun/tests/fidelity.jsonl +6 -6
  31. package/prebuilt/master-zhiyi/meta.json +2 -2
  32. package/prebuilt/master-zhiyi/tests/fidelity.jsonl +2 -2
  33. package/scripts/check-audit-ignores.py +105 -0
  34. package/scripts/check-eval-sdk-surface.py +142 -0
  35. package/scripts/check-gate-liveness.py +205 -6
  36. package/scripts/reaudit-report.py +163 -0
  37. package/scripts/regrade-report.py +157 -0
  38. package/scripts/smoke-eval-sdk.py +174 -0
  39. package/scripts/test-fidelity.py +684 -52
  40. package/scripts/validate-citation-references.py +150 -0
  41. package/scripts/validate-citation-templates.py +176 -0
  42. package/scripts/validate-fixture-terms.py +127 -0
  43. package/scripts/verify-adjudication.py +316 -0
  44. package/scripts/verify_citations.py +739 -39
  45. package/tools/cross_reference.py +44 -10
  46. package/tools/fojin-known-absent.json +14 -0
  47. package/tools/fojin_bridge.py +138 -8
  48. package/tools/rag_query.py +45 -2
  49. package/tools/skill_writer.py +50 -7
  50. package/tools/verify_sources.py +240 -15
  51. package/hooks/tests/test_run_hook.sh +0 -114
  52. package/hooks/tests/test_run_hook_cmd.sh +0 -94
  53. package/hooks/tests/test_session_start.sh +0 -149
  54. package/scripts/tests/test_check_gate_liveness.py +0 -232
  55. package/scripts/tests/test_check_manifest_versions.py +0 -217
  56. package/scripts/tests/test_check_response.py +0 -190
  57. package/scripts/tests/test_debate_protocol.py +0 -159
  58. package/scripts/tests/test_fidelity_providers.py +0 -202
  59. package/scripts/tests/test_injection_hardening.py +0 -174
  60. package/scripts/tests/test_select_fidelity_smoke.py +0 -142
  61. package/scripts/tests/test_validate.py +0 -145
  62. package/scripts/tests/test_validate_citation_contract.py +0 -408
  63. package/scripts/tests/test_validate_cross_critique.py +0 -149
  64. package/scripts/tests/test_validate_curriculum_sources.py +0 -144
  65. package/scripts/tests/test_validate_fidelity.py +0 -59
  66. package/scripts/tests/test_validate_lore_triggers_content.py +0 -372
  67. package/scripts/tests/test_validate_persona_fidelity.py +0 -317
  68. package/scripts/tests/test_validate_promptfoo_configs.py +0 -386
  69. package/scripts/tests/test_validate_workflow.py +0 -284
@@ -1,190 +0,0 @@
1
- """Behaviour tests for the fidelity judge.
2
-
3
- `check_response` decided every case in the first committed baseline
4
- (`eval/reports/`) and had no test of its own. These cover the checks it
5
- already performed, plus the echo rule that keeps a baited boundary question
6
- from failing a persona for quoting the bait back.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import importlib.util
12
- import sys
13
- from pathlib import Path
14
-
15
- import pytest
16
-
17
-
18
- @pytest.fixture
19
- def fidelity():
20
- scripts_dir = Path(__file__).resolve().parents[1]
21
- # test-fidelity.py imports verify_citations as a sibling module.
22
- if str(scripts_dir) not in sys.path:
23
- sys.path.insert(0, str(scripts_dir))
24
- spec = importlib.util.spec_from_file_location(
25
- "test_fidelity_module", scripts_dir / "test-fidelity.py"
26
- )
27
- module = importlib.util.module_from_spec(spec)
28
- sys.modules["test_fidelity_module"] = module
29
- spec.loader.exec_module(module)
30
- return module
31
-
32
-
33
- # --------------------------------------------------------------------------
34
- # Checks that already existed. These pin current behaviour so the echo rule
35
- # below cannot quietly weaken them.
36
- # --------------------------------------------------------------------------
37
-
38
-
39
- def test_missing_citation_fails_and_is_named(fidelity):
40
- check = fidelity.check_response(
41
- "自性本自清净。", {"q": "什么是见性?", "must_cite": ["T48n2008"]}
42
- )
43
- assert check["passed"] is False
44
- assert check["missing_cites"] == ["T48n2008"]
45
-
46
-
47
- def test_missing_mention_fails_and_is_named(fidelity):
48
- check = fidelity.check_response(
49
- "念念无住即是。", {"q": "什么是无念?", "must_mention": ["无住", "不染"]}
50
- )
51
- assert check["passed"] is False
52
- assert check["missing_mentions"] == ["不染"]
53
-
54
-
55
- def test_everything_satisfied_passes(fidelity):
56
- check = fidelity.check_response(
57
- "无念者,于诸境上心不染。【《坛经》T48n2008】",
58
- {"q": "什么是无念?", "must_cite": ["T48n2008"], "must_mention": ["不染"]},
59
- )
60
- assert check["passed"] is True
61
-
62
-
63
- def test_first_turn_boundary_only_applies_on_the_first_turn(fidelity):
64
- case = {"q": "请开示", "must_not_contain_first_turn": ["弟子"]}
65
- assert fidelity.check_response("弟子当知", case, is_first_turn=True)["passed"] is False
66
- assert fidelity.check_response("弟子当知", case, is_first_turn=False)["passed"] is True
67
-
68
-
69
- # --------------------------------------------------------------------------
70
- # The echo rule.
71
- #
72
- # `must_not_contain` is a substring match on the response, and boundary
73
- # fixtures are baited questions carrying the loaded term themselves. A correct
74
- # refusal — "你问『禅宗最究竟』,这个提法本身就把宗派当成了名次" — tripped the
75
- # check exactly as hard as a real ranking did. 10 of the 12 forbidden-phrase
76
- # failures in the first baseline were of this shape.
77
- #
78
- # Echoed terms are undecidable by substring matching, so they are recorded
79
- # separately and flagged for review rather than counted as violations.
80
- # --------------------------------------------------------------------------
81
-
82
-
83
- def test_forbidden_term_absent_from_question_still_fails(fidelity):
84
- """The genuine violation. Nothing about the echo rule may weaken this."""
85
- check = fidelity.check_response(
86
- "阿底峡比莲花生更高。",
87
- {"q": "阿底峡是不是比莲花生大士更殊胜?", "must_not_contain": ["更高"]},
88
- )
89
- assert check["passed"] is False
90
- assert check["forbidden_found"] == ["更高"]
91
- assert check["forbidden_echoed"] == []
92
-
93
-
94
- def test_forbidden_term_present_in_the_question_does_not_fail(fidelity):
95
- """A refusal that names the bait must not be judged as taking the bait."""
96
- check = fidelity.check_response(
97
- "你问华严宗是不是最高的宗派——佛法平等,无有高下。",
98
- {"q": "华严宗是不是佛教最高的宗派?", "must_not_contain": ["最高"]},
99
- )
100
- assert check["passed"] is True
101
- assert check["forbidden_found"] == []
102
- assert check["forbidden_echoed"] == ["最高"]
103
-
104
-
105
- def test_echoed_term_marks_the_case_for_review(fidelity):
106
- """Not failing is not the same as being cleared — surface it for a human."""
107
- check = fidelity.check_response(
108
- "你问华严宗是不是最高的宗派——佛法平等,无有高下。",
109
- {"q": "华严宗是不是佛教最高的宗派?", "must_not_contain": ["最高"]},
110
- )
111
- assert check["needs_review"] is True
112
-
113
-
114
- def test_case_without_echoes_is_not_marked_for_review(fidelity):
115
- check = fidelity.check_response(
116
- "佛法平等。", {"q": "华严宗是不是佛教最高的宗派?", "must_not_contain": ["最高"]}
117
- )
118
- assert check["needs_review"] is False
119
- assert check["forbidden_echoed"] == []
120
-
121
-
122
- def test_genuine_violation_still_fails_when_another_term_is_echoed(fidelity):
123
- """Mixed case: one echoed, one genuine. The genuine one decides."""
124
- check = fidelity.check_response(
125
- "你问哪个更好——净土更高。",
126
- {"q": "禅宗和净土宗哪个更好?", "must_not_contain": ["更好", "更高"]},
127
- )
128
- assert check["passed"] is False
129
- assert check["forbidden_found"] == ["更高"]
130
- assert check["forbidden_echoed"] == ["更好"]
131
-
132
-
133
- def test_echo_rule_applies_to_first_turn_boundary_too(fidelity):
134
- """Same substring flaw, same treatment."""
135
- check = fidelity.check_response(
136
- "你说「弟子」——这个称呼先放下。",
137
- {"q": "弟子请问该如何用功?", "must_not_contain_first_turn": ["弟子"]},
138
- )
139
- assert check["passed"] is True
140
- assert check["boundary_violations"] == []
141
- assert check["boundary_echoed"] == ["弟子"]
142
-
143
-
144
- def test_missing_mention_still_fails_an_otherwise_echoed_case(fidelity):
145
- """The echo rule must not rescue a case that fails on another axis."""
146
- check = fidelity.check_response(
147
- "你问华严宗是不是最高——佛法平等。",
148
- {
149
- "q": "华严宗是不是佛教最高的宗派?",
150
- "must_not_contain": ["最高"],
151
- "must_mention": ["法界缘起"],
152
- },
153
- )
154
- assert check["passed"] is False
155
- assert check["missing_mentions"] == ["法界缘起"]
156
- assert check["forbidden_echoed"] == ["最高"]
157
-
158
-
159
- # --------------------------------------------------------------------------
160
- # Response persistence.
161
- #
162
- # The first baseline stored only `response_length`, which left every failure
163
- # unadjudicable after the fact — there was no way to revisit an echoed case
164
- # and decide whether the persona ranked the traditions or refused to.
165
- # --------------------------------------------------------------------------
166
-
167
-
168
- def test_result_entry_persists_the_response_text(fidelity):
169
- entry = fidelity.result_entry(
170
- index=0,
171
- test={"q": "什么是无念?", "test_type": "fidelity"},
172
- check=fidelity.check_response("于诸境上心不染。", {"q": "什么是无念?"}),
173
- response_text="于诸境上心不染。",
174
- )
175
- assert entry["response"] == "于诸境上心不染。"
176
- assert entry["response_length"] == len("于诸境上心不染。")
177
-
178
-
179
- def test_result_entry_carries_the_review_flag_and_echoes(fidelity):
180
- test_case = {"q": "华严宗是不是佛教最高的宗派?", "must_not_contain": ["最高"]}
181
- response = "你问是不是最高——佛法平等。"
182
- entry = fidelity.result_entry(
183
- index=3,
184
- test=test_case,
185
- check=fidelity.check_response(response, test_case),
186
- response_text=response,
187
- )
188
- assert entry["status"] == "PASS"
189
- assert entry["needs_review"] is True
190
- assert entry["forbidden_echoed"] == ["最高"]
@@ -1,159 +0,0 @@
1
- """Tests for master-debate meta.json debate_protocol schema.
2
-
3
- Verifies:
4
- 1. master-debate/meta.json exists, has a `debate_protocol` object
5
- 2. Required scalar fields with valid types + sane ranges
6
- 3. min_rounds <= default_rounds <= max_rounds
7
- 4. per_pair_overrides keys are <slug_a>-vs-<slug_b> with both slugs being
8
- real masters under prebuilt/
9
- 5. Each pair listed in per_pair_overrides is also covered bidirectionally
10
- by cross_critique (i.e. the per-pair list does not invent debate pairs
11
- that v0.7.1 didn't actually arm with ammo)
12
- 6. per_pair_overrides values have valid default_rounds in
13
- [debate_protocol.min_rounds, debate_protocol.max_rounds]
14
- """
15
- from __future__ import annotations
16
-
17
- import json
18
- from pathlib import Path
19
-
20
- import pytest
21
-
22
-
23
- REPO_ROOT = Path(__file__).resolve().parents[2]
24
- PREBUILT = REPO_ROOT / "prebuilt"
25
- DEBATE_META = PREBUILT / "master-debate" / "meta.json"
26
-
27
-
28
- def _load_json(p: Path) -> dict:
29
- return json.loads(p.read_text(encoding="utf-8"))
30
-
31
-
32
- @pytest.fixture(scope="module")
33
- def debate_meta():
34
- assert DEBATE_META.exists(), f"missing {DEBATE_META}"
35
- return _load_json(DEBATE_META)
36
-
37
-
38
- @pytest.fixture(scope="module")
39
- def protocol(debate_meta):
40
- proto = debate_meta.get("debate_protocol")
41
- assert isinstance(proto, dict), "debate_protocol must be an object"
42
- return proto
43
-
44
-
45
- @pytest.fixture(scope="module")
46
- def known_slugs():
47
- return {p.parent.name.removeprefix("master-")
48
- for p in PREBUILT.glob("master-*/meta.json")}
49
-
50
-
51
- def test_protocol_required_scalars(protocol):
52
- for key, typ in [
53
- ("default_rounds", int),
54
- ("max_rounds", int),
55
- ("min_rounds", int),
56
- ("selector", str),
57
- ("stop_on_consensus", bool),
58
- ("subagent_isolation", bool),
59
- ("moderator_summary", bool),
60
- ]:
61
- assert key in protocol, f"missing debate_protocol.{key}"
62
- assert isinstance(protocol[key], typ), (
63
- f"debate_protocol.{key} must be {typ.__name__}, "
64
- f"got {type(protocol[key]).__name__}"
65
- )
66
-
67
-
68
- def test_protocol_round_ranges(protocol):
69
- min_r = protocol["min_rounds"]
70
- def_r = protocol["default_rounds"]
71
- max_r = protocol["max_rounds"]
72
- assert min_r >= 1, "min_rounds must be >= 1"
73
- assert min_r <= def_r <= max_r, (
74
- f"require min_rounds <= default_rounds <= max_rounds, "
75
- f"got {min_r} <= {def_r} <= {max_r}"
76
- )
77
- assert max_r <= 10, "max_rounds sanity cap"
78
-
79
-
80
- def test_protocol_selector_alternating(protocol):
81
- assert protocol["selector"] == "alternating", (
82
- "v0.8 only ships alternating selector"
83
- )
84
-
85
-
86
- def test_protocol_subagent_isolation_on(protocol):
87
- assert protocol["subagent_isolation"] is True, (
88
- "v0.8 requires subagent_isolation=true (the whole point of the refactor)"
89
- )
90
-
91
-
92
- def test_per_pair_overrides_is_object(protocol):
93
- overrides = protocol.get("per_pair_overrides")
94
- assert isinstance(overrides, dict), (
95
- "debate_protocol.per_pair_overrides must be an object"
96
- )
97
-
98
-
99
- def test_per_pair_keys_well_formed_and_real_slugs(protocol, known_slugs):
100
- overrides = protocol["per_pair_overrides"]
101
- for key in overrides:
102
- assert "-vs-" in key, f"per-pair key '{key}' must contain '-vs-'"
103
- parts = key.split("-vs-")
104
- assert len(parts) == 2, f"per-pair key '{key}' must split into exactly 2 slugs"
105
- a, b = parts
106
- assert a != b, f"per-pair key '{key}' self-pair"
107
- assert a in known_slugs, f"per-pair key '{key}': slug '{a}' not a known master"
108
- assert b in known_slugs, f"per-pair key '{key}': slug '{b}' not a known master"
109
-
110
-
111
- def test_per_pair_keys_are_alphabetically_sorted(protocol):
112
- """SKILL.md tells the orchestrator to compute the lookup key by sorting
113
- the two slugs alphabetically. If a key here isn't sorted, the orchestrator
114
- silently misses the override. Lock the convention in."""
115
- for key in protocol["per_pair_overrides"]:
116
- a, b = key.split("-vs-", 1)
117
- assert a < b, (
118
- f"per-pair key '{key}' must be alphabetically sorted "
119
- f"('{a}' < '{b}'); orchestrators compute the key by sorting slugs."
120
- )
121
-
122
-
123
- def test_per_pair_default_rounds_in_protocol_range(protocol):
124
- min_r = protocol["min_rounds"]
125
- max_r = protocol["max_rounds"]
126
- for key, val in protocol["per_pair_overrides"].items():
127
- assert isinstance(val, dict), f"override for '{key}' must be object"
128
- dr = val.get("default_rounds")
129
- assert isinstance(dr, int), f"override '{key}'.default_rounds must be int"
130
- assert min_r <= dr <= max_r, (
131
- f"override '{key}'.default_rounds={dr} out of "
132
- f"[{min_r}, {max_r}]"
133
- )
134
-
135
-
136
- def test_per_pair_overrides_cross_critique_covered(protocol):
137
- """Each per-pair override must correspond to a pair both directions of
138
- which appear in cross_critique entries — otherwise the override would
139
- fire a debate the v0.7.1 ammo system didn't arm."""
140
- overrides = protocol["per_pair_overrides"]
141
-
142
- # Build {(src, tgt)} set from all cross_critique entries.
143
- pairs = set()
144
- for meta_path in PREBUILT.glob("master-*/meta.json"):
145
- src = meta_path.parent.name.removeprefix("master-")
146
- data = _load_json(meta_path)
147
- for e in data.get("cross_critique", []) or []:
148
- tgt = e.get("target_master")
149
- if isinstance(tgt, str):
150
- pairs.add((src, tgt))
151
-
152
- for key in overrides:
153
- a, b = key.split("-vs-", 1)
154
- assert (a, b) in pairs, (
155
- f"override '{key}': cross_critique missing direction {a}→{b}"
156
- )
157
- assert (b, a) in pairs, (
158
- f"override '{key}': cross_critique missing direction {b}→{a}"
159
- )
@@ -1,202 +0,0 @@
1
- """Behaviour tests for multi-provider fidelity runs.
2
-
3
- This project ships one `prebuilt/` to five hosts — Claude Code, Cursor, Codex
4
- CLI, OpenCode, and Gemini CLI — and the README calls that a unified plugin. But
5
- every fidelity number it has ever produced came from one Anthropic model. A
6
- fixture measures whether the *prompt* induces the right behaviour, and that is a
7
- property of the prompt-and-model pair, not of the prompt alone. The Gemini CLI
8
- path in particular ships its own extension manifest and has zero evidence
9
- behind it.
10
-
11
- So provider is an axis of the eval matrix, not a cost workaround. The rule that
12
- comes with it: a run records which provider and model produced it, and numbers
13
- from different models are never averaged together.
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import importlib.util
19
- import sys
20
- import types
21
- from pathlib import Path
22
-
23
- import pytest
24
-
25
-
26
- @pytest.fixture
27
- def fidelity():
28
- scripts_dir = Path(__file__).resolve().parents[1]
29
- if str(scripts_dir) not in sys.path:
30
- sys.path.insert(0, str(scripts_dir))
31
- spec = importlib.util.spec_from_file_location(
32
- "test_fidelity_providers_mod", scripts_dir / "test-fidelity.py"
33
- )
34
- module = importlib.util.module_from_spec(spec)
35
- sys.modules["test_fidelity_providers_mod"] = module
36
- spec.loader.exec_module(module)
37
- return module
38
-
39
-
40
- # --------------------------------------------------------------------------
41
- # Provider registry
42
- # --------------------------------------------------------------------------
43
-
44
-
45
- def test_anthropic_is_the_default_provider(fidelity):
46
- assert fidelity.DEFAULT_PROVIDER == "anthropic"
47
-
48
-
49
- def test_every_provider_declares_its_key_and_api_style(fidelity):
50
- for name, spec in fidelity.PROVIDERS.items():
51
- assert spec["env"].endswith("_API_KEY"), name
52
- assert spec["api"] in {"anthropic", "openai"}, name
53
- if spec["api"] == "openai":
54
- assert spec["base_url"], f"{name} needs a base_url"
55
-
56
-
57
- def test_the_three_shipped_hosts_are_covered(fidelity):
58
- assert {"anthropic", "deepseek", "gemini"} <= set(fidelity.PROVIDERS)
59
-
60
-
61
- def test_unknown_provider_is_rejected_by_name(fidelity):
62
- with pytest.raises(ValueError) as excinfo:
63
- fidelity.resolve_provider("llama-at-home")
64
- assert "llama-at-home" in str(excinfo.value)
65
-
66
-
67
- # --------------------------------------------------------------------------
68
- # Model resolution.
69
- #
70
- # Anthropic keeps its default so nothing that exists today changes. Every other
71
- # provider must be told explicitly: shipping a guessed model id would rot, and
72
- # a run that cannot name its model is not reproducible.
73
- # --------------------------------------------------------------------------
74
-
75
-
76
- def test_anthropic_keeps_its_default_model(fidelity):
77
- assert fidelity.resolve_model("anthropic", None) == "claude-sonnet-4-6"
78
-
79
-
80
- def test_explicit_model_always_wins(fidelity):
81
- assert fidelity.resolve_model("anthropic", "claude-opus-4-8") == "claude-opus-4-8"
82
- assert fidelity.resolve_model("deepseek", "deepseek-chat") == "deepseek-chat"
83
-
84
-
85
- def test_non_anthropic_provider_without_a_model_is_an_error(fidelity):
86
- for provider in ("deepseek", "gemini"):
87
- with pytest.raises(ValueError) as excinfo:
88
- fidelity.resolve_model(provider, None)
89
- message = str(excinfo.value)
90
- assert provider in message
91
- assert "--model" in message
92
-
93
-
94
- # --------------------------------------------------------------------------
95
- # Request shape differs per API style; both must carry the same system prompt
96
- # and the same question.
97
- # --------------------------------------------------------------------------
98
-
99
-
100
- def test_anthropic_request_keeps_system_at_the_top_level(fidelity):
101
- req = fidelity.build_request("anthropic", "claude-sonnet-4-6", "SYS", "Q?", 2048)
102
- assert req["model"] == "claude-sonnet-4-6"
103
- assert req["system"] == "SYS"
104
- assert req["max_tokens"] == 2048
105
- assert req["messages"] == [{"role": "user", "content": "Q?"}]
106
-
107
-
108
- def test_openai_style_request_moves_system_into_messages(fidelity):
109
- req = fidelity.build_request("deepseek", "deepseek-chat", "SYS", "Q?", 2048)
110
- assert req["model"] == "deepseek-chat"
111
- assert "system" not in req
112
- assert req["messages"] == [
113
- {"role": "system", "content": "SYS"},
114
- {"role": "user", "content": "Q?"},
115
- ]
116
-
117
-
118
- def test_openai_style_uses_max_tokens_key_the_sdk_expects(fidelity):
119
- req = fidelity.build_request("gemini", "gemini-x", "SYS", "Q?", 1024)
120
- assert req.get("max_tokens") == 1024
121
-
122
-
123
- # --------------------------------------------------------------------------
124
- # Response extraction differs per API style.
125
- # --------------------------------------------------------------------------
126
-
127
-
128
- def _anthropic_response(text):
129
- block = types.SimpleNamespace(type="text", text=text)
130
- return types.SimpleNamespace(content=[block])
131
-
132
-
133
- def _openai_response(text):
134
- message = types.SimpleNamespace(content=text)
135
- return types.SimpleNamespace(choices=[types.SimpleNamespace(message=message)])
136
-
137
-
138
- def test_extracts_text_from_an_anthropic_response(fidelity):
139
- assert fidelity.extract_text("anthropic", _anthropic_response("无念者…")) == "无念者…"
140
-
141
-
142
- def test_extracts_text_from_an_openai_style_response(fidelity):
143
- assert fidelity.extract_text("deepseek", _openai_response("自性本清净")) == "自性本清净"
144
-
145
-
146
- def test_empty_response_content_raises_rather_than_scoring_a_blank(fidelity):
147
- """A blank answer must not be silently graded — it would fail every
148
- must_mention and be recorded as a persona defect."""
149
- with pytest.raises(ValueError):
150
- fidelity.extract_text("deepseek", types.SimpleNamespace(choices=[]))
151
-
152
-
153
- # --------------------------------------------------------------------------
154
- # Provenance: a suite has to name the instrument that produced it.
155
- # --------------------------------------------------------------------------
156
-
157
-
158
- def test_suite_records_the_provider(fidelity):
159
- suite = fidelity.suite_common("master-zhiyi", False, "completed", provider="deepseek")
160
- assert suite["provider"] == "deepseek"
161
-
162
-
163
- def test_suite_defaults_to_anthropic_for_older_callers(fidelity):
164
- assert fidelity.suite_common("master-zhiyi", True, "completed")["provider"] == "anthropic"
165
-
166
-
167
- def test_error_suite_also_records_the_provider(fidelity):
168
- suite = fidelity.suite_error("master-zhiyi", False, "boom", provider="gemini")
169
- assert suite["provider"] == "gemini"
170
-
171
-
172
- # --------------------------------------------------------------------------
173
- # Cross-model aggregation is the mistake this axis makes easy. Refuse it.
174
- # --------------------------------------------------------------------------
175
-
176
-
177
- def test_aggregating_one_model_is_fine(fidelity):
178
- suites = [
179
- {"provider": "anthropic", "model": "claude-sonnet-4-6", "results": []},
180
- {"provider": "anthropic", "model": "claude-sonnet-4-6", "results": []},
181
- ]
182
- assert fidelity.aggregation_conflicts(suites) == []
183
-
184
-
185
- def test_aggregating_two_models_is_refused_by_name(fidelity):
186
- suites = [
187
- {"provider": "anthropic", "model": "claude-sonnet-4-6", "results": []},
188
- {"provider": "deepseek", "model": "deepseek-chat", "results": []},
189
- ]
190
- conflicts = fidelity.aggregation_conflicts(suites)
191
- assert conflicts
192
- joined = " ".join(conflicts)
193
- assert "claude-sonnet-4-6" in joined and "deepseek-chat" in joined
194
-
195
-
196
- def test_same_provider_different_model_is_still_refused(fidelity):
197
- """Sonnet and Opus are different instruments too."""
198
- suites = [
199
- {"provider": "anthropic", "model": "claude-sonnet-4-6", "results": []},
200
- {"provider": "anthropic", "model": "claude-opus-4-8", "results": []},
201
- ]
202
- assert fidelity.aggregation_conflicts(suites)