geospec 0.1.0-beta.9 → 0.4.1-next.331

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 (105) hide show
  1. package/README.md +34 -9
  2. package/agent/doctrine.md +2 -1
  3. package/agent/geospec-authoring/SKILL.md +3 -3
  4. package/agent/geospec-authoring/api-index.md +20 -92
  5. package/agent/geospec-authoring/api-types.md +85 -480
  6. package/agent/geospec-authoring/public-api-functions.md +21 -1
  7. package/agent/geospec-authoring/public-api-index.md +215 -319
  8. package/agent/geospec-authoring/public-api-types-2.md +480 -352
  9. package/agent/geospec-authoring/public-api-types-3.md +79 -215
  10. package/agent/geospec-authoring/public-api-types.md +334 -849
  11. package/agent/resources.js +12 -13
  12. package/agent/skills.json +2 -3
  13. package/dist/assertion-client/client.d.mts +14 -15
  14. package/dist/assertion-client/client.mjs +1 -1
  15. package/dist/cli/check.mjs +2 -0
  16. package/dist/cli/cli.d.mts +5 -5
  17. package/dist/cli/cli.mjs +20 -3
  18. package/dist/cli/node-host.d.mts +5 -4
  19. package/dist/cli/node-host.mjs +1 -1
  20. package/dist/cli/wire-value.mjs +1 -1
  21. package/dist/config/define-config.d.mts +2 -3
  22. package/dist/config/node/index.mjs +1 -1
  23. package/dist/config/node/load.d.mts +2 -3
  24. package/dist/config/node/load.mjs +1 -1
  25. package/dist/config/read-geospec-config.d.mts +10 -6
  26. package/dist/config/read-geospec-config.mjs +1 -1
  27. package/dist/config/types.d.mts +6 -7
  28. package/dist/create-geospec.d.mts +7 -6
  29. package/dist/engine/client.d.mts +15 -15
  30. package/dist/engine/client.mjs +1 -1
  31. package/dist/engine/matchers.d.mts +10 -11
  32. package/dist/engine/pmi.types.d.mts +11 -12
  33. package/dist/engine/protocol.d.mts +7 -8
  34. package/dist/engine/protocol.mjs +1 -1
  35. package/dist/geometry-unit.d.mts +2 -3
  36. package/dist/index.d.mts +2 -2
  37. package/dist/index.mjs +1 -1
  38. package/dist/mesh/index.d.mts +2 -2
  39. package/dist/mesh/types.d.mts +8 -505
  40. package/dist/model/errors.d.mts +2 -3
  41. package/dist/model/load-model.d.mts +3 -4
  42. package/dist/model/mesh-evidence-matchers.d.mts +2 -3
  43. package/dist/model/native-model-loader.d.mts +14 -17
  44. package/dist/model/native-model-loader.mjs +1 -1
  45. package/dist/model/node/command-exporter.d.mts +3 -4
  46. package/dist/model/node/command-exporter.mjs +1 -1
  47. package/dist/model/node/index.mjs +1 -1
  48. package/dist/model/region-scope.d.mts +3 -6
  49. package/dist/model/region-scope.mjs +1 -1
  50. package/dist/model/subject.d.mts +2 -5
  51. package/dist/model/subject.mjs +1 -1
  52. package/dist/model/types.d.mts +19 -20
  53. package/dist/runner/collector.d.mts +7 -8
  54. package/dist/runner/collector.mjs +3 -1
  55. package/dist/runner/discovery.d.mts +10 -11
  56. package/dist/runner/discovery.mjs +5 -1
  57. package/dist/runner/filter.d.mts +6 -7
  58. package/dist/runner/filter.mjs +3 -1
  59. package/dist/runner/index.d.mts +3 -1
  60. package/dist/runner/index.mjs +1 -1
  61. package/dist/runner/native/native-pool-grants.d.mts +2 -3
  62. package/dist/runner/native/native-serial-runner.d.mts +4 -7
  63. package/dist/runner/native/native-serial-runner.mjs +1 -1
  64. package/dist/runner/node/engine.d.mts +5 -6
  65. package/dist/runner/node/index.mjs +1 -1
  66. package/dist/runner/node/node-vm-filesystem.d.mts +4 -5
  67. package/dist/runner/node/node-vm-filesystem.mjs +1 -1
  68. package/dist/runner/node/pool-runner.d.mts +15 -6
  69. package/dist/runner/node/pool-runner.mjs +1 -1
  70. package/dist/runner/node/pool-worker-entry.d.mts +9 -6
  71. package/dist/runner/node/pool-worker-entry.mjs +1 -1
  72. package/dist/runner/pool/events.mjs +1 -1
  73. package/dist/runner/pool/pool-messages.d.mts +8 -9
  74. package/dist/runner/pool/pool.d.mts +4 -6
  75. package/dist/runner/pool/pool.mjs +1 -1
  76. package/dist/runner/pool/shard-timings.d.mts +3 -4
  77. package/dist/runner/pool/worker-host.d.mts +6 -5
  78. package/dist/runner/pool/worker-host.mjs +1 -1
  79. package/dist/runner/run-geospec-module.d.mts +2 -3
  80. package/dist/runner/run-geospec-module.mjs +2 -2
  81. package/dist/runner/timeout-codes.d.mts +6 -8
  82. package/dist/runner/types.d.mts +56 -53
  83. package/dist/runner/worker/no-matching-tests-issue.d.mts +3 -4
  84. package/dist/runner/worker/runner-types.d.mts +17 -16
  85. package/dist/runner/worker/serial-runner.mjs +1 -1
  86. package/dist/selector/types.d.mts +22 -23
  87. package/dist/step/types.d.mts +4 -119
  88. package/dist/support/filesystem-errors.mjs +1 -0
  89. package/dist/support/hash.mjs +1 -0
  90. package/dist/support/path.mjs +1 -0
  91. package/dist/support/sha256.mjs +1 -0
  92. package/dist/support/types.d.mts +38 -0
  93. package/dist/vitest/adapter.d.mts +5 -6
  94. package/dist/vitest/adapter.mjs +1 -1
  95. package/dist/vitest/index.d.mts +0 -1
  96. package/dist/vitest/index.mjs +1 -1
  97. package/dist/vitest/node/index.d.mts +2 -0
  98. package/dist/vitest/node/index.mjs +1 -0
  99. package/dist/vitest/node/setup-project.d.mts +60 -0
  100. package/dist/vitest/node/setup-project.mjs +1 -0
  101. package/dist/vm/plain-module-vm.d.mts +13 -0
  102. package/dist/vm/plain-module-vm.mjs +8 -0
  103. package/dist/vm/types.d.mts +142 -0
  104. package/package.json +17 -9
  105. package/agent/geospec-authoring/api-types-2.md +0 -85
@@ -1,27 +1,26 @@
1
- /* Generated by @taucad/api-extractor. Do not edit. */
1
+ /* Generated by geospec scripts/agent-skills/generate.ts. Do not edit. */
2
2
  export default Object.freeze([
3
3
  Object.freeze({
4
4
  slug: "geospec-authoring",
5
5
  name: "GeoSpec authoring",
6
6
  description: "Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.",
7
- version: "0.1.0-beta.9",
7
+ version: "0.4.0",
8
8
  whenToUse: "Use before creating or editing *.geospec.ts or *.geospec.js tests, for a model in any kernel.",
9
- body: "---\nname: geospec-authoring\ndescription: Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.\n---\n\n# GeoSpec authoring\n\nWrite tests before implementation; assert deterministic, measurable properties.\n\n## Test shape\n\n- Use `*.geospec.ts` or `*.geospec.js`. Import `describe`, `it` and `expectGeo` from `geospec`, and `loadModel` from `geospec/model`.\n- Await `loadModel`, then assert synchronously with `expectGeo(model)`. The host supplies the compiled binding; authoring does not select an engine.\n- Await or return the complete model-load chain from the test callback; detached future loads are not part of a completed test.\n- Omit `parameters` for model-code defaults; use `{ file, parameters }` for variants. Loaded subjects are opaque.\n- `loadModel({ file })` resolves `file` against the project root, the same root as every tool's `targetFile`; the result names that root as `projectRoot` where the host reports it, as `tau mcp` does. A suite in a subfolder names the model by its path from that root, or passes `rootDir`, the project-relative folder the model is in: `loadModel({ file: 'main.ts', rootDir: 'parts/gear' })` loads `parts/gear/main.ts`. A file the root does not hold refuses with `GEOSPEC_MODEL_SOURCE_NOT_FOUND`, which names the root and the path it looked for.\n\nCanonical TypeScript authoring:\n\n```ts\nimport { describe, expectGeo, it } from 'geospec';\nimport { loadModel } from 'geospec/model';\n\ndescribe('main geometry', () => {\n it('has the intended envelope', async () => {\n const model = await loadModel({ file: 'main.ts', parameters: { width: 120 } });\n expectGeo(model).toHaveBoundingBox({ size: { x: 120 }, tolerance: 1 });\n });\n});\n```\n\nSpecs are always TypeScript or JavaScript, whatever the model's kernel: `loadModel({ file })` evaluates `main.ts`, `main.tsx`, `main.py`, `main.scad`, `main.kcl` or `main.cs` through Tau's runtime. `test_model` discovers `*.geospec.ts` and `*.geospec.js` only.\n\nEvery matcher takes one expectation object:\n\n- Wrong: `expectGeo(model).toHaveVolume(5000, { tolerance: 5 })`. Correct: `expectGeo(model).toHaveVolume({ value: 5000, tolerance: 5 })`.\n- Wrong: `await expectGeo(model)…`. Correct: await `loadModel`, then assert synchronously.\n- Wrong: exact-feature matchers on a mesh kernel (Manifold, JSCAD, OpenSCAD, PicoGK, PicoVoxel). Correct: `toBeValidBrep`, `toHaveTopologyCounts`, the face, hole, pattern, chamfer, fillet and wall-thickness matchers, `toHaveStepUnits`, `toHaveProductStructure` and `toHaveVoidContinuity` need STEP BRep evidence and report unsupported on meshes; assert meshes with bounds, volume, surface area, connected components and `toBeWatertight`.\n\n## Loads and budgets\n\n- GeoSpec has no `beforeAll`. Write each `loadModel` inside its own `it`: within one run, identical loads (same file or code, parameters, format, tolerances and region) share one export and one admission. Each load keeps its own `loadId` and records `reused: true`, and a file is reused only while its source closure is unchanged.\n- `loadModel({ file, workUnitLimit })` sets the default claim budget for that subject, and `expectGeo(model, { workUnitLimit })` overrides it for one chain. The budget is a positive integer no greater than 512000000. A `MATCHER_TIMEOUT` refusal reports `details.limit` and, when measured, `details.used`; its `test_model` row (category `work-budget`) names in `limit.retry` the one `workUnitLimit` that can decide the claim, or `null` at the host maximum. Never treat the refusal as a pass.\n- `loadModel({ file, region: { min: [x, y, z], max: [x, y, z] } })` makes a separate claim about one millimetre box, never a stand-in for a whole-model claim. The kernel crops before export (PicoVoxel trims Voxels parts), and other kernels, STEP loads and unconfirmed crops refuse with a typed diagnostic. A cropped subject answers only bounding box, volume, surface area, mass and center of mass of its region, and its assertions record the region. Every other matcher on it refuses with `GEOSPEC_REGION_WHOLE_MODEL_CLAIM`. Name the region in the test, and keep whole-model claims on whole-model loads.\n- A host or engine limit leaves a claim undecided, never false: `test_model` reports it as inconclusive with category `analysis-limit`, `resource-limit` or `work-budget` and a typed `limit` naming the limit and its numbers. Keep the model as designed: never coarsen, shrink or crop it to fit. Only a `work-budget` row's `limit.retry` is a retry, and `testTimeout` only answers a `timeout` row; report every other limit row as not decided at this capacity.\n- `toHaveCenterOfMass({ point, tolerance })` takes an exact point. Per-axis ranges such as `{ y: { greaterThan: 1 } }` are refused.\n\n## Ceilings\n\nFixed in this release; design within them, and report a claim that meets one as not decided.\n\n| Ceiling | Value | Where an agent meets it |\n| ------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| `maxSubjectBytes`, `maxResourceBytes` | 64 MiB each | A mesh `loadModel({ file })` exports about 72 bytes per triangle, so admission refuses near 930,000 triangles (`resource-limit`). |\n| `maxTotalBinaryBytes` | 128 MiB | All subjects of one load together. |\n| `maxTriangles`, `maxVertices` | 4,000,000, 2,000,000 | A decoded mesh. |\n| `continuousEvidenceBytes` | 32 MiB | `toHaveNoComponentInterference` on a large assembly (`analysis-limit`). |\n| `workUnitLimit` | 512000000 at most | Any claim; the only limit an argument raises. |\n\nLoad `format: 'step'` only for the exact-BRep matchers above; every other matcher answers on the cheaper mesh.\n\n## Coverage\n\nBounds and physical properties are insufficient. Cover major components, named features, variants, dimensions/positions, disjoint counts, closure, interference and supported exact features. Explicitly report unsupported requirements; proxies cannot fulfill them.\n\n- `toHaveBoundingBox`: size/position.\n- `toHaveConnectedComponents`: qualified material connectivity; visual touching cannot justify tolerance changes.\n- `toBeWatertight`: closed edge incidence, not manifold validity, fusion or material connectivity. Bounds prove none of these. Missing evidence is unsupported.\n- `toHaveSurfaceArea`, `toHaveVolume`, `toHaveCenterOfMass`, `toHaveMass`: physical measurements.\n- `toHaveNoComponentInterference`: overlap; allow deliberate press fits.\n- `loadModel({ file, format: 'step' })`: use BRep evidence for supported topology and feature assertions. Missing occurrence, interference, clearance, containment, lumen, wall, section or motion evidence is unsupported, not a pass or an envelope substitute.\n\nTest assemblies and independent units; preserve sibling coverage when adding files. Non-top-level geometry needs kernel export/invocation, not test removal.\n\nFix geometry at its root; never weaken tolerances, delete assertions, or reduce detail to pass.\n\n`public-api-index.md` covers the host and framework API; authoring needs only the Core API below and `api-*` files.\n\n## Core API\n\nThe symbols real Tau models use most, as verbatim declarations. Anything not shown is in the API reference below.\n\n### Functions\n\n```ts\n// Start a geometry assertion chain\nexport declare function expectGeo(subject: GeoSpecSubject, options?: GeoSpecExpectOptions): GeoSpecMatcher;\n\n// A model admitted by one live GeoSpec host scope\nGeoSpecSubject: {\n readonly [subjectBrand]: true\n}\n\n// Per-chain options accepted by `expectGeo(subject, options)`\nGeoSpecExpectOptions: {\n // Deterministic work-unit budget for every claim in this chain\n readonly workUnitLimit?: number\n}\n\n// Load a CAD model into GeoSpec evidence\nexport declare function loadModel<Code extends Record<string, string> = Record<string, string>>(_options: LoadModelOptions<Code>): Promise<GeoSpecSubject>;\n\n// Options accepted by {@link import ('./load-model.js').loadModel}\nLoadModelOptions: LoadModelSourceOptions | LoadModelCodeOptions<Code> | LoadModelFileOptions\n // Project-relative folder `file` resolves in, for a suite that loads from somewhere other than…\n rootDir?: string\n // Geometry format to export\n format?: GeoSpecModelFormat\n // Explicit parameters passed to the exporter\n parameters?: Record<string, unknown>\n // STEP reader strategy used for STEP exports\n stepStreaming?: StepStreamingMode\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // Linear tolerance used while meshing exact BRep evidence\n meshLinearTolerance?: number\n // Angular tolerance in degrees used while meshing exact BRep evidence\n meshAngularToleranceDegrees?: number\n // Default work-unit budget for every claim on this subject\n workUnitLimit?: number\n\n// Geometry formats accepted by {@link import ('./load-model.js').loadModel}\nGeoSpecModelFormat: MeshFileFormat | 'step' | 'stp'\n\n// GeoSpec test helper used inside VM-executed test modules\nexport declare function it(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n\n// GeoSpec suite helper used inside VM-executed test modules\nexport declare function describe(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n\n// Alias for {@link it}\nexport declare function test(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n```\n\n### Types\n\n```ts\n// Assertion chain returned by `expectGeo(subject)`\nGeoSpecMatcher: {\n // Core-owned negation\n readonly not: Omit<GeoSpecMatcher, 'not'>\n // Assert the fixed rational plate contract\n toSatisfyRationalPlate(): GeoSpecAssertion;\n // Assert the fixed parallel-plane distance contract\n toSatisfyParallelPlaneDistance(): GeoSpecAssertion;\n // Assert axis-aligned bounds, size, or center for a loaded geometry subject\n toHaveBoundingBox(first: Vec3 | GeoSpecBoundingBoxExpectation, second?: Vec3): GeoSpecAssertion;\n // Assert how many spatially disjoint chunks the mesh contains\n toHaveConnectedComponents(expected: GeoSpecConnectedComponentsExpectation): GeoSpecAssertion;\n // Assert closed mesh edge incidence\n toBeWatertight(): GeoSpecAssertion;\n // Assert that separate assembly components do not occupy the same solid volume\n toHaveNoComponentInterference(expected?: GeoSpecComponentInterferenceExpectation): GeoSpecAssertion;\n // Assert that named assembly occurrences exist with expected counts and bounds\n toHaveAssemblyOccurrences(expected: GeoSpecAssemblyOccurrencesExpectation): GeoSpecAssertion;\n // Assert that selected entities satisfy declared spatial relationships\n toHaveSpatialRelationships(expected: GeoSpecSpatialRelationshipsExpectation): GeoSpecAssertion;\n // Assert rendered mesh evidence is internally trustworthy for downstream checks\n toHaveMeshIntegrity(expected: GeoSpecMeshIntegrityExpectation): GeoSpecAssertion;\n // Assert that the subject carries no diagnostics at the rejected severities\n toHaveNoDiagnostics(expected?: GeoSpecNoDiagnosticsExpectation): GeoSpecAssertion;\n // Assert total surface area, preferring exact BRep mass properties when available\n toHaveSurfaceArea(expected: GeoSpecSurfaceAreaExpectation): GeoSpecAssertion;\n // Assert enclosed volume, preferring exact BRep mass properties when available\n toHaveVolume(expected: GeoSpecVolumeExpectation): GeoSpecAssertion;\n // Assert mass derived from exact mass properties or volume times density\n toHaveMass(expected: GeoSpecMassExpectation): GeoSpecAssertion;\n // Assert the center of mass or mesh-derived centroid for a closed subject\n toHaveCenterOfMass(expected: GeoSpecCenterOfMassExpectation): GeoSpecAssertion;\n // Assert that exact BRep evidence reports a valid shape\n toBeValidBrep(expected?: GeoSpecValidBrepExpectation): GeoSpecAssertion;\n // Assert exact BRep topology counts\n toHaveTopologyCounts(expected: GeoSpecTopologyCountsExpectation): GeoSpecAssertion;\n // Assert the STEP unit evidence\n toHaveStepUnits(expected: GeoSpecStepUnitsExpectation): GeoSpecAssertion;\n // Assert STEP product-structure evidence\n toHaveProductStructure(expected: GeoSpecProductStructureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a planar face matching the requested constraints\n toHavePlanarFace(expected: GeoSpecPlanarFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a cylindrical face with the requested radius and axis\n toHaveCylindricalFace(expected: GeoSpecCylindricalFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a circular hole matching diameter, center, and axis\n toHaveCircularHole(expected: GeoSpecCircularHoleExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a repeated circular-hole pattern\n toHaveCircularHolePattern(expected: GeoSpecCircularHolePatternExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a chamfer feature with the requested distance\n toHaveChamferFeature(expected: GeoSpecChamferFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a fillet feature with the requested radius\n toHaveFilletFeature(expected: GeoSpecFilletFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence reports a minimum wall thickness satisfying the expectation\n toHaveMinimumWallThickness(expected: GeoSpecMinimumWallThicknessExpectation): GeoSpecAssertion;\n // Assert that the declared waypoints share one connected void, stay isolated from the declared…\n toHaveVoidContinuity(expected: GeoSpecVoidContinuityExpectation): GeoSpecAssertion;\n}\n\n// Numeric 3D vector\nVec3: readonly [number, number, number]\n\n// Bounding-box expectation accepted by `expectGeo(...).toHaveBoundingBox(...)`\nGeoSpecBoundingBoxExpectation: {\n min?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n max?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n size?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n center?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n tolerance?: number\n}\n\n// Connected-components expectation accepted by `expectGeo(...).toHaveConnectedComponents(...)`\nGeoSpecConnectedComponentsExpectation: {\n count: number\n tolerance?: number\n toleranceMm?: number\n}\n\n// Component-interference expectation accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceExpectation: {\n tolerance?: number\n pairs?: GeoSpecComponentInterferencePairExpectation[]\n allowances?: GeoSpecComponentInterferenceAllowance[]\n}\n\n// Assembly occurrence expectation accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrencesExpectation: {\n occurrences: GeoSpecAssemblyOccurrenceExpectation[]\n uniqueNames?: boolean\n}\n\n// Spatial relationship expectation accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipsExpectation: {\n relationships: GeoSpecSpatialRelationshipExpectation[]\n}\n\n// Mesh integrity expectation accepted by `expectGeo(...).toHaveMeshIntegrity(...)`\nGeoSpecMeshIntegrityExpectation: {\n finitePositions?: boolean\n degenerateTriangles?: {\n count?: number;\n maxCount?: number;\n areaTolerance?: number;\n }\n duplicateFaces?: {\n count?: number;\n maxCount?: number;\n }\n watertight?: boolean\n triangleCount?: GeoSpecNumericExpectation\n}\n\n// Diagnostic severities rejected by `expectGeo(...).toHaveNoDiagnostics(...)`\nGeoSpecNoDiagnosticsExpectation: {\n severities?: Array<GeometryDiagnostic['severity']>\n}\n\n// Surface-area expectation accepted by `expectGeo(...).toHaveSurfaceArea(...)`\nGeoSpecSurfaceAreaExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Volume expectation accepted by `expectGeo(...).toHaveVolume(...)`\nGeoSpecVolumeExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Mass expectation accepted by `expectGeo(...).toHaveMass(...)`\nGeoSpecMassExpectation: {\n value: number | GeoSpecNumericExpectation\n density?: number\n tolerance?: number\n}\n\n// Center-of-mass expectation accepted by `expectGeo(...).toHaveCenterOfMass(...)`\nGeoSpecCenterOfMassExpectation: {\n // Exact expected point in millimetres\n point: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Exact BRep validity expectation accepted by `expectGeo(...).toBeValidBrep(...)`\nGeoSpecValidBrepExpectation: {\n maxTolerance?: number\n freeBounds?: {\n count?: GeoSpecNumericExpectation;\n }\n minEdgeLength?: number\n sameParameter?: boolean\n closedShells?: boolean\n closedWires?: boolean\n}\n\n// Topology-count expectation accepted by `expectGeo(...).toHaveTopologyCounts(...)`\nGeoSpecTopologyCountsExpectation: {\n vertices?: GeoSpecNumericExpectation\n edges?: GeoSpecNumericExpectation\n wires?: GeoSpecNumericExpectation\n faces?: GeoSpecNumericExpectation\n shells?: GeoSpecNumericExpectation\n solids?: GeoSpecNumericExpectation\n compounds?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// STEP unit expectation accepted by `expectGeo(...).toHaveStepUnits(...)`\nGeoSpecStepUnitsExpectation: {\n unit: string\n}\n\n// Product-structure expectation accepted by `expectGeo(...).toHaveProductStructure(...)`\nGeoSpecProductStructureExpectation: {\n names?: string[]\n count?: GeoSpecNumericExpectation\n}\n\n// Planar-face expectation accepted by `expectGeo(...).toHavePlanarFace(...)`\nGeoSpecPlanarFaceExpectation: {\n normal: GeoSpecPointExpectation\n offset: number\n area?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Cylindrical-face expectation accepted by `expectGeo(...).toHaveCylindricalFace(...)`\nGeoSpecCylindricalFaceExpectation: {\n radius: number\n axis: 'x' | 'y' | 'z'\n tolerance?: number\n}\n\n// Circular-hole expectation accepted by `expectGeo(...).toHaveCircularHole(...)`\nGeoSpecCircularHoleExpectation: {\n diameter: number\n through?: boolean\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Circular-hole-pattern expectation accepted by `expectGeo(...).toHaveCircularHolePattern(...)`\nGeoSpecCircularHolePatternExpectation: {\n count: number\n holeDiameter: number\n boltCircleDiameter?: number\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Chamfer-feature expectation accepted by `expectGeo(...).toHaveChamferFeature(...)`\nGeoSpecChamferFeatureExpectation: {\n distance: number\n selection?: string\n tolerance?: number\n}\n\n// Fillet-feature expectation accepted by `expectGeo(...).toHaveFilletFeature(...)`\nGeoSpecFilletFeatureExpectation: {\n radius: number\n selection?: string\n tolerance?: number\n}\n\n// Minimum-wall-thickness expectation accepted by `expectGeo(...).toHaveMinimumWallThickness(...)`\nGeoSpecMinimumWallThicknessExpectation: {\n value: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Void-continuity expectation accepted by `expectGeo(...).toHaveVoidContinuity(...)`\nGeoSpecVoidContinuityExpectation: {\n // Ordered waypoints (>= 1) known to lie in the void being proven\n path: GeoSpecVoidWaypoint[]\n // Occurrence names whose solids bound the void\n material?: string[]\n // Minimum required bottleneck cross-section (mm²), sampled\n minCrossSection?: number\n // Points that must NOT be reachable from the path void (isolation claim)\n isolatedFrom?: Vec3[]\n // Region bounded for the proof (subject frame)\n bounds?: {\n min: Vec3;\n max: Vec3;\n }\n}\n\n// Axis-keyed numeric expectation used by high-level geometry matchers\nGeoSpecAxisExpectation: {\n x?: number\n y?: number\n z?: number\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// A pair-specific component-interference check accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferencePairExpectation: {\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n}\n\n// Intentional component interference allowance accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceAllowance: {\n kind?: 'intentionalInterference'\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n maxVolume?: number\n reason: string\n}\n\n// Assembly occurrence rule accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrenceExpectation: {\n name: GeoSpecComponentSelector\n count?: GeoSpecNumericExpectation\n bounds?: {\n within?: GeoSpecComponentSelector;\n min?: Vec3 | GeoSpecAxisExpectation;\n max?: Vec3 | GeoSpecAxisExpectation;\n center?: GeoSpecPointExpectation;\n tolerance?: number;\n }\n}\n\n// One spatial relationship accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipExpectation: {\n id?: string\n kind: 'contact' | 'clearance' | 'coaxial' | 'concentric' | 'coplanar' | 'parallel' | 'perpendicular' | 'angle' | 'containment' | 'insertion' | 'interference'\n subject: GeoSpecGeometrySelector\n target: GeoSpecGeometrySelector\n tolerance?: number\n angularToleranceDegrees?: number\n // Expected angle in degrees for `kind\n angleDegrees?: number\n // Declared insertion axis (subject-frame direction) for `kind\n axis?: Vec3\n min?: number\n max?: number\n minVolume?: number\n maxVolume?: number\n reason?: string\n}\n\n// Diagnostic emitted by GeoSpec loaders, analyzers, and matchers\nGeometryDiagnostic: {\n code: KernelIssueCode | (string & {})\n severity: 'error' | 'warning' | 'info'\n message: string\n suggestion?: string\n spatial?: {\n min?: Vec3;\n max?: Vec3;\n center?: Vec3;\n }\n details?: unknown\n}\n\n// Point expectation accepted by center and feature matchers\nGeoSpecPointExpectation: Vec3 | GeoSpecAxisExpectation\n\n// One void-continuity waypoint\nGeoSpecVoidWaypoint: Vec3 | {\n occurrence: string;\n}\n\n// Direct geometry-source model load options\nLoadModelSourceOptions: {\n // Source geometry format\n format?: GeoSpecModelFormat\n // Explicit parameters recorded in provenance\n parameters?: Record<string, unknown>\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // … 10 more members in the API reference: source, resources, rootDir, path, name, sourceUnit, stepStreaming, meshLinearTolerance, meshAngularToleranceDegrees, workUnitLimit\n}\n```\n\n## API reference\n\nTo read any other signature, find its name in `api-index.md`, which lists all 368 symbols by file with the lines that hold each, and read that range of the named file.\n\n- `api-functions.md` — Functions\n- `api-constants.md` — Constants\n- `api-types.md` — Types\n- `api-types-2.md` — Types (2)\n- `api-classes.md` — Classes\n\nRead ranges, not whole files. Never copy a reference into a source file.\n",
10
- fingerprint: "5e704f16193aa6d01335dee5374f0fec90037d4651613996896c78d3a2e303ff",
9
+ body: "---\nname: geospec-authoring\ndescription: Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.\n---\n\n# GeoSpec authoring\n\nWrite tests before implementation; assert deterministic, measurable properties.\n\n## Test shape\n\n- Use `*.geospec.ts` or `*.geospec.js`. Import `describe`, `it` and `expectGeo` from `geospec`, and `loadModel` from `geospec/model`.\n- Await `loadModel`, then assert synchronously with `expectGeo(model)`. The host supplies the compiled binding; authoring does not select an engine.\n- Await or return the complete model-load chain from the test callback; detached future loads are not part of a completed test.\n- Omit `parameters` for model-code defaults; use `{ file, parameters }` for variants. Loaded subjects are opaque.\n- `loadModel({ file })` resolves `file` against the project root, the same root as every tool's `targetFile`; the result names that root as `projectRoot` where the host reports it, as `tau mcp` does. A suite in a subfolder names the model by its path from that root, or passes `rootDir`, the project-relative folder the model is in: `loadModel({ file: 'main.ts', rootDir: 'parts/gear' })` loads `parts/gear/main.ts`. A file the root does not hold refuses with `GEOSPEC_MODEL_SOURCE_NOT_FOUND`, which names the root and the path it looked for.\n\nCanonical TypeScript authoring:\n\n```ts\nimport { describe, expectGeo, it } from 'geospec';\nimport { loadModel } from 'geospec/model';\n\ndescribe('main geometry', () => {\n it('has the intended envelope', async () => {\n const model = await loadModel({ file: 'main.ts', parameters: { width: 120 } });\n expectGeo(model).toHaveBoundingBox({ size: { x: 120 }, tolerance: 1 });\n });\n});\n```\n\nSpecs are always TypeScript or JavaScript, whatever the model's kernel: `loadModel({ file })` evaluates `main.ts`, `main.tsx`, `main.py`, `main.scad`, `main.kcl` or `main.cs` through the exporter the host or the project's `geospec.config.ts` supplies. On Tau projects that is Tau's runtime, configured in `tau.config.ts`; never set GeoSpec exporters there. `test_model` discovers `*.geospec.ts` and `*.geospec.js` only.\n\nEvery matcher takes one expectation object:\n\n- Wrong: `expectGeo(model).toHaveVolume(5000, { tolerance: 5 })`. Correct: `expectGeo(model).toHaveVolume({ value: 5000, tolerance: 5 })`.\n- Wrong: `await expectGeo(model)…`. Correct: await `loadModel`, then assert synchronously.\n- Wrong: exact-feature matchers on a mesh kernel (Manifold, JSCAD, OpenSCAD, PicoGK, PicoVoxel). Correct: `toBeValidBrep`, `toHaveTopologyCounts`, the face, hole, pattern, chamfer, fillet and wall-thickness matchers, `toHaveStepUnits`, `toHaveProductStructure` and `toHaveVoidContinuity` need STEP BRep evidence and report unsupported on meshes; assert meshes with bounds, volume, surface area, connected components and `toBeWatertight`.\n\n## Loads and budgets\n\n- GeoSpec has no `beforeAll`. Write each `loadModel` inside its own `it`: within one run, identical loads (same file or code, parameters, format, tolerances and region) share one export and one admission. Each load keeps its own `loadId` and records `reused: true`, and a file is reused only while its source closure is unchanged.\n- `loadModel({ file, workUnitLimit })` sets the default claim budget for that subject, and `expectGeo(model, { workUnitLimit })` overrides it for one chain. The budget is a positive integer no greater than 512000000. A `MATCHER_TIMEOUT` refusal reports `details.limit` and, when measured, `details.used`; its `test_model` row (category `work-budget`) names in `limit.retry` the one `workUnitLimit` that can decide the claim, or `null` at the host maximum. Never treat the refusal as a pass.\n- `loadModel({ file, region: { min: [x, y, z], max: [x, y, z] } })` makes a separate claim about one millimetre box, never a stand-in for a whole-model claim. The kernel crops before export (PicoVoxel trims Voxels parts), and other kernels, STEP loads and unconfirmed crops refuse with a typed diagnostic. A cropped subject answers only bounding box, volume, surface area, mass and center of mass of its region, and its assertions record the region. Every other matcher on it refuses with `GEOSPEC_REGION_WHOLE_MODEL_CLAIM`. Name the region in the test, and keep whole-model claims on whole-model loads.\n- A host or engine limit leaves a claim undecided, never false: `test_model` reports it as inconclusive with category `analysis-limit`, `resource-limit` or `work-budget` and a typed `limit` naming the limit and its numbers. Keep the model as designed: never coarsen, shrink or crop it to fit. Only a `work-budget` row's `limit.retry` is a retry, and `testTimeout` only answers a `timeout` row; report every other limit row as not decided at this capacity.\n- An unknown or mistyped expectation field refuses only that claim, with `GEOSPEC_INVALID_EXPECTATION` naming `details.field`; its `test_model` row is inconclusive, category `invalid-expectation`. Fix the expectation and keep the assertion.\n- `toHaveCenterOfMass({ point, tolerance })` takes an exact point. Per-axis ranges such as `{ y: { greaterThan: 1 } }` are refused.\n\n## Ceilings\n\nFixed in this release; design within them, and report a claim that meets one as not decided.\n\n| Ceiling | Value | Where an agent meets it |\n| ------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| `maxSubjectBytes`, `maxResourceBytes` | 64 MiB each | A mesh `loadModel({ file })` exports about 72 bytes per triangle, so admission refuses near 930,000 triangles (`resource-limit`). |\n| `maxTotalBinaryBytes` | 128 MiB | All subjects of one load together. |\n| `maxTriangles`, `maxVertices` | 4,000,000, 2,000,000 | A decoded mesh. |\n| `continuousEvidenceBytes` | 32 MiB | `toHaveNoComponentInterference` on a large assembly (`analysis-limit`). |\n| `workUnitLimit` | 512000000 at most | Any claim; the only limit an argument raises. |\n\nLoad `format: 'step'` only for the exact-BRep matchers above; every other matcher answers on the cheaper mesh.\n\n## Coverage\n\nBounds and physical properties are insufficient. Cover major components, named features, variants, dimensions/positions, disjoint counts, closure, interference and supported exact features. Explicitly report unsupported requirements; proxies cannot fulfill them.\n\n- `toHaveBoundingBox`: size/position.\n- `toHaveConnectedComponents`: qualified material connectivity; visual touching cannot justify tolerance changes.\n- `toBeWatertight`: closed edge incidence, not manifold validity, fusion or material connectivity. Bounds prove none of these. Missing evidence is unsupported.\n- `toHaveSurfaceArea`, `toHaveVolume`, `toHaveCenterOfMass`, `toHaveMass`: physical measurements.\n- `toHaveNoComponentInterference`: overlap; allow deliberate press fits.\n- `loadModel({ file, format: 'step' })`: use BRep evidence for supported topology and feature assertions. Missing occurrence, interference, clearance, containment, lumen, wall, section or motion evidence is unsupported, not a pass or an envelope substitute.\n\nTest assemblies and independent units; preserve sibling coverage when adding files. Non-top-level geometry needs kernel export/invocation, not test removal.\n\nFix geometry at its root; never weaken tolerances, delete assertions, or reduce detail to pass.\n\n`public-api-index.md` covers the host and framework API; authoring needs only the Core API below and `api-*` files.\n\n## Core API\n\nThe symbols real Tau models use most, as verbatim declarations. Anything not shown is in the API reference below.\n\n### Functions\n\n```ts\n// Start a geometry assertion chain\nexport declare function expectGeo(subject: GeoSpecSubject, options?: GeoSpecExpectOptions): GeoSpecMatcher;\n\n// A model admitted by one live GeoSpec host scope\nGeoSpecSubject: {\n readonly [subjectBrand]: true\n}\n\n// Per-chain options accepted by `expectGeo(subject, options)`\nGeoSpecExpectOptions: {\n // Deterministic work-unit budget for every claim in this chain\n readonly workUnitLimit?: number\n}\n\n// Load a CAD model into GeoSpec evidence\nexport declare function loadModel<Code extends Record<string, string> = Record<string, string>>(_options: LoadModelOptions<Code>): Promise<GeoSpecSubject>;\n\n// Options accepted by {@link import ('./load-model.js').loadModel}\nLoadModelOptions: LoadModelSourceOptions | LoadModelCodeOptions<Code> | LoadModelFileOptions\n // Project-relative folder `file` resolves in, for a suite that loads from somewhere other than…\n rootDir?: string\n // Geometry format to export\n format?: GeoSpecModelFormat\n // Explicit parameters passed to the exporter\n parameters?: Record<string, unknown>\n // STEP reader strategy used for STEP exports\n stepStreaming?: StepStreamingMode\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // Linear tolerance used while meshing exact BRep evidence\n meshLinearTolerance?: number\n // Angular tolerance in degrees used while meshing exact BRep evidence\n meshAngularToleranceDegrees?: number\n // Default work-unit budget for every claim on this subject\n workUnitLimit?: number\n\n// Geometry formats accepted by {@link import ('./load-model.js').loadModel}\nGeoSpecModelFormat: MeshFileFormat | 'step' | 'stp'\n\n// GeoSpec test helper used inside VM-executed test modules\nexport declare function it(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n\n// GeoSpec suite helper used inside VM-executed test modules\nexport declare function describe(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n\n// Alias for {@link it}\nexport declare function test(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n```\n\n### Types\n\n```ts\n// Assertion chain returned by `expectGeo(subject)`\nGeoSpecMatcher: {\n // Core-owned negation\n readonly not: Omit<GeoSpecMatcher, 'not'>\n // Assert the fixed rational plate contract\n toSatisfyRationalPlate(): GeoSpecAssertion;\n // Assert the fixed parallel-plane distance contract\n toSatisfyParallelPlaneDistance(): GeoSpecAssertion;\n // Assert axis-aligned bounds, size, or center for a loaded geometry subject\n toHaveBoundingBox(first: Vec3 | GeoSpecBoundingBoxExpectation, second?: Vec3): GeoSpecAssertion;\n // Assert how many spatially disjoint chunks the mesh contains\n toHaveConnectedComponents(expected: GeoSpecConnectedComponentsExpectation): GeoSpecAssertion;\n // Assert closed mesh edge incidence\n toBeWatertight(): GeoSpecAssertion;\n // Assert that separate assembly components do not occupy the same solid volume\n toHaveNoComponentInterference(expected?: GeoSpecComponentInterferenceExpectation): GeoSpecAssertion;\n // Assert that named assembly occurrences exist with expected counts and bounds\n toHaveAssemblyOccurrences(expected: GeoSpecAssemblyOccurrencesExpectation): GeoSpecAssertion;\n // Assert that selected entities satisfy declared spatial relationships\n toHaveSpatialRelationships(expected: GeoSpecSpatialRelationshipsExpectation): GeoSpecAssertion;\n // Assert rendered mesh evidence is internally trustworthy for downstream checks\n toHaveMeshIntegrity(expected: GeoSpecMeshIntegrityExpectation): GeoSpecAssertion;\n // Assert that the subject carries no diagnostics at the rejected severities\n toHaveNoDiagnostics(expected?: GeoSpecNoDiagnosticsExpectation): GeoSpecAssertion;\n // Assert total surface area, preferring exact BRep mass properties when available\n toHaveSurfaceArea(expected: GeoSpecSurfaceAreaExpectation): GeoSpecAssertion;\n // Assert enclosed volume, preferring exact BRep mass properties when available\n toHaveVolume(expected: GeoSpecVolumeExpectation): GeoSpecAssertion;\n // Assert mass derived from exact mass properties or volume times density\n toHaveMass(expected: GeoSpecMassExpectation): GeoSpecAssertion;\n // Assert the center of mass or mesh-derived centroid for a closed subject\n toHaveCenterOfMass(expected: GeoSpecCenterOfMassExpectation): GeoSpecAssertion;\n // Assert that exact BRep evidence reports a valid shape\n toBeValidBrep(expected?: GeoSpecValidBrepExpectation): GeoSpecAssertion;\n // Assert exact BRep topology counts\n toHaveTopologyCounts(expected: GeoSpecTopologyCountsExpectation): GeoSpecAssertion;\n // Assert the STEP unit evidence\n toHaveStepUnits(expected: GeoSpecStepUnitsExpectation): GeoSpecAssertion;\n // Assert STEP product-structure evidence\n toHaveProductStructure(expected: GeoSpecProductStructureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a planar face matching the requested constraints\n toHavePlanarFace(expected: GeoSpecPlanarFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a cylindrical face with the requested radius and axis\n toHaveCylindricalFace(expected: GeoSpecCylindricalFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a circular hole matching diameter, center, and axis\n toHaveCircularHole(expected: GeoSpecCircularHoleExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a repeated circular-hole pattern\n toHaveCircularHolePattern(expected: GeoSpecCircularHolePatternExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a chamfer feature with the requested distance\n toHaveChamferFeature(expected: GeoSpecChamferFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a fillet feature with the requested radius\n toHaveFilletFeature(expected: GeoSpecFilletFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence reports a minimum wall thickness satisfying the expectation\n toHaveMinimumWallThickness(expected: GeoSpecMinimumWallThicknessExpectation): GeoSpecAssertion;\n // Assert that the declared waypoints share one connected void, stay isolated from the declared…\n toHaveVoidContinuity(expected: GeoSpecVoidContinuityExpectation): GeoSpecAssertion;\n}\n\n// Numeric 3D vector\nVec3: readonly [number, number, number]\n\n// Bounding-box expectation accepted by `expectGeo(...).toHaveBoundingBox(...)`\nGeoSpecBoundingBoxExpectation: {\n min?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n max?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n size?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n center?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n tolerance?: number\n}\n\n// Connected-components expectation accepted by `expectGeo(...).toHaveConnectedComponents(...)`\nGeoSpecConnectedComponentsExpectation: {\n count: number\n tolerance?: number\n toleranceMm?: number\n}\n\n// Component-interference expectation accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceExpectation: {\n tolerance?: number\n pairs?: GeoSpecComponentInterferencePairExpectation[]\n allowances?: GeoSpecComponentInterferenceAllowance[]\n}\n\n// Assembly occurrence expectation accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrencesExpectation: {\n occurrences: GeoSpecAssemblyOccurrenceExpectation[]\n uniqueNames?: boolean\n}\n\n// Spatial relationship expectation accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipsExpectation: {\n relationships: GeoSpecSpatialRelationshipExpectation[]\n}\n\n// Mesh integrity expectation accepted by `expectGeo(...).toHaveMeshIntegrity(...)`\nGeoSpecMeshIntegrityExpectation: {\n finitePositions?: boolean\n degenerateTriangles?: {\n count?: number;\n maxCount?: number;\n areaTolerance?: number;\n }\n duplicateFaces?: {\n count?: number;\n maxCount?: number;\n }\n watertight?: boolean\n triangleCount?: GeoSpecNumericExpectation\n}\n\n// Diagnostic severities rejected by `expectGeo(...).toHaveNoDiagnostics(...)`\nGeoSpecNoDiagnosticsExpectation: {\n severities?: Array<GeometryDiagnostic['severity']>\n}\n\n// Surface-area expectation accepted by `expectGeo(...).toHaveSurfaceArea(...)`\nGeoSpecSurfaceAreaExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Volume expectation accepted by `expectGeo(...).toHaveVolume(...)`\nGeoSpecVolumeExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Mass expectation accepted by `expectGeo(...).toHaveMass(...)`\nGeoSpecMassExpectation: {\n value: number | GeoSpecNumericExpectation\n density?: number\n tolerance?: number\n}\n\n// Center-of-mass expectation accepted by `expectGeo(...).toHaveCenterOfMass(...)`\nGeoSpecCenterOfMassExpectation: {\n // Exact expected point in millimetres\n point: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Exact BRep validity expectation accepted by `expectGeo(...).toBeValidBrep(...)`\nGeoSpecValidBrepExpectation: {\n maxTolerance?: number\n freeBounds?: {\n count?: GeoSpecNumericExpectation;\n }\n minEdgeLength?: number\n sameParameter?: boolean\n closedShells?: boolean\n closedWires?: boolean\n}\n\n// Topology-count expectation accepted by `expectGeo(...).toHaveTopologyCounts(...)`\nGeoSpecTopologyCountsExpectation: {\n vertices?: GeoSpecNumericExpectation\n edges?: GeoSpecNumericExpectation\n wires?: GeoSpecNumericExpectation\n faces?: GeoSpecNumericExpectation\n shells?: GeoSpecNumericExpectation\n solids?: GeoSpecNumericExpectation\n compounds?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// STEP unit expectation accepted by `expectGeo(...).toHaveStepUnits(...)`\nGeoSpecStepUnitsExpectation: {\n unit: string\n}\n\n// Product-structure expectation accepted by `expectGeo(...).toHaveProductStructure(...)`\nGeoSpecProductStructureExpectation: {\n names?: string[]\n count?: GeoSpecNumericExpectation\n}\n\n// Planar-face expectation accepted by `expectGeo(...).toHavePlanarFace(...)`\nGeoSpecPlanarFaceExpectation: {\n normal: GeoSpecPointExpectation\n offset: number\n area?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Cylindrical-face expectation accepted by `expectGeo(...).toHaveCylindricalFace(...)`\nGeoSpecCylindricalFaceExpectation: {\n radius: number\n axis: 'x' | 'y' | 'z'\n tolerance?: number\n}\n\n// Circular-hole expectation accepted by `expectGeo(...).toHaveCircularHole(...)`\nGeoSpecCircularHoleExpectation: {\n diameter: number\n through?: boolean\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Circular-hole-pattern expectation accepted by `expectGeo(...).toHaveCircularHolePattern(...)`\nGeoSpecCircularHolePatternExpectation: {\n count: number\n holeDiameter: number\n boltCircleDiameter?: number\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Chamfer-feature expectation accepted by `expectGeo(...).toHaveChamferFeature(...)`\nGeoSpecChamferFeatureExpectation: {\n distance: number\n selection?: string\n tolerance?: number\n}\n\n// Fillet-feature expectation accepted by `expectGeo(...).toHaveFilletFeature(...)`\nGeoSpecFilletFeatureExpectation: {\n radius: number\n selection?: string\n tolerance?: number\n}\n\n// Minimum-wall-thickness expectation accepted by `expectGeo(...).toHaveMinimumWallThickness(...)`\nGeoSpecMinimumWallThicknessExpectation: {\n value: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Void-continuity expectation accepted by `expectGeo(...).toHaveVoidContinuity(...)`\nGeoSpecVoidContinuityExpectation: {\n // Ordered waypoints (>= 1) known to lie in the void being proven\n path: GeoSpecVoidWaypoint[]\n // Occurrence names whose solids bound the void\n material?: string[]\n // Minimum required bottleneck cross-section (mm²), sampled\n minCrossSection?: number\n // Points that must NOT be reachable from the path void (isolation claim)\n isolatedFrom?: Vec3[]\n // Region bounded for the proof (subject frame)\n bounds?: {\n min: Vec3;\n max: Vec3;\n }\n}\n\n// Axis-keyed numeric expectation used by high-level geometry matchers\nGeoSpecAxisExpectation: {\n x?: number\n y?: number\n z?: number\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// A pair-specific component-interference check accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferencePairExpectation: {\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n}\n\n// Intentional component interference allowance accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceAllowance: {\n kind?: 'intentionalInterference'\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n maxVolume?: number\n reason: string\n}\n\n// Assembly occurrence rule accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrenceExpectation: {\n name: GeoSpecComponentSelector\n count?: GeoSpecNumericExpectation\n bounds?: {\n within?: GeoSpecComponentSelector;\n min?: Vec3 | GeoSpecAxisExpectation;\n max?: Vec3 | GeoSpecAxisExpectation;\n center?: GeoSpecPointExpectation;\n tolerance?: number;\n }\n}\n\n// One spatial relationship accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipExpectation: {\n id?: string\n kind: 'contact' | 'clearance' | 'coaxial' | 'concentric' | 'coplanar' | 'parallel' | 'perpendicular' | 'angle' | 'containment' | 'insertion' | 'interference'\n subject: GeoSpecGeometrySelector\n target: GeoSpecGeometrySelector\n tolerance?: number\n angularToleranceDegrees?: number\n // Expected angle in degrees for `kind\n angleDegrees?: number\n // Declared insertion axis (subject-frame direction) for `kind\n axis?: Vec3\n min?: number\n max?: number\n minVolume?: number\n maxVolume?: number\n reason?: string\n}\n\n// Diagnostic emitted by GeoSpec loaders, analyzers, and matchers\nGeometryDiagnostic: {\n code: KernelIssueCode | (string & {})\n severity: 'error' | 'warning' | 'info'\n message: string\n suggestion?: string\n spatial?: {\n min?: Vec3;\n max?: Vec3;\n center?: Vec3;\n }\n details?: unknown\n}\n\n// Point expectation accepted by center and feature matchers\nGeoSpecPointExpectation: Vec3 | GeoSpecAxisExpectation\n\n// One void-continuity waypoint\nGeoSpecVoidWaypoint: Vec3 | {\n occurrence: string;\n}\n\n// Direct geometry-source model load options\nLoadModelSourceOptions: {\n // Source geometry format\n format?: GeoSpecModelFormat\n // Explicit parameters recorded in provenance\n parameters?: Record<string, unknown>\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // … 10 more members in the API reference: source, resources, rootDir, path, name, sourceUnit, stepStreaming, meshLinearTolerance, meshAngularToleranceDegrees, workUnitLimit\n}\n```\n\n## API reference\n\nTo read any other signature, find its name in `api-index.md`, which lists all 299 symbols by file with the lines that hold each, and read that range of the named file.\n\n- `api-functions.md` — Functions\n- `api-constants.md` — Constants\n- `api-types.md` — Types\n- `api-classes.md` — Classes\n\nRead ranges, not whole files. Never copy a reference into a source file.\n",
10
+ fingerprint: "3fd74c7bf99d7f1b3d6fe23b45c976641a74e396e99b69359cecbe8d9b886c93",
11
11
  files: Object.freeze([
12
- Object.freeze({ path: "SKILL.md", url: new URL("./geospec-authoring/SKILL.md", import.meta.url).href, byteLength: 23657, lineCount: 497, contentKind: 'text', mediaType: 'text/markdown', sha256: "484351daa87c73bc14adbe04ee2ffc961b3c28fd388658cd2174e89d884fea5b" }),
13
- Object.freeze({ path: "api-index.md", url: new URL("./geospec-authoring/api-index.md", import.meta.url).href, byteLength: 30670, lineCount: 389, contentKind: 'text', mediaType: 'text/markdown', sha256: "3b5405c4c680c1e5d97f519f59c00683ab9702adc17be9f518426674469734e9" }),
12
+ Object.freeze({ path: "SKILL.md", url: new URL("./geospec-authoring/SKILL.md", import.meta.url).href, byteLength: 24024, lineCount: 497, contentKind: 'text', mediaType: 'text/markdown', sha256: "19060a4ce60bbb0cc6aba1da7e98f7e17f7de870aa047130a8ae5d58f1ca0845" }),
13
+ Object.freeze({ path: "api-index.md", url: new URL("./geospec-authoring/api-index.md", import.meta.url).href, byteLength: 26968, lineCount: 317, contentKind: 'text', mediaType: 'text/markdown', sha256: "3f4f882aa0df0fa90fe1d9c630e544ce76b00f5079f031a48537f5e5d2ec6b5d" }),
14
14
  Object.freeze({ path: "api-functions.md", url: new URL("./geospec-authoring/api-functions.md", import.meta.url).href, byteLength: 1946, lineCount: 43, contentKind: 'text', mediaType: 'text/markdown', sha256: "b8c0ce6fb9a274b59795b7ca4254d68296cd04d6c7e8124d21f74feb071e960b" }),
15
15
  Object.freeze({ path: "api-constants.md", url: new URL("./geospec-authoring/api-constants.md", import.meta.url).href, byteLength: 1057, lineCount: 17, contentKind: 'text', mediaType: 'text/markdown', sha256: "42c29ab127efb592c0cd7c4d2024d6e2aea8315bec78e476f972166c1b8c1c6b" }),
16
- Object.freeze({ path: "api-types.md", url: new URL("./geospec-authoring/api-types.md", import.meta.url).href, byteLength: 59196, lineCount: 1755, contentKind: 'text', mediaType: 'text/markdown', sha256: "5a7248985515201bb7029f89f7373b1de74f65078a01c6119e526582b4f64085" }),
17
- Object.freeze({ path: "api-types-2.md", url: new URL("./geospec-authoring/api-types-2.md", import.meta.url).href, byteLength: 3210, lineCount: 86, contentKind: 'text', mediaType: 'text/markdown', sha256: "6d75581152c13419ec617081b0b7cd87abd1ffbaf4e8c5567cb3b225a7d35e04" }),
16
+ Object.freeze({ path: "api-types.md", url: new URL("./geospec-authoring/api-types.md", import.meta.url).href, byteLength: 50464, lineCount: 1360, contentKind: 'text', mediaType: 'text/markdown', sha256: "fd903c485e997c8eb615c6042239709e261bbf991563af5a71928e33ecc5066f" }),
18
17
  Object.freeze({ path: "api-classes.md", url: new URL("./geospec-authoring/api-classes.md", import.meta.url).href, byteLength: 550, lineCount: 14, contentKind: 'text', mediaType: 'text/markdown', sha256: "d34a48498cfd33aca05e2c5c1059ca3e2d1bec86046fbc75928d16b375fcae30" }),
19
- Object.freeze({ path: "public-api-index.md", url: new URL("./geospec-authoring/public-api-index.md", import.meta.url).href, byteLength: 73854, lineCount: 951, contentKind: 'text', mediaType: 'text/markdown', sha256: "ce02af19387c7de09717c6754d4fc17ef6429c509ef803493a2866e28d39b66f" }),
20
- Object.freeze({ path: "public-api-functions.md", url: new URL("./geospec-authoring/public-api-functions.md", import.meta.url).href, byteLength: 15034, lineCount: 264, contentKind: 'text', mediaType: 'text/markdown', sha256: "d24f96454d432789ebff1b73278e790a25df6cd7db87f407f0e6ca7fa3a26d88" }),
18
+ Object.freeze({ path: "public-api-index.md", url: new URL("./geospec-authoring/public-api-index.md", import.meta.url).href, byteLength: 69407, lineCount: 847, contentKind: 'text', mediaType: 'text/markdown', sha256: "5985fb1eb4a8b3f398b3c80b0c26c92ed022b5f93ed82854f94b857812747508" }),
19
+ Object.freeze({ path: "public-api-functions.md", url: new URL("./geospec-authoring/public-api-functions.md", import.meta.url).href, byteLength: 16217, lineCount: 284, contentKind: 'text', mediaType: 'text/markdown', sha256: "8f1d70cc0102025bc6ff6241da48cb9434fdf21155f99f0140510c9f222b8757" }),
21
20
  Object.freeze({ path: "public-api-constants.md", url: new URL("./geospec-authoring/public-api-constants.md", import.meta.url).href, byteLength: 2521, lineCount: 52, contentKind: 'text', mediaType: 'text/markdown', sha256: "8d7ecd06cd47616d81d6b5ad0315eee3cb1ed980d6527d6e2edf40c44231d871" }),
22
- Object.freeze({ path: "public-api-types.md", url: new URL("./geospec-authoring/public-api-types.md", import.meta.url).href, byteLength: 63658, lineCount: 2060, contentKind: 'text', mediaType: 'text/markdown', sha256: "f80b75d3f3b7edca2245e3d549030125477f8ba404875ee8e4a7b9edf90c18f4" }),
23
- Object.freeze({ path: "public-api-types-2.md", url: new URL("./geospec-authoring/public-api-types-2.md", import.meta.url).href, byteLength: 53802, lineCount: 1422, contentKind: 'text', mediaType: 'text/markdown', sha256: "69965c3798878d951f1c52224e6e26712f8d4dbd88e4e911ca6ccd70a6bcd8b6" }),
24
- Object.freeze({ path: "public-api-types-3.md", url: new URL("./geospec-authoring/public-api-types-3.md", import.meta.url).href, byteLength: 18307, lineCount: 575, contentKind: 'text', mediaType: 'text/markdown', sha256: "e241bc2a741661c4f12698f0a351336999f7526a15bb5e117ab45ce4c34b254a" }),
21
+ Object.freeze({ path: "public-api-types.md", url: new URL("./geospec-authoring/public-api-types.md", import.meta.url).href, byteLength: 57017, lineCount: 1545, contentKind: 'text', mediaType: 'text/markdown', sha256: "7ccfe6ba33ccb2c15c0576eb24e5c9be0bfc1f7411882850b30a1117676ac46f" }),
22
+ Object.freeze({ path: "public-api-types-2.md", url: new URL("./geospec-authoring/public-api-types-2.md", import.meta.url).href, byteLength: 54865, lineCount: 1550, contentKind: 'text', mediaType: 'text/markdown', sha256: "746f29b1536e8353407d9985691114cad8d773d7f512d31a2512d3b929d5ef78" }),
23
+ Object.freeze({ path: "public-api-types-3.md", url: new URL("./geospec-authoring/public-api-types-3.md", import.meta.url).href, byteLength: 15486, lineCount: 439, contentKind: 'text', mediaType: 'text/markdown', sha256: "3314597895a5fddb201401437bb2691b7b56c76e0b314317ac94a14de4507ad6" }),
25
24
  Object.freeze({ path: "public-api-classes.md", url: new URL("./geospec-authoring/public-api-classes.md", import.meta.url).href, byteLength: 1024, lineCount: 23, contentKind: 'text', mediaType: 'text/markdown', sha256: "569639b75b3a3fae86e3bcbe3dc61a746ddd67cb4dec7ccb6374556d419d02a9" })
26
25
  ]),
27
26
  }),
package/agent/skills.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "slug": "geospec-authoring",
5
5
  "name": "GeoSpec authoring",
6
6
  "description": "Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.",
7
- "version": "0.1.0-beta.9",
7
+ "version": "0.4.0",
8
8
  "whenToUse": "Use before creating or editing *.geospec.ts or *.geospec.js tests, for a model in any kernel.",
9
9
  "directory": "geospec-authoring",
10
10
  "files": [
@@ -13,7 +13,6 @@
13
13
  "api-functions.md",
14
14
  "api-constants.md",
15
15
  "api-types.md",
16
- "api-types-2.md",
17
16
  "api-classes.md",
18
17
  "public-api-index.md",
19
18
  "public-api-functions.md",
@@ -23,7 +22,7 @@
23
22
  "public-api-types-3.md",
24
23
  "public-api-classes.md"
25
24
  ],
26
- "body": "---\nname: geospec-authoring\ndescription: Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.\n---\n\n# GeoSpec authoring\n\nWrite tests before implementation; assert deterministic, measurable properties.\n\n## Test shape\n\n- Use `*.geospec.ts` or `*.geospec.js`. Import `describe`, `it` and `expectGeo` from `geospec`, and `loadModel` from `geospec/model`.\n- Await `loadModel`, then assert synchronously with `expectGeo(model)`. The host supplies the compiled binding; authoring does not select an engine.\n- Await or return the complete model-load chain from the test callback; detached future loads are not part of a completed test.\n- Omit `parameters` for model-code defaults; use `{ file, parameters }` for variants. Loaded subjects are opaque.\n- `loadModel({ file })` resolves `file` against the project root, the same root as every tool's `targetFile`; the result names that root as `projectRoot` where the host reports it, as `tau mcp` does. A suite in a subfolder names the model by its path from that root, or passes `rootDir`, the project-relative folder the model is in: `loadModel({ file: 'main.ts', rootDir: 'parts/gear' })` loads `parts/gear/main.ts`. A file the root does not hold refuses with `GEOSPEC_MODEL_SOURCE_NOT_FOUND`, which names the root and the path it looked for.\n\nCanonical TypeScript authoring:\n\n```ts\nimport { describe, expectGeo, it } from 'geospec';\nimport { loadModel } from 'geospec/model';\n\ndescribe('main geometry', () => {\n it('has the intended envelope', async () => {\n const model = await loadModel({ file: 'main.ts', parameters: { width: 120 } });\n expectGeo(model).toHaveBoundingBox({ size: { x: 120 }, tolerance: 1 });\n });\n});\n```\n\nSpecs are always TypeScript or JavaScript, whatever the model's kernel: `loadModel({ file })` evaluates `main.ts`, `main.tsx`, `main.py`, `main.scad`, `main.kcl` or `main.cs` through Tau's runtime. `test_model` discovers `*.geospec.ts` and `*.geospec.js` only.\n\nEvery matcher takes one expectation object:\n\n- Wrong: `expectGeo(model).toHaveVolume(5000, { tolerance: 5 })`. Correct: `expectGeo(model).toHaveVolume({ value: 5000, tolerance: 5 })`.\n- Wrong: `await expectGeo(model)…`. Correct: await `loadModel`, then assert synchronously.\n- Wrong: exact-feature matchers on a mesh kernel (Manifold, JSCAD, OpenSCAD, PicoGK, PicoVoxel). Correct: `toBeValidBrep`, `toHaveTopologyCounts`, the face, hole, pattern, chamfer, fillet and wall-thickness matchers, `toHaveStepUnits`, `toHaveProductStructure` and `toHaveVoidContinuity` need STEP BRep evidence and report unsupported on meshes; assert meshes with bounds, volume, surface area, connected components and `toBeWatertight`.\n\n## Loads and budgets\n\n- GeoSpec has no `beforeAll`. Write each `loadModel` inside its own `it`: within one run, identical loads (same file or code, parameters, format, tolerances and region) share one export and one admission. Each load keeps its own `loadId` and records `reused: true`, and a file is reused only while its source closure is unchanged.\n- `loadModel({ file, workUnitLimit })` sets the default claim budget for that subject, and `expectGeo(model, { workUnitLimit })` overrides it for one chain. The budget is a positive integer no greater than 512000000. A `MATCHER_TIMEOUT` refusal reports `details.limit` and, when measured, `details.used`; its `test_model` row (category `work-budget`) names in `limit.retry` the one `workUnitLimit` that can decide the claim, or `null` at the host maximum. Never treat the refusal as a pass.\n- `loadModel({ file, region: { min: [x, y, z], max: [x, y, z] } })` makes a separate claim about one millimetre box, never a stand-in for a whole-model claim. The kernel crops before export (PicoVoxel trims Voxels parts), and other kernels, STEP loads and unconfirmed crops refuse with a typed diagnostic. A cropped subject answers only bounding box, volume, surface area, mass and center of mass of its region, and its assertions record the region. Every other matcher on it refuses with `GEOSPEC_REGION_WHOLE_MODEL_CLAIM`. Name the region in the test, and keep whole-model claims on whole-model loads.\n- A host or engine limit leaves a claim undecided, never false: `test_model` reports it as inconclusive with category `analysis-limit`, `resource-limit` or `work-budget` and a typed `limit` naming the limit and its numbers. Keep the model as designed: never coarsen, shrink or crop it to fit. Only a `work-budget` row's `limit.retry` is a retry, and `testTimeout` only answers a `timeout` row; report every other limit row as not decided at this capacity.\n- `toHaveCenterOfMass({ point, tolerance })` takes an exact point. Per-axis ranges such as `{ y: { greaterThan: 1 } }` are refused.\n\n## Ceilings\n\nFixed in this release; design within them, and report a claim that meets one as not decided.\n\n| Ceiling | Value | Where an agent meets it |\n| ------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| `maxSubjectBytes`, `maxResourceBytes` | 64 MiB each | A mesh `loadModel({ file })` exports about 72 bytes per triangle, so admission refuses near 930,000 triangles (`resource-limit`). |\n| `maxTotalBinaryBytes` | 128 MiB | All subjects of one load together. |\n| `maxTriangles`, `maxVertices` | 4,000,000, 2,000,000 | A decoded mesh. |\n| `continuousEvidenceBytes` | 32 MiB | `toHaveNoComponentInterference` on a large assembly (`analysis-limit`). |\n| `workUnitLimit` | 512000000 at most | Any claim; the only limit an argument raises. |\n\nLoad `format: 'step'` only for the exact-BRep matchers above; every other matcher answers on the cheaper mesh.\n\n## Coverage\n\nBounds and physical properties are insufficient. Cover major components, named features, variants, dimensions/positions, disjoint counts, closure, interference and supported exact features. Explicitly report unsupported requirements; proxies cannot fulfill them.\n\n- `toHaveBoundingBox`: size/position.\n- `toHaveConnectedComponents`: qualified material connectivity; visual touching cannot justify tolerance changes.\n- `toBeWatertight`: closed edge incidence, not manifold validity, fusion or material connectivity. Bounds prove none of these. Missing evidence is unsupported.\n- `toHaveSurfaceArea`, `toHaveVolume`, `toHaveCenterOfMass`, `toHaveMass`: physical measurements.\n- `toHaveNoComponentInterference`: overlap; allow deliberate press fits.\n- `loadModel({ file, format: 'step' })`: use BRep evidence for supported topology and feature assertions. Missing occurrence, interference, clearance, containment, lumen, wall, section or motion evidence is unsupported, not a pass or an envelope substitute.\n\nTest assemblies and independent units; preserve sibling coverage when adding files. Non-top-level geometry needs kernel export/invocation, not test removal.\n\nFix geometry at its root; never weaken tolerances, delete assertions, or reduce detail to pass.\n\n`public-api-index.md` covers the host and framework API; authoring needs only the Core API below and `api-*` files.\n\n## Core API\n\nThe symbols real Tau models use most, as verbatim declarations. Anything not shown is in the API reference below.\n\n### Functions\n\n```ts\n// Start a geometry assertion chain\nexport declare function expectGeo(subject: GeoSpecSubject, options?: GeoSpecExpectOptions): GeoSpecMatcher;\n\n// A model admitted by one live GeoSpec host scope\nGeoSpecSubject: {\n readonly [subjectBrand]: true\n}\n\n// Per-chain options accepted by `expectGeo(subject, options)`\nGeoSpecExpectOptions: {\n // Deterministic work-unit budget for every claim in this chain\n readonly workUnitLimit?: number\n}\n\n// Load a CAD model into GeoSpec evidence\nexport declare function loadModel<Code extends Record<string, string> = Record<string, string>>(_options: LoadModelOptions<Code>): Promise<GeoSpecSubject>;\n\n// Options accepted by {@link import ('./load-model.js').loadModel}\nLoadModelOptions: LoadModelSourceOptions | LoadModelCodeOptions<Code> | LoadModelFileOptions\n // Project-relative folder `file` resolves in, for a suite that loads from somewhere other than…\n rootDir?: string\n // Geometry format to export\n format?: GeoSpecModelFormat\n // Explicit parameters passed to the exporter\n parameters?: Record<string, unknown>\n // STEP reader strategy used for STEP exports\n stepStreaming?: StepStreamingMode\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // Linear tolerance used while meshing exact BRep evidence\n meshLinearTolerance?: number\n // Angular tolerance in degrees used while meshing exact BRep evidence\n meshAngularToleranceDegrees?: number\n // Default work-unit budget for every claim on this subject\n workUnitLimit?: number\n\n// Geometry formats accepted by {@link import ('./load-model.js').loadModel}\nGeoSpecModelFormat: MeshFileFormat | 'step' | 'stp'\n\n// GeoSpec test helper used inside VM-executed test modules\nexport declare function it(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n\n// GeoSpec suite helper used inside VM-executed test modules\nexport declare function describe(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n\n// Alias for {@link it}\nexport declare function test(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n```\n\n### Types\n\n```ts\n// Assertion chain returned by `expectGeo(subject)`\nGeoSpecMatcher: {\n // Core-owned negation\n readonly not: Omit<GeoSpecMatcher, 'not'>\n // Assert the fixed rational plate contract\n toSatisfyRationalPlate(): GeoSpecAssertion;\n // Assert the fixed parallel-plane distance contract\n toSatisfyParallelPlaneDistance(): GeoSpecAssertion;\n // Assert axis-aligned bounds, size, or center for a loaded geometry subject\n toHaveBoundingBox(first: Vec3 | GeoSpecBoundingBoxExpectation, second?: Vec3): GeoSpecAssertion;\n // Assert how many spatially disjoint chunks the mesh contains\n toHaveConnectedComponents(expected: GeoSpecConnectedComponentsExpectation): GeoSpecAssertion;\n // Assert closed mesh edge incidence\n toBeWatertight(): GeoSpecAssertion;\n // Assert that separate assembly components do not occupy the same solid volume\n toHaveNoComponentInterference(expected?: GeoSpecComponentInterferenceExpectation): GeoSpecAssertion;\n // Assert that named assembly occurrences exist with expected counts and bounds\n toHaveAssemblyOccurrences(expected: GeoSpecAssemblyOccurrencesExpectation): GeoSpecAssertion;\n // Assert that selected entities satisfy declared spatial relationships\n toHaveSpatialRelationships(expected: GeoSpecSpatialRelationshipsExpectation): GeoSpecAssertion;\n // Assert rendered mesh evidence is internally trustworthy for downstream checks\n toHaveMeshIntegrity(expected: GeoSpecMeshIntegrityExpectation): GeoSpecAssertion;\n // Assert that the subject carries no diagnostics at the rejected severities\n toHaveNoDiagnostics(expected?: GeoSpecNoDiagnosticsExpectation): GeoSpecAssertion;\n // Assert total surface area, preferring exact BRep mass properties when available\n toHaveSurfaceArea(expected: GeoSpecSurfaceAreaExpectation): GeoSpecAssertion;\n // Assert enclosed volume, preferring exact BRep mass properties when available\n toHaveVolume(expected: GeoSpecVolumeExpectation): GeoSpecAssertion;\n // Assert mass derived from exact mass properties or volume times density\n toHaveMass(expected: GeoSpecMassExpectation): GeoSpecAssertion;\n // Assert the center of mass or mesh-derived centroid for a closed subject\n toHaveCenterOfMass(expected: GeoSpecCenterOfMassExpectation): GeoSpecAssertion;\n // Assert that exact BRep evidence reports a valid shape\n toBeValidBrep(expected?: GeoSpecValidBrepExpectation): GeoSpecAssertion;\n // Assert exact BRep topology counts\n toHaveTopologyCounts(expected: GeoSpecTopologyCountsExpectation): GeoSpecAssertion;\n // Assert the STEP unit evidence\n toHaveStepUnits(expected: GeoSpecStepUnitsExpectation): GeoSpecAssertion;\n // Assert STEP product-structure evidence\n toHaveProductStructure(expected: GeoSpecProductStructureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a planar face matching the requested constraints\n toHavePlanarFace(expected: GeoSpecPlanarFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a cylindrical face with the requested radius and axis\n toHaveCylindricalFace(expected: GeoSpecCylindricalFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a circular hole matching diameter, center, and axis\n toHaveCircularHole(expected: GeoSpecCircularHoleExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a repeated circular-hole pattern\n toHaveCircularHolePattern(expected: GeoSpecCircularHolePatternExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a chamfer feature with the requested distance\n toHaveChamferFeature(expected: GeoSpecChamferFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a fillet feature with the requested radius\n toHaveFilletFeature(expected: GeoSpecFilletFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence reports a minimum wall thickness satisfying the expectation\n toHaveMinimumWallThickness(expected: GeoSpecMinimumWallThicknessExpectation): GeoSpecAssertion;\n // Assert that the declared waypoints share one connected void, stay isolated from the declared…\n toHaveVoidContinuity(expected: GeoSpecVoidContinuityExpectation): GeoSpecAssertion;\n}\n\n// Numeric 3D vector\nVec3: readonly [number, number, number]\n\n// Bounding-box expectation accepted by `expectGeo(...).toHaveBoundingBox(...)`\nGeoSpecBoundingBoxExpectation: {\n min?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n max?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n size?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n center?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n tolerance?: number\n}\n\n// Connected-components expectation accepted by `expectGeo(...).toHaveConnectedComponents(...)`\nGeoSpecConnectedComponentsExpectation: {\n count: number\n tolerance?: number\n toleranceMm?: number\n}\n\n// Component-interference expectation accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceExpectation: {\n tolerance?: number\n pairs?: GeoSpecComponentInterferencePairExpectation[]\n allowances?: GeoSpecComponentInterferenceAllowance[]\n}\n\n// Assembly occurrence expectation accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrencesExpectation: {\n occurrences: GeoSpecAssemblyOccurrenceExpectation[]\n uniqueNames?: boolean\n}\n\n// Spatial relationship expectation accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipsExpectation: {\n relationships: GeoSpecSpatialRelationshipExpectation[]\n}\n\n// Mesh integrity expectation accepted by `expectGeo(...).toHaveMeshIntegrity(...)`\nGeoSpecMeshIntegrityExpectation: {\n finitePositions?: boolean\n degenerateTriangles?: {\n count?: number;\n maxCount?: number;\n areaTolerance?: number;\n }\n duplicateFaces?: {\n count?: number;\n maxCount?: number;\n }\n watertight?: boolean\n triangleCount?: GeoSpecNumericExpectation\n}\n\n// Diagnostic severities rejected by `expectGeo(...).toHaveNoDiagnostics(...)`\nGeoSpecNoDiagnosticsExpectation: {\n severities?: Array<GeometryDiagnostic['severity']>\n}\n\n// Surface-area expectation accepted by `expectGeo(...).toHaveSurfaceArea(...)`\nGeoSpecSurfaceAreaExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Volume expectation accepted by `expectGeo(...).toHaveVolume(...)`\nGeoSpecVolumeExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Mass expectation accepted by `expectGeo(...).toHaveMass(...)`\nGeoSpecMassExpectation: {\n value: number | GeoSpecNumericExpectation\n density?: number\n tolerance?: number\n}\n\n// Center-of-mass expectation accepted by `expectGeo(...).toHaveCenterOfMass(...)`\nGeoSpecCenterOfMassExpectation: {\n // Exact expected point in millimetres\n point: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Exact BRep validity expectation accepted by `expectGeo(...).toBeValidBrep(...)`\nGeoSpecValidBrepExpectation: {\n maxTolerance?: number\n freeBounds?: {\n count?: GeoSpecNumericExpectation;\n }\n minEdgeLength?: number\n sameParameter?: boolean\n closedShells?: boolean\n closedWires?: boolean\n}\n\n// Topology-count expectation accepted by `expectGeo(...).toHaveTopologyCounts(...)`\nGeoSpecTopologyCountsExpectation: {\n vertices?: GeoSpecNumericExpectation\n edges?: GeoSpecNumericExpectation\n wires?: GeoSpecNumericExpectation\n faces?: GeoSpecNumericExpectation\n shells?: GeoSpecNumericExpectation\n solids?: GeoSpecNumericExpectation\n compounds?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// STEP unit expectation accepted by `expectGeo(...).toHaveStepUnits(...)`\nGeoSpecStepUnitsExpectation: {\n unit: string\n}\n\n// Product-structure expectation accepted by `expectGeo(...).toHaveProductStructure(...)`\nGeoSpecProductStructureExpectation: {\n names?: string[]\n count?: GeoSpecNumericExpectation\n}\n\n// Planar-face expectation accepted by `expectGeo(...).toHavePlanarFace(...)`\nGeoSpecPlanarFaceExpectation: {\n normal: GeoSpecPointExpectation\n offset: number\n area?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Cylindrical-face expectation accepted by `expectGeo(...).toHaveCylindricalFace(...)`\nGeoSpecCylindricalFaceExpectation: {\n radius: number\n axis: 'x' | 'y' | 'z'\n tolerance?: number\n}\n\n// Circular-hole expectation accepted by `expectGeo(...).toHaveCircularHole(...)`\nGeoSpecCircularHoleExpectation: {\n diameter: number\n through?: boolean\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Circular-hole-pattern expectation accepted by `expectGeo(...).toHaveCircularHolePattern(...)`\nGeoSpecCircularHolePatternExpectation: {\n count: number\n holeDiameter: number\n boltCircleDiameter?: number\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Chamfer-feature expectation accepted by `expectGeo(...).toHaveChamferFeature(...)`\nGeoSpecChamferFeatureExpectation: {\n distance: number\n selection?: string\n tolerance?: number\n}\n\n// Fillet-feature expectation accepted by `expectGeo(...).toHaveFilletFeature(...)`\nGeoSpecFilletFeatureExpectation: {\n radius: number\n selection?: string\n tolerance?: number\n}\n\n// Minimum-wall-thickness expectation accepted by `expectGeo(...).toHaveMinimumWallThickness(...)`\nGeoSpecMinimumWallThicknessExpectation: {\n value: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Void-continuity expectation accepted by `expectGeo(...).toHaveVoidContinuity(...)`\nGeoSpecVoidContinuityExpectation: {\n // Ordered waypoints (>= 1) known to lie in the void being proven\n path: GeoSpecVoidWaypoint[]\n // Occurrence names whose solids bound the void\n material?: string[]\n // Minimum required bottleneck cross-section (mm²), sampled\n minCrossSection?: number\n // Points that must NOT be reachable from the path void (isolation claim)\n isolatedFrom?: Vec3[]\n // Region bounded for the proof (subject frame)\n bounds?: {\n min: Vec3;\n max: Vec3;\n }\n}\n\n// Axis-keyed numeric expectation used by high-level geometry matchers\nGeoSpecAxisExpectation: {\n x?: number\n y?: number\n z?: number\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// A pair-specific component-interference check accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferencePairExpectation: {\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n}\n\n// Intentional component interference allowance accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceAllowance: {\n kind?: 'intentionalInterference'\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n maxVolume?: number\n reason: string\n}\n\n// Assembly occurrence rule accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrenceExpectation: {\n name: GeoSpecComponentSelector\n count?: GeoSpecNumericExpectation\n bounds?: {\n within?: GeoSpecComponentSelector;\n min?: Vec3 | GeoSpecAxisExpectation;\n max?: Vec3 | GeoSpecAxisExpectation;\n center?: GeoSpecPointExpectation;\n tolerance?: number;\n }\n}\n\n// One spatial relationship accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipExpectation: {\n id?: string\n kind: 'contact' | 'clearance' | 'coaxial' | 'concentric' | 'coplanar' | 'parallel' | 'perpendicular' | 'angle' | 'containment' | 'insertion' | 'interference'\n subject: GeoSpecGeometrySelector\n target: GeoSpecGeometrySelector\n tolerance?: number\n angularToleranceDegrees?: number\n // Expected angle in degrees for `kind\n angleDegrees?: number\n // Declared insertion axis (subject-frame direction) for `kind\n axis?: Vec3\n min?: number\n max?: number\n minVolume?: number\n maxVolume?: number\n reason?: string\n}\n\n// Diagnostic emitted by GeoSpec loaders, analyzers, and matchers\nGeometryDiagnostic: {\n code: KernelIssueCode | (string & {})\n severity: 'error' | 'warning' | 'info'\n message: string\n suggestion?: string\n spatial?: {\n min?: Vec3;\n max?: Vec3;\n center?: Vec3;\n }\n details?: unknown\n}\n\n// Point expectation accepted by center and feature matchers\nGeoSpecPointExpectation: Vec3 | GeoSpecAxisExpectation\n\n// One void-continuity waypoint\nGeoSpecVoidWaypoint: Vec3 | {\n occurrence: string;\n}\n\n// Direct geometry-source model load options\nLoadModelSourceOptions: {\n // Source geometry format\n format?: GeoSpecModelFormat\n // Explicit parameters recorded in provenance\n parameters?: Record<string, unknown>\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // … 10 more members in the API reference: source, resources, rootDir, path, name, sourceUnit, stepStreaming, meshLinearTolerance, meshAngularToleranceDegrees, workUnitLimit\n}\n```\n\n## API reference\n\nTo read any other signature, find its name in `api-index.md`, which lists all 368 symbols by file with the lines that hold each, and read that range of the named file.\n\n- `api-functions.md` — Functions\n- `api-constants.md` — Constants\n- `api-types.md` — Types\n- `api-types-2.md` — Types (2)\n- `api-classes.md` — Classes\n\nRead ranges, not whole files. Never copy a reference into a source file.\n"
25
+ "body": "---\nname: geospec-authoring\ndescription: Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.\n---\n\n# GeoSpec authoring\n\nWrite tests before implementation; assert deterministic, measurable properties.\n\n## Test shape\n\n- Use `*.geospec.ts` or `*.geospec.js`. Import `describe`, `it` and `expectGeo` from `geospec`, and `loadModel` from `geospec/model`.\n- Await `loadModel`, then assert synchronously with `expectGeo(model)`. The host supplies the compiled binding; authoring does not select an engine.\n- Await or return the complete model-load chain from the test callback; detached future loads are not part of a completed test.\n- Omit `parameters` for model-code defaults; use `{ file, parameters }` for variants. Loaded subjects are opaque.\n- `loadModel({ file })` resolves `file` against the project root, the same root as every tool's `targetFile`; the result names that root as `projectRoot` where the host reports it, as `tau mcp` does. A suite in a subfolder names the model by its path from that root, or passes `rootDir`, the project-relative folder the model is in: `loadModel({ file: 'main.ts', rootDir: 'parts/gear' })` loads `parts/gear/main.ts`. A file the root does not hold refuses with `GEOSPEC_MODEL_SOURCE_NOT_FOUND`, which names the root and the path it looked for.\n\nCanonical TypeScript authoring:\n\n```ts\nimport { describe, expectGeo, it } from 'geospec';\nimport { loadModel } from 'geospec/model';\n\ndescribe('main geometry', () => {\n it('has the intended envelope', async () => {\n const model = await loadModel({ file: 'main.ts', parameters: { width: 120 } });\n expectGeo(model).toHaveBoundingBox({ size: { x: 120 }, tolerance: 1 });\n });\n});\n```\n\nSpecs are always TypeScript or JavaScript, whatever the model's kernel: `loadModel({ file })` evaluates `main.ts`, `main.tsx`, `main.py`, `main.scad`, `main.kcl` or `main.cs` through the exporter the host or the project's `geospec.config.ts` supplies. On Tau projects that is Tau's runtime, configured in `tau.config.ts`; never set GeoSpec exporters there. `test_model` discovers `*.geospec.ts` and `*.geospec.js` only.\n\nEvery matcher takes one expectation object:\n\n- Wrong: `expectGeo(model).toHaveVolume(5000, { tolerance: 5 })`. Correct: `expectGeo(model).toHaveVolume({ value: 5000, tolerance: 5 })`.\n- Wrong: `await expectGeo(model)…`. Correct: await `loadModel`, then assert synchronously.\n- Wrong: exact-feature matchers on a mesh kernel (Manifold, JSCAD, OpenSCAD, PicoGK, PicoVoxel). Correct: `toBeValidBrep`, `toHaveTopologyCounts`, the face, hole, pattern, chamfer, fillet and wall-thickness matchers, `toHaveStepUnits`, `toHaveProductStructure` and `toHaveVoidContinuity` need STEP BRep evidence and report unsupported on meshes; assert meshes with bounds, volume, surface area, connected components and `toBeWatertight`.\n\n## Loads and budgets\n\n- GeoSpec has no `beforeAll`. Write each `loadModel` inside its own `it`: within one run, identical loads (same file or code, parameters, format, tolerances and region) share one export and one admission. Each load keeps its own `loadId` and records `reused: true`, and a file is reused only while its source closure is unchanged.\n- `loadModel({ file, workUnitLimit })` sets the default claim budget for that subject, and `expectGeo(model, { workUnitLimit })` overrides it for one chain. The budget is a positive integer no greater than 512000000. A `MATCHER_TIMEOUT` refusal reports `details.limit` and, when measured, `details.used`; its `test_model` row (category `work-budget`) names in `limit.retry` the one `workUnitLimit` that can decide the claim, or `null` at the host maximum. Never treat the refusal as a pass.\n- `loadModel({ file, region: { min: [x, y, z], max: [x, y, z] } })` makes a separate claim about one millimetre box, never a stand-in for a whole-model claim. The kernel crops before export (PicoVoxel trims Voxels parts), and other kernels, STEP loads and unconfirmed crops refuse with a typed diagnostic. A cropped subject answers only bounding box, volume, surface area, mass and center of mass of its region, and its assertions record the region. Every other matcher on it refuses with `GEOSPEC_REGION_WHOLE_MODEL_CLAIM`. Name the region in the test, and keep whole-model claims on whole-model loads.\n- A host or engine limit leaves a claim undecided, never false: `test_model` reports it as inconclusive with category `analysis-limit`, `resource-limit` or `work-budget` and a typed `limit` naming the limit and its numbers. Keep the model as designed: never coarsen, shrink or crop it to fit. Only a `work-budget` row's `limit.retry` is a retry, and `testTimeout` only answers a `timeout` row; report every other limit row as not decided at this capacity.\n- An unknown or mistyped expectation field refuses only that claim, with `GEOSPEC_INVALID_EXPECTATION` naming `details.field`; its `test_model` row is inconclusive, category `invalid-expectation`. Fix the expectation and keep the assertion.\n- `toHaveCenterOfMass({ point, tolerance })` takes an exact point. Per-axis ranges such as `{ y: { greaterThan: 1 } }` are refused.\n\n## Ceilings\n\nFixed in this release; design within them, and report a claim that meets one as not decided.\n\n| Ceiling | Value | Where an agent meets it |\n| ------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| `maxSubjectBytes`, `maxResourceBytes` | 64 MiB each | A mesh `loadModel({ file })` exports about 72 bytes per triangle, so admission refuses near 930,000 triangles (`resource-limit`). |\n| `maxTotalBinaryBytes` | 128 MiB | All subjects of one load together. |\n| `maxTriangles`, `maxVertices` | 4,000,000, 2,000,000 | A decoded mesh. |\n| `continuousEvidenceBytes` | 32 MiB | `toHaveNoComponentInterference` on a large assembly (`analysis-limit`). |\n| `workUnitLimit` | 512000000 at most | Any claim; the only limit an argument raises. |\n\nLoad `format: 'step'` only for the exact-BRep matchers above; every other matcher answers on the cheaper mesh.\n\n## Coverage\n\nBounds and physical properties are insufficient. Cover major components, named features, variants, dimensions/positions, disjoint counts, closure, interference and supported exact features. Explicitly report unsupported requirements; proxies cannot fulfill them.\n\n- `toHaveBoundingBox`: size/position.\n- `toHaveConnectedComponents`: qualified material connectivity; visual touching cannot justify tolerance changes.\n- `toBeWatertight`: closed edge incidence, not manifold validity, fusion or material connectivity. Bounds prove none of these. Missing evidence is unsupported.\n- `toHaveSurfaceArea`, `toHaveVolume`, `toHaveCenterOfMass`, `toHaveMass`: physical measurements.\n- `toHaveNoComponentInterference`: overlap; allow deliberate press fits.\n- `loadModel({ file, format: 'step' })`: use BRep evidence for supported topology and feature assertions. Missing occurrence, interference, clearance, containment, lumen, wall, section or motion evidence is unsupported, not a pass or an envelope substitute.\n\nTest assemblies and independent units; preserve sibling coverage when adding files. Non-top-level geometry needs kernel export/invocation, not test removal.\n\nFix geometry at its root; never weaken tolerances, delete assertions, or reduce detail to pass.\n\n`public-api-index.md` covers the host and framework API; authoring needs only the Core API below and `api-*` files.\n\n## Core API\n\nThe symbols real Tau models use most, as verbatim declarations. Anything not shown is in the API reference below.\n\n### Functions\n\n```ts\n// Start a geometry assertion chain\nexport declare function expectGeo(subject: GeoSpecSubject, options?: GeoSpecExpectOptions): GeoSpecMatcher;\n\n// A model admitted by one live GeoSpec host scope\nGeoSpecSubject: {\n readonly [subjectBrand]: true\n}\n\n// Per-chain options accepted by `expectGeo(subject, options)`\nGeoSpecExpectOptions: {\n // Deterministic work-unit budget for every claim in this chain\n readonly workUnitLimit?: number\n}\n\n// Load a CAD model into GeoSpec evidence\nexport declare function loadModel<Code extends Record<string, string> = Record<string, string>>(_options: LoadModelOptions<Code>): Promise<GeoSpecSubject>;\n\n// Options accepted by {@link import ('./load-model.js').loadModel}\nLoadModelOptions: LoadModelSourceOptions | LoadModelCodeOptions<Code> | LoadModelFileOptions\n // Project-relative folder `file` resolves in, for a suite that loads from somewhere other than…\n rootDir?: string\n // Geometry format to export\n format?: GeoSpecModelFormat\n // Explicit parameters passed to the exporter\n parameters?: Record<string, unknown>\n // STEP reader strategy used for STEP exports\n stepStreaming?: StepStreamingMode\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // Linear tolerance used while meshing exact BRep evidence\n meshLinearTolerance?: number\n // Angular tolerance in degrees used while meshing exact BRep evidence\n meshAngularToleranceDegrees?: number\n // Default work-unit budget for every claim on this subject\n workUnitLimit?: number\n\n// Geometry formats accepted by {@link import ('./load-model.js').loadModel}\nGeoSpecModelFormat: MeshFileFormat | 'step' | 'stp'\n\n// GeoSpec test helper used inside VM-executed test modules\nexport declare function it(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n\n// GeoSpec suite helper used inside VM-executed test modules\nexport declare function describe(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n\n// Alias for {@link it}\nexport declare function test(name: string, function_: GeoSpecTestCallback): void;\n skip(name: string, function_?: GeoSpecTestCallback): void;\n```\n\n### Types\n\n```ts\n// Assertion chain returned by `expectGeo(subject)`\nGeoSpecMatcher: {\n // Core-owned negation\n readonly not: Omit<GeoSpecMatcher, 'not'>\n // Assert the fixed rational plate contract\n toSatisfyRationalPlate(): GeoSpecAssertion;\n // Assert the fixed parallel-plane distance contract\n toSatisfyParallelPlaneDistance(): GeoSpecAssertion;\n // Assert axis-aligned bounds, size, or center for a loaded geometry subject\n toHaveBoundingBox(first: Vec3 | GeoSpecBoundingBoxExpectation, second?: Vec3): GeoSpecAssertion;\n // Assert how many spatially disjoint chunks the mesh contains\n toHaveConnectedComponents(expected: GeoSpecConnectedComponentsExpectation): GeoSpecAssertion;\n // Assert closed mesh edge incidence\n toBeWatertight(): GeoSpecAssertion;\n // Assert that separate assembly components do not occupy the same solid volume\n toHaveNoComponentInterference(expected?: GeoSpecComponentInterferenceExpectation): GeoSpecAssertion;\n // Assert that named assembly occurrences exist with expected counts and bounds\n toHaveAssemblyOccurrences(expected: GeoSpecAssemblyOccurrencesExpectation): GeoSpecAssertion;\n // Assert that selected entities satisfy declared spatial relationships\n toHaveSpatialRelationships(expected: GeoSpecSpatialRelationshipsExpectation): GeoSpecAssertion;\n // Assert rendered mesh evidence is internally trustworthy for downstream checks\n toHaveMeshIntegrity(expected: GeoSpecMeshIntegrityExpectation): GeoSpecAssertion;\n // Assert that the subject carries no diagnostics at the rejected severities\n toHaveNoDiagnostics(expected?: GeoSpecNoDiagnosticsExpectation): GeoSpecAssertion;\n // Assert total surface area, preferring exact BRep mass properties when available\n toHaveSurfaceArea(expected: GeoSpecSurfaceAreaExpectation): GeoSpecAssertion;\n // Assert enclosed volume, preferring exact BRep mass properties when available\n toHaveVolume(expected: GeoSpecVolumeExpectation): GeoSpecAssertion;\n // Assert mass derived from exact mass properties or volume times density\n toHaveMass(expected: GeoSpecMassExpectation): GeoSpecAssertion;\n // Assert the center of mass or mesh-derived centroid for a closed subject\n toHaveCenterOfMass(expected: GeoSpecCenterOfMassExpectation): GeoSpecAssertion;\n // Assert that exact BRep evidence reports a valid shape\n toBeValidBrep(expected?: GeoSpecValidBrepExpectation): GeoSpecAssertion;\n // Assert exact BRep topology counts\n toHaveTopologyCounts(expected: GeoSpecTopologyCountsExpectation): GeoSpecAssertion;\n // Assert the STEP unit evidence\n toHaveStepUnits(expected: GeoSpecStepUnitsExpectation): GeoSpecAssertion;\n // Assert STEP product-structure evidence\n toHaveProductStructure(expected: GeoSpecProductStructureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a planar face matching the requested constraints\n toHavePlanarFace(expected: GeoSpecPlanarFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a cylindrical face with the requested radius and axis\n toHaveCylindricalFace(expected: GeoSpecCylindricalFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a circular hole matching diameter, center, and axis\n toHaveCircularHole(expected: GeoSpecCircularHoleExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a repeated circular-hole pattern\n toHaveCircularHolePattern(expected: GeoSpecCircularHolePatternExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a chamfer feature with the requested distance\n toHaveChamferFeature(expected: GeoSpecChamferFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a fillet feature with the requested radius\n toHaveFilletFeature(expected: GeoSpecFilletFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence reports a minimum wall thickness satisfying the expectation\n toHaveMinimumWallThickness(expected: GeoSpecMinimumWallThicknessExpectation): GeoSpecAssertion;\n // Assert that the declared waypoints share one connected void, stay isolated from the declared…\n toHaveVoidContinuity(expected: GeoSpecVoidContinuityExpectation): GeoSpecAssertion;\n}\n\n// Numeric 3D vector\nVec3: readonly [number, number, number]\n\n// Bounding-box expectation accepted by `expectGeo(...).toHaveBoundingBox(...)`\nGeoSpecBoundingBoxExpectation: {\n min?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n max?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n size?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n center?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n tolerance?: number\n}\n\n// Connected-components expectation accepted by `expectGeo(...).toHaveConnectedComponents(...)`\nGeoSpecConnectedComponentsExpectation: {\n count: number\n tolerance?: number\n toleranceMm?: number\n}\n\n// Component-interference expectation accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceExpectation: {\n tolerance?: number\n pairs?: GeoSpecComponentInterferencePairExpectation[]\n allowances?: GeoSpecComponentInterferenceAllowance[]\n}\n\n// Assembly occurrence expectation accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrencesExpectation: {\n occurrences: GeoSpecAssemblyOccurrenceExpectation[]\n uniqueNames?: boolean\n}\n\n// Spatial relationship expectation accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipsExpectation: {\n relationships: GeoSpecSpatialRelationshipExpectation[]\n}\n\n// Mesh integrity expectation accepted by `expectGeo(...).toHaveMeshIntegrity(...)`\nGeoSpecMeshIntegrityExpectation: {\n finitePositions?: boolean\n degenerateTriangles?: {\n count?: number;\n maxCount?: number;\n areaTolerance?: number;\n }\n duplicateFaces?: {\n count?: number;\n maxCount?: number;\n }\n watertight?: boolean\n triangleCount?: GeoSpecNumericExpectation\n}\n\n// Diagnostic severities rejected by `expectGeo(...).toHaveNoDiagnostics(...)`\nGeoSpecNoDiagnosticsExpectation: {\n severities?: Array<GeometryDiagnostic['severity']>\n}\n\n// Surface-area expectation accepted by `expectGeo(...).toHaveSurfaceArea(...)`\nGeoSpecSurfaceAreaExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Volume expectation accepted by `expectGeo(...).toHaveVolume(...)`\nGeoSpecVolumeExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Mass expectation accepted by `expectGeo(...).toHaveMass(...)`\nGeoSpecMassExpectation: {\n value: number | GeoSpecNumericExpectation\n density?: number\n tolerance?: number\n}\n\n// Center-of-mass expectation accepted by `expectGeo(...).toHaveCenterOfMass(...)`\nGeoSpecCenterOfMassExpectation: {\n // Exact expected point in millimetres\n point: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Exact BRep validity expectation accepted by `expectGeo(...).toBeValidBrep(...)`\nGeoSpecValidBrepExpectation: {\n maxTolerance?: number\n freeBounds?: {\n count?: GeoSpecNumericExpectation;\n }\n minEdgeLength?: number\n sameParameter?: boolean\n closedShells?: boolean\n closedWires?: boolean\n}\n\n// Topology-count expectation accepted by `expectGeo(...).toHaveTopologyCounts(...)`\nGeoSpecTopologyCountsExpectation: {\n vertices?: GeoSpecNumericExpectation\n edges?: GeoSpecNumericExpectation\n wires?: GeoSpecNumericExpectation\n faces?: GeoSpecNumericExpectation\n shells?: GeoSpecNumericExpectation\n solids?: GeoSpecNumericExpectation\n compounds?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// STEP unit expectation accepted by `expectGeo(...).toHaveStepUnits(...)`\nGeoSpecStepUnitsExpectation: {\n unit: string\n}\n\n// Product-structure expectation accepted by `expectGeo(...).toHaveProductStructure(...)`\nGeoSpecProductStructureExpectation: {\n names?: string[]\n count?: GeoSpecNumericExpectation\n}\n\n// Planar-face expectation accepted by `expectGeo(...).toHavePlanarFace(...)`\nGeoSpecPlanarFaceExpectation: {\n normal: GeoSpecPointExpectation\n offset: number\n area?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Cylindrical-face expectation accepted by `expectGeo(...).toHaveCylindricalFace(...)`\nGeoSpecCylindricalFaceExpectation: {\n radius: number\n axis: 'x' | 'y' | 'z'\n tolerance?: number\n}\n\n// Circular-hole expectation accepted by `expectGeo(...).toHaveCircularHole(...)`\nGeoSpecCircularHoleExpectation: {\n diameter: number\n through?: boolean\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Circular-hole-pattern expectation accepted by `expectGeo(...).toHaveCircularHolePattern(...)`\nGeoSpecCircularHolePatternExpectation: {\n count: number\n holeDiameter: number\n boltCircleDiameter?: number\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Chamfer-feature expectation accepted by `expectGeo(...).toHaveChamferFeature(...)`\nGeoSpecChamferFeatureExpectation: {\n distance: number\n selection?: string\n tolerance?: number\n}\n\n// Fillet-feature expectation accepted by `expectGeo(...).toHaveFilletFeature(...)`\nGeoSpecFilletFeatureExpectation: {\n radius: number\n selection?: string\n tolerance?: number\n}\n\n// Minimum-wall-thickness expectation accepted by `expectGeo(...).toHaveMinimumWallThickness(...)`\nGeoSpecMinimumWallThicknessExpectation: {\n value: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Void-continuity expectation accepted by `expectGeo(...).toHaveVoidContinuity(...)`\nGeoSpecVoidContinuityExpectation: {\n // Ordered waypoints (>= 1) known to lie in the void being proven\n path: GeoSpecVoidWaypoint[]\n // Occurrence names whose solids bound the void\n material?: string[]\n // Minimum required bottleneck cross-section (mm²), sampled\n minCrossSection?: number\n // Points that must NOT be reachable from the path void (isolation claim)\n isolatedFrom?: Vec3[]\n // Region bounded for the proof (subject frame)\n bounds?: {\n min: Vec3;\n max: Vec3;\n }\n}\n\n// Axis-keyed numeric expectation used by high-level geometry matchers\nGeoSpecAxisExpectation: {\n x?: number\n y?: number\n z?: number\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// A pair-specific component-interference check accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferencePairExpectation: {\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n}\n\n// Intentional component interference allowance accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceAllowance: {\n kind?: 'intentionalInterference'\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n maxVolume?: number\n reason: string\n}\n\n// Assembly occurrence rule accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrenceExpectation: {\n name: GeoSpecComponentSelector\n count?: GeoSpecNumericExpectation\n bounds?: {\n within?: GeoSpecComponentSelector;\n min?: Vec3 | GeoSpecAxisExpectation;\n max?: Vec3 | GeoSpecAxisExpectation;\n center?: GeoSpecPointExpectation;\n tolerance?: number;\n }\n}\n\n// One spatial relationship accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipExpectation: {\n id?: string\n kind: 'contact' | 'clearance' | 'coaxial' | 'concentric' | 'coplanar' | 'parallel' | 'perpendicular' | 'angle' | 'containment' | 'insertion' | 'interference'\n subject: GeoSpecGeometrySelector\n target: GeoSpecGeometrySelector\n tolerance?: number\n angularToleranceDegrees?: number\n // Expected angle in degrees for `kind\n angleDegrees?: number\n // Declared insertion axis (subject-frame direction) for `kind\n axis?: Vec3\n min?: number\n max?: number\n minVolume?: number\n maxVolume?: number\n reason?: string\n}\n\n// Diagnostic emitted by GeoSpec loaders, analyzers, and matchers\nGeometryDiagnostic: {\n code: KernelIssueCode | (string & {})\n severity: 'error' | 'warning' | 'info'\n message: string\n suggestion?: string\n spatial?: {\n min?: Vec3;\n max?: Vec3;\n center?: Vec3;\n }\n details?: unknown\n}\n\n// Point expectation accepted by center and feature matchers\nGeoSpecPointExpectation: Vec3 | GeoSpecAxisExpectation\n\n// One void-continuity waypoint\nGeoSpecVoidWaypoint: Vec3 | {\n occurrence: string;\n}\n\n// Direct geometry-source model load options\nLoadModelSourceOptions: {\n // Source geometry format\n format?: GeoSpecModelFormat\n // Explicit parameters recorded in provenance\n parameters?: Record<string, unknown>\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // … 10 more members in the API reference: source, resources, rootDir, path, name, sourceUnit, stepStreaming, meshLinearTolerance, meshAngularToleranceDegrees, workUnitLimit\n}\n```\n\n## API reference\n\nTo read any other signature, find its name in `api-index.md`, which lists all 299 symbols by file with the lines that hold each, and read that range of the named file.\n\n- `api-functions.md` — Functions\n- `api-constants.md` — Constants\n- `api-types.md` — Types\n- `api-classes.md` — Classes\n\nRead ranges, not whole files. Never copy a reference into a source file.\n"
27
26
  }
28
27
  ]
29
28
  }
@@ -1,11 +1,11 @@
1
+ import { JSONValue } from "../support/types.mjs";
1
2
  import { GeoSpecSubject } from "../model/subject.mjs";
2
3
  import { GeoSpecAssertion, GeoSpecExpectOptions, GeoSpecMatcher } from "../runner/types.mjs";
3
4
  import { GeoSpecMatcherName } from "../engine/matchers.mjs";
4
5
  import { GeoSpecCanonicalClaimReport, GeoSpecClaimPolarity, GeoSpecNativeEngine, GeoSpecNativeEvidenceProfile, GeoSpecNativeSubject, GeoSpecQueryCapability } from "../engine/client.mjs";
5
- import { JSONValue } from "@taucad/runtime/types";
6
6
  //#region src/assertion-client/client.d.ts
7
7
  /** One authored call shared by the collector and native assertion client. @public */
8
- type GeoSpecAuthoringInvocation = {
8
+ export type GeoSpecAuthoringInvocation = {
9
9
  readonly arguments: readonly unknown[];
10
10
  readonly expected: unknown;
11
11
  readonly kind: GeoSpecAssertion['kind'];
@@ -14,13 +14,13 @@ type GeoSpecAuthoringInvocation = {
14
14
  readonly subject: unknown;
15
15
  };
16
16
  /** Matcher methods derived mechanically from the existing GeoSpec registry. @public */
17
- type GeoSpecMatcherMethods<Result> = { [Name in GeoSpecMatcherName]: (...arguments_: Parameters<GeoSpecMatcher[Name]>) => Result; };
17
+ export type GeoSpecMatcherMethods<Result> = { [Name in GeoSpecMatcherName]: (...arguments_: Parameters<GeoSpecMatcher[Name]>) => Result; };
18
18
  /** Standalone native matcher chain, including core-owned negation. @public */
19
- type GeoSpecAssertionMatchers = GeoSpecMatcherMethods<GeoSpecCanonicalClaimReport> & {
19
+ export type GeoSpecAssertionMatchers = GeoSpecMatcherMethods<GeoSpecCanonicalClaimReport> & {
20
20
  readonly not: GeoSpecMatcherMethods<GeoSpecCanonicalClaimReport>;
21
21
  };
22
22
  /** Runner-independent native assertion client. @public */
23
- type GeoSpecAssertionClient = {
23
+ export type GeoSpecAssertionClient = {
24
24
  /**
25
25
  * Start a chain; `options.workUnitLimit` overrides the client budget for its claims.
26
26
  * Without one, a loaded subject's `loadModel` budget applies, then the client's.
@@ -30,7 +30,7 @@ type GeoSpecAssertionClient = {
30
30
  query(options: GeoSpecQueryOptions): Promise<GeoSpecCanonicalClaimReport>;
31
31
  };
32
32
  /** Complete AP242 minimum over two subject-bound occurrence paths. @public */
33
- type MinimumDistanceQuery = {
33
+ export type MinimumDistanceQuery = {
34
34
  readonly capability: 'minimumDistance';
35
35
  readonly subject: GeoSpecNativeSubject;
36
36
  readonly payload: {
@@ -42,7 +42,7 @@ type MinimumDistanceQuery = {
42
42
  };
43
43
  };
44
44
  /** Complete native minimum and ordered finite witnesses in canonical millimetres/Z-up. @public */
45
- type MinimumDistanceFact = {
45
+ export type MinimumDistanceFact = {
46
46
  readonly source: 'ap242';
47
47
  readonly assurance: 'exact-brep';
48
48
  readonly unit: 'mm';
@@ -54,7 +54,7 @@ type MinimumDistanceFact = {
54
54
  readonly points: readonly [readonly [number, number, number], readonly [number, number, number]];
55
55
  };
56
56
  /** Geometry refusal and infrastructure interruption never masquerade as facts. @public */
57
- type MinimumDistanceResult = {
57
+ export type MinimumDistanceResult = {
58
58
  readonly status: 'complete';
59
59
  readonly fact: MinimumDistanceFact;
60
60
  } | {
@@ -80,14 +80,14 @@ type MinimumDistanceResult = {
80
80
  * report whose diagnostic names the reason, such as `GEOSPEC_UNSUPPORTED_EVIDENCE`.
81
81
  * @public
82
82
  */
83
- type GeoSpecQueryOptions = {
83
+ export type GeoSpecQueryOptions = {
84
84
  readonly capability: GeoSpecQueryCapability;
85
85
  readonly claimId?: string;
86
86
  readonly payload?: unknown;
87
87
  readonly subject: GeoSpecNativeSubject;
88
88
  };
89
89
  /** Flat construction options for a runner-independent native assertion client. @public */
90
- type GeoSpecAssertionClientOptions = {
90
+ export type GeoSpecAssertionClientOptions = {
91
91
  readonly claimId?: (matcher: GeoSpecMatcherName, sequence: number) => string;
92
92
  readonly engine: GeoSpecNativeEngine;
93
93
  /** Success-evidence profile of every claim and query; omitted means `complete`. */
@@ -102,7 +102,7 @@ type GeoSpecAssertionClientOptions = {
102
102
  readonly maximumWorkUnitLimit?: number;
103
103
  };
104
104
  /** A non-passed core assertion report surfaced by standalone `expectGeo`. @public */
105
- declare class GeoSpecAssertionError extends Error {
105
+ export declare class GeoSpecAssertionError extends Error {
106
106
  readonly canonicalClaim: Uint8Array<ArrayBuffer>;
107
107
  readonly canonicalPlan: Uint8Array<ArrayBuffer>;
108
108
  readonly canonicalResult: Uint8Array<ArrayBuffer>;
@@ -119,7 +119,7 @@ declare class GeoSpecAssertionError extends Error {
119
119
  * @returns All 26 matcher methods in registry order.
120
120
  * @public
121
121
  */
122
- declare const createGeoSpecMatcherMethods: <Result>(options: {
122
+ export declare const createGeoSpecMatcherMethods: <Result>(options: {
123
123
  readonly invoke: (invocation: GeoSpecAuthoringInvocation) => Result;
124
124
  readonly polarity: GeoSpecClaimPolarity;
125
125
  readonly subject: unknown;
@@ -131,6 +131,5 @@ declare const createGeoSpecMatcherMethods: <Result>(options: {
131
131
  * @returns An `expectGeo`-style assertion client independent of any test runner.
132
132
  * @public
133
133
  */
134
- declare const createGeoSpecAssertionClient: (options: GeoSpecAssertionClientOptions) => GeoSpecAssertionClient;
135
- //#endregion
136
- export { GeoSpecAssertionClient, GeoSpecAssertionClientOptions, GeoSpecAssertionError, GeoSpecAssertionMatchers, GeoSpecAuthoringInvocation, GeoSpecMatcherMethods, GeoSpecQueryOptions, MinimumDistanceFact, MinimumDistanceQuery, MinimumDistanceResult, createGeoSpecAssertionClient, createGeoSpecMatcherMethods };
134
+ export declare const createGeoSpecAssertionClient: (options: GeoSpecAssertionClientOptions) => GeoSpecAssertionClient;
135
+ //#endregion
@@ -1,2 +1,2 @@
1
1
  import{geoSpecMatcherDescriptors as e,normalizeGeoSpecExpected as t}from"../engine/matchers.mjs";import{assertGeoSpecAuthoredWorkUnitLimit as n,evaluateGeoSpecNativeClaim as r,evaluateGeoSpecNativeQuery as i,resolveGeoSpecMaximumWorkUnitLimit as a,resolveGeoSpecNativeWorkUnitLimit as o,supportsGeoSpecNativeMinimumDistance as s}from"../engine/client.mjs";import{resolveGeoSpecSubject as c}from"../model/subject.mjs";const l=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))return;let{fact:n}=e;if(typeof n!=`object`||!n||Array.isArray(n))return;let{distance:r,points:i,occurrences:a,subjectHash:o}=n,s=e=>Array.isArray(e)&&e.length===3&&e.every(e=>typeof e==`number`&&Number.isFinite(e));if(!(e.profile!==`geospec-minimum-distance-v1`||n.source!==`ap242`||n.assurance!==`exact-brep`||n.unit!==`mm`||n.coordinateSystem!==`z-up`||n.algorithmProfile!==`geospec-minimum-distance-v1`||typeof o!=`string`||!/^[0-9a-f]{64}$/u.test(o)||o!==(t.subject.subjectHash??t.subject.contentHash)||typeof r!=`number`||!Number.isFinite(r)||r<0||!Array.isArray(a)||a.length!==2||a[0]!==t.payload.pair[0].occurrencePath||a[1]!==t.payload.pair[1].occurrencePath||!Array.isArray(i)||i.length!==2||!s(i[0])||!s(i[1])))return{source:`ap242`,assurance:`exact-brep`,unit:`mm`,coordinateSystem:`z-up`,subjectHash:o,algorithmProfile:`geospec-minimum-distance-v1`,occurrences:[a[0],a[1]],distance:r,points:[i[0],i[1]]}},u=(e,t)=>{if(e.status===`passed`){let n=l(e.evidence,t);return n===void 0?{status:`interrupted`,code:`engine-error`,message:`Native minimum returned malformed evidence.`}:{status:`complete`,fact:n}}let n=e.diagnostics.find(e=>typeof e==`object`&&!!e&&!Array.isArray(e)),r=typeof n==`object`&&n&&!Array.isArray(n)?n.code:void 0,i=typeof n==`object`&&n&&!Array.isArray(n)&&typeof n.message==`string`?n.message:`Native minimum is unavailable.`;return r===`GEOSPEC_INVALID_SELECTION`?{status:`refused`,code:`invalid-selection`,message:i}:r===`MATCHER_TIMEOUT`?{status:`refused`,code:`work-limit`,message:i}:r===`GEOSPEC_ANALYSIS_LIMIT`?{status:`refused`,code:`analysis-limit`,message:i}:r===`GEOSPEC_RESOURCE_LIMIT`?{status:`refused`,code:`resource-limit`,message:i}:r===`GEOSPEC_UNSUPPORTED_EVIDENCE`||r===`GEOSPEC_EVIDENCE_UNSUPPORTED`?{status:`refused`,code:`unsupported-evidence`,message:i}:{status:`interrupted`,code:`engine-error`,message:i}};var d=class extends Error{canonicalClaim;canonicalPlan;canonicalResult;claim;diagnostics;report;result;constructor(e){let t=e.diagnostics.flatMap(e=>{if(typeof e!=`object`||!e||Array.isArray(e))return[];let{message:t}=e;return typeof t==`string`?[t]:[]});super(t.join(`
2
- `)||`GeoSpec assertion '${e.claimId}' ended with status '${e.status}'.`),this.name=`GeoSpecAssertionError`,this.report=e,this.diagnostics=e.diagnostics,this.claim=e.claim,this.result=e.result,this.canonicalClaim=e.canonicalClaim,this.canonicalPlan=e.canonicalPlan,this.canonicalResult=e.canonicalResult}};const f=[`x`,`y`,`z`],p=e=>{if(typeof e!=`object`||!e||!(`point`in e))return;let{point:t}=e,n=Array.isArray(t)?t.map((e,t)=>[f[t]??String(t),e]):typeof t==`object`&&t?Object.entries(t):[];if(n.length===0&&!(typeof t==`object`&&t))throw TypeError(`GeoSpec matcher toHaveCenterOfMass needs point as [x, y, z] or { x, y, z } in millimetres, with an optional tolerance.`);for(let[e,t]of n)if(!(typeof t==`number`&&Number.isFinite(t)))throw TypeError(typeof t==`object`&&t&&!Array.isArray(t)?`GeoSpec matcher toHaveCenterOfMass point.${e} must be a finite number in millimetres: per-axis ranges such as { greaterThan } are not supported for center of mass. Assert the expected point with a tolerance instead.`:`GeoSpec matcher toHaveCenterOfMass point.${e} must be a finite number in millimetres.`)},m=n=>{let r={};for(let i of Object.keys(e)){let a=e[i];r[i]=(...e)=>{if(a.expected===`true`&&e.length>0)throw TypeError(`GeoSpec matcher ${i} does not accept arguments.`);return i===`toHaveCenterOfMass`&&p(e[0]),n.invoke({arguments:e,expected:t(a.expected,e),kind:a.kind,matcher:i,polarity:n.polarity,subject:n.subject})}}return r},h=e=>{let t=0,l=e.subjectSlot??`subject`,f,p=()=>(f??=a(e.maximumWorkUnitLimit),f),h,g=()=>(h??=o(e.engine,e.workUnitLimit),h),_=(n,i,a)=>m({subject:n,polarity:i,invoke:o=>{a.validate(),t+=1;let s={claimId:e.claimId?.(o.matcher,t)??`geospec-claim-${t}`,engine:e.engine,...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile},polarity:i,subject:n,subjectSlot:l,workUnitLimit:a.workUnitLimit??g(),maximumWorkUnitLimit:p()},c=r({...s,arguments:o.arguments,capability:o.matcher,kind:o.kind});if(c.status!==`passed`)throw new d(c);return c}});async function v(n){let r=`claimId`in n?n.claimId??`geospec-claim-${++t}`:`geospec-claim-${++t}`;if(n.capability===`minimumDistance`){let t=n.payload?.pair,r=e=>{if(!(typeof e!=`object`||!e||!(`occurrencePath`in e)))return typeof e.occurrencePath==`string`&&e.occurrencePath.length>0?e.occurrencePath:void 0};if(!Array.isArray(t)||t.length!==2||r(t[0])===void 0||r(t[1])===void 0||r(t[0])===r(t[1]))return{status:`refused`,code:`invalid-selection`,message:`Select two distinct resolved AP242 occurrence paths.`};try{if(!s(e.engine))return{status:`refused`,code:`unsupported-evidence`,message:`This native engine does not advertise the complete minimum-distance profile.`}}catch{return{status:`refused`,code:`unsupported-evidence`,message:`This native engine cannot negotiate the complete minimum-distance profile.`}}}try{let t=i({capability:n.capability,claimId:r,engine:e.engine,...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile},payload:n.payload,subject:n.subject,subjectSlot:l,workUnitLimit:g()});return n.capability===`minimumDistance`?u(t,n):t}catch(e){if(n.capability!==`minimumDistance`)throw e;return{status:`interrupted`,code:`engine-error`,message:e instanceof Error?e.message:`Native minimum query failed.`}}}return{query:v,expectGeo(t,r){n(r?.workUnitLimit,`expectGeo workUnitLimit`,p());let i=`subjectHash`in t||`contentHash`in t,a=i?void 0:c(t,e.engine),o=i?t:a.identity,s={validate:()=>{i||c(t,e.engine)},workUnitLimit:r?.workUnitLimit??a?.load?.workUnitLimit},l=_(o,`positive`,s);return Object.assign(l,{not:_(o,`negative`,s)})}}};export{d as GeoSpecAssertionError,h as createGeoSpecAssertionClient,m as createGeoSpecMatcherMethods};
2
+ `)||`GeoSpec assertion '${e.claimId}' ended with status '${e.status}'.`),this.name=`GeoSpecAssertionError`,this.report=e,this.diagnostics=e.diagnostics,this.claim=e.claim,this.result=e.result,this.canonicalClaim=e.canonicalClaim,this.canonicalPlan=e.canonicalPlan,this.canonicalResult=e.canonicalResult}};const f=[`x`,`y`,`z`],p=e=>{if(typeof e!=`object`||!e||!(`point`in e))return;let{point:t}=e,n=Array.isArray(t)?t.map((e,t)=>[f[t]??String(t),e]):typeof t==`object`&&t?Object.entries(t):[];if(n.length===0&&!(typeof t==`object`&&t))throw TypeError(`GeoSpec matcher toHaveCenterOfMass needs point as [x, y, z] or { x, y, z } in millimetres, with an optional tolerance.`);for(let[e,t]of n)if(!(typeof t==`number`&&Number.isFinite(t)))throw TypeError(typeof t==`object`&&t&&!Array.isArray(t)?`GeoSpec matcher toHaveCenterOfMass point.${e} must be a finite number in millimetres: per-axis ranges such as { greaterThan } are not supported for center of mass. Assert the expected point with a tolerance instead.`:`GeoSpec matcher toHaveCenterOfMass point.${e} must be a finite number in millimetres.`)},m=n=>{let r={};for(let i of Object.keys(e)){let a=e[i];r[i]=(...e)=>{if(a.expected===`true`&&e.length>0)throw TypeError(`GeoSpec matcher ${i} does not accept arguments.`);return i===`toHaveCenterOfMass`&&p(e[0]),n.invoke({arguments:e,expected:t(a.expected,e),kind:a.kind,matcher:i,polarity:n.polarity,subject:n.subject})}}return r},h=e=>{let t=0,l=e.subjectSlot??`subject`,f,p=()=>(f??=a(e.maximumWorkUnitLimit),f),h,g=()=>(h??=o(e.engine,e.workUnitLimit),h),_=(n,i,a)=>m({subject:n,polarity:i,invoke:o=>{a.validate(),t+=1;let s={claimId:e.claimId?.(o.matcher,t)??`geospec-claim-${t}`,engine:e.engine,...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile},polarity:i,subject:n,subjectSlot:l,workUnitLimit:a.workUnitLimit??g(),maximumWorkUnitLimit:p()},c=r({...s,arguments:o.arguments,capability:o.matcher,kind:o.kind});if(c.status!==`passed`)throw new d(c);return c}});async function v(n){let r=`claimId`in n?n.claimId??`geospec-claim-${++t}`:`geospec-claim-${++t}`;if(n.capability===`minimumDistance`){let t=n.payload?.pair,r=e=>{if(typeof e==`object`&&e&&`occurrencePath`in e)return typeof e.occurrencePath==`string`&&e.occurrencePath.length>0?e.occurrencePath:void 0};if(!Array.isArray(t)||t.length!==2||r(t[0])===void 0||r(t[1])===void 0||r(t[0])===r(t[1]))return{status:`refused`,code:`invalid-selection`,message:`Select two distinct resolved AP242 occurrence paths.`};try{if(!s(e.engine))return{status:`refused`,code:`unsupported-evidence`,message:`This native engine does not advertise the complete minimum-distance profile.`}}catch{return{status:`refused`,code:`unsupported-evidence`,message:`This native engine cannot negotiate the complete minimum-distance profile.`}}}try{let t=i({capability:n.capability,claimId:r,engine:e.engine,...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile},payload:n.payload,subject:n.subject,subjectSlot:l,workUnitLimit:g()});return n.capability===`minimumDistance`?u(t,n):t}catch(e){if(n.capability!==`minimumDistance`)throw e;return{status:`interrupted`,code:`engine-error`,message:e instanceof Error?e.message:`Native minimum query failed.`}}}return{query:v,expectGeo(t,r){n(r?.workUnitLimit,`expectGeo workUnitLimit`,p());let i=`subjectHash`in t||`contentHash`in t,a=i?void 0:c(t,e.engine),o=i?t:a.identity,s={validate:()=>{i||c(t,e.engine)},workUnitLimit:r?.workUnitLimit??a?.load?.workUnitLimit},l=_(o,`positive`,s);return Object.assign(l,{not:_(o,`negative`,s)})}}};export{d as GeoSpecAssertionError,h as createGeoSpecAssertionClient,m as createGeoSpecMatcherMethods};
@@ -0,0 +1,2 @@
1
+ import{basename as e,dirname as t,extname as n,isAbsolute as r,join as i}from"node:path";const a=`.geospec-check.geospec.ts`,o={".glb":`glb`,".step":`step`,".stp":`step`},s=e=>{let t=JSON.stringify(e.file),n=(n,...r)=>[` it(${JSON.stringify(n)}, async () => {`,` const model = await loadModel({ source: ${t}, format: '${e.format}' });`,...r.map(e=>` expectGeo(model).${e};`),` });`];return[`import { describe, expectGeo, it } from 'geospec';`,`import { loadModel } from 'geospec/model';`,``,`describe(${t}, () => {`,...n(`loads without error diagnostics`,`toHaveNoDiagnostics({ severities: ['error'] })`),...e.format===`glb`?n(`has mesh integrity`,`toHaveMeshIntegrity({ finitePositions: true, degenerateTriangles: { count: 0 }, duplicateFaces: { count: 0 }, triangleCount: { greaterThan: 0 } })`):n(`is a valid BRep with closed shells`,`toBeValidBrep({ closedShells: true })`),...n(`is watertight`,`toBeWatertight()`),...n(`has a bounding box with positive size`,`toHaveBoundingBox({ size: { x: { greaterThan: 0 }, y: { greaterThan: 0 }, z: { greaterThan: 0 } } })`),...e.components===void 0?[]:n(`has ${String(e.components)} connected component${e.components===1?``:`s`}`,`toHaveConnectedComponents({ count: ${String(e.components)} })`),`});`,``].join(`
2
+ `)},c=(c,l)=>{let u=o[n(c.file).toLowerCase()];if(u===void 0)return{error:`geospec check reads .step, .stp or .glb files, not '${c.file}'.`};let d=r(c.file)?c.file:i(l,c.file),f=e(d);return{projectPath:t(d),file:f,sources:{[a]:s({file:f,format:u,...c.components===void 0?{}:{components:c.components}})}}};export{a as geoSpecCheckSpecPath,c as planGeoSpecCheck,s as validitySpecSource};
@@ -1,4 +1,3 @@
1
- import "../runner/types.mjs";
2
1
  import { GeoSpecConfigData } from "../config/types.mjs";
3
2
  import { discoverGeoSpecFiles } from "../runner/discovery.mjs";
4
3
  import { GeoSpecRunner } from "../runner/worker/runner-types.mjs";
@@ -10,7 +9,7 @@ import { GeoSpecRunner } from "../runner/worker/runner-types.mjs";
10
9
  *
11
10
  * @public
12
11
  */
13
- type GeoSpecCliHost = {
12
+ export type GeoSpecCliHost = {
14
13
  /** Absolute project root for a relative `projectPath`. */
15
14
  cwd(): string;
16
15
  write(line: string): void;
@@ -33,6 +32,8 @@ type GeoSpecCliHost = {
33
32
  configPath: string | undefined;
34
33
  workers: number | undefined;
35
34
  shardTimeout: number | undefined;
35
+ /** In-memory spec sources keyed by project-rooted path, served in place of the disk (`geospec check`). */
36
+ sources?: Readonly<Record<string, string>>;
36
37
  }): GeoSpecRunner;
37
38
  };
38
39
  /**
@@ -43,6 +44,5 @@ type GeoSpecCliHost = {
43
44
  * @returns The process exit code.
44
45
  * @public
45
46
  */
46
- declare const runGeoSpecCli: (argv: readonly string[], host: GeoSpecCliHost) => Promise<number>;
47
- //#endregion
48
- export { GeoSpecCliHost, runGeoSpecCli };
47
+ export declare const runGeoSpecCli: (argv: readonly string[], host: GeoSpecCliHost) => Promise<number>;
48
+ //#endregion
package/dist/cli/cli.mjs CHANGED
@@ -1,7 +1,8 @@
1
- import{protocolWireValue as e}from"./wire-value.mjs";import{discoverGeoSpecFiles as t}from"../runner/discovery.mjs";const n=`geospec — CAD geometry specs
1
+ import{geoSpecCheckSpecPath as e,planGeoSpecCheck as t}from"./check.mjs";import{protocolWireValue as n}from"./wire-value.mjs";import{discoverGeoSpecFiles as r}from"../runner/discovery.mjs";const i=`geospec — CAD geometry specs
2
2
 
3
3
  Usage:
4
4
  geospec run [projectPath] [options]
5
+ geospec check <file.step|file.stp|file.glb> [--components <n>] [--json]
5
6
 
6
7
  Options:
7
8
  --file <path> GeoSpec file or directory root (repeatable)
@@ -16,6 +17,14 @@ Options:
16
17
  --json Print one JSON result document and nothing else
17
18
  -h, --help Show this help
18
19
 
20
+ Check:
21
+ geospec check runs one bundled validity spec on a STEP or GLB file, with no
22
+ *.geospec.ts to write: no error diagnostics, closed edge incidence and a
23
+ bounding box with positive size, plus mesh integrity for GLB or a valid BRep
24
+ with closed shells for STEP. --components <n> also requires exactly n
25
+ connected components. The file's folder is the project root; the exit code
26
+ is the verdict.
27
+
19
28
  Timeouts:
20
29
  Every host reports wall-clock timeouts with one code family. None of them is
21
30
  a geometry verdict, and nothing is timed out unless asked.
@@ -26,5 +35,13 @@ Timeouts:
26
35
  GEOSPEC_RUN_TIMEOUT a browser run outlived its host run budget; completed
27
36
  requirements are still reported
28
37
  The engine's work-unit budget (MATCHER_TIMEOUT) is deterministic, not a clock.
29
- `,r=(e,t)=>{let n=Number(e);return e!==void 0&&Number.isFinite(n)&&n>=0?n:{error:`${t} needs a number.`}},i=e=>{if(e.length===0||e[0]===`-h`||e[0]===`--help`)return{kind:`help`,message:n};if(e[0]!==`run`)return{kind:`error`,message:`Unknown command '${e[0]}'.`};let t={projectPath:`.`,files:[],include:[],exclude:[],bail:!1,json:!1},i=!1;for(let a=1;a<e.length;a++){let o=e[a],s=e[a+1];switch(o){case`-h`:case`--help`:return{kind:`help`,message:n};case`--file`:if(s===void 0)return{kind:`error`,message:`--file needs a path.`};t.files.push(s),a+=1;continue;case`--config`:if(s===void 0)return{kind:`error`,message:`--config needs a path.`};t.config=s,a+=1;continue;case`--include`:case`--exclude`:if(s===void 0)return{kind:`error`,message:`${o} needs a glob.`};(o===`--include`?t.include:t.exclude).push(s),a+=1;continue;case`-t`:case`--test-name-pattern`:if(s===void 0)return{kind:`error`,message:`${o} needs a pattern.`};t.testNamePattern=s,a+=1;continue;case`--test-timeout`:case`--shard-timeout`:{let e=r(s,o);if(typeof e!=`number`)return{kind:`error`,message:e.error};o===`--test-timeout`?t.testTimeout=e:t.shardTimeout=e,a+=1;continue}case`--workers`:{if(s===void 0||s.startsWith(`-`)){t.workers=0;continue}let e=r(s,o);if(typeof e!=`number`)return{kind:`error`,message:e.error};t.workers=e,a+=1;continue}case`--bail`:t.bail=!0;continue;case`--json`:t.json=!0;continue;default:if(o.startsWith(`-`))return{kind:`error`,message:`Unknown option '${o}'.`};if(i)return{kind:`error`,message:`Unexpected argument '${o}'.`};t.projectPath=o,i=!0}}return{kind:`run`,options:t}},a=e=>[...e.suite,e.name].join(` > `),o={passed:`pass`,failed:`fail`,inconclusive:`inconclusive`,unsupported:`unsupported`,"not-run":`not-run`,skipped:`skip`},s=(e,t)=>{if(!t.success)return[`FAIL ${e}`,...t.issues.map(e=>` ${e.code}: ${e.message}`)];let n=[`${t.passed?`PASS`:`FAIL`} ${e}`];for(let e of t.tests){if(e.status===`passed`)continue;n.push(` ${o[e.status]} ${a(e)}`);let t=new Set;for(let r of[...e.diagnostics,...e.assertions.flatMap(e=>e.diagnostics??[])]){let e=` ${r.code}: ${r.message}`;t.has(e)||(t.add(e),n.push(e))}}return n},c=e=>{let t=[];for(let n of e.files)t.push(...s(n.file,n.result));for(let n of e.issues??[])t.push(`${n.code}: ${n.message}`);return t.push(l(e)),t},l=e=>{let{accounting:t}=e,n={passed:t?.passed??e.passed,failed:t?.failed??e.failed,inconclusive:t?.inconclusive??0,unsupported:t?.unsupported??0,notRun:t?.notRun??0,skipped:t?.skipped??0,total:t?.selected??e.selectedTests},r=n.failed>0?`failed`:n.inconclusive>0||t?.cancelled===!0?`inconclusive`:n.unsupported>0?`unsupported`:n.total===n.skipped?`not-run`:e.success?`passed`:`inconclusive`,i=[`${String(n.passed)} passed`,`${String(n.failed)} failed`];for(let[e,t]of[[n.inconclusive,`inconclusive`],[n.unsupported,`unsupported`],[n.notRun,`not run`],[n.skipped,`skipped`]])e>0&&i.push(`${String(e)} ${t}`);return`GeoSpec ${r}: ${i.join(`, `)} of ${String(n.total)}.`},u=t=>({success:t.success,passed:t.passed,failed:t.failed,selectedTests:t.selectedTests,...t.accounting===void 0?{}:{accounting:t.accounting},...t.lineageStatus===void 0?{}:{lineageStatus:t.lineageStatus},...t.durationMs===void 0?{}:{durationMs:t.durationMs},...t.issues===void 0?{}:{issues:t.issues},files:t.files.map(t=>({file:t.file,success:t.result.success&&t.result.passed,...t.result.accounting===void 0?{}:{accounting:t.result.accounting},...t.result.lineage===void 0?{}:{lineage:e(t.result.lineage)},...t.durationMs===void 0?{}:{durationMs:t.durationMs},...t.result.tests===void 0?{}:{tests:t.result.tests.map(e=>({suite:e.suite,name:e.name,...e.ordinal===void 0?{}:{ordinal:e.ordinal},status:e.status,...e.durationMs===void 0?{}:{durationMs:e.durationMs},diagnostics:[...e.diagnostics,...e.assertions.flatMap(e=>e.diagnostics??[])],...e.assertions.some(e=>e.report!==void 0)?{reports:e.assertions.flatMap(e=>{let{report:t}=e;return t===void 0?[]:[{claimId:t.claimId,status:t.status,polarity:t.polarity,claim:structuredClone(t.claim),result:structuredClone(t.result),diagnostics:structuredClone(t.diagnostics),...t.evidence===void 0?{}:{evidence:structuredClone(t.evidence)},...e.loadId===void 0?{}:{loadId:e.loadId},canonical:{claim:[...t.canonicalClaim],plan:[...t.canonicalPlan],result:[...t.canonicalResult]}}]})}:{}}))},...t.result.success?{}:{issues:t.result.issues}}))}),d=e=>{try{return JSON.stringify(e)}catch{return}};function*f(e,t=67108864){let n=d(e);if(n!==void 0&&n.length<=t){yield n;return}if(typeof e!=`object`||!e){yield n??`null`;return}if(Array.isArray(e)){yield`[`;for(let[n,r]of e.entries())n>0&&(yield`,`),yield*f(r,t);yield`]`;return}let r=Object.entries(e).filter(([,e])=>e!==void 0&&typeof e!=`function`&&typeof e!=`symbol`);yield`{`;for(let[e,[n,i]]of r.entries())yield`${e>0?`,`:``}${JSON.stringify(n)}:`,yield*f(i,t);yield`}`}const p=async(e,r)=>{let a=i(e);if(a.kind===`help`)return r.write(a.message),0;if(a.kind===`error`)return r.write(`${a.message}\n\n${n}`),1;let{options:o}=a,s=o.projectPath.startsWith(`/`)?o.projectPath:`${r.cwd()}/${o.projectPath}`.replace(/\/\.$/u,``),l;try{l=await r.loadConfig({projectPath:s,configPath:o.config})}catch(e){let t=`GeoSpec config: ${e instanceof Error?e.message:String(e)}`;return r.write(o.json?JSON.stringify({success:!1,error:t}):t),1}let d=o.include.length>0?o.include:l.options.include,p=o.exclude.length>0?o.exclude:l.options.exclude,m=o.testNamePattern??l.options.testNamePattern,h=o.testTimeout??l.options.testTimeout,g=o.bail||l.options.bail===!0,_=await t({filesystem:r.discoveryFileSystem(s),projectPath:s,...o.files.length>0?{files:o.files}:{},...d===void 0?{}:{include:d},...p===void 0?{}:{exclude:p}});if(_.files.length===0){let e=`No GeoSpec files matched (unmatched: ${_.unmatchedRoots.join(`, `)}).`;return r.write(o.json?JSON.stringify({success:!1,error:e,..._}):e),1}let v=r.createRunner({projectPath:s,configPath:l.configPath,workers:o.workers,shardTimeout:o.shardTimeout}),y;try{y=await v.run({files:_.files,...m===void 0?{}:{testNamePattern:m},...h===void 0?{}:{testTimeout:h},...g?{bail:!0}:{}})}finally{await v.close()}if(o.json)for(let e of f(u(y)))r.write(e);else r.write(c(y).join(`
30
- `));return+!y.success};export{s as formatFileReport,l as formatRunHeadline,c as formatRunReport,n as geoSpecCliUsage,f as jsonChunks,i as parseGeoSpecCliArguments,p as runGeoSpecCli,u as runReportJson};
38
+
39
+ Imports:
40
+ A spec may import project files and the geospec builtins (geospec,
41
+ geospec/model, geospec/config). An npm package or URL import is refused with
42
+ BUNDLER_FAILED naming the specifier; nothing is fetched. A host that resolves
43
+ packages injects its own module VM (createModuleVm in geospec/runner).
44
+ `,a=(e,t)=>{let n=Number(e);return e!==void 0&&Number.isFinite(n)&&n>=0?n:{error:`${t} needs a number.`}},o=e=>{let t,n,r=!1;for(let a=0;a<e.length;a++){let o=e[a];if(o===`-h`||o===`--help`)return{kind:`help`,message:i};if(o===`--json`)r=!0;else if(o===`--components`){let t=Number(e[a+1]);if(!Number.isInteger(t)||t<1)return{kind:`error`,message:`--components needs a whole number of at least 1.`};n=t,a+=1}else{if(o.startsWith(`-`))return{kind:`error`,message:`Unknown option '${o}'.`};if(t!==void 0)return{kind:`error`,message:`Unexpected argument '${o}'.`};t=o}}return t===void 0?{kind:`error`,message:`geospec check needs a STEP or GLB file.`}:{kind:`check`,options:{file:t,json:r,...n===void 0?{}:{components:n}}}},s=e=>{if(e.length===0||e[0]===`-h`||e[0]===`--help`)return{kind:`help`,message:i};if(e[0]===`check`)return o(e.slice(1));if(e[0]!==`run`)return{kind:`error`,message:`Unknown command '${e[0]}'.`};let t={projectPath:`.`,files:[],include:[],exclude:[],bail:!1,json:!1},n=!1;for(let r=1;r<e.length;r++){let o=e[r],s=e[r+1];switch(o){case`-h`:case`--help`:return{kind:`help`,message:i};case`--file`:if(s===void 0)return{kind:`error`,message:`--file needs a path.`};t.files.push(s),r+=1;continue;case`--config`:if(s===void 0)return{kind:`error`,message:`--config needs a path.`};t.config=s,r+=1;continue;case`--include`:case`--exclude`:if(s===void 0)return{kind:`error`,message:`${o} needs a glob.`};(o===`--include`?t.include:t.exclude).push(s),r+=1;continue;case`-t`:case`--test-name-pattern`:if(s===void 0)return{kind:`error`,message:`${o} needs a pattern.`};t.testNamePattern=s,r+=1;continue;case`--test-timeout`:case`--shard-timeout`:{let e=a(s,o);if(typeof e!=`number`)return{kind:`error`,message:e.error};o===`--test-timeout`?t.testTimeout=e:t.shardTimeout=e,r+=1;continue}case`--workers`:{if(s===void 0||s.startsWith(`-`)){t.workers=0;continue}let e=a(s,o);if(typeof e!=`number`)return{kind:`error`,message:e.error};t.workers=e,r+=1;continue}case`--bail`:t.bail=!0;continue;case`--json`:t.json=!0;continue;default:if(o.startsWith(`-`))return{kind:`error`,message:`Unknown option '${o}'.`};if(n)return{kind:`error`,message:`Unexpected argument '${o}'.`};t.projectPath=o,n=!0}}return{kind:`run`,options:t}},c=e=>[...e.suite,e.name].join(` > `),l={passed:`pass`,failed:`fail`,inconclusive:`inconclusive`,unsupported:`unsupported`,"not-run":`not-run`,skipped:`skip`},u=(e,t)=>{if(!t.success)return[`FAIL ${e}`,...t.issues.map(e=>` ${e.code}: ${e.message}`)];let n=[`${t.passed?`PASS`:`FAIL`} ${e}`];for(let e of t.tests){if(e.status===`passed`)continue;n.push(` ${l[e.status]} ${c(e)}`);let t=/* @__PURE__ */ new Set;for(let r of[...e.diagnostics,...e.assertions.flatMap(e=>e.diagnostics??[])]){let e=` ${r.code}: ${r.message}`;t.has(e)||(t.add(e),n.push(e))}}return n},d=e=>{let t=[];for(let n of e.files)t.push(...u(n.file,n.result));for(let n of e.issues??[])t.push(`${n.code}: ${n.message}`);return t.push(f(e)),t},f=e=>{let{accounting:t}=e,n={passed:t?.passed??e.passed,failed:t?.failed??e.failed,inconclusive:t?.inconclusive??0,unsupported:t?.unsupported??0,notRun:t?.notRun??0,skipped:t?.skipped??0,total:t?.selected??e.selectedTests},r=n.failed>0?`failed`:n.inconclusive>0||t?.cancelled===!0?`inconclusive`:n.unsupported>0?`unsupported`:n.total===n.skipped?`not-run`:e.success?`passed`:`inconclusive`,i=[`${String(n.passed)} passed`,`${String(n.failed)} failed`];for(let[e,t]of[[n.inconclusive,`inconclusive`],[n.unsupported,`unsupported`],[n.notRun,`not run`],[n.skipped,`skipped`]])e>0&&i.push(`${String(e)} ${t}`);return`GeoSpec ${r}: ${i.join(`, `)} of ${String(n.total)}.`},p=e=>({success:e.success,passed:e.passed,failed:e.failed,selectedTests:e.selectedTests,...e.accounting===void 0?{}:{accounting:e.accounting},...e.lineageStatus===void 0?{}:{lineageStatus:e.lineageStatus},...e.durationMs===void 0?{}:{durationMs:e.durationMs},...e.issues===void 0?{}:{issues:e.issues},files:e.files.map(e=>({file:e.file,success:e.result.success&&e.result.passed,...e.result.accounting===void 0?{}:{accounting:e.result.accounting},...e.result.lineage===void 0?{}:{lineage:n(e.result.lineage)},...e.durationMs===void 0?{}:{durationMs:e.durationMs},...e.result.tests===void 0?{}:{tests:e.result.tests.map(e=>({suite:e.suite,name:e.name,...e.ordinal===void 0?{}:{ordinal:e.ordinal},status:e.status,...e.durationMs===void 0?{}:{durationMs:e.durationMs},diagnostics:[...e.diagnostics,...e.assertions.flatMap(e=>e.diagnostics??[])],...e.assertions.some(e=>e.report!==void 0)?{reports:e.assertions.flatMap(e=>{let{report:t}=e;return t===void 0?[]:[{claimId:t.claimId,status:t.status,polarity:t.polarity,claim:structuredClone(t.claim),result:structuredClone(t.result),diagnostics:structuredClone(t.diagnostics),...t.evidence===void 0?{}:{evidence:structuredClone(t.evidence)},...e.loadId===void 0?{}:{loadId:e.loadId},canonical:{claim:[...t.canonicalClaim],plan:[...t.canonicalPlan],result:[...t.canonicalResult]}}]})}:{}}))},...e.result.success?{}:{issues:e.result.issues}}))}),m=e=>{try{return JSON.stringify(e)}catch{
45
+ /* v8 ignore next -- Only a RangeError from an over-long document reaches here. */
46
+ return}};function*h(e,t=67108864){let n=m(e);if(n!==void 0&&n.length<=t){yield n;return}if(typeof e!=`object`||!e){yield n??`null`;return}if(Array.isArray(e)){yield`[`;for(let[n,r]of e.entries())n>0&&(yield`,`),yield*h(r,t);yield`]`;return}let r=Object.entries(e).filter(([,e])=>e!==void 0&&typeof e!=`function`&&typeof e!=`symbol`);yield`{`;for(let[e,[n,i]]of r.entries())yield`${e>0?`,`:``}${JSON.stringify(n)}:`,yield*h(i,t);yield`}`}const g=async(e,t)=>{let n=s(e);if(n.kind===`help`)return t.write(n.message),0;if(n.kind===`error`)return t.write(`${n.message}\n\n${i}`),1;if(n.kind===`check`)return y(n.options,t);let{options:a}=n,o=a.projectPath.startsWith(`/`)?a.projectPath:`${t.cwd()}/${a.projectPath}`.replace(/\/\.$/u,``),c;try{c=await t.loadConfig({projectPath:o,configPath:a.config})}catch(e){let n=`GeoSpec config: ${e instanceof Error?e.message:String(e)}`;return t.write(a.json?JSON.stringify({success:!1,error:n}):n),1}let l=a.include.length>0?a.include:c.options.include,u=a.exclude.length>0?a.exclude:c.options.exclude,d=a.testNamePattern??c.options.testNamePattern,f=a.testTimeout??c.options.testTimeout,p=a.bail||c.options.bail===!0,m=await r({filesystem:t.discoveryFileSystem(o),projectPath:o,...a.files.length>0?{files:a.files}:{},...l===void 0?{}:{include:l},...u===void 0?{}:{exclude:u}});if(m.files.length===0){let e=`No GeoSpec files matched (unmatched: ${m.unmatchedRoots.join(`, `)}).`;return t.write(a.json?JSON.stringify({success:!1,error:e,...m}):e),1}let h=t.createRunner({projectPath:o,configPath:c.configPath,workers:a.workers,shardTimeout:a.shardTimeout}),g;try{g=await h.run({files:m.files,...d===void 0?{}:{testNamePattern:d},...f===void 0?{}:{testTimeout:f},...p?{bail:!0}:{}})}finally{await h.close()}return _(g,a.json,t),+!g.success},_=(e,t,n)=>{if(t)for(let t of h(p(e)))n.write(t);else n.write(d(e).join(`
47
+ `))},v=async(e,t)=>{try{return(await e.stat(t)).kind===`file`}catch{return!1}},y=async(n,r)=>{let i=e=>(r.write(n.json?JSON.stringify({success:!1,error:e}):e),1),a=t(n,r.cwd());if(`error`in a)return i(a.error);if(!await v(r.discoveryFileSystem(a.projectPath),`${a.projectPath}/${a.file}`))return i(`geospec check found no file at '${n.file}'.`);let o=r.createRunner({projectPath:a.projectPath,configPath:void 0,workers:void 0,shardTimeout:void 0,sources:a.sources}),s;try{s=await o.run({files:[e]})}finally{await o.close()}return _(s,n.json,r),+!s.success};export{u as formatFileReport,f as formatRunHeadline,d as formatRunReport,i as geoSpecCliUsage,h as jsonChunks,s as parseGeoSpecCliArguments,g as runGeoSpecCli,p as runReportJson};
@@ -5,7 +5,7 @@ import { GeoSpecCliHost } from "./cli.mjs";
5
5
  *
6
6
  * @public
7
7
  */
8
- type NodeGeoSpecCliHostOptions = {
8
+ export type NodeGeoSpecCliHostOptions = {
9
9
  /** Report sink used by embedding hosts and tests; defaults to stdout. */
10
10
  readonly reportStream?: (text: string) => void;
11
11
  /**
@@ -13,6 +13,8 @@ type NodeGeoSpecCliHostOptions = {
13
13
  * can run a project with no `geospec.config.*` (see `GeoSpecExporterModule` in `geospec/runner/node`).
14
14
  */
15
15
  readonly exporterModules?: readonly string[];
16
+ /** URL of a module each pool worker imports for its module VM (see `GeoSpecModuleVmModule` in `geospec/runner`). */
17
+ readonly moduleVmModule?: string;
16
18
  };
17
19
  /**
18
20
  * Build the Node CLI host.
@@ -21,6 +23,5 @@ type NodeGeoSpecCliHostOptions = {
21
23
  * @returns The host the CLI runs against.
22
24
  * @public
23
25
  */
24
- declare const createNodeGeoSpecCliHost: (options?: NodeGeoSpecCliHostOptions) => GeoSpecCliHost;
25
- //#endregion
26
- export { NodeGeoSpecCliHostOptions, createNodeGeoSpecCliHost };
26
+ export declare const createNodeGeoSpecCliHost: (options?: NodeGeoSpecCliHostOptions) => GeoSpecCliHost;
27
+ //#endregion
@@ -1 +1 @@
1
- import{loadGeoSpecConfig as e}from"../config/node/load.mjs";import{createGeoSpecNodePoolRunner as t}from"../runner/node/pool-runner.mjs";import{readdir as n,stat as r}from"node:fs/promises";import{availableParallelism as i}from"node:os";const a=(a={})=>{let o=a.reportStream??(e=>{process.stdout.write(e)});return{cwd:()=>process.cwd(),write:e=>{o(`${e}\n`)},discoveryFileSystem:()=>({readdir:async e=>n(e),stat:async e=>({kind:(await r(e)).isDirectory()?`directory`:`file`})}),loadConfig:async({projectPath:t,configPath:n})=>e({projectPath:t,...n===void 0?{}:{configPath:n}}),createRunner:({projectPath:e,configPath:n,workers:r,shardTimeout:o})=>t({projectPath:e,...n===void 0?{}:{configPath:n},...a.exporterModules===void 0?{}:{exporterModules:a.exporterModules},workers:r===0?i():r??1,...o===void 0?{}:{shardTimeout:o}})}};export{a as createNodeGeoSpecCliHost};
1
+ import{loadGeoSpecConfig as e}from"../config/node/load.mjs";import{createGeoSpecNodePoolRunner as t}from"../runner/node/pool-runner.mjs";import{readdir as n,stat as r}from"node:fs/promises";import{availableParallelism as i}from"node:os";const a=(a={})=>{let o=a.reportStream??(e=>{process.stdout.write(e)});return{cwd:()=>process.cwd(),write:e=>{o(`${e}\n`)},discoveryFileSystem:()=>({readdir:async e=>n(e),stat:async e=>({kind:(await r(e)).isDirectory()?`directory`:`file`})}),loadConfig:async({projectPath:t,configPath:n})=>e({projectPath:t,...n===void 0?{}:{configPath:n}}),createRunner:({projectPath:e,configPath:n,workers:r,shardTimeout:o,sources:s})=>t({projectPath:e,...n===void 0?{}:{configPath:n},...s===void 0?{}:{sources:s},...a.exporterModules===void 0?{}:{exporterModules:a.exporterModules},...a.moduleVmModule===void 0?{}:{moduleVmModule:a.moduleVmModule},workers:r===0?i():r??1,...o===void 0?{}:{shardTimeout:o}})}};export{a as createNodeGeoSpecCliHost};
@@ -1 +1 @@
1
- const e=e=>e instanceof Int8Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array;function t(n,r=new WeakSet){if(n==null)return null;if(typeof n==`string`||typeof n==`boolean`)return n;if(typeof n==`number`){if(!Number.isFinite(n))throw TypeError(`GeoSpec engine emitted a non-finite number.`);return n}if(n instanceof Error)return{name:n.name,message:n.message};if(n instanceof RegExp)return{type:`regexp`,pattern:n.source,flags:n.flags};if(e(n))return[...n];if(typeof n!=`object`||r.has(n))throw TypeError(`GeoSpec engine emitted a non-wire value.`);if(r.add(n),Array.isArray(n)){let e=n.map(e=>t(e,r));return r.delete(n),e}let i=Reflect.getPrototypeOf(n);if(i!==Object.prototype&&i!==null)throw TypeError(`GeoSpec engine emitted a non-plain object.`);let a={};for(let[e,i]of Object.entries(n))i!==void 0&&(a[e]=t(i,r));return r.delete(n),a}export{t as protocolWireValue};
1
+ const e=e=>e instanceof Int8Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array;function t(n,r=/* @__PURE__ */ new WeakSet){if(n==null)return null;if(typeof n==`string`||typeof n==`boolean`)return n;if(typeof n==`number`){if(!Number.isFinite(n))throw TypeError(`GeoSpec engine emitted a non-finite number.`);return n}if(n instanceof Error)return{name:n.name,message:n.message};if(n instanceof RegExp)return{type:`regexp`,pattern:n.source,flags:n.flags};if(e(n))return[...n];if(typeof n!=`object`||r.has(n))throw TypeError(`GeoSpec engine emitted a non-wire value.`);if(r.add(n),Array.isArray(n)){let e=n.map(e=>t(e,r));return r.delete(n),e}let i=Reflect.getPrototypeOf(n);if(i!==Object.prototype&&i!==null)throw TypeError(`GeoSpec engine emitted a non-plain object.`);let a={};for(let[e,i]of Object.entries(n))i!==void 0&&(a[e]=t(i,r));return r.delete(n),a}export{t as protocolWireValue};
@@ -10,6 +10,5 @@ import { GeoSpecConfig } from "./types.mjs";
10
10
  * @param config - The project configuration.
11
11
  * @returns The same object.
12
12
  */
13
- declare const defineConfig: (config: GeoSpecConfig) => GeoSpecConfig;
14
- //#endregion
15
- export { defineConfig };
13
+ export declare const defineConfig: (config: GeoSpecConfig) => GeoSpecConfig;
14
+ //#endregion
@@ -1 +1 @@
1
- import{loadGeoSpecConfig as e}from"./load.mjs";export{e as loadGeoSpecConfig};
1
+ export{loadGeoSpecConfig}from"./load.mjs";