@heretek-ai/epistemic-swarm 0.2.3 → 0.3.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 (138) hide show
  1. package/.agents/skills/brainstorming/SKILL.md +13 -0
  2. package/.agents/skills/code_audit/SKILL.md +13 -0
  3. package/.agents/skills/epistemic_search/SKILL.md +13 -0
  4. package/.agents/skills/grilling/SKILL.md +13 -0
  5. package/.agents/skills/oss_scout/SKILL.md +13 -0
  6. package/.agents/skills/research_cache/SKILL.md +13 -0
  7. package/.agents/skills/swarm_config/SKILL.md +13 -0
  8. package/.claude-plugin/plugin.json +15 -5
  9. package/.omp/README.md +39 -0
  10. package/.omp/SYSTEM.md +12 -0
  11. package/.omp/commands/audit.md +10 -0
  12. package/.omp/commands/brainstorming.md +12 -0
  13. package/.omp/commands/grill.md +9 -0
  14. package/.omp/commands/scout.md +10 -0
  15. package/.omp/commands/swarm-config.md +10 -0
  16. package/.omp/commands/swarm.md +9 -0
  17. package/.omp/hooks/post/epistemic-audit.ts +16 -0
  18. package/.omp/hooks/pre/epistemic-redirect.ts +21 -0
  19. package/.omp/prompts/brainstorming.md +8 -0
  20. package/.omp/prompts/swarm.md +8 -0
  21. package/MARKETPLACE.md +8 -0
  22. package/README.md +32 -3
  23. package/bin/cli.js +27 -3
  24. package/config/domain_packs/biopharma.json +23 -0
  25. package/config/domain_packs/legal.json +19 -0
  26. package/config/domain_packs/quant.json +19 -0
  27. package/config/mcp-research-servers.json +7 -0
  28. package/config/mcp_launcher.py +48 -137
  29. package/config/opencode-snippet.json +20 -1
  30. package/config/searxng_mcp.py +42 -83
  31. package/extensions/pi/index.js +138 -73
  32. package/install.sh +20 -4
  33. package/package.json +40 -5
  34. package/plugins/antigravity/README.md +28 -0
  35. package/plugins/antigravity/agents/alpha-thesis.md +6 -0
  36. package/plugins/antigravity/agents/beta-antithesis.md +7 -0
  37. package/plugins/antigravity/agents/brainstormer.md +7 -0
  38. package/plugins/antigravity/agents/epistemic-auditor.md +5 -0
  39. package/plugins/antigravity/hooks.json +23 -0
  40. package/plugins/antigravity/mcp_config.json +33 -0
  41. package/plugins/antigravity/plugin.json +21 -0
  42. package/plugins/antigravity/rules/epistemic-integrity.md +6 -0
  43. package/plugins/antigravity/skills/brainstorming/SKILL.md +13 -0
  44. package/plugins/antigravity/skills/code_audit/SKILL.md +13 -0
  45. package/plugins/antigravity/skills/epistemic_search/SKILL.md +13 -0
  46. package/plugins/antigravity/skills/grilling/SKILL.md +13 -0
  47. package/plugins/antigravity/skills/oss_scout/SKILL.md +13 -0
  48. package/plugins/antigravity/skills/research_cache/SKILL.md +13 -0
  49. package/plugins/antigravity/skills/swarm_config/SKILL.md +13 -0
  50. package/plugins/codex/AGENTS.md.snippet +10 -0
  51. package/plugins/codex/README.md +37 -0
  52. package/plugins/codex/config.toml.snippet +28 -0
  53. package/plugins/codex/openai.yaml +24 -0
  54. package/plugins/codex/skills/brainstorming/SKILL.md +13 -0
  55. package/plugins/codex/skills/code_audit/SKILL.md +13 -0
  56. package/plugins/codex/skills/epistemic_search/SKILL.md +13 -0
  57. package/plugins/codex/skills/grilling/SKILL.md +13 -0
  58. package/plugins/codex/skills/oss_scout/SKILL.md +13 -0
  59. package/plugins/codex/skills/research_cache/SKILL.md +13 -0
  60. package/plugins/codex/skills/swarm_config/SKILL.md +13 -0
  61. package/plugins/gemini/GEMINI.md +15 -0
  62. package/plugins/gemini/README.md +19 -0
  63. package/plugins/gemini/commands/audit.toml +6 -0
  64. package/plugins/gemini/commands/brainstorming.toml +10 -0
  65. package/plugins/gemini/commands/grill.toml +6 -0
  66. package/plugins/gemini/commands/scout.toml +7 -0
  67. package/plugins/gemini/commands/swarm-config.toml +7 -0
  68. package/plugins/gemini/commands/swarm.toml +8 -0
  69. package/plugins/gemini/gemini-extension.json +38 -0
  70. package/plugins/gemini/hooks/hooks.json +11 -0
  71. package/plugins/gemini/skills/brainstorming/SKILL.md +13 -0
  72. package/plugins/gemini/skills/code_audit/SKILL.md +13 -0
  73. package/plugins/gemini/skills/epistemic_search/SKILL.md +13 -0
  74. package/plugins/gemini/skills/grilling/SKILL.md +13 -0
  75. package/plugins/gemini/skills/oss_scout/SKILL.md +13 -0
  76. package/plugins/gemini/skills/research_cache/SKILL.md +13 -0
  77. package/plugins/gemini/skills/swarm_config/SKILL.md +13 -0
  78. package/plugins/opencode/index.js +326 -263
  79. package/prompts/agent_brainstormer.md +97 -0
  80. package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
  81. package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
  82. package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
  83. package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
  84. package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
  85. package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
  86. package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
  87. package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
  88. package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
  89. package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
  90. package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
  91. package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
  92. package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
  93. package/runner/auctioneer.py +169 -0
  94. package/runner/auditor_engine.py +127 -12
  95. package/runner/claim_store.py +361 -0
  96. package/runner/claim_witness.py +183 -0
  97. package/runner/living_dossiers.py +355 -0
  98. package/runner/mcp_protocol.py +188 -0
  99. package/runner/mcp_server.py +567 -0
  100. package/runner/pcrb.py +212 -0
  101. package/runner/pcrb_verify.py +237 -0
  102. package/runner/refinement.py +335 -0
  103. package/runner/research_swarm.py +378 -94
  104. package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
  105. package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
  106. package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
  107. package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
  108. package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
  109. package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
  110. package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
  111. package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
  112. package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
  113. package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
  114. package/runner/tests/fixtures/auction_objective.json +35 -0
  115. package/runner/tests/fixtures/borderline_claims.json +27 -0
  116. package/runner/tests/fixtures/divergence_objectives.json +31 -0
  117. package/runner/tests/test_auction_order.py +173 -0
  118. package/runner/tests/test_claim_store.py +162 -0
  119. package/runner/tests/test_claim_witness.py +186 -0
  120. package/runner/tests/test_domain_packs.py +217 -0
  121. package/runner/tests/test_fleet_seam.py +113 -0
  122. package/runner/tests/test_living_dossiers.py +204 -0
  123. package/runner/tests/test_mcp_server.py +212 -0
  124. package/runner/tests/test_pcrb.py +240 -0
  125. package/runner/tests/test_refinement.py +255 -0
  126. package/runner/tests/test_swarm.py +137 -30
  127. package/scripts/auction_experiment.py +180 -0
  128. package/scripts/build_adapters.py +183 -0
  129. package/scripts/divergence_experiment.py +184 -0
  130. package/skills/brainstorming/SKILL.md +106 -0
  131. package/skills/brainstorming/__init__.py +1 -0
  132. package/skills/brainstorming/scripts/brainstorm.py +200 -0
  133. package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
  134. package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
  135. package/skills/swarm_config/SKILL.md +1 -1
  136. package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
  137. package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
  138. package/skills/swarm_config/configure.py +45 -11
@@ -0,0 +1,335 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Refinement-type checker for epistemic claims + pure E(D) scoring.
4
+
5
+ Machine-checkable invariants over ClaimWitness records:
6
+ VERIFIED => has source_hash AND verbatim_quote AND passes witness_check
7
+ INFERRED => parent_claims non-empty AND deductive_logic present
8
+ HYPOTHESIS => falsification present (measurable test)
9
+ NEGATIVE_KNOWLEDGE => query AND finding present
10
+ cross-reference => every parent_claims id resolves within the dossier set
11
+
12
+ Also extracts the epistemic score E(D) into a pure function so Domain Packs
13
+ (Stream G) can override constitution without touching the auditor.
14
+
15
+ Constitution defaults reproduce the LEGACY inlined arithmetic exactly
16
+ (flat 1.0 per verified claim), because the published docs' tier-weighted
17
+ V(c_i) has never been what the runtime computes. Tier weighting becomes an
18
+ opt-in constitution — nothing flips without an explicit pack.
19
+ """
20
+
21
+ from dataclasses import dataclass, field
22
+ from typing import Any, Dict, Iterable, List, Optional, Tuple
23
+
24
+ from runner.claim_witness import (
25
+ KIND_CLAIM,
26
+ KIND_HYPOTHESIS,
27
+ KIND_INFERENCE,
28
+ KIND_NEGATIVE_KNOWLEDGE,
29
+ STATUS_REJECTED,
30
+ TAG_HYPOTHESIS,
31
+ TAG_INFERRED,
32
+ TAG_NEGATIVE_KNOWLEDGE,
33
+ TAG_VERIFIED,
34
+ ClaimWitness,
35
+ witness_check,
36
+ )
37
+
38
+
39
+ @dataclass
40
+ class Violation:
41
+ claim_id: str
42
+ rule: str
43
+ message: str
44
+
45
+ def to_dict(self) -> Dict[str, str]:
46
+ return {"claim_id": self.claim_id, "rule": self.rule, "message": self.message}
47
+
48
+
49
+ @dataclass
50
+ class Constitution:
51
+ """Scoring + enforcement policy. Defaults == legacy auditor behavior."""
52
+
53
+ # Per-verified-claim weight. Flat 1.0 reproduces legacy E(D).
54
+ tier_weights: Dict[str, float] = field(
55
+ default_factory=lambda: {"__default__": 1.0}
56
+ )
57
+ neg_bonus: float = 0.5
58
+ reject_penalty: float = 2.5
59
+ accept_threshold: float = 0.65
60
+
61
+ # Domain-pack enforcement (Stream G); empty = no restriction.
62
+ banned_domains: List[str] = field(default_factory=list)
63
+ mandatory_tags: List[str] = field(default_factory=list)
64
+ retraction_policy: str = "standard" # or "zero_tolerance"
65
+
66
+ def weight_for(self, tier: Optional[str]) -> float:
67
+ if tier is not None and tier in self.tier_weights:
68
+ return self.tier_weights[tier]
69
+ return self.tier_weights.get("__default__", 1.0)
70
+
71
+
72
+ LEGACY_CONSTITUTION = Constitution()
73
+
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Domain Packs (Stream G): pluggable epistemic constitutions
77
+ # ---------------------------------------------------------------------------
78
+
79
+
80
+ def _project_root():
81
+ from pathlib import Path
82
+
83
+ return Path(__file__).resolve().parent.parent
84
+
85
+
86
+ def load_domain_pack(pack: str) -> Constitution:
87
+ """Load a Constitution from a pack id (config/domain_packs/<id>.json) or path.
88
+
89
+ Unknown/unloadable packs raise FileNotFoundError — silent fallback to
90
+ legacy would hide a regulatory misconfiguration.
91
+ """
92
+ import json
93
+ from pathlib import Path
94
+
95
+ candidates = []
96
+ p = Path(pack)
97
+ if p.suffix == ".json" or "/" in pack:
98
+ candidates.append(p)
99
+ else:
100
+ candidates.append(_project_root() / "config" / "domain_packs" / f"{pack}.json")
101
+
102
+ for cand in candidates:
103
+ if cand.exists():
104
+ data = json.loads(cand.read_text(encoding="utf-8"))
105
+ return Constitution(
106
+ tier_weights=data.get("tier_weights") or {"__default__": 1.0},
107
+ neg_bonus=float(data.get("neg_bonus", 0.5)),
108
+ reject_penalty=float(data.get("reject_penalty", 2.5)),
109
+ accept_threshold=float(data.get("accept_threshold", 0.65)),
110
+ banned_domains=list(data.get("banned_domains") or []),
111
+ mandatory_tags=list(data.get("mandatory_tags") or []),
112
+ retraction_policy=str(data.get("retraction_policy") or "standard"),
113
+ )
114
+ raise FileNotFoundError(f"domain pack not found: {pack}")
115
+
116
+
117
+ def claim_verdict(
118
+ claim: "ClaimWitness",
119
+ hasher: Any = None,
120
+ constitution: Optional[Constitution] = None,
121
+ retracted_hashes: Optional[set] = None,
122
+ ) -> Dict[str, Any]:
123
+ """Per-claim ACCEPTED/REJECTED gate used by Domain Packs.
124
+
125
+ A claim is REJECTED when:
126
+ - any check_invariants rule fires under this constitution
127
+ (incl. banned domains, missing mandatory tags), or
128
+ - constitution.retraction_policy == "zero_tolerance" and its source is
129
+ in `retracted_hashes` (downgrade regardless of quote match or score).
130
+ """
131
+ from runner.claim_witness import STATUS_STALE, STATUS_SUSPECT
132
+
133
+ constitution = constitution or LEGACY_CONSTITUTION
134
+ violations = check_invariants([claim], hasher=hasher, constitution=constitution)
135
+
136
+ reasons = [f"{v.rule}: {v.message}" for v in violations]
137
+ if (
138
+ constitution.retraction_policy == "zero_tolerance"
139
+ and claim.source_hash
140
+ and retracted_hashes
141
+ and claim.source_hash in retracted_hashes
142
+ ):
143
+ reasons.append(
144
+ f"ZERO_TOLERANCE_RETRACTION: source {claim.source_hash} was retracted"
145
+ )
146
+ if claim.status in (STATUS_STALE, STATUS_SUSPECT) and constitution.retraction_policy == "zero_tolerance":
147
+ if not any(r.startswith("ZERO_TOLERANCE_RETRACTION") for r in reasons):
148
+ reasons.append(f"ZERO_TOLERANCE_RETRACTION: claim status {claim.status}")
149
+
150
+ return {
151
+ "claim_id": claim.claim_id,
152
+ "verdict": "REJECTED" if reasons else "ACCEPTED",
153
+ "reasons": reasons,
154
+ }
155
+
156
+
157
+ def check_invariants(
158
+ claims: Iterable[ClaimWitness],
159
+ hasher: Any = None,
160
+ constitution: Optional[Constitution] = None,
161
+ ) -> List[Violation]:
162
+ """Return every invariant violation found across the claim set.
163
+
164
+ Pass `hasher` (a SourceHasher) to enable the live quote check for
165
+ VERIFIED claims; without it, only structural rules are enforced.
166
+ """
167
+ constitution = constitution or LEGACY_CONSTITUTION
168
+ claims = list(claims)
169
+ known_ids = {c.claim_id for c in claims}
170
+ out: List[Violation] = []
171
+
172
+ for c in claims:
173
+ # --- tag/kind-specific structural rules ---
174
+ if c.tag == TAG_VERIFIED or c.kind == KIND_CLAIM:
175
+ if not c.source_hash:
176
+ out.append(Violation(c.claim_id, "VERIFIED_REQUIRES_HASH", "source_hash missing"))
177
+ if not c.verbatim_quote:
178
+ out.append(Violation(c.claim_id, "VERIFIED_REQUIRES_QUOTE", "verbatim_quote missing"))
179
+ if hasher is not None and c.source_hash and c.verbatim_quote:
180
+ passed, _conf, msg = witness_check(c, hasher)
181
+ if not passed:
182
+ out.append(Violation(c.claim_id, "WITNESS_CHECK_FAILED", msg or "quote not in source"))
183
+
184
+ if c.tag == TAG_INFERRED or c.kind == KIND_INFERENCE:
185
+ if not c.parent_claims:
186
+ out.append(Violation(c.claim_id, "INFERRED_REQUIRES_PARENTS", "parent_claims empty"))
187
+ if not c.deductive_logic:
188
+ out.append(Violation(c.claim_id, "INFERRED_REQUIRES_LOGIC", "deductive_logic missing"))
189
+
190
+ if c.tag == TAG_HYPOTHESIS or c.kind == KIND_HYPOTHESIS:
191
+ if not c.falsification:
192
+ out.append(Violation(c.claim_id, "HYPOTHESIS_REQUIRES_FALSIFICATION", "falsification missing"))
193
+
194
+ if c.tag == TAG_NEGATIVE_KNOWLEDGE or c.kind == KIND_NEGATIVE_KNOWLEDGE:
195
+ if not c.query:
196
+ out.append(Violation(c.claim_id, "NEG_KNOWLEDGE_REQUIRES_QUERY", "query missing"))
197
+ if not c.finding:
198
+ out.append(Violation(c.claim_id, "NEG_KNOWLEDGE_REQUIRES_FINDING", "finding missing"))
199
+
200
+ # --- cross-reference resolution ---
201
+ for parent in c.parent_claims:
202
+ if parent not in known_ids:
203
+ out.append(
204
+ Violation(c.claim_id, "PARENT_UNRESOLVED", f"parent_claims '{parent}' not in dossier set")
205
+ )
206
+
207
+ # --- domain-pack rules ---
208
+ if constitution.mandatory_tags and not c.tag:
209
+ out.append(Violation(c.claim_id, "MISSING_TAG", "tag required by constitution"))
210
+ if constitution.banned_domains and c.source_url:
211
+ for dom in constitution.banned_domains:
212
+ if dom.lower() in c.source_url.lower():
213
+ out.append(
214
+ Violation(c.claim_id, "BANNED_DOMAIN", f"source_url on banned domain '{dom}'")
215
+ )
216
+
217
+ return out
218
+
219
+
220
+ def compute_epistemic_score(
221
+ counts: Dict[str, int],
222
+ constitution: Optional[Constitution] = None,
223
+ ) -> Tuple[float, Dict[str, Any]]:
224
+ """Pure E(D) — deterministic, bounded to [0, 1], legacy-compatible.
225
+
226
+ Takes the tallies the auditor already computes:
227
+ counts = {"verified": N, "rejected": N, "inferred": N,
228
+ "hypotheses": N, "neg_knowledge": N}
229
+
230
+ Legacy formula (runner/auditor_engine.py, pre-refactor):
231
+ total_assertions = max(1, verified + inferred + hypotheses + rejected)
232
+ raw = (1.0*verified + 0.5*neg_knowledge - 2.5*rejected) / total_assertions
233
+ score = clamp(round(raw, 3), 0.0, 1.0)
234
+
235
+ Returns (score, breakdown). Breakdown carries the same counts back so the
236
+ auditor's summary keys stay byte-identical.
237
+ """
238
+ constitution = constitution or LEGACY_CONSTITUTION
239
+
240
+ total_verified = int(counts.get("verified", 0))
241
+ total_rejected = int(counts.get("rejected", 0))
242
+ total_inferred = int(counts.get("inferred", 0))
243
+ total_hypotheses = int(counts.get("hypotheses", 0))
244
+ total_neg_knowledge = int(counts.get("neg_knowledge", 0))
245
+
246
+ # Legacy flat weighting: every verified claim counts as 1.0 regardless of
247
+ # tier. A Domain Pack may override tier_weights; then each verified claim
248
+ # would need its tier looked up — that path is exercised by Stream G's
249
+ # `compute_epistemic_score_from_claims`, not here.
250
+ verified_weight = (
251
+ constitution.weight_for(None) * total_verified
252
+ )
253
+
254
+ total_assertions = max(
255
+ 1, total_verified + total_inferred + total_hypotheses + total_rejected
256
+ )
257
+ raw_score = (
258
+ verified_weight
259
+ + constitution.neg_bonus * total_neg_knowledge
260
+ - constitution.reject_penalty * total_rejected
261
+ ) / total_assertions
262
+ epistemic_score = max(0.0, min(1.0, round(raw_score, 3)))
263
+
264
+ breakdown = {
265
+ "total_claims_audited": total_verified + total_rejected,
266
+ "verified_passed": total_verified,
267
+ "unverified_rejected": total_rejected,
268
+ "negative_knowledge_count": total_neg_knowledge,
269
+ "inferred_count": total_inferred,
270
+ "hypothesis_count": total_hypotheses,
271
+ "verified_weight": verified_weight,
272
+ "total_assertions": total_assertions,
273
+ "raw_score": raw_score,
274
+ }
275
+ return epistemic_score, breakdown
276
+
277
+
278
+ def compute_epistemic_score_from_claims(
279
+ claims: Iterable[ClaimWitness],
280
+ constitution: Optional[Constitution] = None,
281
+ ) -> Tuple[float, Dict[str, Any]]:
282
+ """Same E(D), but computed over ClaimWitness records (tier-aware path).
283
+
284
+ Used by Domain Packs (Stream G) where a constitution supplies per-tier
285
+ weights. Falls back to flat 1.0 when the constitution is legacy.
286
+ """
287
+ constitution = constitution or LEGACY_CONSTITUTION
288
+ counts = {
289
+ "verified": 0,
290
+ "rejected": 0,
291
+ "inferred": 0,
292
+ "hypotheses": 0,
293
+ "neg_knowledge": 0,
294
+ }
295
+ weight_sum = 0.0
296
+ tier_weighted = any(
297
+ w != 1.0 for w in constitution.tier_weights.values()
298
+ )
299
+
300
+ for c in claims:
301
+ if c.status == STATUS_REJECTED or c.tag == "UNVERIFIED_REJECTED":
302
+ counts["rejected"] += 1
303
+ elif c.tag == TAG_VERIFIED:
304
+ counts["verified"] += 1
305
+ weight_sum += constitution.weight_for(c.tier) if tier_weighted else 1.0
306
+ elif c.tag == TAG_INFERRED:
307
+ counts["inferred"] += 1
308
+ elif c.tag == TAG_HYPOTHESIS:
309
+ counts["hypotheses"] += 1
310
+ elif c.tag == TAG_NEGATIVE_KNOWLEDGE:
311
+ counts["neg_knowledge"] += 1
312
+
313
+ total_assertions = max(
314
+ 1,
315
+ counts["verified"] + counts["inferred"] + counts["hypotheses"] + counts["rejected"],
316
+ )
317
+ raw_score = (
318
+ weight_sum
319
+ + constitution.neg_bonus * counts["neg_knowledge"]
320
+ - constitution.reject_penalty * counts["rejected"]
321
+ ) / total_assertions
322
+ epistemic_score = max(0.0, min(1.0, round(raw_score, 3)))
323
+
324
+ breakdown = {
325
+ "total_claims_audited": counts["verified"] + counts["rejected"],
326
+ "verified_passed": counts["verified"],
327
+ "unverified_rejected": counts["rejected"],
328
+ "negative_knowledge_count": counts["neg_knowledge"],
329
+ "inferred_count": counts["inferred"],
330
+ "hypothesis_count": counts["hypotheses"],
331
+ "verified_weight": weight_sum,
332
+ "total_assertions": total_assertions,
333
+ "raw_score": raw_score,
334
+ }
335
+ return epistemic_score, breakdown