@tangle-network/agent-bench 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/HARNESS.md +2 -2
  3. package/dist/adapters.js +6 -2
  4. package/dist/adapters.js.map +1 -1
  5. package/dist/benchmarks/appworld.d.ts +61 -2
  6. package/dist/benchmarks/appworld.js +111 -40
  7. package/dist/benchmarks/appworld.js.map +1 -1
  8. package/dist/benchmarks/cadbench.js +1 -1
  9. package/dist/benchmarks/cadgenbench.js +1 -1
  10. package/dist/benchmarks/finresearchbench.js +16 -22
  11. package/dist/benchmarks/finresearchbench.js.map +1 -1
  12. package/dist/benchmarks/finsearchcomp.js +16 -22
  13. package/dist/benchmarks/finsearchcomp.js.map +1 -1
  14. package/dist/benchmarks/frames.js +18 -20
  15. package/dist/benchmarks/frames.js.map +1 -1
  16. package/dist/benchmarks/mcad-bench.d.ts +106 -0
  17. package/dist/benchmarks/mcad-bench.js +569 -0
  18. package/dist/benchmarks/mcad-bench.js.map +1 -0
  19. package/dist/benchmarks/mcad-cq-bench.d.ts +82 -0
  20. package/dist/benchmarks/mcad-cq-bench.js +339 -0
  21. package/dist/benchmarks/mcad-cq-bench.js.map +1 -0
  22. package/dist/benchmarks/mcad-cq-golds.d.ts +36 -0
  23. package/dist/benchmarks/mcad-cq-golds.js +342 -0
  24. package/dist/benchmarks/mcad-cq-golds.js.map +1 -0
  25. package/dist/benchmarks/mcad-golds.d.ts +20 -0
  26. package/dist/benchmarks/mcad-golds.js +318 -0
  27. package/dist/benchmarks/mcad-golds.js.map +1 -0
  28. package/dist/benchmarks/mcad-tasks.d.ts +66 -0
  29. package/dist/benchmarks/mcad-tasks.js +508 -0
  30. package/dist/benchmarks/mcad-tasks.js.map +1 -0
  31. package/dist/benchmarks/simpleqa.js +15 -19
  32. package/dist/benchmarks/simpleqa.js.map +1 -1
  33. package/dist/benchmarks/trata-hedge.js +20 -23
  34. package/dist/benchmarks/trata-hedge.js.map +1 -1
  35. package/dist/{cadbench-BrpwOU6A.js → cadbench-BLSyxR1N.js} +21 -20
  36. package/dist/cadbench-BLSyxR1N.js.map +1 -0
  37. package/dist/{cadgenbench-DF7hYHdl.js → cadgenbench-x2OFkf8y.js} +2 -2
  38. package/dist/cadgenbench-x2OFkf8y.js.map +1 -0
  39. package/dist/index.d.ts +0 -1
  40. package/dist/index.js +10 -9
  41. package/dist/index.js.map +1 -1
  42. package/dist/router-turn-C2wMiDoo.js +31 -0
  43. package/dist/router-turn-C2wMiDoo.js.map +1 -0
  44. package/package.json +7 -7
  45. package/scripts/appworld_driver.py +1 -170
  46. package/scripts/run-package-tests.mjs +56 -36
  47. package/scripts/run-package-tests.test.mjs +33 -0
  48. package/scripts/trata-hedge/README.md +3 -3
  49. package/scripts/trata-hedge/run.sh +6 -2
  50. package/src/adapters.ts +11 -0
  51. package/src/aec-gate.mts +30 -9
  52. package/src/agent-graphs-gen2.mts +523 -0
  53. package/src/agent-graphs-gen3.mts +660 -0
  54. package/src/agent-graphs-improve/offline-seams.mts +128 -0
  55. package/src/agent-graphs-improve.mts +747 -0
  56. package/src/atom-humaneval.mts +76 -55
  57. package/src/atom-mcp-e2e.mts +27 -13
  58. package/src/benchmarks/appworld.test.mts +119 -1
  59. package/src/benchmarks/appworld.ts +156 -33
  60. package/src/benchmarks/cadbench.ts +21 -8
  61. package/src/benchmarks/finresearchbench.ts +20 -16
  62. package/src/benchmarks/finsearchcomp.ts +20 -20
  63. package/src/benchmarks/frames.ts +19 -18
  64. package/src/benchmarks/mcad-bench.test.mts +455 -0
  65. package/src/benchmarks/mcad-bench.ts +561 -0
  66. package/src/benchmarks/mcad-cq-bench.ts +423 -0
  67. package/src/benchmarks/mcad-cq-golds.ts +374 -0
  68. package/src/benchmarks/mcad-cq.test.mts +386 -0
  69. package/src/benchmarks/mcad-golds.ts +359 -0
  70. package/src/benchmarks/mcad-tasks.ts +490 -0
  71. package/src/benchmarks/simpleqa.ts +19 -17
  72. package/src/benchmarks/trata-hedge.ts +26 -18
  73. package/src/browser/ui-reviewer.ts +1 -1
  74. package/src/clbench-context-gate.mts +45 -9
  75. package/src/cloud-loop.mts +24 -6
  76. package/src/commit0-env-run.mts +6 -3
  77. package/src/commit0-env.ts +2 -3
  78. package/src/commit0-gate.mts +53 -24
  79. package/src/corpus.ts +3 -3
  80. package/src/david-attribution.mts +28 -9
  81. package/src/david-goliath.mts +31 -12
  82. package/src/egress-probe.mts +3 -3
  83. package/src/examples/lean-proof-gate.mts +10 -3
  84. package/src/examples/math-demo.mts +10 -3
  85. package/src/examples/strategy-demo.mts +10 -4
  86. package/src/fleet.mts +30 -8
  87. package/src/gate-cli.mts +9 -4
  88. package/src/gate.test.mts +7 -2
  89. package/src/gate.ts +23 -75
  90. package/src/generate-eval/certify.ts +13 -5
  91. package/src/hev-eval.mts +39 -10
  92. package/src/hev-improve.mts +28 -16
  93. package/src/hev-structural.mts +28 -20
  94. package/src/humaneval-gate.mts +24 -8
  95. package/src/humaneval-object-ablation.mts +52 -14
  96. package/src/humaneval-repair-gate.mts +53 -14
  97. package/src/mbpp-structural.mts +28 -20
  98. package/src/mcp-mount-probe.mts +23 -11
  99. package/src/official-optimizer-config.mts +61 -12
  100. package/src/official-optimizer-config.test.mts +72 -3
  101. package/src/quant-arena/profiles/quant-researcher.profile.json +6 -1
  102. package/src/quant-arena/quant-loop.mts +88 -61
  103. package/src/research-shot.ts +21 -11
  104. package/src/resolve-client.ts +6 -8
  105. package/src/rollout-ledger/backfill-swe-arena.test.mts +7 -2
  106. package/src/router-executor.ts +1 -1
  107. package/src/router-turn.ts +135 -0
  108. package/src/run-benchmarks.ts +7 -2
  109. package/src/sandbox-run.ts +23 -41
  110. package/src/search-bench/bridge.ts +66 -8
  111. package/src/search-bench/parametric-check.mts +19 -4
  112. package/src/search-bench/profiles.ts +24 -9
  113. package/src/search-bench/run.mts +3 -4
  114. package/src/smoke-structural-rollout.mts +21 -5
  115. package/src/supervisor-arena.mts +28 -20
  116. package/src/swe-arena/arms.ts +73 -15
  117. package/src/swe-arena/capacity.ts +44 -41
  118. package/src/swe-arena/diagnosis-ensemble.ts +51 -48
  119. package/src/swe-arena/execution.test.mts +12 -12
  120. package/src/swe-arena/gepa-seat.mts +12 -5
  121. package/src/swe-arena/gepa-seat.test.mts +45 -33
  122. package/src/swe-arena/outer-loop.mts +94 -70
  123. package/src/swe-arena/premeasured-from-cells.mts +16 -1
  124. package/src/swe-arena/premeasured-from-cells.test.mts +21 -1
  125. package/src/swe-arena/profiles/deepseek-author.profile.json +12 -0
  126. package/src/swe-arena/profiles/default-author.profile.json +9 -1
  127. package/src/swe-arena/proposer-fanout.mts +52 -99
  128. package/src/swe-arena/proposer-fanout.test.mts +92 -57
  129. package/src/swe-arena/proposer-provenance.mts +1 -1
  130. package/src/swe-code-improve.mts +30 -52
  131. package/src/swe-emit-patch.mts +26 -5
  132. package/src/swe-improve.mts +27 -8
  133. package/src/swe-jail.test.ts +128 -0
  134. package/src/swe-jail.ts +106 -22
  135. package/src/swe-local-proof.mts +27 -5
  136. package/src/swe-repro-calibrate.mts +5 -0
  137. package/src/swe-self-improve.mts +45 -10
  138. package/src/swe-stream.mts +68 -6
  139. package/src/swe-structural.mts +46 -7
  140. package/src/tb-container-executor.mts +38 -6
  141. package/src/tb-container-executor.test.mts +11 -3
  142. package/src/tb-supervisor-sidecar.mts +11 -27
  143. package/src/terminal-compare.ts +38 -18
  144. package/src/trata-gate.mts +26 -26
  145. package/src/trata-gepa.mts +25 -16
  146. package/src/trata-hedge-solve.mts +80 -0
  147. package/src/worker-blender.ts +21 -11
  148. package/src/worker-browser.ts +18 -5
  149. package/src/worker-build123d.ts +17 -8
  150. package/src/worker-cad.ts +31 -12
  151. package/src/worker.ts +1 -1
  152. package/tb_agents/opencode_router_agent.py +84 -42
  153. package/dist/cadbench-BrpwOU6A.js.map +0 -1
  154. package/dist/cadgenbench-DF7hYHdl.js.map +0 -1
  155. package/scripts/trata-hedge/solve.py +0 -83
@@ -0,0 +1,82 @@
1
+ import { BenchmarkAdapter } from "./types.js";
2
+ import { McadTask } from "./mcad-tasks.js";
3
+ import { McadGeometry, McadScoring, Tri } from "./mcad-bench.js";
4
+ //#region src/benchmarks/mcad-cq-bench.d.ts
5
+ /** Package-relative venv, resolved through `_harness`'s bench-root lookup. */
6
+ declare const CADQUERY_VENV = ".venv-cadquery";
7
+ /** Files the worker's script must leave in its working directory. */
8
+ declare const STL_NAME = "part.stl";
9
+ declare const STEP_NAME = "part.step";
10
+ /** Every ISO 10303-21 exchange file (a "STEP file") starts with this token. */
11
+ declare const STEP_MAGIC = "ISO-10303-21";
12
+ /** Hard deadline for one worker script. A hang is a miss, not a stall. */
13
+ declare const MCAD_CQ_TIMEOUT_MS = 120000;
14
+ /**
15
+ * The exact, VERIFIED install command. Two pins here are load-bearing and neither
16
+ * is optional on a current host:
17
+ * - `--python 3.12`: cadquery 2.4.0 resolves to `cadquery-ocp` 7.7.2, which ships
18
+ * wheels for cp38–cp312 only. On a default CPython 3.13 the resolve fails.
19
+ * - `'numpy<2'`: cadquery 2.4.0 pins `nptyping==2.0.1`, whose module body reads
20
+ * `np.bool8` — removed in numpy 2. Without the pin `import cadquery` raises
21
+ * `AttributeError: module 'numpy' has no attribute 'bool8'` at import time.
22
+ */
23
+ declare const CADQUERY_INSTALL_FIX: string;
24
+ /**
25
+ * Resolve the CadQuery interpreter. Never falls back to a system `python`: a
26
+ * silent fallback would either fail deep inside a worker script or — worse — run
27
+ * against a different CadQuery than the golds were calibrated on.
28
+ */
29
+ declare function resolveCadqueryPython(env?: Readonly<{
30
+ MCAD_CQ_PYTHON?: string;
31
+ }>): string;
32
+ /** The one message shape for "there is no usable interpreter" — HARNESS.md style. */
33
+ declare function cadqueryMissingError(python: string, cause?: string): Error;
34
+ /** v2 replacement for `MCAD_DELIVERABLE`. */
35
+ declare const MCAD_CQ_DELIVERABLE: string;
36
+ /** v2 replacement for `MCAD_DELIVERABLE_MULTIBODY` — task 10 only, same reason as v1. */
37
+ declare const MCAD_CQ_DELIVERABLE_MULTIBODY: string;
38
+ /**
39
+ * Swap the deliverable preamble for the CadQuery one, leaving the upstream
40
+ * dimensional text byte-identical. Fails loud rather than guessing: a task whose
41
+ * prompt does not open with one of v1's two known preambles means v1 changed
42
+ * shape, and silently shipping a prompt with two contradictory deliverables is
43
+ * exactly the failure this function exists to prevent.
44
+ */
45
+ declare function toCadQueryPrompt(task: McadTask): string;
46
+ /**
47
+ * Reject anything that is not an ASCII STL. A binary STL also opens with the
48
+ * bytes `solid` often enough that the header alone is not a test, so the NUL scan
49
+ * carries the decision: ASCII STL is printable text end to end.
50
+ */
51
+ declare function asciiStlProblem(buf: Buffer): string | undefined;
52
+ /** `part.step` exists AND opens with the ISO 10303-21 magic. Reads 12 bytes, not the file. */
53
+ declare function stepFileEmitted(path: string): Promise<boolean>;
54
+ type McadCqBuild = {
55
+ ok: true;
56
+ geo: McadGeometry;
57
+ tris: Tri[];
58
+ dir: string;
59
+ stlPath: string;
60
+ stepEmitted: boolean;
61
+ } | {
62
+ ok: false;
63
+ detail: string;
64
+ dir: string;
65
+ stepEmitted: boolean;
66
+ };
67
+ /**
68
+ * Run one CadQuery script and measure what it delivered. Judge-time only.
69
+ * Throws (never scores) when there is no usable interpreter — an absent toolchain
70
+ * is an operator defect, and scoring it 0 would silently poison a whole corpus.
71
+ */
72
+ declare function runAndMeasureCadQuery(src: string, timeoutMs?: number): Promise<McadCqBuild>;
73
+ /**
74
+ * Append the STEP-delivery check to a v1 scoring and recompute. It is one check
75
+ * among the spec's, deliberately: the upstream benchmark's deliverable IS a STEP
76
+ * file, so failing to produce one costs exactly what failing a dimension costs.
77
+ */
78
+ declare function withStepCheck(scored: McadScoring, stepEmitted: boolean): McadScoring;
79
+ declare function createMcadCqAdapter(): BenchmarkAdapter;
80
+ //#endregion
81
+ export { CADQUERY_INSTALL_FIX, CADQUERY_VENV, MCAD_CQ_DELIVERABLE, MCAD_CQ_DELIVERABLE_MULTIBODY, MCAD_CQ_TIMEOUT_MS, McadCqBuild, STEP_MAGIC, STEP_NAME, STL_NAME, asciiStlProblem, cadqueryMissingError, createMcadCqAdapter, resolveCadqueryPython, runAndMeasureCadQuery, stepFileEmitted, toCadQueryPrompt, withStepCheck };
82
+ //# sourceMappingURL=mcad-cq-bench.d.ts.map
@@ -0,0 +1,339 @@
1
+ import { venvPythonAt } from "./_harness.js";
2
+ import { MCAD_DELIVERABLE, MCAD_DELIVERABLE_MULTIBODY, MCAD_TASKS } from "./mcad-tasks.js";
3
+ import { measureMesh, parseAsciiStl, scoreAgainstSpec, stripCodeFence } from "./mcad-bench.js";
4
+ import { MCAD_CQ_GOLDS, MCAD_CQ_UNCALIBRATED } from "./mcad-cq-golds.js";
5
+ import { mkdtemp, open, readFile, writeFile } from "node:fs/promises";
6
+ import { isAbsolute, join } from "node:path";
7
+ import { execFile } from "node:child_process";
8
+ import { existsSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { promisify } from "node:util";
11
+ //#region src/benchmarks/mcad-cq-bench.ts
12
+ /**
13
+ * MCAD-CQ adapter — the v2 runner that closes v1's format deviation.
14
+ *
15
+ * `mcad-tasks.ts` states the deviation in its header: the upstream text-to-cad
16
+ * benchmark asks for STEP, and OpenSCAD cannot emit it, so v1 grades an STL that
17
+ * OpenSCAD compiled. This adapter runs the SAME ten dimensioned parts against the
18
+ * SAME spec assertions, but the worker's artifact is a Python CadQuery script that
19
+ * must export BOTH `part.step` (the format upstream actually asks for) and
20
+ * `part.stl` (ASCII — what the geometry engine measures).
21
+ *
22
+ * Everything downstream of the mesh is v1's engine, IMPORTED not copied:
23
+ * `parseAsciiStl` / `measureMesh` / `pointInSolid` / `scoreAgainstSpec` /
24
+ * `stripCodeFence` all come from `./mcad-bench`, and the specs come from
25
+ * `./mcad-tasks`. Only two things differ:
26
+ * 1. the deliverable preamble (Python + CadQuery, not OpenSCAD) — applied by
27
+ * mapping at load time, so v1's prompt strings are never mutated;
28
+ * 2. one EXTRA scored check, `stepEmitted`: `part.step` exists and begins with
29
+ * `ISO-10303-21` (the ISO 10303-21 exchange-file magic every STEP file
30
+ * opens with). It is scored like any other assertion, so a script that
31
+ * emits only the mesh cannot reach 1.0 no matter how correct its geometry.
32
+ *
33
+ * Pipeline, in order:
34
+ * 1. run : `<python> model.py` in a fresh temp dir, 120 s hard deadline
35
+ * 2. deliver : `part.stl` exists and is ASCII (a binary STL is a miss, not a
36
+ * parse problem — the prompt pins the format)
37
+ * 3. watertight: every undirected edge shared by exactly two faces
38
+ * 4. measure : bbox / volume / connected components / triangle count
39
+ * 5. probe : ray-parity point-in-solid at the spec's pinned coordinates
40
+ * 6. step : the `stepEmitted` check above
41
+ * A non-watertight mesh scores 0 for the same reason as v1: an unclosed surface
42
+ * has no well-defined interior, so every downstream number would be fiction.
43
+ *
44
+ * SAFETY, stated once and deliberately. The judge EXECUTES the worker's Python on
45
+ * the host with no sandbox. That is the same trust level as v1 handing arbitrary
46
+ * source to the OpenSCAD kernel, and it carries the same rule: run this judge only
47
+ * on artifacts you would be willing to run by hand. The child process is given a
48
+ * scrubbed environment — no inherited variables beyond `PATH`, `HOME`/`TMPDIR`
49
+ * pointed at the scratch directory, and proxy variables pointed at a closed port —
50
+ * which DISCOURAGES network access and keeps host secrets out of the script's
51
+ * reach. It is not a network jail and does not claim to be one; put the judge in a
52
+ * container if you need that guarantee.
53
+ *
54
+ * Requires an interpreter that can `import cadquery` at JUDGE time only.
55
+ * Resolution order: `MCAD_CQ_PYTHON` (absolute path) → `bench/.venv-cadquery/bin/python`
56
+ * → throw with the exact install command (see `CADQUERY_INSTALL_FIX`).
57
+ */
58
+ const execFileAsync = promisify(execFile);
59
+ /** Package-relative venv, resolved through `_harness`'s bench-root lookup. */
60
+ const CADQUERY_VENV = ".venv-cadquery";
61
+ /** Files the worker's script must leave in its working directory. */
62
+ const STL_NAME = "part.stl";
63
+ const STEP_NAME = "part.step";
64
+ /** Every ISO 10303-21 exchange file (a "STEP file") starts with this token. */
65
+ const STEP_MAGIC = "ISO-10303-21";
66
+ /** Hard deadline for one worker script. A hang is a miss, not a stall. */
67
+ const MCAD_CQ_TIMEOUT_MS = 12e4;
68
+ /**
69
+ * The exact, VERIFIED install command. Two pins here are load-bearing and neither
70
+ * is optional on a current host:
71
+ * - `--python 3.12`: cadquery 2.4.0 resolves to `cadquery-ocp` 7.7.2, which ships
72
+ * wheels for cp38–cp312 only. On a default CPython 3.13 the resolve fails.
73
+ * - `'numpy<2'`: cadquery 2.4.0 pins `nptyping==2.0.1`, whose module body reads
74
+ * `np.bool8` — removed in numpy 2. Without the pin `import cadquery` raises
75
+ * `AttributeError: module 'numpy' has no attribute 'bool8'` at import time.
76
+ */
77
+ const CADQUERY_INSTALL_FIX = "Fix: create the isolated CadQuery venv (needs `uv`):\n cd bench && uv venv --python 3.12 .venv-cadquery \\\n && uv pip install --python .venv-cadquery/bin/python 'cadquery==2.4.0' 'numpy<2'\nBoth pins are required: cadquery-ocp 7.7.2 has no cp313 wheel, and cadquery 2.4.0 pins nptyping 2.0.1, which reads the numpy-1.x-only `np.bool8` at import.\nOr set MCAD_CQ_PYTHON to the ABSOLUTE path of any interpreter that can `import cadquery`.";
78
+ /**
79
+ * Resolve the CadQuery interpreter. Never falls back to a system `python`: a
80
+ * silent fallback would either fail deep inside a worker script or — worse — run
81
+ * against a different CadQuery than the golds were calibrated on.
82
+ */
83
+ function resolveCadqueryPython(env = process.env) {
84
+ const configured = env.MCAD_CQ_PYTHON;
85
+ if (configured === void 0) return venvPythonAt(CADQUERY_VENV);
86
+ if (!isAbsolute(configured)) throw new Error(`MCAD_CQ_PYTHON must be an absolute path (got ${JSON.stringify(configured)})`);
87
+ return configured;
88
+ }
89
+ /** The one message shape for "there is no usable interpreter" — HARNESS.md style. */
90
+ function cadqueryMissingError(python, cause) {
91
+ return /* @__PURE__ */ new Error(`mcad-cq: no CadQuery interpreter at ${python}${cause ? `\n${cause}` : ""}\n${CADQUERY_INSTALL_FIX}`);
92
+ }
93
+ /** Shared tail: what the script must WRITE, which is the whole deliverable contract. */
94
+ const CQ_DELIVERABLE_TAIL = `The script must run standalone under \`python model.py\` and write BOTH of these files into its current working directory: \`${STEP_NAME}\` (STEP) and \`${STL_NAME}\` (ASCII STL, not binary). Export with exactly these two lines:
95
+ ${`cq.exporters.export(result, "${STEP_NAME}")`}\n ${`cq.exporters.export(result, "${STL_NAME}", exportType="STL", opt={"ascii": True}, tolerance=0.01, angularTolerance=0.05)`}\nThose tessellation tolerances are what make round features accurate — do not coarsen them. Reply with ONLY the Python code.`;
96
+ /** v2 replacement for `MCAD_DELIVERABLE`. */
97
+ const MCAD_CQ_DELIVERABLE = "Author a Python script using CadQuery (units: mm) that builds exactly this part as one fused solid and binds it to a variable named `result`. " + CQ_DELIVERABLE_TAIL;
98
+ /** v2 replacement for `MCAD_DELIVERABLE_MULTIBODY` — task 10 only, same reason as v1. */
99
+ const MCAD_CQ_DELIVERABLE_MULTIBODY = "Author a Python script using CadQuery (units: mm) that builds exactly this assembly as the separate solid bodies listed below and binds them to a variable named `result` (a `cq.Compound` of those bodies, or a Workplane holding all of them). The bodies must stay disjoint — no two of them may touch or intersect. " + CQ_DELIVERABLE_TAIL;
100
+ /**
101
+ * Swap the deliverable preamble for the CadQuery one, leaving the upstream
102
+ * dimensional text byte-identical. Fails loud rather than guessing: a task whose
103
+ * prompt does not open with one of v1's two known preambles means v1 changed
104
+ * shape, and silently shipping a prompt with two contradictory deliverables is
105
+ * exactly the failure this function exists to prevent.
106
+ */
107
+ function toCadQueryPrompt(task) {
108
+ for (const [v1, v2] of [[MCAD_DELIVERABLE_MULTIBODY, MCAD_CQ_DELIVERABLE_MULTIBODY], [MCAD_DELIVERABLE, MCAD_CQ_DELIVERABLE]]) if (task.prompt.startsWith(v1)) return v2 + task.prompt.slice(v1.length);
109
+ throw new Error(`mcad-cq: task ${task.id} does not open with a known mcad deliverable preamble; mcad-tasks.ts changed shape and the CadQuery prompt mapping must be updated with it`);
110
+ }
111
+ /**
112
+ * The child's whole environment. Nothing is inherited except `PATH` (the
113
+ * interpreter needs its own toolchain on it) and `LANG`. `HOME`/`TMPDIR` point at
114
+ * the scratch dir so the script cannot write to the operator's home, and the proxy
115
+ * variables point at port 9 (discard), which every mainstream HTTP client honours.
116
+ * DISCOURAGEMENT, not a jail — see the file header.
117
+ */
118
+ function scriptEnv(dir) {
119
+ const blackhole = "http://127.0.0.1:9";
120
+ return {
121
+ PATH: process.env.PATH ?? "/usr/bin:/bin",
122
+ LANG: process.env.LANG ?? "C.UTF-8",
123
+ HOME: dir,
124
+ TMPDIR: dir,
125
+ PYTHONDONTWRITEBYTECODE: "1",
126
+ PYTHONHASHSEED: "0",
127
+ http_proxy: blackhole,
128
+ https_proxy: blackhole,
129
+ HTTP_PROXY: blackhole,
130
+ HTTPS_PROXY: blackhole,
131
+ no_proxy: "",
132
+ NO_PROXY: ""
133
+ };
134
+ }
135
+ /**
136
+ * Reject anything that is not an ASCII STL. A binary STL also opens with the
137
+ * bytes `solid` often enough that the header alone is not a test, so the NUL scan
138
+ * carries the decision: ASCII STL is printable text end to end.
139
+ */
140
+ function asciiStlProblem(buf) {
141
+ if (buf.length === 0) return `${STL_NAME} is empty`;
142
+ if (buf.includes(0)) return `${STL_NAME} is not ASCII (contains NUL bytes — it looks like a binary STL)`;
143
+ const head = buf.subarray(0, 64).toString("latin1");
144
+ if (!/^\s*solid\b/.test(head)) return `${STL_NAME} does not start with "solid" (got ${JSON.stringify(head.slice(0, 32))})`;
145
+ if (!/\bfacet\s+normal\b/.test(buf.toString("utf8"))) return `${STL_NAME} contains no "facet normal" records`;
146
+ }
147
+ /** `part.step` exists AND opens with the ISO 10303-21 magic. Reads 12 bytes, not the file. */
148
+ async function stepFileEmitted(path) {
149
+ let handle;
150
+ try {
151
+ handle = await open(path, "r");
152
+ } catch {
153
+ return false;
154
+ }
155
+ try {
156
+ const buf = Buffer.alloc(12);
157
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
158
+ return bytesRead === buf.length && buf.toString("latin1") === "ISO-10303-21";
159
+ } finally {
160
+ await handle.close();
161
+ }
162
+ }
163
+ /**
164
+ * Run one CadQuery script and measure what it delivered. Judge-time only.
165
+ * Throws (never scores) when there is no usable interpreter — an absent toolchain
166
+ * is an operator defect, and scoring it 0 would silently poison a whole corpus.
167
+ */
168
+ async function runAndMeasureCadQuery(src, timeoutMs = MCAD_CQ_TIMEOUT_MS) {
169
+ const python = resolveCadqueryPython();
170
+ if (!existsSync(python)) throw cadqueryMissingError(python);
171
+ const dir = await mkdtemp(join(tmpdir(), "mcad-cq-"));
172
+ const stlPath = join(dir, STL_NAME);
173
+ const stepPath = join(dir, STEP_NAME);
174
+ await writeFile(join(dir, "model.py"), `${src}\n`);
175
+ const fail = async (detail) => ({
176
+ ok: false,
177
+ detail,
178
+ dir,
179
+ stepEmitted: await stepFileEmitted(stepPath)
180
+ });
181
+ try {
182
+ await execFileAsync(python, ["model.py"], {
183
+ cwd: dir,
184
+ env: scriptEnv(dir),
185
+ timeout: timeoutMs,
186
+ killSignal: "SIGKILL",
187
+ maxBuffer: 1024 * 1024 * 64
188
+ });
189
+ } catch (err) {
190
+ const e = err;
191
+ if (e.killed) return fail(`script timed out after ${timeoutMs} ms and was killed`);
192
+ return fail(`script failed: ${(e.stderr ?? e.message ?? "").trim().slice(-800)}`);
193
+ }
194
+ let stl;
195
+ try {
196
+ stl = await readFile(stlPath);
197
+ } catch {
198
+ return fail(`script wrote no ${STL_NAME} in its working directory`);
199
+ }
200
+ const bad = asciiStlProblem(stl);
201
+ if (bad) return fail(bad);
202
+ const tris = parseAsciiStl(stl.toString("utf8"));
203
+ if (tris.length === 0) return fail(`${STL_NAME} parsed to zero triangles (empty geometry)`);
204
+ return {
205
+ ok: true,
206
+ geo: measureMesh(tris),
207
+ tris,
208
+ dir,
209
+ stlPath,
210
+ stepEmitted: await stepFileEmitted(stepPath)
211
+ };
212
+ }
213
+ /**
214
+ * Append the STEP-delivery check to a v1 scoring and recompute. It is one check
215
+ * among the spec's, deliberately: the upstream benchmark's deliverable IS a STEP
216
+ * file, so failing to produce one costs exactly what failing a dimension costs.
217
+ */
218
+ function withStepCheck(scored, stepEmitted) {
219
+ const check = {
220
+ name: "stepEmitted",
221
+ ok: stepEmitted,
222
+ measured: stepEmitted ? `${STEP_NAME} starts with ${STEP_MAGIC}` : `no ${STEP_NAME} starting with ${STEP_MAGIC}`,
223
+ expected: `${STEP_NAME} exists and starts with ${STEP_MAGIC}`
224
+ };
225
+ const checks = [...scored.checks, check];
226
+ const failed = checks.filter((c) => !c.ok);
227
+ return {
228
+ checks,
229
+ failed,
230
+ score: (checks.length - failed.length) / checks.length,
231
+ resolved: failed.length === 0
232
+ };
233
+ }
234
+ function createMcadCqAdapter() {
235
+ return {
236
+ name: "mcad-cq",
237
+ async preflight() {
238
+ const python = resolveCadqueryPython();
239
+ if (!existsSync(python)) throw cadqueryMissingError(python);
240
+ try {
241
+ await execFileAsync(python, ["-c", "import cadquery; print(cadquery.__version__)"], {
242
+ timeout: 3e5,
243
+ env: scriptEnv(tmpdir())
244
+ });
245
+ } catch (err) {
246
+ const e = err;
247
+ throw cadqueryMissingError(python, `\`import cadquery\` failed: ${(e.stderr ?? e.message ?? "").trim().slice(-800)}`);
248
+ }
249
+ },
250
+ async loadTasks(opts = {}) {
251
+ let tasks = MCAD_TASKS;
252
+ if (opts.ids) tasks = tasks.filter((t) => opts.ids?.includes(t.id));
253
+ if (opts.limit != null) tasks = tasks.slice(0, opts.limit);
254
+ return tasks.map((t) => ({
255
+ id: t.id,
256
+ prompt: toCadQueryPrompt(t),
257
+ metadata: {
258
+ spec: t.spec,
259
+ source: t.source,
260
+ calibrated: t.calibrated && !MCAD_CQ_UNCALIBRATED.has(t.id)
261
+ }
262
+ }));
263
+ },
264
+ async goldArtifact(task) {
265
+ return MCAD_CQ_GOLDS[task.id];
266
+ },
267
+ async judge(task, artifact) {
268
+ const spec = task.metadata?.spec;
269
+ if (!spec) return {
270
+ resolved: false,
271
+ score: 0,
272
+ detail: `task ${task.id} carries no mcad spec`
273
+ };
274
+ const src = stripCodeFence(artifact);
275
+ if (!src) return {
276
+ resolved: false,
277
+ score: 0,
278
+ detail: "empty artifact"
279
+ };
280
+ const built = await runAndMeasureCadQuery(src);
281
+ if (!built.ok) return {
282
+ resolved: false,
283
+ score: 0,
284
+ detail: `${built.detail} [dir ${built.dir}, stepEmitted ${built.stepEmitted}]`
285
+ };
286
+ const { geo, tris, stlPath, stepEmitted, dir } = built;
287
+ if (!geo.watertight) return {
288
+ resolved: false,
289
+ score: 0,
290
+ detail: JSON.stringify({
291
+ gate: "watertight",
292
+ failed: [{
293
+ name: "watertight",
294
+ measured: `${geo.openEdges} edge(s) not shared by exactly two faces`,
295
+ expected: "0 open edges (closed 2-manifold solid)"
296
+ }],
297
+ geo: {
298
+ triangles: geo.triangles,
299
+ degenerateFaces: geo.degenerateFaces
300
+ },
301
+ stepEmitted,
302
+ stlPath,
303
+ dir
304
+ })
305
+ };
306
+ const scored = withStepCheck(scoreAgainstSpec(geo, tris, spec), stepEmitted);
307
+ return {
308
+ resolved: scored.resolved,
309
+ score: scored.score,
310
+ detail: JSON.stringify({
311
+ failed: scored.failed.map((c) => ({
312
+ name: c.name,
313
+ measured: c.measured,
314
+ expected: c.expected
315
+ })),
316
+ checks: Object.fromEntries(scored.checks.map((c) => [c.name, c.ok])),
317
+ geo: {
318
+ triangles: geo.triangles,
319
+ solids: geo.solids,
320
+ volume: +geo.volume.toFixed(2),
321
+ degenerateFaces: geo.degenerateFaces,
322
+ bbox: {
323
+ x: +geo.bbox.size.x.toFixed(3),
324
+ y: +geo.bbox.size.y.toFixed(3),
325
+ z: +geo.bbox.size.z.toFixed(3)
326
+ }
327
+ },
328
+ stepEmitted,
329
+ stlPath,
330
+ dir
331
+ })
332
+ };
333
+ }
334
+ };
335
+ }
336
+ //#endregion
337
+ export { CADQUERY_INSTALL_FIX, CADQUERY_VENV, MCAD_CQ_DELIVERABLE, MCAD_CQ_DELIVERABLE_MULTIBODY, MCAD_CQ_TIMEOUT_MS, STEP_MAGIC, STEP_NAME, STL_NAME, asciiStlProblem, cadqueryMissingError, createMcadCqAdapter, resolveCadqueryPython, runAndMeasureCadQuery, stepFileEmitted, toCadQueryPrompt, withStepCheck };
338
+
339
+ //# sourceMappingURL=mcad-cq-bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcad-cq-bench.js","names":[],"sources":["../../src/benchmarks/mcad-cq-bench.ts"],"sourcesContent":["/**\n * MCAD-CQ adapter — the v2 runner that closes v1's format deviation.\n *\n * `mcad-tasks.ts` states the deviation in its header: the upstream text-to-cad\n * benchmark asks for STEP, and OpenSCAD cannot emit it, so v1 grades an STL that\n * OpenSCAD compiled. This adapter runs the SAME ten dimensioned parts against the\n * SAME spec assertions, but the worker's artifact is a Python CadQuery script that\n * must export BOTH `part.step` (the format upstream actually asks for) and\n * `part.stl` (ASCII — what the geometry engine measures).\n *\n * Everything downstream of the mesh is v1's engine, IMPORTED not copied:\n * `parseAsciiStl` / `measureMesh` / `pointInSolid` / `scoreAgainstSpec` /\n * `stripCodeFence` all come from `./mcad-bench`, and the specs come from\n * `./mcad-tasks`. Only two things differ:\n * 1. the deliverable preamble (Python + CadQuery, not OpenSCAD) — applied by\n * mapping at load time, so v1's prompt strings are never mutated;\n * 2. one EXTRA scored check, `stepEmitted`: `part.step` exists and begins with\n * `ISO-10303-21` (the ISO 10303-21 exchange-file magic every STEP file\n * opens with). It is scored like any other assertion, so a script that\n * emits only the mesh cannot reach 1.0 no matter how correct its geometry.\n *\n * Pipeline, in order:\n * 1. run : `<python> model.py` in a fresh temp dir, 120 s hard deadline\n * 2. deliver : `part.stl` exists and is ASCII (a binary STL is a miss, not a\n * parse problem — the prompt pins the format)\n * 3. watertight: every undirected edge shared by exactly two faces\n * 4. measure : bbox / volume / connected components / triangle count\n * 5. probe : ray-parity point-in-solid at the spec's pinned coordinates\n * 6. step : the `stepEmitted` check above\n * A non-watertight mesh scores 0 for the same reason as v1: an unclosed surface\n * has no well-defined interior, so every downstream number would be fiction.\n *\n * SAFETY, stated once and deliberately. The judge EXECUTES the worker's Python on\n * the host with no sandbox. That is the same trust level as v1 handing arbitrary\n * source to the OpenSCAD kernel, and it carries the same rule: run this judge only\n * on artifacts you would be willing to run by hand. The child process is given a\n * scrubbed environment — no inherited variables beyond `PATH`, `HOME`/`TMPDIR`\n * pointed at the scratch directory, and proxy variables pointed at a closed port —\n * which DISCOURAGES network access and keeps host secrets out of the script's\n * reach. It is not a network jail and does not claim to be one; put the judge in a\n * container if you need that guarantee.\n *\n * Requires an interpreter that can `import cadquery` at JUDGE time only.\n * Resolution order: `MCAD_CQ_PYTHON` (absolute path) → `bench/.venv-cadquery/bin/python`\n * → throw with the exact install command (see `CADQUERY_INSTALL_FIX`).\n */\n\nimport { execFile } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { mkdtemp, open, readFile, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { isAbsolute, join } from 'node:path'\nimport { promisify } from 'node:util'\nimport { venvPythonAt } from './_harness'\nimport {\n type McadCheck,\n type McadGeometry,\n type McadScoring,\n type McadTaskMeta,\n type Tri,\n measureMesh,\n parseAsciiStl,\n scoreAgainstSpec,\n stripCodeFence,\n} from './mcad-bench'\nimport { MCAD_CQ_GOLDS, MCAD_CQ_UNCALIBRATED } from './mcad-cq-golds'\nimport { MCAD_DELIVERABLE, MCAD_DELIVERABLE_MULTIBODY, MCAD_TASKS, type McadTask } from './mcad-tasks'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\n\nconst execFileAsync = promisify(execFile)\n\n/** Package-relative venv, resolved through `_harness`'s bench-root lookup. */\nexport const CADQUERY_VENV = '.venv-cadquery'\n\n/** Files the worker's script must leave in its working directory. */\nexport const STL_NAME = 'part.stl'\nexport const STEP_NAME = 'part.step'\n\n/** Every ISO 10303-21 exchange file (a \"STEP file\") starts with this token. */\nexport const STEP_MAGIC = 'ISO-10303-21'\n\n/** Hard deadline for one worker script. A hang is a miss, not a stall. */\nexport const MCAD_CQ_TIMEOUT_MS = 120_000\n\n/**\n * The exact, VERIFIED install command. Two pins here are load-bearing and neither\n * is optional on a current host:\n * - `--python 3.12`: cadquery 2.4.0 resolves to `cadquery-ocp` 7.7.2, which ships\n * wheels for cp38–cp312 only. On a default CPython 3.13 the resolve fails.\n * - `'numpy<2'`: cadquery 2.4.0 pins `nptyping==2.0.1`, whose module body reads\n * `np.bool8` — removed in numpy 2. Without the pin `import cadquery` raises\n * `AttributeError: module 'numpy' has no attribute 'bool8'` at import time.\n */\nexport const CADQUERY_INSTALL_FIX =\n 'Fix: create the isolated CadQuery venv (needs `uv`):\\n' +\n ' cd bench && uv venv --python 3.12 .venv-cadquery \\\\\\n' +\n \" && uv pip install --python .venv-cadquery/bin/python 'cadquery==2.4.0' 'numpy<2'\\n\" +\n 'Both pins are required: cadquery-ocp 7.7.2 has no cp313 wheel, and cadquery 2.4.0 pins ' +\n \"nptyping 2.0.1, which reads the numpy-1.x-only `np.bool8` at import.\\n\" +\n 'Or set MCAD_CQ_PYTHON to the ABSOLUTE path of any interpreter that can `import cadquery`.'\n\n/**\n * Resolve the CadQuery interpreter. Never falls back to a system `python`: a\n * silent fallback would either fail deep inside a worker script or — worse — run\n * against a different CadQuery than the golds were calibrated on.\n */\nexport function resolveCadqueryPython(\n env: Readonly<{ MCAD_CQ_PYTHON?: string }> = process.env,\n): string {\n const configured = env.MCAD_CQ_PYTHON\n if (configured === undefined) return venvPythonAt(CADQUERY_VENV)\n if (!isAbsolute(configured)) throw new Error(`MCAD_CQ_PYTHON must be an absolute path (got ${JSON.stringify(configured)})`)\n return configured\n}\n\n/** The one message shape for \"there is no usable interpreter\" — HARNESS.md style. */\nexport function cadqueryMissingError(python: string, cause?: string): Error {\n return new Error(\n `mcad-cq: no CadQuery interpreter at ${python}${cause ? `\\n${cause}` : ''}\\n${CADQUERY_INSTALL_FIX}`,\n )\n}\n\n// ---------------------------------------------------------------------------\n// Prompt mapping (v1's prompt strings are read, never written)\n// ---------------------------------------------------------------------------\n\nconst CQ_EXPORT_STEP = `cq.exporters.export(result, \"${STEP_NAME}\")`\nconst CQ_EXPORT_STL =\n `cq.exporters.export(result, \"${STL_NAME}\", exportType=\"STL\", opt={\"ascii\": True}, ` +\n 'tolerance=0.01, angularTolerance=0.05)'\n\n/** Shared tail: what the script must WRITE, which is the whole deliverable contract. */\nconst CQ_DELIVERABLE_TAIL =\n 'The script must run standalone under `python model.py` and write BOTH of these files into its ' +\n `current working directory: \\`${STEP_NAME}\\` (STEP) and \\`${STL_NAME}\\` (ASCII STL, not binary). ` +\n 'Export with exactly these two lines:\\n' +\n ` ${CQ_EXPORT_STEP}\\n` +\n ` ${CQ_EXPORT_STL}\\n` +\n 'Those tessellation tolerances are what make round features accurate — do not coarsen them. ' +\n 'Reply with ONLY the Python code.'\n\n/** v2 replacement for `MCAD_DELIVERABLE`. */\nexport const MCAD_CQ_DELIVERABLE =\n 'Author a Python script using CadQuery (units: mm) that builds exactly this part as one fused solid ' +\n 'and binds it to a variable named `result`. ' +\n CQ_DELIVERABLE_TAIL\n\n/** v2 replacement for `MCAD_DELIVERABLE_MULTIBODY` — task 10 only, same reason as v1. */\nexport const MCAD_CQ_DELIVERABLE_MULTIBODY =\n 'Author a Python script using CadQuery (units: mm) that builds exactly this assembly as the separate ' +\n 'solid bodies listed below and binds them to a variable named `result` (a `cq.Compound` of those bodies, ' +\n 'or a Workplane holding all of them). The bodies must stay disjoint — no two of them may touch or ' +\n 'intersect. ' +\n CQ_DELIVERABLE_TAIL\n\n/**\n * Swap the deliverable preamble for the CadQuery one, leaving the upstream\n * dimensional text byte-identical. Fails loud rather than guessing: a task whose\n * prompt does not open with one of v1's two known preambles means v1 changed\n * shape, and silently shipping a prompt with two contradictory deliverables is\n * exactly the failure this function exists to prevent.\n */\nexport function toCadQueryPrompt(task: McadTask): string {\n for (const [v1, v2] of [\n [MCAD_DELIVERABLE_MULTIBODY, MCAD_CQ_DELIVERABLE_MULTIBODY],\n [MCAD_DELIVERABLE, MCAD_CQ_DELIVERABLE],\n ] as const) {\n if (task.prompt.startsWith(v1)) return v2 + task.prompt.slice(v1.length)\n }\n throw new Error(\n `mcad-cq: task ${task.id} does not open with a known mcad deliverable preamble; ` +\n 'mcad-tasks.ts changed shape and the CadQuery prompt mapping must be updated with it',\n )\n}\n\n// ---------------------------------------------------------------------------\n// Running the worker's script\n// ---------------------------------------------------------------------------\n\n/**\n * The child's whole environment. Nothing is inherited except `PATH` (the\n * interpreter needs its own toolchain on it) and `LANG`. `HOME`/`TMPDIR` point at\n * the scratch dir so the script cannot write to the operator's home, and the proxy\n * variables point at port 9 (discard), which every mainstream HTTP client honours.\n * DISCOURAGEMENT, not a jail — see the file header.\n */\nfunction scriptEnv(dir: string): NodeJS.ProcessEnv {\n const blackhole = 'http://127.0.0.1:9'\n return {\n PATH: process.env.PATH ?? '/usr/bin:/bin',\n LANG: process.env.LANG ?? 'C.UTF-8',\n HOME: dir,\n TMPDIR: dir,\n PYTHONDONTWRITEBYTECODE: '1',\n PYTHONHASHSEED: '0',\n http_proxy: blackhole,\n https_proxy: blackhole,\n HTTP_PROXY: blackhole,\n HTTPS_PROXY: blackhole,\n no_proxy: '',\n NO_PROXY: '',\n }\n}\n\n/**\n * Reject anything that is not an ASCII STL. A binary STL also opens with the\n * bytes `solid` often enough that the header alone is not a test, so the NUL scan\n * carries the decision: ASCII STL is printable text end to end.\n */\nexport function asciiStlProblem(buf: Buffer): string | undefined {\n if (buf.length === 0) return `${STL_NAME} is empty`\n if (buf.includes(0)) return `${STL_NAME} is not ASCII (contains NUL bytes — it looks like a binary STL)`\n const head = buf.subarray(0, 64).toString('latin1')\n if (!/^\\s*solid\\b/.test(head)) return `${STL_NAME} does not start with \"solid\" (got ${JSON.stringify(head.slice(0, 32))})`\n if (!/\\bfacet\\s+normal\\b/.test(buf.toString('utf8'))) return `${STL_NAME} contains no \"facet normal\" records`\n return undefined\n}\n\n/** `part.step` exists AND opens with the ISO 10303-21 magic. Reads 12 bytes, not the file. */\nexport async function stepFileEmitted(path: string): Promise<boolean> {\n let handle: Awaited<ReturnType<typeof open>>\n try {\n handle = await open(path, 'r')\n } catch {\n return false\n }\n try {\n const buf = Buffer.alloc(STEP_MAGIC.length)\n const { bytesRead } = await handle.read(buf, 0, buf.length, 0)\n return bytesRead === buf.length && buf.toString('latin1') === STEP_MAGIC\n } finally {\n await handle.close()\n }\n}\n\nexport type McadCqBuild =\n | { ok: true; geo: McadGeometry; tris: Tri[]; dir: string; stlPath: string; stepEmitted: boolean }\n | { ok: false; detail: string; dir: string; stepEmitted: boolean }\n\n/**\n * Run one CadQuery script and measure what it delivered. Judge-time only.\n * Throws (never scores) when there is no usable interpreter — an absent toolchain\n * is an operator defect, and scoring it 0 would silently poison a whole corpus.\n */\nexport async function runAndMeasureCadQuery(src: string, timeoutMs = MCAD_CQ_TIMEOUT_MS): Promise<McadCqBuild> {\n const python = resolveCadqueryPython()\n if (!existsSync(python)) throw cadqueryMissingError(python)\n\n const dir = await mkdtemp(join(tmpdir(), 'mcad-cq-'))\n const stlPath = join(dir, STL_NAME)\n const stepPath = join(dir, STEP_NAME)\n await writeFile(join(dir, 'model.py'), `${src}\\n`)\n\n const fail = async (detail: string): Promise<McadCqBuild> => ({\n ok: false,\n detail,\n dir,\n stepEmitted: await stepFileEmitted(stepPath),\n })\n\n try {\n await execFileAsync(python, ['model.py'], {\n cwd: dir,\n env: scriptEnv(dir),\n timeout: timeoutMs,\n killSignal: 'SIGKILL',\n maxBuffer: 1024 * 1024 * 64,\n })\n } catch (err) {\n const e = err as NodeJS.ErrnoException & { killed?: boolean; stderr?: string }\n if (e.killed) return fail(`script timed out after ${timeoutMs} ms and was killed`)\n const stderr = (e.stderr ?? e.message ?? '').trim()\n return fail(`script failed: ${stderr.slice(-800)}`)\n }\n\n let stl: Buffer\n try {\n stl = await readFile(stlPath)\n } catch {\n return fail(`script wrote no ${STL_NAME} in its working directory`)\n }\n const bad = asciiStlProblem(stl)\n if (bad) return fail(bad)\n\n const tris = parseAsciiStl(stl.toString('utf8'))\n if (tris.length === 0) return fail(`${STL_NAME} parsed to zero triangles (empty geometry)`)\n\n return {\n ok: true,\n geo: measureMesh(tris),\n tris,\n dir,\n stlPath,\n stepEmitted: await stepFileEmitted(stepPath),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Scoring\n// ---------------------------------------------------------------------------\n\n/**\n * Append the STEP-delivery check to a v1 scoring and recompute. It is one check\n * among the spec's, deliberately: the upstream benchmark's deliverable IS a STEP\n * file, so failing to produce one costs exactly what failing a dimension costs.\n */\nexport function withStepCheck(scored: McadScoring, stepEmitted: boolean): McadScoring {\n const check: McadCheck = {\n name: 'stepEmitted',\n ok: stepEmitted,\n measured: stepEmitted ? `${STEP_NAME} starts with ${STEP_MAGIC}` : `no ${STEP_NAME} starting with ${STEP_MAGIC}`,\n expected: `${STEP_NAME} exists and starts with ${STEP_MAGIC}`,\n }\n const checks = [...scored.checks, check]\n const failed = checks.filter((c) => !c.ok)\n return { checks, failed, score: (checks.length - failed.length) / checks.length, resolved: failed.length === 0 }\n}\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\nexport function createMcadCqAdapter(): BenchmarkAdapter {\n return {\n name: 'mcad-cq',\n\n async preflight() {\n const python = resolveCadqueryPython()\n if (!existsSync(python)) throw cadqueryMissingError(python)\n try {\n await execFileAsync(python, ['-c', 'import cadquery; print(cadquery.__version__)'], {\n timeout: 300_000,\n env: scriptEnv(tmpdir()),\n })\n } catch (err) {\n const e = err as NodeJS.ErrnoException & { stderr?: string }\n throw cadqueryMissingError(python, `\\`import cadquery\\` failed: ${(e.stderr ?? e.message ?? '').trim().slice(-800)}`)\n }\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n let tasks: McadTask[] = MCAD_TASKS\n if (opts.ids) tasks = tasks.filter((t) => opts.ids?.includes(t.id))\n if (opts.limit != null) tasks = tasks.slice(0, opts.limit)\n return tasks.map(\n (t): BenchTask => ({\n id: t.id,\n prompt: toCadQueryPrompt(t),\n metadata: {\n spec: t.spec,\n source: t.source,\n // Calibration is per-ADAPTER: a task calibrated against the OpenSCAD\n // gold says nothing about whether a CadQuery gold reaches 1.0 here.\n calibrated: t.calibrated && !MCAD_CQ_UNCALIBRATED.has(t.id),\n } satisfies McadTaskMeta,\n }),\n )\n },\n\n async goldArtifact(task: BenchTask) {\n return MCAD_CQ_GOLDS[task.id]\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const spec = (task.metadata as McadTaskMeta | undefined)?.spec\n if (!spec) return { resolved: false, score: 0, detail: `task ${task.id} carries no mcad spec` }\n\n const src = stripCodeFence(artifact)\n if (!src) return { resolved: false, score: 0, detail: 'empty artifact' }\n\n const built = await runAndMeasureCadQuery(src)\n if (!built.ok) {\n return { resolved: false, score: 0, detail: `${built.detail} [dir ${built.dir}, stepEmitted ${built.stepEmitted}]` }\n }\n\n const { geo, tris, stlPath, stepEmitted, dir } = built\n if (!geo.watertight) {\n return {\n resolved: false,\n score: 0,\n detail: JSON.stringify({\n gate: 'watertight',\n failed: [\n {\n name: 'watertight',\n measured: `${geo.openEdges} edge(s) not shared by exactly two faces`,\n expected: '0 open edges (closed 2-manifold solid)',\n },\n ],\n geo: { triangles: geo.triangles, degenerateFaces: geo.degenerateFaces },\n stepEmitted,\n stlPath,\n dir,\n }),\n }\n }\n\n const scored = withStepCheck(scoreAgainstSpec(geo, tris, spec), stepEmitted)\n return {\n resolved: scored.resolved,\n score: scored.score,\n detail: JSON.stringify({\n failed: scored.failed.map((c) => ({ name: c.name, measured: c.measured, expected: c.expected })),\n checks: Object.fromEntries(scored.checks.map((c) => [c.name, c.ok])),\n geo: {\n triangles: geo.triangles,\n solids: geo.solids,\n volume: +geo.volume.toFixed(2),\n degenerateFaces: geo.degenerateFaces,\n bbox: {\n x: +geo.bbox.size.x.toFixed(3),\n y: +geo.bbox.size.y.toFixed(3),\n z: +geo.bbox.size.z.toFixed(3),\n },\n },\n stepEmitted,\n stlPath,\n dir,\n }),\n }\n },\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ //#region src/benchmarks/mcad-cq-golds.d.ts
2
+ /**
3
+ * MCAD-CQ gold oracles — one Python CadQuery script per task in `mcad-tasks.ts`.
4
+ *
5
+ * These are CALIBRATION artifacts, not exemplar answers: their whole job is to
6
+ * prove this adapter's accept direction fires. Every one has been run through
7
+ * `createMcadCqAdapter().judge()` on this host, and a task is reported calibrated
8
+ * only when its gold below scores 1.0 — including the `stepEmitted` check, so
9
+ * every gold really does write a STEP file, which is the deviation v1 could not
10
+ * close.
11
+ *
12
+ * They are written plain and share one preamble of four helpers. Two of those
13
+ * helpers are not stylistic:
14
+ * - `fuse_all` runs ONE multi-argument boolean instead of N sequential unions
15
+ * on a growing solid. OCC's cost is superlinear in the accumulated face
16
+ * count, and the fin stack (task 07), the staircase (task 09) and the blade
17
+ * ring (task 08) all miss the judge's 120 s deadline the sequential way.
18
+ * - `group` builds a compound of DISJOINT cutters, so a four-bore cut is one
19
+ * boolean rather than four.
20
+ *
21
+ * Where a gold deviates from the prompt's literal dimensions the deviation is
22
+ * named in a comment at the point of the deviation, with the reason. There is one,
23
+ * inherited verbatim from v1: task 10's ring tooth-tip diameter.
24
+ */
25
+ /** Gold CadQuery script per task id — preamble, body, then the two export lines. */
26
+ declare const MCAD_CQ_GOLDS: Record<string, string>;
27
+ /**
28
+ * Task ids whose CadQuery gold does NOT reach 1.0 through this adapter. Empty
29
+ * means every gold calibrates; an entry here is a promise that the spec was left
30
+ * alone and the gold is the thing that fell short, with the failing check named in
31
+ * a comment on the body above.
32
+ */
33
+ declare const MCAD_CQ_UNCALIBRATED: ReadonlySet<string>;
34
+ //#endregion
35
+ export { MCAD_CQ_GOLDS, MCAD_CQ_UNCALIBRATED };
36
+ //# sourceMappingURL=mcad-cq-golds.d.ts.map