The v1.6 roadmap in five tracks: 154 -> 247 registered tools in 28 groups. Evidence re-measured for the release: - correctness A/B against v1.5.1: 26/48 -> 48/48, 22 fixed, 0 regressed (ab-v1.5.1-vs-v1.6.0.json, replacing the -dev report); - the reference adapter on matrix v7: 21/21 (autocad-mcp-pro.json, which was the v3 run of 1.5); the published-report test follows it to v7; - the 100-point rubric re-scored per category, every withheld point named: 93 (the competitor rows are their July snapshots, not re-reviewed); - the perf lane as medians of five alternating runs, beside a v1.4.0 comparison on one interpreter (perf-v1.4.0-vs-v1.6.0.json). Two roadmap criteria are not green, and the changelog, the README and the roadmap say so: criterion 5 is withdrawn (track D, Plant 3D, dropped) and criterion 7 is not met (creation 1.25x / 1.38x v1.4.0's time, still 1.17x / 1.20x with the timeout off). Carried to 1.7. README rewritten for 1.6: what is new, the mechanical and architectural sheets, the rubric and task-matrix charts, the measured limits (including the four feature tables that ship empty), and the 1.7 roadmap. server.json describes the 1.6 surface and declares TOOL_PACKS. CLAUDE.md corrects the ISO 286 letter set and the empty feature tables. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
161 lines
5.9 KiB
Python
161 lines
5.9 KiB
Python
"""M9 — the v3 task matrix.
|
|
|
|
v2 returned 10/10 for this server. That was not a good sign: every task in it
|
|
tested something the server was designed around, so the score could only ever
|
|
have been full marks. The five tasks added here were chosen because they can
|
|
fail, and three of them did fail while being written — a hatch could not be
|
|
measured at all, and the boundary tools disagreed with the measurement tool
|
|
about the same shape.
|
|
|
|
Two assertions in this file exist purely to keep the matrix honest rather than
|
|
to test behaviour:
|
|
|
|
* the plan's ``tool_discovery_bilingual`` and ``measurement_massprops`` must
|
|
**not** appear, because neither capability shipped; and
|
|
* the published competitor reports must not carry the new task ids, because
|
|
those servers were never run against them and a zero we invented would read
|
|
exactly like a zero we measured.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from backends.base import UnsupportedCapabilityError
|
|
from benchmarks.adapters.autocad_mcp_pro import AutoCADMCPProAdapter
|
|
from benchmarks.adapters.base import BenchmarkAdapter
|
|
from benchmarks.run_competitors import run_tasks
|
|
from benchmarks.tasks_v2 import TASKS_V2
|
|
from benchmarks.tasks_v3 import MATRICES, NEW_TASKS_V3, TASKS_V3, task_by_id
|
|
|
|
PUBLISHED = Path(__file__).resolve().parents[1] / "benchmarks" / "results" / "published"
|
|
|
|
|
|
# ── shape of the matrix ─────────────────────────────────────────────────────
|
|
|
|
|
|
def test_v3_extends_v2_without_disturbing_it():
|
|
"""A renumbered or reweighted v2 task would silently invalidate the
|
|
published v1.4 reports, which are keyed by task id."""
|
|
assert TASKS_V3[: len(TASKS_V2)] == TASKS_V2
|
|
assert len(NEW_TASKS_V3) == 5
|
|
assert len(TASKS_V3) == 15
|
|
|
|
|
|
def test_every_task_id_is_unique():
|
|
ids = [task.task_id for task in TASKS_V3]
|
|
assert len(ids) == len(set(ids))
|
|
|
|
|
|
def test_the_two_capabilities_that_did_not_ship_are_not_advertised_as_tasks():
|
|
ids = {task.task_id for task in TASKS_V3}
|
|
|
|
assert "tool_discovery_bilingual" not in ids, (
|
|
"the Turkish normalizer was built and removed; this repo is English "
|
|
"throughout, so a bilingual task would be a benchmark for nothing"
|
|
)
|
|
assert "measurement_massprops" not in ids, (
|
|
"no centroid or moment-of-inertia tool ships — F2 was cut to entity_measure"
|
|
)
|
|
assert "tool_discovery" in ids
|
|
assert "measure_from_handle" in ids
|
|
|
|
|
|
def test_task_by_id_can_still_address_the_v2_matrix():
|
|
assert task_by_id("core_geometry", "v2").category == "creation"
|
|
with pytest.raises(KeyError):
|
|
task_by_id("hatch_islands", "v2")
|
|
|
|
|
|
def test_matrices_registry_exposes_both_sets():
|
|
assert MATRICES["v2"] == TASKS_V2
|
|
assert MATRICES["v3"] == TASKS_V3
|
|
|
|
|
|
# ── the reference adapter actually answers them ─────────────────────────────
|
|
|
|
|
|
def test_the_reference_adapter_implements_every_v3_task():
|
|
"""An unimplemented task scores `unsupported` and is easy to not notice."""
|
|
missing = [
|
|
task.task_id
|
|
for task in TASKS_V3
|
|
if not hasattr(AutoCADMCPProAdapter, f"_task_{task.task_id}")
|
|
]
|
|
assert missing == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("task", NEW_TASKS_V3, ids=lambda t: t.task_id)
|
|
async def test_each_new_task_passes_on_the_headless_backend(task, tmp_path):
|
|
report = await run_tasks(AutoCADMCPProAdapter(), [task], tmp_path, timeout=120.0)
|
|
|
|
result = report["results"][0]
|
|
assert result["status"] == "pass", result["message"]
|
|
assert result["metrics"], "a task that proves nothing reports no evidence"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_the_full_v3_matrix_runs_green(tmp_path):
|
|
report = await run_tasks(AutoCADMCPProAdapter(), TASKS_V3, tmp_path, timeout=180.0, matrix="v3")
|
|
|
|
assert report["summary"]["attempted"] == 15
|
|
assert report["summary"]["passed"] == 15, [
|
|
(item["task_id"], item["status"], item["message"]) for item in report["results"]
|
|
]
|
|
assert report["matrix"] == "v3"
|
|
|
|
|
|
# ── a capability refusal is not a failure ───────────────────────────────────
|
|
|
|
|
|
class RefusingAdapter(BenchmarkAdapter):
|
|
name = "refusing"
|
|
|
|
async def setup(self, artifact_dir):
|
|
return None
|
|
|
|
async def run_task(self, task):
|
|
raise UnsupportedCapabilityError("chspace", "no change-space member on this engine")
|
|
|
|
async def cleanup(self):
|
|
return None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_capability_refusal_is_reported_unsupported_not_failed(tmp_path):
|
|
"""`fail` and `unsupported` are different claims about a competitor.
|
|
|
|
"Your server got this wrong" and "your engine cannot reach this" are not
|
|
the same finding, and the runner used to collapse the second into the
|
|
first because a refusal is still an exception.
|
|
"""
|
|
report = await run_tasks(RefusingAdapter(), [NEW_TASKS_V3[0]], tmp_path)
|
|
|
|
result = report["results"][0]
|
|
assert result["status"] == "unsupported"
|
|
assert "chspace" in result["message"]
|
|
assert report["summary"]["unsupported"] == 1
|
|
assert report["summary"]["score"] == 0.0
|
|
|
|
|
|
# ── nothing was invented for the competitors ────────────────────────────────
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"filename", ["beiming183-autocad-mcp.json", "puran-water-autocad-mcp.json"]
|
|
)
|
|
def test_competitor_reports_carry_no_result_for_the_new_tasks(filename):
|
|
report = json.loads((PUBLISHED / filename).read_text(encoding="utf-8"))
|
|
scored = {item["task_id"] for item in report["results"]}
|
|
new_ids = {task.task_id for task in NEW_TASKS_V3}
|
|
|
|
assert scored & new_ids == set(), (
|
|
"these servers were pinned and run against the v2 matrix at v1.4; a "
|
|
"result here would be fabricated"
|
|
)
|
|
assert report.get("matrix", "v2") == "v2"
|