partforge 0.41.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +31 -10
  2. package/bin/cli.js +100 -27
  3. package/docs/AUTHORING-PARTS.md +126 -14
  4. package/docs/ERROR-PATTERNS.md +6 -0
  5. package/package.json +48 -7
  6. package/skills/partforge/SKILL.md +17 -3
  7. package/src/app-embed-test.js +1 -1
  8. package/src/app-hinged-box.js +12 -0
  9. package/src/framework/animation-controls.js +243 -0
  10. package/src/framework/animation.js +217 -0
  11. package/src/framework/app.css +32 -0
  12. package/src/framework/assembly.js +1 -1
  13. package/src/framework/backend-select.js +25 -0
  14. package/src/framework/camera-tween.js +58 -0
  15. package/src/framework/chrome.css +16 -0
  16. package/src/framework/controls.js +13 -3
  17. package/src/framework/cutaway-gizmo-scene.js +244 -0
  18. package/src/framework/cutaway-gizmo.js +80 -243
  19. package/src/framework/default-view.js +46 -0
  20. package/src/framework/download.js +7 -2
  21. package/src/framework/export-controller.js +13 -2
  22. package/src/framework/geometry/probe.js +3 -22
  23. package/src/framework/jobs.js +9 -40
  24. package/src/framework/lint/finding.js +4 -0
  25. package/src/framework/lint/index.js +7 -3
  26. package/src/framework/lint/rules-animations.js +404 -0
  27. package/src/framework/lint/rules-place.js +76 -0
  28. package/src/framework/lint/rules-shape.js +12 -0
  29. package/src/framework/lint/rules-verify.js +2 -2
  30. package/src/framework/mount.js +93 -18
  31. package/src/{testing → framework/oracle}/build.js +1 -1
  32. package/src/{testing → framework/oracle}/bvh.js +1 -1
  33. package/src/{testing → framework/oracle}/measure.js +1 -1
  34. package/src/{testing → framework/oracle}/min-wall.js +1 -1
  35. package/src/{testing → framework/oracle}/verify.js +3 -3
  36. package/src/framework/param-deps.js +1 -1
  37. package/src/framework/part-model.js +48 -0
  38. package/src/framework/pick-request/client.js +11 -3
  39. package/src/framework/pick-request/endpoint.js +60 -0
  40. package/src/framework/pick-request/index.js +6 -0
  41. package/src/framework/pick-request/server.js +222 -34
  42. package/src/framework/pick-request/token-store.js +31 -0
  43. package/src/framework/pose-fast-path.js +12 -1
  44. package/src/framework/pose-probe-core.js +129 -0
  45. package/src/framework/pose-probe.js +7 -123
  46. package/src/framework/regen-loop.js +10 -3
  47. package/src/framework/safe-name.js +26 -0
  48. package/src/framework/verify-metrics.js +4 -4
  49. package/src/framework/view-state.js +25 -21
  50. package/src/framework/view-tabs.js +22 -7
  51. package/src/framework/viewer-controls.js +5 -26
  52. package/src/framework/viewer.js +58 -17
  53. package/src/hinged-box-worker.js +3 -0
  54. package/src/index.js +1 -1
  55. package/src/parts/hinged-box.js +94 -0
  56. package/src/testing/render.js +19 -8
  57. package/src/testing.js +15 -8
  58. package/types/derive.d.ts +14 -0
  59. package/types/geometry.d.ts +117 -0
  60. package/types/index.d.ts +240 -0
  61. package/types/kernel.d.ts +409 -0
  62. package/types/lint.d.ts +85 -0
  63. package/types/part.d.ts +381 -0
  64. package/types/testing.d.ts +362 -0
  65. package/types/worker.d.ts +21 -0
  66. /package/src/{testing → framework/oracle}/assert-dsl.js +0 -0
  67. /package/src/{testing → framework/oracle}/cases.js +0 -0
  68. /package/src/{testing → framework/oracle}/dfm-profiles.js +0 -0
  69. /package/src/{testing → framework/oracle}/gaps.js +0 -0
  70. /package/src/{testing → framework/oracle}/mesh.js +0 -0
@@ -0,0 +1,409 @@
1
+ // Type declarations for the backend-agnostic geometry kernel.
2
+ //
3
+ // This is the TypeScript half of the contract stated twice already: the
4
+ // `@typedef`s in src/framework/geometry/kernel.js (signatures) and
5
+ // docs/KERNEL-CONTRACT.md (prose semantics). The op LISTS in kernel.js
6
+ // (KERNEL_OPS / KERNEL_OPTIONAL_OPS / SOLID_OPS / SOLID_OPTIONAL_OPS /
7
+ // SHAPE2D_OPS / OCCT_ONLY_OPS) are data, and test/types-surface.test.js holds
8
+ // the interfaces below to them member-for-member — so an op added to the kernel
9
+ // cannot silently go undeclared here.
10
+ //
11
+ // Units are millimetres throughout.
12
+
13
+ /** A 2-D point, `[x, y]`. */
14
+ export type Point2 = [number, number] | number[];
15
+ /** A 3-D point or vector, `[x, y, z]`. */
16
+ export type Point3 = [number, number, number] | number[];
17
+
18
+ /** A closed CCW contour as a plain point list (the `polygon.js` helpers' output). */
19
+ export type PointsContour = Point2[];
20
+
21
+ /**
22
+ * A curve-native contour that carries arcs/béziers SYMBOLICALLY — the output of
23
+ * `roundedProfile()` and `pathProfile(...).close()`. OCCT turns these into exact
24
+ * B-rep edges (true circles in STEP); Manifold tessellates them at mesh LOD.
25
+ */
26
+ export interface ArcContour {
27
+ start: Point2;
28
+ segments: Array<{ to: Point2; via?: Point2; c1?: Point2; c2?: Point2 }>;
29
+ /** Set by `roundedProfile`; absent on a `pathProfile` contour. */
30
+ arc?: boolean;
31
+ }
32
+
33
+ /** Either contour form. */
34
+ export type Contour = PointsContour | ArcContour;
35
+
36
+ /** A polygon-with-holes region: one outer contour and any number of hole contours. */
37
+ export interface Region2D {
38
+ outer: Contour;
39
+ holes?: Contour[];
40
+ }
41
+
42
+ /** Anything the kernel accepts where a 2-D profile is wanted. */
43
+ export type ProfileInput = Contour | Region2D | Shape2D;
44
+
45
+ /** Mesh level of detail. Manifold bakes this in at primitive creation. */
46
+ export type MeshQuality = "preview" | "print";
47
+
48
+ /** A triangle soup (Manifold) or an indexed mesh (OCCT), as `Solid.toMesh()` returns it. */
49
+ export interface Mesh {
50
+ positions: Float32Array;
51
+ normals: Float32Array;
52
+ /** Present on the OCCT backend; absent for a Manifold non-indexed soup. */
53
+ indices?: Uint32Array;
54
+ triangles: number;
55
+ /** Feature-edge line segments (Manifold). */
56
+ edges?: Float32Array;
57
+ }
58
+
59
+ /** The indexed mesh 3MF export needs. */
60
+ export interface IndexedMesh {
61
+ positions: Float32Array;
62
+ indices: Uint32Array;
63
+ }
64
+
65
+ /** Axis-aligned bounds of a solid. */
66
+ export interface BoundingBox3 {
67
+ min: number[];
68
+ max: number[];
69
+ center: number[];
70
+ size: number[];
71
+ }
72
+
73
+ /** Axis-aligned bounds of a 2-D shape. */
74
+ export interface BoundingBox2 {
75
+ min: number[];
76
+ max: number[];
77
+ }
78
+
79
+ /** A materialized region: point rings only, arcs already flattened. */
80
+ export interface MaterializedRegion {
81
+ outer: number[][];
82
+ holes: number[][][];
83
+ }
84
+
85
+ /** Which geometry backend a part builds on. */
86
+ export type BackendName = "manifold" | "occt";
87
+
88
+ /** Cardinal direction for `Solid.along()`. */
89
+ export type AxisDirection = "+X" | "-X" | "+Y" | "-Y" | "+Z" | "-Z";
90
+ /** A mirror plane. */
91
+ export type MirrorPlane = "XY" | "XZ" | "YZ";
92
+ /** A named world axis. */
93
+ export type AxisName = "X" | "Y" | "Z";
94
+
95
+ /** Convex-corner style for an offset. */
96
+ export type OffsetCorners = "round" | "chamfer" | "sharp";
97
+
98
+ // --- edge / face selectors (OCCT-only ops) ---------------------------------
99
+
100
+ /**
101
+ * Which edges a `fillet`/`chamfer` applies to. Omit for every edge. The object
102
+ * forms are portable across backends-by-contract; a raw replicad finder callback
103
+ * is an OCCT-only escape hatch (see docs/KERNEL-CONTRACT.md).
104
+ */
105
+ export type EdgeSelector =
106
+ | { dir: AxisName }
107
+ | { inPlane: MirrorPlane; at?: number }
108
+ | { near: Point3 }
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- an opaque replicad EdgeFinder
110
+ | ((finder: any) => any);
111
+
112
+ /** Which face(s) `shell` opens. Same forms as `EdgeSelector`. */
113
+ export type FaceSelector = EdgeSelector;
114
+
115
+ // --- Shape2D ----------------------------------------------------------------
116
+
117
+ /**
118
+ * An opaque 2-D boolean value (Manifold wraps a CrossSection, OCCT a replicad
119
+ * Drawing). `_`-prefixed keys are backend internals and are not declared.
120
+ */
121
+ export interface Shape2D {
122
+ union(other: Shape2D | Contour): Shape2D;
123
+ cut(other: Shape2D | Contour): Shape2D;
124
+ /** Batch subtract. */
125
+ cutAll(others: Array<Shape2D | Contour>): Shape2D;
126
+ intersect(other: Shape2D | Contour): Shape2D;
127
+ /**
128
+ * Grow (`delta > 0`) or inset (`delta < 0`). Curve-preserving on OCCT,
129
+ * faceted at mesh LOD on Manifold. Throws if the offset collapses the shape.
130
+ */
131
+ offset(delta: number, opts?: { corners?: OffsetCorners; segs?: number }): Shape2D;
132
+ /** Net area (outers minus holes), mm². */
133
+ area(): number;
134
+ boundingBox(): BoundingBox2;
135
+ /** Materialize into region arrays. */
136
+ toRegions(): MaterializedRegion[];
137
+ /** `toRegions()` unwrapped — throws unless there is exactly one region. */
138
+ simple(): MaterializedRegion;
139
+ /** Scission: each disjoint region as its own live `Shape2D`. */
140
+ regions(): Shape2D[];
141
+ clone(): Shape2D;
142
+ /** Sugar for `k.extrude({ profile: this, ... })`. */
143
+ extrude(opts: { h: number; twist?: number; scaleTop?: number }): Solid;
144
+ /** Sugar for `k.revolve({ profile: this, ... })`. */
145
+ revolve(opts?: { degrees?: number }): Solid;
146
+ }
147
+
148
+ // --- Solid ------------------------------------------------------------------
149
+
150
+ /**
151
+ * An opaque handle to a backend solid. `_`-prefixed keys are backend internals
152
+ * and are not declared.
153
+ *
154
+ * On the OCCT backend a transform CONSUMES its operand — never reuse a solid
155
+ * after transforming it; `.clone()` first.
156
+ */
157
+ export interface Solid {
158
+ cut(tool: Solid): Solid;
159
+ /** Batch subtract (backend-optimized). */
160
+ cutAll(tools: Solid[]): Solid;
161
+ intersect(other: Solid): Solid;
162
+ /** Boolean union with one other solid (n-ary: `k.union([...])`). */
163
+ union(other: Solid): Solid;
164
+ /** Independent copy. */
165
+ clone(): Solid;
166
+ /**
167
+ * Name this solid's surface for hover/pick feature attribution. Survives
168
+ * transforms and booleans; the same name on several solids merges into one
169
+ * feature.
170
+ */
171
+ label(name: string): Solid;
172
+ /** Axis-aligned bounds (a query, not a transform). */
173
+ boundingBox(): BoundingBox3;
174
+ /** Volume in mm³. */
175
+ volume(): number;
176
+ translate(v: Point3): Solid;
177
+ /** Internal primitive — prefer `rotateX`/`rotateY`/`rotateZ`/`rotateAbout`. */
178
+ rotate(deg: number, center: Point3, axis: Point3): Solid;
179
+ rotateX(deg: number): Solid;
180
+ rotateY(deg: number): Solid;
181
+ rotateZ(deg: number): Solid;
182
+ /** General rotation: `axis` names a world axis or gives a vector. */
183
+ rotateAbout(o: { axis: AxisName | Point3; deg: number; through?: Point3 }): Solid;
184
+ /** Orient the canonical +Z build axis along `dir`. */
185
+ along(dir: AxisDirection): Solid;
186
+ /** Place an origin-built solid at point `v` (readable alias of `translate`). */
187
+ at(v: Point3): Solid;
188
+ mirror(plane: MirrorPlane): Solid;
189
+ /** Uniform scale about `center` (default origin). */
190
+ scale(factor: number, center?: Point3): Solid;
191
+ toMesh(opts?: { quality?: MeshQuality }): Mesh;
192
+ toSTL(opts?: { quality?: MeshQuality }): Promise<ArrayBuffer>;
193
+ toIndexedMesh(): IndexedMesh;
194
+ /**
195
+ * Round edges — OCCT only (Manifold throws `KernelCapabilityError`, and the
196
+ * probe routes a part that calls this to OCCT). Legacy `(r, selector)` is
197
+ * accepted until contract v2.
198
+ */
199
+ fillet(r: number | { r: number; edges?: EdgeSelector }): Solid;
200
+ /** Bevel edges — OCCT only. Legacy `(d, selector)` accepted until contract v2. */
201
+ chamfer(d: number | { d: number; edges?: EdgeSelector }): Solid;
202
+ /**
203
+ * Hollow inward, wall `t`, opening the faces `open` selects — OCCT only.
204
+ * Closed (no open face) hollows are not supported.
205
+ */
206
+ shell(o: { t: number; open: FaceSelector }): Solid;
207
+ /** Through-hole count (Manifold only). */
208
+ genus?(): number;
209
+ /** No geometry at all (Manifold only). */
210
+ isEmpty?(): boolean;
211
+ }
212
+
213
+ // --- kernel op option objects ----------------------------------------------
214
+
215
+ /** `k.cylinder` — a straight cylinder (`r`|`d`) or a frustum (`r1`,`r2` / `d1`,`d2`). */
216
+ export interface CylinderOptions {
217
+ r?: number;
218
+ d?: number;
219
+ r1?: number;
220
+ r2?: number;
221
+ d1?: number;
222
+ d2?: number;
223
+ h: number;
224
+ /** Centre the solid on Z too (default: base at z = 0). */
225
+ center?: boolean;
226
+ }
227
+
228
+ export interface BoxOptions {
229
+ /** `[x, y, z]` — centred in X/Y with the base at z = 0. */
230
+ size?: Point3;
231
+ center?: boolean;
232
+ min?: Point3;
233
+ max?: Point3;
234
+ }
235
+
236
+ export interface PrismOptions {
237
+ points: PointsContour | ArcContour;
238
+ h: number;
239
+ /** Degrees of twist over the height. */
240
+ twist?: number;
241
+ /** Uniform top taper: 1 straight, < 1 taper in, 0 → a point. */
242
+ scaleTop?: number;
243
+ }
244
+
245
+ export interface ExtrudeOptions {
246
+ profile: ProfileInput;
247
+ h: number;
248
+ twist?: number;
249
+ scaleTop?: number;
250
+ /** 45° rim bevel (no `twist`/`scaleTop`; `bottom + top < h`). */
251
+ bevel?: number | { bottom?: number; top?: number };
252
+ }
253
+
254
+ export interface LoftRing {
255
+ polygon?: PointsContour;
256
+ sides?: number;
257
+ radius?: number;
258
+ z: number;
259
+ /** Degrees about Z. */
260
+ rotate?: number;
261
+ scale?: number | Point2;
262
+ }
263
+
264
+ export interface LoftOptions {
265
+ rings: LoftRing[];
266
+ /** `false` = smooth C2 blend, honoured only by OCCT. */
267
+ ruled?: boolean;
268
+ /** Capless loop — Manifold only. */
269
+ closed?: boolean;
270
+ }
271
+
272
+ export interface SweepOptions {
273
+ profile: PointsContour;
274
+ /** A 3-D polyline, `[[x, y, z], …]`. */
275
+ path: Point3[];
276
+ /** Capless loop (must be planar) — Manifold only. */
277
+ closed?: boolean;
278
+ cornerRadius?: number;
279
+ ruled?: boolean;
280
+ /** OCCT-native swept B-rep. */
281
+ smooth?: boolean;
282
+ }
283
+
284
+ /** `k.revolve` — a lathe profile `[[r, z], …]` with `r >= 0`, revolved about Z. */
285
+ export interface RevolveOptions {
286
+ profile: ProfileInput;
287
+ degrees?: number;
288
+ }
289
+
290
+ export interface HelixSweptTubeOptions {
291
+ pathR: number;
292
+ profileR: number;
293
+ pitch: number;
294
+ turns: number;
295
+ z0: number;
296
+ lefthand: boolean;
297
+ }
298
+
299
+ export interface RoundedCylinderOptions {
300
+ r?: number;
301
+ d?: number;
302
+ h: number;
303
+ center?: boolean;
304
+ /** Rim round-over: a number (both rims) or per-rim. `round <= r`, `top + bottom <= h`. */
305
+ round: number | { top?: number; bottom?: number };
306
+ }
307
+
308
+ export interface RoundedBoxOptions {
309
+ size: Point3;
310
+ center?: boolean;
311
+ /** `side` = vertical edges, `top`/`bottom` = rims. */
312
+ round: number | { side?: number; top?: number; bottom?: number };
313
+ }
314
+
315
+ export interface TorusOptions {
316
+ rMajor: number;
317
+ rMinor: number;
318
+ }
319
+
320
+ export interface BoredCylinderOptions {
321
+ od: number;
322
+ h: number;
323
+ bore: number;
324
+ }
325
+
326
+ /** Horizontal alignment of a `text2d` block. */
327
+ export type TextAlign = "center" | "left" | "right";
328
+ /** Vertical alignment of a `text2d` block. */
329
+ export type TextVAlign = "middle" | "baseline" | "top" | "bottom";
330
+
331
+ export interface Text2dOptions {
332
+ /** Cap height in mm (the design height of a capital letter). */
333
+ size: number;
334
+ /** A name declared in the part's `fonts` map; omit for the bundled default. */
335
+ font?: string;
336
+ align?: TextAlign;
337
+ valign?: TextVAlign;
338
+ /** Baseline-to-baseline distance in mm; omit for the font-metrics default. */
339
+ lineHeight?: number;
340
+ /** Letter spacing in mm. */
341
+ tracking?: number;
342
+ /** Pair-wise kerning (default `true`). */
343
+ kerning?: boolean;
344
+ }
345
+
346
+ /** Anything `k.hull`/`k.hullChain` accepts as one input. */
347
+ export type HullInput = Shape2D | Contour;
348
+
349
+ // --- the kernel -------------------------------------------------------------
350
+
351
+ /**
352
+ * The backend-agnostic kernel handed to `build(k, p, d)`. The same code runs on
353
+ * Manifold (mesh CSG) and OCCT/replicad (exact B-rep).
354
+ *
355
+ * Every multi-parameter op takes a single options object — the canonical calling
356
+ * convention. Legacy positional forms stay silently accepted until contract v2
357
+ * and are deliberately NOT declared here.
358
+ */
359
+ export interface GeometryKernel {
360
+ cylinder(o: CylinderOptions): Solid;
361
+ /** Compound: a bored-through cylinder as one cache node. */
362
+ boredCylinder(o: BoredCylinderOptions): Solid;
363
+ /** Sphere centred at the origin; the bare `sphere(r)` form stays valid. */
364
+ sphere(o: { r?: number; d?: number } | number): Solid;
365
+ box(o: BoxOptions): Solid;
366
+ /** Extrude a polygon (or arc profile) from z = 0. */
367
+ prism(o: PrismOptions): Solid;
368
+ /** Extrude a polygon-with-holes region from z = 0. */
369
+ extrude(o: ExtrudeOptions): Solid;
370
+ /** Revolve a lathe profile around Z. */
371
+ revolve(o: RevolveOptions): Solid;
372
+ /** Stack polygon cross-sections into a solid. */
373
+ loft(o: LoftOptions): Solid;
374
+ /** Sweep a 2-D profile along a 3-D polyline. */
375
+ sweep(o: SweepOptions): Solid;
376
+ helixSweptTube(o: HelixSweptTubeOptions): Solid;
377
+ /** Rim round-overs via one lathe revolve; curve-exact in STEP. */
378
+ roundedCylinder(o: RoundedCylinderOptions): Solid;
379
+ torus(o: TorusOptions): Solid;
380
+ /** Selective edge rounding. Stays on Manifold (unlike `Solid.fillet`). */
381
+ roundedBox(o: RoundedBoxOptions): Solid;
382
+ /** N-ary boolean union. */
383
+ union(solids: Solid[]): Solid;
384
+ /** Lift a profile into a 2-D boolean value. */
385
+ shape2d(profile: ProfileInput): Shape2D;
386
+ /** Render outline-font text as a `Shape2D`. */
387
+ text2d(string: string, opts?: Text2dOptions): Shape2D;
388
+ /** Convex hull of all inputs → a convex (faceted) `Shape2D`. */
389
+ hull(inputs: HullInput[]): Shape2D;
390
+ /** Swept hull over an ordered sequence (>= 2 inputs). */
391
+ hullChain(inputs: HullInput[]): Shape2D;
392
+ /** STEP bytes — OCCT only (Manifold throws `KernelCapabilityError`). */
393
+ toSTEP(named: Array<{ name: string; solid: Solid }>): Promise<ArrayBuffer>;
394
+
395
+ // Backend-optional: the sub-part cache brackets and WASM lifetime hooks. Every
396
+ // framework caller reaches these through `?.`, so a third-party backend may
397
+ // omit them entirely.
398
+
399
+ /** Open a per-sub-part solid-cache round. */
400
+ beginSubPart?(name: string): void;
401
+ /** Close the cache round — always pair with `beginSubPart`. */
402
+ endSubPart?(): void;
403
+ /** Drop cache partitions idle for 3 rebinds. Never call mid-bracket. */
404
+ sweepCache?(): void;
405
+ cacheStats?(): { hits: number; misses: number };
406
+ resetCacheStats?(): void;
407
+ /** Free per-job WASM objects (Manifold backend); call after each job. */
408
+ cleanup?(): void;
409
+ }
@@ -0,0 +1,85 @@
1
+ // partforge/lint — static PartDefinition validation.
2
+ //
3
+ // Zero runtime dependencies: it never imports a geometry kernel or the DOM
4
+ // viewer, so it runs unchanged in Node, a Web Worker, a sandboxed iframe, and
5
+ // Deno.
6
+
7
+ import type { Derived, PartDefinition, ResolvedParams } from "./part.js";
8
+
9
+ /**
10
+ * `error` = the part is provably broken (it cannot behave as authored, whether
11
+ * or not that surfaces as a throw). `warning` = suspicious or lossy, but the
12
+ * part behaves as authored; a warning never blocks anything. `note` = neither
13
+ * broken nor suspicious — informational context for an authoring agent (e.g.
14
+ * "this animated track rebuilds geometry"); notes never gate `measure` or
15
+ * `--strict`.
16
+ */
17
+ export type FindingSeverity = "error" | "warning" | "note";
18
+
19
+ export interface Finding {
20
+ /** The rule id, e.g. `"features-requires-sliders"`. */
21
+ rule: string;
22
+ severity: FindingSeverity;
23
+ message: string;
24
+ /** One self-contained corrective sentence. Always present. */
25
+ hint: string;
26
+ /**
27
+ * A JS accessor path rooted at the PartDefinition —
28
+ * `parameters[1].features[0]`, `defaults.bore`, `parts.spacer.views[0]`.
29
+ * `""` for findings about the definition as a whole. For navigation only.
30
+ */
31
+ path: string;
32
+ /** A stable ERROR-PATTERNS.md entry id, when one applies. */
33
+ pattern?: string;
34
+ }
35
+
36
+ export interface LintReport {
37
+ /** True when there are no `error` findings. Warnings do not affect it. */
38
+ ok: boolean;
39
+ errors: Finding[];
40
+ warnings: Finding[];
41
+ /** Informational findings. Never affect `ok`, `measure`, or `--strict`. */
42
+ notes: Finding[];
43
+ }
44
+
45
+ /**
46
+ * Lint a PartDefinition. NEVER throws — a rule that throws yields an
47
+ * `internal-rule-error` warning and the run continues.
48
+ *
49
+ * @param part - the default-exported PartDefinition (deliberately `unknown`:
50
+ * lint's whole job is to be handed something that may not be one).
51
+ * @param opts - `params` are layered over `part.defaults` for the probe pass.
52
+ */
53
+ export function lintPart(part: unknown, opts?: { params?: ResolvedParams } | null): LintReport;
54
+
55
+ /** The shared context a rule reads. */
56
+ export interface LintContext {
57
+ part: unknown;
58
+ /** `{ ...part.defaults, ...params }`, or `{}` if building it threw. */
59
+ p: ResolvedParams;
60
+ /** `resolveDerived(part, p)`, or `{}` if it threw. */
61
+ d: Derived;
62
+ /** The message from a throwing `defaults`/`params` read, else `null`. */
63
+ pError: string | null;
64
+ /** The message from a throwing `derive()`, else `null`. */
65
+ deriveError: string | null;
66
+ /** A memoized geometry-free probe run of every `build`. */
67
+ probe(): unknown;
68
+ /** A second, un-memoized probe run — for the determinism diff. */
69
+ probeAgain(): unknown;
70
+ /** `verify.expect` resolved once per lint pass. */
71
+ resolveExpectOnce(): unknown;
72
+ }
73
+
74
+ export interface LintRule {
75
+ id: string;
76
+ run(ctx: LintContext): Finding[];
77
+ }
78
+
79
+ /**
80
+ * The rule registry — one rule object per finding id, so it doubles as the
81
+ * documented rule catalog.
82
+ */
83
+ export const RULES: LintRule[];
84
+
85
+ export type { PartDefinition };