Files
codestable__codestable/tests/test_cs_skill_convergence.py
Wyatt Fang 30fecaae5e release(codestable): ship skill workflow v1.0.2 (#41)
收敛 CodeStable 主入口、runtime preflight、goal driver 与 skill 工程化评测闭环。

- 根 cs 对行动请求同轮直转,咨询与介绍保持非执行
- feature/issue/refactor/epic/docs 按仓库事实恢复,旧 stage skill 保留兼容薄壳
- repo-local runtime 支持版本检测、安全自动同步和显式 refresh-runtime
- 完成 Codex/Claude marketplace 1.0.2、升级文档和回归/评测证据

验证:215 tests passed;package/runtime/diff checks passed;独立 review 与 QA 无 unresolved blocking/important findings。
2026-07-10 15:23:56 +08:00

121 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""M2 autoresearch 护栏:收敛数学 + V_meta 分量 + provenance + 认知诚实机械校验。"""
from __future__ import annotations
import json
import re
import shutil
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / ".claude/skills/eval-cs-skill/scripts"
sys.path.insert(0, str(SCRIPTS))
import optimize as opt # noqa: E402
from _model import MEASURED, SOFT, tagged # noqa: E402
from config import ExperimentConfig # noqa: E402
EXPERIMENT = ROOT / "experiments/cs-code-review-001"
_TAG_RE = re.compile(r"\[(measured|soft|underpowered)")
# ---- 收敛数学 ----
def test_convergence_modes():
assert opt.convergence(0.9, 0.9) == (True, "standard")
assert opt.convergence(0.60, 0.85) == (True, "meta-focused")
assert opt.convergence(0.9, 0.5) == (False, "not-converged")
assert opt.convergence(0.5, 0.5) == (False, "not-converged")
# ---- V_instance tag 诚实 ----
def test_v_instance_tag_follows_constituents():
measured = {"recall": tagged(0.8, MEASURED)}
assert opt.v_instance(measured, {"recall": 1.0})["tag"] == MEASURED
mixed = {"recall": tagged(0.8, MEASURED), "judge_quality": tagged(0.5, SOFT)}
assert opt.v_instance(mixed, {"recall": 0.5, "judge_quality": 0.5})["tag"] == SOFT
# ---- V_meta 四分量 ----
def _synth_config(**kw):
base = dict(name="t", skill_under_test="cs-code-review", variants=["baseline"],
model_list=["m1", "m2"], k=5, harnesses=["mock"],
scorers=["planted_defect"], fixture_classes=["planted-defect"])
base.update(kw)
return ExperimentConfig(**base)
def test_v_meta_statistical_power(tmp_path):
(tmp_path / "fixtures/planted-defect").mkdir(parents=True)
for i in range(8):
(tmp_path / f"fixtures/planted-defect/p{i}.json").write_text("{}", encoding="utf-8")
# k>=5 且 n>=8 → statistical_power pass
_, comp = opt.v_meta_experiment(tmp_path, _synth_config(k=5), 5, [])
assert comp["statistical_power"] is True
# k<5 → fail
_, comp2 = opt.v_meta_experiment(tmp_path, _synth_config(k=1), 1, [])
assert comp2["statistical_power"] is False
def test_v_meta_confound_control():
# 用 judge 但 judge_model 在被测 model_list → fail
cfg_bad = _synth_config(scorers=["planted_defect", "llm_judge"], judge_model="m1")
assert opt._confound_ok(cfg_bad) is False
# judge_model 独立 → pass
cfg_ok = _synth_config(scorers=["planted_defect", "llm_judge"], judge_model="judge-x")
assert opt._confound_ok(cfg_ok) is True
# 不用 judge → pass无同源偏差
assert opt._confound_ok(_synth_config(scorers=["planted_defect"])) is True
def test_v_meta_oracle_calibration(tmp_path):
(tmp_path / "fixtures/planted-defect").mkdir(parents=True)
_, comp = opt.v_meta_experiment(tmp_path, _synth_config(), 5, [])
assert comp["oracle_calibration"] is False
(tmp_path / "calibration.md").write_text("x", encoding="utf-8")
_, comp2 = opt.v_meta_experiment(tmp_path, _synth_config(), 5, [])
assert comp2["oracle_calibration"] is True
# ---- provenance ----
def test_preregistration_uncommitted_fails(tmp_path):
# tmp 里的 hypotheses.md 不在 git → 未冻结 → False
(tmp_path / "hypotheses.md").write_text("H-x: r >= 0.8", encoding="utf-8")
assert opt._preregistered(tmp_path) is False
# 无 hypotheses.md → False
assert opt._preregistered(tmp_path / "nope") is False
# ---- 认知诚实机械护栏(对生成的 iteration 产物)----
def _run_optimize_in_tmp(tmp_path):
dst = tmp_path / "exp"
shutil.copytree(EXPERIMENT, dst, ignore=shutil.ignore_patterns("artifacts", "iteration-*.md", "results.md"))
opt.main(["--experiment", str(dst), "--max-iterations", "2"])
return dst
_ASSIGN_RE = re.compile(r"V_(instance|meta_experiment)\s*=") # 只查「赋值行」,不查 section 标题
_BARE_RE = re.compile(r"V_(instance|meta_experiment)\s*=\s*[0-9.]+\s*$") # 数值结尾、无 tag
def test_iterations_all_numbers_tagged(tmp_path):
dst = _run_optimize_in_tmp(tmp_path)
iters = sorted(dst.glob("iteration-*.md"))
assert iters, "应生成 iteration 文件"
for path in iters:
for line in path.read_text(encoding="utf-8").splitlines():
if _ASSIGN_RE.search(line):
assert _TAG_RE.search(line), f"{path.name} 赋值行缺 tag: {line}"
def test_no_bare_v_instance_decimal(tmp_path):
dst = _run_optimize_in_tmp(tmp_path)
for path in dst.glob("iteration-*.md"):
for line in path.read_text(encoding="utf-8").splitlines():
assert not _BARE_RE.search(line), f"{path.name} 裸数值(缺 tag: {line}"