eduevidence 6.0.0 → 6.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 (267) hide show
  1. package/CHANGELOG.md +395 -0
  2. package/CONTRIBUTING.md +105 -0
  3. package/README.md +113 -49
  4. package/README.zh-CN.md +39 -12
  5. package/SKILL.md +15 -5
  6. package/assets/readme/landing-tour.gif +0 -0
  7. package/assets/readme/studio-tour.gif +0 -0
  8. package/benchmarks/evidence-library.json +277 -1
  9. package/bin/eduevidence.js +2 -1
  10. package/docs/architecture.md +325 -46
  11. package/docs/demo-workplace-ai.md +1 -1
  12. package/docs/install-guide.md +1 -1
  13. package/docs/j-ev-experimental.md +250 -0
  14. package/docs/orchestration-role-model.md +1 -1
  15. package/docs/release-closeout/README.md +1 -1
  16. package/docs/reproducibility.md +138 -0
  17. package/docs/sciverse-api.md +125 -0
  18. package/domains/_neutral/copy/few_shots.json +21 -0
  19. package/domains/_neutral/copy/framing_lexicon.json +19 -0
  20. package/domains/_neutral/copy/module_labels.json +5 -0
  21. package/domains/_neutral/copy/module_labels_footer.json +102 -0
  22. package/domains/_neutral/copy/module_labels_modules.json +204 -0
  23. package/domains/_neutral/copy/module_labels_nav.json +126 -0
  24. package/domains/_neutral/copy/module_labels_summary.json +98 -0
  25. package/domains/_neutral/copy/module_labels_tables.json +164 -0
  26. package/domains/_neutral/copy/module_labels_v2.json +90 -0
  27. package/domains/_neutral/copy/risk_constructs.json +20 -0
  28. package/domains/_neutral/copy/section_titles.json +66 -0
  29. package/domains/_neutral/copy/terminology.json +11 -0
  30. package/domains/check_copy_packs.py +103 -0
  31. package/domains/education/copy/few_shots.json +22 -0
  32. package/domains/education/copy/framing_enums.json +167 -0
  33. package/domains/education/copy/framing_lexicon.json +166 -0
  34. package/domains/education/copy/module_labels.json +169 -0
  35. package/domains/education/copy/risk_constructs.json +48 -0
  36. package/domains/education/copy/section_titles.json +186 -0
  37. package/domains/education/copy/terminology.json +70 -0
  38. package/domains/education/manifest.json +1 -1
  39. package/domains/education/outcome_taxonomy.json +2 -2
  40. package/domains/manifest.json +1 -1
  41. package/domains/policy/copy/few_shots.json +22 -0
  42. package/domains/policy/copy/framing_enums.json +94 -0
  43. package/domains/policy/copy/framing_lexicon.json +174 -0
  44. package/domains/policy/copy/module_labels.json +168 -0
  45. package/domains/policy/copy/risk_constructs.json +33 -0
  46. package/domains/policy/copy/section_titles.json +186 -0
  47. package/domains/policy/copy/terminology.json +64 -0
  48. package/eduevidence_cli.py +10 -0
  49. package/engine/capabilities.py +57 -5
  50. package/engine/decision_policy.py +167 -0
  51. package/engine/evidence_graph.py +14 -10
  52. package/engine/gaps.py +42 -22
  53. package/engine/ids.py +2 -0
  54. package/engine/library.py +6 -2
  55. package/engine/library_builtin.py +7 -4
  56. package/engine/living.py +34 -4
  57. package/engine/migration.py +88 -3
  58. package/engine/orchestration.py +5 -5
  59. package/engine/paths.py +2 -0
  60. package/engine/pilot.py +34 -32
  61. package/engine/taxonomy.py +211 -0
  62. package/engine/tribunal.py +49 -43
  63. package/engine/versions.py +1 -1
  64. package/examples/ai-coding-assistant-evidence/EduEvidence_Report.html +1361 -147
  65. package/examples/ai-coding-assistant-evidence/artifact_manifest.json +3 -3
  66. package/examples/ai-coding-assistant-evidence/citation_check.json +1 -1
  67. package/examples/ai-coding-assistant-evidence/final_verdict.json +107 -0
  68. package/examples/ai-coding-assistant-evidence/gate_report.json +101 -0
  69. package/examples/ai-coding-assistant-evidence/report_spec.json +23 -12
  70. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_academic.html +448 -128
  71. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_claude.html +448 -128
  72. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab-dark.html +448 -128
  73. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab.html +448 -128
  74. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_presentation.html +448 -128
  75. package/examples/ai-coding-assistant-evidence/reports-5themes/report_academic.html +1360 -146
  76. package/examples/ai-coding-assistant-evidence/reports-5themes/report_claude.html +1360 -146
  77. package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab-dark.html +1360 -146
  78. package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab.html +1360 -146
  79. package/examples/ai-coding-assistant-evidence/reports-5themes/report_presentation.html +1360 -146
  80. package/examples/ai-coding-assistant-evidence/result.json +13 -9
  81. package/examples/ai-coding-assistant-evidence/result.zh.json +45 -41
  82. package/examples/ai-coding-assistant-evidence/skeptic.json +72 -0
  83. package/examples/ai-coding-assistant-evidence/verdict.json +6 -2
  84. package/examples/spaced-retrieval-practice/EduEvidence_Report.html +2728 -0
  85. package/examples/spaced-retrieval-practice/applicability.json +14 -0
  86. package/examples/spaced-retrieval-practice/artifact_manifest.json +15 -0
  87. package/examples/spaced-retrieval-practice/claims.jsonl +3 -0
  88. package/examples/spaced-retrieval-practice/evidence.jsonl +6 -0
  89. package/examples/spaced-retrieval-practice/final_verdict.json +93 -0
  90. package/examples/spaced-retrieval-practice/frame.json +58 -0
  91. package/examples/spaced-retrieval-practice/gate_report.json +101 -0
  92. package/examples/spaced-retrieval-practice/methodology.json +78 -0
  93. package/examples/spaced-retrieval-practice/report.html +2522 -0
  94. package/examples/spaced-retrieval-practice/report_spec.json +212 -0
  95. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_academic.html +2728 -0
  96. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_claude.html +2728 -0
  97. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab-dark.html +2728 -0
  98. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab.html +2728 -0
  99. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_presentation.html +2728 -0
  100. package/examples/spaced-retrieval-practice/reports-5themes/report_academic.html +2728 -0
  101. package/examples/spaced-retrieval-practice/reports-5themes/report_claude.html +2728 -0
  102. package/examples/spaced-retrieval-practice/reports-5themes/report_datalab-dark.html +2728 -0
  103. package/examples/spaced-retrieval-practice/reports-5themes/report_datalab.html +2728 -0
  104. package/examples/spaced-retrieval-practice/reports-5themes/report_presentation.html +2728 -0
  105. package/examples/spaced-retrieval-practice/result.json +942 -0
  106. package/examples/spaced-retrieval-practice/result.zh.json +942 -0
  107. package/examples/spaced-retrieval-practice/skeptic.json +70 -0
  108. package/examples/spaced-retrieval-practice/sources.jsonl +7 -0
  109. package/examples/spaced-retrieval-practice/verdict.json +93 -0
  110. package/examples/workplace-ai-assistant/EduEvidence_Report.html +2814 -0
  111. package/examples/workplace-ai-assistant/artifact_manifest.json +15 -0
  112. package/examples/workplace-ai-assistant/claims.jsonl +4 -4
  113. package/examples/workplace-ai-assistant/evidence.jsonl +4 -4
  114. package/examples/workplace-ai-assistant/evidence_graph.json +15 -15
  115. package/examples/workplace-ai-assistant/final_verdict.json +78 -0
  116. package/examples/workplace-ai-assistant/gate_report.json +101 -0
  117. package/examples/workplace-ai-assistant/report_spec.json +209 -40
  118. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_academic.html +449 -119
  119. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_claude.html +449 -119
  120. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab-dark.html +449 -119
  121. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab.html +449 -119
  122. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_presentation.html +449 -119
  123. package/examples/workplace-ai-assistant/reports-5themes/report_academic.html +2814 -0
  124. package/examples/workplace-ai-assistant/reports-5themes/report_claude.html +2814 -0
  125. package/examples/workplace-ai-assistant/reports-5themes/report_datalab-dark.html +2814 -0
  126. package/examples/workplace-ai-assistant/reports-5themes/report_datalab.html +2814 -0
  127. package/examples/workplace-ai-assistant/reports-5themes/report_presentation.html +2814 -0
  128. package/examples/workplace-ai-assistant/result.json +82 -20
  129. package/examples/workplace-ai-assistant/result.zh.json +82 -20
  130. package/examples/workplace-ai-assistant/skeptic.json +72 -0
  131. package/examples/workplace-ai-assistant/verdict.json +36 -10
  132. package/integrations/agent_mcp.py +2 -2
  133. package/integrations/jev/__init__.py +115 -0
  134. package/integrations/jev/approval.py +212 -0
  135. package/integrations/jev/cli.py +84 -0
  136. package/integrations/jev/config.py +112 -0
  137. package/integrations/jev/gateway.py +128 -0
  138. package/integrations/jev/modes.py +38 -0
  139. package/integrations/jev/tools_classify.py +88 -0
  140. package/integrations/jev/tools_extract.py +111 -0
  141. package/integrations/jev/tools_rerank.py +71 -0
  142. package/integrations/jev/tools_screen.py +87 -0
  143. package/integrations/jev/tools_verify.py +95 -0
  144. package/integrations/jev_mcp.py +22 -0
  145. package/integrations/semantic_decide.py +286 -0
  146. package/integrations/semdecide_cli.py +55 -0
  147. package/package.json +19 -2
  148. package/pyproject.toml +4 -3
  149. package/references/report-copy-style.md +107 -0
  150. package/references/retrieval-compliance.md +75 -0
  151. package/references/retrieval-protocol.md +20 -0
  152. package/retrieval/audit.py +27 -3
  153. package/retrieval/fetch.py +96 -0
  154. package/retrieval/sciverse.py +398 -0
  155. package/retrieval/search.py +47 -7
  156. package/schemas/applicability.schema.json +94 -0
  157. package/schemas/chart-spec.schema.json +10 -3
  158. package/schemas/evidence.schema.json +316 -43
  159. package/schemas/fetch-result.schema.json +2 -1
  160. package/schemas/report-result.schema.json +3 -3
  161. package/schemas/report-spec.schema.json +98 -100
  162. package/schemas/skeptic.schema.json +86 -0
  163. package/schemas/source.schema.json +21 -2
  164. package/schemas/v2/decision-snapshot.schema.json +20 -9
  165. package/schemas/v2/finding.schema.json +5 -1
  166. package/schemas/v2/intake.schema.json +191 -0
  167. package/schemas/v2/methodology-audit.schema.json +5 -1
  168. package/schemas/v2/outcome.schema.json +28 -5
  169. package/schemas/v2/study.schema.json +5 -1
  170. package/schemas/vNext/autoevolve-session.schema.json +34 -1
  171. package/schemas/vNext/eval-snapshot.schema.json +77 -1
  172. package/schemas/vNext/execution-plan.schema.json +50 -1
  173. package/schemas/vNext/gap-priority.schema.json +54 -1
  174. package/schemas/vNext/negative-search-record.schema.json +68 -1
  175. package/schemas/vNext/research-iteration.schema.json +87 -1
  176. package/schemas/vNext/research-strategy.schema.json +62 -1
  177. package/schemas/vNext/skill-experiment.schema.json +90 -1
  178. package/schemas/vNext/task-spec.schema.json +156 -1
  179. package/schemas/vNext/worker-result.schema.json +60 -1
  180. package/schemas/verdict.schema.json +164 -28
  181. package/scripts/build_evidence_library.py +15 -5
  182. package/scripts/build_report_variants.py +18 -2
  183. package/scripts/build_result.py +74 -9
  184. package/scripts/check_package_parity.py +85 -0
  185. package/scripts/check_protocol_alignment.py +375 -0
  186. package/scripts/check_versioned_schemas.py +254 -0
  187. package/scripts/claim_audit.py +13 -8
  188. package/scripts/compute_confidence.py +10 -0
  189. package/scripts/dashboard_server.py +13 -2
  190. package/scripts/did_regression.py +12 -2
  191. package/scripts/evidence_score.py +5 -2
  192. package/scripts/intake/__init__.py +31 -0
  193. package/scripts/intake/__main__.py +18 -0
  194. package/scripts/intake/background.py +78 -0
  195. package/scripts/intake/browser.py +79 -0
  196. package/scripts/intake/cli.py +57 -0
  197. package/scripts/intake/constants.py +57 -0
  198. package/scripts/intake/depth.py +53 -0
  199. package/scripts/intake/enhancements.py +106 -0
  200. package/scripts/intake/hooks.py +90 -0
  201. package/scripts/intake/prefs.py +76 -0
  202. package/scripts/intake/prompts.py +85 -0
  203. package/scripts/intake/session.py +152 -0
  204. package/scripts/lint_file_layers.py +126 -0
  205. package/scripts/orchestrator.py +187 -40
  206. package/scripts/pre_verdict_gate.py +241 -29
  207. package/scripts/quickstart.py +18 -2
  208. package/scripts/run_workspace.py +7 -1
  209. package/scripts/skill_lint.py +11 -1
  210. package/scripts/skill_payload.py +6 -3
  211. package/scripts/test_adversarial_empirical.py +96 -25
  212. package/scripts/validate_schema.py +31 -1
  213. package/skill/agents/evaluation-designer.md +20 -4
  214. package/skill/agents/evidence-analyst.md +19 -3
  215. package/skill/agents/evidence-judge.md +98 -8
  216. package/skill/agents/evidence-retriever.md +20 -3
  217. package/skill/agents/intervention-designer.md +20 -4
  218. package/skill/agents/method-reviewer.md +18 -2
  219. package/skill/agents/{education-planner.md → research-planner.md} +19 -3
  220. package/skill/agents/skeptic.md +18 -2
  221. package/skill/roles/registry.yaml +11 -11
  222. package/skill/sub-skills/aihot-trend-analysis/SKILL.md +28 -9
  223. package/skill/sub-skills/contradiction-analysis/SKILL.md +31 -11
  224. package/skill/sub-skills/data-analysis/SKILL.md +34 -15
  225. package/skill/sub-skills/ethics-review/SKILL.md +33 -10
  226. package/skill/sub-skills/evidence-extraction/SKILL.md +29 -11
  227. package/skill/sub-skills/evidence-review/SKILL.md +31 -12
  228. package/skill/sub-skills/gap-analysis/SKILL.md +31 -9
  229. package/skill/sub-skills/literature-review/SKILL.md +35 -14
  230. package/skill/sub-skills/methodology-audit/SKILL.md +29 -12
  231. package/skill/sub-skills/report-generation/SKILL.md +28 -0
  232. package/skill/sub-skills/research-planning/SKILL.md +41 -14
  233. package/skill/sub-skills/study-design/SKILL.md +30 -9
  234. package/skill/task-briefs/adjudicate.md +32 -7
  235. package/skill/task-briefs/applicability.md +37 -2
  236. package/skill/task-briefs/audit.md +32 -7
  237. package/skill/task-briefs/challenge.md +34 -5
  238. package/skill/task-briefs/evaluate.md +30 -5
  239. package/skill/task-briefs/extract.md +31 -8
  240. package/skill/task-briefs/frame.md +39 -10
  241. package/skill/task-briefs/intervene.md +32 -6
  242. package/skill/task-briefs/present.md +32 -8
  243. package/skill/task-briefs/projection.md +36 -2
  244. package/skill/task-briefs/retrieve.md +36 -6
  245. package/skill/workflows/decision-and-pilot.md +76 -1
  246. package/skill/workflows/evaluate-and-update.md +83 -0
  247. package/skill/workflows/evidence-review.md +104 -0
  248. package/skill/workflows/experimental-jev.md +170 -0
  249. package/skill/workflows/intake.md +120 -0
  250. package/visualization/eduevidence-report/scripts/build_figures.py +25 -3
  251. package/visualization/eduevidence-report/scripts/build_infographics.py +37 -15
  252. package/visualization/eduevidence-report/scripts/build_report.py +435 -575
  253. package/visualization/eduevidence-report/scripts/charts_data.py +2 -0
  254. package/visualization/eduevidence-report/scripts/lieflat_engine.py +349 -38
  255. package/visualization/eduevidence-report/scripts/report_copy_pack.py +296 -0
  256. package/visualization/eduevidence-report/scripts/report_copy_policy_guard.py +47 -0
  257. package/visualization/eduevidence-report/scripts/zh_labels.py +141 -1
  258. package/web/architecture.html +14885 -0
  259. package/web/studio/assets/index-B8tkF44Q.css +1 -0
  260. package/web/studio/index.html +2 -2
  261. package/scripts/build_esl_artifacts.py +0 -1921
  262. package/scripts/build_killer_demo.py +0 -295
  263. package/scripts/enrich_projects_human_and_lieflat.py +0 -315
  264. package/scripts/generate_new_projects.py +0 -686
  265. package/scripts/sync_killer_demo_report.py +0 -270
  266. package/web/studio/assets/index-CzXocaGv.css +0 -1
  267. /package/web/studio/assets/{index-pa7jD7n4.js → index-CQ6Keoyc.js} +0 -0
@@ -0,0 +1,286 @@
1
+ """SemDecide CLI integration (experimental mode 2 / hybrid 3).
2
+
3
+ Exit 2/3/4 never resolve silently — escalate to the host large model.
4
+ Secrets stay in env files; never passed on argv.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import os
10
+ import shutil
11
+ import subprocess
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ SEMDECIDE_ENV_FILE = os.environ.get("JEV_ENV_FILE", "~/.eduevidence/env")
16
+ SEMDECIDE_BIN_ENV = "SEMDECIDE_BIN"
17
+ SEMDECIDE_UNAVAILABLE = "SEMDECIDE_UNAVAILABLE"
18
+ SEMDECIDE_APPROVAL_REQUIRED = "SEMDECIDE_APPROVAL_REQUIRED"
19
+ ESCALATE_TO_LLM = "ESCALATE_TO_LLM"
20
+ DEFAULT_THRESHOLD = 0.7
21
+ DEFAULT_TIMEOUT = 10.0
22
+ DEFAULT_MIN_CONFIDENCE = 0.0
23
+ SEMDECIDE_TIMEOUT = DEFAULT_TIMEOUT
24
+ EXIT_OK = 0
25
+ EXIT_FALSE = 1
26
+ ESCALATE_EXIT_CODES = frozenset({2, 3, 4})
27
+ EXIT_MEANINGS = {
28
+ 0: "true/selected/match",
29
+ 1: "false/no match",
30
+ 2: "invalid input",
31
+ 3: "uncertain",
32
+ 4: "provider failure",
33
+ }
34
+ WRAPPED_COMMANDS = ("is", "filter", "choose", "score", "guard")
35
+
36
+ def _env_file_values(path: str | Path | None = None) -> dict[str, str]:
37
+ try:
38
+ text = Path(os.path.expanduser(path or SEMDECIDE_ENV_FILE)).read_text(
39
+ encoding="utf-8")
40
+ except OSError:
41
+ return {}
42
+ values: dict[str, str] = {}
43
+ for line in text.splitlines():
44
+ line = line.strip()
45
+ if not line or line.startswith("#") or "=" not in line:
46
+ continue
47
+ if line.startswith("export "):
48
+ line = line[len("export "):]
49
+ key, _, value = line.partition("=")
50
+ values[key.strip()] = value.strip().strip("\"'")
51
+ return values
52
+
53
+
54
+ def binary_path() -> str | None:
55
+ override = os.environ.get(SEMDECIDE_BIN_ENV)
56
+ if override and os.path.exists(os.path.expanduser(override)):
57
+ return os.path.expanduser(override)
58
+ return shutil.which("semdecide")
59
+
60
+
61
+ def detect_semdecide() -> dict[str, Any]:
62
+ """Probe the local semdecide CLI (never the network)."""
63
+ path = binary_path()
64
+ env_values = _env_file_values()
65
+ has_key = bool(
66
+ os.environ.get("TYPESAFE_API_KEY")
67
+ or env_values.get("TYPESAFE_API_KEY")
68
+ or os.environ.get("AI_GATEWAY_API_KEY")
69
+ or env_values.get("AI_GATEWAY_API_KEY")
70
+ )
71
+ available = path is not None
72
+ reasons: list[str] = []
73
+ if not available:
74
+ reasons.append("semdecide binary not on PATH (pipx/uv tool install "
75
+ "from sharziki/semdecide release wheel)")
76
+ if not has_key:
77
+ reasons.append("TYPESAFE_API_KEY / AI_GATEWAY_API_KEY not set — "
78
+ "semdecide will exit 4 (provider_failure)")
79
+ return {
80
+ "available": available,
81
+ "state": "available" if available else "unavailable",
82
+ "mode": "semdecide" if available else "none",
83
+ "binary": path,
84
+ "credentials_declared": has_key,
85
+ "reason": "; ".join(reasons) or "semdecide on PATH",
86
+ "reasons": reasons,
87
+ "hint": ("install: pipx install "
88
+ "https://github.com/sharziki/semdecide/releases/download/"
89
+ "v0.2.1/semdecide-0.2.1-py3-none-any.whl"
90
+ if not available else ""),
91
+ "commands": list(WRAPPED_COMMANDS),
92
+ "escalate_exit_codes": sorted(ESCALATE_EXIT_CODES),
93
+ }
94
+
95
+
96
+
97
+ def requires_llm_escalation(result: dict[str, Any]) -> bool:
98
+ """True for exit 2 / 3 / 4 (and missing binary)."""
99
+ if result.get("status") == SEMDECIDE_UNAVAILABLE:
100
+ return True
101
+ return result.get("exit_code") in ESCALATE_EXIT_CODES
102
+
103
+
104
+ def _run(argv: list[str], *, stdin_text: str | None = None,
105
+ timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]:
106
+ path = binary_path()
107
+ if not path:
108
+ return {
109
+ "status": SEMDECIDE_UNAVAILABLE,
110
+ "exit_code": None,
111
+ "escalate": ESCALATE_TO_LLM,
112
+ "reason": "semdecide binary not found",
113
+ }
114
+ # Credentials come from the process env / credentials.env — never argv.
115
+ env = os.environ.copy()
116
+ for key, value in _env_file_values().items():
117
+ env.setdefault(key, value)
118
+ try:
119
+ proc = subprocess.run(
120
+ [path, *argv],
121
+ input=stdin_text if stdin_text is not None else "",
122
+ capture_output=True,
123
+ text=True,
124
+ timeout=timeout,
125
+ env=env,
126
+ )
127
+ except subprocess.TimeoutExpired:
128
+ return {
129
+ "status": "SEMDECIDE_TIMEOUT",
130
+ "exit_code": None,
131
+ "escalate": ESCALATE_TO_LLM,
132
+ "reason": f"semdecide timed out after {timeout}s",
133
+ }
134
+ except OSError as exc:
135
+ return {
136
+ "status": SEMDECIDE_UNAVAILABLE,
137
+ "exit_code": None,
138
+ "escalate": ESCALATE_TO_LLM,
139
+ "reason": f"semdecide spawn failed: {type(exc).__name__}",
140
+ }
141
+
142
+ code = proc.returncode
143
+ stdout = proc.stdout or ""
144
+ stderr = proc.stderr or ""
145
+ payload: Any = None
146
+ if stdout.strip().startswith("{") or stdout.strip().startswith("["):
147
+ try:
148
+ payload = json.loads(stdout)
149
+ except json.JSONDecodeError:
150
+ payload = None
151
+
152
+ escalate = code in ESCALATE_EXIT_CODES
153
+ return {
154
+ "status": "ok" if code in (EXIT_OK, EXIT_FALSE) else EXIT_MEANINGS.get(
155
+ code, "unknown_exit"),
156
+ "exit_code": code,
157
+ "exit_meaning": EXIT_MEANINGS.get(code, "unknown_exit"),
158
+ "stdout": stdout,
159
+ "stderr": stderr[-2000:],
160
+ "json": payload,
161
+ "escalate": ESCALATE_TO_LLM if escalate else None,
162
+ "requires_llm": escalate,
163
+ }
164
+
165
+
166
+
167
+ def is_predicate(text: str, predicate: str, *, threshold: float = DEFAULT_THRESHOLD,
168
+ uncertainty_margin: float = 0.0, use_json: bool = True,
169
+ timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]:
170
+ """`semdecide is` — semantic predicate. Exit 2/3/4 escalate to the LLM."""
171
+ argv = ["is", predicate, f"--threshold={threshold}",
172
+ f"--uncertainty-margin={uncertainty_margin}"]
173
+ if use_json:
174
+ argv.append("--json")
175
+ result = _run(argv, stdin_text=text, timeout=timeout)
176
+ result["command"] = "is"
177
+ result["predicate"] = predicate
178
+ if isinstance(result.get("json"), dict):
179
+ result["verdict"] = result["json"].get("verdict")
180
+ result["probability"] = result["json"].get("probability")
181
+ result["confidence"] = result["json"].get("confidence")
182
+ return result
183
+
184
+
185
+ def filter_records(records: list[dict[str, Any]] | str, predicate: str, *,
186
+ field: str | None = "text", raw: bool = False,
187
+ max_records: int | None = None,
188
+ timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]:
189
+ """`semdecide filter` — semantic JSONL filtering (order preserved)."""
190
+ if isinstance(records, str):
191
+ jsonl = records
192
+ else:
193
+ jsonl = "\n".join(json.dumps(r, ensure_ascii=False) for r in records)
194
+ argv = ["filter", predicate]
195
+ if field:
196
+ argv.extend(["--field", field])
197
+ if raw:
198
+ argv.append("--raw")
199
+ if max_records is not None:
200
+ argv.extend(["--max-records", str(int(max_records))])
201
+ result = _run(argv, stdin_text=jsonl + ("\n" if jsonl else ""), timeout=timeout)
202
+ result["command"] = "filter"
203
+ result["predicate"] = predicate
204
+ matched: list[Any] = []
205
+ for line in (result.get("stdout") or "").splitlines():
206
+ line = line.strip()
207
+ if not line:
208
+ continue
209
+ try:
210
+ matched.append(json.loads(line))
211
+ except json.JSONDecodeError:
212
+ matched.append(line)
213
+ result["matched"] = matched
214
+ result["match_count"] = len(matched)
215
+ # filter: exit 1 means no definite match — not an escalation by itself.
216
+ if result.get("exit_code") == EXIT_FALSE:
217
+ result["escalate"] = None
218
+ result["requires_llm"] = False
219
+ return result
220
+
221
+
222
+ def choose_route(text: str, question: str,
223
+ options: dict[str, str], *,
224
+ min_confidence: float = DEFAULT_MIN_CONFIDENCE,
225
+ use_json: bool = True,
226
+ timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]:
227
+ """`semdecide choose` — route among named options. Exit 2/3/4 escalate."""
228
+ argv = ["choose", question, f"--min-confidence={min_confidence}"]
229
+ for key, description in options.items():
230
+ argv.append(f"--option={key}={description}")
231
+ if use_json:
232
+ argv.append("--json")
233
+ result = _run(argv, stdin_text=text, timeout=timeout)
234
+ result["command"] = "choose"
235
+ result["question"] = question
236
+ if isinstance(result.get("json"), dict):
237
+ result["selected"] = result["json"].get("selected") or result["json"].get("choice")
238
+ result["probabilities"] = result["json"].get("probabilities")
239
+ result["confidence"] = result["json"].get("confidence")
240
+ return result
241
+
242
+
243
+ def escalate_plan(result: dict[str, Any], *, stage: str = "") -> dict[str, Any]:
244
+ """Build the fail-closed hand-off when exit is 2/3/4 (or binary missing)."""
245
+ code = result.get("exit_code")
246
+ meaning = result.get("exit_meaning") or EXIT_MEANINGS.get(code, "unknown")
247
+ return {
248
+ "action": ESCALATE_TO_LLM,
249
+ "reason": meaning,
250
+ "exit_code": code,
251
+ "stage": stage,
252
+ "policy": {
253
+ 2: "invalid input — repair inputs and re-run, or hand the whole "
254
+ "decision to the large model (never invent a verdict)",
255
+ 3: "uncertain under margin — route to large model / human; do not "
256
+ "force true/false",
257
+ 4: "provider failure — degrade to large model for this call only; "
258
+ "record the attempt",
259
+ }.get(code if isinstance(code, int) else -1,
260
+ "unavailable — use Platform Native / large model path"),
261
+ "raw_status": result.get("status"),
262
+ }
263
+
264
+
265
+
266
+ __all__ = [
267
+ "DEFAULT_THRESHOLD", "DEFAULT_TIMEOUT", "ESCALATE_TO_LLM",
268
+ "SEMDECIDE_APPROVAL_REQUIRED", "SEMDECIDE_BIN_ENV", "SEMDECIDE_ENV_FILE",
269
+ "SEMDECIDE_UNAVAILABLE", "binary_path", "choose_route", "detect_semdecide",
270
+ "escalate_plan", "filter_records", "is_predicate", "requires_llm_escalation",
271
+ ]
272
+
273
+
274
+ if __name__ == "__main__":
275
+ # `python3 integrations/semantic_decide.py ...` runs this file as a script,
276
+ # so the repository root must be importable before `integrations.*` is used.
277
+ # The CLI itself lives in integrations/semdecide_cli.py (file-size budget).
278
+ import sys as _sys
279
+
280
+ _ROOT = Path(__file__).resolve().parent.parent
281
+ if str(_ROOT) not in _sys.path:
282
+ _sys.path.insert(0, str(_ROOT))
283
+
284
+ from integrations.semdecide_cli import main as _cli_main
285
+
286
+ raise SystemExit(_cli_main())
@@ -0,0 +1,55 @@
1
+ """SemDecide detect CLI: the documented `--experimental <mode>` entry point.
2
+
3
+ Kept separate from `integrations/semantic_decide.py` so the wrapper module stays
4
+ inside the repository's 300-line file budget, the same way the Jev layer keeps
5
+ its CLI in `integrations/jev/cli.py`.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ from typing import Any
12
+
13
+ from integrations.semantic_decide import (
14
+ ESCALATE_EXIT_CODES,
15
+ WRAPPED_COMMANDS,
16
+ detect_semdecide,
17
+ )
18
+
19
+
20
+ def main(argv: list[str] | None = None) -> int:
21
+ """Detect probe: local binary/credential state plus the resolved mode plan."""
22
+ parser = argparse.ArgumentParser(
23
+ description="SemDecide experimental detect probe (never calls the network)")
24
+ parser.add_argument("--experimental", nargs="?", const="1", default=None,
25
+ metavar="MODE",
26
+ help="enable experimental mode 0|1|2|3 (default 1) and "
27
+ "print the resolved plan")
28
+ args = parser.parse_args(argv)
29
+
30
+ out: dict[str, Any] = {"detect": detect_semdecide()}
31
+ if args.experimental is not None:
32
+ from integrations.jev.config import MODE_HYBRID, MODE_SEMDECIDE, MODE_STANDARD
33
+ from integrations.jev.modes import resolve_experimental_mode
34
+
35
+ mode = resolve_experimental_mode(args.experimental)
36
+ out["experimental"] = {
37
+ "mode": mode,
38
+ "mode_name": {0: "standard", 1: "jev-mcp", 2: "semdecide",
39
+ 3: "hybrid"}.get(mode, str(mode)),
40
+ "enabled": mode in (MODE_SEMDECIDE, MODE_HYBRID),
41
+ "active": mode != MODE_STANDARD,
42
+ "commands": list(WRAPPED_COMMANDS),
43
+ "escalate_exit_codes": sorted(ESCALATE_EXIT_CODES),
44
+ "fail_closed": ("exit 2/3/4 and a missing binary escalate to the "
45
+ "large model; filter exit 1 alone does not"),
46
+ }
47
+ print(json.dumps(out, ensure_ascii=False, indent=2))
48
+ return 0
49
+
50
+
51
+ __all__ = ["main"]
52
+
53
+
54
+ if __name__ == "__main__":
55
+ raise SystemExit(main())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eduevidence",
3
- "version": "6.0.0",
3
+ "version": "6.3.0",
4
4
  "description": "Evidence research and decision skill with education and organizational policy domains.",
5
5
  "license": "MIT",
6
6
  "author": "EduEvidence Contributors",
@@ -40,6 +40,9 @@
40
40
  "references/",
41
41
  "schemas/",
42
42
  "scripts/",
43
+ "!scripts/build_esl_artifacts.py",
44
+ "!scripts/generate_new_projects.py",
45
+ "!scripts/enrich_projects_human_and_lieflat.py",
43
46
  "retrieval/",
44
47
  "integrations/",
45
48
  "visualization/eduevidence-report/",
@@ -51,6 +54,12 @@
51
54
  "autoevolve/protected.manifest.yaml",
52
55
  "setup.py",
53
56
  "docs/architecture.md",
57
+ "docs/sciverse-api.md",
58
+ "docs/reproducibility.md",
59
+ "docs/j-ev-experimental.md",
60
+ "CHANGELOG.md",
61
+ "CONTRIBUTING.md",
62
+ "web/architecture.html",
54
63
  "docs/install-guide.md",
55
64
  "docs/release-contract.md",
56
65
  "docs/research-studio-guide.zh-CN.md",
@@ -68,11 +77,19 @@
68
77
  "examples/workplace-ai-assistant/*.jsonl",
69
78
  "examples/workplace-ai-assistant/*.html",
70
79
  "examples/workplace-ai-assistant/reports-5themes/*.html",
80
+ "examples/spaced-retrieval-practice/*.json",
81
+ "examples/spaced-retrieval-practice/*.jsonl",
82
+ "examples/spaced-retrieval-practice/*.html",
83
+ "examples/spaced-retrieval-practice/reports-5themes/*.html",
71
84
  "docs/demo-workplace-ai.md",
72
85
  "docs/demo.md",
73
86
  "docs/demo-storyboard.md",
74
87
  "docs/release-closeout/*.md",
75
- "assets/readme/"
88
+ "assets/readme/",
89
+ "!**/__pycache__/",
90
+ "!**/*.pyc",
91
+ "!**/*.pyo",
92
+ "!**/.DS_Store"
76
93
  ],
77
94
  "engines": {
78
95
  "node": ">=18"
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "eduevidence"
7
- version = "6.0.0"
7
+ version = "6.3.0"
8
8
  description = "Evidence research and decision skill with education and organizational policy domains."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -18,7 +18,7 @@ classifiers = [
18
18
  ]
19
19
 
20
20
  [project.optional-dependencies]
21
- dev = ["pytest>=7.0"]
21
+ dev = ["pytest>=7.0", "jsonschema>=4.0"]
22
22
 
23
23
  [project.scripts]
24
24
  eduevidence = "eduevidence_cli:main"
@@ -27,7 +27,8 @@ eduevidence = "eduevidence_cli:main"
27
27
  testpaths = ["tests", "scripts"]
28
28
  python_files = ["test_*.py"]
29
29
  addopts = "-q"
30
- filterwarnings = ["ignore::pytest.PytestReturnNotNoneWarning:scripts.*"]
30
+ # scripts/* tests must assert; returning a dict is reported, not filtered.
31
+ filterwarnings = []
31
32
 
32
33
  [tool.setuptools]
33
34
  py-modules = ["eduevidence_cli"]
@@ -0,0 +1,107 @@
1
+ # Report Copy Style — 读者向文案规范
2
+
3
+ 本规范约束**报告里面向读者的文字**(第一屏决策叙事、裁决列表、章节引导语、图表标题与说明)。它服务于一个判断标准:**读者能否一次读懂,且能核对**。规则由 `skill/agents/evidence-judge.md`(写作前)与 `visualization/eduevidence-report/scripts/build_report.py` 的语言门禁(交付后)双向执行。
4
+
5
+ ## 1. 谁写什么
6
+
7
+ | 内容 | 作者 | 说明 |
8
+ |---|---|---|
9
+ | 决策叙事四件套(`strongest_support` / `key_uncertainty` / `main_risk` / `next_action`) | **裁决角色(模型)撰写** | 渲染器只呈现,缺字段就报缺,禁止用列表片段拼装句子 |
10
+ | `decision_rationale` | 裁决角色 | ≤4 句,可独立阅读 |
11
+ | 章节引导语、图例、按钮、兜底句 | 渲染器(固定文案表) | 不随研究内容变化 |
12
+ | 证据与主张原文 | 抽取/裁决角色 | 渲染器不改写 |
13
+
14
+ **为什么写死这条**:报告第一屏曾由渲染器从 `what_can_be_claimed[0]` 之类的片段拼出,读起来像拼装而非撰写,且掩盖了「该字段没人产出」这一事实。撰写责任在模型,呈现责任在渲染器。
15
+
16
+ ## 2. 语言
17
+
18
+ - 面向**非本领域的决策者**:不假设读者熟悉术语。
19
+ - 一句话一个意思;先结论、后依据。
20
+ - 中文用中文,英文用英文;两种语言各自成篇,不做逐字直译。
21
+ - 允许保留原文的例外:AI、RCT、DOI、WWC、GRADE、论文原标题(须标注「原文标题」)。
22
+
23
+ ## 3. 字数上限
24
+
25
+ | 字段 | 上限 | 约合 |
26
+ |---|---|---|
27
+ | `strongest_support` | 60 字 | 2 句 |
28
+ | `key_uncertainty` | 70 字 | 2–3 句 |
29
+ | `main_risk` | 60 字 | 2 句 |
30
+ | `next_action` | 80 字 | 2–3 句 |
31
+ | `decision_rationale` | 160 字 | ≤4 句 |
32
+
33
+ 英文字数上限按等义折算(约为中文字数 ÷ 3 个单词)。
34
+
35
+ ## 4. 按域选文案(Domain copy packs)
36
+
37
+ 报告层**不得**再写死教育域用语。面向读者的研究文案、章节标题、模块标签与框架枚举一律从领域文案包读取:
38
+
39
+ ```
40
+ domains/<domain_id>/copy/
41
+ framing_lexicon.json # frame 枚举 + 字段/子字段标签
42
+ section_titles.json # 01–12 章节标题与引导语、默认章节大纲、摘要块标题
43
+ module_labels.json # 域敏感 UI 模块标签(干预/评价/护栏/分组)
44
+ terminology.json # 方法学项标签 + 字段展示名
45
+ risk_constructs.json # 结果分组、结果分离标题/注、构念护栏
46
+ few_shots.json # 四态(adopt / pilot / reject / insufficient_evidence)各 1 例文案
47
+ ```
48
+
49
+ 中性 UI 词典在 `domains/_neutral/copy/`(`module_labels*.json` 按主题拆分;渲染器 chrome,无研究口吻)。大词典允许主题拆伴文件:`framing_enums.json`、`module_labels_*.json`,加载时并入上述六件套视图。加载与合并:`visualization/eduevidence-report/scripts/report_copy_pack.py`。
50
+
51
+ **域名解析**:`result.meta.domain` → `result.research_frame.extensions.domain`。缺省按 `education`(引擎惯例)并告警;未知域中性回退 + 告警(`strict=True` 则 fail-clear)。
52
+
53
+ **域差异(写作时对照)**:
54
+
55
+ | 概念 | education | policy |
56
+ |---|---|---|
57
+ | 干预 | AI 干预 / 教学干预 | **干预方案**(禁止「AI 干预」) |
58
+ | target_learners | 目标学习者 | **目标人群** |
59
+ | 结果分离 | 任务表现 ≠ 学习效果 | 过程产出 ≠ 政策效果 |
60
+ | 评价指标 | 过程 / **学习** / 风险 | 过程 / **效果** / 风险 |
61
+ | 构念护栏 | 任务 vs 学习护栏 | 产出 vs 效果护栏 |
62
+ | 章节 09 | 教学干预 | 干预方案 |
63
+
64
+ **policy 词典硬约束**:`python3 domains/check_copy_packs.py` 断言 policy 文案包不含教学词(教学/学习者/学习指标/AI 干预/…);`report_copy_pack.assert_policy_copy_clean` 在加载时同样 fail-clear。`build_infographics` 遗留的教育 SVG 标题由 `retitle_infographics()` 按域改写,不改适配器本体。
65
+
66
+ **新增第三域(例如 workplace / health)**:
67
+
68
+ 1. 在 `domains/manifest.json` 注册 `id` / frame_schema / outcome_taxonomy / methodology_checklist(沿用 v4 领域契约)。
69
+ 2. 新建 `domains/<id>/copy/`,写入上述 6 个 JSON(`domain` 字段必须等于 `<id>`)。
70
+ 3. 口吻:从 policy 包复制结构,换成该域构念词;禁止混入其他域硬词(用 `check_copy_packs.py` 的词表扩展后自检)。
71
+ 4. `few_shots.json` 四态各写 1 例该域决策口吻(≤ 规范字数上限)。
72
+ 5. 结果分组:在 `risk_constructs.json` 的 `outcome_groups` 把该域 outcome token 映射到 `task` / `learning` / `risk`(内部桶名不变,展示名由 `module_labels` 决定)。
73
+ 6. 跑 `python3 domains/check_copy_packs.py` + 用该域 `result.json` 跑一次 `build_report.py`,确认 HTML 无外域硬词。
74
+
75
+ ## 5. 术语对照
76
+
77
+ 同一概念全文只用一个说法:
78
+
79
+ | 内部字段 | 中文 | 英文 |
80
+ |---|---|---|
81
+ | `outcome` / `outcome_type` | 结果 | outcome |
82
+ | `claim` | 主张 | claim |
83
+ | `evidence` | 证据 | evidence |
84
+ | `relation_to_claim` | 与主张的关系 | relation to claim |
85
+ | `effect_direction` | 效应方向 | effect direction |
86
+ | `directness` | 直接性 | directness |
87
+ | `task_performance` | 任务表现 | task performance |
88
+ | `retention` | 保持 | retention |
89
+ | `transfer` | 迁移 | transfer |
90
+ | `applicability` | 适用性 | applicability |
91
+
92
+ ## 6. 禁止
93
+
94
+ - 内部字段名与存储标识(`effect_direction`、`first_programming_course_...`)出现在叙述句里;它们只能出现在「原始标识」提示或溯源展开区。
95
+ - 证据 ID 堆砌(`E-001、E-006`)出现在决策叙事里;引用研究用「作者-年份 + 人话描述」。
96
+ - 图表标题写公式(如 `position = (positive − negative) ÷ count`);用自然语言说明图形含义。
97
+ - 半句截断、`null` 残留、中英夹生。
98
+ - 用兜底句掩盖缺失:字段没产出就如实说没产出。
99
+
100
+ ## 7. 门禁如何执行
101
+
102
+ - `check_language_parallel()`:叙述字段必须为对应语言、双语不得完全相同、不得含内部键名。
103
+ - 原始标识检查:`research_frame` 与决策字段中的多词串若含未注册的 `snake_case`,记为缺陷。
104
+ - 字数检查:四件套超过上限即失败。
105
+ - 一致性检查:`result.json` 与 `result.zh.json` 的证据在 id/来源/研究/样本/方向/关系六个结构字段上必须逐条相等(自由文本可不同)。
106
+
107
+ 相关:`visualization/eduevidence-report/references/bilingual-style.md`(双语渲染约定)、`references/scientific-invariants.md`(科学不变量)。
@@ -0,0 +1,75 @@
1
+ # Retrieval Compliance Policy(检索合规政策)
2
+
3
+ 本条政策约束 EduEvidence 的一切外部检索与抓取行为。它服务于两件事:**研究的可复现性**(每条来源都能被第三方重新定位)与**对他方服务与作者的尊重**(不越权、不滥用、不掩盖出处)。违反本条政策的检索结果不得进入证据链。
4
+
5
+ ## 1. 定位与适用范围
6
+
7
+ 适用于 `retrieval/` 下的全部通道:
8
+
9
+ | 通道 | 类型 | 是否需 key |
10
+ |---|---|---|
11
+ | OpenAlex / Semantic Scholar / CrossRef | 学术元数据 API | 否 |
12
+ | AIHot | 行业动态源 | 否 |
13
+ | AgentSearch / ArXiv | 预印本检索 | 否 |
14
+ | DuckDuckGo | 通用网页回退 | 否 |
15
+ | Sciverse | 学术检索(含全文) | 是(`SCIVERSE_API_TOKEN`) |
16
+ | Tavily / Brave | 商业搜索 API | 是 |
17
+ | 抓取链(builtin / jina_reader / defuddle / markdown_new / raw_html) | 正文读取 | 否 |
18
+
19
+ ## 2. 抓取前:robots 与访问边界
20
+
21
+ - **尊重 robots.txt**:目标站点在 robots.txt 中禁止抓取的路径不得作为抓取目标;需要该内容时改用其官方 API 或元数据记录,并在筛选中注明"仅元数据"。
22
+ - **不绕过访问控制**:登录墙、付费墙、验证码页面一律视为不可读内容。抓取链会在校验门中把这些页面判为无效(`is_login_page` / `is_captcha_page`),此时正确做法是回到检索阶段寻找开放版本(预印本、机构库、作者主页),**而不是尝试绕过**。
23
+ - **不伪造可读性**:`FETCH_FAILED` / `FETCH_PARTIAL` 的内容不得作为证据;不得由模型根据标题或摘要"补写"正文。
24
+ - **私有地址不外发**:指向本机/私网的目标只允许本地读取,绝不提交给第三方清洗服务(`retrieval/fetch.py` 的 `LOCAL_PROVIDERS` 约束)。
25
+
26
+ ## 3. 限速与重试
27
+
28
+ - 检索按查询批次串行执行,每次 provider 尝试最多重试 1 次(`AuditedSearchExecutor(max_retries=1)`);失败即记录并切换通道,不做无限重试。
29
+ - 单次运行的检索预算由 `SearchPlan.provider_budget` 限定;S/M/L 分级只影响预算,不影响协议。
30
+ - 超时:检索 12 秒、抓取 20 秒;超时按失败处理并进入降级链。
31
+ - 商用 API 通道按其配额与速率限制使用;配额耗尽是运营问题,不构成放宽其他通道约束的理由。
32
+
33
+ ### Sciverse 通道附加约定
34
+
35
+ - 单次 `agentic-search` 的 `top_k` 上限 100,且同一篇论文最多返回约 3 个 chunk;`balanced` 模式服务端约截断至 50 条。
36
+ - `filters` 为**软过滤**语义:chunk 侧元数据缺失的文档不会被排除。按年份等条件过滤时,结论表述必须写"近似范围";需要严格范围时改用 `meta-search` 的结构化字段并核对返回记录。
37
+ - `offset` / `limit` 以 **Unicode 码点**计(与 Python `len` 一致),不是字节;翻页使用返回的 `next_offset`。
38
+ - 引用必须回指论文本身(DOI 或 `unique_id`),**不得把 Sciverse 记为引用目标**——它是读取路径,不是来源。
39
+
40
+ ## 4. 署名与引用
41
+
42
+ - 引用目标永远是被引文献本身(DOI / 正式 URL / 数据库标识),绝不是检索或清洗通道(`r.jina.ai`、`markdown.new`、Sciverse 等)。
43
+ - 作者、年份、标题、期刊按原文记录,不改写、不翻译、不合并同名作者。
44
+ - 使用的每个来源都要能给出可核验的 `source_location`;没有位置的记录标 `needs_manual_location` 进入人工筛选。
45
+ - 撤稿与更正:已引 DOI 通过 `scripts/retraction_watch.py` 定期核查,命中即移除其支撑作用并重新裁决。
46
+
47
+ ## 5. 缓存与留存
48
+
49
+ - 抓取正文在 run workspace 的 `fetch/` 下保存(raw + clean + provenance + fallback_chain),用于复现与审计;该目录随 run 生命周期管理。
50
+ - 检索审计(`search-provenance.json` / `search-attempts.jsonl` / `source-screening.csv` / `exclusion-log.csv`)与 run 同寿命,用于证明"检索确实发生过、范围如何"。
51
+ - 不长期镜像第三方全文;需要长期复用时保存定位信息与哈希,而非内容副本。
52
+ - 私有项目、用户上传数据与本地运行历史一律不进入公共产物(提交包由 `scripts/skill_payload.py` 的显式白名单构建)。
53
+
54
+ ## 6. 凭据处理
55
+
56
+ - API key 只从环境变量读取(`SCIVERSE_API_TOKEN` / `TAVILY_API_KEY` / `BRAVE_API_KEY`);不写入仓库、不写入产物、不进日志。
57
+ - 错误信息只保留状态与简短描述,不携带 Authorization 头或 token 片段。
58
+ - 缺少 key 时通道静默失活并如实上报状态,不影响零配置通道与科学门。
59
+
60
+ ## 7. 失败与例外
61
+
62
+ | 情形 | 处理 |
63
+ |---|---|
64
+ | 站点禁止抓取 | 退回元数据记录并标注;不得绕过。 |
65
+ | 付费墙 | 寻找开放版本;找不到则记 `needs_manual_location`。 |
66
+ | 配额耗尽 | 记录该次尝试;切换零配置通道继续。 |
67
+ | 合规与时效冲突 | 以合规为准;宁可结论标注"证据不足"。 |
68
+
69
+ ## 8. 交叉引用
70
+
71
+ - 检索协议:`references/retrieval-protocol.md`(查询构造、来源分级、饱和规则)
72
+ - 来源有效性:`references/source-validity.md`
73
+ - 抓取与校验实现:`retrieval/fetch.py`、`retrieval/validate.py`
74
+ - Sciverse 通道:`docs/sciverse-api.md`
75
+
@@ -68,6 +68,24 @@ source-validity.md)后才能进入 Evidence Extraction。
68
68
  | RP-03 | 厂商/行业声明(如 Copilot 官方博客、AI 产品宣传页)**一律不得**作为独立证据,即使域名是 `.edu` / `.gov`(需核查内容是否厂商资助)。 |
69
69
  | RP-04 | 无法回溯到原始来源的二手转述,不得进入 Evidence Matrix。 |
70
70
 
71
+ ### 3.1 Sciverse 通道使用规则(key-based 学术通道)
72
+
73
+ Sciverse(`retrieval/sciverse.py`,需 `SCIVERSE_API_TOKEN`)提供引用级学术检索与全文定位。使用时必须遵守以下语义,否则结论口径会被静默夸大:
74
+
75
+ | 编号 | 规则 |
76
+ | --- | --- |
77
+ | RP-SV-01 | `/agentic-search` 返回的 chunk 是**定位子**(`doc_id` + Unicode 码点 `offset`),必须经 `/content` 读出正文并通过 `retrieval/validate.py` 校验门,才可进入 Extract(RULE 2 的机器化)。定位写入 `chunks.jsonl`,标注 `discovery_only_requires_content_fetch`。 |
78
+ | RP-SV-02 | `filters` 是**软过滤**:chunk 元数据缺失的文档不会被排除。按年份等条件过滤时,结论与筛选表必须写"近似范围";需要严格范围时改用 `/meta-search` 的结构化过滤并逐条核对返回记录。 |
79
+ | RP-SV-03 | `offset` / `limit` 以 **Unicode 码点**计(与 Python `len` 一致);翻页使用返回的 `next_offset`,不得用 `bytes_returned` 推算。 |
80
+ | RP-SV-04 | 调用 `/content` 必须显式传 `offset`(省略会返回整篇全文并忽略 `limit`)。 |
81
+ | RP-SV-05 | 同一篇论文最多返回约 3 个 chunk,`balanced` 模式服务端约截断至 50 条;高 `top_k` 需要足够多的不同论文,不得用同一篇的多个 chunk 充当多项独立证据。 |
82
+ | RP-SV-06 | 引用目标永远是论文本身(DOI / `unique_id`),**不得把 Sciverse 记为来源或抓取渠道**。 |
83
+ | RP-SV-07 | 无 DOI、无 URL 的记录标 `needs_manual_location` 进入人工筛选,**禁止伪造定位**。 |
84
+ | RP-SV-08 | 引文链(`/meta-paper-relations`)用于饱和判断与滚雪球检索,对应 `SearchQuery.purpose = citation_chain`,其方向语义(CITATIONS 被引 / REFERENCES 参考文献)必须在记录中保留。 |
85
+ | RP-SV-09 | 通道不可用(无 token / 401 / 429 / 5xx / 网络失败)时按定型状态记录并切换其他通道;不得因配额耗尽放宽证据标准。 |
86
+
87
+ 端点契约与限制见 `docs/sciverse-api.md`;配额、缓存与署名见 `references/retrieval-compliance.md`。
88
+
71
89
  ## 4. 检索轮次与饱和规则
72
90
 
73
91
  ### 4.1 最小轮次
@@ -140,3 +158,5 @@ Skeptic 的 9 项固定任务(skeptic-protocol.md)需要对应的独立查
140
158
  | RP-09 | snippet 与摘要不得直接作为证据内容(RULE 2);检索阶段产物只能是线索。 |
141
159
  | RP-10 | 厂商声明与二手转述不得作为独立证据(RP-03 / RP-04)。 |
142
160
  | RP-11 | 达到饱和规则或数量下限后仍不足的,如实输出 `INSUFFICIENT_SOURCES`,禁止降低纳入标准凑数。 |
161
+
162
+ 来源合规(robots、限速、paywall、署名与凭据)统一见 `references/retrieval-compliance.md`;Sciverse 通道附加约定见本文 §3.1。