release: 1.6.0

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
This commit is contained in:
Umut CAN
2026-09-25 00:17:17 +03:00
co-authored by Claude Opus 5.5
parent 7e7fe11a71
commit 17bf3a39ce
28 changed files with 3843 additions and 2033 deletions
+74 -32
View File
@@ -41,13 +41,14 @@ Unsupported tasks remain in the fixed-matrix denominator with score zero,
preventing partial implementations from receiving an inflated score.
```bash
python -m benchmarks.run_competitors --list --matrix v5
python -m benchmarks.run_competitors --server autocad-mcp-pro --backend ezdxf --matrix v5 --json
python -m benchmarks.run_competitors --list --matrix v7
python -m benchmarks.run_competitors --server autocad-mcp-pro --backend ezdxf --matrix v7 --json
python -m benchmarks.run_competitors --task table_mleader --task hatch_islands --json
```
The release-machine ezdxf self-check is **18/18 (100.0)** on the v5 matrix
(`--matrix v4` / `--matrix v3` / `--matrix v2` reproduce the earlier sets).
The release-machine ezdxf self-check is **21/21 (100.0)** on the v7 matrix
(`--matrix v6` … `--matrix v2` reproduce the earlier sets); the published
report is [`results/published/autocad-mcp-pro.json`](results/published/autocad-mcp-pro.json).
Repository stars and raw tool counts do not contribute to the score. Adapter
registration lives in `competitors.yaml`.
@@ -145,13 +146,13 @@ MCP stdio, against commits pinned in `competitors.yaml`:
| Server | Matrix | Pinned | Score | Pass | Coverage |
|---|---|---|---:|---:|---:|
| autocad-mcp-pro (reference) | v3 (15) | working tree | 100.0 | 15/15 | 100% |
| autocad-mcp-pro (reference) | v7 (21) | 1.6.0 tree | 100.0 | 21/21 | 100% |
| beiming183-cloud/AutoCAD-MCP | v2 (10) | `11f7c47e` | 50.0 | 5/10 | 50% |
| puran-water/autocad-mcp | v2 (10) | `95476a33` | 45.0 | 4/10 | 50% |
The competitor rows are the v1.4 runs, unchanged. Read the matrix column
before the score column: the denominators differ because the five v3 tasks
were added after those runs and have not been put to them.
before the score column: the denominators differ because the eleven tasks
added since v2 came after those runs and have not been put to them.
Method and boundaries:
@@ -205,38 +206,79 @@ python -m benchmarks.perf_suite --out benchmarks/results/published/perf-ezdxf.js
python -m benchmarks.render_perf_chart
```
### What the call-timeout guard costs
### v1.4.0 against v1.6.0, and what the call-timeout guard costs
The published v1.4 report was recorded on CPython 3.14 and the later ones on
3.11.15, so comparing those two files directly shows an apparent 2–6× speedup
that is almost entirely the interpreter. The comparison that means something is
the guard against no guard, on one interpreter, back to back. Three runs of
each configuration, median taken by hand — `perf_suite` executes each workload
once per invocation and cannot produce a median itself:
3.11.15, so comparing those files directly shows an apparent speedup that is
almost entirely the interpreter. The comparison that means something runs one
interpreter and one ezdxf over each tree's own code: 1.6.0's `perf_suite.py`
(v1.4.0 had none) against a v1.4.0 worktree, against 1.6.0, and against 1.6.0
with the guard off — five rounds, the three configurations alternating within
a round. Medians, from
[`results/published/perf-v1.4.0-vs-v1.6.0.json`](results/published/perf-v1.4.0-vs-v1.6.0.json),
which keeps every run:
| Workload | `EZDXF_CALL_TIMEOUT=0` | default (120 s) | Cost |
| Workload | v1.4.0 | v1.6.0 | v1.6.0, `EZDXF_CALL_TIMEOUT=0` |
|---|---:|---:|---:|
| `create_lines_2k` | 243.7 ms | 294.3 ms | **+20.8%** |
| `roundtrip_10k` | 1,709.6 ms | 2,049.2 ms | **+19.9%** |
| `create_lines_2k` | 244.5 ms | 306.1 ms (1.25×) | 284.9 ms (1.17×) |
| `roundtrip_10k` | 1,847.9 ms | 2,553.5 ms (1.38×) | 2,216.4 ms (1.20×) |
| `region_query_10k` | 665.2 ms | 712.2 ms (1.07×) | 783.9 ms (1.18×) |
| `premium_pass` | 150.6 ms | 65.3 ms (0.43×) | 69.9 ms (0.46×) |
v1.5.0 paid 28.6% on the first workload; v1.5.1 rebuilt the wrapper around a
single `asyncio.timeout_at` instead of two `asyncio.wait_for`s — `wait_for`
wraps its awaitable in a Task, so the old shape created two extra Tasks and two
timer handles per call — which recovered about a third of the overhead. The
rest is the Task the abandon-on-timeout design genuinely needs. The guard is
what stops one hung ezdxf call wedging a server whose document lock is a single
`asyncio.Lock`, so it stays; the knob is documented rather than hidden.
The guard explains part of the creation cost, not all of it. v1.5.1 rebuilt
the wrapper around one `asyncio.timeout_at` instead of two `asyncio.wait_for`s
(`wait_for` wraps its awaitable in a Task, so the old shape created two extra
Tasks and two timer handles per call), which recovered about a third of its
overhead; the rest is the Task the abandon-on-timeout design needs. With the
guard off, creation is still 1.17× and 1.20× v1.4.0's time. A cProfile of the
event-loop thread over the 2,000 creates counts **491,071** function calls on
1.6.0 against **319,038** on v1.4.0: per-call work added since 1.4 that 1.6
has not removed. The region-query row, whose guard-off median is the slower
one, is this machine's noise floor (about ±10 %).
These figures are hand-collected and have **no published artifact** —
`perf-ezdxf.json` records one default-configuration run only. Reproduce:
Roadmap criterion 7 — headless creation throughput back to v1.4.0's numbers —
is therefore **not met** and moves to 1.7. Earlier versions of this page said
that `EZDXF_CALL_TIMEOUT=0` returned creation to v1.4.0's numbers; measured
this way, it does not. The guard stays: it is what stops one hung ezdxf call
wedging a server whose document lock is a single `asyncio.Lock`.
`perf-ezdxf.json`, the chart's input, is the median of the five
default-configuration runs, with every run kept in `runs_ms`.
Reproduce (the v1.4.0 tree needs 1.6.0's `benchmarks/perf_suite.py` copied in):
```bash
for i in 1 2 3; do
python -m benchmarks.perf_suite --out /tmp/on-$i.json
EZDXF_CALL_TIMEOUT=0 python -m benchmarks.perf_suite --out /tmp/off-$i.json
git worktree add ../v140 v1.4.0 && cp benchmarks/perf_suite.py ../v140/benchmarks/
for i in 1 2 3 4 5; do
python -m benchmarks.perf_suite --json > main-$i.json
EZDXF_CALL_TIMEOUT=0 python -m benchmarks.perf_suite --json > off-$i.json
(cd ../v140 && python -m benchmarks.perf_suite --json) > v140-$i.json
done
```
The profile, run from each tree's root:
```python
import asyncio, cProfile, pstats
from backends.ezdxf_backend import EzdxfBackend
async def work():
b = EzdxfBackend()
await b.connect()
await b.drawing_new()
for i in range(2000):
await b.entity_create_line(0, i, 100, i)
await b.disconnect()
pr = cProfile.Profile()
pr.enable()
asyncio.run(work())
pr.disable()
print(pstats.Stats(pr).total_calls)
```
## Correctness A/B suite
`correctness_suite.py` is a set of deterministic, headless (ezdxf-backend) checks
@@ -253,15 +295,15 @@ python benchmarks/compare_versions.py v1.0.0 # vs a tag/ref
python benchmarks/compare_versions.py --json results.json
```
### Result — this branch (1.6.0-dev) vs v1.5.1 (release gate)
### Result — v1.6.0 vs v1.5.1 (release gate)
48 checks, ezdxf backend, one subprocess per check. Machine-readable report:
[`results/published/ab-v1.5.1-vs-v1.6.0-dev.json`](results/published/ab-v1.5.1-vs-v1.6.0-dev.json).
[`results/published/ab-v1.5.1-vs-v1.6.0.json`](results/published/ab-v1.5.1-vs-v1.6.0.json).
| Version | Checks passing | Pass rate | Fixed | Regressed |
|---------|----------------|-----------|-------|-----------|
| **v1.5.1** (baseline) | 26 / 48 | 54.2 % | — | — |
| **v1.6.0-dev** (this branch) | 48 / 48 | 100 % | 22 | **0** |
| **v1.6.0** (this release) | 48 / 48 | 100 % | 22 | **0** |
The twenty-two are all `miss → pass` — v1.5.1 has none of the methods. Five are track H: `scale_check_detects_schematic` (the synthetic P&ID is schematic against its layout; a uniform 2× copy is to scale with factor 2), `pipe_takeoff_rmst_exact` (eight routable runs, each exactly the rectilinear MST of its tags), `cable_takeoff_roundup` (9 / 17 / 19 / 10 m, the unstated power left empty), `diff_detects_known_edits` (five known edits and nothing else) and `topology_known_defects` (one near miss, one crossing, a clean T). Four are track F: `arch_junction_l_t_x` (L, T and X junction outlines against hand-computed corners and wall areas), `arch_room_area_net` (a 4 × 5 m room between 200 mm walls is labelled 20.00 m², not its 21.84 m² axis area), `arch_opening_cuts_wall` (both faces interrupted across a door, two jambs) and `arch_rooms_detect_foreign` (plain lines on a WALLS layer read as 20 and 15 m² at confidence 0.6, nothing written). Seven
are tracks B and G: `mech_part_roundtrip` (a part read back from its own
@@ -286,7 +328,7 @@ through the lister; `settings_layer_state_roundtrip`: save → change → restor
puts the layer table back and the state survives save/reopen as an XRECORD in
the file; `settings_pdf_mediabox_a3`: the plotted PDF's own `/MediaBox` reads
420 × 297 mm). Every one of the 26 checks it was released on still passes, so
the four tracks added capability without moving a number it had already earned.
the five tracks added capability without moving a number it had already earned.
### Result — v1.5.1 vs v1.5.0