diff --git a/docs/superpowers/specs/2026-09-16-v1.6-settings-design.md b/docs/superpowers/specs/2026-09-16-v1.6-settings-design.md new file mode 100644 index 0000000..b5404bd --- /dev/null +++ b/docs/superpowers/specs/2026-09-16-v1.6-settings-design.md @@ -0,0 +1,267 @@ +# v1.6 Track E — AutoCAD settings and environment + +**Date:** 2026-09-16 · **Status:** design approved in conversation; plan at +`docs/superpowers/plans/2026-09-16-v1.6-settings-implementation.md` · **Parent:** +`2026-09-15-v1.6-roadmap-design.md` (track E) · **Baseline:** main after +Track A (166 tools, 8 resources) + +## 1. Summary + +v1.5 lets a model read and write raw system variables and a friendly +`drawing_settings` facade, and nothing else about the environment a drawing +lives in: no dimension/text/leader styles (every dimension inherits +`Standard`), no page setup (a PDF is whatever the renderer's default is), no +templates beyond a layer list, one document at a time, no named views, UCS or +layer states, and no way to ask AutoCAD itself anything (preferences, document +properties, a point picked on screen). + +This track adds those as **38 tools** in three new sections, on both engines +wherever a headless file can hold the result, with an honest `@capability` +refusal where only a live AutoCAD can (preferences, document properties' +summary fields, launching, prompting the operator). Authored standards data — +ISO-25 and ANSI dimension styles, text styles, five drawing templates built by +the server's own tools — is pinned by tests. `drawing_apply_standard` makes the +common case one call. + +**The user's principle for this track (2026-09-16):** tool count is not the +goal. Every tool here has a rich alias record, a docstring that names its +refusals, a pack, and a place in the `lean` profile decision; a near-duplicate +is folded into an `action` enum rather than registered twice. + +Tool budget: **+38** → 204 registered. Packs: styles, settings, page setup and +templates are `core` (drafting essentials); the environment and interactive +groups form the new `settings` pack. + +## 2. Scope + +**In:** sections 4–10. **Track A hardening (wave 0 of this track's plan):** the +review findings from Track A that are real defects — `entity_create_block_ref` +accepts an undefined block, `block_explode` drops ATTRIB text, `pid_from_spec` +lets NaN reach the drawing and does not roll back on `BaseException`, signal +lines receive a fabricated line number and label, a bubble's exit point ignores +the first waypoint, `block_insert` coerces attribute values with `str()`, +`block_define` writes NaN base points and dangling layer references — each a +fix with a test; the rest of the ~80 findings are filed in +`docs/analysis/track-a-review-backlog.md` with a one-line disposition each. + +**Out of 1.6 (recorded):** TABLESTYLE objects (ezdxf cannot author them), +AutoCAD-native `LAYERSTATE` entries (our layer states are portable XRECORDs, +see §8.3), CUI/ribbon/workspace changes, plotter (`.pc3`) authoring, annotative +object *creation* (scales are settable; entities stay non-annotative), ANSI +*inch* templates (ANSI templates here are metric ANSI: `ansi` dimstyle with +decimal inches is a 1.7 item once an inch drawing benchmark exists). + +## 3. Architecture + +``` +engineering/standards/ + __init__.py + dimstyles.py ISO_25, ANSI presets as data; DIM_VARIABLE_WHITELIST; apply/describe + textstyles.py ISOCP, ISOCPEUR, ARIAL, ROMANS presets (font file, width, oblique) + mleaderstyles.py ISO / ANSI leader presets + sysvars.py SYSVAR_CATALOG (~60 entries: name, type, range, meaning, engines) + papers.py PAPER_SIZES (ISO A0–A4, ANSI A–E), CTB_CATALOG, scale vocabulary + templates.py TEMPLATE_CATALOG (5 bundled templates: what each contains) +engineering/environment/ + layer_states.py portable layer-state snapshot <-> XRECORD codec +templates/ built artefacts: .dxf (headless) + .dwt (COM), README +scripts/build_templates.py, scripts/smoke_settings_com.py + +backends/contracts/styles.py (new) dimstyle_*, textstyle_*, mleaderstyle_* +backends/contracts/pagesetup.py (new) page_setup_*, plot_style_list, batch_plot, template_save +backends/contracts/environment.py (new) document_*, layer_state_*, view_named_*, ucs_*, + preferences_*, drawing_properties_*, system_launch, + user_pick_point, user_select, system_prompt_message +backends/contracts/settings.py (edit) drawing_settings facade grows +server.py SECTION 18 Styles (10) · SECTION 19 Page setup & templates (6) · SECTION 20 Environment (22) +``` + +Every new contract method is `@abstractmethod` unless the table in §9 marks it +`@capability`. Both backends implement every method; COM paths are fake-tested +and executed once live by `scripts/smoke_settings_com.py`. + +## 4. Styles (SECTION 18, 10 tools, pack `core`) + +### 4.1 Authored presets (`engineering/standards/dimstyles.py`) + +| Variable | `iso-25` | `ansi` (metric) | Meaning | +|---|---|---|---| +| DIMTXT | 2.5 | 3.0 | text height (mm) | +| DIMASZ | 2.5 | 3.0 | arrow size | +| DIMEXE | 1.25 | 1.5 | extension beyond dim line | +| DIMEXO | 0.625 | 1.5 | extension line offset | +| DIMGAP | 0.625 | 1.0 | text gap | +| DIMTAD | 1 (above) | 0 (centred) | text vertical placement | +| DIMTIH / DIMTOH | 0 / 0 (aligned) | 1 / 1 (horizontal) | text orientation | +| DIMDEC | 2 | 2 | linear decimals | +| DIMDSEP | `,` | `.` | decimal separator | +| DIMLUNIT | 2 (decimal) | 2 | linear unit format | +| DIMZIN | 8 (trailing zeros suppressed) | 8 | zero suppression | +| DIMBLK | `""` (closed filled) | `""` | arrowhead | +| DIMTXSTY | `ISOCP` | `ROMANS` | text style (created if missing) | +| DIMLWD / DIMLWE | -2 (ByBlock) | -2 | line/extension lineweight | +| DIMSCALE | 1.0 | 1.0 | overall scale | + +Source: ISO 129-1:2018 (text and arrow sizes for the 2.5 mm lettering row of +ISO 3098-1) and ASME Y14.2-2014 (line conventions and lettering: 3 mm / .12 in +lettering, .125 in arrowheads), metric values. +A provenance test asserts each preset's values against this table by name. +`DIM_VARIABLE_WHITELIST` = the 15 variables above plus DIMTOL/DIMTP/DIMTM/ +DIMTOLJ/DIMTFAC/DIMLFAC/DIMRND/DIMATFIT/DIMTMOVE/DIMCLRD/DIMCLRE/DIMCLRT/ +DIMSAH/DIMBLK1/DIMBLK2/DIMCEN — `dimstyle_modify` refuses anything else by +name. + +### 4.2 Tools + +- `dimstyle_list()` → `[{name, current, values: {15 whitelist vars}}]` +- `dimstyle_create(name, preset: "iso-25"|"ansi"|None = None, overrides: dict|None = None, set_current=False)` — preset values then overrides; unknown override key or a value outside the variable's range (`DIMDEC` 0–8, sizes > 0, DIMTAD 0–4, DIMDSEP one char) → `ValueError` naming it, nothing written; name clash → `ValueError` (use `dimstyle_modify`). +- `dimstyle_modify(name, overrides)` — `changed` reports only variables that actually moved (the `hatch_edit` rule). Dimensions already using the style are listed in `dimensions_using_style` with `rerender_required: true` on the headless engine: AutoCAD re-renders them on the next regen, but a headless DIMENSION carries its rendered block, so the drawing shows the old style until the dimension is redrawn — stated rather than silent. +- `dimstyle_set_current(name)` — `$DIMSTYLE`; the dimension tools then use it (the existing per-dimension override path stays as is). +- `textstyle_list()`, `textstyle_create(name, font: "isocp.shx"|"isocpeur.ttf"|"arial.ttf"|"romans.shx"|, height=0.0, width_factor=1.0, oblique_deg=0.0, set_current=False)`, `textstyle_set_current(name)` — `$TEXTSTYLE`; a font file that is neither a bundled preset nor present in the engine's font path is written but reported `font_resolved: false` (AutoCAD substitutes at open; the headless renderer falls back), never refused, because DXF only stores the name. +- `mleaderstyle_list()`, `mleaderstyle_create(name, preset: "iso"|"ansi", overrides)` — arrow size, landing gap, text style, text height on ezdxf's `doc.mleader_styles`. ActiveX exposes no MLeaderStyle collection, so the COM engine refuses both with `capability: "mleaderstyle"` (the reason names `leader_create_mleader`'s existing per-leader parameters as the route); `mleaderstyle_list` on COM still reads the names from the `ACAD_MLEADERSTYLE` dictionary. +- `drawing_apply_standard(standard: "iso"|"ansi", layers: bool = True, units: bool = True)` — one call: the dimstyle preset (created and set current), its text style, `drawing_settings` (units mm, decimal linear and angular units, LTSCALE 1, DIMSCALE 1; the decimal separator comes from the preset) and, with `layers`, the `mech` layer set for both standards (ANSI layer naming is company-specific; the set reuses the mech names with ISO 128 lineweights — only styles and the separator differ between the two). Returns, per item, whether it was created or was already present. + +## 5. Settings (SECTION 20 shares these; 3 tools + facade growth) + +- `drawing_settings` facade gains: `limits` (`[[xmin,ymin],[xmax,ymax]]` → `$LIMMIN/$LIMMAX`), `grid` (bool → GRIDMODE) and `grid_spacing`, `snap` (bool) and `snap_spacing`, `ortho` (bool), `polar` (bool + `polar_angle`), `psltscale` (0/1), `annotation_scale` (`"1:50"` → CANNOSCALE string + CANNOSCALEVALUE), `linear_units` (`decimal|engineering|architectural|fractional|scientific` → LUNITS 2/3/4/5/1), `angular_units` (`degrees|dms|grads|radians|surveyor` → AUNITS), `dimstyle` and `textstyle` (current names). Ranges enforced as today (`_RANGES`), unknown keys refused, and the read side reports every key. +- `system_variable_describe(name=None, search=None)` — from `SYSVAR_CATALOG`: `{name, type, range|enum, default, meaning, saved_in: drawing|registry|not_saved, engines: {ezdxf: bool, com: bool}, friendly_key: str|None}`; `search` matches names and meanings; unknown name → `known: false` plus the nearest names, never an error. The catalog is authored (~60: the DIM* whitelist, LTSCALE, PSLTSCALE, INSUNITS, LUNITS, LUPREC, AUNITS, AUPREC, LIMMIN/LIMMAX, GRIDMODE/GRIDUNIT, SNAPMODE/SNAPUNIT, ORTHOMODE, POLARMODE/POLARANG, OSMODE, CANNOSCALE, TEXTSIZE, TEXTSTYLE, DIMSTYLE, CLAYER, CELTYPE, CECOLOR, CELWEIGHT, PDMODE/PDSIZE, FILLETRAD, MIRRTEXT, ATTREQ/ATTDIA, TILEMODE, CTAB, DWGNAME/DWGPREFIX, SAVETIME, FILEDIA, CMDECHO, HIGHLIGHT, REGENMODE, UCSNAME, VIEWCTR/VIEWSIZE, ANGBASE/ANGDIR, PLINEWID, HPNAME/HPSCALE/HPANG, LWDISPLAY, XREFCTL, PROXYSHOW, ISOLINES, FACETRES, DISPSILH). `system_set_variable` gains a soft check: a name in the catalog with a range refuses out-of-range values before writing; a name not in the catalog behaves as today. +- `drawing_properties_get()` / `drawing_properties_set(title=None, subject=None, author=None, keywords=None, comments=None, custom: dict|None=None)` — COM: `SummaryInfo` (+ `AddCustomInfo/SetCustomByKey`); ezdxf: `custom` through `doc.header.custom_vars` (`$CUSTOMPROPERTYTAG`/`$CUSTOMPROPERTY` pairs, both engines) and the five summary fields refused with `capability: "dwgprops"` when any of them is passed headlessly (`get` returns them as `null` with `summary_available: false`). + +## 6. Page setup, plotting, templates (SECTION 19, 6 tools, pack `core`) + +- `page_setup_list(layout=None)` → per layout: `{paper: "ISO_A3", size_mm: [420,297], orientation, plot_style, scale, plot_area, device, margins_mm}` — read from LAYOUT plot settings (ezdxf `dxf_layout.dxf.*`; COM `Layout.*`). +- `page_setup_apply(layout, paper: "ISO_A4"|"ISO_A3"|"ISO_A2"|"ISO_A1"|"ISO_A0"|"ANSI_A"|…|"ANSI_E", orientation: "landscape"|"portrait" = "landscape", plot_style: str = "monochrome.ctb", scale: "fit"|"1:1"|"1:2"|"1:5"|"1:10"|"1:20"|"1:50"|"1:100"|"2:1"|"5:1"|"10:1" = "fit", plot_area: "layout"|"extents" = "layout", device: str = "DWG To PDF.pc3", margins_mm=None, center=True)` — `PAPER_SIZES` authored (ISO 216 / ANSI Y14.1 sizes in mm), `CTB_CATALOG` names the ctb files AutoCAD ships (`monochrome.ctb`, `acad.ctb`, `Grayscale.ctb`, `Screening 100%.ctb`, …); an unknown ctb is written and reported `plot_style_known: false` (a missing ctb only matters at plot time). Returns the applied setup and `changed`. +- `plot_style_list()` — the authored catalog plus, on COM, the files actually present in `Preferences.Files.PrintStyleSheetPath` (`source: "catalog"|"installed"`). +- `batch_plot(layouts: list[str]|None = None, output_dir: str, format: "pdf" = "pdf", file_pattern: "{drawing}-{layout}.pdf")` — every named (default: every paper) layout through the existing `drawing_export_pdf(layout=)` path; returns one row per layout `{layout, path, bytes, mediabox_mm}` where `mediabox_mm` is **read back from the PDF** (`/MediaBox` parsed from the file) so the sheet size is verified, not assumed. Model space is included only when named explicitly. +- `drawing_template_list()` → `TEMPLATE_CATALOG` rows `{name, standard, sheet, layer_set, dimstyle, textstyle, title_block, files: {dxf, dwt}}`. +- `drawing_new(template=…)` (existing tool, parameter grows): a catalog name resolves to the bundled file (`templates/.dxf` headless, `templates/.dwt` on COM); a path is used as today. Returns `template: {name|path, source: "bundled"|"path"}`. +- `drawing_template_save(path, name=None, description=None)` — current drawing as a template: ezdxf writes DXF (a `.dwt` request headlessly is refused with `capability: "dwt_write"` naming the DXF route); COM `SaveAs(path, acTemplateDwg)` and the description goes to the template's summary. Path validated for write. + +**Bundled templates** (`scripts/build_templates.py` builds all five through the +server's own backend methods — layers, styles, settings, ISO 5457 frame from +`titleblock_apply_iso_a3` where the sheet is A3, page setup — then saves DXF; +the `.dwt` twins are produced on the live machine by opening each DXF over COM +and `SaveAs(..., acTemplateDwg)`, committed once, and a test asserts the DXF +side is byte-reproducible from the script): + +| name | standard | sheet | layer set | styles | page setup | +|---|---|---|---|---|---| +| `iso_a3_mech` | ISO | A3 landscape | mech | ISO-25 / ISOCP | ISO_A3, monochrome.ctb, 1:1 | +| `iso_a1_arch` | ISO | A1 landscape | iso13567 | ISO-25 / ISOCP, CANNOSCALE 1:50 | ISO_A1, monochrome.ctb, fit | +| `iso_a3_pid` | ISO | A3 landscape | pid | ISO-25 / ISOCP | ISO_A3, monochrome.ctb, 1:1 | +| `ansi_b_mech` | ANSI | ANSI B landscape | mech | ANSI / ROMANS | ANSI_B, monochrome.ctb, 1:1 | +| `ansi_d_arch` | ANSI | ANSI D landscape | iso13567 | ANSI / ROMANS | ANSI_D, monochrome.ctb, fit | + +## 7. Environment (SECTION 20, 22 tools, pack `settings`) + +### 7.1 Documents (3, both engines) + +- `document_list()` → `[{name, path, active, saved, entity_count}]`. +- `document_activate(name_or_path)` → switches the backend's active document; every later tool call targets it. ezdxf: the backend keeps `_docs: dict[key, DocState]` (document, path, dirty flag, undo stack, plan spec, transaction snapshot) and `_doc` becomes a property over the active key — `drawing_new` / `drawing_open` register a new entry instead of replacing the only one; `drawing_close` removes the active entry and activates the most recent other one (or none). COM: `Documents.Item(...).Activate()`. +- `document_close(name_or_path, save=False)` — refuses to discard unsaved changes unless `discard=True` (the message names the unsaved document); closing the last document leaves the backend in the "no document" state every tool already handles. +- `drawing_close` (existing) becomes the active-document case of `document_close`. + +### 7.2 Layer states (4, both engines, portable) + +`layer_state_save(name, description=None)`, `layer_state_restore(name, properties: list|None = None)`, `layer_state_list()`, `layer_state_delete(name)`. + +A state is the layer table snapshot `{layer: {on, frozen, locked, color, linetype, lineweight, plot}}` plus `current_layer`, stored as JSON chunks in an XRECORD under the drawing's named object dictionary `ACADMCP_LAYERSTATES` → `` (ezdxf `rootdict.add_new_dict` / `add_xrecord`; COM `Dictionaries.Add` / `AddXRecord` / `SetXRecordData` with 1000-code chunks ≤ 255 chars). Restore applies only the requested `properties` (default all) and reports `applied`, `missing_layers` (in the state, not in the drawing — skipped, never created) and `new_layers` (in the drawing, not in the state — untouched). The docstring and `system_capabilities` say plainly that these states live in the file and travel with it but **do not appear in AutoCAD's Layer States Manager**. + +### 7.3 Named views (3, both engines) + +`view_named_save(name, center=None, height=None, width=None)` (defaults: the current view on COM; on ezdxf the drawing's extents), `view_named_restore(name)` (COM: `ActiveViewport` set; ezdxf: `$VIEWCTR/$VIEWSIZE` and the reported no-op rule of the other view tools), `view_named_list()`. Refuses `height <= 0`; a name clash replaces (`replaced: true`). + +### 7.4 UCS (3, both engines) + +`ucs_list()`, `ucs_set(name, origin, x_axis, y_axis)` (unit vectors normalised; non-orthogonal axes refused with the measured angle), `ucs_restore(name|"world")`. **Tool coordinates stay WCS** (the repository rule) — a UCS is stored and made current for the operator's benefit; no tool starts interpreting inputs in UCS. The docstring says so. + +### 7.5 Application, preferences, properties, interactive (9) + +- `system_launch(visible=True, open_path=None)` — COM: connect or start the ProgID, optionally open a file; returns `{launched: bool, attached: bool, version, document}`; ezdxf: `capability: "live_application"`. +- `system_preferences_get(keys=None)` / `system_preferences_set(key, value)` — whitelist (COM `Preferences.*`): + `OpenSave.SaveAsType` (enum name ↔ value), `OpenSave.AutoSaveInterval` (0–600 min), `OpenSave.CreateBackup`, `OpenSave.IncrementalSavePercent` (0–100), `Display.CursorSize` (1–100), `Drafting.AutoSnapMarkerSize` (1–20), `Drafting.AutoSnapTooltip`, `Selection.PickBoxSize` (0–50), `Output.DefaultPlotStyleTable`, `Output.DefaultOutputDevice`, and read-only `Files.SupportPath`, `Files.TemplateDwgPath`, `Files.PrintStyleSheetPath`, `Files.PrinterConfigPath`. Writes report `old`/`new`; a read-only key or a value outside its range is refused by name; ezdxf: `capability: "preferences"`. +- `drawing_properties_get/set` — §5. +- `user_pick_point(prompt)`, `user_select(prompt, mode: "single"|"multiple")`, `system_prompt_message(text)` — COM `Utility.GetPoint` / `GetEntity` + `SelectionSets.Add(...).SelectOnScreen` / `Utility.Prompt`; a pick cancelled by the operator returns `{cancelled: true}` (not an error); a wall-clock timeout (`COM_CALL_TIMEOUT` applies) reports `timed_out`; ezdxf: `capability: "interactive_prompt"`. + +## 8. Cross-cutting + +### 8.1 Capability keys (added to **both** maps) + +`mleaderstyle` (COM false), `dwgprops` (ezdxf false: summary fields), `dwt_write` +(ezdxf false), `live_application`, `preferences`, `interactive_prompt` (ezdxf +false). `tests/test_capability_contract.py` enforces both maps. + +### 8.2 Packs, profiles, discovery + +`PACK_TOOL_NAMES["settings"]` = the 22 SECTION 20 tools; SECTION 18/19 are +core. `LEAN_TOOL_NAMES` gains `drawing_apply_standard`, `page_setup_apply`, +`batch_plot`, `dimstyle_set_current`, `textstyle_set_current` (lean = 55). +Alias records for all 38 (AutoCAD commands: `DIMSTYLE`, `DDIM`, `STYLE`, +`MLEADERSTYLE`, `PAGESETUP`, `PLOT`, `PUBLISH`, `NEW`, `QNEW`, `SAVEAS`, +`OPTIONS`, `DWGPROPS`, `LAYERSTATE`, `VIEW`, `UCS`, `UCSMAN`, `LIMITS`, +`GRID`, `SNAP`, `ORTHO`, `UNITS`, `CANNOSCALE`, `SYSVAR`, `SETVAR`) and +shop-floor synonyms; the discovery golden set gains ≥ 10 queries for them +(`tests/test_discovery_transform.py` golden file). + +### 8.3 Honesty rules specific to this track + +- A style or setting write is reported with `changed` (what moved) — re-setting + a value to itself is not a change. +- Page setup evidence is the PDF's own `/MediaBox`, read back. +- Layer states are ours, not AutoCAD's; documented in three places (tool + docstring, `system_capabilities`, README). +- Nothing here starts interpreting coordinates in UCS. + +## 9. Testing and evidence + +- Presets provenance: `tests/test_standards_presets.py` asserts every ISO-25 / + ANSI value in §4.1 by name, every paper size against ISO 216 / ANSI Y14.1 + (A0 841×1189 … A4 210×297; ANSI A 216×279 … E 864×1118), every catalog + sysvar has type+meaning+engines. +- Dual-engine tests for every tool (ezdxf real; COM fake-ActiveX asserting call + shapes: `DimStyles.Add`, `TextStyles.Add`, `Layout.ConfigName/…`, + `Documents.Item().Activate`, `Dictionaries.Add`+`AddXRecord`, `Views.Add`, + `UserCoordinateSystems.Add`, `Preferences.OpenSave.*`, `SummaryInfo.*`, + `Utility.GetPoint`). +- Multi-document: two headless documents open, entity created in one is not in + the other, activate switches, close-with-unsaved refuses, closing the last + leaves "no document". +- Layer states: save → change layers → restore → table equals snapshot; + `missing_layers`/`new_layers` reported; the XRECORD survives save/reopen. +- Page setup: apply ISO_A3 landscape → `batch_plot` → parsed `/MediaBox` is + 420×297 mm (±0.5 mm at 72 dpi units); ANSI_B → 432×279. +- Templates: `scripts/build_templates.py --check` proves the five DXFs are + reproducible; `drawing_new(template="iso_a3_mech")` yields the mech layers, + ISO-25 current, ISO_A3 page setup; a `.dwt` twin exists for each and opens on + COM in the smoke. +- `drawing_apply_standard("iso")` then `dimension_linear` → the DIMENSION carries + DIMTXT 2.5 / DIMDSEP `,`; `"ansi"` → 3.0 / `.`. +- Live COM smoke `scripts/smoke_settings_com.py`: styles, page setup + PDF + mediabox, template `.dwt` open, layer state round trip, named view, UCS, + preferences get (+ one reversible set), `SummaryInfo`, `system_prompt_message`; + `user_pick_point` is exercised only if `--interactive` is passed (it needs the + operator). Result recorded in the CHANGELOG. +- Benchmark v4 gains `page_setup_truth` (`benchmarks/tasks_v4.py`, the same + v3→v4 rule: competitors `not run`); correctness suite +3 + (`settings_dimstyle_iso25_values`, `settings_layer_state_roundtrip`, + `settings_pdf_mediabox_a3`). +- Docs: CLAUDE.md sections 18–20 + packs, README rows + config table + (`TOOL_PACKS=settings`), CHANGELOG `Unreleased`. + +## 10. Milestones (input to the plan) + +0. Track A hardening (7 fixes + backlog file) — independent, parallel wave. +1. `engineering/standards/` data modules + provenance tests (pure). +2. Styles contract + both engines + SECTION 18 tools. +3. Settings facade growth + `system_variable_describe` + properties. +4. Page setup + plot style list + batch_plot with mediabox read-back. +5. Templates: build script, bundled DXF, catalog, `drawing_new(template=)`, `drawing_template_save`; `.dwt` twins from the live machine. +6. Multi-document on both engines (+ `drawing_close` alignment). +7. Layer states, named views, UCS. +8. Launch, preferences, interactive tools, capability keys. +9. Packs/lean/aliases/golden queries, benchmark + correctness, live smoke, docs, gates. + +Groups 2, 4, 6+7 touch disjoint modules and get their own SECTIONs, so the +plan runs them in parallel worktrees (the Track A wave-1 pattern) and a merge +agent regenerates the three count files (`docs/tool-inventory.json`, README +snapshot line, `tests/test_tool_registry.py`) after merging.