From 2c2fba1459ec60b92b4378143aec05eca0bec507 Mon Sep 17 00:00:00 2001 From: Umutcan Edizsalan Date: Fri, 19 Jun 2026 08:37:08 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20release=20v1.1.0=20=E2=80=94=20accurate?= =?UTF-8?q?=20counts,=20changelog,=20audit=20report?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pyproject: version 1.0.0 -> 1.1.0 - README: 110 tools / 5 resources (was 87 / 6), 318 tests, new "Closed-Loop Quality Gate" highlight, Engineering + Premium tool categories, refreshed roadmap (1.1 shipped; 1.2 closed-loop moat; 1.3 ISO production). - CHANGELOG: comprehensive [1.1.0] entry (Added/Changed/Fixed/Security). - CLAUDE.md: drop removed CommandResult dataclass; ~110 tool count. - docs/analysis/03-AUDIT-VERIFICATION.md: the verified audit + fix plan. --- CHANGELOG.md | 76 +++++--- CLAUDE.md | 4 +- README.md | 23 ++- docs/analysis/03-AUDIT-VERIFICATION.md | 247 +++++++++++++++++++++++++ pyproject.toml | 2 +- 5 files changed, 317 insertions(+), 35 deletions(-) create mode 100644 docs/analysis/03-AUDIT-VERIFICATION.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ba6bf..d289bc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,36 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +_Next: 1.2 — closed-loop validation moat (scalar drawing-score, pre-plan clarification, iterative refiner + transaction-stack isolation)._ + +## [1.1.0] — 2026-06-19 + +Correctness, cross-backend parity, and an **enforced** quality gate, landed across four audited +sprints (see `docs/analysis/`). 318 tests, ruff-clean. + ### Added -- Security module (`security.py`) with path traversal protection and command injection prevention -- Centralized configuration (`config.py`) with environment variable support -- `.env.example` for environment variable documentation -- Path validation for all file operations (`drawing_open`, `drawing_save`, `drawing_export_*`) -- Command sanitization for `system_run_command` and `system_run_lisp` -- Ruff linting configuration in `pyproject.toml` -- Pre-commit hooks (`.pre-commit-config.yaml`) for automated code quality -- pytest-cov for test coverage reporting -- Comprehensive test suite: 95 tests across 7 test files - - `test_entity_creation.py` — 13 entity creation tests - - `test_drawing.py` — 8 drawing operation tests - - `test_dimensions.py` — 5 dimension tests - - `test_layer_block.py` — 12 layer and block tests - - `test_analysis.py` — 12 analysis and modification tests - - `test_security.py` — 29 security tests -- GitHub Actions CI workflow (lint + test on Python 3.11/3.12) -- README.md with full documentation -- CHANGELOG.md -- `.gitignore` +- **Premium drafting workflow** (shared across both backends): `drawing_plan`, `drawing_critique` + (ISO-128 focuses), `point_from_snap`, `drawing_apply_iso_layers`, `dimension_auto`, + `entity_select_smart`, `construction_xline` / `construction_clear`. +- **Deterministic geometry** for exact OSNAP coordinates: `point_intersection` + (line/line, line/circle, circle/circle) and `point_tangent` (external point → circle). +- **Engineering / deterministic CAD layer**: involute gear front view + section A-A, + DIN 6885 keyed bore, ISO A3 title block, and the 8-step `drawing_finalize` validator. +- **HTTP bearer-token auth**: `StaticTokenVerifier` wired into FastMCP when `MCP_AUTH_TOKEN` is set. +- Mocked-COM test harness (`tests/test_com_backend.py` + Sprint-3/4 suites) — COM logic is now + regression-tested on Linux CI without a live AutoCAD. +- `entity_create_mtext` rotation parameter; `dimension_auto` layer override; `bounding_box` / + ARC `length` / TEXT-MTEXT `rotation`+`char_height` on both backends; `BlockInfo.description`. +- Security module (`security.py`), centralized config (`config.py`), `.env.example`, path + validation, command/LISP sanitization, ruff config, pre-commit hooks, pytest-cov, GitHub Actions CI. ### Changed -- All silent `except Exception: pass` blocks now include logging -- Removed unused imports across all source files -- Renamed ambiguous variable `l` to `lyr` in layer iteration code -- Import sorting organized with ruff isort rules +- **`drawing_finalize` now enforces the premium critique** in addition to the structural validator: + leftover construction geometry, non-ISO-128 lineweights, untrimmed corners, duplicate entities, + and dimension overlap block the gate (was advisory-only). `strict_critique=True` fails on warnings too. +- **`drawing_save_as` derives the on-disk format from the file extension** — `part.dxf` writes DXF, + not DWG. ezdxf refuses to mislabel a `.dwg` file. +- Dimension and construction layers resolve from the active layer set (iso13567 → `M-DIMEN-T-N` / + `M-CONST-E-N`, not the hardcoded `DIM` / `CONSTRUCTION`). +- Premium meta-tools lifted into the shared `AutoCADBackend` base class (single source of truth). +- Dead code removed (`section.py`, `generate_tooth_profile`, `CommandResult`, `set_layer_active`, …). ### Fixed -- `_detect_autocad_running` now logs detection failures instead of silently swallowing them -- Resource functions now log errors before returning error JSON +- ezdxf `dimension_aligned` / `dimension_angular` raised `TypeError` (wrong ezdxf 1.4 args) — fixed. +- Full-circle polar array placed a duplicate copy over the original — fixed (divisor = count). +- `entity_offset` ignored `side_x`/`side_y` on both backends — now honored (and COM no longer leaks extras). +- COM `entity_create_hatch` built an associative hatch then deleted its boundary — now non-associative. +- Lineweight mm-vs-hundredths truncation wiped ISO-128 weights — fixed via `normalize_lineweight`. +- **`view_screenshot` / `drawing_export_pdf` could SIGSEGV** (matplotlib GUI backend in a worker thread) + — now render headless via `Agg` (`Figure` + `FigureCanvasAgg`). +- `entity_select_smart` `length_range` silently rejected all ARCs (no `length`) — fixed. +- Gear tooth profile self-overlapped for high tooth counts (z ≥ ~42); section view drew duplicate + bore lines; validator keyway heuristic was a permanent false-negative — all fixed. +- COM `system_run_lisp` always reported `"nil"`; `system_set_variable` didn't coerce numeric sysvars + — fixed. `system_about` tool groups / `_registered_tool_count` no longer drift or surface `-1`. +- `drawing_new` bootstrap failures now surface as `degraded` instead of reporting success. + +### Security +- HTTP remote-bind guard now fires on **every** launch path (including `fastmcp run server.py:mcp`), + not only the `__main__` block — closing an anonymous-remote-bind gap. +- AutoLISP allowlist bypass-vector regression tests (newline injection, symbol aliasing, `vla*`/`acet-*`, + `c:` custom commands). +- COM apartment leak bounded (`CoUninitialize` on teardown); transaction commit/rollback and + `system_run_lisp` now respect the CMDACTIVE guard. ## [1.0.0] — 2026-03-01 diff --git a/CLAUDE.md b/CLAUDE.md index e31741e..1f05292 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -AutoCAD MCP Pro is a FastMCP 3.0 server that exposes 87 tools, 6 resources, and 5 prompt templates for AutoCAD automation. (The exact tool count is reported dynamically by `system_status` / `system_about` — never hardcode it.) It runs with a dual-engine architecture: a live COM backend (Windows/AutoCAD required) and a headless ezdxf backend (works anywhere). +AutoCAD MCP Pro is a FastMCP 3.0 server that exposes ~110 tools, 5 resources, and 5 prompt templates for AutoCAD automation. (The exact tool count is reported dynamically by `system_status` / `system_about` — never hardcode it.) It runs with a dual-engine architecture: a live COM backend (Windows/AutoCAD required) and a headless ezdxf backend (works anywhere). ## Running the Server @@ -53,7 +53,7 @@ Uses `pytest-asyncio` for async test support. The server uses a strategy pattern with an abstract base: -- `backends/base.py` — `AutoCADBackend` ABC defining the full interface + shared dataclasses (`EntityInfo`, `LayerInfo`, `BlockInfo`, `DrawingInfo`, `CommandResult`) +- `backends/base.py` — `AutoCADBackend` ABC defining the full interface + shared dataclasses (`EntityInfo`, `LayerInfo`, `BlockInfo`, `DrawingInfo`) - `backends/ezdxf_backend.py` — `EzdxfBackend`: file-based DXF operations using the `ezdxf` library. All sync ezdxf calls are wrapped with `asyncio.to_thread` via `_async()`. Transactions are implemented as full DXF snapshots on an `_undo_stack`. - `backends/com_backend.py` — `ComBackend`: live AutoCAD control via `pywin32` COM. All COM calls are routed through a `ThreadPoolExecutor` with a single thread to satisfy AutoCAD's STA (Single-Threaded Apartment) COM requirement. diff --git a/README.md b/README.md index 06b1dd3..3ab3b02 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Production-grade AutoCAD automation for the Model Context Protocol.** Dual-engine. Battle-tested. Model-agnostic. -87 tools · 6 resources · 5 prompt templates · COM + ezdxf backends · Python 3.11+ · MIT +v1.1.0 · 110 tools · 5 resources · 5 prompt templates · COM + ezdxf backends · Python 3.11+ · MIT --- @@ -15,7 +15,7 @@ My day job at **Anka-Makine** revolves around intensive AutoCAD work — product After months of leaning on this server inside my own daily workflow — and watching it shave hours off my week without a single misstep on critical drawings — I decided it deserved a public release. It works. It is fast. It stays out of the way. -The model on the other end of the wire does not matter. The Model Context Protocol is the contract; AutoCAD MCP Pro is one well-typed implementation of that contract. Any MCP-aware client — and the LLM behind it — sees the same 87 tools. +The model on the other end of the wire does not matter. The Model Context Protocol is the contract; AutoCAD MCP Pro is one well-typed implementation of that contract. Any MCP-aware client — and the LLM behind it — sees the same 110 tools. I will keep this repository actively maintained as my own use of it evolves, and **a public benchmark suite is on the way** — I want to give you numbers, not adjectives. @@ -63,7 +63,7 @@ AutoCAD MCP Pro takes a different stance: - **ezdxf backend**: headless DXF file operations powered by [ezdxf](https://github.com/mozman/ezdxf). Works on every platform, ideal for batch workloads and CI pipelines. - Automatic backend selection, with a clean override via `AUTOCAD_MCP_BACKEND`. -- **87 Tools, 11 Categories** +- **110 Tools, 12 Categories** - Drawing management — `drawing_new`, `drawing_open`, `drawing_save`, `drawing_save_as`, `drawing_export_dxf`, `drawing_export_pdf`, `drawing_purge`, `drawing_audit`, `drawing_undo`, `drawing_redo`, `drawing_close` - Entity creation — line, circle, arc, polyline, rectangle, text, mtext, hatch, spline, ellipse, point, block reference, batch create - Dimensions — linear, aligned, angular, radius, diameter @@ -75,14 +75,21 @@ AutoCAD MCP Pro takes a different stance: - Transactions — begin, commit, rollback, with disk-backed snapshots - System — status, get/set variables, run command, run AutoLISP, about - Templates and validation — apply standard layer templates, validate drawings against rule sets + - Engineering / deterministic CAD — involute gear front view + section A-A, DIN 6885 keyed bore, ISO A3 title block, and the 8-step `drawing_finalize` gate + - Premium drafting workflow — `drawing_plan`, deterministic OSNAP (`point_from_snap` / `point_intersection` / `point_tangent`), `drawing_apply_iso_layers`, `dimension_auto`, `entity_select_smart`, `drawing_critique`, `construction_*` - **Production-Grade Plumbing** - FastMCP 3.0 lifespan-managed backend singleton - Middleware stack: error handling, audit logging, timing, request logging - Structured progress reports for long-running operations (`drawing_open`, exports, batch ops) - - 104 tests across drawing, entity, dimension, layer, block, analysis, batch/template, security suites + - 318 tests across drawing, entity, dimension, layer, block, analysis, batch/template, engineering, premium, mocked-COM, and security suites - Ruff-clean codebase, GitHub Actions CI +- **Closed-Loop Quality Gate** + - `drawing_finalize` runs **both** the 8-step structural validator **and** the premium ISO-128 critique (lineweights, layer colors, untrimmed corners, duplicate entities, leftover construction, dimension overlap) — leftover scaffolding or a non-standard lineweight blocks the gate instead of slipping through. + - Deterministic geometry: `point_from_snap` / `point_intersection` / `point_tangent` give the agent exact OSNAP coordinates instead of guessed ones — the single biggest source of LLM drawing errors. + - Cross-backend parity: the same `EntityInfo.properties` contract, save-format-from-extension, offset side selection, and ARC length on **both** engines. + - **Security-First Defaults** - Path traversal & system-directory protection - AutoLISP allowlist with regression tests for known bypass patterns (`vla-SendCommand`, `acet-*`, alias tricks, `funcall` indirection, `c:` custom commands, error-handler hijacks) @@ -117,7 +124,7 @@ pip install -e ".[full]" python server.py ``` -That is it. The server starts in STDIO mode and your MCP client will discover all 87 tools. +That is it. The server starts in STDIO mode and your MCP client will discover all 110 tools. For headless / CI workflows: @@ -363,8 +370,10 @@ autocad-mcp/ ## Roadmap - [x] **1.0** — Initial public release -- [x] **1.1** — Security hardening (LISP allowlist, transaction snapshots on disk, COM timeouts, HTTP bind guard, dynamic tool count) -- [ ] **1.2** — Public benchmark suite (`benchmarks/` with reproducible runner) +- [x] **1.1** — Correctness, cross-backend parity & the enforced quality gate: `drawing_finalize` now runs the premium ISO-128 critique; deterministic geometry (`point_intersection`/`point_tangent`); dimension/save-format/polar-array/offset fixes across both engines; COM robustness (CoUninitialize, transaction & LISP guards); security hardening (HTTP bind guard on every launch path, LISP-allowlist bypass tests); mocked-COM test harness. **318 tests.** +- [ ] **1.2** — Closed-loop validation moat: scalar drawing-score, pre-plan clarification pass, and an iterative critique→repair→re-critique refiner (with dedicated transaction-stack isolation) +- [ ] **1.3** — ISO production: 2D GD&T (ISO 1101) feature-control frames, ISO 129 dimension tolerances/fits, ISO-25 dimension styles +- [ ] **1.4** — Public benchmark suite (`benchmarks/` with reproducible runner) - [ ] **1.3** — Backend `capabilities()` map and per-tool enablement (e.g. hide `system_run_command` automatically on ezdxf) - [ ] **1.4** — Module split for `server.py` and a clean `services/` orchestration layer - [ ] **1.5** — Windows CI matrix with mocked COM backend; full coverage report diff --git a/docs/analysis/03-AUDIT-VERIFICATION.md b/docs/analysis/03-AUDIT-VERIFICATION.md new file mode 100644 index 0000000..b485143 --- /dev/null +++ b/docs/analysis/03-AUDIT-VERIFICATION.md @@ -0,0 +1,247 @@ + + +# AUDIT + VERIFICATION — AutoCAD MCP Pro (post Sprint-1) + +This audit (a) verifies the 4 Sprint-1 commits, (b) re-confirms which of the 40 baseline +diagnostics are still open after those commits, and (c) adds fresh-sweep findings — then +sequences everything into a fix plan. Line numbers reflect the **current** working tree +(branch `fix/http-auth`, 233 tests green, ruff clean). + +--- + +## A. Verdict on the 4 Sprint-1 commits + +| Commit | Item | Verdict | +|---|---|---| +| `2b80d00` I16 | **N1** COM hatch associativity→False | ✅ correct & complete | +| | **R9** polar-array 360° divisor | ✅ correct (divisor=count on full ring; matches COM total-instance count) | +| | **R12 / R19** ezdxf aligned/angular dim API | ✅ correct (matches ezdxf 1.4.3 sig; xfail removed, passes strict) | +| | dead-code removal (section.py, generate_tooth_profile, CommandResult, _ACI_NAMES, set_layer_active, ensure_linetypes, _batch_mode) | ✅ clean — zero dangling Python refs; only docs still mention them | +| | **N12 / N13 / N14** dead helpers | ✅ removed | +| | **R13** entity_offset side_x/side_y | ⚠️ **incomplete** — honored in ezdxf only; COM still ignores it | +| | **R18** drawing_new bootstrap surfacing | ⚠️ **incomplete** — only a *total* exception → `degraded`; per-item `failed:` swallowed → still `bootstrap.ok=True` | +| | **N2** save format | ⚠️ **partial** — `drawing_finalize` derives fmt from extension ✅, ezdxf refuses `dwg` ✅, but the standalone `drawing_save_as` tool still defaults `fmt='dwg'` and ignores the extension | +| `0f1f051` I9 | **point_intersection / point_tangent** | ✅ math fully correct (LINE-LINE, LINE-CIRCLE, CIRCLE-CIRCLE, tangent perpendicularity verified numerically); 13 tests assert real invariants | +| `82c9fe0` I12 | StaticTokenVerifier wiring | ✅ `auth=_auth` correctly passed; token never logged; reject works at runtime | +| | **Guard enforcement** | ⚠️ **gap** — `_validate_http_bind` runs only in `__main__`; the documented `fastmcp run` import path skips it (see NEW-AUTH-1) | +| `f2f3fa6` I19 | `_safe_send_command` coverage | ✅ meaningful (var-restore, timeout, new-handle faithful) | +| | normalize_lineweight table | ⚠️ omits the ambiguous bare ints `1`/`2`; COM transactions + CMDACTIVE guard still untested | + +**Bottom line:** the Sprint-1 fixes that landed are correct where they landed. The recurring +defect pattern is **one-backend / partial fixes**: R13 (ezdxf-only), R18 (total-failure-only), +N2 (finalize-only), I12 guard (`__main__`-only). Cross-backend parity is the theme of the open work. + +--- + +## B. Confirmed open issues (ranked) + +Severity recalibrated by impact-on-common-path. `★` = re-confirmed by direct code read in this session. + +### HIGH + +- **NEW-screenshot-1 ★** — `view_screenshot` / `drawing_export_pdf` build a `matplotlib.pyplot` + figure **inside an `asyncio.to_thread` worker without forcing the `Agg` backend** + (`ezdxf_backend.py:1420-1444`). On a host whose default MPL backend is GUI (Qt/Tk) this can + **hard-crash the process (SIGSEGV)** — this is the real cause of the `pytest` warning + "Starting a Matplotlib GUI outside of the main thread will likely fail." `drawing_finalize` + calls `view_screenshot`, so finalize can take down the server. + **Fix:** render via `matplotlib.figure.Figure` + `FigureCanvasAgg` (no `pyplot` global state), + or `matplotlib.use("Agg")` before the pyplot import. Headless-testable. + +- **I15 ★** — `drawing_finalize` (`server.py:2333`) runs **only** `DrawingValidator().run()` and + **never** `backend.drawing_critique` / `run_critique`. The 6 premium critique focuses + (iso128, layer_color, untrimmed_corner, duplicate_entities, construction_left, dim_overlap) + are **not part of the finalize gate** — a drawing passes finalize with untrimmed corners, + non-ISO-128 lineweights, leftover construction scaffolding, duplicate lines. The product's + entire differentiator ("critique-then-finalize", CLAUDE.md) is advisory, not enforced. + **Fix:** run `run_critique(focus=None)` inside `drawing_finalize`, merge into the report, + gate (or warn) on critique errors. This is the moat. + +- **NEW-AUTH-1 ★** — `_validate_http_bind(host)` is called **only** in the + `if __name__ == "__main__"` block (`server.py:2940`). The documented launch command + `fastmcp run server.py:mcp --transport http --host 0.0.0.0` *imports* the module and never + runs `__main__`, so the remote-bind guards (refuse remote bind without `ALLOW_REMOTE_HTTP`, + refuse without `MCP_AUTH_TOKEN`) **never fire** under the recommended launch. With the token + unset, the server binds to `0.0.0.0` anonymously with nothing to stop it. + **Fix:** enforce the bind check at import time / in the FastMCP lifespan, not only in `__main__`. + +- **NEW-gear-roottooth + R21 ★** — `generate_involute_flank` (`gear.py:40-50`) prepends + `(root_r, 0)` while the involute starts at `(base_r, 0)` — both on y=0, so for `root_r < base_r` + the root→base join is a **straight radial segment, not a trochoid** (R21). Worse, for + `root_r ≥ base_r` (teeth ≥ ~42 @20° PA, within the teeth≥6 API limit) no root point is added and + **the flank starts at `base_r` *inside* the root circle** → the tooth profile dips below the root + arc and the closed outline **self-overlaps** (z=50 reproduced: outline points inside root_r). + **Fix:** clamp the flank start to `max(base_r, root_r)`; skip/relocate the root arc when + `root_r ≥ base_r`; ideally a true trochoid below the base circle. + +### MED + +- **R4 ★** — `dim_overlap` critique is a **guaranteed no-op**: `_check_dim_overlap` + (`critique.py:199-201`) reads `text_position`/`defpoint`/`insertion_point` which **no backend + populates** for dimensions; it also filters `entity_list(layer_filter="DIM")` (`critique.py:192`) + which misses the iso13567 dim layer. **Fix:** populate a dim reference point in both extractors + (or fall back to bbox center), and resolve the dim layer from the active set. + +- **N4** — `dimension_auto` hardcodes `layer="DIM"` (`base.py:948/962/970/977`); construction ops + hardcode `"CONSTRUCTION"` (`base.py:869/880`); `_check_construction_left` also hardcodes + `"CONSTRUCTION"` (`critique.py:99`). For the **iso13567** set (dim layer `M-DIMEN-T-N`, + construction `M-CONST-E-N`) dims land on the wrong layer and leftover scaffold is never caught. + **Fix:** resolve dim/construction layer from the active `LAYER_SET_REGISTRY` keyed on `PlanSpec`. + +- **R13-COM / NEW-offset-1 ★** — `ComBackend.entity_offset` (`com_backend.py:860-874`) accepts + `side_x/side_y` but never reads them (`ent.Offset(distance)` only) → COM offsets to a + backend-decided side while ezdxf honors the hint. Same tool, opposite results. + Also: only `offset_ent[0]` is kept; extra entities `Offset` returns leak. **Fix:** honor side in COM. + +- **N2 (tool)** — standalone `drawing_save_as` MCP tool defaults `fmt='dwg'` and doesn't derive from + the extension, so `drawing_save_as('part.dxf')` raises on ezdxf / writes DWG bytes into a `.dxf` + name on COM. **Fix:** derive fmt from the path extension in the tool too (mirror finalize). + +- **R5** — COM executor calls `CoInitialize()` but **`CoUninitialize` appears nowhere**; on every + `SendCommand` timeout the stuck STA thread + its COM apartment leak (a fresh executor is built), + and `disconnect()` uses the same `wait=False`. Long live-AutoCAD sessions exhaust resources. + **Fix:** worker-side `try/finally` `CoUninitialize`; bound abandoned executors. + +- **R16** — COM `transaction_begin/commit/rollback` set `_transaction_active` *after* the await + with no `try/finally` (`com_backend.py:1398/1407/1417`) → stale flag on error; rollback's raw + `_UNDO B` (`:1414`) bypasses the CMDACTIVE guard. **Fix:** `try/finally`; route via `_safe_send_command`. + +- **R23 / N6** — `system_run_lisp` (`com_backend.py:1483-1488`) sends raw LISP with **no CMDACTIVE + guard** (deadlock on a prompting form) and captures the **void** `SendCommand` return so it + **always reports `result:"nil"`**. **Fix:** route through `_safe_send_command`; capture via + `(setvar "USERS1" …)` + `GetVariable`, or drop the bogus `result`. + +- **N5** — `EntityInfo.properties` shape diverges: ezdxf never sets `bounding_box` (COM always does); + ezdxf TEXT sets `rotation` / MTEXT `char_height`, COM sets neither. Clients break on one backend. + **Fix:** pick one contract; enforce both ways. + +- **N3** — `entity_select_smart` `length_range` rejects **all ARCs** (no extractor sets ARC `length`). + **Fix:** populate ARC `length = radius·|Δangle|` or compute on the fly. + +- **NEW-dimauto-baseline-rotation** — `dimension_auto` baseline mode (`base.py:952-963`) offsets + perpendicular to a rotated baseline but omits `rotation=` → dim lines render horizontal. +- **NEW-dimauto-ordinate-misnomer** — `dimension_auto` "ordinate" (`base.py:965-979`) emits two + linear dims at a constant offset → overlap; not real ordinate dims. + +- **NEW-com-chamfer-return** — `entity_chamfer` (`com_backend.py:1609-1629`) hunts for a new + `AcDbLine` that CHAMFER of two distinct lines never creates → almost always returns the wrong entity. +- **NEW-com-set-variable-coercion** — `system_set_variable` (`com_backend.py:1453-1458`) forwards the + raw value to `SetVariable` with no int/float coercion → type mismatch for numeric sysvars. + +- **NEW-section-dupbore** — `gear_draw_section_aa` + keyway draws the two bore lines twice → + `duplicate_entities` critique fires and blocks the premium workflow. +- **NEW-validator-keyway** — validator step-4 keyway heuristic requires a closed polyline, but + `draw_keyed_bore` emits `closed=False` and the section keyway is 4 lines → permanent + false-negative (`keyway_section_unverified`) even when a keyway exists. +- **NEW-gear-baseconstr** — the gear base circle is placed on `CONSTRUCTION` and is silently deleted + by the **mandated** `construction_clear()` before finalize → the returned handle dangles. + +- **R15** — `system_about` `tool_groups` static dict omits ~22 registered tools (all engineering + + premium + corner-ops + `drawing_close`) and misfiles `entity_delete_many`. **Fix:** derive groups + from tags dynamically. + +- **NEW-test-com-1** — `normalize_lineweight` table never tests the ambiguous bare ints `1`/`2` + (`normalize_lineweight(2)==200`, i.e. 2.00 mm not 2 hundredths) — the exact P0 truncation hazard. +- **NEW-test-com-2** — COM `transaction_begin/commit/rollback` + the `system_run_command` CMDACTIVE + busy-guard are entirely untested (R6 residual). + +- **NEW-offset-attrs (ezdxf)** — `entity_offset` copies only `layer`, dropping source color/linetype, + so offsetting a CENTER/colored line yields a Continuous ByLayer line (unlike copy/mirror). + +### LOW + +- **R22** — single `_undo_stack` shared by undo *and* transactions (no txn-id isolation); + `drawing_undo` can eat an open transaction's snapshot; reload leaves `_current_layer` stale; + `system_status` reports `len(_undo_stack)` *as* `transaction_depth`. +- **R24** — `_apply_attrs` (`ezdxf_backend.py:501-502`) assigns linetype without + `_ensure_linetype_loaded` → `entity_create_line(linetype="CENTER")` silently renders Continuous. +- **R26** — ezdxf `view_zoom_window` no-op returns `ok:True`; `view_zoom_and_screenshot` ignores the + window region (renders the whole modelspace). +- **R28** — COM screenshot HWND via title-substring `EnumWindows` instead of `_acad_app().HWND`. +- **R20** — `_registered_tool_count` reaches FastMCP private attrs with a silent `-1` fallback while + a public list API exists. +- **R18-residual** — per-item bootstrap failures swallowed (only total exception → `degraded`). +- **N7** — `point_from_snap` ARC `end`/`mid` subscript `p["start_angle"]` → opaque `KeyError`. +- **N8** — `_plan_spec` written, never read; the "replayed by drawing_critique" contract is unmet. +- **NEW-base-1** — `point_tangent` leaks raw `math domain error` ValueError for `from`-points in + `[r-1e-9, r)` (needs an `acos`-arg clamp). +- **NEW-snap-quad-arc / NEW-snap-near-arc** — `point_from_snap` `quad`/`near` on an ARC return + full-circle points that may not lie on the arc — defeats the no-guessing guarantee. +- **NEW-mtext-1** — `entity_create_mtext` ignores caller rotation and never reports it. +- **NEW-undo-1** — `drawing_undo` / `transaction_begin` mutate `_undo_stack` outside `self._lock`. +- **NEW-constclear-1** — `construction_clear` / `_ensure_layer` (base.py) swallow all per-entity + exceptions (silent-pass introduced by the meta-tool refactor). +- **NEW-AUTH-2** — no test asserts `mcp.auth is _auth` or reject-on-bad-token (only construction). +- **NEW-batch-1** — `entity_batch_create` docstring promises a `rectangle` type `create_map` lacks, + and pops `type` off the caller's dict. +- **NEW-test-com-3/4/5/6** — test looseness (vacuous polling assert, single-ESC vs 3, wrong boundary docstring). + +### SMELL +- **S5** — two layer systems (`_LAYER_TEMPLATES` vs `LAYER_SET_REGISTRY`); mechanical HIDDEN/CENTER + ACI colors (1/3) swapped vs `ENGINEERING_LAYERS` (3/1) → `template_apply_layers('mechanical')` + fails the `layer_color` critique. (The "violates ISO-128" sub-claim is **refuted** — template + weights all pass.) +- **S3** — `BlockInfo.description` always empty. +- **R27** — validator step-5 SPUR/HELICAL substring match over all TEXT-layer text (no word boundary). +- **NEW-base-2** — `point_intersection` treats LINE as infinite line; undocumented. +- **NEW-base-3** — COM path of `point_*` untested. +- **NEW-claudemd-1 / doc drift** — CLAUDE.md/README still list the removed `CommandResult` dataclass. +- **NEW-titleblock-titlex** — long title x-position can overflow left of the block. + +--- + +## C. Fix plan (dependency-ordered) + +### Sprint 2 — Make the gate real + stop the crashes *(CI-verifiable, headless)* — ✅ DONE +Goal: the validation moat must actually run and must not crash the server. +Implemented on branch `fix/sprint2-gate-correctness` (tests 233 → 242, ruff clean). +All six items below landed + locked by `tests/test_sprint2_fixes.py`. See degisilik.md. + +1. **NEW-screenshot-1** — force Agg rendering (Figure+FigureCanvasAgg). *(S, no deps)* +2. **I15** — wire `run_critique(focus=None)` into `drawing_finalize`; merge into the report; gate/warn. *(M, depends on #3 to be honest)* +3. **R4 + N4** — populate a dim reference point + resolve dim/construction layer from the active set; fixes both the no-op and the iso13567 mislayer. *(M)* +4. **NEW-AUTH-1** — enforce the remote-bind guard at import/lifespan, not only `__main__`. *(S)* +5. **NEW-gear-roottooth + R21** — clamp flank start to `max(base_r,root_r)`, fix root-arc stitch. *(M)* +6. **NEW-section-dupbore + NEW-validator-keyway + NEW-gear-baseconstr** — unblock the gear/keyway premium workflow end-to-end. *(M)* + +### Sprint 3 — Cross-backend parity + COM robustness *(some need live AutoCAD)* — ✅ DONE +Implemented on `fix/sprint2-gate-correctness` (tests 242 → 266). COM items code-read + mocked-COM tests (`tests/test_com_sprint3.py`); ezdxf/shared in `tests/test_sprint3_fixes.py`; R8 in `tests/test_security.py`. +7. **R13-COM** offset side ✅ (+ NEW-com-offset-orphan); **N2** save_as tool fmt ✅; **N5** properties parity ✅; **N3** ARC length ✅. +8. **R5** COM CoUninitialize/leak ✅; **R16** txn try/finally + UNDO via _safe_send_command ✅; **R23/N6** lisp guard + value capture ✅. *(live-verify pending on real AutoCAD)* +9. **NEW-com-set-variable-coercion** ✅; **NEW-dimauto-baseline/ordinate** ✅; **NEW-com-chamfer-return** → REFUTED on review (CHAMFER does create the bevel AcDbLine; code is correct). +10. **NEW-test-com-1/2** ✅ + adversarial sanitizer tests (R8) ✅. + +### Sprint 4 — Hygiene + lower-severity correctness — ✅ DONE (R22 deferred) +Implemented via a 5-agent parallel workflow on `fix/sprint2-gate-correctness` (tests 266 → 318). +Done: R24, R26, R28, R20, R15, R27, N7, N8, NEW-base-1, NEW-snap-quad-arc, NEW-snap-near-arc, +NEW-undo-1, NEW-mtext-1, S3, doc drift (CLAUDE.md CommandResult + counts). +New tests: `tests/test_sprint4_{ezdxf,base,server,validator,com}.py`. +**Deferred — R22** (undo/transaction stack isolation): the roadmap makes it the prerequisite for the +I1 refiner's rollback cycle, so it lands with Phase A (1.2), not as standalone hygiene. +Not pursued (low value / refuted): N8 implemented as docstring-fix + `get_plan_spec()` accessor; +S5 (parallel layer systems) and NEW-constclear-1 / NEW-batch-1 left as documented SMELL. + +--- + +## D. Top 5 next actions +1. **NEW-screenshot-1** — `Agg` backend in `view_screenshot`/`drawing_export_pdf` (prevents SIGSEGV; screenshots feed the visual signal). +2. **I15** — run the premium critique inside `drawing_finalize` (the moat — currently advisory only). +3. **R4 + N4** — make `dim_overlap` fire and resolve dim/construction layers per active set (so the gate doesn't lie). +4. **NEW-AUTH-1** — enforce the remote-bind guard on the documented `fastmcp run` path (fail-open security gap). +5. **NEW-gear-roottooth + R21** — fix the self-overlapping tooth outline for teeth ≥ ~42. + +## E. Risks / notes +- **Live-AutoCAD only:** R5, R16, R23/N6, R28, NEW-com-chamfer/set-variable can't be CI-verified + here — gate them behind mocked-COM tests + manual live check. +- **Cross-backend parity** is the dominant theme of the open work (Sprint-1 fixes were mostly + one-backend). Every new behavior should land on both backends or explicitly document the gap. +- **Security residual:** the `audit:security-hardening` finder was blocked by a provider + cyber-safety filter, so the deny-list bypass analysis (R7/R8) is **not** complete. The sanitizer + *is* wired (`server.py:2055/2072`) and `test_security.py` exists, but adversarial bypass coverage + remains an open defensive item — handle in Sprint 3 #10. diff --git a/pyproject.toml b/pyproject.toml index 5120e92..6b07535 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "autocad-mcp-pro" -version = "1.0.0" +version = "1.1.0" description = "Production-grade AutoCAD MCP server — COM API + ezdxf dual engine" readme = "README.md" requires-python = ">=3.11"