eduevidence 5.2.0 → 6.2.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 (386) hide show
  1. package/CONTRIBUTING.md +105 -0
  2. package/README.md +142 -75
  3. package/README.zh-CN.md +73 -30
  4. package/SKILL.md +397 -131
  5. package/agents/openai.yaml +4 -0
  6. package/assets/readme/controlled-execution.svg +34 -0
  7. package/assets/readme/landing-tour.gif +0 -0
  8. package/assets/readme/logo.png +0 -0
  9. package/assets/readme/research-workflow.svg +56 -0
  10. package/assets/readme/studio-graph.png +0 -0
  11. package/assets/readme/studio-overview.png +0 -0
  12. package/assets/readme/studio-reports.png +0 -0
  13. package/assets/readme/studio-tour.gif +0 -0
  14. package/autoevolve/config.yaml +17 -0
  15. package/autoevolve/program.md +25 -0
  16. package/autoevolve/protected.manifest.yaml +34 -0
  17. package/benchmarks/adversarial/cases.jsonl +7 -0
  18. package/benchmarks/evidence-library.json +5268 -0
  19. package/benchmarks/partitions.json +8 -0
  20. package/bin/eduevidence.js +2 -1
  21. package/docs/architecture.md +496 -0
  22. package/docs/autoresearch-evolution-plan.md +2903 -0
  23. package/docs/autoresearch-implementation-status.md +101 -0
  24. package/docs/demo-storyboard.md +20 -0
  25. package/docs/demo-workplace-ai.md +92 -0
  26. package/docs/demo.md +32 -0
  27. package/docs/install-guide.md +150 -0
  28. package/docs/orchestration-role-model.md +1254 -0
  29. package/docs/release-closeout/README.md +17 -0
  30. package/docs/release-closeout/frontend-acceptance.md +23 -0
  31. package/docs/release-closeout/issues.md +19 -0
  32. package/docs/release-closeout/verification.md +28 -0
  33. package/docs/release-contract.md +108 -0
  34. package/docs/research-studio-guide.zh-CN.md +166 -0
  35. package/docs/sciverse-api.md +125 -0
  36. package/eduevidence_cli.py +29 -13
  37. package/engine/_resources.py +13 -0
  38. package/engine/autoevolve/__init__.py +3 -0
  39. package/engine/autoevolve/agent_view.py +167 -0
  40. package/engine/autoevolve/core.py +357 -0
  41. package/engine/autoevolve/events.py +11 -0
  42. package/engine/autoevolve/git_workspace.py +77 -0
  43. package/engine/autoevolve/projection.py +23 -0
  44. package/engine/autoevolve/runner.py +413 -0
  45. package/engine/autoevolve/trust.py +146 -0
  46. package/engine/autoresearch/__init__.py +6 -0
  47. package/engine/autoresearch/commit.py +132 -0
  48. package/engine/autoresearch/contracts.py +126 -0
  49. package/engine/autoresearch/controller.py +207 -0
  50. package/engine/autoresearch/events.py +12 -0
  51. package/engine/autoresearch/gap_priority.py +168 -0
  52. package/engine/autoresearch/projection.py +30 -0
  53. package/engine/autoresearch/research_memory.py +59 -0
  54. package/engine/autoresearch/saturation.py +91 -0
  55. package/engine/briefs.py +2 -1
  56. package/engine/capabilities.py +1 -0
  57. package/engine/contracts.py +3 -1
  58. package/engine/decision_policy.py +96 -0
  59. package/engine/evidence_graph.py +14 -10
  60. package/engine/evidencecore.py +7 -5
  61. package/engine/gaps.py +132 -73
  62. package/engine/ids.py +2 -0
  63. package/engine/judge_pack.py +65 -0
  64. package/engine/library.py +6 -2
  65. package/engine/library_builtin.py +3 -1
  66. package/engine/living.py +36 -5
  67. package/engine/meta_synthesis.py +3 -1
  68. package/engine/migration.py +88 -3
  69. package/engine/orchestration.py +460 -0
  70. package/engine/paths.py +2 -0
  71. package/engine/pilot.py +36 -33
  72. package/engine/project.py +2 -2
  73. package/engine/research_service.py +113 -0
  74. package/engine/studio_read_model.py +400 -0
  75. package/engine/taxonomy.py +211 -0
  76. package/engine/tribunal.py +44 -33
  77. package/engine/update.py +1 -0
  78. package/engine/versions.py +1 -1
  79. package/engine/worker_result.py +109 -0
  80. package/engine/workflows.py +70 -0
  81. package/examples/ai-coding-assistant-evidence/EduEvidence_Report.html +2934 -0
  82. package/examples/ai-coding-assistant-evidence/artifact_manifest.json +15 -0
  83. package/examples/ai-coding-assistant-evidence/citation_check.json +79 -0
  84. package/examples/ai-coding-assistant-evidence/claims.jsonl +12 -0
  85. package/examples/ai-coding-assistant-evidence/evaluation.json +35 -0
  86. package/examples/ai-coding-assistant-evidence/evidence.jsonl +12 -0
  87. package/examples/ai-coding-assistant-evidence/final_verdict.json +107 -0
  88. package/examples/ai-coding-assistant-evidence/frame.json +48 -0
  89. package/examples/ai-coding-assistant-evidence/gate_report.json +101 -0
  90. package/examples/ai-coding-assistant-evidence/intervention.json +51 -0
  91. package/examples/ai-coding-assistant-evidence/methodology.json +36 -0
  92. package/examples/ai-coding-assistant-evidence/raw_verdict.json +86 -0
  93. package/examples/ai-coding-assistant-evidence/report_spec.json +230 -0
  94. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_academic.html +2934 -0
  95. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_claude.html +2934 -0
  96. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab-dark.html +2934 -0
  97. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab.html +2934 -0
  98. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_presentation.html +2934 -0
  99. package/examples/ai-coding-assistant-evidence/reports-5themes/report_academic.html +2934 -0
  100. package/examples/ai-coding-assistant-evidence/reports-5themes/report_claude.html +2934 -0
  101. package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab-dark.html +2934 -0
  102. package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab.html +2934 -0
  103. package/examples/ai-coding-assistant-evidence/reports-5themes/report_presentation.html +2934 -0
  104. package/examples/ai-coding-assistant-evidence/result.json +1457 -0
  105. package/examples/ai-coding-assistant-evidence/result.zh.json +1457 -0
  106. package/examples/ai-coding-assistant-evidence/skeptic.json +72 -0
  107. package/examples/ai-coding-assistant-evidence/sources.jsonl +8 -0
  108. package/examples/ai-coding-assistant-evidence/verdict.json +107 -0
  109. package/examples/spaced-retrieval-practice/applicability.json +14 -0
  110. package/examples/spaced-retrieval-practice/artifact_manifest.json +15 -0
  111. package/examples/spaced-retrieval-practice/claims.jsonl +3 -0
  112. package/examples/spaced-retrieval-practice/evidence.jsonl +6 -0
  113. package/examples/spaced-retrieval-practice/final_verdict.json +93 -0
  114. package/examples/spaced-retrieval-practice/frame.json +58 -0
  115. package/examples/spaced-retrieval-practice/gate_report.json +101 -0
  116. package/examples/spaced-retrieval-practice/methodology.json +78 -0
  117. package/examples/spaced-retrieval-practice/report_spec.json +212 -0
  118. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_academic.html +2728 -0
  119. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_claude.html +2728 -0
  120. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab-dark.html +2728 -0
  121. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab.html +2728 -0
  122. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_presentation.html +2728 -0
  123. package/examples/spaced-retrieval-practice/reports-5themes/report_academic.html +2728 -0
  124. package/examples/spaced-retrieval-practice/reports-5themes/report_claude.html +2728 -0
  125. package/examples/spaced-retrieval-practice/reports-5themes/report_datalab-dark.html +2728 -0
  126. package/examples/spaced-retrieval-practice/reports-5themes/report_datalab.html +2728 -0
  127. package/examples/spaced-retrieval-practice/reports-5themes/report_presentation.html +2728 -0
  128. package/examples/spaced-retrieval-practice/result.json +942 -0
  129. package/examples/spaced-retrieval-practice/result.zh.json +942 -0
  130. package/examples/spaced-retrieval-practice/skeptic.json +70 -0
  131. package/examples/spaced-retrieval-practice/sources.jsonl +7 -0
  132. package/examples/spaced-retrieval-practice/verdict.json +93 -0
  133. package/examples/workplace-ai-assistant/artifact_manifest.json +15 -0
  134. package/examples/workplace-ai-assistant/claims.jsonl +4 -0
  135. package/examples/workplace-ai-assistant/evaluation.json +19 -0
  136. package/examples/workplace-ai-assistant/evidence.jsonl +4 -0
  137. package/examples/workplace-ai-assistant/evidence_graph.json +444 -0
  138. package/examples/workplace-ai-assistant/final_verdict.json +78 -0
  139. package/examples/workplace-ai-assistant/frame.json +41 -0
  140. package/examples/workplace-ai-assistant/gate_report.json +101 -0
  141. package/examples/workplace-ai-assistant/intervention.json +27 -0
  142. package/examples/workplace-ai-assistant/legacy-link-check.json +16 -0
  143. package/examples/workplace-ai-assistant/methodology.json +60 -0
  144. package/examples/workplace-ai-assistant/report_spec.json +224 -0
  145. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_academic.html +2814 -0
  146. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_claude.html +2814 -0
  147. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab-dark.html +2814 -0
  148. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab.html +2814 -0
  149. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_presentation.html +2814 -0
  150. package/examples/workplace-ai-assistant/reports-5themes/report_academic.html +2814 -0
  151. package/examples/workplace-ai-assistant/reports-5themes/report_claude.html +2814 -0
  152. package/examples/workplace-ai-assistant/reports-5themes/report_datalab-dark.html +2814 -0
  153. package/examples/workplace-ai-assistant/reports-5themes/report_datalab.html +2814 -0
  154. package/examples/workplace-ai-assistant/reports-5themes/report_presentation.html +2814 -0
  155. package/examples/workplace-ai-assistant/result.json +615 -0
  156. package/examples/workplace-ai-assistant/result.zh.json +615 -0
  157. package/examples/workplace-ai-assistant/search_log.json +19 -0
  158. package/examples/workplace-ai-assistant/skeptic.json +72 -0
  159. package/examples/workplace-ai-assistant/sources.jsonl +3 -0
  160. package/examples/workplace-ai-assistant/validation_result.json +9 -0
  161. package/examples/workplace-ai-assistant/verdict.json +78 -0
  162. package/install.sh +7 -7
  163. package/integrations/agent_mcp.py +2 -2
  164. package/integrations/orchestration_dispatch.py +146 -0
  165. package/package.json +46 -3
  166. package/pyproject.toml +14 -22
  167. package/references/autoresearch.md +30 -0
  168. package/references/evaluation-policy.md +24 -0
  169. package/references/orchestration.md +22 -0
  170. package/references/report-copy-style.md +67 -0
  171. package/references/retrieval-compliance.md +75 -0
  172. package/references/retrieval-protocol.md +20 -0
  173. package/references/scientific-invariants.md +19 -0
  174. package/retrieval/audit.py +178 -0
  175. package/retrieval/fetch.py +96 -0
  176. package/retrieval/sciverse.py +398 -0
  177. package/retrieval/search.py +47 -7
  178. package/schemas/applicability.schema.json +94 -0
  179. package/schemas/chart-spec.schema.json +10 -3
  180. package/schemas/evidence.schema.json +316 -43
  181. package/schemas/fetch-result.schema.json +2 -1
  182. package/schemas/intervention.schema.json +106 -21
  183. package/schemas/report-result.schema.json +12 -4
  184. package/schemas/report-spec.schema.json +98 -100
  185. package/schemas/skeptic.schema.json +86 -0
  186. package/schemas/source.schema.json +21 -2
  187. package/schemas/v2/finding.schema.json +5 -1
  188. package/schemas/v2/methodology-audit.schema.json +5 -1
  189. package/schemas/v2/outcome.schema.json +28 -5
  190. package/schemas/v2/project.schema.json +2 -2
  191. package/schemas/v2/run.schema.json +1 -1
  192. package/schemas/v2/study.schema.json +5 -1
  193. package/schemas/vNext/autoevolve-session.schema.json +34 -0
  194. package/schemas/vNext/eval-snapshot.schema.json +77 -0
  195. package/schemas/vNext/execution-plan.schema.json +50 -0
  196. package/schemas/vNext/gap-priority.schema.json +54 -0
  197. package/schemas/vNext/negative-search-record.schema.json +68 -0
  198. package/schemas/vNext/research-iteration.schema.json +87 -0
  199. package/schemas/vNext/research-strategy.schema.json +62 -0
  200. package/schemas/vNext/skill-experiment.schema.json +90 -0
  201. package/schemas/vNext/task-spec.schema.json +156 -0
  202. package/schemas/vNext/worker-result.schema.json +60 -0
  203. package/schemas/verdict.schema.json +164 -28
  204. package/scripts/benchmark_judge.py +2 -2
  205. package/scripts/benchmark_v3.py +26 -43
  206. package/scripts/build_esl_artifacts.py +4 -4
  207. package/scripts/build_evidence_library.py +2 -2
  208. package/scripts/build_gh_pages.py +98 -0
  209. package/scripts/build_readme_diagrams.py +72 -0
  210. package/scripts/build_report_variants.py +101 -0
  211. package/scripts/build_result.py +74 -9
  212. package/scripts/check_autoresearch_invariants.py +95 -0
  213. package/scripts/check_package_parity.py +85 -0
  214. package/scripts/check_protocol_alignment.py +375 -0
  215. package/scripts/check_versioned_schemas.py +254 -0
  216. package/scripts/claim_audit.py +13 -8
  217. package/scripts/compute_confidence.py +10 -0
  218. package/scripts/daily_evolve.py +30 -0
  219. package/scripts/dashboard_server.py +130 -101
  220. package/scripts/did_regression.py +17 -32
  221. package/scripts/enrich_projects_human_and_lieflat.py +1 -1
  222. package/scripts/evidence_score.py +5 -2
  223. package/scripts/generate_metrics.py +4 -3
  224. package/scripts/generate_new_projects.py +5 -5
  225. package/scripts/orchestrator.py +286 -36
  226. package/scripts/pre_verdict_gate.py +224 -26
  227. package/scripts/quickstart.py +18 -2
  228. package/scripts/rebake_all_5themes.py +1 -2
  229. package/scripts/research_auto_cli.py +475 -0
  230. package/scripts/run_workspace.py +24 -8
  231. package/scripts/search_provenance.py +64 -0
  232. package/scripts/serve_web.py +9 -10
  233. package/scripts/skill_lint.py +1 -1
  234. package/scripts/skill_payload.py +81 -0
  235. package/scripts/test_adversarial_empirical.py +26 -19
  236. package/scripts/validate_schema.py +46 -2
  237. package/scripts/vnext_cli.py +133 -0
  238. package/setup.py +12 -0
  239. package/skill/agents/evaluation-designer.md +20 -4
  240. package/skill/agents/evidence-analyst.md +19 -3
  241. package/skill/agents/evidence-judge.md +50 -2
  242. package/skill/agents/evidence-retriever.md +20 -3
  243. package/skill/agents/intervention-designer.md +20 -4
  244. package/skill/agents/method-reviewer.md +18 -2
  245. package/skill/agents/{education-planner.md → research-planner.md} +19 -3
  246. package/skill/agents/skeptic.md +18 -2
  247. package/skill/roles/registry.yaml +45 -0
  248. package/skill/sub-skills/aihot-trend-analysis/SKILL.md +28 -9
  249. package/skill/sub-skills/contradiction-analysis/SKILL.md +31 -11
  250. package/skill/sub-skills/data-analysis/SKILL.md +34 -15
  251. package/skill/sub-skills/ethics-review/SKILL.md +33 -10
  252. package/skill/sub-skills/evidence-extraction/SKILL.md +29 -11
  253. package/skill/sub-skills/evidence-review/SKILL.md +31 -12
  254. package/skill/sub-skills/gap-analysis/SKILL.md +31 -9
  255. package/skill/sub-skills/literature-review/SKILL.md +35 -14
  256. package/skill/sub-skills/methodology-audit/SKILL.md +29 -12
  257. package/skill/sub-skills/report-generation/SKILL.md +40 -6
  258. package/skill/sub-skills/research-planning/SKILL.md +41 -14
  259. package/skill/sub-skills/study-design/SKILL.md +30 -9
  260. package/skill/task-briefs/adjudicate.md +32 -7
  261. package/skill/task-briefs/applicability.md +38 -0
  262. package/skill/task-briefs/audit.md +32 -7
  263. package/skill/task-briefs/challenge.md +34 -5
  264. package/skill/task-briefs/evaluate.md +30 -5
  265. package/skill/task-briefs/extract.md +31 -8
  266. package/skill/task-briefs/frame.md +39 -10
  267. package/skill/task-briefs/intervene.md +32 -6
  268. package/skill/task-briefs/present.md +32 -8
  269. package/skill/task-briefs/projection.md +37 -0
  270. package/skill/task-briefs/retrieve.md +36 -6
  271. package/skill/workflows/decision-and-pilot.md +85 -0
  272. package/skill/workflows/evaluate-and-update.md +93 -0
  273. package/skill/workflows/evidence-review.md +117 -0
  274. package/visualization/eduevidence-report/assets/base.css +2 -2
  275. package/visualization/eduevidence-report/assets/reader.css +752 -0
  276. package/visualization/eduevidence-report/assets/reader.js +132 -0
  277. package/visualization/eduevidence-report/references/chart-selection-catalog.md +109 -0
  278. package/visualization/eduevidence-report/references/lieflat-composition.md +3 -1
  279. package/visualization/eduevidence-report/scripts/build_figures.py +25 -3
  280. package/visualization/eduevidence-report/scripts/build_infographics.py +5 -1
  281. package/visualization/eduevidence-report/scripts/build_report.py +561 -121
  282. package/visualization/eduevidence-report/scripts/charts_data.py +2 -0
  283. package/visualization/eduevidence-report/scripts/lieflat_engine.py +371 -136
  284. package/visualization/eduevidence-report/scripts/zh_labels.py +80 -1
  285. package/visualization/eduevidence-report/themes/academic.css +1 -1
  286. package/visualization/eduevidence-report/themes/claude.css +1 -1
  287. package/visualization/eduevidence-report/themes/datalab-dark.css +2 -2
  288. package/visualization/eduevidence-report/themes/datalab.css +2 -2
  289. package/visualization/eduevidence-report/themes/presentation.css +2 -2
  290. package/web/README.md +18 -0
  291. package/web/architecture.html +14885 -0
  292. package/web/index.html +53 -0
  293. package/web/studio/THIRD_PARTY_LICENSES.txt +146 -0
  294. package/web/studio/assets/index-B8tkF44Q.css +1 -0
  295. package/web/studio/assets/index-CQ6Keoyc.js +230 -0
  296. package/web/studio/config.json +1 -0
  297. package/web/studio/index.html +14 -0
  298. package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
  299. package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
  300. package/engine/__pycache__/bias.cpython-312.pyc +0 -0
  301. package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
  302. package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
  303. package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
  304. package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
  305. package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
  306. package/engine/__pycache__/events.cpython-312.pyc +0 -0
  307. package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
  308. package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
  309. package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
  310. package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
  311. package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
  312. package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
  313. package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
  314. package/engine/__pycache__/ids.cpython-312.pyc +0 -0
  315. package/engine/__pycache__/library.cpython-312.pyc +0 -0
  316. package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
  317. package/engine/__pycache__/living.cpython-312.pyc +0 -0
  318. package/engine/__pycache__/log.cpython-312.pyc +0 -0
  319. package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
  320. package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
  321. package/engine/__pycache__/migration.cpython-312.pyc +0 -0
  322. package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
  323. package/engine/__pycache__/paths.cpython-312.pyc +0 -0
  324. package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
  325. package/engine/__pycache__/planner.cpython-312.pyc +0 -0
  326. package/engine/__pycache__/project.cpython-312.pyc +0 -0
  327. package/engine/__pycache__/projections.cpython-312.pyc +0 -0
  328. package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
  329. package/engine/__pycache__/run.cpython-312.pyc +0 -0
  330. package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
  331. package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
  332. package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
  333. package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
  334. package/engine/__pycache__/update.cpython-312.pyc +0 -0
  335. package/engine/__pycache__/versions.cpython-312.pyc +0 -0
  336. package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
  337. package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
  338. package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
  339. package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
  340. package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
  341. package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
  342. package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
  343. package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
  344. package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
  345. package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
  346. package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
  347. package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  348. package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
  349. package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
  350. package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
  351. package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
  352. package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
  353. package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
  354. package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
  355. package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
  356. package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
  357. package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
  358. package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
  359. package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
  360. package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
  361. package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
  362. package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
  363. package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
  364. package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
  365. package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
  366. package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
  367. package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
  368. package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
  369. package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
  370. package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
  371. package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
  372. package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
  373. package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
  374. package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
  375. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
  376. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
  377. package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
  378. package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
  379. package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
  380. package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
  381. package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
  382. package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
  383. package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
  384. package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
  385. package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
  386. package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
@@ -10,14 +10,14 @@ stages); the two deterministic stages it executes locally are:
10
10
  adjudicate — Pre-Verdict Gate (scripts/pre_verdict_gate.py) + deterministic
11
11
  confidence (scripts/compute_confidence.py) producing
12
12
  final_verdict.json from raw_verdict.json + evidence.jsonl
13
- present — assemble result.json from the workspace artifacts
13
+ projection — assemble result.json and renderable projections from artifacts
14
14
  (decision = final_verdict.json; claims carry claim_id per
15
15
  report-result.schema.json)
16
16
 
17
17
  Stage machine (execution_plan.json / state.json):
18
18
 
19
19
  frame -> retrieve -> extract -> challenge -> audit -> adjudicate
20
- -> intervene -> evaluate -> present
20
+ -> applicability -> intervene -> evaluate -> projection
21
21
 
22
22
  Each stage writes exactly one primary artifact and is schema-gated against
23
23
  schemas/*. When the artifact is missing the orchestrator either seeds it from
@@ -51,6 +51,9 @@ for _p in (str(ROOT), str(ROOT / "scripts")):
51
51
  if _p not in sys.path:
52
52
  sys.path.insert(0, _p)
53
53
 
54
+ from engine._resources import resource_root # noqa: E402
55
+ ROOT = resource_root()
56
+
54
57
  from run_workspace import (RESOURCE_POLICY_VERSION, STAGES, RunWorkspace, # noqa: E402
55
58
  load_json, load_jsonl, next_run_id, save_jsonl)
56
59
  from pre_verdict_gate import apply_enforcement, evaluate_workspace # noqa: E402
@@ -64,15 +67,20 @@ DEPTHS = ("S", "M", "L")
64
67
 
65
68
  #: Stage -> primary artifact + schema gate + whether it is locally executable.
66
69
  STAGE_SPEC: dict[str, dict[str, Any]] = {
67
- "frame": {"artifact": "frame.json", "schema": "education-frame.schema.json", "jsonl": False, "local": False},
70
+ # The frame contract belongs to the run's domain; the placeholder is
71
+ # resolved by frame_schema_for() below (education-frame.schema.json
72
+ # for education, domains/policy/frame.schema.json for policy, and so
73
+ # on for any domain registered under domains/).
74
+ "frame": {"artifact": "frame.json", "schema": "@domain_frame", "jsonl": False, "local": False},
68
75
  "retrieve": {"artifact": "sources.jsonl", "schema": "source.schema.json", "jsonl": True, "local": False},
69
76
  "extract": {"artifact": "evidence.jsonl", "schema": "evidence.schema.json", "jsonl": True, "local": False},
70
- "challenge": {"artifact": "skeptic.json", "schema": None, "jsonl": False, "local": False},
77
+ "challenge": {"artifact": "skeptic.json", "schema": "skeptic.schema.json", "jsonl": False, "local": False},
71
78
  "audit": {"artifact": "methodology.json", "schema": "methodology.schema.json", "jsonl": False, "local": False},
72
79
  "adjudicate": {"artifact": "final_verdict.json", "schema": "verdict.schema.json", "jsonl": False, "local": True},
80
+ "applicability": {"artifact": "applicability.json", "schema": "applicability.schema.json", "jsonl": False, "local": False},
73
81
  "intervene": {"artifact": "intervention.json", "schema": "intervention.schema.json", "jsonl": False, "local": False},
74
82
  "evaluate": {"artifact": "evaluation.json", "schema": "evaluation.schema.json", "jsonl": False, "local": False},
75
- "present": {"artifact": "result.json", "schema": "report-result.schema.json", "jsonl": False, "local": True},
83
+ "projection": {"artifact": "result.json", "schema": "report-result.schema.json", "jsonl": False, "local": True},
76
84
  }
77
85
 
78
86
  #: Phase 33 — canonical failure -> handling-action mapping. Extends the
@@ -143,11 +151,14 @@ _STAGE_BRIEFS: dict[str, str] = {
143
151
  "adjudicate": ("Judge the evidence: write raw_verdict.json (model verdict). The orchestrator "
144
152
  "then runs the Pre-Verdict Gate and deterministic confidence to produce "
145
153
  "final_verdict.json."),
154
+ "applicability": ("Assess whether supported effects apply to the target population, setting, "
155
+ "implementation constraints and outcomes; write applicability.json. Do not "
156
+ "upgrade a decision merely because evidence is present."),
146
157
  "intervene": ("Design the minimal verifiable teaching intervention (phased pilot, "
147
158
  "stop conditions, evidence alignment); write intervention.json."),
148
159
  "evaluate": ("Design the evaluation plan (baseline/post/retention/transfer, task vs learning "
149
160
  "separation); write evaluation.json."),
150
- "present": ("Translate result.json into result.zh.json and render report_spec.json / "
161
+ "projection": ("Translate result.json into result.zh.json and render report_spec.json / "
151
162
  "report.html via the visualization layer."),
152
163
  }
153
164
 
@@ -179,11 +190,16 @@ def init_run(
179
190
  run_id: str | None = None,
180
191
  approve_agent_mcp: bool = False,
181
192
  scp_available: bool | None = None,
193
+ approval_record: dict | None = None,
194
+ domain: str = "education",
182
195
  ) -> RunWorkspace:
183
196
  """Create the run workspace + manifest + planning artifacts (Phase 11-13)."""
184
197
  depth = DEPTH_ALIASES.get(depth, depth)
185
198
  if depth not in DEPTHS:
186
199
  raise ValueError(f"unknown depth {depth!r}; use quick/standard/deep or S/M/L")
200
+ # Validate the domain up front: every later stage gate reads it from the
201
+ # manifest, so an unknown id must fail here rather than mid-run.
202
+ domain_frame_schema(domain)
187
203
 
188
204
  try:
189
205
  from integrations.agent_mcp import detect_agent_mcp
@@ -206,6 +222,7 @@ def init_run(
206
222
  agent_mcp_available=agent_available,
207
223
  agent_mcp_approved=approve_agent_mcp,
208
224
  root=ROOT,
225
+ domain=domain,
209
226
  )
210
227
  ws.save_manifest(manifest)
211
228
 
@@ -256,7 +273,7 @@ def init_run(
256
273
  "run_id": run_id,
257
274
  "execution_mode": agent_mode,
258
275
  "routing": {
259
- "education-planner": "strong/reasoning",
276
+ "research-planner": "strong/reasoning",
260
277
  "evidence-retriever": "fast/low-cost",
261
278
  "evidence-analyst": "strong/structured",
262
279
  "skeptic": "independent/reasoning",
@@ -275,6 +292,16 @@ def init_run(
275
292
  "reason": ("user-approved via --approve-agent-mcp"
276
293
  if approve_agent_mcp else "not yet approved; runs in platform-native mode"),
277
294
  }
295
+ if approval_record:
296
+ agent_mcp_approval["approval_global_path"] = str(_global_approval_path())
297
+ agent_mcp_approval["role_mapping_hash"] = approval_record.get("role_mapping_hash")
298
+ agent_mcp_approval["roles"] = approval_record.get("roles", {})
299
+ agent_mcp_approval["approved_at"] = _utc_now()
300
+ agent_mcp_approval["reason"] = "user-confirmed role mapping (global approval, hash-verified)"
301
+ elif approve_agent_mcp:
302
+ agent_mcp_approval["reason"] = (
303
+ "user-approved via --approve-agent-mcp (boolean only; a role mapping "
304
+ "in the global approval is required before any spawn)")
278
305
 
279
306
  for name, data in (("capability_plan", capability_plan),
280
307
  ("resource_plan", resource_plan),
@@ -301,27 +328,67 @@ def _load_artifact(ws: RunWorkspace, artifact: str) -> list[dict[str, Any]]:
301
328
  return [data] if data else []
302
329
 
303
330
 
331
+
332
+ #: Placeholder replaced by the run's registered frame schema.
333
+ DOMAIN_FRAME_SENTINEL = "@domain_frame"
334
+
335
+
336
+ def domain_frame_schema(domain: str) -> str:
337
+ """Registered frame schema for a domain, relative to the repository root.
338
+
339
+ Raises ValueError for an unknown domain so a typo fails loudly instead of
340
+ silently validating against the education contract.
341
+ """
342
+ try:
343
+ from engine.evidencecore import load_domain
344
+
345
+ entry = load_domain(domain)
346
+ except KeyError as exc:
347
+ raise ValueError(str(exc)) from exc
348
+ return str(entry["frame_schema"]) # type: ignore[return-value]
349
+
350
+
351
+ def frame_schema_for(ws: "RunWorkspace") -> str:
352
+ """The frame schema this run must satisfy (from its manifest domain)."""
353
+ domain = str(ws.load_manifest().get("domain") or "education")
354
+ return domain_frame_schema(domain)
355
+
356
+
304
357
  def schema_gate(ws: RunWorkspace, stage: str) -> dict[str, Any]:
305
358
  """Validate a stage's primary artifact against its schema. Never raises."""
306
359
  spec = STAGE_SPEC[stage]
307
360
  artifact = spec["artifact"]
308
361
  schema_name = spec["schema"]
309
- if schema_name is None: # challenge: light parseability contract
362
+ if schema_name == DOMAIN_FRAME_SENTINEL:
363
+ # The frame contract is per-domain; resolve it from the run manifest.
364
+ try:
365
+ schema_name = frame_schema_for(ws)
366
+ except ValueError as exc:
367
+ return {"passed": False, "stage": stage, "artifact": artifact,
368
+ "schema": DOMAIN_FRAME_SENTINEL,
369
+ "issues": [f"unknown run domain: {exc}"]}
370
+ if schema_name is None: # lightweight parseability contract
310
371
  data = load_json(ws.path / artifact)
311
372
  ok = bool(data) and isinstance(data, dict)
312
373
  return {"passed": ok, "stage": stage, "artifact": artifact,
313
- "schema": None, "issues": [] if ok else ["skeptic.json missing or unparseable"]}
374
+ "schema": None, "issues": [] if ok else [f"{artifact} missing or unparseable"]}
314
375
 
315
376
  from validate_schema import SchemaError, Validator
316
377
 
317
378
  schemas_dir = ROOT / "schemas"
318
- if not (schemas_dir / schema_name).is_file():
319
- share_dir = Path(sys.prefix) / "share" / "eduevidence" / "schemas"
320
- if (share_dir / schema_name).is_file():
321
- schemas_dir = share_dir
379
+ schema_path = schemas_dir / schema_name
380
+ if not schema_path.is_file():
381
+ # A domain may own its frame schema outside schemas/ (policy does).
382
+ candidate = ROOT / schema_name
383
+ if candidate.is_file():
384
+ schema_path = candidate
385
+ else:
386
+ share_candidates = (Path(sys.prefix) / "share" / "eduevidence" / schema_name,
387
+ Path(sys.prefix) / "share" / "eduevidence" / "schemas" / schema_name)
388
+ schema_path = next((c for c in share_candidates if c.is_file()), schema_path)
322
389
 
323
390
  try:
324
- schema = json.loads((schemas_dir / schema_name).read_text(encoding="utf-8"))
391
+ schema = json.loads(schema_path.read_text(encoding="utf-8"))
325
392
  except OSError:
326
393
  return {"passed": False, "stage": stage, "artifact": artifact,
327
394
  "schema": schema_name, "issues": [f"schema file {schema_name} not found"]}
@@ -380,22 +447,52 @@ def derive_sources_from_evidence(evidence: list[dict[str, Any]]) -> list[dict[st
380
447
  return list(seen.values())
381
448
 
382
449
 
450
+ #: The nine checks the skeptic contract requires (skill/task-briefs/challenge.md).
451
+ SKEPTIC_CHECKS = (
452
+ "1_null_result", "2_negative_result", "3_contradictory_evidence",
453
+ "4_alternative_explanation", "5_measurement_mismatch", "6_sampling_bias",
454
+ "7_novelty_effect", "8_ai_dependency", "9_scope_overreach",
455
+ )
456
+
457
+
383
458
  def derive_skeptic_from_evidence(evidence: list[dict[str, Any]]) -> dict[str, Any]:
384
459
  """Deterministic skeptic summary derived from evidence directions (demo/test mode).
385
460
 
386
461
  Records what the corpus itself contains (contradictions / null results /
387
- confounders); it never invents counter-evidence.
462
+ confounders); it never invents counter-evidence. Field names follow the
463
+ challenge brief and the skeptic role prompt so the Pre-Verdict Gate reads
464
+ the same keys this writes.
388
465
  """
389
- contradictions = [e.get("evidence_id") for e in evidence if e.get("direction") == "contradict"]
390
- null_results = [e.get("evidence_id") for e in evidence if e.get("direction") == "neutral"]
466
+ contradictions = [e.get("evidence_id") for e in evidence
467
+ if (e.get("relation_to_claim") or e.get("direction")) == "contradict"]
468
+ null_results = [e.get("evidence_id") for e in evidence
469
+ if e.get("effect_direction") == "null"]
391
470
  confounders = sorted({c for e in evidence for c in (e.get("confounders", []) or [])})
471
+
472
+ status_for = {
473
+ "1_null_result": "found" if null_results else "not_found",
474
+ "2_negative_result": "found" if any(
475
+ e.get("effect_direction") == "negative" for e in evidence) else "not_found",
476
+ "3_contradictory_evidence": "found" if contradictions else "not_found",
477
+ "4_alternative_explanation": "found" if confounders else "not_found",
478
+ }
479
+ findings = []
480
+ for check in SKEPTIC_CHECKS:
481
+ findings.append({
482
+ "check": check,
483
+ "status": status_for.get(check, "not_found"),
484
+ "detail": "derived from the evidence corpus in demo/test mode",
485
+ "related_evidence_ids": (contradictions if "contradict" in check
486
+ else null_results if "null" in check else []),
487
+ })
392
488
  return {
393
489
  "search_performed": True,
394
490
  "method": "derived from evidence corpus directions (demo/test mode)",
395
- "contradictions": contradictions,
396
- "null_results": null_results,
397
- "confounders": confounders,
398
- "no_contradictory_evidence_found": not contradictions,
491
+ "skeptic_findings": findings,
492
+ "contradictory_evidence_found": bool(contradictions),
493
+ "no_contradictory_evidence_statement": (
494
+ "" if contradictions else "NO CONTRADICTORY EVIDENCE FOUND"),
495
+ "threats_to_validity": confounders,
399
496
  }
400
497
 
401
498
 
@@ -404,7 +501,13 @@ def _cap_verdict(gate: dict[str, Any], raw_verdict: dict[str, Any],
404
501
  """Build final_verdict.json: deterministic confidence + gate enforcement."""
405
502
  final = copy.deepcopy(raw_verdict)
406
503
  final["raw_model_confidence"] = raw_verdict.get("confidence")
407
- final["raw_model_confidence_breakdown"] = raw_verdict.get("confidence_breakdown")
504
+ # The schema types this as an object; a model verdict without the field
505
+ # produced None here, which made the written file schema-invalid and the
506
+ # gate fail on data the pipeline had just produced.
507
+ final["raw_model_confidence_breakdown"] = (
508
+ raw_verdict.get("confidence_breakdown")
509
+ if isinstance(raw_verdict.get("confidence_breakdown"), dict)
510
+ else {})
408
511
  final["confidence"] = computed["confidence"]
409
512
  final["confidence_score"] = computed["confidence_breakdown"].get("score")
410
513
  final["confidence_policy_version"] = computed["confidence_policy_version"]
@@ -512,7 +615,8 @@ def _assemble_result(ws: RunWorkspace, manifest: dict[str, Any]) -> dict[str, An
512
615
  "methodology_reviews": methodology_list,
513
616
  "conflicts": [{"reason_for_disagreement": verdict.get("reason_for_disagreement", "")}]
514
617
  if verdict.get("reason_for_disagreement") else [],
515
- "applicability": verdict.get("applicability", {}),
618
+ "applicability": (load_json(ws.path / "applicability.json")
619
+ or verdict.get("applicability", {})),
516
620
  "intervention": intervention,
517
621
  "evaluation": evaluation,
518
622
  "benchmark": {},
@@ -520,14 +624,14 @@ def _assemble_result(ws: RunWorkspace, manifest: dict[str, Any]) -> dict[str, An
520
624
  }
521
625
 
522
626
 
523
- def _run_present(ws: RunWorkspace, manifest: dict[str, Any], question: str,
524
- demo_pack: Path | None = None) -> dict[str, Any]:
525
- """Local present: assemble + validate result.json, seed render artifacts."""
627
+ def _run_projection(ws: RunWorkspace, manifest: dict[str, Any], question: str,
628
+ demo_pack: Path | None = None) -> dict[str, Any]:
629
+ """Build projections after science; this is not a scientific protocol stage."""
526
630
  required = ("final_verdict.json", "intervention.json", "evaluation.json")
527
631
  missing = [name for name in required if not (ws.path / name).is_file()
528
632
  or not load_json(ws.path / name)]
529
633
  if missing:
530
- ws.write_brief("present", question, _STAGE_BRIEFS["present"])
634
+ ws.write_brief("projection", question, _STAGE_BRIEFS["projection"])
531
635
  return {"status": "pending",
532
636
  "detail": f"missing prerequisite artifacts: {', '.join(missing)}"}
533
637
 
@@ -550,7 +654,7 @@ def _run_present(ws: RunWorkspace, manifest: dict[str, Any], question: str,
550
654
  result = _assemble_result(ws, manifest)
551
655
  (ws.path / "result.json").write_text(
552
656
  json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
553
- gate = schema_gate(ws, "present")
657
+ gate = schema_gate(ws, "projection")
554
658
  if not gate["passed"]:
555
659
  return {"status": "failed", "detail": f"result.json schema gate: {gate['issues']}"}
556
660
  missing_render = [n for n in ("result.zh.json", "report_spec.json", "report.html")
@@ -576,6 +680,10 @@ def run_stage(ws: RunWorkspace, stage: str, *, demo_pack: Path | None = None) ->
576
680
  Deterministic stages execute locally; external stages are either seeded
577
681
  from ``demo_pack`` (demo/test mode) or handed off via a task brief.
578
682
  """
683
+ # Compatibility for callers of the retired name. State/manifests only
684
+ # record ``projection`` from this point forward.
685
+ if stage == "present":
686
+ stage = "projection"
579
687
  ws.trace("stage_started", stage=stage)
580
688
  log.info("stage=%s run=%s start", stage, ws.run_id)
581
689
  spec = STAGE_SPEC[stage]
@@ -598,8 +706,8 @@ def run_stage(ws: RunWorkspace, stage: str, *, demo_pack: Path | None = None) ->
598
706
  # local deterministic stages
599
707
  if stage == "adjudicate":
600
708
  result = _run_adjudicate(ws, question, demo_pack=demo_pack)
601
- elif stage == "present":
602
- result = _run_present(ws, ws.load_manifest(), question, demo_pack=demo_pack)
709
+ elif stage == "projection":
710
+ result = _run_projection(ws, ws.load_manifest(), question, demo_pack=demo_pack)
603
711
  else:
604
712
  # demo/test seeding
605
713
  if demo_pack is not None:
@@ -666,6 +774,27 @@ def _seed_from_demo(ws: RunWorkspace, stage: str, demo_pack: Path) -> dict[str,
666
774
  if (pack / "methodology.json").is_file():
667
775
  (ws.path / "methodology.json").write_bytes((pack / "methodology.json").read_bytes())
668
776
  return {"seeded": True, "detail": "methodology.json seeded from demo pack"}
777
+ elif stage == "applicability":
778
+ if (pack / "applicability.json").is_file():
779
+ (ws.path / "applicability.json").write_bytes((pack / "applicability.json").read_bytes())
780
+ else:
781
+ verdict = load_json(ws.path / "final_verdict.json") or load_json(pack / "verdict.json")
782
+ value = verdict.get("applicability") if isinstance(verdict, dict) else None
783
+ # A demo can only carry the decision's existing applicability
784
+ # boundary; absence remains explicit rather than inferred.
785
+ # The derived boundary is tagged ASSESSED so it satisfies the
786
+ # applicability contract instead of being an unlabelled dict.
787
+ if isinstance(value, dict) and value:
788
+ payload = {"status": "ASSESSED"}
789
+ payload.update(value)
790
+ else:
791
+ payload = {
792
+ "status": "NOT_CAPTURED",
793
+ "reason": "demo pack does not provide an applicability assessment",
794
+ }
795
+ (ws.path / "applicability.json").write_text(
796
+ json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
797
+ return {"seeded": True, "detail": "applicability.json seeded from decision boundary (demo)"}
669
798
  elif stage == "intervene":
670
799
  if (pack / "intervention.json").is_file():
671
800
  (ws.path / "intervention.json").write_bytes((pack / "intervention.json").read_bytes())
@@ -732,6 +861,9 @@ def interactive_agent_mcp_setup(approved: bool) -> bool:
732
861
 
733
862
  保留 GitHub 版全部行为(--approve-agent-mcp 旗标、agent_mcp_approval.json、
734
863
  safe_spawn 门);此处只补 run 启动时的交互提示层。非交互终端直接返回原值。
864
+ 新增:Agent MCP 可用时,生成角色→CLI→模型推荐表,询问用户是否采用并
865
+ 固化到全局 ~/.eduevidence/agent_mcp_approval.json(含 hash 防篡改);
866
+ 确认后返回 True,调用方可把该记录带入本次 run 的批准工件。
735
867
  """
736
868
  if approved:
737
869
  return True
@@ -779,13 +911,78 @@ def interactive_agent_mcp_setup(approved: bool) -> bool:
779
911
  return False
780
912
 
781
913
  answer = input("是否启用 Agent MCP 增强模式(推荐)?[Y/n] ").strip().lower()
782
- return answer not in ("n", "no")
914
+ if answer in ("n", "no"):
915
+ return False
916
+
917
+ # Agent MCP 可用:生成推荐表并询问是否固化到全局(简短的 8 角色表)。
918
+ approved_now, _ = _confirm_global_approval()
919
+ return approved_now
920
+
921
+
922
+ def _global_approval_path() -> Path:
923
+ """全局用户批准文件:EDUEVIDENCE_HOME 优先,缺省 ~/.eduevidence。"""
924
+ home = Path(os.environ.get("EDUEVIDENCE_HOME", "~/.eduevidence")).expanduser()
925
+ return home / "agent_mcp_approval.json"
926
+
927
+
928
+ def _confirm_global_approval() -> tuple[bool, dict | None]:
929
+ """构建角色推荐表 → 展示 → 询问 → 固化;返回 (approved, approval_record)。
930
+
931
+ - available CLIs 只扫描本机真实存在的(omp/codex/claude/grok/opencode),
932
+ 不猜模型;无任何可用 CLI 时回退为布尔批准。
933
+ - 任何展示内容都只来自已验证模型清单;推荐行缺少 cli/model 的角色
934
+ 不进映射(safe_spawn 会对该角色保持关闭)。
935
+ - 用户确认后写 ~/.eduevidence/agent_mcp_approval.json(含 role_mapping_hash),
936
+ 后续 run 加载并校验:映射变更即失效,需重新确认。
937
+ """
938
+ import shutil as _shutil
939
+ from integrations.agent_mcp import (build_recommendation_table,
940
+ scan_available_models, write_approval)
941
+
942
+ allowed_clis = [c for c in ("omp", "codex", "claude", "grok", "opencode")
943
+ if _shutil.which(c)]
944
+ if not allowed_clis:
945
+ return True, None
946
+ inventory = scan_available_models(allowed_clis, timeout=20)
947
+ table = build_recommendation_table(allowed_clis, inventory)
948
+ rows = [r for r in table["recommendations"] if r.get("cli") and r.get("model")]
949
+ if not rows:
950
+ print("[startup] 未能从已安装 CLI 解析到已验证模型;按布尔批准启用。")
951
+ return True, None
952
+
953
+ print("[startup] 角色 → CLI / 模型 推荐表(仅基于本机扫描到的可用模型,无固定推荐):")
954
+ for r in rows:
955
+ print(f" {r['role']:<20} → {r['cli']} / {r['model']}")
956
+ summary = table.get("summary", {})
957
+ print(f" cross_model_review(反证异族复核): {summary.get('cross_model_review', 'unknown')}"
958
+ f" | 角色数: {summary.get('role_count', len(rows))}")
959
+ answer = input("采用推荐表并固化到全局批准文件?[Y/n] ").strip().lower()
960
+ if answer in ("n", "no"):
961
+ print("[startup] 未固化;本次以平台原生模式运行(可用 --approve-agent-mcp 跳过询问)。")
962
+ return False, None
963
+ roles = {r["role"]: {"cli": r["cli"], "model": r["model"]} for r in rows}
964
+ path = _global_approval_path()
965
+ path.parent.mkdir(parents=True, exist_ok=True)
966
+ record = write_approval(path, roles, sorted(allowed_clis))
967
+ print(f"[startup] 已固化 → {path}")
968
+ return True, record
969
+
970
+
971
+ def load_global_approval() -> dict | None:
972
+ """加载全局批准文件(missing/corrupt -> None;有效期交由
973
+ integrations.agent_mcp.is_approval_current 判定)。"""
974
+ from integrations.agent_mcp import load_approval
975
+ return load_approval(_global_approval_path())
783
976
 
784
977
 
785
978
  def _cmd_run(args: argparse.Namespace) -> int:
786
979
  approve = args.approve_agent_mcp or interactive_agent_mcp_setup(args.approve_agent_mcp)
980
+ approval_record = None
981
+ if approve:
982
+ approval_record = load_global_approval()
787
983
  ws = init_run(Path(args.runs_dir), args.question, depth=args.depth, run_id=args.run_id,
788
- approve_agent_mcp=approve)
984
+ approve_agent_mcp=approve, approval_record=approval_record,
985
+ domain=getattr(args, "domain", "education"))
789
986
  print(f"workspace created: {ws.path}")
790
987
  print(f"manifest: {json.dumps(ws.load_manifest(), ensure_ascii=False, indent=2)}")
791
988
  if args.dry_run:
@@ -984,9 +1181,15 @@ def _cmd_synthesize(args) -> int:
984
1181
  def _cmd_benchmark(args) -> int:
985
1182
  import benchmark_v3 as bv3
986
1183
  if args.action == "run":
987
- return bv3.main(["run", "--baselines", args.baselines, "--questions", args.questions,
988
- "--repeats", str(args.repeats), "--driver", args.driver,
989
- "--out", args.out, "--budget-tokens", str(args.budget)])
1184
+ argv = ["run", "--baselines", args.baselines, "--questions", args.questions,
1185
+ "--repeats", str(args.repeats), "--out", args.out,
1186
+ "--budget-tokens", str(args.budget), "--model", args.model,
1187
+ "--thinking", args.thinking]
1188
+ if getattr(args, "ids", None):
1189
+ argv.extend(["--ids", args.ids])
1190
+ if args.driver:
1191
+ argv.extend(["--driver", args.driver])
1192
+ return bv3.main(argv)
990
1193
  if args.action == "eval":
991
1194
  return bv3.main(["eval", "--run", args.run, "--annotations", args.annotations])
992
1195
  if args.action == "report":
@@ -1172,6 +1375,17 @@ def _cmd_report(args) -> int:
1172
1375
  return 0
1173
1376
 
1174
1377
 
1378
+ def _cmd_export(args) -> int:
1379
+ from engine.judge_pack import export_judge_pack
1380
+ from engine.project import ProjectWorkspace
1381
+ project = ProjectWorkspace.open(_home(args), args.project)
1382
+ output = Path(args.out) if args.out else project.path / "exports" / "judge-pack"
1383
+ manifest = export_judge_pack(project, output)
1384
+ print(json.dumps({"output": str(output), "files": len(manifest["copied_files"]),
1385
+ "missing_categories": manifest["missing_categories"]}, ensure_ascii=False))
1386
+ return 0
1387
+
1388
+
1175
1389
  def _cmd_migrate(args) -> int:
1176
1390
  from engine.migration import migrate_v1_pack
1177
1391
  result = migrate_v1_pack(args.pack, home=_home(args), title=args.title)
@@ -1194,6 +1408,17 @@ def _cmd_search(args) -> int:
1194
1408
  return 0
1195
1409
 
1196
1410
 
1411
+ def _cmd_search_plan(args) -> int:
1412
+ from search_provenance import main as search_plan_main
1413
+ argv = [args.query, "--out", str(args.out), "--domain", args.domain,
1414
+ "--limit", str(args.limit), "--channel", args.channel, "--policy", args.policy]
1415
+ for concept in args.concept:
1416
+ argv.extend(["--concept", concept])
1417
+ for synonym in args.synonym:
1418
+ argv.extend(["--synonym", synonym])
1419
+ return search_plan_main(argv)
1420
+
1421
+
1197
1422
  def _cmd_did(args) -> int:
1198
1423
  from did_regression import run_did_analysis
1199
1424
  res = run_did_analysis(str(args.csv))
@@ -1226,7 +1451,10 @@ def main(argv: list[str] | None = None) -> int:
1226
1451
  sub = parser.add_subparsers(dest="command", required=True)
1227
1452
 
1228
1453
  p_run = sub.add_parser("run", help="create a run workspace and advance stages")
1229
- p_run.add_argument("--question", required=True, help="education question to research")
1454
+ p_run.add_argument("--question", required=True, help="research question to investigate")
1455
+ p_run.add_argument("--domain", default="education", metavar="ID",
1456
+ help="registered research domain (default: education; "
1457
+ "see `domain list`)")
1230
1458
  p_run.add_argument("--depth", default="M", choices=["quick", "standard", "deep", "S", "M", "L"],
1231
1459
  help="complexity depth (default: standard/M)")
1232
1460
  p_run.add_argument("--run-id", default=None, help="explicit run id (default: timestamp)")
@@ -1329,6 +1557,13 @@ def main(argv: list[str] | None = None) -> int:
1329
1557
  p_report.add_argument("--home", default=None)
1330
1558
  p_report.set_defaults(func=_cmd_report)
1331
1559
 
1560
+ p_export = sub.add_parser("export", help="export a project evidence pack")
1561
+ p_export.add_argument("kind", choices=["judge-pack"])
1562
+ p_export.add_argument("project", help="project id")
1563
+ p_export.add_argument("--home", default=None)
1564
+ p_export.add_argument("--out", default=None)
1565
+ p_export.set_defaults(func=_cmd_export)
1566
+
1332
1567
 
1333
1568
  p_pilot = sub.add_parser("pilot", help="V3 Decision-to-Outcome Loop")
1334
1569
  p_pilot.add_argument("action", choices=["register", "import", "analyze-link", "redecide"])
@@ -1363,11 +1598,15 @@ def main(argv: list[str] | None = None) -> int:
1363
1598
  p_bench.add_argument("action", choices=["run", "eval", "report"])
1364
1599
  p_bench.add_argument("--baselines", default="B2_standard_agent,B3_eduevidence_single")
1365
1600
  p_bench.add_argument("--questions", default="benchmarks/questions.jsonl")
1601
+ p_bench.add_argument("--ids", default=None, help="comma-separated question ids to run")
1366
1602
  p_bench.add_argument("--repeats", type=int, default=3)
1367
1603
  p_bench.add_argument("--driver", default=None, choices=["api", "cli", "sim"],
1368
1604
  help="api | cli (omp) | sim (harness validation only); default: auto (api > cli > sim)")
1369
1605
  p_bench.add_argument("--out", default="benchmarks/empirical/run-001")
1370
1606
  p_bench.add_argument("--budget", type=int, default=1000000)
1607
+ p_bench.add_argument("--model", default="",
1608
+ help="model for --driver cli (required; no unconfirmed default)")
1609
+ p_bench.add_argument("--thinking", default="max", choices=["low", "high", "max"])
1371
1610
  p_bench.add_argument("--run", default=None, help="run dir (eval/report)")
1372
1611
  p_bench.add_argument("--annotations", default="benchmarks/annotations")
1373
1612
  p_bench.add_argument("--report", default="benchmarks/empirical/v3-report.md")
@@ -1414,6 +1653,17 @@ def main(argv: list[str] | None = None) -> int:
1414
1653
  p_srch.add_argument("--academic", action="store_true", help="academic only")
1415
1654
  p_srch.set_defaults(func=_cmd_search)
1416
1655
 
1656
+ p_sp = sub.add_parser("search-plan", help="audited, bounded search with provenance export")
1657
+ p_sp.add_argument("query", help="research question")
1658
+ p_sp.add_argument("--out", required=True, type=Path)
1659
+ p_sp.add_argument("--domain", default="education", choices=["education", "policy"])
1660
+ p_sp.add_argument("--concept", action="append", default=[])
1661
+ p_sp.add_argument("--synonym", action="append", default=[])
1662
+ p_sp.add_argument("--limit", type=int, default=10)
1663
+ p_sp.add_argument("--channel", default="all", choices=["all", "academic", "web"])
1664
+ p_sp.add_argument("--policy", default="2026.09")
1665
+ p_sp.set_defaults(func=_cmd_search_plan)
1666
+
1417
1667
  p_did = sub.add_parser("did", help="run DID regression on classroom CSV")
1418
1668
  p_did.add_argument("csv", type=Path, help="CSV file path")
1419
1669
  p_did.set_defaults(func=_cmd_did)