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.
- package/LICENSE +21 -0
- package/README.md +506 -0
- package/README.zh-CN.md +434 -0
- package/SKILL.md +195 -0
- package/bin/eduevidence.js +127 -0
- package/domains/education/manifest.json +183 -0
- package/domains/education/outcome_taxonomy.json +127 -0
- package/domains/manifest.json +26 -0
- package/domains/policy/frame.schema.json +234 -0
- package/domains/policy/manifest.json +10 -0
- package/domains/policy/methodology_checklist.json +109 -0
- package/domains/policy/outcome_taxonomy.json +53 -0
- package/domains/policy/references/causal-identification.md +45 -0
- package/domains/policy/references/cost-evidence.md +44 -0
- package/domains/policy/references/equity.md +42 -0
- package/domains/policy/references/evidence-hierarchy.md +41 -0
- package/domains/policy/references/implementation-evidence.md +47 -0
- package/eduevidence_cli.py +26 -0
- package/engine/__init__.py +11 -0
- package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
- package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/bias.cpython-312.pyc +0 -0
- package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
- package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
- package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
- package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
- package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
- package/engine/__pycache__/events.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
- package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
- package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
- package/engine/__pycache__/ids.cpython-312.pyc +0 -0
- package/engine/__pycache__/library.cpython-312.pyc +0 -0
- package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
- package/engine/__pycache__/living.cpython-312.pyc +0 -0
- package/engine/__pycache__/log.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/migration.cpython-312.pyc +0 -0
- package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
- package/engine/__pycache__/paths.cpython-312.pyc +0 -0
- package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
- package/engine/__pycache__/planner.cpython-312.pyc +0 -0
- package/engine/__pycache__/project.cpython-312.pyc +0 -0
- package/engine/__pycache__/projections.cpython-312.pyc +0 -0
- package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
- package/engine/__pycache__/run.cpython-312.pyc +0 -0
- package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
- package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
- package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
- package/engine/__pycache__/update.cpython-312.pyc +0 -0
- package/engine/__pycache__/versions.cpython-312.pyc +0 -0
- package/engine/analysis.py +308 -0
- package/engine/bias.py +178 -0
- package/engine/briefs.py +106 -0
- package/engine/capabilities.py +99 -0
- package/engine/citation_check.py +192 -0
- package/engine/contracts.py +117 -0
- package/engine/datasets.py +165 -0
- package/engine/events.py +67 -0
- package/engine/evidence_graph.py +571 -0
- package/engine/evidence_review.py +88 -0
- package/engine/evidencecore.py +182 -0
- package/engine/gap_lens.py +132 -0
- package/engine/gaps.py +169 -0
- package/engine/graph_store.py +335 -0
- package/engine/graph_validate.py +87 -0
- package/engine/ids.py +77 -0
- package/engine/library.py +268 -0
- package/engine/library_builtin.py +301 -0
- package/engine/living.py +671 -0
- package/engine/log.py +39 -0
- package/engine/meta_analysis.py +333 -0
- package/engine/meta_synthesis.py +111 -0
- package/engine/migration.py +397 -0
- package/engine/mode_router.py +72 -0
- package/engine/paths.py +15 -0
- package/engine/pilot.py +368 -0
- package/engine/planner.py +126 -0
- package/engine/project.py +118 -0
- package/engine/projections.py +240 -0
- package/engine/robustness.py +109 -0
- package/engine/run.py +85 -0
- package/engine/semantics.py +135 -0
- package/engine/study_design.py +87 -0
- package/engine/synthesis.py +187 -0
- package/engine/tribunal.py +408 -0
- package/engine/update.py +113 -0
- package/engine/versions.py +12 -0
- package/install.sh +510 -0
- package/integrations/__init__.py +1 -0
- package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
- package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
- package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
- package/integrations/agent_mcp.py +856 -0
- package/integrations/smart_web_fetch.py +59 -0
- package/package.json +50 -0
- package/pyproject.toml +55 -0
- package/references/applicability-policy.md +88 -0
- package/references/education-framing.md +132 -0
- package/references/effect_size_formulas.md +35 -0
- package/references/evaluation-design.md +111 -0
- package/references/evidence-quality.md +79 -0
- package/references/grade_framework.md +29 -0
- package/references/intervention-design.md +98 -0
- package/references/methodology-audit.md +103 -0
- package/references/outcome-taxonomy.md +106 -0
- package/references/retrieval-protocol.md +142 -0
- package/references/skeptic-protocol.md +93 -0
- package/references/social_science_pitfalls.md +48 -0
- package/references/source-validity.md +140 -0
- package/references/tribunal-policy.md +112 -0
- package/references/wwc_standards.md +29 -0
- package/retrieval/__init__.py +1 -0
- package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
- package/retrieval/corpus_store.py +181 -0
- package/retrieval/dedupe.py +127 -0
- package/retrieval/failures.py +90 -0
- package/retrieval/fetch.py +435 -0
- package/retrieval/search.py +493 -0
- package/retrieval/source.py +160 -0
- package/retrieval/validate.py +257 -0
- package/schemas/agent-mcp-approval.schema.json +57 -0
- package/schemas/chart-spec.schema.json +88 -0
- package/schemas/cross-model-review.schema.json +28 -0
- package/schemas/education-frame.schema.json +106 -0
- package/schemas/evaluation.schema.json +35 -0
- package/schemas/evidence.schema.json +81 -0
- package/schemas/fetch-result.schema.json +119 -0
- package/schemas/intervention.schema.json +46 -0
- package/schemas/methodology.schema.json +102 -0
- package/schemas/report-result.schema.json +381 -0
- package/schemas/report-spec.schema.json +130 -0
- package/schemas/source.schema.json +311 -0
- package/schemas/v2/analysis-plan.schema.json +28 -0
- package/schemas/v2/analysis-run.schema.json +33 -0
- package/schemas/v2/claim.schema.json +26 -0
- package/schemas/v2/dataset-asset.schema.json +40 -0
- package/schemas/v2/decision-snapshot.schema.json +53 -0
- package/schemas/v2/evidence-link.schema.json +38 -0
- package/schemas/v2/finding.schema.json +47 -0
- package/schemas/v2/graph-revision.schema.json +30 -0
- package/schemas/v2/knowledge-gap.schema.json +40 -0
- package/schemas/v2/methodology-audit.schema.json +30 -0
- package/schemas/v2/outcome.schema.json +18 -0
- package/schemas/v2/project.schema.json +31 -0
- package/schemas/v2/research-intent.schema.json +24 -0
- package/schemas/v2/run.schema.json +43 -0
- package/schemas/v2/source.schema.json +24 -0
- package/schemas/v2/study-design.schema.json +67 -0
- package/schemas/v2/study.schema.json +37 -0
- package/schemas/v3/pilot-outcome.schema.json +132 -0
- package/schemas/v3/run-manifest.schema.json +193 -0
- package/schemas/v3/synthesis.schema.json +86 -0
- package/schemas/v4/drift-report.schema.json +66 -0
- package/schemas/v4/evidence-library.schema.json +42 -0
- package/schemas/v4/living-subscription.schema.json +55 -0
- package/schemas/v4/meta-analysis.schema.json +152 -0
- package/schemas/verdict.schema.json +56 -0
- package/scripts/__init__.py +0 -0
- package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
- package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
- package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
- package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
- package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
- package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
- package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
- package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
- package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
- package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
- package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
- package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
- package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
- package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
- package/scripts/audit_dois.py +292 -0
- package/scripts/bake_pack.sh +37 -0
- package/scripts/benchmark.py +183 -0
- package/scripts/benchmark_evaluator.py +371 -0
- package/scripts/benchmark_judge.py +535 -0
- package/scripts/benchmark_routing.py +120 -0
- package/scripts/benchmark_v2.py +304 -0
- package/scripts/benchmark_v3.py +552 -0
- package/scripts/build_esl_artifacts.py +1921 -0
- package/scripts/build_evidence_library.py +307 -0
- package/scripts/build_killer_demo.py +295 -0
- package/scripts/build_result.py +311 -0
- package/scripts/check_version_consistency.py +96 -0
- package/scripts/citation_check.py +123 -0
- package/scripts/claim_audit.py +157 -0
- package/scripts/complexity_gate.py +180 -0
- package/scripts/compute_confidence.py +176 -0
- package/scripts/dashboard_server.py +536 -0
- package/scripts/did_regression.py +315 -0
- package/scripts/effect_calculator.py +99 -0
- package/scripts/enrich_projects_human_and_lieflat.py +315 -0
- package/scripts/evidence_matrix.py +129 -0
- package/scripts/evidence_score.py +234 -0
- package/scripts/evidence_semantics.py +87 -0
- package/scripts/fetch_benchmark.py +177 -0
- package/scripts/generate_metrics.py +99 -0
- package/scripts/generate_new_projects.py +686 -0
- package/scripts/generate_promo.py +192 -0
- package/scripts/lint_report_layout.py +182 -0
- package/scripts/orchestrator.py +1456 -0
- package/scripts/pre_verdict_gate.py +513 -0
- package/scripts/quickstart.py +121 -0
- package/scripts/rebake_all_5themes.py +88 -0
- package/scripts/recompute_demo_quality.py +205 -0
- package/scripts/render_report.py +270 -0
- package/scripts/render_report_html.py +356 -0
- package/scripts/retraction_watch.py +110 -0
- package/scripts/run_workspace.py +337 -0
- package/scripts/serve_web.py +54 -0
- package/scripts/skill_lint.py +150 -0
- package/scripts/startup_probe.py +265 -0
- package/scripts/sync_killer_demo_report.py +270 -0
- package/scripts/test_adversarial_empirical.py +541 -0
- package/scripts/validate_schema.py +256 -0
- package/skill/agents/education-planner.md +80 -0
- package/skill/agents/evaluation-designer.md +74 -0
- package/skill/agents/evidence-analyst.md +106 -0
- package/skill/agents/evidence-judge.md +111 -0
- package/skill/agents/evidence-retriever.md +80 -0
- package/skill/agents/intervention-designer.md +82 -0
- package/skill/agents/method-reviewer.md +104 -0
- package/skill/agents/skeptic.md +89 -0
- package/skill/sub-skills/aihot-trend-analysis/SKILL.md +31 -0
- package/skill/sub-skills/contradiction-analysis/SKILL.md +17 -0
- package/skill/sub-skills/data-analysis/SKILL.md +23 -0
- package/skill/sub-skills/ethics-review/SKILL.md +25 -0
- package/skill/sub-skills/evidence-extraction/SKILL.md +19 -0
- package/skill/sub-skills/evidence-review/SKILL.md +18 -0
- package/skill/sub-skills/gap-analysis/SKILL.md +25 -0
- package/skill/sub-skills/literature-review/SKILL.md +21 -0
- package/skill/sub-skills/methodology-audit/SKILL.md +20 -0
- package/skill/sub-skills/report-generation/SKILL.md +51 -0
- package/skill/sub-skills/research-planning/SKILL.md +21 -0
- package/skill/sub-skills/study-design/SKILL.md +16 -0
- package/skill/task-briefs/adjudicate.md +17 -0
- package/skill/task-briefs/audit.md +15 -0
- package/skill/task-briefs/challenge.md +15 -0
- package/skill/task-briefs/evaluate.md +13 -0
- package/skill/task-briefs/extract.md +16 -0
- package/skill/task-briefs/frame.md +17 -0
- package/skill/task-briefs/intervene.md +14 -0
- package/skill/task-briefs/present.md +16 -0
- package/skill/task-briefs/retrieve.md +15 -0
- package/visualization/eduevidence-report/assets/base.css +337 -0
- package/visualization/eduevidence-report/motion/motion.css +157 -0
- package/visualization/eduevidence-report/motion/motion.js +107 -0
- package/visualization/eduevidence-report/references/bilingual-style.md +7 -0
- package/visualization/eduevidence-report/references/component-catalog.md +145 -0
- package/visualization/eduevidence-report/references/evidence-expansion.md +65 -0
- package/visualization/eduevidence-report/references/full-report-outline.md +86 -0
- package/visualization/eduevidence-report/references/layout-constraints.md +63 -0
- package/visualization/eduevidence-report/references/lieflat-composition.md +79 -0
- package/visualization/eduevidence-report/references/motion-system.md +31 -0
- package/visualization/eduevidence-report/schemas/adapter-envelope.schema.json +22 -0
- package/visualization/eduevidence-report/schemas/visual-layout.schema.json +87 -0
- package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/adapter_contract.py +72 -0
- package/visualization/eduevidence-report/scripts/build_artifact_manifest.py +70 -0
- package/visualization/eduevidence-report/scripts/build_charts.py +283 -0
- package/visualization/eduevidence-report/scripts/build_figures.py +515 -0
- package/visualization/eduevidence-report/scripts/build_infographics.py +268 -0
- package/visualization/eduevidence-report/scripts/build_report.py +3211 -0
- package/visualization/eduevidence-report/scripts/charts_data.py +617 -0
- package/visualization/eduevidence-report/scripts/check_mobile_layout.js +220 -0
- package/visualization/eduevidence-report/scripts/lieflat_engine.py +852 -0
- package/visualization/eduevidence-report/scripts/zh_labels.py +245 -0
- package/visualization/eduevidence-report/themes/academic.css +94 -0
- package/visualization/eduevidence-report/themes/claude.css +96 -0
- package/visualization/eduevidence-report/themes/datalab-dark.css +147 -0
- package/visualization/eduevidence-report/themes/datalab.css +151 -0
- 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()
|