The header, global constraints, file structure, the interface block every task
builds against, the group/ownership table and the merge notes. Task sections
are drafted next.
Also corrects the spec's architecture section: the engineering tools compose
the generic contract members the way gear/keyway/titleblock already do, so only
four operations (xref_attach, xref_manage, image_attach, drawing_export_dwg)
need contract members and an implementation per engine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
Two blocking review findings on the standards data layer:
* DIMTP and DIMTM were pinned to ("float", 0.0, 1e6), refusing every signed
deviation with a false "must be at least 0". AutoCAD documents both as
signed and the repo already relies on it: build_dim_override writes
dimtm=-0.022 for ISO 286 p6 and dimtp=-0.020 for f7, and
dimension_linear(fit=...) emits exactly those. A dimension style could
therefore never hold a double-positive or double-negative fit that the same
server draws correctly per dimension, and an agent obeying the message
would ship a sign-flipped tolerance. Both are now ("float", -1e6, 1e6); a
provenance test resolves the p6/f7/H7/h6 overrides build_dim_override
emits through resolve_dimstyle and pins the signs.
* 827719f made check_dim_value return DIMBLK/DIMBLK1/DIMBLK2 in the
canonical _OBLIQUE spelling and added ezdxf_arrowhead without telling
Task 10, whose plan helpers store the value verbatim and read the raw
string back - executed as written the headless drawing raised at render
and at doc.write, a user block passed the data layer and failed at save,
and ezdxf's OBLIQUE-in-session / _OBLIQUE-after-reload split made an
idempotent re-set report a change (spec section 8.3). This commit adds the
read-side twin reported_arrowhead (never raises; a foreign name passes
through), pins it with an in-session-equals-reloaded round trip, and edits
the plan: Task 9 Produces and Task 10 Consumes now name ARROWHEAD_BLOCKS,
canonical_arrowhead, ezdxf_arrowhead and reported_arrowhead; Task 10's
_write_dimstyle_values maps through ezdxf_arrowhead, _dimstyle_value and
_com_dimvar_for_report map through reported_arrowhead, a new
_require_arrowhead_blocks refuses a user block absent from doc.blocks
before dimstyle_create/modify write anything (and rewrites the name to the
block table's own spelling, measured to be what a reload reports), and two
headless tests witness the rendered INSERTs, the save/reopen spelling and
the refusal. The plan's own copy of the range table is corrected to match
the module (arrow kind, signed tolerances). The edited helper block was
executed against ezdxf 1.4.4 before being written into the plan.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
Skeleton (contracts, interfaces, file ownership, merge notes) plus the
drafted task steps for Track A hardening (H), styles (S), settings (C),
page setup and templates (P), environment (V) and the final tasks (F);
25 cross-group consistency findings reconciled before commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
scripts/smoke_pid_com.py runs the whole track against the live AutoCAD in
a new document. ComBackend.connect() is lazy by design, so the script reads
reachability from system_status()["connected"] rather than from connect()
as the plan sketched, and closes its document without saving unless --keep
is passed. On this machine (AutoCAD 2026, "25.1s (LMS Tech)") it exits 0:
5 nodes, 4 edges, 0 dangling, confidence_min 1.0, 0 critique issues,
2 XDATA chunks — identical to the headless engine on the same spec. The
first attempt exited 2 on a stale win32com gen_py cache (a package folder
holding only __pycache__); an environment fault, no backend change needed.
A/B against v1.5.1 published as ab-v1.5.1-vs-v1.6.0-dev.json: 26/29 ->
29/29, three miss -> pass, zero regressed. check_doc_numbers.py now names
the report and the two row labels in one place (AB_REPORT, AB_*_LABEL),
and README.md / benchmarks/README.md carry the new rows; the v1.5.0 and
v1.4.0 tables stay as history.
README: P&ID row, the showcase and catalogue images, the token table
re-measured (161 / 44,930 by default, 152 / 41,420 with TOOL_PACKS=core,
lean 50 / 14,007), every stale count reconciled (154 -> 166 registered,
149 -> 161 advertised, corpus 158/672 -> 162/731 by the same count, 19 ->
20 groups, 6 -> 8 resources, six-task matrix with pid_roundtrip), a
"Known limitations of the P&ID track" subsection, and the snapshot line at
this machine's 2423 collected tests (CI has not run on this tree; noted in
the changelog with the pre-existing 1369 vs 1363 drift).
CLAUDE.md: section 17, sections 5 and 7 counts, TOOL_PACKS, the two 1.6
contracts and the reader rules. Spec status -> implemented. The plan and
spec markdown were reformatted by ruff (fenced Python only) because
`ruff format --check .` was already red on them at HEAD and the gate
requires the whole tree clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
Two authoring slips in the valve x actuator composition:
- The diaphragm actuator's stem stopped at y=4 while the dome's flat base
sat at y=6, so the canonical control-valve symbol (PID_VALVE_*_DIAPHRAGM,
9 catalogue entries) shipped with its dome floating 2 mm above the stem.
- Actuators were placed at a fixed y=4 regardless of the body, so on the
needle body (top 5.6) the piston/motor/solenoid glyphs were drawn over
the knob and the hand bar across the needle's stem; the stem line(0,0,0,4)
also duplicated the needle's own axial line (and the butterfly disc).
Placement is now body-relative: `_body` reports `axis_top` (where its own
geometry on x=0 ends) and the stem runs from there to the glyph base at
`body_top + STEM_CLEARANCE`; the diaphragm dome's base is the stem top
(base + DIAPHRAGM_YOKE), and the FAIL attribute follows the glyph base.
The reference 8x4 bodies keep their published numbers (globe+diaphragm
signal at y=9.0, bbox top 9.0).
Tests: per-combination check that the stem starts at the body's derived
axis top, the glyph's lowest edge equals the stem top, the glyph clears
the body, and the signal port is the topmost edge; plus a catalogue-wide
no-duplicate-primitives gate. 33 combinations fail on the old geometry.
The plan's Step 3 snippet is synced with the fixed module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
Plan covers the prerequisite block_insert attribute fix (ezdxf wrapped the
INSERT in an anonymous *U block and lost its ATTRIBs), the two generic
contracts, the 43-symbol catalogue, routing, the ISA-5.1 grammar, the
graph reader, deliverables, six critique focuses, pid_from_spec, TOOL_PACKS,
resources, benchmark v4, and the live COM smoke.
Spec reconciled with the plan: 43 base symbols, FUNC/LOOP at +-2.0,
solid = DXF SOLID entity, shape/direction options, example spec uses an
actuated FCV-101 (a hand valve has no signal port), refine hints in
detail["hint"], preflight LAYER_SET_INTENT_MISMATCH, radial port matching
on the circle.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
Tool count 131 -> 154; 474 -> 1242 collected tests.
The release exists for two measured reasons. A 149-tool catalog costs a client
~40,300 tokens before it has asked for anything, and a drafter searching the way
drafters think -- FILLET, BPOLY, QSELECT, WBLOCK -- scored df=0 against the stock
index, because those words were simply not in the data.
Discovery and batching
- DISCOVERY_MODE=search replaces the catalog with search_tools + call_tool,
ranked over an AutoCAD command and synonym corpus (discovery/aliases.py) on
top of fastmcp's BM25 index. Measured: 40,305 -> 356 tokens advertised.
- cad_batch runs a step list in one round trip; fields= projects result-heavy
responses. The `core` profile is gone -- discovery replaced it.
Honesty debt (Tier 0)
- OCS/ECS: `extrusion` appeared nowhere in this codebase, so mirrored and
rotated geometry reported coordinates that were quietly wrong. backends/ocs.py
translates at the backend boundary; every tool coordinate is WCS on both
engines, and a tilted plane refuses rather than answering with a number that
looks fine.
- drawing_save_as(fmt="dwg") wrote DXF bytes to a .dwg path. drawing_audit never
repaired. analysis_measure_area shoelaced points the caller typed in.
- ezdxf calls gained a per-call timeout, so one hung call can no longer wedge a
server whose document lock is a single asyncio.Lock.
Architecture
- The 1854-line backend ABC is 19 contract modules; @capability(key, reason=)
makes optional methods declarative and a gate fails on undeclared holes.
- 8 layout/viewport tools, CHSPACE, CAD_PROGID escape hatch.
- Wave A: selection filters, boundary tracing, hatch depth, annotation objects,
measurement -- 13 tools of a planned 39. The REGION and 2D-boolean family was
cut on evidence: add_region() yields zero ACIS bytes and the greiner_hormann
substitute loses 28.2% of the area on a square with one semicircular edge.
Verified against a live AutoCAD 2026, which found four COM defects no amount of
reading would have caught -- AcadPViewport has no ViewCenter, and the line that
set it had been swallowed by a bare except since v1.4.
Benchmark v3 (15 tasks) and what it caught
- v2 scored this server 10/10, which carried no information: every task in it
exercised something the server was built around. Five tasks were added because
they can fail, and three did while being written -- a hatch could not be
measured though the refusal listed HATCH as measurable, the boundary tools and
the measure tool disagreed about the same shape (139.2177 vs 139.2699), and a
circle stored as two bulged vertices measured 0.0.
- Competitor reports are NOT re-scored. They are pinned v1.4 runs against v2 and
were never asked these five questions; the chart shows those rows `not run`.
- Correctness A/B: 21/24 -> 24/24, zero regressions.
Performance, stated plainly
The published v1.4 perf report was recorded on CPython 3.14 and the new one on
3.11.15, so the apparent 2-6x speedup is the interpreter, not this release.
Re-measured on one interpreter, v1.5.0 is 28% slower creating entities and 22%
slower on the 10k roundtrip. Fully attributed: EZDXF_CALL_TIMEOUT=0 restores
v1.4.0's exact numbers. That is the deadlock fix being paid for, the knob is
documented, and winning it back is on the 1.6 roadmap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>