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.
- package/CONTRIBUTING.md +105 -0
- package/README.md +142 -75
- package/README.zh-CN.md +73 -30
- package/SKILL.md +397 -131
- package/agents/openai.yaml +4 -0
- package/assets/readme/controlled-execution.svg +34 -0
- package/assets/readme/landing-tour.gif +0 -0
- package/assets/readme/logo.png +0 -0
- package/assets/readme/research-workflow.svg +56 -0
- package/assets/readme/studio-graph.png +0 -0
- package/assets/readme/studio-overview.png +0 -0
- package/assets/readme/studio-reports.png +0 -0
- package/assets/readme/studio-tour.gif +0 -0
- package/autoevolve/config.yaml +17 -0
- package/autoevolve/program.md +25 -0
- package/autoevolve/protected.manifest.yaml +34 -0
- package/benchmarks/adversarial/cases.jsonl +7 -0
- package/benchmarks/evidence-library.json +5268 -0
- package/benchmarks/partitions.json +8 -0
- package/bin/eduevidence.js +2 -1
- package/docs/architecture.md +496 -0
- package/docs/autoresearch-evolution-plan.md +2903 -0
- package/docs/autoresearch-implementation-status.md +101 -0
- package/docs/demo-storyboard.md +20 -0
- package/docs/demo-workplace-ai.md +92 -0
- package/docs/demo.md +32 -0
- package/docs/install-guide.md +150 -0
- package/docs/orchestration-role-model.md +1254 -0
- package/docs/release-closeout/README.md +17 -0
- package/docs/release-closeout/frontend-acceptance.md +23 -0
- package/docs/release-closeout/issues.md +19 -0
- package/docs/release-closeout/verification.md +28 -0
- package/docs/release-contract.md +108 -0
- package/docs/research-studio-guide.zh-CN.md +166 -0
- package/docs/sciverse-api.md +125 -0
- package/eduevidence_cli.py +29 -13
- package/engine/_resources.py +13 -0
- package/engine/autoevolve/__init__.py +3 -0
- package/engine/autoevolve/agent_view.py +167 -0
- package/engine/autoevolve/core.py +357 -0
- package/engine/autoevolve/events.py +11 -0
- package/engine/autoevolve/git_workspace.py +77 -0
- package/engine/autoevolve/projection.py +23 -0
- package/engine/autoevolve/runner.py +413 -0
- package/engine/autoevolve/trust.py +146 -0
- package/engine/autoresearch/__init__.py +6 -0
- package/engine/autoresearch/commit.py +132 -0
- package/engine/autoresearch/contracts.py +126 -0
- package/engine/autoresearch/controller.py +207 -0
- package/engine/autoresearch/events.py +12 -0
- package/engine/autoresearch/gap_priority.py +168 -0
- package/engine/autoresearch/projection.py +30 -0
- package/engine/autoresearch/research_memory.py +59 -0
- package/engine/autoresearch/saturation.py +91 -0
- package/engine/briefs.py +2 -1
- package/engine/capabilities.py +1 -0
- package/engine/contracts.py +3 -1
- package/engine/decision_policy.py +96 -0
- package/engine/evidence_graph.py +14 -10
- package/engine/evidencecore.py +7 -5
- package/engine/gaps.py +132 -73
- package/engine/ids.py +2 -0
- package/engine/judge_pack.py +65 -0
- package/engine/library.py +6 -2
- package/engine/library_builtin.py +3 -1
- package/engine/living.py +36 -5
- package/engine/meta_synthesis.py +3 -1
- package/engine/migration.py +88 -3
- package/engine/orchestration.py +460 -0
- package/engine/paths.py +2 -0
- package/engine/pilot.py +36 -33
- package/engine/project.py +2 -2
- package/engine/research_service.py +113 -0
- package/engine/studio_read_model.py +400 -0
- package/engine/taxonomy.py +211 -0
- package/engine/tribunal.py +44 -33
- package/engine/update.py +1 -0
- package/engine/versions.py +1 -1
- package/engine/worker_result.py +109 -0
- package/engine/workflows.py +70 -0
- package/examples/ai-coding-assistant-evidence/EduEvidence_Report.html +2934 -0
- package/examples/ai-coding-assistant-evidence/artifact_manifest.json +15 -0
- package/examples/ai-coding-assistant-evidence/citation_check.json +79 -0
- package/examples/ai-coding-assistant-evidence/claims.jsonl +12 -0
- package/examples/ai-coding-assistant-evidence/evaluation.json +35 -0
- package/examples/ai-coding-assistant-evidence/evidence.jsonl +12 -0
- package/examples/ai-coding-assistant-evidence/final_verdict.json +107 -0
- package/examples/ai-coding-assistant-evidence/frame.json +48 -0
- package/examples/ai-coding-assistant-evidence/gate_report.json +101 -0
- package/examples/ai-coding-assistant-evidence/intervention.json +51 -0
- package/examples/ai-coding-assistant-evidence/methodology.json +36 -0
- package/examples/ai-coding-assistant-evidence/raw_verdict.json +86 -0
- package/examples/ai-coding-assistant-evidence/report_spec.json +230 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_academic.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_claude.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab-dark.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_presentation.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_academic.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_claude.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab-dark.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_presentation.html +2934 -0
- package/examples/ai-coding-assistant-evidence/result.json +1457 -0
- package/examples/ai-coding-assistant-evidence/result.zh.json +1457 -0
- package/examples/ai-coding-assistant-evidence/skeptic.json +72 -0
- package/examples/ai-coding-assistant-evidence/sources.jsonl +8 -0
- package/examples/ai-coding-assistant-evidence/verdict.json +107 -0
- package/examples/spaced-retrieval-practice/applicability.json +14 -0
- package/examples/spaced-retrieval-practice/artifact_manifest.json +15 -0
- package/examples/spaced-retrieval-practice/claims.jsonl +3 -0
- package/examples/spaced-retrieval-practice/evidence.jsonl +6 -0
- package/examples/spaced-retrieval-practice/final_verdict.json +93 -0
- package/examples/spaced-retrieval-practice/frame.json +58 -0
- package/examples/spaced-retrieval-practice/gate_report.json +101 -0
- package/examples/spaced-retrieval-practice/methodology.json +78 -0
- package/examples/spaced-retrieval-practice/report_spec.json +212 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_academic.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_claude.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab-dark.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_presentation.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_academic.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_claude.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_datalab-dark.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_datalab.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_presentation.html +2728 -0
- package/examples/spaced-retrieval-practice/result.json +942 -0
- package/examples/spaced-retrieval-practice/result.zh.json +942 -0
- package/examples/spaced-retrieval-practice/skeptic.json +70 -0
- package/examples/spaced-retrieval-practice/sources.jsonl +7 -0
- package/examples/spaced-retrieval-practice/verdict.json +93 -0
- package/examples/workplace-ai-assistant/artifact_manifest.json +15 -0
- package/examples/workplace-ai-assistant/claims.jsonl +4 -0
- package/examples/workplace-ai-assistant/evaluation.json +19 -0
- package/examples/workplace-ai-assistant/evidence.jsonl +4 -0
- package/examples/workplace-ai-assistant/evidence_graph.json +444 -0
- package/examples/workplace-ai-assistant/final_verdict.json +78 -0
- package/examples/workplace-ai-assistant/frame.json +41 -0
- package/examples/workplace-ai-assistant/gate_report.json +101 -0
- package/examples/workplace-ai-assistant/intervention.json +27 -0
- package/examples/workplace-ai-assistant/legacy-link-check.json +16 -0
- package/examples/workplace-ai-assistant/methodology.json +60 -0
- package/examples/workplace-ai-assistant/report_spec.json +224 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_academic.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_claude.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab-dark.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_presentation.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_academic.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_claude.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_datalab-dark.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_datalab.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_presentation.html +2814 -0
- package/examples/workplace-ai-assistant/result.json +615 -0
- package/examples/workplace-ai-assistant/result.zh.json +615 -0
- package/examples/workplace-ai-assistant/search_log.json +19 -0
- package/examples/workplace-ai-assistant/skeptic.json +72 -0
- package/examples/workplace-ai-assistant/sources.jsonl +3 -0
- package/examples/workplace-ai-assistant/validation_result.json +9 -0
- package/examples/workplace-ai-assistant/verdict.json +78 -0
- package/install.sh +7 -7
- package/integrations/agent_mcp.py +2 -2
- package/integrations/orchestration_dispatch.py +146 -0
- package/package.json +46 -3
- package/pyproject.toml +14 -22
- package/references/autoresearch.md +30 -0
- package/references/evaluation-policy.md +24 -0
- package/references/orchestration.md +22 -0
- package/references/report-copy-style.md +67 -0
- package/references/retrieval-compliance.md +75 -0
- package/references/retrieval-protocol.md +20 -0
- package/references/scientific-invariants.md +19 -0
- package/retrieval/audit.py +178 -0
- package/retrieval/fetch.py +96 -0
- package/retrieval/sciverse.py +398 -0
- package/retrieval/search.py +47 -7
- package/schemas/applicability.schema.json +94 -0
- package/schemas/chart-spec.schema.json +10 -3
- package/schemas/evidence.schema.json +316 -43
- package/schemas/fetch-result.schema.json +2 -1
- package/schemas/intervention.schema.json +106 -21
- package/schemas/report-result.schema.json +12 -4
- package/schemas/report-spec.schema.json +98 -100
- package/schemas/skeptic.schema.json +86 -0
- package/schemas/source.schema.json +21 -2
- package/schemas/v2/finding.schema.json +5 -1
- package/schemas/v2/methodology-audit.schema.json +5 -1
- package/schemas/v2/outcome.schema.json +28 -5
- package/schemas/v2/project.schema.json +2 -2
- package/schemas/v2/run.schema.json +1 -1
- package/schemas/v2/study.schema.json +5 -1
- package/schemas/vNext/autoevolve-session.schema.json +34 -0
- package/schemas/vNext/eval-snapshot.schema.json +77 -0
- package/schemas/vNext/execution-plan.schema.json +50 -0
- package/schemas/vNext/gap-priority.schema.json +54 -0
- package/schemas/vNext/negative-search-record.schema.json +68 -0
- package/schemas/vNext/research-iteration.schema.json +87 -0
- package/schemas/vNext/research-strategy.schema.json +62 -0
- package/schemas/vNext/skill-experiment.schema.json +90 -0
- package/schemas/vNext/task-spec.schema.json +156 -0
- package/schemas/vNext/worker-result.schema.json +60 -0
- package/schemas/verdict.schema.json +164 -28
- package/scripts/benchmark_judge.py +2 -2
- package/scripts/benchmark_v3.py +26 -43
- package/scripts/build_esl_artifacts.py +4 -4
- package/scripts/build_evidence_library.py +2 -2
- package/scripts/build_gh_pages.py +98 -0
- package/scripts/build_readme_diagrams.py +72 -0
- package/scripts/build_report_variants.py +101 -0
- package/scripts/build_result.py +74 -9
- package/scripts/check_autoresearch_invariants.py +95 -0
- package/scripts/check_package_parity.py +85 -0
- package/scripts/check_protocol_alignment.py +375 -0
- package/scripts/check_versioned_schemas.py +254 -0
- package/scripts/claim_audit.py +13 -8
- package/scripts/compute_confidence.py +10 -0
- package/scripts/daily_evolve.py +30 -0
- package/scripts/dashboard_server.py +130 -101
- package/scripts/did_regression.py +17 -32
- package/scripts/enrich_projects_human_and_lieflat.py +1 -1
- package/scripts/evidence_score.py +5 -2
- package/scripts/generate_metrics.py +4 -3
- package/scripts/generate_new_projects.py +5 -5
- package/scripts/orchestrator.py +286 -36
- package/scripts/pre_verdict_gate.py +224 -26
- package/scripts/quickstart.py +18 -2
- package/scripts/rebake_all_5themes.py +1 -2
- package/scripts/research_auto_cli.py +475 -0
- package/scripts/run_workspace.py +24 -8
- package/scripts/search_provenance.py +64 -0
- package/scripts/serve_web.py +9 -10
- package/scripts/skill_lint.py +1 -1
- package/scripts/skill_payload.py +81 -0
- package/scripts/test_adversarial_empirical.py +26 -19
- package/scripts/validate_schema.py +46 -2
- package/scripts/vnext_cli.py +133 -0
- package/setup.py +12 -0
- package/skill/agents/evaluation-designer.md +20 -4
- package/skill/agents/evidence-analyst.md +19 -3
- package/skill/agents/evidence-judge.md +50 -2
- package/skill/agents/evidence-retriever.md +20 -3
- package/skill/agents/intervention-designer.md +20 -4
- package/skill/agents/method-reviewer.md +18 -2
- package/skill/agents/{education-planner.md → research-planner.md} +19 -3
- package/skill/agents/skeptic.md +18 -2
- package/skill/roles/registry.yaml +45 -0
- package/skill/sub-skills/aihot-trend-analysis/SKILL.md +28 -9
- package/skill/sub-skills/contradiction-analysis/SKILL.md +31 -11
- package/skill/sub-skills/data-analysis/SKILL.md +34 -15
- package/skill/sub-skills/ethics-review/SKILL.md +33 -10
- package/skill/sub-skills/evidence-extraction/SKILL.md +29 -11
- package/skill/sub-skills/evidence-review/SKILL.md +31 -12
- package/skill/sub-skills/gap-analysis/SKILL.md +31 -9
- package/skill/sub-skills/literature-review/SKILL.md +35 -14
- package/skill/sub-skills/methodology-audit/SKILL.md +29 -12
- package/skill/sub-skills/report-generation/SKILL.md +40 -6
- package/skill/sub-skills/research-planning/SKILL.md +41 -14
- package/skill/sub-skills/study-design/SKILL.md +30 -9
- package/skill/task-briefs/adjudicate.md +32 -7
- package/skill/task-briefs/applicability.md +38 -0
- package/skill/task-briefs/audit.md +32 -7
- package/skill/task-briefs/challenge.md +34 -5
- package/skill/task-briefs/evaluate.md +30 -5
- package/skill/task-briefs/extract.md +31 -8
- package/skill/task-briefs/frame.md +39 -10
- package/skill/task-briefs/intervene.md +32 -6
- package/skill/task-briefs/present.md +32 -8
- package/skill/task-briefs/projection.md +37 -0
- package/skill/task-briefs/retrieve.md +36 -6
- package/skill/workflows/decision-and-pilot.md +85 -0
- package/skill/workflows/evaluate-and-update.md +93 -0
- package/skill/workflows/evidence-review.md +117 -0
- package/visualization/eduevidence-report/assets/base.css +2 -2
- package/visualization/eduevidence-report/assets/reader.css +752 -0
- package/visualization/eduevidence-report/assets/reader.js +132 -0
- package/visualization/eduevidence-report/references/chart-selection-catalog.md +109 -0
- package/visualization/eduevidence-report/references/lieflat-composition.md +3 -1
- package/visualization/eduevidence-report/scripts/build_figures.py +25 -3
- package/visualization/eduevidence-report/scripts/build_infographics.py +5 -1
- package/visualization/eduevidence-report/scripts/build_report.py +561 -121
- package/visualization/eduevidence-report/scripts/charts_data.py +2 -0
- package/visualization/eduevidence-report/scripts/lieflat_engine.py +371 -136
- package/visualization/eduevidence-report/scripts/zh_labels.py +80 -1
- package/visualization/eduevidence-report/themes/academic.css +1 -1
- package/visualization/eduevidence-report/themes/claude.css +1 -1
- package/visualization/eduevidence-report/themes/datalab-dark.css +2 -2
- package/visualization/eduevidence-report/themes/datalab.css +2 -2
- package/visualization/eduevidence-report/themes/presentation.css +2 -2
- package/web/README.md +18 -0
- package/web/architecture.html +14885 -0
- package/web/index.html +53 -0
- package/web/studio/THIRD_PARTY_LICENSES.txt +146 -0
- package/web/studio/assets/index-B8tkF44Q.css +1 -0
- package/web/studio/assets/index-CQ6Keoyc.js +230 -0
- package/web/studio/config.json +1 -0
- package/web/studio/index.html +14 -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/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/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/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/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
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""check_protocol_alignment.py — Protocol five-way alignment gate.
|
|
3
|
+
|
|
4
|
+
Every scientific contract in this repository is declared in more than one
|
|
5
|
+
place: the workflow registry (engine/workflows.py), the capability registry
|
|
6
|
+
(engine/capabilities.py), the role registry (skill/roles/registry.yaml), the
|
|
7
|
+
role prompts (skill/agents/*.md), the stage briefs (skill/task-briefs/*.md),
|
|
8
|
+
the sub-skill recipes (skill/sub-skills/*/SKILL.md), the routing requirements
|
|
9
|
+
(integrations/agent_mcp.py) and the packaging manifest (packaging/*).
|
|
10
|
+
|
|
11
|
+
Drift between them is silent: a role can lose its brief, a capability can
|
|
12
|
+
exist with no recipe, a version can be bumped in one file only. This gate
|
|
13
|
+
makes that drift fail loudly. Stdlib only; a non-zero exit blocks CI.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
python3 scripts/check_protocol_alignment.py
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import re
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
26
|
+
sys.path.insert(0, str(ROOT))
|
|
27
|
+
|
|
28
|
+
from engine.capabilities import capability_registry # noqa: E402
|
|
29
|
+
from engine.versions import ENGINE_VERSION # noqa: E402
|
|
30
|
+
from engine.workflows import execution_stages, workflow_registry # noqa: E402
|
|
31
|
+
|
|
32
|
+
FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
|
|
33
|
+
|
|
34
|
+
#: Projection-layer capabilities sit outside the scientific stage model
|
|
35
|
+
#: (engine/workflows.py: Projection is not a scientific stage), so they are
|
|
36
|
+
#: owned by the projection brief rather than by a scientific role.
|
|
37
|
+
PROJECTION_CAPABILITIES = {"report_projection", "report_rendering"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def list_domains_from_registry() -> list[dict]:
|
|
41
|
+
"""Registered domains (evidencecore is the registry owner)."""
|
|
42
|
+
from engine.evidencecore import list_domains
|
|
43
|
+
|
|
44
|
+
return list_domains()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _frontmatter(path: Path) -> dict[str, str]:
|
|
48
|
+
"""Parse the flat key: value frontmatter used by skills and role prompts."""
|
|
49
|
+
match = FRONTMATTER_RE.match(path.read_text(encoding="utf-8"))
|
|
50
|
+
if not match:
|
|
51
|
+
return {}
|
|
52
|
+
fields: dict[str, str] = {}
|
|
53
|
+
for line in match.group(1).splitlines():
|
|
54
|
+
if line.strip().startswith("#") or ":" not in line:
|
|
55
|
+
continue
|
|
56
|
+
key, _, value = line.partition(":")
|
|
57
|
+
fields[key.strip()] = value.split("#", 1)[0].strip()
|
|
58
|
+
return fields
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _registry_roles() -> dict[str, dict]:
|
|
62
|
+
"""The role registry is a small fixed-shape YAML file; parse it narrowly."""
|
|
63
|
+
text = (ROOT / "skill" / "roles" / "registry.yaml").read_text(encoding="utf-8")
|
|
64
|
+
roles: dict[str, dict] = {}
|
|
65
|
+
current: str | None = None
|
|
66
|
+
for raw in text.splitlines():
|
|
67
|
+
if not raw.strip() or raw.strip().startswith("#"):
|
|
68
|
+
continue
|
|
69
|
+
if raw.startswith("roles:"):
|
|
70
|
+
continue
|
|
71
|
+
if raw.startswith("execution:"):
|
|
72
|
+
break
|
|
73
|
+
if re.match(r"^ [A-Za-z0-9_-]+:\s*$", raw):
|
|
74
|
+
current = raw.strip().rstrip(":")
|
|
75
|
+
roles[current] = {}
|
|
76
|
+
continue
|
|
77
|
+
if current and raw.strip().startswith("stages:"):
|
|
78
|
+
stages = raw.split(":", 1)[1].strip().strip("[]")
|
|
79
|
+
roles[current]["stages"] = [s.strip() for s in stages.split(",") if s.strip()]
|
|
80
|
+
elif current and raw.strip().startswith("capabilities:"):
|
|
81
|
+
caps = raw.split(":", 1)[1].strip().strip("[]")
|
|
82
|
+
roles[current]["capabilities"] = [c.strip() for c in caps.split(",") if c.strip()]
|
|
83
|
+
elif current and ":" in raw.strip():
|
|
84
|
+
key, _, value = raw.strip().partition(":")
|
|
85
|
+
roles[current][key.strip()] = value.strip()
|
|
86
|
+
return roles
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _merge_role(roles: dict[str, dict], name: str, stage: str, capability: str,
|
|
90
|
+
critical: bool | None = None, independence: bool | None = None) -> None:
|
|
91
|
+
entry = roles.setdefault(name, {"stages": [], "capabilities": []})
|
|
92
|
+
if stage not in entry["stages"]:
|
|
93
|
+
entry["stages"].append(stage)
|
|
94
|
+
for cap in capability.split("+"):
|
|
95
|
+
cap = cap.strip()
|
|
96
|
+
if cap and cap not in entry["capabilities"]:
|
|
97
|
+
entry["capabilities"].append(cap)
|
|
98
|
+
if critical is not None:
|
|
99
|
+
entry["critical_path"] = "true" if critical else "false"
|
|
100
|
+
if independence:
|
|
101
|
+
entry["independence_required"] = "true"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def check() -> list[str]:
|
|
105
|
+
errors: list[str] = []
|
|
106
|
+
|
|
107
|
+
stages = list(execution_stages())
|
|
108
|
+
scientific_stages = [s for s in stages if s != "projection"]
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------- briefs
|
|
111
|
+
brief_dir = ROOT / "skill" / "task-briefs"
|
|
112
|
+
briefs = {p.stem for p in brief_dir.glob("*.md")}
|
|
113
|
+
for stage in stages:
|
|
114
|
+
if stage not in briefs:
|
|
115
|
+
errors.append(f"stage {stage!r} has no task brief in skill/task-briefs/")
|
|
116
|
+
for extra in sorted(briefs - set(stages) - {"present"}):
|
|
117
|
+
errors.append(f"task brief {extra!r} does not map to a canonical stage")
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------- roles
|
|
120
|
+
registry = _registry_roles()
|
|
121
|
+
if not registry:
|
|
122
|
+
errors.append("skill/roles/registry.yaml declares no roles")
|
|
123
|
+
agent_files = {p.stem: p for p in (ROOT / "skill" / "agents").glob("*.md")}
|
|
124
|
+
if set(registry) != set(agent_files):
|
|
125
|
+
errors.append("role registry and skill/agents/*.md disagree: "
|
|
126
|
+
f"registry-only={sorted(set(registry) - set(agent_files))} "
|
|
127
|
+
f"agent-only={sorted(set(agent_files) - set(registry))}")
|
|
128
|
+
|
|
129
|
+
stage_owner: dict[str, str] = {}
|
|
130
|
+
for role, entry in registry.items():
|
|
131
|
+
for stage in entry.get("stages", []):
|
|
132
|
+
if stage in stage_owner:
|
|
133
|
+
errors.append(f"stage {stage!r} is owned by both {stage_owner[stage]!r} and {role!r}")
|
|
134
|
+
stage_owner[stage] = role
|
|
135
|
+
for stage in scientific_stages:
|
|
136
|
+
if stage not in stage_owner:
|
|
137
|
+
errors.append(f"stage {stage!r} has no owning role in the registry")
|
|
138
|
+
|
|
139
|
+
# Registry capabilities must be engine capability IDs: a free-text label
|
|
140
|
+
# here silently detaches the role from the capability it claims to run.
|
|
141
|
+
for role, entry in registry.items():
|
|
142
|
+
for cap in entry.get("capabilities", []):
|
|
143
|
+
if cap not in capability_registry():
|
|
144
|
+
errors.append(f"registry role {role!r} declares capability {cap!r}, "
|
|
145
|
+
"which is not in engine/capabilities.py")
|
|
146
|
+
|
|
147
|
+
# Independence is graded: the skeptic must come from a different model
|
|
148
|
+
# family, the method reviewer must be separated from the content judgement.
|
|
149
|
+
independence = {role: entry.get("independence_required")
|
|
150
|
+
for role, entry in registry.items() if entry.get("independence_required")}
|
|
151
|
+
if independence != {"skeptic": "different-model-family",
|
|
152
|
+
"method-reviewer": "role-separation"}:
|
|
153
|
+
expected = {"skeptic": "different-model-family", "method-reviewer": "role-separation"}
|
|
154
|
+
errors.append("independence_required must be "
|
|
155
|
+
f"{expected}, found {independence}")
|
|
156
|
+
|
|
157
|
+
# ------------------------------------------------- role prompt frontmatter
|
|
158
|
+
for role, path in agent_files.items():
|
|
159
|
+
fields = _frontmatter(path)
|
|
160
|
+
if fields.get("name") != role:
|
|
161
|
+
errors.append(f"{path.name}: frontmatter name {fields.get('name')!r} != filename {role!r}")
|
|
162
|
+
if fields.get("role_id") != role:
|
|
163
|
+
errors.append(f"{path.name}: missing role_id: {role}")
|
|
164
|
+
if not fields.get("capabilities"):
|
|
165
|
+
errors.append(f"{path.name}: missing capabilities declaration")
|
|
166
|
+
if not fields.get("output_contracts"):
|
|
167
|
+
errors.append(f"{path.name}: missing output_contracts declaration")
|
|
168
|
+
for banned in ("default_cli", "default_model"):
|
|
169
|
+
if banned in fields:
|
|
170
|
+
errors.append(f"{path.name}: {banned} must not be bound in the role prompt "
|
|
171
|
+
"(model/CLI choice is a user-confirmed routing decision)")
|
|
172
|
+
for token in ("claude-", "gpt-", "deepseek-", "glm-", "kimi-"):
|
|
173
|
+
if token in fields.get("recommended_reasoning", ""):
|
|
174
|
+
errors.append(f"{path.name}: recommended_reasoning must not contain a model name")
|
|
175
|
+
if role in registry:
|
|
176
|
+
declared = set(registry[role].get("capabilities", []))
|
|
177
|
+
prompt_caps = {c.strip() for c in fields.get("capabilities", "").split(",") if c.strip()}
|
|
178
|
+
unknown = {c for c in prompt_caps if c not in capability_registry()}
|
|
179
|
+
unmapped = {c for c in unknown if not c.startswith("(")}
|
|
180
|
+
if unmapped:
|
|
181
|
+
errors.append(f"{path.name}: capabilities not in the capability registry: {sorted(unmapped)}")
|
|
182
|
+
missing = {c for c in declared if c in capability_registry()} - prompt_caps
|
|
183
|
+
if missing:
|
|
184
|
+
errors.append(f"{path.name}: registry capabilities absent from the prompt: "
|
|
185
|
+
f"{sorted(missing)}")
|
|
186
|
+
if registry.get(role, {}).get("critical_path") == "true":
|
|
187
|
+
if fields.get("critical_path") != "true":
|
|
188
|
+
errors.append(f"{path.name}: registry marks this role critical_path but the "
|
|
189
|
+
"prompt does not declare critical_path: true")
|
|
190
|
+
|
|
191
|
+
# ----------------------------------------------- routing-side requirements
|
|
192
|
+
from integrations.agent_mcp import ROLE_REQUIREMENTS # noqa: E402
|
|
193
|
+
if set(ROLE_REQUIREMENTS) != set(registry):
|
|
194
|
+
errors.append("integrations.agent_mcp.ROLE_REQUIREMENTS and the role registry disagree: "
|
|
195
|
+
f"routing-only={sorted(set(ROLE_REQUIREMENTS) - set(registry))} "
|
|
196
|
+
f"registry-only={sorted(set(registry) - set(ROLE_REQUIREMENTS))}")
|
|
197
|
+
for role, reqs in ROLE_REQUIREMENTS.items():
|
|
198
|
+
for banned in ("default_cli", "default_model", "model", "cli"):
|
|
199
|
+
if banned in reqs:
|
|
200
|
+
errors.append(f"ROLE_REQUIREMENTS[{role!r}] must not bind {banned!r}")
|
|
201
|
+
wants_family = registry.get(role, {}).get("independence_required") == "different-model-family"
|
|
202
|
+
if wants_family and reqs.get("independence") != "different-model-family":
|
|
203
|
+
errors.append(f"ROLE_REQUIREMENTS[{role!r}] must require a different model family")
|
|
204
|
+
|
|
205
|
+
# ------------------------------------------------------------ capabilities
|
|
206
|
+
capabilities = capability_registry()
|
|
207
|
+
sub_skills = sorted(p for p in (ROOT / "skill" / "sub-skills").iterdir()
|
|
208
|
+
if p.is_dir() and not p.name.startswith("."))
|
|
209
|
+
if len(sub_skills) < 5:
|
|
210
|
+
errors.append(f"expected at least 5 sub-skills, found {len(sub_skills)}")
|
|
211
|
+
mapped: set[str] = set()
|
|
212
|
+
for skill_dir in sub_skills:
|
|
213
|
+
path = skill_dir / "SKILL.md"
|
|
214
|
+
if not path.is_file():
|
|
215
|
+
errors.append(f"sub-skill {skill_dir.name} has no SKILL.md")
|
|
216
|
+
continue
|
|
217
|
+
fields = _frontmatter(path)
|
|
218
|
+
if fields.get("name") != skill_dir.name:
|
|
219
|
+
errors.append(f"{skill_dir.name}/SKILL.md: name {fields.get('name')!r} != directory")
|
|
220
|
+
declared = fields.get("capability", "")
|
|
221
|
+
if not declared:
|
|
222
|
+
errors.append(f"{skill_dir.name}/SKILL.md: missing capability declaration")
|
|
223
|
+
continue
|
|
224
|
+
for cap in declared.split("+"):
|
|
225
|
+
cap = cap.strip().split()[0] if cap.strip() else ""
|
|
226
|
+
if cap and not cap.startswith("("):
|
|
227
|
+
mapped.add(cap)
|
|
228
|
+
if cap not in capabilities:
|
|
229
|
+
errors.append(f"{skill_dir.name}/SKILL.md: capability {cap!r} is not in "
|
|
230
|
+
"engine/capabilities.py")
|
|
231
|
+
# Every registered capability must be owned by at least one role, and every
|
|
232
|
+
# recipe capability must be one the engine actually registers.
|
|
233
|
+
owned: set[str] = set()
|
|
234
|
+
for entry in registry.values():
|
|
235
|
+
owned.update(entry.get("capabilities", []))
|
|
236
|
+
for cap in capabilities:
|
|
237
|
+
if cap not in owned and cap not in PROJECTION_CAPABILITIES:
|
|
238
|
+
errors.append(f"capability {cap!r} is registered in engine/capabilities.py "
|
|
239
|
+
"but no role owns it")
|
|
240
|
+
for cap in sorted(mapped):
|
|
241
|
+
if cap in capabilities and cap not in owned and cap not in PROJECTION_CAPABILITIES:
|
|
242
|
+
errors.append(f"sub-skill capability {cap!r} is owned by no role")
|
|
243
|
+
|
|
244
|
+
# ---------------------------------------------------------------- workflows
|
|
245
|
+
workflow_dir = ROOT / "skill" / "workflows"
|
|
246
|
+
workflow_files = {p.stem for p in workflow_dir.glob("*.md")}
|
|
247
|
+
registry_workflows = workflow_registry()
|
|
248
|
+
public = {w for w in registry_workflows if w != "full_research_cycle"}
|
|
249
|
+
normalised = {name.replace("_", "-") for name in public}
|
|
250
|
+
if not normalised <= workflow_files:
|
|
251
|
+
errors.append("workflows missing a runbook: "
|
|
252
|
+
f"{sorted(normalised - workflow_files)}")
|
|
253
|
+
skill_text = (ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
254
|
+
for name in public:
|
|
255
|
+
reference = f"skill/workflows/{name.replace('_', '-')}.md"
|
|
256
|
+
if reference not in skill_text:
|
|
257
|
+
errors.append(f"SKILL.md does not route to {reference}")
|
|
258
|
+
|
|
259
|
+
skill_md = root_skill_text = skill_text # alias for readability
|
|
260
|
+
for stage in scientific_stages:
|
|
261
|
+
if f"| {stage.capitalize()} " not in skill_md and stage not in skill_md:
|
|
262
|
+
errors.append(f"SKILL.md does not mention stage {stage!r}")
|
|
263
|
+
|
|
264
|
+
# ------------------------------------------------------------- taxonomy
|
|
265
|
+
# The registry is the authority for outcome tokens and their categories;
|
|
266
|
+
# the JSON Schemas carry static enums because JSON Schema cannot read a
|
|
267
|
+
# file at validation time. This dimension is what keeps the static enums
|
|
268
|
+
# honest: drift between a schema enum and the registry fails the gate.
|
|
269
|
+
from engine.taxonomy import (
|
|
270
|
+
all_tokens_ordered,
|
|
271
|
+
categories as taxonomy_categories,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
registered_tokens = set(all_tokens_ordered())
|
|
275
|
+
evidence_path = ROOT / "schemas" / "evidence.schema.json"
|
|
276
|
+
if evidence_path.is_file():
|
|
277
|
+
evidence_schema = json.loads(evidence_path.read_text(encoding="utf-8"))
|
|
278
|
+
enum = (evidence_schema.get("properties", {})
|
|
279
|
+
.get("outcome_type", {}).get("enum"))
|
|
280
|
+
if not isinstance(enum, list) or not enum:
|
|
281
|
+
errors.append("schemas/evidence.schema.json declares no outcome_type enum")
|
|
282
|
+
else:
|
|
283
|
+
missing = sorted(registered_tokens - set(enum))
|
|
284
|
+
extra = sorted(set(enum) - registered_tokens)
|
|
285
|
+
if missing:
|
|
286
|
+
errors.append(
|
|
287
|
+
"outcome_type enum is missing registered token(s): " + repr(missing))
|
|
288
|
+
if extra:
|
|
289
|
+
errors.append(
|
|
290
|
+
"outcome_type enum declares unregistered token(s): " + repr(extra))
|
|
291
|
+
|
|
292
|
+
# Every domain's ADOPT-gate categories must be categories it declares.
|
|
293
|
+
from engine.tribunal import primary_effect_categories
|
|
294
|
+
|
|
295
|
+
for domain_id in (d["id"] for d in list_domains_from_registry()):
|
|
296
|
+
declared = set(taxonomy_categories(domain_id))
|
|
297
|
+
try:
|
|
298
|
+
primary = primary_effect_categories(domain_id)
|
|
299
|
+
except ValueError as exc:
|
|
300
|
+
errors.append(f"domain {domain_id!r}: ADOPT gate misconfigured: {exc}")
|
|
301
|
+
continue
|
|
302
|
+
for category in primary:
|
|
303
|
+
if category not in declared:
|
|
304
|
+
errors.append(
|
|
305
|
+
f"domain {domain_id!r}: ADOPT gate names undeclared category "
|
|
306
|
+
+ repr(category))
|
|
307
|
+
|
|
308
|
+
# Every V2 outcome bucket must be a category some domain declares.
|
|
309
|
+
v2_outcome = ROOT / "schemas" / "v2" / "outcome.schema.json"
|
|
310
|
+
if v2_outcome.is_file():
|
|
311
|
+
v2_schema = json.loads(v2_outcome.read_text(encoding="utf-8"))
|
|
312
|
+
buckets = (v2_schema.get("properties", {})
|
|
313
|
+
.get("outcome_type", {}).get("enum"))
|
|
314
|
+
if isinstance(buckets, list) and buckets:
|
|
315
|
+
every = set()
|
|
316
|
+
for domain_id in (d["id"] for d in list_domains_from_registry()):
|
|
317
|
+
every.update(taxonomy_categories(domain_id))
|
|
318
|
+
orphan = sorted(set(buckets) - every)
|
|
319
|
+
# Reverse direction too: a category a domain declares but the V2
|
|
320
|
+
# contract omits would reject that domain's outcomes at the graph
|
|
321
|
+
# layer, which is exactly how policy was blocked.
|
|
322
|
+
missing_bucket = sorted(every - set(buckets))
|
|
323
|
+
if missing_bucket:
|
|
324
|
+
errors.append(
|
|
325
|
+
"schemas/v2/outcome.schema.json is missing category bucket(s) "
|
|
326
|
+
"that domains declare: " + repr(missing_bucket))
|
|
327
|
+
if orphan:
|
|
328
|
+
errors.append(
|
|
329
|
+
"schemas/v2/outcome.schema.json declares bucket(s) no domain "
|
|
330
|
+
"registers: " + repr(orphan))
|
|
331
|
+
|
|
332
|
+
# ---------------------------------------------------------------- versions
|
|
333
|
+
for relative in ("packaging/scp-manifest.json",):
|
|
334
|
+
path = ROOT / relative
|
|
335
|
+
if not path.is_file():
|
|
336
|
+
continue
|
|
337
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
338
|
+
declared = (data.get("skill") or {}).get("version")
|
|
339
|
+
if declared != ENGINE_VERSION:
|
|
340
|
+
errors.append(f"{relative}: skill.version {declared!r} != ENGINE_VERSION {ENGINE_VERSION!r}")
|
|
341
|
+
start_here = ROOT / "packaging" / "START-HERE.md"
|
|
342
|
+
if start_here.is_file():
|
|
343
|
+
text = start_here.read_text(encoding="utf-8")
|
|
344
|
+
major_minor = ".".join(ENGINE_VERSION.split(".")[:2])
|
|
345
|
+
if f"EduEvidence {major_minor}" not in text:
|
|
346
|
+
errors.append(f"packaging/START-HERE.md does not state EduEvidence {major_minor}")
|
|
347
|
+
|
|
348
|
+
# ------------------------------------------------------- docs must not drift
|
|
349
|
+
for doc in ("docs/architecture.md", "README.zh-CN.md", "docs/install-guide.md"):
|
|
350
|
+
path = ROOT / doc
|
|
351
|
+
if not path.is_file():
|
|
352
|
+
continue
|
|
353
|
+
text = path.read_text(encoding="utf-8")
|
|
354
|
+
for stale in ("752 个测试", "752 tests"):
|
|
355
|
+
if stale in text:
|
|
356
|
+
errors.append(f"{doc}: stale test count {stale!r}; use docs/metrics.json")
|
|
357
|
+
|
|
358
|
+
return errors
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def main() -> int:
|
|
362
|
+
print("[*] Checking protocol alignment across workflows, roles, capabilities and packaging...")
|
|
363
|
+
errors = check()
|
|
364
|
+
if errors:
|
|
365
|
+
print(f"[-] Protocol alignment FAILED with {len(errors)} error(s):", file=sys.stderr)
|
|
366
|
+
for error in errors:
|
|
367
|
+
print(f" • {error}", file=sys.stderr)
|
|
368
|
+
return 1
|
|
369
|
+
print("[+] Protocol alignment PASSED: stages, briefs, roles, prompts, capabilities, "
|
|
370
|
+
"sub-skills, workflows and packaging agree.")
|
|
371
|
+
return 0
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
if __name__ == "__main__":
|
|
375
|
+
sys.exit(main())
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""check_versioned_schemas.py - data-level validation for v3 / v4 / vNext.
|
|
3
|
+
|
|
4
|
+
CI validated this contract family by parsing the JSON only, so a schema could
|
|
5
|
+
drift arbitrarily (renamed required fields, changed enums, wrong types) and
|
|
6
|
+
stay green. Each family is exercised here by building a record from its own
|
|
7
|
+
dataclass or builder and validating that record against its schema, which is
|
|
8
|
+
the check that would have caught a real mismatch.
|
|
9
|
+
|
|
10
|
+
Stdlib only; exit 1 on any invalid record.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
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
|
+
|
|
23
|
+
def _validate(record, schema_rel, label):
|
|
24
|
+
from validate_schema import SchemaError, Validator
|
|
25
|
+
|
|
26
|
+
path = ROOT / schema_rel
|
|
27
|
+
schema = json.loads(path.read_text(encoding='utf-8'))
|
|
28
|
+
validator = Validator(schema, base_dir=path.parent)
|
|
29
|
+
try:
|
|
30
|
+
validator.validate(record, schema, label)
|
|
31
|
+
except SchemaError as exc:
|
|
32
|
+
return f'{label}: {exc}'
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main() -> int:
|
|
37
|
+
problems = []
|
|
38
|
+
checked = 0
|
|
39
|
+
|
|
40
|
+
# --- vNext: records built from their own dataclasses -------------------
|
|
41
|
+
try:
|
|
42
|
+
from engine.autoresearch.contracts import NegativeSearchRecord
|
|
43
|
+
|
|
44
|
+
record = NegativeSearchRecord(
|
|
45
|
+
negative_search_id='NSR-1', research_iteration_id='RI-1',
|
|
46
|
+
gap_id='GAP-1', queries=('a', 'b'), providers=('openalex',),
|
|
47
|
+
candidate_count=0, fetched_count=0, eligible_count=0)
|
|
48
|
+
payload = record.__dict__ if hasattr(record, '__dict__') else record
|
|
49
|
+
# JSON round-trip puts tuples back into their serialized form, which is
|
|
50
|
+
# what actually gets persisted; validate that shape.
|
|
51
|
+
payload = json.loads(json.dumps(payload, default=str))
|
|
52
|
+
err = _validate(payload, 'schemas/vNext/negative-search-record.schema.json',
|
|
53
|
+
'negative-search-record')
|
|
54
|
+
if err:
|
|
55
|
+
problems.append(err)
|
|
56
|
+
checked += 1
|
|
57
|
+
except Exception as exc: # import or construction failure is itself a defect
|
|
58
|
+
problems.append(f'negative-search-record: could not build record: {exc}')
|
|
59
|
+
|
|
60
|
+
# --- vNext: autoresearch lifecycle records ---------------------------
|
|
61
|
+
# Every schema in this family is exercised against the dataclass that
|
|
62
|
+
# actually produces it, so a renamed field, retyped value or narrowed
|
|
63
|
+
# enum fails CI instead of drifting silently. Only `negative-search-record`
|
|
64
|
+
# was wired before; the rest parsed as JSON and nothing more.
|
|
65
|
+
try:
|
|
66
|
+
import dataclasses
|
|
67
|
+
|
|
68
|
+
from engine.autoresearch.contracts import (ResearchBudget,
|
|
69
|
+
ResearchExperimentType,
|
|
70
|
+
ResearchIteration,
|
|
71
|
+
ResearchStrategy,
|
|
72
|
+
IterationStatus)
|
|
73
|
+
from engine.autoresearch.gap_priority import GapPriority
|
|
74
|
+
|
|
75
|
+
strategy = ResearchStrategy(
|
|
76
|
+
strategy_id='RST-1',
|
|
77
|
+
experiment_type=ResearchExperimentType.TARGETED_RETRIEVAL,
|
|
78
|
+
hypothesis='a targeted retrieval closes the retention gap',
|
|
79
|
+
expected_gain='one or more direct retention findings',
|
|
80
|
+
budget=ResearchBudget())
|
|
81
|
+
iteration = ResearchIteration(
|
|
82
|
+
iteration_id='RI-1', project_id='PRJ-1', base_graph_revision=1,
|
|
83
|
+
gap_id='GAP-1', strategy=strategy)
|
|
84
|
+
iteration.complete(IterationStatus.SEARCH_SATURATED)
|
|
85
|
+
iteration_payload = json.loads(json.dumps(iteration.as_dict(), default=str))
|
|
86
|
+
strategy_payload = json.loads(json.dumps(dataclasses.asdict(strategy), default=str))
|
|
87
|
+
strategy_payload['experiment_type'] = strategy.experiment_type.value
|
|
88
|
+
priority = GapPriority(
|
|
89
|
+
gap_id='GAP-1', dvi_band='HIGH', cost_band='LOW',
|
|
90
|
+
decision_material=True, drivers=('missing_retention',),
|
|
91
|
+
# The enum is the controller's own vocabulary (gap_priority.py and
|
|
92
|
+
# controller.py emit these three); it is not the experiment-type
|
|
93
|
+
# vocabulary. Using a plausible-looking but wrong value here is
|
|
94
|
+
# exactly the drift this check exists to catch.
|
|
95
|
+
next_research_mode='secondary_evidence_search', score=1)
|
|
96
|
+
priority_payload = json.loads(json.dumps(dataclasses.asdict(priority), default=str))
|
|
97
|
+
|
|
98
|
+
for label, payload, schema_rel in (
|
|
99
|
+
('research-strategy', strategy_payload, 'schemas/vNext/research-strategy.schema.json'),
|
|
100
|
+
('research-iteration', iteration_payload, 'schemas/vNext/research-iteration.schema.json'),
|
|
101
|
+
('gap-priority', priority_payload, 'schemas/vNext/gap-priority.schema.json'),
|
|
102
|
+
):
|
|
103
|
+
err = _validate(payload, schema_rel, label)
|
|
104
|
+
if err:
|
|
105
|
+
problems.append(err)
|
|
106
|
+
checked += 1
|
|
107
|
+
except Exception as exc:
|
|
108
|
+
problems.append(f'autoresearch lifecycle records: could not build record: {exc}')
|
|
109
|
+
|
|
110
|
+
# --- vNext: orchestration plan and task contracts ---------------------
|
|
111
|
+
try:
|
|
112
|
+
import dataclasses as _dc
|
|
113
|
+
|
|
114
|
+
from engine.orchestration import ExecutionPlanner, ExecutionMode
|
|
115
|
+
|
|
116
|
+
# A delegated plan needs a run id and a base revision; without them
|
|
117
|
+
# TaskSpec.validate_for_dispatch refuses to describe a dispatchable task.
|
|
118
|
+
plan = ExecutionPlanner().plan('M', run_id='RUN-1', base_revision=1)
|
|
119
|
+
plan_payload = json.loads(json.dumps(_dc.asdict(plan), default=str))
|
|
120
|
+
plan_payload['complexity'] = str(getattr(plan.complexity, 'value', plan.complexity))
|
|
121
|
+
err = _validate(plan_payload, 'schemas/vNext/execution-plan.schema.json',
|
|
122
|
+
'execution-plan')
|
|
123
|
+
if err:
|
|
124
|
+
problems.append(err)
|
|
125
|
+
checked += 1
|
|
126
|
+
|
|
127
|
+
delegated = [t for t in plan.tasks if t.execution_mode is ExecutionMode.DELEGATED]
|
|
128
|
+
if not delegated:
|
|
129
|
+
problems.append('execution plan has no delegated task to exercise task-spec')
|
|
130
|
+
else:
|
|
131
|
+
task_payload = json.loads(json.dumps(delegated[0].to_dict(), default=str))
|
|
132
|
+
err = _validate(task_payload, 'schemas/vNext/task-spec.schema.json', 'task-spec')
|
|
133
|
+
if err:
|
|
134
|
+
problems.append(err)
|
|
135
|
+
checked += 1
|
|
136
|
+
|
|
137
|
+
# A WorkerResult is built only by the lead process, which ignores
|
|
138
|
+
# worker self-attestation; exercise that real path, not a hand-built dict.
|
|
139
|
+
from engine.worker_result import validate_worker_output
|
|
140
|
+
result = validate_worker_output(delegated[0], {
|
|
141
|
+
'task_id': delegated[0].task_id,
|
|
142
|
+
'status': 'completed',
|
|
143
|
+
'staging_artifacts': [{'artifact_type': delegated[0].expected_outputs[0],
|
|
144
|
+
'summary': 'staged'}],
|
|
145
|
+
'validated': True,
|
|
146
|
+
'metrics': {},
|
|
147
|
+
'summary': 'ok',
|
|
148
|
+
})
|
|
149
|
+
err = _validate(json.loads(json.dumps(result.to_dict(), default=str)),
|
|
150
|
+
'schemas/vNext/worker-result.schema.json', 'worker-result')
|
|
151
|
+
if err:
|
|
152
|
+
problems.append(err)
|
|
153
|
+
checked += 1
|
|
154
|
+
except Exception as exc:
|
|
155
|
+
problems.append(f'orchestration records: could not build record: {exc}')
|
|
156
|
+
|
|
157
|
+
# --- vNext: autoevolve session and experiment records -----------------
|
|
158
|
+
try:
|
|
159
|
+
import dataclasses as _dc2
|
|
160
|
+
|
|
161
|
+
from engine.autoevolve.core import EvalSnapshot, SkillExperiment
|
|
162
|
+
|
|
163
|
+
snapshot = EvalSnapshot(
|
|
164
|
+
eval_id='EVAL-1', hard_gates_passed=True, science_score=0.8,
|
|
165
|
+
research_score=0.7, robustness=0.9, cost=0.0,
|
|
166
|
+
latency=12.5, complexity=0.5, repeats=3, noise_floor=0.01,
|
|
167
|
+
dev_passed=True, holdout_passed=True, adversarial_passed=True,
|
|
168
|
+
holdout_isolation_verified=False, eval_suite_hash='deadbeef')
|
|
169
|
+
snapshot_payload = json.loads(json.dumps(_dc2.asdict(snapshot), default=str))
|
|
170
|
+
err = _validate(snapshot_payload, 'schemas/vNext/eval-snapshot.schema.json',
|
|
171
|
+
'eval-snapshot')
|
|
172
|
+
if err:
|
|
173
|
+
problems.append(err)
|
|
174
|
+
checked += 1
|
|
175
|
+
|
|
176
|
+
experiment = SkillExperiment(
|
|
177
|
+
experiment_id='EXP-1', session_id='session-1',
|
|
178
|
+
parent_skill_revision='rev-1', hypothesis='a narrower prompt scores higher',
|
|
179
|
+
mutation_scope=('safe',), changed_files=('skill/agents/skeptic.md',),
|
|
180
|
+
candidate_commit='deadbeef', baseline_eval_id='EVAL-1',
|
|
181
|
+
candidate_eval_id='EVAL-2', protected_hash_before='h1',
|
|
182
|
+
protected_hash_after='h1', status='REJECT',
|
|
183
|
+
promotion_reason='no significant improvement')
|
|
184
|
+
experiment_payload = json.loads(json.dumps(_dc2.asdict(experiment), default=str))
|
|
185
|
+
err = _validate(experiment_payload, 'schemas/vNext/skill-experiment.schema.json',
|
|
186
|
+
'skill-experiment')
|
|
187
|
+
if err:
|
|
188
|
+
problems.append(err)
|
|
189
|
+
checked += 1
|
|
190
|
+
|
|
191
|
+
# The session report is the real runner payload (runner.py writes
|
|
192
|
+
# daily-report.json); validate that documented shape directly.
|
|
193
|
+
session_report = {
|
|
194
|
+
'run_tag': 'session-1', 'branch': 'autoevolve/session-1',
|
|
195
|
+
'experiments': 1, 'statuses': ['REJECT'], 'best_experiment_id': None,
|
|
196
|
+
'best_candidate_commit': None, 'cost': 0.0, 'wall_minutes': 1.0,
|
|
197
|
+
'plateau': False, 'stop_reason': 'completed',
|
|
198
|
+
'promotion': 'branch_only', 'branch_push_requested': False,
|
|
199
|
+
'branch_pushed': False, 'mutation_view': 'dev_only_context_isolation',
|
|
200
|
+
'holdout_isolation_verified': False, 'isolation_provider': 'none',
|
|
201
|
+
'isolation_reason': 'no os isolation provider available',
|
|
202
|
+
'eval_suite_hash': 'deadbeef', 'security_note': 'branch only',
|
|
203
|
+
'candidate_artifacts': 'local session state only; never auto-pushed',
|
|
204
|
+
}
|
|
205
|
+
err = _validate(session_report, 'schemas/vNext/autoevolve-session.schema.json',
|
|
206
|
+
'autoevolve-session')
|
|
207
|
+
if err:
|
|
208
|
+
problems.append(err)
|
|
209
|
+
checked += 1
|
|
210
|
+
except Exception as exc:
|
|
211
|
+
problems.append(f'autoevolve records: could not build record: {exc}')
|
|
212
|
+
|
|
213
|
+
# --- v3: the BENCHMARK run manifest (scripts/benchmark_v3.py) ---------
|
|
214
|
+
# Note: schemas/v3/run-manifest.schema.json describes the empirical
|
|
215
|
+
# benchmark harness record, not the run-workspace manifest. Validating the
|
|
216
|
+
# wrong producer is how a contract silently stops describing reality.
|
|
217
|
+
try:
|
|
218
|
+
fixture = sorted((ROOT / 'benchmarks' / 'empirical').glob('*/manifest.json'))
|
|
219
|
+
if fixture:
|
|
220
|
+
payload = json.loads(fixture[0].read_text(encoding='utf-8'))
|
|
221
|
+
err = _validate(payload, 'schemas/v3/run-manifest.schema.json',
|
|
222
|
+
f'run-manifest ({fixture[0].parent.name})')
|
|
223
|
+
if err:
|
|
224
|
+
problems.append(err)
|
|
225
|
+
checked += 1
|
|
226
|
+
else:
|
|
227
|
+
# benchmarks/empirical/ holds local empirical runs and is excluded
|
|
228
|
+
# from the submission package on purpose. Its absence means there is
|
|
229
|
+
# nothing to check in this checkout, not that the contract is broken.
|
|
230
|
+
print('note: no benchmarks/empirical/*/manifest.json in this checkout; '
|
|
231
|
+
'skipping the v3 run-manifest data check')
|
|
232
|
+
except Exception as exc:
|
|
233
|
+
problems.append(f'v3 run-manifest: {exc}')
|
|
234
|
+
|
|
235
|
+
# --- sanity: every declared schema still parses -----------------------
|
|
236
|
+
for family in ('v3', 'v4', 'vNext'):
|
|
237
|
+
for path in sorted((ROOT / 'schemas' / family).glob('*.json')):
|
|
238
|
+
try:
|
|
239
|
+
json.loads(path.read_text(encoding='utf-8'))
|
|
240
|
+
checked += 1
|
|
241
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
242
|
+
problems.append(f'{path.relative_to(ROOT)}: {exc}')
|
|
243
|
+
|
|
244
|
+
if problems:
|
|
245
|
+
print('ERROR: versioned schema validation failed', file=sys.stderr)
|
|
246
|
+
for item in problems:
|
|
247
|
+
print(f' {item}', file=sys.stderr)
|
|
248
|
+
return 1
|
|
249
|
+
print(f'versioned schemas OK ({checked} checks across v3 / v4 / vNext)')
|
|
250
|
+
return 0
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
if __name__ == '__main__':
|
|
254
|
+
sys.exit(main())
|
package/scripts/claim_audit.py
CHANGED
|
@@ -27,14 +27,19 @@ from pathlib import Path
|
|
|
27
27
|
|
|
28
28
|
from evidence_semantics import claim_relation
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
30
|
+
def _supported_outcomes() -> set[str]:
|
|
31
|
+
"""Every registered outcome token, read from the domain registry.
|
|
32
|
+
|
|
33
|
+
This was a hand-copied 20-token education list in three separate files;
|
|
34
|
+
it silently rejected policy tokens such as policy_effectiveness. The
|
|
35
|
+
registry (domains/<id>/outcome_taxonomy.json) is the single authority.
|
|
36
|
+
"""
|
|
37
|
+
from engine.taxonomy import all_tokens_ordered
|
|
38
|
+
|
|
39
|
+
return set(all_tokens_ordered())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
SUPPORTED_OUTCOMES = _supported_outcomes()
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
def load_records(path: Path) -> list[dict]:
|
|
@@ -40,6 +40,16 @@ import json
|
|
|
40
40
|
import sys
|
|
41
41
|
from pathlib import Path
|
|
42
42
|
|
|
43
|
+
# This module is also documented as a standalone command (SKILL.md), so it
|
|
44
|
+
# has to find the repository root on its own path; the orchestrator sets
|
|
45
|
+
# sys.path for it, which hid the missing import from that caller.
|
|
46
|
+
import sys as _sys
|
|
47
|
+
from pathlib import Path as _Path
|
|
48
|
+
_ROOT = _Path(__file__).resolve().parent.parent
|
|
49
|
+
if str(_ROOT) not in _sys.path:
|
|
50
|
+
_sys.path.insert(0, str(_ROOT))
|
|
51
|
+
|
|
52
|
+
|
|
43
53
|
from evidence_score import (CONFIDENCE_POLICY_VERSION,
|
|
44
54
|
decision_consistency_score, directness_score,
|
|
45
55
|
independent_samples, independent_studies)
|