The evidence for track H, in the shape tracks A, E, B + G and F used.
- benchmarks/tasks_v7.py: 21 tasks, DEFAULT_MATRIX = "v7"; v6 to v2 stay
addressable. Both new tasks read the synthetic plant pair and nothing
else. takeoff_roundtrip: the P&ID called schematic, every routable run's
layout length equal to the rectilinear MST of its tags, sizes and services
as drawn (a return drawn on the supply layer included), the cable metres
rounded up after the allowance, an unstated power left empty.
understand_foreign: inches over millimetres flagged, the stray entity found
by handle, two plan copies separated, five layers classified. A test breaks
each input and watches its gate fail by name.
- Five correctness checks, every number worked out by hand in its docstring;
the published A/B report is re-run over 48 checks: 26 / 48 -> 48 / 48,
zero regressed.
- scripts/smoke_understand_com.py reads the pair through the live engine -
snapshots, the DWG route, both takeoffs and a diff - and closes every
document it opens; the CHANGELOG records what it returned.
scripts/field_test_takeoff.py runs the takeoffs on a user's own files,
refuses to write inside the repository and prints no quantity by default.
- The README token table is remeasured; check_doc_numbers derives the task
count from the v7 matrix; CLAUDE.md, both READMEs, the CHANGELOG and the
spec status line say what shipped.
One reader defect found on the way: describe.tag_occurrences read a wiring
callout ("Wiring to CP-2") as a place its panel is written, so the scale
check called CP-2 ambiguous and read "Wiring to CP1" as a phantom tag CP1 -
10 tags and 45 pairs (13.3 % within 10 %) where the generator placed 11 and
55 (21.8 %). A callout is no longer a tag occurrence, the rule
network.tag_occurrences already kept; two tests failed before the fix.
The smoke works only in documents it opened from its own temporary folder,
known by full path; it attaches with GetActiveObject only (the backend's
Dispatch fallback would launch AutoCAD), checks in the same COM-thread call
that the active document is its own before each snapshot, the DWG save and
the move, closes its documents with Close(False) and re-activates the
document that was active when it started. No new ActiveX member: Documents,
Item, FullName, ActiveDocument, Activate and Close are the ones the backend
already calls. Measured on AutoCAD 2026 (25.1s): exit 0, model-space types
equal file / live / DWG, export 0.104 s, no document of the run left open.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
233 lines
7.5 KiB
Python
233 lines
7.5 KiB
Python
"""Hold the numbers in the documents equal to the numbers in the tree.
|
|
|
|
Every figure this checks has been wrong at least once, and the expensive one was
|
|
wrong in three files at the same time: `benchmarks/README.md` and `CLAUDE.md`
|
|
described the A/B suite as 24 checks at 87.5% while the suite held 26 and the
|
|
published report said 80.8%. Nothing caught it because each file was edited on
|
|
its own and nobody re-read the other two.
|
|
|
|
The rule is the one the release snapshot already follows: a number a human
|
|
types is a number that rots, so derive it and fail when the prose disagrees.
|
|
|
|
python scripts/check_doc_numbers.py # report and exit non-zero on drift
|
|
python scripts/check_doc_numbers.py --list # show every derived value
|
|
|
|
`tests/test_release_consistency.py` runs this as a gate.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
#: The published A/B report the README and benchmarks/README quote. Bump this,
|
|
#: the two row labels below and the prose together, or the gate reports drift.
|
|
AB_REPORT = "benchmarks/results/published/ab-v1.5.1-vs-v1.6.0-dev.json"
|
|
AB_BASELINE_LABEL = r"v1\.5\.1"
|
|
AB_CURRENT_LABEL = r"v1\.6\.0-dev"
|
|
|
|
|
|
def _read(relative: str) -> str:
|
|
return (ROOT / relative).read_text(encoding="utf-8")
|
|
|
|
|
|
def _json(relative: str) -> dict:
|
|
return json.loads(_read(relative))
|
|
|
|
|
|
def _collected_tests() -> int:
|
|
"""How many tests pytest actually collects.
|
|
|
|
On the same README line as tools/resources/prompts, and the only number on
|
|
it that nothing derived -- so it drifted by 95 while its neighbours stayed
|
|
exact. Collection only; running the suite here would make the gate cost a
|
|
minute.
|
|
"""
|
|
env = dict(os.environ, AUTOCAD_MCP_BACKEND="ezdxf")
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "pytest", "--collect-only", "-q"],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
env=env,
|
|
timeout=300,
|
|
)
|
|
match = re.search(r"(\d+) tests collected", result.stdout)
|
|
if not match:
|
|
raise SystemExit(
|
|
"check_doc_numbers: could not read a collected-test count from pytest.\n"
|
|
+ result.stdout[-2000:]
|
|
)
|
|
return int(match.group(1))
|
|
|
|
|
|
def derived() -> dict[str, object]:
|
|
"""Every number a document is allowed to quote, computed from the tree."""
|
|
sys.path.insert(0, str(ROOT))
|
|
inventory = _json("docs/tool-inventory.json")["totals"]
|
|
ab = _json(AB_REPORT)["summary"]
|
|
perf = {
|
|
w["workload_id"]: w["wall_ms"]
|
|
for w in _json("benchmarks/results/published/perf-ezdxf.json")["workloads"]
|
|
}
|
|
|
|
from benchmarks import correctness_suite
|
|
from benchmarks.tasks_v7 import TASKS_V7
|
|
|
|
return {
|
|
"collected_tests": _collected_tests(),
|
|
"tools": inventory["tools"],
|
|
"resources": inventory["resources"],
|
|
"prompts": inventory["prompts"],
|
|
"groups": inventory["groups"],
|
|
"ab_baseline_ref": ab["baseline_ref"],
|
|
"ab_total": ab["total"],
|
|
"ab_old_pass": ab["old_pass"],
|
|
"ab_old_pct": ab["old_pct"],
|
|
"ab_fixed": ab["fixed"],
|
|
"ab_regressed": ab["regressed"],
|
|
"suite_checks": len(correctness_suite.CHECKS),
|
|
"matrix_tasks": len(TASKS_V7),
|
|
"perf_ms": perf,
|
|
}
|
|
|
|
|
|
#: (relative path, human label, regex over the file, callable -> expected value)
|
|
#:
|
|
#: Each pattern captures the number as group 1. A pattern that matches nothing is
|
|
#: itself a failure: the sentence it was written for has moved, and a silent
|
|
#: no-op gate is worse than no gate.
|
|
CHECKS: list[tuple[str, str, str, str]] = [
|
|
("README.md", "release snapshot tools", r"release snapshot:\*\*\s*(\d+) tools", "tools"),
|
|
(
|
|
"README.md",
|
|
"release snapshot resources",
|
|
r"release snapshot:.*?(\d+) resources",
|
|
"resources",
|
|
),
|
|
(
|
|
"README.md",
|
|
"release snapshot prompts",
|
|
r"release snapshot:.*?(\d+) prompt templates",
|
|
"prompts",
|
|
),
|
|
("README.md", "A/B suite size", r"(\d+) deterministic headless checks", "ab_total"),
|
|
(
|
|
"README.md",
|
|
"A/B baseline pass",
|
|
AB_BASELINE_LABEL + r" \*\(baseline\)\* \| (\d+) / \d+",
|
|
"ab_old_pass",
|
|
),
|
|
(
|
|
"README.md",
|
|
"A/B suite denominator",
|
|
AB_BASELINE_LABEL + r" \*\(baseline\)\* \| \d+ / (\d+)",
|
|
"ab_total",
|
|
),
|
|
("benchmarks/README.md", "A/B suite size", r"(\d+) checks, ezdxf backend", "ab_total"),
|
|
(
|
|
"benchmarks/README.md",
|
|
"A/B baseline pass",
|
|
r"\*\*" + AB_BASELINE_LABEL + r"\*\* \(baseline\)\s*\| (\d+) / \d+",
|
|
"ab_old_pass",
|
|
),
|
|
(
|
|
"benchmarks/README.md",
|
|
"A/B suite denominator",
|
|
r"\*\*" + AB_BASELINE_LABEL + r"\*\* \(baseline\)\s*\| \d+ / (\d+)",
|
|
"ab_total",
|
|
),
|
|
(
|
|
"README.md",
|
|
"release snapshot collected tests",
|
|
r"release snapshot:.*?(\d+) collected tests",
|
|
"collected_tests",
|
|
),
|
|
(
|
|
"README.md",
|
|
"A/B baseline pass rate",
|
|
AB_BASELINE_LABEL + r" \*\(baseline\)\* \| \d+ / \d+ \| ([\d.]+) %",
|
|
"ab_old_pct",
|
|
),
|
|
(
|
|
"README.md",
|
|
"A/B fixed count",
|
|
AB_CURRENT_LABEL
|
|
+ r"\*\* \*\(this branch\)\* \| \*\*\d+ / \d+\*\* \| \*\*\d+ %\*\* \| (\d+)",
|
|
"ab_fixed",
|
|
),
|
|
(
|
|
"benchmarks/README.md",
|
|
"A/B baseline pass rate",
|
|
r"\*\*" + AB_BASELINE_LABEL + r"\*\* \(baseline\)\s*\| \d+ / \d+ \| ([\d.]+) %",
|
|
"ab_old_pct",
|
|
),
|
|
(
|
|
"benchmarks/README.md",
|
|
"A/B fixed count",
|
|
r"\*\*" + AB_CURRENT_LABEL + r"\*\* \(this branch\) \| \d+ / \d+ \| \d+ % \| (\d+)",
|
|
"ab_fixed",
|
|
),
|
|
("CLAUDE.md", "A/B suite size", r"correctness_suite\.py` \((\d+) checks\)", "ab_total"),
|
|
("CLAUDE.md", "matrix task count", r"the current set \((\d+) tasks", "matrix_tasks"),
|
|
]
|
|
|
|
|
|
def run(verbose: bool = False) -> int:
|
|
values = derived()
|
|
|
|
if values["suite_checks"] != values["ab_total"]:
|
|
print(
|
|
f"DRIFT correctness_suite.py has {values['suite_checks']} checks but the published "
|
|
f"A/B report was run over {values['ab_total']} -- regenerate the report:\n"
|
|
f" python benchmarks/compare_versions.py {values['ab_baseline_ref']} "
|
|
f"--json {AB_REPORT}"
|
|
)
|
|
return 1
|
|
|
|
if verbose:
|
|
for key, value in values.items():
|
|
print(f" {key:20s} {value}")
|
|
print()
|
|
|
|
failures = 0
|
|
for relative, label, pattern, key in CHECKS:
|
|
text = _read(relative)
|
|
match = re.search(pattern, text, re.DOTALL)
|
|
expected = values[key]
|
|
if match is None:
|
|
print(
|
|
f"DRIFT {relative}: the sentence carrying '{label}' no longer matches its pattern"
|
|
)
|
|
failures += 1
|
|
continue
|
|
found = type(expected)(match.group(1))
|
|
if found != expected:
|
|
print(f"DRIFT {relative}: {label} says {found}, the tree says {expected}")
|
|
failures += 1
|
|
elif verbose:
|
|
print(f" ok {relative:24s} {label:24s} {found}")
|
|
|
|
if failures:
|
|
print(f"\n{failures} document number(s) disagree with the tree.")
|
|
return 1
|
|
print(f"ok: {len(CHECKS)} document numbers match the tree")
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--list", action="store_true", help="Print every derived value")
|
|
return run(verbose=parser.parse_args().list)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|