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,72 @@
1
+ """Generate GitHub-safe vector diagrams from the canonical protocol registry."""
2
+ from pathlib import Path
3
+ import sys
4
+ from html import escape
5
+
6
+ ROOT = Path(__file__).resolve().parents[1]
7
+ sys.path.insert(0, str(ROOT))
8
+ from engine.workflows import SCIENTIFIC_STAGE_IDS # noqa: E402
9
+
10
+ OUT = ROOT / 'assets/readme'
11
+ OUT.mkdir(parents=True, exist_ok=True)
12
+
13
+
14
+ def start(title, subtitle, height):
15
+ return [f'''<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="{height}" viewBox="0 0 1200 {height}" role="img" aria-label="{escape(title)}">
16
+ <title>{escape(title)}</title><desc>{escape(subtitle)}</desc>
17
+ <defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0 0L10 5L0 10" fill="none" stroke="#a15c40" stroke-width="1.5"/></marker></defs>
18
+ <rect width="1200" height="{height}" rx="24" fill="#f7f5f0"/>
19
+ <g font-family="-apple-system,BlinkMacSystemFont,Segoe UI,Arial,sans-serif">
20
+ <text x="48" y="43" font-size="12" letter-spacing="3" fill="#9b5e45">EDUEVIDENCE / RESEARCH STUDIO</text>
21
+ <text x="48" y="92" font-size="32" font-weight="600" fill="#272924">{escape(title)}</text>
22
+ <text x="48" y="126" font-size="16" fill="#6f7068">{escape(subtitle)}</text>''']
23
+
24
+
25
+ def card(parts, x, y, w, num, title, detail, accent=False):
26
+ fill = '#eee2d8' if accent else '#ffffff'
27
+ parts.append(f'<rect x="{x}" y="{y}" width="{w}" height="96" rx="14" fill="{fill}" stroke="#dcd8ce"/>')
28
+ parts.append(f'<text x="{x+20}" y="{y+28}" font-size="12" fill="#a15c40">{escape(num)}</text>')
29
+ parts.append(f'<text x="{x+20}" y="{y+54}" font-size="21" font-weight="600" fill="#30332e">{escape(title)}</text>')
30
+ parts.append(f'<text x="{x+20}" y="{y+78}" font-size="13" fill="#6f7068">{escape(detail)}</text>')
31
+
32
+
33
+ def line(parts, d):
34
+ parts.append(f'<path d="{d}" fill="none" stroke="#a15c40" stroke-width="1.7" marker-end="url(#arrow)"/>')
35
+
36
+
37
+ labels = {
38
+ 'frame': ('Frame / 定义问题', 'Question, population, comparison, outcomes'),
39
+ 'retrieve': ('Retrieve / 检索来源', 'Primary sources and retrieval provenance'),
40
+ 'extract': ('Extract / 提取证据', 'Study findings, measures and uncertainty'),
41
+ 'challenge': ('Challenge / 反证质疑', 'Counter-evidence and alternative explanations'),
42
+ 'audit': ('Audit / 方法审计', 'Study quality, bias and evidence limitations'),
43
+ 'adjudicate': ('Adjudicate / 证据裁决', 'Supported claims and bounded decisions'),
44
+ 'applicability': ('Applicability / 适用边界', 'For whom, where and under which conditions'),
45
+ 'intervene': ('Intervene / 设计试点', 'A grounded knowledge gap before a new design'),
46
+ 'evaluate': ('Evaluate / 评估更新', 'Validate new data and revise the decision'),
47
+ }
48
+ p=start('From evidence to a decision you can inspect', 'Nine scientific stages · 三条公开工作流 · Education + organizational policy', 704)
49
+ for i, stage in enumerate(SCIENTIFIC_STAGE_IDS):
50
+ row,col=divmod(i,3)
51
+ if row==1: col=2-col
52
+ x,y=48+col*376,166+row*128
53
+ card(p,x,y,352,f'{i+1:02d}',*labels[stage],stage=='adjudicate')
54
+ if i in (0,1,6,7): line(p,f'M{x+352} {y+48} H{x+370}')
55
+ if i in (3,4): line(p,f'M{x} {y+48} H{x-18}')
56
+ if i in (2,5): line(p,f'M{x+176} {y+96} V{y+121}')
57
+ p.append('<rect x="48" y="568" width="1104" height="88" rx="14" fill="#e9eee7"/>')
58
+ p.append('<text x="70" y="600" font-size="18" font-weight="600" fill="#456450">Projection / 展示制品</text>')
59
+ p.append('<text x="70" y="627" font-size="15" fill="#456450">Read-only Studio · Five report themes · Bilingual HTML · A report is not proof of an executed study.</text>')
60
+ p.append('<text x="48" y="682" font-size="12" fill="#6f7068">Evidence Review 01–07 / Decision &amp; Pilot 01–08 / Evaluate &amp; Update 09</text></g></svg>')
61
+ (OUT/'research-workflow.svg').write_text('\n'.join(p))
62
+ p=start('One research record. Controlled contributions.', 'Roles describe scientific responsibilities. Workers contribute only when the host supports delegation.',600)
63
+ card(p,48,178,290,'01 / LEAD','Plan the work','Bounded tasks and input snapshots')
64
+ card(p,442,178,310,'02 / EXECUTION','Native or delegated','Same scientific protocol and validation gates')
65
+ card(p,854,178,298,'03 / STAGING','Review contributions','Evidence, critique and audit artifacts')
66
+ line(p,'M338 226 H432'); line(p,'M752 226 H844');line(p,'M1004 274 V330 H599 V358')
67
+ card(p,442,368,310,'04 / VALIDATED COMMIT','Single writer','Lead commits a new immutable graph revision',True)
68
+ line(p,'M762 416 H844');card(p,854,368,298,'05 / PROJECTION','Read and trace','Studio, reports and revision history')
69
+ p.append('<text x="48" y="520" font-size="17" fill="#456450">Append evidence. Preserve provenance. Derive the decision from validated facts.</text>')
70
+ p.append('<text x="48" y="550" font-size="14" fill="#6f7068">Native execution needs no worker service. Cross-backend empirical performance is a separate verification task.</text></g></svg>')
71
+ (OUT/'controlled-execution.svg').write_text('\n'.join(p))
72
+ print('Generated research-workflow.svg and controlled-execution.svg from current protocol.')
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env python3
2
+ """Bake all five report identities from the same validated bilingual inputs.
3
+
4
+ This is a build step, never a read endpoint. No evidence or decision is changed.
5
+ Failures are explicit and never replaced with a synthetic success document.
6
+ """
7
+ from __future__ import annotations
8
+ import argparse
9
+ import hashlib
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ ROOT = Path(__file__).resolve().parent.parent
17
+ THEMES = ('claude', 'academic', 'datalab', 'datalab-dark', 'presentation')
18
+
19
+
20
+ def bake(examples: Path, *, force: bool = False) -> list[dict]:
21
+ renderer_dir = ROOT / 'visualization' / 'eduevidence-report'
22
+ renderer = renderer_dir / 'scripts' / 'build_report.py'
23
+ digest = hashlib.sha256()
24
+ for path in sorted(renderer_dir.rglob('*')):
25
+ if path.suffix in {'.py', '.css', '.js', '.json'} and '__pycache__' not in path.parts:
26
+ digest.update(path.relative_to(renderer_dir).as_posix().encode())
27
+ digest.update(path.read_bytes())
28
+ engine_hash = digest.hexdigest()
29
+ reports = []
30
+ for directory in sorted(examples.iterdir()):
31
+ if not directory.is_dir() or directory.is_symlink():
32
+ continue
33
+ source, parallel = directory / 'result.json', directory / 'result.zh.json'
34
+ if not source.exists() or not parallel.exists():
35
+ continue
36
+ result_hash = hashlib.sha256(source.read_bytes()).hexdigest()
37
+ cache_key = hashlib.sha256((engine_hash + result_hash + hashlib.sha256(parallel.read_bytes()).hexdigest()).encode()).hexdigest()
38
+ out_dir = directory / 'reports-5themes'
39
+ manifest = out_dir / 'reader-manifest.json'
40
+ if not force and manifest.is_file():
41
+ try:
42
+ prior = json.loads(manifest.read_text(encoding='utf-8'))
43
+ main = prior.get('main_report') or {}
44
+ main_ok = bool(main.get('file')) and (directory / main['file']).is_file() and hashlib.sha256((directory / main['file']).read_bytes()).hexdigest() == main.get('sha256')
45
+ valid = prior.get('cache_key') == cache_key and main_ok and all(
46
+ (out_dir / record['file']).is_file() and hashlib.sha256((out_dir / record['file']).read_bytes()).hexdigest() == record['sha256']
47
+ for record in prior.get('reports', [])) and len(prior.get('reports', [])) == len(THEMES)
48
+ if valid:
49
+ reports.append(prior)
50
+ continue
51
+ except (ValueError, KeyError, OSError):
52
+ pass
53
+ out_dir.mkdir(parents=True, exist_ok=True)
54
+ records = []
55
+ for theme in THEMES:
56
+ target = out_dir / f'EduEvidence_Report_{theme}.html'
57
+ # Build to temporary files, promote only after scientific gates pass.
58
+ temporary = out_dir / f'.{theme}.pending.html'
59
+ spec = out_dir / f'report_spec_{theme}.json'
60
+ completed = subprocess.run([sys.executable, str(renderer), '--result', str(source),
61
+ '--result-zh', str(parallel), '--theme', theme,
62
+ '--out', str(temporary), '--spec-out', str(spec)],
63
+ cwd=ROOT, capture_output=True, text=True, timeout=120)
64
+ if completed.returncode:
65
+ temporary.unlink(missing_ok=True)
66
+ raise RuntimeError(f'{directory.name}/{theme}: renderer rejected input\n{completed.stdout}\n{completed.stderr}')
67
+ os.replace(temporary, target)
68
+ records.append({'theme': theme, 'file': target.name, 'sha256': hashlib.sha256(target.read_bytes()).hexdigest()})
69
+ # Also refresh the pack-root report. This used to write only the themed
70
+ # variants, so examples/*/EduEvidence_Report.html kept whatever bytes it
71
+ # was first rendered with - the packaged example shipped a report the
72
+ # current renderer would not produce.
73
+ from_default = next((r for r in records if r['theme'] == 'claude'), records[0])
74
+ main_target = directory / 'EduEvidence_Report.html'
75
+ main_temp = directory / '.EduEvidence_Report.pending.html'
76
+ main_temp.write_bytes((out_dir / from_default['file']).read_bytes())
77
+ os.replace(main_temp, main_target)
78
+ main_sha = hashlib.sha256(main_target.read_bytes()).hexdigest()
79
+
80
+ value = {'schema_version': 1, 'project': directory.name, 'cache_key': cache_key,
81
+ 'result_sha256': result_hash, 'renderer_sha256': engine_hash,
82
+ 'main_report': {'file': main_target.name, 'sha256': main_sha,
83
+ 'theme': from_default['theme']},
84
+ 'reports': records}
85
+ manifest.write_text(json.dumps(value, indent=2) + '\n', encoding='utf-8')
86
+ reports.append(value)
87
+ print(f'{directory.name}: {len(records)} verified report variants')
88
+ return reports
89
+
90
+
91
+ def main() -> int:
92
+ parser = argparse.ArgumentParser(description=__doc__)
93
+ parser.add_argument('--examples', type=Path, default=ROOT / 'examples')
94
+ parser.add_argument('--force', action='store_true')
95
+ args = parser.parse_args()
96
+ bake(args.examples, force=args.force)
97
+ return 0
98
+
99
+
100
+ if __name__ == '__main__':
101
+ raise SystemExit(main())
@@ -30,14 +30,14 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
30
30
  from evidence_semantics import effect_direction
31
31
  from engine.versions import ENGINE_VERSION
32
32
 
33
- OUTCOME_ORDER = [
34
- "knowledge_gain", "concept_understanding", "retention", "transfer",
35
- "independent_problem_solving", "completion_time", "accuracy",
36
- "code_quality", "assignment_score", "engagement", "motivation",
37
- "cognitive_load", "help_seeking", "metacognition", "ai_dependency",
38
- "over_reliance", "reduced_effort", "reduced_transfer",
39
- "academic_integrity_risk", "false_confidence",
40
- ]
33
+ def _outcome_order() -> list[str]:
34
+ """Registered outcome tokens in registry order (was a hard-coded list)."""
35
+ from engine.taxonomy import all_tokens_ordered
36
+
37
+ return list(all_tokens_ordered())
38
+
39
+
40
+ OUTCOME_ORDER = _outcome_order()
41
41
 
42
42
 
43
43
  def _load_json(path: Path) -> dict[str, Any] | None:
@@ -221,12 +221,74 @@ def build_claims(evidence: list[dict[str, Any]]) -> list[dict[str, Any]]:
221
221
  return list(claims.values())
222
222
 
223
223
 
224
+ def _applicability(pack_dir: Path, verdict: dict) -> dict:
225
+ """Stage-7 applicability assessment, falling back to the verdict boundary.
226
+
227
+ applicability.json is the dedicated deliverable of the Applicability stage.
228
+ It used to be written and then ignored, because the renderer only looked at
229
+ the verdict; this is where it re-enters the result.
230
+ """
231
+ import json as _json
232
+
233
+ path = pack_dir / "applicability.json"
234
+ if path.is_file():
235
+ try:
236
+ data = _json.loads(path.read_text(encoding="utf-8"))
237
+ except (OSError, _json.JSONDecodeError):
238
+ data = None
239
+ if isinstance(data, dict) and data and data.get("status") != "NOT_CAPTURED":
240
+ return data
241
+ value = verdict.get("applicability") if isinstance(verdict, dict) else None
242
+ return value if isinstance(value, dict) else {}
243
+
244
+ def _derive_study_audits(methodology: list[dict], evidence: list[dict]) -> list[dict]:
245
+ """Per-study audit rows derived from the audits and evidence present.
246
+
247
+ Each row names the study and reports the audit verdict that covers it,
248
+ so the per-study axis the schema advertises actually exists downstream.
249
+ Rows are only emitted for studies the audits or evidence actually name.
250
+ """
251
+ by_study: dict[str, dict] = {}
252
+ for audit in methodology:
253
+ if not isinstance(audit, dict):
254
+ continue
255
+ target = audit.get("target") or "overall"
256
+ if target == "overall":
257
+ # The aggregate audit is not a study row; label it as the
258
+ # body-of-evidence review so it cannot be mistaken for one.
259
+ target = "body_of_evidence"
260
+ entry = by_study.setdefault(target, {
261
+ "study_id": target,
262
+ "verdict": audit.get("verdict"),
263
+ "audit_items": audit.get("audit_items") or {},
264
+ "limitations": list(audit.get("limitations") or []),
265
+ "task_vs_learning_guard": audit.get("task_vs_learning_guard"),
266
+ })
267
+ entry.setdefault("evidence_ids", [])
268
+ known = {e.get("study_id") for e in evidence if e.get("study_id")}
269
+ for study_id in sorted(known):
270
+ by_study.setdefault(study_id, {
271
+ "study_id": study_id,
272
+ "verdict": None,
273
+ "audit_items": {},
274
+ "limitations": [],
275
+ "task_vs_learning_guard": None,
276
+ "evidence_ids": [e.get("evidence_id") for e in evidence
277
+ if e.get("study_id") == study_id],
278
+ })
279
+ return list(by_study.values())
280
+
281
+
224
282
  def build_result(pack_dir: Path, *, mode: str = "platform_native") -> dict[str, Any]:
225
283
  frame = _load_json(pack_dir / "frame.json") or {}
226
284
  evidence = _load_jsonl(pack_dir / "evidence.jsonl")
227
285
  # methodology.json is a single MethodologyAudit object (or a JSONL list)
228
286
  methodology_single = _load_json(pack_dir / "methodology.json")
229
287
  methodology = [methodology_single] if methodology_single else _load_jsonl(pack_dir / "methodology.jsonl")
288
+ # Per-study audits: the contract advertised them but nothing produced
289
+ # them, so a multi-study review silently shipped a single audit object.
290
+ # Derive the per-study axis from the audits actually present.
291
+ study_audits = _derive_study_audits(methodology, evidence)
230
292
  verdict = _load_json(pack_dir / "verdict.json") or {}
231
293
  intervention = _load_json(pack_dir / "intervention.json") or {}
232
294
  evaluation = _load_json(pack_dir / "evaluation.json") or {}
@@ -268,9 +330,12 @@ def build_result(pack_dir: Path, *, mode: str = "platform_native") -> dict[str,
268
330
  "sources": sources,
269
331
  "evidence": evidence,
270
332
  "methodology_reviews": methodology,
333
+ "study_audits": study_audits,
271
334
  "conflicts": [{"reason_for_disagreement": verdict.get("reason_for_disagreement", "")}]
272
335
  if verdict.get("reason_for_disagreement") else [],
273
- "applicability": verdict.get("applicability", {}),
336
+ # Prefer the dedicated stage-7 assessment; fall back to the verdict-embedded
337
+ # boundary when the run carries no separate applicability.json.
338
+ "applicability": _applicability(pack_dir, verdict),
274
339
  "intervention": intervention,
275
340
  "evaluation": evaluation,
276
341
  "benchmark": {},
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+ import json
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ ROOT = Path(__file__).resolve().parent.parent
9
+
10
+
11
+ def fail(message: str) -> None:
12
+ print(f"ERROR: {message}", file=sys.stderr)
13
+ raise SystemExit(1)
14
+
15
+
16
+ def main() -> int:
17
+ invariants = ROOT / "references" / "scientific-invariants.md"
18
+ if not invariants.is_file():
19
+ fail("missing scientific invariants")
20
+ text = invariants.read_text(encoding="utf-8").lower()
21
+ for phrase in (
22
+ "optimize the research process, never the conclusion.",
23
+ "single writer",
24
+ "append-only",
25
+ ):
26
+ if phrase not in text:
27
+ fail(f"missing invariant: {phrase}")
28
+
29
+ registry = ROOT / "skill" / "roles" / "registry.yaml"
30
+ if not registry.is_file():
31
+ fail("missing role registry")
32
+ registry_text = registry.read_text(encoding="utf-8")
33
+ for role in ("evidence-retriever", "skeptic", "method-reviewer", "evidence-judge"):
34
+ if role not in registry_text:
35
+ fail(f"role missing: {role}")
36
+
37
+ from engine.orchestration import CanonicalWriteGuard, ExecutionPlanner
38
+ for level, cap in (("S", 0), ("M", 3), ("L", 6)):
39
+ plan = ExecutionPlanner().plan(level)
40
+ if level == "S" and plan.delegated_tasks:
41
+ fail("S must delegate zero tasks")
42
+ if len(plan.delegated_tasks) > cap:
43
+ fail(f"{level} delegated worker plan exceeds policy cap")
44
+ if max((len(group) for group in plan.parallel_groups), default=0) > plan.max_parallel_workers:
45
+ fail(f"{level} execution group exceeds max_parallel_workers")
46
+ try:
47
+ CanonicalWriteGuard().require("worker", "GraphRevision")
48
+ fail("single writer guard did not block worker")
49
+ except PermissionError:
50
+ pass
51
+
52
+ schema_dir = ROOT / "schemas" / "vNext"
53
+ required = {
54
+ "research-iteration.schema.json",
55
+ "research-strategy.schema.json",
56
+ "negative-search-record.schema.json",
57
+ "gap-priority.schema.json",
58
+ "task-spec.schema.json",
59
+ "worker-result.schema.json",
60
+ "execution-plan.schema.json",
61
+ "skill-experiment.schema.json",
62
+ "eval-snapshot.schema.json",
63
+ "autoevolve-session.schema.json",
64
+ }
65
+ missing = [name for name in required if not (schema_dir / name).is_file()]
66
+ if missing:
67
+ fail("missing schemas: " + ",".join(sorted(missing)))
68
+ for name in required:
69
+ json.loads((schema_dir / name).read_text(encoding="utf-8"))
70
+
71
+ head = os.getenv("GITHUB_HEAD_REF", "") or os.getenv("GITHUB_REF_NAME", "")
72
+ if head.startswith("autoresearch/"):
73
+ base = os.getenv("GITHUB_BASE_REF", "main")
74
+ subprocess.run(
75
+ ["git", "fetch", "origin", base, "--depth=1"],
76
+ cwd=ROOT,
77
+ check=True,
78
+ stdout=subprocess.DEVNULL,
79
+ )
80
+ changed = subprocess.check_output(
81
+ ["git", "diff", "--name-only", f"origin/{base}...HEAD"],
82
+ cwd=ROOT,
83
+ text=True,
84
+ ).splitlines()
85
+ from engine.autoevolve import ProtectedManifest
86
+ ok, bad = ProtectedManifest.from_repo(ROOT).validate_changes(changed)
87
+ if not ok:
88
+ fail("protected mutation on autoresearch branch: " + ",".join(bad))
89
+
90
+ print("autoresearch invariants OK")
91
+ return 0
92
+
93
+
94
+ if __name__ == "__main__":
95
+ raise SystemExit(main())
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+ """check_package_parity.py - prove the shipped package matches the source tree.
3
+
4
+ The upload package is a projection of this repository. If a source file changed
5
+ and the package still carries the old bytes, reviewers receive a different
6
+ product from the one under source control. CI previously compared only
7
+ SKILL.md, so a renamed role file went unnoticed for a whole change set.
8
+
9
+ Compares every file the shared payload allowlist ships: content must match and
10
+ nothing may be missing. Stdlib only; exit 1 on any drift.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ ROOT = Path(__file__).resolve().parent.parent
19
+ sys.path.insert(0, str(ROOT))
20
+ sys.path.insert(0, str(ROOT / "scripts"))
21
+
22
+ PACKAGE = ROOT / "dist" / "eduevidence-submission"
23
+
24
+
25
+ def digest(path: Path) -> str:
26
+ return hashlib.sha256(path.read_bytes()).hexdigest()
27
+
28
+
29
+ def main() -> int:
30
+ if not PACKAGE.is_dir():
31
+ print(f"ERROR: no package at {PACKAGE}; run bash packaging/make_upload.sh")
32
+ return 1
33
+
34
+ from skill_payload import payload_files
35
+
36
+ expected = set(payload_files(ROOT))
37
+ # The manifest and the packaging notes are written by the build, not copied.
38
+ generated = {"submission-manifest.json", "UPLOAD-README.md", "START-HERE.md",
39
+ "scp-manifest.json", "upload-layout.md", "README.zh-CN.md"}
40
+ expected |= {name for name in generated if (PACKAGE / name).is_file()}
41
+
42
+ missing = sorted(rel for rel in expected if not (PACKAGE / rel).is_file())
43
+
44
+ # Reverse direction: a file the package carries but the source does not is
45
+ # stale output from an earlier build. A one-way check cannot see that,
46
+ # which is how pre-rename report copies once survived inside a package.
47
+ unexpected = []
48
+ for path in sorted(PACKAGE.rglob("*")):
49
+ if not path.is_file():
50
+ continue
51
+ rel = path.relative_to(PACKAGE).as_posix()
52
+ if rel in expected or rel == "submission-manifest.json":
53
+ continue
54
+ if any(part in {"__pycache__", ".git"} for part in path.parts):
55
+ continue
56
+ if path.suffix in {".pyc", ".pyo"} or path.name == ".DS_Store":
57
+ continue
58
+ if not (ROOT / rel).exists():
59
+ unexpected.append(rel)
60
+ differing = []
61
+ for rel in sorted(expected):
62
+ source = ROOT / rel
63
+ shipped = PACKAGE / rel
64
+ if not source.is_file() or not shipped.is_file():
65
+ continue
66
+ if digest(source) != digest(shipped):
67
+ differing.append(rel)
68
+
69
+ if missing or differing or unexpected:
70
+ print("ERROR: package does not match the source tree", file=sys.stderr)
71
+ for rel in missing[:20]:
72
+ print(f" missing from package: {rel}", file=sys.stderr)
73
+ for rel in differing[:20]:
74
+ print(f" differs from source: {rel}", file=sys.stderr)
75
+ for rel in unexpected[:20]:
76
+ print(f" stale in package: {rel}", file=sys.stderr)
77
+ print(" fix: bash packaging/make_upload.sh", file=sys.stderr)
78
+ return 1
79
+
80
+ print(f"package parity OK ({len(expected)} files byte-identical to source)")
81
+ return 0
82
+
83
+
84
+ if __name__ == "__main__":
85
+ sys.exit(main())