eduevidence 5.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 (312) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +506 -0
  3. package/README.zh-CN.md +434 -0
  4. package/SKILL.md +195 -0
  5. package/bin/eduevidence.js +127 -0
  6. package/domains/education/manifest.json +183 -0
  7. package/domains/education/outcome_taxonomy.json +127 -0
  8. package/domains/manifest.json +26 -0
  9. package/domains/policy/frame.schema.json +234 -0
  10. package/domains/policy/manifest.json +10 -0
  11. package/domains/policy/methodology_checklist.json +109 -0
  12. package/domains/policy/outcome_taxonomy.json +53 -0
  13. package/domains/policy/references/causal-identification.md +45 -0
  14. package/domains/policy/references/cost-evidence.md +44 -0
  15. package/domains/policy/references/equity.md +42 -0
  16. package/domains/policy/references/evidence-hierarchy.md +41 -0
  17. package/domains/policy/references/implementation-evidence.md +47 -0
  18. package/eduevidence_cli.py +26 -0
  19. package/engine/__init__.py +11 -0
  20. package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
  22. package/engine/__pycache__/bias.cpython-312.pyc +0 -0
  23. package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
  24. package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
  25. package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
  26. package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
  27. package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
  28. package/engine/__pycache__/events.cpython-312.pyc +0 -0
  29. package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
  30. package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
  31. package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
  32. package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
  33. package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
  34. package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
  35. package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
  36. package/engine/__pycache__/ids.cpython-312.pyc +0 -0
  37. package/engine/__pycache__/library.cpython-312.pyc +0 -0
  38. package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
  39. package/engine/__pycache__/living.cpython-312.pyc +0 -0
  40. package/engine/__pycache__/log.cpython-312.pyc +0 -0
  41. package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
  42. package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
  43. package/engine/__pycache__/migration.cpython-312.pyc +0 -0
  44. package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
  45. package/engine/__pycache__/paths.cpython-312.pyc +0 -0
  46. package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
  47. package/engine/__pycache__/planner.cpython-312.pyc +0 -0
  48. package/engine/__pycache__/project.cpython-312.pyc +0 -0
  49. package/engine/__pycache__/projections.cpython-312.pyc +0 -0
  50. package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
  51. package/engine/__pycache__/run.cpython-312.pyc +0 -0
  52. package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
  53. package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
  54. package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
  55. package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
  56. package/engine/__pycache__/update.cpython-312.pyc +0 -0
  57. package/engine/__pycache__/versions.cpython-312.pyc +0 -0
  58. package/engine/analysis.py +308 -0
  59. package/engine/bias.py +178 -0
  60. package/engine/briefs.py +106 -0
  61. package/engine/capabilities.py +99 -0
  62. package/engine/citation_check.py +192 -0
  63. package/engine/contracts.py +117 -0
  64. package/engine/datasets.py +165 -0
  65. package/engine/events.py +67 -0
  66. package/engine/evidence_graph.py +571 -0
  67. package/engine/evidence_review.py +88 -0
  68. package/engine/evidencecore.py +182 -0
  69. package/engine/gap_lens.py +132 -0
  70. package/engine/gaps.py +169 -0
  71. package/engine/graph_store.py +335 -0
  72. package/engine/graph_validate.py +87 -0
  73. package/engine/ids.py +77 -0
  74. package/engine/library.py +268 -0
  75. package/engine/library_builtin.py +301 -0
  76. package/engine/living.py +671 -0
  77. package/engine/log.py +39 -0
  78. package/engine/meta_analysis.py +333 -0
  79. package/engine/meta_synthesis.py +111 -0
  80. package/engine/migration.py +397 -0
  81. package/engine/mode_router.py +72 -0
  82. package/engine/paths.py +15 -0
  83. package/engine/pilot.py +368 -0
  84. package/engine/planner.py +126 -0
  85. package/engine/project.py +118 -0
  86. package/engine/projections.py +240 -0
  87. package/engine/robustness.py +109 -0
  88. package/engine/run.py +85 -0
  89. package/engine/semantics.py +135 -0
  90. package/engine/study_design.py +87 -0
  91. package/engine/synthesis.py +187 -0
  92. package/engine/tribunal.py +408 -0
  93. package/engine/update.py +113 -0
  94. package/engine/versions.py +12 -0
  95. package/install.sh +510 -0
  96. package/integrations/__init__.py +1 -0
  97. package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
  98. package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
  99. package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
  100. package/integrations/agent_mcp.py +856 -0
  101. package/integrations/smart_web_fetch.py +59 -0
  102. package/package.json +50 -0
  103. package/pyproject.toml +55 -0
  104. package/references/applicability-policy.md +88 -0
  105. package/references/education-framing.md +132 -0
  106. package/references/effect_size_formulas.md +35 -0
  107. package/references/evaluation-design.md +111 -0
  108. package/references/evidence-quality.md +79 -0
  109. package/references/grade_framework.md +29 -0
  110. package/references/intervention-design.md +98 -0
  111. package/references/methodology-audit.md +103 -0
  112. package/references/outcome-taxonomy.md +106 -0
  113. package/references/retrieval-protocol.md +142 -0
  114. package/references/skeptic-protocol.md +93 -0
  115. package/references/social_science_pitfalls.md +48 -0
  116. package/references/source-validity.md +140 -0
  117. package/references/tribunal-policy.md +112 -0
  118. package/references/wwc_standards.md +29 -0
  119. package/retrieval/__init__.py +1 -0
  120. package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
  121. package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
  122. package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
  123. package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
  124. package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
  125. package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
  126. package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
  127. package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
  128. package/retrieval/corpus_store.py +181 -0
  129. package/retrieval/dedupe.py +127 -0
  130. package/retrieval/failures.py +90 -0
  131. package/retrieval/fetch.py +435 -0
  132. package/retrieval/search.py +493 -0
  133. package/retrieval/source.py +160 -0
  134. package/retrieval/validate.py +257 -0
  135. package/schemas/agent-mcp-approval.schema.json +57 -0
  136. package/schemas/chart-spec.schema.json +88 -0
  137. package/schemas/cross-model-review.schema.json +28 -0
  138. package/schemas/education-frame.schema.json +106 -0
  139. package/schemas/evaluation.schema.json +35 -0
  140. package/schemas/evidence.schema.json +81 -0
  141. package/schemas/fetch-result.schema.json +119 -0
  142. package/schemas/intervention.schema.json +46 -0
  143. package/schemas/methodology.schema.json +102 -0
  144. package/schemas/report-result.schema.json +381 -0
  145. package/schemas/report-spec.schema.json +130 -0
  146. package/schemas/source.schema.json +311 -0
  147. package/schemas/v2/analysis-plan.schema.json +28 -0
  148. package/schemas/v2/analysis-run.schema.json +33 -0
  149. package/schemas/v2/claim.schema.json +26 -0
  150. package/schemas/v2/dataset-asset.schema.json +40 -0
  151. package/schemas/v2/decision-snapshot.schema.json +53 -0
  152. package/schemas/v2/evidence-link.schema.json +38 -0
  153. package/schemas/v2/finding.schema.json +47 -0
  154. package/schemas/v2/graph-revision.schema.json +30 -0
  155. package/schemas/v2/knowledge-gap.schema.json +40 -0
  156. package/schemas/v2/methodology-audit.schema.json +30 -0
  157. package/schemas/v2/outcome.schema.json +18 -0
  158. package/schemas/v2/project.schema.json +31 -0
  159. package/schemas/v2/research-intent.schema.json +24 -0
  160. package/schemas/v2/run.schema.json +43 -0
  161. package/schemas/v2/source.schema.json +24 -0
  162. package/schemas/v2/study-design.schema.json +67 -0
  163. package/schemas/v2/study.schema.json +37 -0
  164. package/schemas/v3/pilot-outcome.schema.json +132 -0
  165. package/schemas/v3/run-manifest.schema.json +193 -0
  166. package/schemas/v3/synthesis.schema.json +86 -0
  167. package/schemas/v4/drift-report.schema.json +66 -0
  168. package/schemas/v4/evidence-library.schema.json +42 -0
  169. package/schemas/v4/living-subscription.schema.json +55 -0
  170. package/schemas/v4/meta-analysis.schema.json +152 -0
  171. package/schemas/verdict.schema.json +56 -0
  172. package/scripts/__init__.py +0 -0
  173. package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  174. package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
  175. package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
  176. package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
  177. package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
  178. package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
  179. package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
  180. package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
  181. package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
  182. package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
  183. package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
  184. package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
  185. package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
  186. package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
  187. package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
  188. package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
  189. package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
  190. package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
  191. package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
  192. package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
  193. package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
  194. package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
  195. package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
  196. package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
  197. package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
  198. package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
  199. package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
  200. package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
  201. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
  202. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
  203. package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
  204. package/scripts/audit_dois.py +292 -0
  205. package/scripts/bake_pack.sh +37 -0
  206. package/scripts/benchmark.py +183 -0
  207. package/scripts/benchmark_evaluator.py +371 -0
  208. package/scripts/benchmark_judge.py +535 -0
  209. package/scripts/benchmark_routing.py +120 -0
  210. package/scripts/benchmark_v2.py +304 -0
  211. package/scripts/benchmark_v3.py +552 -0
  212. package/scripts/build_esl_artifacts.py +1921 -0
  213. package/scripts/build_evidence_library.py +307 -0
  214. package/scripts/build_killer_demo.py +295 -0
  215. package/scripts/build_result.py +311 -0
  216. package/scripts/check_version_consistency.py +96 -0
  217. package/scripts/citation_check.py +123 -0
  218. package/scripts/claim_audit.py +157 -0
  219. package/scripts/complexity_gate.py +180 -0
  220. package/scripts/compute_confidence.py +176 -0
  221. package/scripts/dashboard_server.py +536 -0
  222. package/scripts/did_regression.py +315 -0
  223. package/scripts/effect_calculator.py +99 -0
  224. package/scripts/enrich_projects_human_and_lieflat.py +315 -0
  225. package/scripts/evidence_matrix.py +129 -0
  226. package/scripts/evidence_score.py +234 -0
  227. package/scripts/evidence_semantics.py +87 -0
  228. package/scripts/fetch_benchmark.py +177 -0
  229. package/scripts/generate_metrics.py +99 -0
  230. package/scripts/generate_new_projects.py +686 -0
  231. package/scripts/generate_promo.py +192 -0
  232. package/scripts/lint_report_layout.py +182 -0
  233. package/scripts/orchestrator.py +1456 -0
  234. package/scripts/pre_verdict_gate.py +513 -0
  235. package/scripts/quickstart.py +121 -0
  236. package/scripts/rebake_all_5themes.py +88 -0
  237. package/scripts/recompute_demo_quality.py +205 -0
  238. package/scripts/render_report.py +270 -0
  239. package/scripts/render_report_html.py +356 -0
  240. package/scripts/retraction_watch.py +110 -0
  241. package/scripts/run_workspace.py +337 -0
  242. package/scripts/serve_web.py +54 -0
  243. package/scripts/skill_lint.py +150 -0
  244. package/scripts/startup_probe.py +265 -0
  245. package/scripts/sync_killer_demo_report.py +270 -0
  246. package/scripts/test_adversarial_empirical.py +541 -0
  247. package/scripts/validate_schema.py +256 -0
  248. package/skill/agents/education-planner.md +80 -0
  249. package/skill/agents/evaluation-designer.md +74 -0
  250. package/skill/agents/evidence-analyst.md +106 -0
  251. package/skill/agents/evidence-judge.md +111 -0
  252. package/skill/agents/evidence-retriever.md +80 -0
  253. package/skill/agents/intervention-designer.md +82 -0
  254. package/skill/agents/method-reviewer.md +104 -0
  255. package/skill/agents/skeptic.md +89 -0
  256. package/skill/sub-skills/aihot-trend-analysis/SKILL.md +31 -0
  257. package/skill/sub-skills/contradiction-analysis/SKILL.md +17 -0
  258. package/skill/sub-skills/data-analysis/SKILL.md +23 -0
  259. package/skill/sub-skills/ethics-review/SKILL.md +25 -0
  260. package/skill/sub-skills/evidence-extraction/SKILL.md +19 -0
  261. package/skill/sub-skills/evidence-review/SKILL.md +18 -0
  262. package/skill/sub-skills/gap-analysis/SKILL.md +25 -0
  263. package/skill/sub-skills/literature-review/SKILL.md +21 -0
  264. package/skill/sub-skills/methodology-audit/SKILL.md +20 -0
  265. package/skill/sub-skills/report-generation/SKILL.md +51 -0
  266. package/skill/sub-skills/research-planning/SKILL.md +21 -0
  267. package/skill/sub-skills/study-design/SKILL.md +16 -0
  268. package/skill/task-briefs/adjudicate.md +17 -0
  269. package/skill/task-briefs/audit.md +15 -0
  270. package/skill/task-briefs/challenge.md +15 -0
  271. package/skill/task-briefs/evaluate.md +13 -0
  272. package/skill/task-briefs/extract.md +16 -0
  273. package/skill/task-briefs/frame.md +17 -0
  274. package/skill/task-briefs/intervene.md +14 -0
  275. package/skill/task-briefs/present.md +16 -0
  276. package/skill/task-briefs/retrieve.md +15 -0
  277. package/visualization/eduevidence-report/assets/base.css +337 -0
  278. package/visualization/eduevidence-report/motion/motion.css +157 -0
  279. package/visualization/eduevidence-report/motion/motion.js +107 -0
  280. package/visualization/eduevidence-report/references/bilingual-style.md +7 -0
  281. package/visualization/eduevidence-report/references/component-catalog.md +145 -0
  282. package/visualization/eduevidence-report/references/evidence-expansion.md +65 -0
  283. package/visualization/eduevidence-report/references/full-report-outline.md +86 -0
  284. package/visualization/eduevidence-report/references/layout-constraints.md +63 -0
  285. package/visualization/eduevidence-report/references/lieflat-composition.md +79 -0
  286. package/visualization/eduevidence-report/references/motion-system.md +31 -0
  287. package/visualization/eduevidence-report/schemas/adapter-envelope.schema.json +22 -0
  288. package/visualization/eduevidence-report/schemas/visual-layout.schema.json +87 -0
  289. package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
  290. package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
  291. package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
  292. package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
  293. package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
  294. package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
  295. package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
  296. package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
  297. package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
  298. package/visualization/eduevidence-report/scripts/adapter_contract.py +72 -0
  299. package/visualization/eduevidence-report/scripts/build_artifact_manifest.py +70 -0
  300. package/visualization/eduevidence-report/scripts/build_charts.py +283 -0
  301. package/visualization/eduevidence-report/scripts/build_figures.py +515 -0
  302. package/visualization/eduevidence-report/scripts/build_infographics.py +268 -0
  303. package/visualization/eduevidence-report/scripts/build_report.py +3211 -0
  304. package/visualization/eduevidence-report/scripts/charts_data.py +617 -0
  305. package/visualization/eduevidence-report/scripts/check_mobile_layout.js +220 -0
  306. package/visualization/eduevidence-report/scripts/lieflat_engine.py +852 -0
  307. package/visualization/eduevidence-report/scripts/zh_labels.py +245 -0
  308. package/visualization/eduevidence-report/themes/academic.css +94 -0
  309. package/visualization/eduevidence-report/themes/claude.css +96 -0
  310. package/visualization/eduevidence-report/themes/datalab-dark.css +147 -0
  311. package/visualization/eduevidence-report/themes/datalab.css +151 -0
  312. package/visualization/eduevidence-report/themes/presentation.css +140 -0
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env python3
2
+ """run_workspace.py — Run Workspace & Run Manifest (Phase 12-13).
3
+
4
+ A run is an isolated directory under ``<runs_dir>/<run_id>/`` holding every
5
+ artifact the EvidenceFlow Protocol touches, so a run can be audited, resumed
6
+ and reproduced without any external state:
7
+
8
+ run_manifest.json run identity + policy versions (Phase 13)
9
+ state.json stage progress machine (Phase 32 resume)
10
+ capability_plan.json required vs available capabilities
11
+ resource_plan.json token budgets / concurrency / timeouts
12
+ execution_plan.json stage list with artifacts + schema gates
13
+ model_inventory.json model routing per role
14
+ agent_mcp_approval.json Mode B availability + user approval
15
+ frame.json Education Research Frame
16
+ sources.jsonl Source Objects (registry, post-dedupe)
17
+ fetch/ fetched content provenance (raw + validation)
18
+ evidence.jsonl Claim-level Evidence Objects
19
+ skeptic.json counter-evidence / null-result / confounder list
20
+ methodology.json Methodology Audit
21
+ raw_verdict.json model verdict (pre-gate)
22
+ final_verdict.json deterministic verdict (post-gate, Phase 15)
23
+ intervention.json Teaching Intervention Plan
24
+ evaluation.json Evaluation Plan
25
+ result.json / result.zh.json bilingual result pack
26
+ report_spec.json report contract consumed by the render layer
27
+ report.html rendered bilingual report
28
+ trace.jsonl append-only run event log
29
+ task-briefs/ per-stage prompts written when a stage waits for
30
+ an external agent
31
+
32
+ Manifest fields (Phase 13): run_id, skill_version, git_commit, started_at,
33
+ question, execution_mode, scp_available, agent_mcp_available,
34
+ agent_mcp_approved, resource_policy_version, confidence_policy_version.
35
+
36
+ Usage (importable; also a small CLI for workspace maintenance):
37
+
38
+ python scripts/run_workspace.py --runs-dir runs create --run-id 20260812-103000
39
+ """
40
+ from __future__ import annotations
41
+
42
+ import json
43
+ import os
44
+ import subprocess
45
+ from datetime import datetime, timezone
46
+ from pathlib import Path
47
+ from typing import Any
48
+
49
+ SKILL_VERSION = "1.0.0"
50
+ RESOURCE_POLICY_VERSION = "2026-08-12.v1"
51
+
52
+ #: Ordered EvidenceFlow stages the orchestrator routes through.
53
+ STAGES = [
54
+ "frame", "retrieve", "extract", "challenge", "audit",
55
+ "adjudicate", "intervene", "evaluate", "present",
56
+ ]
57
+
58
+ #: Every artifact a run workspace owns (dirs end with '/').
59
+ WORKSPACE_FILES: list[str] = [
60
+ "run_manifest.json", "state.json",
61
+ "capability_plan.json", "resource_plan.json", "execution_plan.json",
62
+ "model_inventory.json", "agent_mcp_approval.json",
63
+ "frame.json", "sources.jsonl", "fetch/", "evidence.jsonl", "skeptic.json",
64
+ "methodology.json", "raw_verdict.json", "final_verdict.json",
65
+ "intervention.json", "evaluation.json", "result.json", "result.zh.json",
66
+ "report_spec.json", "report.html", "trace.jsonl",
67
+ ]
68
+
69
+ EMPTY_SEED: dict[str, str] = {
70
+ "capability_plan.json": "{}",
71
+ "resource_plan.json": "{}",
72
+ "execution_plan.json": "{}",
73
+ "model_inventory.json": "{}",
74
+ "agent_mcp_approval.json": "{}",
75
+ "frame.json": "{}",
76
+ "sources.jsonl": "",
77
+ "evidence.jsonl": "",
78
+ "skeptic.json": "{}",
79
+ "methodology.json": "{}",
80
+ "raw_verdict.json": "{}",
81
+ "final_verdict.json": "{}",
82
+ "intervention.json": "{}",
83
+ "evaluation.json": "{}",
84
+ "result.json": "{}",
85
+ "result.zh.json": "{}",
86
+ "report_spec.json": "{}",
87
+ "report.html": "",
88
+ }
89
+
90
+
91
+ def utc_now() -> str:
92
+ """RFC 3339 UTC timestamp for manifest/state/trace."""
93
+ return datetime.now(timezone.utc).isoformat()
94
+
95
+
96
+ def git_commit(root: Path | None = None) -> str:
97
+ """Short HEAD commit of the repository (best-effort, 'unknown' off-repo)."""
98
+ root = root or Path.cwd()
99
+ try:
100
+ out = subprocess.run(
101
+ ["git", "-C", str(root), "rev-parse", "--short", "HEAD"],
102
+ capture_output=True, text=True, timeout=5)
103
+ return out.stdout.strip() or "unknown"
104
+ except Exception:
105
+ return "unknown"
106
+
107
+
108
+ def detect_scp() -> bool:
109
+ """Scholar-Copilot availability marker (env override, default off)."""
110
+ return os.environ.get("EDUEVIDENCE_SCP_AVAILABLE", "").lower() in ("1", "true", "yes")
111
+
112
+
113
+ def next_run_id(runs_dir: Path, *, now: datetime | None = None) -> str:
114
+ """Timestamp run id, uniquified with a -N suffix on collision."""
115
+ now = now or datetime.now(timezone.utc)
116
+ base = now.strftime("%Y%m%d-%H%M%S")
117
+ candidate = base
118
+ suffix = 2
119
+ while (runs_dir / candidate).exists():
120
+ candidate = f"{base}-{suffix}"
121
+ suffix += 1
122
+ return candidate
123
+
124
+
125
+ # ----------------------------------------------------------------- JSON I/O
126
+
127
+
128
+ def load_json(path: Path) -> dict[str, Any]:
129
+ if not path.is_file():
130
+ return {}
131
+ try:
132
+ data = json.loads(path.read_text(encoding="utf-8"))
133
+ return data if isinstance(data, dict) else {}
134
+ except (json.JSONDecodeError, OSError):
135
+ return {}
136
+
137
+
138
+ def save_json(path: Path, data: dict[str, Any]) -> None:
139
+ path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
140
+
141
+
142
+ def load_jsonl(path: Path) -> list[dict[str, Any]]:
143
+ if not path.is_file():
144
+ return []
145
+ records = []
146
+ for line in path.read_text(encoding="utf-8").splitlines():
147
+ line = line.strip()
148
+ if line:
149
+ try:
150
+ records.append(json.loads(line))
151
+ except json.JSONDecodeError:
152
+ continue
153
+ return records
154
+
155
+
156
+ def save_jsonl(path: Path, records: list[dict[str, Any]]) -> None:
157
+ lines = [json.dumps(r, ensure_ascii=False) for r in records]
158
+ path.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
159
+
160
+
161
+ def append_jsonl(path: Path, record: dict[str, Any]) -> None:
162
+ with path.open("a", encoding="utf-8") as fh:
163
+ fh.write(json.dumps(record, ensure_ascii=False) + "\n")
164
+
165
+
166
+ # --------------------------------------------------------------- workspace
167
+
168
+
169
+ def build_manifest(
170
+ run_id: str,
171
+ question: str,
172
+ *,
173
+ execution_mode: str,
174
+ agent_mcp_available: bool,
175
+ agent_mcp_approved: bool,
176
+ scp_available: bool | None = None,
177
+ root: Path | None = None,
178
+ started_at: str | None = None,
179
+ ) -> dict[str, Any]:
180
+ """Phase 13 run manifest with every contract field."""
181
+ return {
182
+ "run_id": run_id,
183
+ "skill_version": SKILL_VERSION,
184
+ "git_commit": git_commit(root),
185
+ "started_at": started_at or utc_now(),
186
+ "question": question,
187
+ "execution_mode": execution_mode,
188
+ "scp_available": detect_scp() if scp_available is None else scp_available,
189
+ "agent_mcp_available": agent_mcp_available,
190
+ "agent_mcp_approved": agent_mcp_approved,
191
+ "resource_policy_version": RESOURCE_POLICY_VERSION,
192
+ "confidence_policy_version": "2026-08-12.v2",
193
+ }
194
+
195
+
196
+ class RunWorkspace:
197
+ """Filesystem + state access for one run under ``<runs_dir>/<run_id>``."""
198
+
199
+ def __init__(self, runs_dir: Path, run_id: str):
200
+ self.runs_dir = Path(runs_dir)
201
+ self.run_id = run_id
202
+ self.path = self.runs_dir / run_id
203
+
204
+ # -- lifecycle ----------------------------------------------------------
205
+
206
+ def exists(self) -> bool:
207
+ return (self.path / "run_manifest.json").is_file()
208
+
209
+ def create(self) -> "RunWorkspace":
210
+ """Materialize the full workspace skeleton (empty seeds + state)."""
211
+ self.path.mkdir(parents=True, exist_ok=True)
212
+ for name, content in EMPTY_SEED.items():
213
+ target = self.path / name
214
+ if not target.exists():
215
+ target.write_text(content, encoding="utf-8")
216
+ (self.path / "fetch").mkdir(exist_ok=True)
217
+ (self.path / "task-briefs").mkdir(exist_ok=True)
218
+ if not (self.path / "trace.jsonl").exists():
219
+ self.path.joinpath("trace.jsonl").write_text("", encoding="utf-8")
220
+ if not (self.path / "state.json").exists():
221
+ self.save_state({"run_id": self.run_id, "question": "",
222
+ "depth": "M", "status": "running",
223
+ "current_stage": STAGES[0],
224
+ "stages": {s: {"status": "pending"} for s in STAGES}})
225
+ if not self.manifest_path.exists():
226
+ self.save_manifest(build_manifest(
227
+ self.run_id, "", execution_mode="platform_native",
228
+ agent_mcp_available=False, agent_mcp_approved=False))
229
+ self.trace("workspace_created", stage=None, detail=f"run workspace {self.run_id}")
230
+ return self
231
+
232
+ # -- manifest ------------------------------------------------------------
233
+
234
+ @property
235
+ def manifest_path(self) -> Path:
236
+ return self.path / "run_manifest.json"
237
+
238
+ def load_manifest(self) -> dict[str, Any]:
239
+ return load_json(self.manifest_path)
240
+
241
+ def save_manifest(self, manifest: dict[str, Any]) -> None:
242
+ save_json(self.manifest_path, manifest)
243
+
244
+ # -- state (Phase 32 resume) ----------------------------------------------
245
+
246
+ @property
247
+ def state_path(self) -> Path:
248
+ return self.path / "state.json"
249
+
250
+ def load_state(self) -> dict[str, Any]:
251
+ state = load_json(self.state_path)
252
+ state.setdefault("stages", {s: {"status": "pending"} for s in STAGES})
253
+ return state
254
+
255
+ def save_state(self, updates: dict[str, Any] | None = None) -> dict[str, Any]:
256
+ """Persist state; returns the merged state. Threads one writer at a time."""
257
+ lock = self.path / ".state.lock"
258
+ for _ in range(50):
259
+ try:
260
+ with lock.open("x", encoding="utf-8"):
261
+ break
262
+ except FileExistsError:
263
+ pass
264
+ import time
265
+ time.sleep(0.02)
266
+ try:
267
+ state = self.load_state()
268
+ if updates:
269
+ state.update(updates)
270
+ state["updated_at"] = utc_now()
271
+ save_json(self.state_path, state)
272
+ return state
273
+ finally:
274
+ try:
275
+ lock.unlink()
276
+ except OSError:
277
+ pass
278
+
279
+ def stage_status(self, stage: str) -> str:
280
+ return self.load_state().get("stages", {}).get(stage, {}).get("status", "pending")
281
+
282
+ def mark_stage(self, stage: str, status: str, *, detail: str = "", artifacts: list[str] | None = None) -> None:
283
+ """Update one stage's row in state.json."""
284
+ state = self.load_state()
285
+ row = state["stages"].setdefault(stage, {"status": "pending"})
286
+ row["status"] = status
287
+ if detail:
288
+ row["detail"] = detail
289
+ if artifacts:
290
+ row["artifacts"] = artifacts
291
+ self.save_state({"stages": state["stages"]})
292
+
293
+ # -- trace -----------------------------------------------------------------
294
+
295
+ def trace(self, event: str, *, stage: str | None = None, detail: str = "") -> None:
296
+ record = {"ts": utc_now(), "event": event, "stage": stage, "detail": detail}
297
+ append_jsonl(self.path / "trace.jsonl", record)
298
+
299
+ # -- external-agent briefs --------------------------------------------------
300
+
301
+ def brief_path(self, stage: str) -> Path:
302
+ return self.path / "task-briefs" / f"{stage}.md"
303
+
304
+ def write_brief(self, stage: str, question: str, prompt: str) -> Path:
305
+ """Write the handoff brief the orchestrator leaves for an external agent."""
306
+ path = self.brief_path(stage)
307
+ path.parent.mkdir(parents=True, exist_ok=True)
308
+ content = (f"# Run {self.run_id} — stage: {stage}\n\n"
309
+ f"## Question\n{question}\n\n## Task\n{prompt}\n\n"
310
+ f"## Output\nWrite the stage artifact into this run workspace "
311
+ f"(see execution_plan.json for the artifact path and schema gate).\n")
312
+ path.write_text(content, encoding="utf-8")
313
+ self.trace("brief_written", stage=stage, detail=str(path))
314
+ return path
315
+
316
+
317
+ def main() -> int:
318
+ import argparse
319
+ parser = argparse.ArgumentParser(description="EduEvidence run workspace maintenance")
320
+ parser.add_argument("--runs-dir", default="runs")
321
+ sub = parser.add_subparsers(dest="command", required=True)
322
+ create = sub.add_parser("create", help="create a workspace skeleton")
323
+ create.add_argument("--run-id", required=True)
324
+ create.add_argument("--question", default="")
325
+ args = parser.parse_args()
326
+
327
+ if args.command == "create":
328
+ ws = RunWorkspace(Path(args.runs_dir), args.run_id)
329
+ ws.create()
330
+ print(f"workspace created: {ws.path}")
331
+ return 0
332
+ return 2
333
+
334
+
335
+ if __name__ == "__main__":
336
+ import sys
337
+ sys.exit(main())
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python3
2
+ """scripts/serve_web.py — EduEvidence 落地页/静态资源预览服务器(CORS 开启)。
3
+
4
+ web/ 的营销落地页(landing.html 等)按架构是独立入口,不挂在
5
+ dashboard_server 路由下(P3 gate: /landing.html 必须 404)。本脚本以只读方式
6
+ 静态托管 web/,并为所有响应加上 Access-Control-Allow-Origin: *,
7
+ 使落地页与不同端口的 Web Studio 控制台之间能做跨源探测与互相跳转。
8
+
9
+ python3 scripts/serve_web.py --host 127.0.0.1 --port 8877
10
+
11
+ 首页入口:http://127.0.0.1:8877/landing.html
12
+ 控制台: http://127.0.0.1:8766/(scripts/dashboard_server.py)
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import http.server
18
+ import os
19
+ from pathlib import Path
20
+
21
+ ROOT = Path(__file__).resolve().parent.parent
22
+ WEB_DIR = ROOT / "web"
23
+
24
+
25
+ class CORSStaticHandler(http.server.SimpleHTTPRequestHandler):
26
+ """SimpleHTTPRequestHandler + 允许跨源只读访问(探测/预览用)。"""
27
+
28
+ def end_headers(self) -> None:
29
+ self.send_header("Access-Control-Allow-Origin", "*")
30
+ super().end_headers()
31
+
32
+
33
+ def main() -> None:
34
+ parser = argparse.ArgumentParser(description=__doc__)
35
+ parser.add_argument("--host", default="127.0.0.1")
36
+ parser.add_argument("--port", type=int, default=8877)
37
+ args = parser.parse_args()
38
+
39
+ os.chdir(WEB_DIR)
40
+ server = http.server.ThreadingHTTPServer(
41
+ (args.host, args.port), CORSStaticHandler)
42
+ 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)")
46
+ print("=" * 60)
47
+ try:
48
+ server.serve_forever()
49
+ except KeyboardInterrupt:
50
+ pass
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ """scripts/skill_lint.py — Skill Static Consistency and Contract Linter.
3
+
4
+ Verifies:
5
+ 1. Root SKILL.md exists, has valid YAML frontmatter (name, description), and is concise.
6
+ 2. Sub-skills in skill/sub-skills/ exist, each with a valid SKILL.md containing YAML frontmatter.
7
+ 3. Reference documents exist in references/.
8
+ 4. Key schemas exist in schemas/.
9
+ 5. Search providers and scripts exist.
10
+
11
+ Usage:
12
+ python3 scripts/skill_lint.py
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import re
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ ROOT = Path(__file__).resolve().parent.parent
22
+
23
+
24
+ def lint_skill() -> list[str]:
25
+ errors = []
26
+
27
+ # 1. Check root SKILL.md
28
+ root_skill = ROOT / "SKILL.md"
29
+ if not root_skill.exists():
30
+ errors.append("Root SKILL.md is missing")
31
+ else:
32
+ text = root_skill.read_text(encoding="utf-8")
33
+ if not re.search(r"^---\s*\nname:\s*\S+.*?\n---", text, re.DOTALL):
34
+ errors.append("Root SKILL.md missing valid YAML frontmatter with 'name:'")
35
+ if "Progressive Disclosure" not in text and "skill/sub-skills" not in text:
36
+ errors.append("Root SKILL.md should mention progressive disclosure / sub-skills router")
37
+
38
+ # 2. Check sub-skills in skill/sub-skills/
39
+ skills_dir = ROOT / "skill" / "sub-skills"
40
+ if not skills_dir.exists():
41
+ errors.append("skill/sub-skills/ directory is missing")
42
+ else:
43
+ sub_skills = [d for d in skills_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]
44
+ if len(sub_skills) < 5:
45
+ errors.append(f"Expected at least 5 sub-skills in skill/sub-skills/, found {len(sub_skills)}")
46
+ for sd in sub_skills:
47
+ skill_md = sd / "SKILL.md"
48
+ if not skill_md.exists():
49
+ errors.append(f"Sub-skill '{sd.name}' is missing SKILL.md")
50
+ else:
51
+ stext = skill_md.read_text(encoding="utf-8")
52
+ if not re.search(r"^---\s*\nname:\s*\S+.*?\n---", stext, re.DOTALL):
53
+ errors.append(f"Sub-skill '{sd.name}/SKILL.md' missing valid YAML frontmatter")
54
+
55
+ # 3. Check reference documents
56
+ ref_dir = ROOT / "references"
57
+ expected_refs = [
58
+ "social_science_pitfalls.md",
59
+ "wwc_standards.md",
60
+ "grade_framework.md",
61
+ "effect_size_formulas.md",
62
+ ]
63
+ for r in expected_refs:
64
+ if not (ref_dir / r).exists():
65
+ errors.append(f"Missing methodology reference: references/{r}")
66
+
67
+ # 4. Check core scripts
68
+ expected_scripts = [
69
+ "did_regression.py",
70
+ "effect_calculator.py",
71
+ "orchestrator.py",
72
+ ]
73
+ for s in expected_scripts:
74
+ if not (ROOT / "scripts" / s).exists():
75
+ errors.append(f"Missing required script: scripts/{s}")
76
+
77
+ # 5. Check retrieval search
78
+ if not (ROOT / "retrieval" / "search.py").exists():
79
+ errors.append("Missing retrieval/search.py")
80
+
81
+ # 6. Canonical Protocol 口径(W5 架构对齐)
82
+ if root_skill.exists():
83
+ text = root_skill.read_text(encoding="utf-8")
84
+ if "Canonical" not in text and "Research Core" not in text:
85
+ errors.append("Root SKILL.md should present the Canonical Protocol (Research Core 6 + Decision Extension 3)")
86
+ if re.search(r"12-Step|12 步|twelve", text, re.I):
87
+ errors.append("Root SKILL.md still mentions the deprecated 12-Step pipeline (canonical = 9 steps)")
88
+ for banned in ("运行时主题切换", "theme-switcher", "theme_switcher", "agent 派遣"):
89
+ if banned in text:
90
+ errors.append(f"Root SKILL.md contains banned wording: {banned}")
91
+ if "Schema 版本口径" not in text:
92
+ errors.append("Root SKILL.md missing canonical Schema 版本口径 (V1 top-level vs schemas/v2)")
93
+ if re.search(r"12-Step|12 步流水线", text):
94
+ errors.append("Root SKILL.md drifted back to 12-step pipeline wording")
95
+
96
+ # 6b. 输出契约解析安全(禁止 FINAL_ANSWER 文本尾巴)
97
+ for role_md in sorted((ROOT / "skill" / "agents").glob("*.md")):
98
+ rtext = role_md.read_text(encoding="utf-8")
99
+ if "FINAL_ANSWER" in rtext:
100
+ errors.append(f"Role prompt {role_md.name} still uses FINAL_ANSWER tail (output must be pure JSON)")
101
+
102
+ # 7. task-briefs 模板(编排链补全)
103
+ brief_dir = ROOT / "skill" / "task-briefs"
104
+ stage_briefs = ["frame", "retrieve", "extract", "challenge", "audit",
105
+ "adjudicate", "intervene", "evaluate", "present"]
106
+ if not brief_dir.exists():
107
+ errors.append("skill/task-briefs/ is missing (orchestration chain)")
108
+ else:
109
+ for st in stage_briefs:
110
+ if not (brief_dir / f"{st}.md").exists():
111
+ errors.append(f"Missing task brief template: skill/task-briefs/{st}.md")
112
+
113
+ # 8. 语言人话化规则下沉到核心角色
114
+ for role in ("evidence-judge", "skeptic", "method-reviewer"):
115
+ role_md = ROOT / "skill" / "agents" / f"{role}.md"
116
+ if not role_md.exists():
117
+ errors.append(f"Missing role prompt: skill/agents/{role}.md")
118
+ elif "语言人话化规则" not in role_md.read_text(encoding="utf-8"):
119
+ errors.append(f"Role {role} prompt missing 语言人话化规则 (Present language contract)")
120
+
121
+ # 9. 渲染器无运行时换肤(Present 烘焙原则)
122
+ renderer = ROOT / "visualization" / "eduevidence-report" / "scripts" / "build_report.py"
123
+ if renderer.exists():
124
+ rtext = renderer.read_text(encoding="utf-8")
125
+ if re.search(r"_theme_switcher\(|theme-switcher", rtext):
126
+ errors.append("build_report.py still contains runtime theme switcher code")
127
+ legacy = ROOT / "scripts" / "render_report_html.py"
128
+ if legacy.exists():
129
+ ltext = legacy.read_text(encoding="utf-8")
130
+ if re.search(r"theme-btn|theme-switcher|data-theme-target", ltext):
131
+ errors.append("scripts/render_report_html.py still contains runtime theme switcher")
132
+
133
+ return errors
134
+
135
+
136
+ def main():
137
+ print("[*] Running EduEvidence Skill Linter...")
138
+ errors = lint_skill()
139
+ if errors:
140
+ print(f"[-] Skill Lint FAILED with {len(errors)} error(s):", file=sys.stderr)
141
+ for e in errors:
142
+ print(f" • {e}", file=sys.stderr)
143
+ sys.exit(1)
144
+ else:
145
+ print("[+] Skill Lint PASSED: Root SKILL.md, 12 sub-skills, canonical 9-step protocol, orchestration chain, and scripts are consistent!")
146
+ sys.exit(0)
147
+
148
+
149
+ if __name__ == "__main__":
150
+ main()