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
@@ -0,0 +1,475 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import shlex
7
+ import subprocess
8
+ from contextlib import contextmanager
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+
14
+ def _read_json(path: str | Path | None, default=None):
15
+ if not path:
16
+ return default
17
+ return json.loads(Path(path).read_text(encoding="utf-8"))
18
+
19
+
20
+ def _write_json(path: Path, value: dict[str, Any]) -> None:
21
+ path.parent.mkdir(parents=True, exist_ok=True)
22
+ tmp = path.with_name(path.name + ".tmp")
23
+ tmp.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
24
+ os.replace(tmp, path)
25
+
26
+
27
+ def _home(value):
28
+ if value:
29
+ return Path(value).expanduser().resolve()
30
+ from engine.paths import resolve_home
31
+ return resolve_home()
32
+
33
+
34
+ def _project(home, project_id):
35
+ from engine.project import ProjectWorkspace
36
+ return ProjectWorkspace.open(home, project_id)
37
+
38
+
39
+ def _load_gaps(workspace, explicit=None):
40
+ if explicit:
41
+ path = Path(explicit)
42
+ rows = [
43
+ json.loads(line)
44
+ for line in path.read_text(encoding="utf-8").splitlines()
45
+ if line.strip()
46
+ ]
47
+ else:
48
+ gap_dir = workspace.path / "gaps"
49
+ files = sorted(gap_dir.glob("gaps-rev-*.jsonl")) if gap_dir.exists() else []
50
+ if not files:
51
+ raise ValueError("no persisted KnowledgeGap file; derive gaps first or pass --gaps")
52
+ rows = [
53
+ json.loads(line)
54
+ for line in files[-1].read_text(encoding="utf-8").splitlines()
55
+ if line.strip()
56
+ ]
57
+ return rows
58
+
59
+
60
+ def _apply_gap_state(gaps: list[dict[str, Any]], state: dict[str, str]) -> None:
61
+ for gap in gaps:
62
+ gap_id = str(gap.get("gap_id", ""))
63
+ if gap_id in state:
64
+ gap["status"] = state[gap_id]
65
+
66
+
67
+ def _save_gap_state(path: Path, gaps: list[dict[str, Any]]) -> None:
68
+ _write_json(
69
+ path,
70
+ {
71
+ str(gap.get("gap_id")): str(gap.get("status", "open"))
72
+ for gap in gaps
73
+ if gap.get("gap_id")
74
+ },
75
+ )
76
+
77
+
78
+ def _require_fresh_research_state(
79
+ *,
80
+ active_revision: int,
81
+ gaps: list[dict[str, Any]],
82
+ decision: dict[str, Any],
83
+ previous_state: dict[str, Any],
84
+ ) -> None:
85
+ if previous_state.get("next_action") == "re_adjudicate":
86
+ if int(decision.get("graph_revision", -1)) != active_revision:
87
+ raise ValueError(
88
+ "re-adjudication barrier: provide a DecisionSnapshot bound to the current GraphRevision"
89
+ )
90
+ for gap in gaps:
91
+ derived = gap.get("derived_from_graph_revision")
92
+ if derived is not None and int(derived) != active_revision:
93
+ raise ValueError(
94
+ f"stale KnowledgeGap {gap.get('gap_id')}: derived from revision {derived}, "
95
+ f"active revision is {active_revision}; re-derive gaps before continuing"
96
+ )
97
+
98
+
99
+ def _iteration_id(iteration_number: int) -> str:
100
+ return f"RIT-{iteration_number:04d}"
101
+
102
+
103
+ def _request(priority, strategy, project_id, revision, iteration_number, gap):
104
+ iteration_id = _iteration_id(iteration_number)
105
+ return {
106
+ "project_id": project_id,
107
+ "iteration_id": iteration_id,
108
+ "iteration_number": iteration_number,
109
+ "base_graph_revision": revision,
110
+ "gap_id": priority.gap_id,
111
+ "gap": gap,
112
+ "gap_priority": priority.as_dict(),
113
+ "strategy": {
114
+ "strategy_id": strategy.strategy_id,
115
+ "experiment_type": strategy.experiment_type.value,
116
+ "hypothesis": strategy.hypothesis,
117
+ "expected_gain": strategy.expected_gain,
118
+ "budget": strategy.budget.__dict__,
119
+ },
120
+ "contract": {
121
+ "canonical_state_write": "FORBIDDEN",
122
+ "output": "validated staging result JSON only",
123
+ "required_measurements": ["query_count", "candidate_count", "fetched_count"],
124
+ "negative_search_research_iteration_id": iteration_id,
125
+ "negative_search_gap_id": priority.gap_id,
126
+ "search_snippets_are_evidence": False,
127
+ },
128
+ }
129
+
130
+
131
+ def _validate_outcome_budget(outcome: dict[str, Any], strategy) -> None:
132
+ if not isinstance(outcome, dict):
133
+ raise ValueError("executor output must be a JSON object")
134
+ budget = strategy.budget
135
+ checks = (
136
+ ("query_count", budget.max_queries),
137
+ ("candidate_count", budget.max_candidates),
138
+ ("fetched_count", budget.max_fulltext_fetches),
139
+ )
140
+ measured: dict[str, int] = {}
141
+ for key, limit in checks:
142
+ if key not in outcome:
143
+ raise ValueError(f"executor output must report {key} to enforce bounded research")
144
+ try:
145
+ value = int(outcome[key])
146
+ except (TypeError, ValueError) as exc:
147
+ raise ValueError(f"executor measurement {key} must be an integer") from exc
148
+ if value < 0:
149
+ raise ValueError(f"executor measurement {key} cannot be negative")
150
+ if value > limit:
151
+ raise ValueError(f"research budget exceeded: {key}={value} > {limit}")
152
+ measured[key] = value
153
+ if measured["fetched_count"] > measured["candidate_count"]:
154
+ raise ValueError("fetched_count cannot exceed candidate_count")
155
+ candidates = outcome.get("candidate_sources")
156
+ if isinstance(candidates, list) and len(candidates) > measured["candidate_count"]:
157
+ raise ValueError("candidate_sources length exceeds reported candidate_count")
158
+ attempts = outcome.get("search_attempts")
159
+ if isinstance(attempts, list) and len(attempts) > measured["query_count"]:
160
+ raise ValueError("search_attempts length exceeds reported query_count")
161
+
162
+
163
+ def _validate_outcome_identity(
164
+ outcome: dict[str, Any], *, expected_iteration_id: str, expected_gap_id: str
165
+ ) -> None:
166
+ supplied_iteration = outcome.get("iteration_id")
167
+ supplied_gap = outcome.get("gap_id")
168
+ if supplied_iteration is not None and supplied_iteration != expected_iteration_id:
169
+ raise ValueError("executor iteration_id does not match ResearchRequest")
170
+ if supplied_gap is not None and supplied_gap != expected_gap_id:
171
+ raise ValueError("executor gap_id does not match ResearchRequest")
172
+
173
+
174
+ def _external_executor(command, request, request_path, *, timeout_seconds: int):
175
+ _write_json(request_path, request)
176
+ argv = shlex.split(command)
177
+ if not argv:
178
+ raise ValueError("empty executor command")
179
+ env = os.environ.copy()
180
+ env["EDUEVIDENCE_RESEARCH_REQUEST"] = str(request_path)
181
+ completed = subprocess.run(
182
+ argv,
183
+ check=True,
184
+ text=True,
185
+ capture_output=True,
186
+ env=env,
187
+ timeout=timeout_seconds,
188
+ )
189
+ value = json.loads(completed.stdout)
190
+ if not isinstance(value, dict):
191
+ raise ValueError("executor stdout must be one JSON object")
192
+ return value
193
+
194
+
195
+ def _commit_payload(workspace, run_id, payload, *, expected_base_revision: int):
196
+ from engine.autoresearch import commit_staging_bundle
197
+ from engine.graph_store import GraphStore
198
+
199
+ return commit_staging_bundle(
200
+ GraphStore.create(workspace),
201
+ run_id=run_id,
202
+ expected_base_revision=expected_base_revision,
203
+ payload=payload,
204
+ )
205
+
206
+
207
+ @contextmanager
208
+ def _writer_lock(path: Path):
209
+ """One research-auto writer per Project; stale locks fail closed."""
210
+ payload = {
211
+ "pid": os.getpid(),
212
+ "acquired_at": datetime.now(timezone.utc).isoformat(),
213
+ }
214
+ flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
215
+ try:
216
+ fd = os.open(path, flags, 0o600)
217
+ except FileExistsError as exc:
218
+ detail = ""
219
+ try:
220
+ detail = path.read_text(encoding="utf-8").strip()
221
+ except OSError:
222
+ pass
223
+ raise RuntimeError(
224
+ f"research auto writer already active or a stale lock exists at {path}. "
225
+ "Do not delete it while another run is active; after confirming no writer is running, "
226
+ f"remove it manually. lock={detail or 'unreadable'}"
227
+ ) from exc
228
+ try:
229
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
230
+ json.dump(payload, handle)
231
+ handle.write("\n")
232
+ yield
233
+ finally:
234
+ try:
235
+ path.unlink()
236
+ except FileNotFoundError:
237
+ pass
238
+
239
+
240
+ def _mutating_research_auto(args, workspace, root, state_path, gap_state_path, stop_path):
241
+ from engine.autoresearch import EvidenceAutoresearchController, NegativeSearchRecord, ResearchMemory
242
+ from engine.graph_store import GraphStore
243
+
244
+ memory = ResearchMemory(root)
245
+ gaps = _load_gaps(workspace, args.gaps)
246
+ _apply_gap_state(gaps, _read_json(gap_state_path, {}) or {})
247
+ decision = _read_json(args.decision, {}) or {}
248
+ controller = EvidenceAutoresearchController(max_iterations=args.max_iterations)
249
+ store = GraphStore.create(workspace)
250
+ previous_state = _read_json(state_path, {}) or {}
251
+ _require_fresh_research_state(
252
+ active_revision=store.active_revision(),
253
+ gaps=gaps,
254
+ decision=decision,
255
+ previous_state=previous_state,
256
+ )
257
+
258
+ def process(outcome, history, priority, strategy):
259
+ base_revision = GraphStore.create(workspace).active_revision()
260
+ expected_iteration_id = _iteration_id(len(history) + 1)
261
+ _validate_outcome_identity(
262
+ outcome,
263
+ expected_iteration_id=expected_iteration_id,
264
+ expected_gap_id=priority.gap_id,
265
+ )
266
+ _validate_outcome_budget(outcome, strategy)
267
+ evidence_ids = outcome.get("validated_evidence_ids") or [
268
+ item.get("finding_id")
269
+ for item in outcome.get("findings", [])
270
+ if isinstance(item, dict) and item.get("finding_id")
271
+ ]
272
+ outcome["validated_evidence_ids"] = [item for item in evidence_ids if item]
273
+
274
+ def executor(actual_strategy, gap):
275
+ if actual_strategy.strategy_id != strategy.strategy_id:
276
+ raise RuntimeError("controller strategy changed between request and execution")
277
+ return outcome
278
+
279
+ def commit(_ids):
280
+ return _commit_payload(
281
+ workspace,
282
+ f"autoresearch-{len(history) + 1:04d}",
283
+ outcome,
284
+ expected_base_revision=base_revision,
285
+ )
286
+
287
+ result = controller.step(
288
+ project_id=args.project,
289
+ base_graph_revision=base_revision,
290
+ gaps=gaps,
291
+ decision=decision,
292
+ history=history,
293
+ executor=executor,
294
+ graph_commit=commit if outcome["validated_evidence_ids"] else None,
295
+ ethics_feasible=args.ethics_feasible,
296
+ )
297
+ for raw in outcome.get("negative_searches", []):
298
+ record = NegativeSearchRecord(**raw)
299
+ if record.research_iteration_id != result.iteration.iteration_id:
300
+ raise ValueError(
301
+ "NegativeSearchRecord research_iteration_id does not match current iteration"
302
+ )
303
+ if record.gap_id != result.iteration.gap_id:
304
+ raise ValueError("NegativeSearchRecord gap_id does not match current gap")
305
+ memory.append_negative_search(record)
306
+ if record.negative_search_id not in result.iteration.negative_search_ids:
307
+ result.iteration.negative_search_ids.append(record.negative_search_id)
308
+ memory.append_iteration(result.iteration)
309
+ return result
310
+
311
+ def persist_result(result, completed=None):
312
+ selected = next(g for g in gaps if g.get("gap_id") == result.iteration.gap_id)
313
+ if result.next_action == "stop_search_saturated":
314
+ selected["status"] = "search_saturated"
315
+ elif result.next_action == "empirical_evidence_needed":
316
+ selected["status"] = "empirical_needed"
317
+ state = {
318
+ "status": result.iteration.status.value if result.iteration.status else "unknown",
319
+ "next_action": result.next_action,
320
+ "gap_priority": result.priority.as_dict(),
321
+ "iteration": result.iteration.as_dict(),
322
+ "rationale": list(result.rationale),
323
+ }
324
+ if completed is not None:
325
+ state["completed_iterations"] = list(completed)
326
+ _save_gap_state(gap_state_path, gaps)
327
+ _write_json(state_path, state)
328
+ return state
329
+
330
+ if args.action == "step":
331
+ history = memory.load_iterations()
332
+ priority = controller.select_gap(gaps, decision)
333
+ gap = next(g for g in gaps if g.get("gap_id") == priority.gap_id)
334
+ strategy = controller.build_strategy(priority, gap, history)
335
+ outcome = _read_json(args.outcome_file) if args.outcome_file else None
336
+ if outcome is None:
337
+ pending = {
338
+ "status": "awaiting_execution",
339
+ **_request(
340
+ priority,
341
+ strategy,
342
+ args.project,
343
+ GraphStore.create(workspace).active_revision(),
344
+ len(history) + 1,
345
+ gap,
346
+ ),
347
+ }
348
+ _write_json(state_path, pending)
349
+ print(json.dumps(pending, ensure_ascii=False, indent=2))
350
+ return 0
351
+ result = process(outcome, history, priority, strategy)
352
+ state = persist_result(result)
353
+ print(json.dumps(state, ensure_ascii=False, indent=2))
354
+ return 0
355
+
356
+ if args.outcome_file and args.max_iterations != 1:
357
+ raise ValueError(
358
+ "--outcome-file with start requires --max-iterations 1; use --executor-command for a loop"
359
+ )
360
+ if not args.executor_command and not args.outcome_file:
361
+ raise ValueError(
362
+ "start requires --executor-command or one --outcome-file with --max-iterations 1"
363
+ )
364
+
365
+ completed = []
366
+ for _ in range(args.max_iterations):
367
+ if stop_path.exists():
368
+ stop_path.unlink(missing_ok=True)
369
+ latest = _read_json(state_path, {}) or {}
370
+ latest["status"] = "stopped_by_user"
371
+ latest["completed_iterations"] = list(completed)
372
+ _write_json(state_path, latest)
373
+ break
374
+ history = memory.load_iterations()
375
+ priority = controller.select_gap(gaps, decision)
376
+ gap = next(g for g in gaps if g.get("gap_id") == priority.gap_id)
377
+ strategy = controller.build_strategy(priority, gap, history)
378
+ request = _request(
379
+ priority,
380
+ strategy,
381
+ args.project,
382
+ GraphStore.create(workspace).active_revision(),
383
+ len(history) + 1,
384
+ gap,
385
+ )
386
+ outcome = (
387
+ _read_json(args.outcome_file)
388
+ if args.outcome_file
389
+ else _external_executor(
390
+ args.executor_command,
391
+ request,
392
+ root / f"request-{len(history) + 1:04d}.json",
393
+ timeout_seconds=args.executor_timeout_seconds,
394
+ )
395
+ )
396
+ result = process(outcome, history, priority, strategy)
397
+ completed.append(result.iteration.iteration_id)
398
+ persist_result(result, completed)
399
+
400
+ if result.next_action in {
401
+ "re_adjudicate",
402
+ "empirical_evidence_needed",
403
+ "stop_search_saturated",
404
+ }:
405
+ break
406
+ if all(str(row.get("status", "")).lower() == "resolved" for row in gaps):
407
+ break
408
+
409
+ final = _read_json(state_path, {"status": "completed_no_iterations"})
410
+ print(json.dumps(final, ensure_ascii=False, indent=2))
411
+ return 0
412
+
413
+
414
+ def research_auto(argv):
415
+ parser = argparse.ArgumentParser(prog="eduevidence research auto")
416
+ sub = parser.add_subparsers(dest="action", required=True)
417
+ for name in ("step", "start"):
418
+ cmd = sub.add_parser(name)
419
+ cmd.add_argument("--project", required=True)
420
+ cmd.add_argument("--home")
421
+ cmd.add_argument("--gaps")
422
+ cmd.add_argument("--decision")
423
+ cmd.add_argument("--outcome-file")
424
+ cmd.add_argument("--max-iterations", type=int, default=5)
425
+ cmd.add_argument("--ethics-feasible", action="store_true")
426
+ cmd.add_argument("--executor-timeout-seconds", type=int, default=1800)
427
+ if name == "start":
428
+ cmd.add_argument("--executor-command")
429
+ for name in ("status", "report", "stop"):
430
+ cmd = sub.add_parser(name)
431
+ cmd.add_argument("--project", required=True)
432
+ cmd.add_argument("--home")
433
+ args = parser.parse_args(argv)
434
+ if getattr(args, "executor_timeout_seconds", 1) <= 0:
435
+ raise ValueError("executor timeout must be positive")
436
+
437
+ workspace = _project(_home(args.home), args.project)
438
+ root = workspace.path / "autoresearch"
439
+ root.mkdir(parents=True, exist_ok=True)
440
+ state_path = root / "state.json"
441
+ gap_state_path = root / "gap-state.json"
442
+ stop_path = root / "stop.requested"
443
+ lock_path = root / ".writer.lock"
444
+
445
+ if args.action in {"status", "report"}:
446
+ state = _read_json(state_path, {"status": "not_started"})
447
+ if isinstance(state, dict):
448
+ state = dict(state)
449
+ state["stop_requested"] = stop_path.exists()
450
+ state["writer_active"] = lock_path.exists()
451
+ print(json.dumps(state, ensure_ascii=False, indent=2))
452
+ return 0
453
+ if args.action == "stop":
454
+ stop_path.write_text(
455
+ json.dumps(
456
+ {
457
+ "requested_at": datetime.now(timezone.utc).isoformat(),
458
+ "requested_by_pid": os.getpid(),
459
+ }
460
+ )
461
+ + "\n",
462
+ encoding="utf-8",
463
+ )
464
+ print("stop requested")
465
+ return 0
466
+
467
+ with _writer_lock(lock_path):
468
+ return _mutating_research_auto(
469
+ args,
470
+ workspace,
471
+ root,
472
+ state_path,
473
+ gap_state_path,
474
+ stop_path,
475
+ )
@@ -46,14 +46,16 @@ from datetime import datetime, timezone
46
46
  from pathlib import Path
47
47
  from typing import Any
48
48
 
49
- SKILL_VERSION = "1.0.0"
49
+ from engine.versions import CONFIDENCE_POLICY_VERSION, ENGINE_VERSION
50
+ from engine.workflows import execution_stages
51
+
52
+ # Kept as an import-compatible name for existing integrations. Engine version
53
+ # is the sole source of truth for new manifests.
54
+ SKILL_VERSION = ENGINE_VERSION
50
55
  RESOURCE_POLICY_VERSION = "2026-08-12.v1"
51
56
 
52
57
  #: Ordered EvidenceFlow stages the orchestrator routes through.
53
- STAGES = [
54
- "frame", "retrieve", "extract", "challenge", "audit",
55
- "adjudicate", "intervene", "evaluate", "present",
56
- ]
58
+ STAGES = list(execution_stages())
57
59
 
58
60
  #: Every artifact a run workspace owns (dirs end with '/').
59
61
  WORKSPACE_FILES: list[str] = [
@@ -61,7 +63,7 @@ WORKSPACE_FILES: list[str] = [
61
63
  "capability_plan.json", "resource_plan.json", "execution_plan.json",
62
64
  "model_inventory.json", "agent_mcp_approval.json",
63
65
  "frame.json", "sources.jsonl", "fetch/", "evidence.jsonl", "skeptic.json",
64
- "methodology.json", "raw_verdict.json", "final_verdict.json",
66
+ "methodology.json", "raw_verdict.json", "final_verdict.json", "applicability.json",
65
67
  "intervention.json", "evaluation.json", "result.json", "result.zh.json",
66
68
  "report_spec.json", "report.html", "trace.jsonl",
67
69
  ]
@@ -77,6 +79,7 @@ EMPTY_SEED: dict[str, str] = {
77
79
  "evidence.jsonl": "",
78
80
  "skeptic.json": "{}",
79
81
  "methodology.json": "{}",
82
+ "applicability.json": "{}",
80
83
  "raw_verdict.json": "{}",
81
84
  "final_verdict.json": "{}",
82
85
  "intervention.json": "{}",
@@ -176,20 +179,26 @@ def build_manifest(
176
179
  scp_available: bool | None = None,
177
180
  root: Path | None = None,
178
181
  started_at: str | None = None,
182
+ domain: str = "education",
179
183
  ) -> dict[str, Any]:
180
- """Phase 13 run manifest with every contract field."""
184
+ """Phase 13 run manifest with every contract field.
185
+
186
+ ``domain`` selects which registered frame contract this run must satisfy;
187
+ it defaults to education so existing callers and manifests stay valid.
188
+ """
181
189
  return {
182
190
  "run_id": run_id,
183
191
  "skill_version": SKILL_VERSION,
184
192
  "git_commit": git_commit(root),
185
193
  "started_at": started_at or utc_now(),
186
194
  "question": question,
195
+ "domain": domain,
187
196
  "execution_mode": execution_mode,
188
197
  "scp_available": detect_scp() if scp_available is None else scp_available,
189
198
  "agent_mcp_available": agent_mcp_available,
190
199
  "agent_mcp_approved": agent_mcp_approved,
191
200
  "resource_policy_version": RESOURCE_POLICY_VERSION,
192
- "confidence_policy_version": "2026-08-12.v2",
201
+ "confidence_policy_version": CONFIDENCE_POLICY_VERSION,
193
202
  }
194
203
 
195
204
 
@@ -249,7 +258,14 @@ class RunWorkspace:
249
258
 
250
259
  def load_state(self) -> dict[str, Any]:
251
260
  state = load_json(self.state_path)
261
+ # Read old workspaces without treating a presentation record as a
262
+ # scientific stage. New writes only use the canonical projection name.
263
+ legacy = (state.get("stages") or {}).pop("present", None)
264
+ if legacy and "projection" not in state.get("stages", {}):
265
+ state.setdefault("stages", {})["projection"] = legacy
252
266
  state.setdefault("stages", {s: {"status": "pending"} for s in STAGES})
267
+ for stage in STAGES:
268
+ state["stages"].setdefault(stage, {"status": "pending"})
253
269
  return state
254
270
 
255
271
  def save_state(self, updates: dict[str, Any] | None = None) -> dict[str, Any]:
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ """Execute an audited, bounded search plan and persist provenance.
3
+
4
+ For every explicit query (core / expansion / counter_evidence) this records
5
+ each provider attempt, then exports search-provenance.json,
6
+ search-attempts.jsonl, source-screening.csv and exclusion-log.csv into the
7
+ output directory. A search result without this provenance file is a candidate,
8
+ never evidence.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ ROOT = Path(__file__).resolve().parent.parent
18
+ for _p in (str(ROOT), str(ROOT / "scripts")):
19
+ if _p not in sys.path:
20
+ sys.path.insert(0, _p)
21
+
22
+ from retrieval.audit import AuditedSearchExecutor, SearchPlan # noqa: E402
23
+ from retrieval.search import search_router # noqa: E402
24
+
25
+
26
+ CHANNELS = {
27
+ "academic": lambda router: router.zero_config_academic,
28
+ "web": lambda router: router.zero_config_web + router.configured_providers,
29
+ "all": lambda router: (router.zero_config_academic + router.zero_config_web
30
+ + [p for p in router.configured_providers if p.is_available()]),
31
+ }
32
+
33
+
34
+ def main(argv: list[str] | None = None) -> int:
35
+ parser = argparse.ArgumentParser(description="audited external search with provenance")
36
+ parser.add_argument("question", help="research question")
37
+ parser.add_argument("--out", required=True, type=Path, help="provenance output directory")
38
+ parser.add_argument("--domain", default="education", choices=["education", "policy"])
39
+ parser.add_argument("--concept", action="append", default=[], help="concept term (repeatable)")
40
+ parser.add_argument("--synonym", action="append", default=[], help="synonym term (repeatable)")
41
+ parser.add_argument("--limit", type=int, default=10)
42
+ parser.add_argument("--channel", default="all", choices=list(CHANNELS))
43
+ parser.add_argument("--policy", default="2026.09", help="search policy version")
44
+ args = parser.parse_args(argv)
45
+ plan = SearchPlan.from_question(args.question, domain=args.domain,
46
+ concepts=args.concept, synonyms=args.synonym)
47
+ plan = SearchPlan(plan.question, plan.domain, plan.concepts, plan.synonyms,
48
+ plan.inclusion_criteria, plan.exclusion_criteria, plan.queries,
49
+ provider_budget=args.limit, policy_version=args.policy)
50
+ providers = CHANNELS[args.channel](search_router)
51
+ hits = AuditedSearchExecutor(providers).execute(plan, args.out, limit=args.limit)
52
+ print(json.dumps({
53
+ "output": str(args.out),
54
+ "sources": len(hits),
55
+ "query_count": len(plan.queries),
56
+ "counter_evidence_queries": sum(q.purpose == "counter_evidence" for q in plan.queries),
57
+ "provenance_files": ["search-provenance.json", "search-attempts.jsonl",
58
+ "source-screening.csv", "exclusion-log.csv"],
59
+ }, ensure_ascii=False, indent=2))
60
+ return 0
61
+
62
+
63
+ if __name__ == "__main__":
64
+ sys.exit(main())
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env python3
2
- """scripts/serve_web.py — EduEvidence 落地页/静态资源预览服务器(CORS 开启)。
2
+ """scripts/serve_web.py — EduEvidence 介绍页/静态资源预览服务器(CORS 开启)。
3
3
 
4
- web/ 的营销落地页(landing.html 等)按架构是独立入口,不挂在
5
- dashboard_server 路由下(P3 gate: /landing.html 必须 404)。本脚本以只读方式
6
- 静态托管 web/,并为所有响应加上 Access-Control-Allow-Origin: *,
7
- 使落地页与不同端口的 Web Studio 控制台之间能做跨源探测与互相跳转。
4
+ `web/landing.html` 是独立的公开介绍页;正式研究控制台由
5
+ `scripts/dashboard_server.py` 提供 `web/studio/` 中的 Research Studio。
6
+ 本脚本只用于只读预览介绍页和静态展示资源,不承载研究状态或控制台 API。
8
7
 
9
8
  python3 scripts/serve_web.py --host 127.0.0.1 --port 8877
10
9
 
11
- 首页入口:http://127.0.0.1:8877/landing.html
12
- 控制台: http://127.0.0.1:8766/(scripts/dashboard_server.py)
10
+ 介绍页:http://127.0.0.1:8877/landing.html
11
+ Research Studio:http://127.0.0.1:8765/studio/
13
12
  """
14
13
  from __future__ import annotations
15
14
 
@@ -40,9 +39,9 @@ def main() -> None:
40
39
  server = http.server.ThreadingHTTPServer(
41
40
  (args.host, args.port), CORSStaticHandler)
42
41
  print("=" * 60)
43
- print(f"🌐 EduEvidence 落地页 (CORS) running at http://{args.host}:{args.port}/")
44
- print(f" 首页 /landing.html")
45
- print(f" 控制台 http://{args.host}:8766/ (scripts/dashboard_server.py)")
42
+ print(f"🌐 EduEvidence 介绍页预览 running at http://{args.host}:{args.port}/")
43
+ print(" 介绍页 /landing.html")
44
+ print(f" Research Studio http://{args.host}:8765/studio/ (scripts/dashboard_server.py)")
46
45
  print("=" * 60)
47
46
  try:
48
47
  server.serve_forever()
@@ -102,7 +102,7 @@ def lint_skill() -> list[str]:
102
102
  # 7. task-briefs 模板(编排链补全)
103
103
  brief_dir = ROOT / "skill" / "task-briefs"
104
104
  stage_briefs = ["frame", "retrieve", "extract", "challenge", "audit",
105
- "adjudicate", "intervene", "evaluate", "present"]
105
+ "adjudicate", "applicability", "intervene", "evaluate", "projection"]
106
106
  if not brief_dir.exists():
107
107
  errors.append("skill/task-briefs/ is missing (orchestration chain)")
108
108
  else: