brepjs-verify 0.3.0 → 0.4.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.3.0...brepjs-verify-v0.4.0) (2026-06-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * **brepjs-verify:** eval-driven skill, hint, and reference improvements ([#1219](https://github.com/andymai/brepjs/issues/1219)) ([1a9b80f](https://github.com/andymai/brepjs/commit/1a9b80f3d3dbb44d7a8ae2f601ff305b70534efe))
9
+
3
10
  ## [0.3.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.2.1...brepjs-verify-v0.3.0) (2026-06-04)
4
11
 
5
12
 
package/README.md CHANGED
@@ -21,6 +21,15 @@ The CLI the skill invokes. Install it in **your** project, where `brepjs` + the
21
21
  npm i -D brepjs-verify brepjs occt-wasm
22
22
  ```
23
23
 
24
+ ## API reference
25
+
26
+ The package bundles brepjs's full API reference for offline/agent use — the complete export surface with signatures and examples:
27
+
28
+ - `reference/llms-full.txt` — every export, full signatures (the deep reference)
29
+ - `reference/llms.txt` — the same content as a quicker index
30
+
31
+ Point your agent at `node_modules/brepjs-verify/reference/llms-full.txt` for anything the skill's curated references don't cover.
32
+
24
33
  ## The `.brep.ts` contract
25
34
 
26
35
  A model is a module whose default export is a zero-arg function returning a shape (or a `Result<shape>`):
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_diff = require("./diff-mxAOOl_m.cjs");
2
+ const require_diff = require("./diff-BNmCp_8I.cjs");
3
3
  exports.DEFAULT_TOLERANCE_PCT = require_diff.DEFAULT_TOLERANCE_PCT;
4
4
  exports.TYPECHECK_CODE = require_diff.TYPECHECK_CODE;
5
5
  exports.emptyReport = require_diff.emptyReport;
@@ -1,2 +1,2 @@
1
- import { a as typecheckPart, c as isExpectedDims, d as emptyReport, i as TYPECHECK_CODE, l as pctDelta, m as serializeReport, n as runMeasure, o as DEFAULT_TOLERANCE_PCT, r as runPart, s as evaluateExpected, t as runDiff, u as runChecks } from "./diff-Covv1XuJ.js";
1
+ import { a as typecheckPart, c as isExpectedDims, d as emptyReport, i as TYPECHECK_CODE, l as pctDelta, m as serializeReport, n as runMeasure, o as DEFAULT_TOLERANCE_PCT, r as runPart, s as evaluateExpected, t as runDiff, u as runChecks } from "./diff-D5U3Ie2F.js";
2
2
  export { DEFAULT_TOLERANCE_PCT, TYPECHECK_CODE, emptyReport, evaluateExpected, isExpectedDims, pctDelta, runChecks, runDiff, runMeasure, runPart, serializeReport, typecheckPart };
package/dist/cli/main.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_diff = require("../diff-mxAOOl_m.cjs");
3
+ const require_diff = require("../diff-BNmCp_8I.cjs");
4
4
  let node_url = require("node:url");
5
5
  let node_fs = require("node:fs");
6
6
  let node_path = require("node:path");
package/dist/cli/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { f as pushError, h as loadBrep, m as serializeReport, n as runMeasure, p as reportOk, r as runPart, t as runDiff } from "../diff-Covv1XuJ.js";
2
+ import { f as pushError, h as loadBrep, m as serializeReport, n as runMeasure, p as reportOk, r as runPart, t as runDiff } from "../diff-D5U3Ie2F.js";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { existsSync, mkdirSync, realpathSync, watch, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, join, resolve } from "node:path";
@@ -164,6 +164,14 @@ var HINT_TABLE = {
164
164
  fix: "The boolean subtraction failed — often a tool that does not actually intersect the base, or tolerance issues.",
165
165
  nextStep: "Confirm the tool overlaps the base, optionally heal the inputs, then re-cut."
166
166
  },
167
+ FILLET_FAILED: {
168
+ fix: "The fillet could not be built — usually the radius is too large for an edge it touched (it cannot exceed the adjacent face/wall), or you filleted EVERY edge (the no-edge-list form) including ones too thin to round.",
169
+ nextStep: "Select only the edges you mean to round — e.g. edgeFinder().inDirection(\"Z\").findAll(solid) — and/or reduce the radius below the thinnest adjacent wall, then re-verify."
170
+ },
171
+ CHAMFER_FAILED: {
172
+ fix: "The chamfer could not be built — usually the distance is too large for an edge it touched, or you chamfered EVERY edge (the no-edge-list form) including ones too thin.",
173
+ nextStep: "Select only the target edges with an edge finder and/or reduce the distance below the shortest adjacent edge, then re-verify."
174
+ },
167
175
  BOOLEAN_HAS_ERRORS: {
168
176
  fix: "The boolean ran but the kernel reported errors (often coincident faces or near-tangent contact).",
169
177
  nextStep: "Perturb one operand slightly so contact is a clean overlap, or heal the inputs, then retry."
@@ -199,6 +207,10 @@ var HINT_TABLE = {
199
207
  TYPECHECK: {
200
208
  fix: "Fix the TypeScript type error before running the part — the API call or value does not match brepjs’s types.",
201
209
  nextStep: "Correct the flagged type (e.g. argument/return type or import), then re-verify."
210
+ },
211
+ EXPECTED_UNKNOWN_KEY: {
212
+ fix: "Your `expected` block has keys the CLI does not assert (so the intended check never ran). Bounds must be `{ xMin, xMax, yMin, yMax, zMin, zMax }` — not `{ min, max }` or `{ x, y, z }`.",
213
+ nextStep: "Rewrite `expected` using only volume, area, tolerancePct, and bounds.{xMin..zMax}, then re-verify."
202
214
  }
203
215
  };
204
216
  /** Synthetic code attached to validity-check failures (validSolid returns a plain string error). */
@@ -302,8 +314,38 @@ function pctDelta(actual, expected) {
302
314
  return Math.abs(actual - expected) / Math.abs(expected) * 100;
303
315
  }
304
316
  function withinTolerance(actual, expected, tolerancePct) {
317
+ if (Math.abs(actual - expected) <= 1e-6) return true;
305
318
  return pctDelta(actual, expected) <= tolerancePct;
306
319
  }
320
+ var TOP_LEVEL_KEYS = new Set([
321
+ "volume",
322
+ "area",
323
+ "bounds",
324
+ "tolerancePct"
325
+ ]);
326
+ var BOUND_KEYS = new Set([
327
+ "xMin",
328
+ "xMax",
329
+ "yMin",
330
+ "yMax",
331
+ "zMin",
332
+ "zMax"
333
+ ]);
334
+ /**
335
+ * Keys in an `expected` block that the CLI does not understand and would silently ignore — a
336
+ * `{ min: [...], max: [...] }` or `{ x: [...] }` bounds shape, or a misspelled top-level field.
337
+ * Surfaced as an error (not dropped) so a wrong `expected` shape fails loud instead of passing
338
+ * vacuously with the intended assertion never run.
339
+ */
340
+ function unknownExpectedKeys(expected) {
341
+ const bad = [];
342
+ for (const k of Object.keys(expected)) if (!TOP_LEVEL_KEYS.has(k)) bad.push(k);
343
+ const bounds = expected.bounds;
344
+ if (bounds && typeof bounds === "object") {
345
+ for (const k of Object.keys(bounds)) if (!BOUND_KEYS.has(k)) bad.push(`bounds.${k}`);
346
+ }
347
+ return bad;
348
+ }
307
349
  function isExpectedDims(v) {
308
350
  if (typeof v !== "object" || v === null) return false;
309
351
  const r = v;
@@ -427,6 +469,25 @@ var COMPILER_OPTIONS = {
427
469
  skipLibCheck: true,
428
470
  allowImportingTsExtensions: true
429
471
  };
472
+ /**
473
+ * Locate the `@types/node` declarations so a part may import Node built-ins (`node:fs` to load a
474
+ * font, `node:fs/promises` to read a STEP file, etc.) without `--check` failing on the import.
475
+ * Returns the `@types` directory to use as a `typeRoots` entry. Probes the tool's own install
476
+ * first (where `@types/node` ships as a dependency), then the part's directory.
477
+ */
478
+ function nodeTypesRoot(partPath, toolDir) {
479
+ const froms = [
480
+ {}.url,
481
+ toolDir ? (0, node_url.pathToFileURL)((0, node_path.resolve)(toolDir, "package.json")).href : void 0,
482
+ (0, node_url.pathToFileURL)(partPath).href
483
+ ];
484
+ for (const from of froms) {
485
+ if (!from) continue;
486
+ try {
487
+ return (0, node_path.dirname)((0, node_path.dirname)((0, node_module.createRequire)(from).resolve("@types/node/package.json")));
488
+ } catch {}
489
+ }
490
+ }
430
491
  function diagnosticToErrorInfo(d) {
431
492
  const text = typescript.default.flattenDiagnosticMessageText(d.messageText, "\n");
432
493
  let where = "";
@@ -451,6 +512,11 @@ function typecheckPart(partPath, toolDir) {
451
512
  const dts = resolveBrepjsTypes(partPath, toolDir);
452
513
  const options = { ...COMPILER_OPTIONS };
453
514
  if (dts) options.paths = { brepjs: [dts] };
515
+ const typesRoot = nodeTypesRoot(partPath, toolDir);
516
+ if (typesRoot) {
517
+ options.typeRoots = [typesRoot];
518
+ options.types = ["node"];
519
+ }
454
520
  const program = typescript.default.createProgram([partPath], options);
455
521
  const errors = [
456
522
  ...program.getSemanticDiagnostics(),
@@ -487,7 +553,13 @@ function toErrorInfo(prefix, e) {
487
553
  code: e.code,
488
554
  suggestion: e.suggestion
489
555
  };
490
- if (e instanceof Error) return { message: `${prefix}: ${e.message}` };
556
+ if (e instanceof Error) {
557
+ const code = e.message.match(/\[[A-Z][A-Z0-9_]*\]\s+([A-Z][A-Z0-9_]+):/)?.[1];
558
+ return code ? {
559
+ message: `${prefix}: ${e.message}`,
560
+ code
561
+ } : { message: `${prefix}: ${e.message}` };
562
+ }
491
563
  return { message: `${prefix}: ${String(e)}` };
492
564
  }
493
565
  function finalize(result) {
@@ -567,6 +639,11 @@ async function runPart(modulePath, opts = {}) {
567
639
  if (isExpectedDims(mod.expected)) {
568
640
  const expected = mod.expected;
569
641
  result.assertions = evaluateExpected(expected, result.measurements);
642
+ const unknown = unknownExpectedKeys(expected);
643
+ if (unknown.length > 0) pushError(result, {
644
+ message: `expected has unrecognized keys (ignored): ${unknown.join(", ")}. Valid keys: volume, area, tolerancePct, bounds.{xMin,xMax,yMin,yMax,zMin,zMax}.`,
645
+ code: "EXPECTED_UNKNOWN_KEY"
646
+ });
570
647
  }
571
648
  let glb;
572
649
  let step;
@@ -162,6 +162,14 @@ var HINT_TABLE = {
162
162
  fix: "The boolean subtraction failed — often a tool that does not actually intersect the base, or tolerance issues.",
163
163
  nextStep: "Confirm the tool overlaps the base, optionally heal the inputs, then re-cut."
164
164
  },
165
+ FILLET_FAILED: {
166
+ fix: "The fillet could not be built — usually the radius is too large for an edge it touched (it cannot exceed the adjacent face/wall), or you filleted EVERY edge (the no-edge-list form) including ones too thin to round.",
167
+ nextStep: "Select only the edges you mean to round — e.g. edgeFinder().inDirection(\"Z\").findAll(solid) — and/or reduce the radius below the thinnest adjacent wall, then re-verify."
168
+ },
169
+ CHAMFER_FAILED: {
170
+ fix: "The chamfer could not be built — usually the distance is too large for an edge it touched, or you chamfered EVERY edge (the no-edge-list form) including ones too thin.",
171
+ nextStep: "Select only the target edges with an edge finder and/or reduce the distance below the shortest adjacent edge, then re-verify."
172
+ },
165
173
  BOOLEAN_HAS_ERRORS: {
166
174
  fix: "The boolean ran but the kernel reported errors (often coincident faces or near-tangent contact).",
167
175
  nextStep: "Perturb one operand slightly so contact is a clean overlap, or heal the inputs, then retry."
@@ -197,6 +205,10 @@ var HINT_TABLE = {
197
205
  TYPECHECK: {
198
206
  fix: "Fix the TypeScript type error before running the part — the API call or value does not match brepjs’s types.",
199
207
  nextStep: "Correct the flagged type (e.g. argument/return type or import), then re-verify."
208
+ },
209
+ EXPECTED_UNKNOWN_KEY: {
210
+ fix: "Your `expected` block has keys the CLI does not assert (so the intended check never ran). Bounds must be `{ xMin, xMax, yMin, yMax, zMin, zMax }` — not `{ min, max }` or `{ x, y, z }`.",
211
+ nextStep: "Rewrite `expected` using only volume, area, tolerancePct, and bounds.{xMin..zMax}, then re-verify."
200
212
  }
201
213
  };
202
214
  /** Synthetic code attached to validity-check failures (validSolid returns a plain string error). */
@@ -300,8 +312,38 @@ function pctDelta(actual, expected) {
300
312
  return Math.abs(actual - expected) / Math.abs(expected) * 100;
301
313
  }
302
314
  function withinTolerance(actual, expected, tolerancePct) {
315
+ if (Math.abs(actual - expected) <= 1e-6) return true;
303
316
  return pctDelta(actual, expected) <= tolerancePct;
304
317
  }
318
+ var TOP_LEVEL_KEYS = new Set([
319
+ "volume",
320
+ "area",
321
+ "bounds",
322
+ "tolerancePct"
323
+ ]);
324
+ var BOUND_KEYS = new Set([
325
+ "xMin",
326
+ "xMax",
327
+ "yMin",
328
+ "yMax",
329
+ "zMin",
330
+ "zMax"
331
+ ]);
332
+ /**
333
+ * Keys in an `expected` block that the CLI does not understand and would silently ignore — a
334
+ * `{ min: [...], max: [...] }` or `{ x: [...] }` bounds shape, or a misspelled top-level field.
335
+ * Surfaced as an error (not dropped) so a wrong `expected` shape fails loud instead of passing
336
+ * vacuously with the intended assertion never run.
337
+ */
338
+ function unknownExpectedKeys(expected) {
339
+ const bad = [];
340
+ for (const k of Object.keys(expected)) if (!TOP_LEVEL_KEYS.has(k)) bad.push(k);
341
+ const bounds = expected.bounds;
342
+ if (bounds && typeof bounds === "object") {
343
+ for (const k of Object.keys(bounds)) if (!BOUND_KEYS.has(k)) bad.push(`bounds.${k}`);
344
+ }
345
+ return bad;
346
+ }
305
347
  function isExpectedDims(v) {
306
348
  if (typeof v !== "object" || v === null) return false;
307
349
  const r = v;
@@ -425,6 +467,25 @@ var COMPILER_OPTIONS = {
425
467
  skipLibCheck: true,
426
468
  allowImportingTsExtensions: true
427
469
  };
470
+ /**
471
+ * Locate the `@types/node` declarations so a part may import Node built-ins (`node:fs` to load a
472
+ * font, `node:fs/promises` to read a STEP file, etc.) without `--check` failing on the import.
473
+ * Returns the `@types` directory to use as a `typeRoots` entry. Probes the tool's own install
474
+ * first (where `@types/node` ships as a dependency), then the part's directory.
475
+ */
476
+ function nodeTypesRoot(partPath, toolDir) {
477
+ const froms = [
478
+ import.meta.url,
479
+ toolDir ? pathToFileURL(resolve(toolDir, "package.json")).href : void 0,
480
+ pathToFileURL(partPath).href
481
+ ];
482
+ for (const from of froms) {
483
+ if (!from) continue;
484
+ try {
485
+ return dirname(dirname(createRequire(from).resolve("@types/node/package.json")));
486
+ } catch {}
487
+ }
488
+ }
428
489
  function diagnosticToErrorInfo(d) {
429
490
  const text = ts.flattenDiagnosticMessageText(d.messageText, "\n");
430
491
  let where = "";
@@ -449,6 +510,11 @@ function typecheckPart(partPath, toolDir) {
449
510
  const dts = resolveBrepjsTypes(partPath, toolDir);
450
511
  const options = { ...COMPILER_OPTIONS };
451
512
  if (dts) options.paths = { brepjs: [dts] };
513
+ const typesRoot = nodeTypesRoot(partPath, toolDir);
514
+ if (typesRoot) {
515
+ options.typeRoots = [typesRoot];
516
+ options.types = ["node"];
517
+ }
452
518
  const program = ts.createProgram([partPath], options);
453
519
  const errors = [
454
520
  ...program.getSemanticDiagnostics(),
@@ -485,7 +551,13 @@ function toErrorInfo(prefix, e) {
485
551
  code: e.code,
486
552
  suggestion: e.suggestion
487
553
  };
488
- if (e instanceof Error) return { message: `${prefix}: ${e.message}` };
554
+ if (e instanceof Error) {
555
+ const code = e.message.match(/\[[A-Z][A-Z0-9_]*\]\s+([A-Z][A-Z0-9_]+):/)?.[1];
556
+ return code ? {
557
+ message: `${prefix}: ${e.message}`,
558
+ code
559
+ } : { message: `${prefix}: ${e.message}` };
560
+ }
489
561
  return { message: `${prefix}: ${String(e)}` };
490
562
  }
491
563
  function finalize(result) {
@@ -565,6 +637,11 @@ async function runPart(modulePath, opts = {}) {
565
637
  if (isExpectedDims(mod.expected)) {
566
638
  const expected = mod.expected;
567
639
  result.assertions = evaluateExpected(expected, result.measurements);
640
+ const unknown = unknownExpectedKeys(expected);
641
+ if (unknown.length > 0) pushError(result, {
642
+ message: `expected has unrecognized keys (ignored): ${unknown.join(", ")}. Valid keys: volume, area, tolerancePct, bounds.{xMin,xMax,yMin,yMax,zMin,zMax}.`,
643
+ code: "EXPECTED_UNKNOWN_KEY"
644
+ });
568
645
  }
569
646
  let glb;
570
647
  let step;
@@ -16,8 +16,23 @@ export interface ExpectedDims {
16
16
  tolerancePct?: number;
17
17
  }
18
18
  export declare const DEFAULT_TOLERANCE_PCT = 0.5;
19
+ /**
20
+ * Absolute slack (mm / mm² / mm³) below which a deviation always passes, independent of
21
+ * percent tolerance. Kernel coordinates carry sub-nanometer float noise (e.g. a loft base
22
+ * lands at z = -1e-7, not 0), so a percent comparison against an expected `0` — where any
23
+ * nonzero deviation is infinite percent — would make a zero-valued bound or measurement
24
+ * impossible to assert. 1e-6 is far above that noise and far below any real feature size.
25
+ */
26
+ export declare const ABS_EPSILON = 0.000001;
19
27
  /** Percent deviation of `actual` from `expected`; 0 expected matches only 0 actual. */
20
28
  export declare function pctDelta(actual: number, expected: number): number;
29
+ /**
30
+ * Keys in an `expected` block that the CLI does not understand and would silently ignore — a
31
+ * `{ min: [...], max: [...] }` or `{ x: [...] }` bounds shape, or a misspelled top-level field.
32
+ * Surfaced as an error (not dropped) so a wrong `expected` shape fails loud instead of passing
33
+ * vacuously with the intended assertion never run.
34
+ */
35
+ export declare function unknownExpectedKeys(expected: object): string[];
21
36
  export declare function isExpectedDims(v: unknown): v is ExpectedDims;
22
37
  /**
23
38
  * Compare measured dimensions against a part's `expected` export. Each declared field becomes one
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs-verify",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Agent skill + verify/preview tooling for authoring parametric brepjs CAD code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -34,12 +34,13 @@
34
34
  "files": [
35
35
  "dist",
36
36
  "viewer/dist",
37
+ "reference",
37
38
  "CHANGELOG.md",
38
39
  "LICENSE",
39
40
  "README.md"
40
41
  ],
41
42
  "scripts": {
42
- "build": "vite build && vite build --config viewer/vite.config.ts",
43
+ "build": "node scripts/copyReference.mjs && vite build && vite build --config viewer/vite.config.ts",
43
44
  "typecheck": "tsc --noEmit && tsc -p viewer/tsconfig.json --noEmit && tsc -p bench/tsconfig.json",
44
45
  "lint": "eslint src tests viewer bench",
45
46
  "test": "vitest run",
@@ -50,6 +51,7 @@
50
51
  "prepack": "npm run build"
51
52
  },
52
53
  "dependencies": {
54
+ "@types/node": "^25.9.1",
53
55
  "brepjs": "^18.0.0",
54
56
  "commander": "^13.0.0",
55
57
  "occt-wasm": "^3.0.0",