docs(pid): track A shipped — CLAUDE.md, README, changelog with the live COM smoke record

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
This commit is contained in:
Umut CAN
2026-09-16 13:34:06 +03:00
co-authored by Claude Opus 5
parent 8d9c444b76
commit 2be5e4180d
9 changed files with 2604 additions and 789 deletions
+169
View File
@@ -5,6 +5,175 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
Track A of 1.6: the server becomes a P&ID expert and a P&ID reader. Nine
`pid_*` tools in a new SECTION 17, two generic contracts every engine
implements, six critique focuses that join `drawing_finalize`, and a
`TOOL_PACKS` gate so a mechanical-only client never pays for the P&ID
surface. Spec: `docs/superpowers/specs/2026-09-15-v1.6-pid-design.md`.
### Added
- **P&ID — 9 tools, pack `pid`.**
- `pid_symbol_list` / `pid_symbol_insert`: a 43-symbol ISO 10628-2 /
ISA-5.1 catalogue authored in code (11 valve bodies × 6 actuators, 6
rotating machines, 4 heat exchangers, 6 parametric vessels with nozzle
ports, 7 inline items, the instrument bubble in 4 types × 5 locations,
5 line markers, 3 connectors), placed as real blocks with a `TAG`
ATTRIB, named ports, and an `ACADMCP_PID` XDATA payload. Ports are
transformed through the INSERT's real rotation, scale and mirror; a
stretched symbol (`|x_scale| ≠ |y_scale|`) has no circle for a radial
port and is refused rather than answered with a wrong point.
- `pid_line_draw`: port-to-port orthogonal routing (`auto` picks from seven
candidates by fewest bends, then length; `direct`; or waypoints), eight
ISA-5.1 line classes mapped to layer / linetype / marker blocks, and line
numbers filled from a format (default
`{size}-{service}-{seq}-{spec}-{insulation}`; an empty field drops out
with its separator). A route that cannot honour the `stub` on both
ports is refused by name (`route((0,0), 0°, (3,8), 180°, stub=5)`
raises) rather than drawn short; a non-axis port direction, a NaN
coordinate and an unfillable format token are refused before anything
is written. Lines it crosses are **counted and
reported** (`crossings`), never silently drawn over — and never refused.
- `pid_tag_parse`: the ISA-5.1 identification-letter grammar (Table 4.1,
pinned by `tests/data/isa51_tags.json`), with the equipment-prefix map.
- `pid_graph`: the reader. Every symbol INSERT becomes a node with a
`source` and a `confidence` — catalogue name 1.0, `ACADMCP_PID` payload
0.95, `TAG`-style attribute or keyword block name 0.6 with inferred
ports, an INSERT a plain line merely touches 0.3. Confidence is the
minimum of its inputs, never raised by agreement between heuristics.
Each line end resolves to a port, a junction on another line, or
`dangling` with the nearest connectable port as a hint, always within
its own space (`scope="all"` never joins two layouts). Edge `length` is
the bulge-aware length the engine reports, not a chord walk (12.5 %
short on a semicircular jump).
- `pid_instrument_index` / `pid_line_list` / `pid_equipment_list`: derived
from the graph only, optionally written as CSV. An instrument tapping a
pipe reports the pipe's line number, never its own signal wire.
- `pid_from_spec`: a whole sheet from one JSON spec in one transaction —
rolled back entirely on the first refusal — returning the handles, the
graph and the critique; `dry_run=True` routes in memory and touches
nothing.
- **`block_define(name, entities, attdefs)`** on both engines: a block from
typed primitive specs (line, circle, arc, polyline, text, solid) and
ATTDEFs, no source entities needed; `overwrite=True` replaces the contents
so existing INSERTs keep the name. Validation runs before any write: a
DXF-illegal layer name, a duplicate or trailing-newline ATTDEF tag, a
multi-line or trailing-caret string are `TypeError`/`ValueError` naming
`entities[i]` / `attdefs[i]`, and nothing is created.
- **`entity_get_xdata` / `entity_set_xdata`** on both engines, typed by DXF
group code. The two limits AutoCAD enforces silently are enforced loudly
and before the write: a string over **255 characters** and an entity
budget of **16 KB across every application** (three 15 KB apps on one
entity used to pass headlessly and would have failed `SetXData` live).
LF/CR in a string is refused — it split a DXF tag, the saved file failed
to reopen and, because every undo snapshot is a DXF save,
`transaction_rollback` broke after one such write. `nan`/`inf` are refused
rather than written as literal text that reads back as `null`.
- **Six critique focuses** — `pid_dangling_line`, `pid_duplicate_tag`,
`pid_incompatible_connection`, `pid_untagged_instrument`,
`pid_illegal_tag`, `pid_unconnected_equipment` — run under `focus=None`
and inside `drawing_finalize`, over one cached graph. They score
evidence only: a drawing without a recognised P&ID node returns `[]`, a
0.3 touch guess can neither create nor suppress a finding, and a line
ending on a sheet frame (an INSERT the drawing does not vouch for) is
dangling. `drawing_refine` hints name only ports `pid_line_draw` accepts.
- **Two resources:** `autocad://pid/symbols` (the catalogue with ports and
parameter schemas) and `autocad://standards/isa51` (the letter tables and
the grammar's disambiguation rules).
- **Benchmark evidence.** `benchmarks/tasks_v4.py` adds `pid_roundtrip`
(the example sheet drawn through `pid_from_spec`, read back by a graph
builder that never sees the spec); the correctness suite grows to 29 with
three P&ID checks. A/B against `v1.5.1`: **26 / 29 → 29 / 29, three
`miss → pass`, zero regressed** (`ab-v1.5.1-vs-v1.6.0-dev.json`).
- `scripts/smoke_pid_com.py` runs the track once against the live AutoCAD
on the machine, in a new document; `scripts/render_pid_catalog.py` and
`scripts/render_readme_pid.py` rebuild the two README images.
### Fixed
- **Headless `block_insert(attributes=…)` lost its ATTRIBs.** The ezdxf path
used `add_auto_blockref`, which wraps the INSERT in an anonymous `*U`
block: the handle it returned reported `block_name` `*U1`, and
`block_get_attributes` on it returned `{}`. Reproduce on v1.5.1 with
`block_create_from_entities` of a block with one ATTDEF, then
`block_insert(name, attributes={"TAG": "P-101"})` and
`block_get_attributes(handle)`. It now inserts the named block with
`add_auto_attribs`, so the INSERT carries the name and the ATTRIBs, on
both engines alike. `block_insert` of an undefined name is refused before
writing (it used to return a handle whose dangling INSERT `drawing_audit`
later deleted).
- Live LWPOLYLINEs report as `POLYLINE` through ActiveX (`AcDbPolyline`),
which the P&ID line reader's type filter excluded: on AutoCAD every
auto-built line number restarted at seq 1 and `crossings` was always 0.
Both spellings are accepted; the COM `_entity_info` polyline branch now
returns `points` (and per-vertex `bulges`) for a live LWPOLYLINE.
- Polyline `bulges` now leave both engines in WCS: a mirrored polyline's
arcs were handed out in the entity frame next to WCS points, so a reader
tested them on the wrong side of the chord.
- INSERT properties gain `geometry_bbox` (drawn geometry only, ATTRIBs
excluded) and `mirrored` on both engines. The COM box is exact at right
angles and measured off them; a member ActiveX cannot measure is carried
by its corners and the box says `approximate: true`.
### Changed
- **`TOOL_PACKS=all|core,pid`** (default `all`) advertises only the vertical
packs a client uses; `core` is always on and unknown names are ignored
with a warning. It composes with `TOOL_PROFILE=lean` (which now carries
`pid_symbol_insert`, `pid_line_draw` and `pid_graph`: 50 tools, 47 with
`TOOL_PACKS=core`) and `ENABLE_3D`; `system_about` reports `tool_packs`.
Measured idle cost: 161 tools / 44,930 tokens by default, 152 / 41,420
with `TOOL_PACKS=core`.
- `PID_LAYERS` (`drawing_apply_iso_layers("pid")`) gains `PROCESS-LINE-TEXT`;
`drawing_plan` / `drawing_preflight` report `LAYER_SET_INTENT_MISMATCH`
when a P&ID intent is planned onto a non-`pid` layer set.
- The `prompt_pid_diagram` prompt template is rewritten onto the tools (`pid_from_spec`,
`pid_symbol_insert`, `pid_line_draw`, `pid_graph`) instead of describing
primitives.
- `CritiqueFocus` grows by the six `pid_*` values; benchmark matrix v4
(`--matrix v3` / `v2` reproduce the earlier sets); correctness suite 29.
- README release snapshot: 166 tools · 8 resources · 5 prompts · 2423
collected tests. The collected-test figure is this machine's; CI has not
run on this tree yet, and the pre-existing 1369-vs-1363 drift between the
README and Linux collection predates the track and is superseded by the
new figure.
### Live COM smoke
`scripts/smoke_pid_com.py`, AutoCAD 2026 (`25.1s (LMS Tech)`), Windows 11,
2026-09-16, in a new document (`Drawing2.dwg`) — `block_define` →
`pid_symbol_insert` → `entity_set_xdata` → `pid_line_draw` → `pid_graph` →
six focuses → `entity_get_xdata`, all through the COM engine, exit 0:
```json
{
"autocad": "25.1s (LMS Tech)",
"active_document": "Drawing2.dwg",
"handles": {"P-101": "98", "V-201": "A8", "FCV-101": "B8", "FIC-101": "C5", "OP-1": "CF"},
"graph_stats": {
"nodes_by_kind": {"connector": 1, "equipment": 2, "instrument": 1, "valve": 1},
"edges_by_class": {"electric": 1, "process_major": 3},
"dangling": 0,
"unclassified": 0,
"confidence_min": 1.0,
"entities_scanned": 16
},
"crossings_total": 0,
"critique_issues": 0,
"critique": [],
"xdata_chunks": 2
}
```
The headless engine reports the identical `graph_stats`, `crossings_total`,
`critique_issues` and `xdata_chunks` on the same spec. The first attempt
exited 2 on this machine because of a stale `win32com` `gen_py` cache for the
AutoCAD type library (a package directory holding only a `__pycache__`) — an
environment fault, not a backend one; deleting `%TEMP%\gen_py\3.11` cleared
it and no backend change was needed.
## [1.5.1] — 2026-08-06
A patch for three defects found *after* 1.5.0 went to PyPI, two of them by the