brepjs-verify 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +5 -1
- package/dist/mcp/server.cjs +65 -0
- package/dist/mcp/server.js +65 -0
- package/dist/mcp/telemetry.d.ts +6 -0
- package/dist/sandbox/runProgram.d.ts +21 -0
- package/package.json +8 -1
- package/reference/llms-full.txt +4 -4
- package/viewer/dist/assets/{brepjs--Y77m6Ep.js → brepjs-CTRhaUQW.js} +2 -2
- package/viewer/dist/assets/{index-BryWZEsB.js → index-BfoFDXLd.js} +38 -38
- package/viewer/dist/assets/{kernelWorker-aIBoIbHI.js → kernelWorker-pKJGBWtg.js} +1 -1
- package/viewer/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.28.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.27.0...brepjs-verify-v0.28.0) (2026-06-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **brepjs-verify:** fan out the eval across sharded CI runners ([#1503](https://github.com/andymai/brepjs/issues/1503)) ([8eb1f69](https://github.com/andymai/brepjs/commit/8eb1f690588e418e4b884e1294ba91b679667d61))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **brepjs-verify:** chamfer-fragility + unwrap/TS2322 SKILL.md rules ([#1506](https://github.com/andymai/brepjs/issues/1506)) ([0f24004](https://github.com/andymai/brepjs/commit/0f24004983effbd85545372b167f472eed35ec96))
|
|
14
|
+
|
|
15
|
+
## [0.27.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.26.0...brepjs-verify-v0.27.0) (2026-06-19)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **brepjs-verify:** enrich experiment traces with request + code ([#1502](https://github.com/andymai/brepjs/issues/1502)) ([5b9141f](https://github.com/andymai/brepjs/commit/5b9141f8107794683d3ef7945466e326132d4a6d))
|
|
21
|
+
* **brepjs-verify:** Langfuse observability foundation ([#1498](https://github.com/andymai/brepjs/issues/1498)) ([78c81f3](https://github.com/andymai/brepjs/commit/78c81f3b3e4890cbd3abc2474fcc5b7e4926bd3e))
|
|
22
|
+
* **brepjs-verify:** looped skill eval + Langfuse v5 telemetry ([#1494](https://github.com/andymai/brepjs/issues/1494)) ([06a7e7c](https://github.com/andymai/brepjs/commit/06a7e7c6e882fe795122141dce1e4c04933f3fdc))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **brepjs-verify:** SKILL.md import rule — eval-driven self-heal ([#1501](https://github.com/andymai/brepjs/issues/1501)) ([550d2ba](https://github.com/andymai/brepjs/commit/550d2ba08849a1692ff237d1a79a28afd88b9053))
|
|
28
|
+
* **brepjs-verify:** three deferred Langfuse-eval follow-ups ([#1500](https://github.com/andymai/brepjs/issues/1500)) ([90b63bd](https://github.com/andymai/brepjs/commit/90b63bd6a2f277db6ec634f2bbecadf1379f4e4d))
|
|
29
|
+
|
|
3
30
|
## [0.26.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.25.1...brepjs-verify-v0.26.0) (2026-06-18)
|
|
4
31
|
|
|
5
32
|
|
package/README.md
CHANGED
|
@@ -110,9 +110,13 @@ Few-shot examples live under `skill/examples/<name>.brep.ts`, each with a `<name
|
|
|
110
110
|
|
|
111
111
|
`npm run eval` (`bench/run.ts`) replays every `skill/examples/*.brep.ts` with a sibling `*.expected.json` through the public `runPart` runtime, compares measured volume/area/validity/shape-type against the recorded baseline within each file's tolerance (default 0.5%), prints a PASS/FAIL scorecard, and exits non-zero on any regression. It is deterministic (no LLM or API key) so it runs in CI as the package's regression net. Refresh a baseline by re-recording the example's `*.expected.json` after an intentional geometry change.
|
|
112
112
|
|
|
113
|
+
### Manual skill eval (`/eval-skill`)
|
|
114
|
+
|
|
115
|
+
The fast iteration loop — run on your Claude subscription, no API key, no billing. The `/eval-skill` slash command drives the same `bench/prompts.ts` corpus through the current Claude Code session: Claude authors each part from the **deployed `SKILL.md`**, runs `verify --check --snapshot`, judges the rendered snapshots against each prompt's rubric, emits the two-signal scorecard, and proposes SKILL.md fixes from the failures. This is the recommended way to answer "did my SKILL.md edit help?" — see `.claude/commands/eval-skill.md`.
|
|
116
|
+
|
|
113
117
|
### Live eval (`npm run eval:live`)
|
|
114
118
|
|
|
115
|
-
The
|
|
119
|
+
The **automated / isolated** counterpart to `/eval-skill` — opt-in and billed. An SDK harness sends the `bench/prompts.ts` prompts to a model with the **deployed `SKILL.md` as the system prompt** (so it measures the skill _in isolation_, not inside a Claude Code session), then verifies each generated part two ways:
|
|
116
120
|
|
|
117
121
|
- **Auto (objective):** `runPart --check` → valid solid + any pinned dims within tolerance.
|
|
118
122
|
- **Judge (intent):** a multimodal Claude call looks at the rendered iso/front/top/right snapshots and decides whether the part matches the request + rubric.
|
package/dist/mcp/server.cjs
CHANGED
|
@@ -297,6 +297,70 @@ async function appendRunRecord(filePath, record) {
|
|
|
297
297
|
await (0, node_fs_promises.appendFile)(filePath, `${JSON.stringify(record)}\n`);
|
|
298
298
|
}
|
|
299
299
|
//#endregion
|
|
300
|
+
//#region src/mcp/telemetry.ts
|
|
301
|
+
/**
|
|
302
|
+
* Optional Langfuse tracing for MCP `run_program` executions — a strictly best-effort side channel,
|
|
303
|
+
* exactly like the `BREPJS_RUN_RECORD_PATH` JSONL record. The @langfuse / OpenTelemetry packages are
|
|
304
|
+
* NOT runtime dependencies of the shipped MCP: they are *dynamically* imported, and only when the
|
|
305
|
+
* `LANGFUSE_*` keys are present. So the server never hard-requires telemetry — no keys (or the deps
|
|
306
|
+
* simply absent in a published install) → {@link traceRun} is a silent no-op. It never throws.
|
|
307
|
+
*/
|
|
308
|
+
var tracerPromise;
|
|
309
|
+
async function initTracer() {
|
|
310
|
+
if (!process.env["LANGFUSE_PUBLIC_KEY"] || !process.env["LANGFUSE_SECRET_KEY"]) return null;
|
|
311
|
+
try {
|
|
312
|
+
const [{ NodeSDK }, otel, { startActiveObservation }, { LangfuseClient }] = await Promise.all([
|
|
313
|
+
import("@opentelemetry/sdk-node"),
|
|
314
|
+
import("@langfuse/otel"),
|
|
315
|
+
import("@langfuse/tracing"),
|
|
316
|
+
import("@langfuse/client")
|
|
317
|
+
]);
|
|
318
|
+
const processor = new otel.LangfuseSpanProcessor();
|
|
319
|
+
new NodeSDK({ spanProcessors: [processor] }).start();
|
|
320
|
+
const client = new LangfuseClient();
|
|
321
|
+
return { trace: async (code, result) => {
|
|
322
|
+
const rec = buildRunRecord(code, result);
|
|
323
|
+
startActiveObservation("run_program", (obs) => {
|
|
324
|
+
obs.update({
|
|
325
|
+
input: { code },
|
|
326
|
+
output: {
|
|
327
|
+
outcome: rec.outcome,
|
|
328
|
+
ok: rec.ok,
|
|
329
|
+
measurements: rec.measurements
|
|
330
|
+
},
|
|
331
|
+
metadata: {
|
|
332
|
+
source: "mcp",
|
|
333
|
+
resultHash: rec.resultHash
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
try {
|
|
337
|
+
client.score.create({
|
|
338
|
+
traceId: obs.traceId,
|
|
339
|
+
name: "ok",
|
|
340
|
+
value: rec.ok ? 1 : 0,
|
|
341
|
+
dataType: "NUMERIC"
|
|
342
|
+
});
|
|
343
|
+
} catch {}
|
|
344
|
+
});
|
|
345
|
+
await Promise.allSettled([processor.forceFlush(), client.flush()]);
|
|
346
|
+
} };
|
|
347
|
+
} catch (e) {
|
|
348
|
+
console.warn(`langfuse: MCP tracing unavailable (${e.message.split("\n")[0]})`);
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Trace one `run_program` execution to Langfuse (best-effort; no-op without the `LANGFUSE_*` keys or
|
|
354
|
+
* the optional telemetry deps). Never throws — callers fire-and-forget it as a side channel.
|
|
355
|
+
*/
|
|
356
|
+
async function traceRun(code, result) {
|
|
357
|
+
try {
|
|
358
|
+
await (await (tracerPromise ??= initTracer()))?.trace(code, result);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
console.warn(`langfuse: traceRun failed (${e.message.split("\n")[0]})`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
300
364
|
//#region src/mcp/tools.ts
|
|
301
365
|
/** JSON Schema for `run_program` input (the low-level Server API takes plain JSON Schema). */
|
|
302
366
|
var RUN_PROGRAM_INPUT_SCHEMA = {
|
|
@@ -330,6 +394,7 @@ async function runProgramTool(args) {
|
|
|
330
394
|
if (recordPath) appendRunRecord(recordPath, buildRunRecord(args.code, result)).catch((err) => {
|
|
331
395
|
console.warn(`run-record append failed (BREPJS_RUN_RECORD_PATH=${recordPath}):`, err);
|
|
332
396
|
});
|
|
397
|
+
traceRun(args.code, result);
|
|
333
398
|
if (result.outcome === "completed") {
|
|
334
399
|
const payload = {
|
|
335
400
|
outcome: "completed",
|
package/dist/mcp/server.js
CHANGED
|
@@ -297,6 +297,70 @@ async function appendRunRecord(filePath, record) {
|
|
|
297
297
|
await appendFile(filePath, `${JSON.stringify(record)}\n`);
|
|
298
298
|
}
|
|
299
299
|
//#endregion
|
|
300
|
+
//#region src/mcp/telemetry.ts
|
|
301
|
+
/**
|
|
302
|
+
* Optional Langfuse tracing for MCP `run_program` executions — a strictly best-effort side channel,
|
|
303
|
+
* exactly like the `BREPJS_RUN_RECORD_PATH` JSONL record. The @langfuse / OpenTelemetry packages are
|
|
304
|
+
* NOT runtime dependencies of the shipped MCP: they are *dynamically* imported, and only when the
|
|
305
|
+
* `LANGFUSE_*` keys are present. So the server never hard-requires telemetry — no keys (or the deps
|
|
306
|
+
* simply absent in a published install) → {@link traceRun} is a silent no-op. It never throws.
|
|
307
|
+
*/
|
|
308
|
+
var tracerPromise;
|
|
309
|
+
async function initTracer() {
|
|
310
|
+
if (!process.env["LANGFUSE_PUBLIC_KEY"] || !process.env["LANGFUSE_SECRET_KEY"]) return null;
|
|
311
|
+
try {
|
|
312
|
+
const [{ NodeSDK }, otel, { startActiveObservation }, { LangfuseClient }] = await Promise.all([
|
|
313
|
+
import("@opentelemetry/sdk-node"),
|
|
314
|
+
import("@langfuse/otel"),
|
|
315
|
+
import("@langfuse/tracing"),
|
|
316
|
+
import("@langfuse/client")
|
|
317
|
+
]);
|
|
318
|
+
const processor = new otel.LangfuseSpanProcessor();
|
|
319
|
+
new NodeSDK({ spanProcessors: [processor] }).start();
|
|
320
|
+
const client = new LangfuseClient();
|
|
321
|
+
return { trace: async (code, result) => {
|
|
322
|
+
const rec = buildRunRecord(code, result);
|
|
323
|
+
startActiveObservation("run_program", (obs) => {
|
|
324
|
+
obs.update({
|
|
325
|
+
input: { code },
|
|
326
|
+
output: {
|
|
327
|
+
outcome: rec.outcome,
|
|
328
|
+
ok: rec.ok,
|
|
329
|
+
measurements: rec.measurements
|
|
330
|
+
},
|
|
331
|
+
metadata: {
|
|
332
|
+
source: "mcp",
|
|
333
|
+
resultHash: rec.resultHash
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
try {
|
|
337
|
+
client.score.create({
|
|
338
|
+
traceId: obs.traceId,
|
|
339
|
+
name: "ok",
|
|
340
|
+
value: rec.ok ? 1 : 0,
|
|
341
|
+
dataType: "NUMERIC"
|
|
342
|
+
});
|
|
343
|
+
} catch {}
|
|
344
|
+
});
|
|
345
|
+
await Promise.allSettled([processor.forceFlush(), client.flush()]);
|
|
346
|
+
} };
|
|
347
|
+
} catch (e) {
|
|
348
|
+
console.warn(`langfuse: MCP tracing unavailable (${e.message.split("\n")[0]})`);
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Trace one `run_program` execution to Langfuse (best-effort; no-op without the `LANGFUSE_*` keys or
|
|
354
|
+
* the optional telemetry deps). Never throws — callers fire-and-forget it as a side channel.
|
|
355
|
+
*/
|
|
356
|
+
async function traceRun(code, result) {
|
|
357
|
+
try {
|
|
358
|
+
await (await (tracerPromise ??= initTracer()))?.trace(code, result);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
console.warn(`langfuse: traceRun failed (${e.message.split("\n")[0]})`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
300
364
|
//#region src/mcp/tools.ts
|
|
301
365
|
/** JSON Schema for `run_program` input (the low-level Server API takes plain JSON Schema). */
|
|
302
366
|
var RUN_PROGRAM_INPUT_SCHEMA = {
|
|
@@ -330,6 +394,7 @@ async function runProgramTool(args) {
|
|
|
330
394
|
if (recordPath) appendRunRecord(recordPath, buildRunRecord(args.code, result)).catch((err) => {
|
|
331
395
|
console.warn(`run-record append failed (BREPJS_RUN_RECORD_PATH=${recordPath}):`, err);
|
|
332
396
|
});
|
|
397
|
+
traceRun(args.code, result);
|
|
333
398
|
if (result.outcome === "completed") {
|
|
334
399
|
const payload = {
|
|
335
400
|
outcome: "completed",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { RunProgramResult } from '../sandbox/runProgram.js';
|
|
2
|
+
/**
|
|
3
|
+
* Trace one `run_program` execution to Langfuse (best-effort; no-op without the `LANGFUSE_*` keys or
|
|
4
|
+
* the optional telemetry deps). Never throws — callers fire-and-forget it as a side channel.
|
|
5
|
+
*/
|
|
6
|
+
export declare function traceRun(code: string, result: RunProgramResult): Promise<void>;
|
|
@@ -69,6 +69,27 @@ export declare function killActiveSandboxes(signal?: NodeJS.Signals): void;
|
|
|
69
69
|
export declare function installSandboxShutdownHandlers(): void;
|
|
70
70
|
/** Execute `code` (an agent-authored `.brep.ts` module) in an isolated, resource-bounded child. */
|
|
71
71
|
export declare function runProgram(code: string, opts?: RunProgramOptions): Promise<RunProgramResult>;
|
|
72
|
+
/** Outcome of a sandboxed `--check --step` run. `completed` carries the report; `stepPath` is set
|
|
73
|
+
* only when a valid solid produced a STEP. */
|
|
74
|
+
export type RunProgramWithStepResult = {
|
|
75
|
+
outcome: 'completed';
|
|
76
|
+
report: SerializedReport;
|
|
77
|
+
stepPath?: string;
|
|
78
|
+
} | {
|
|
79
|
+
outcome: 'timeout';
|
|
80
|
+
timeoutMs: number;
|
|
81
|
+
} | {
|
|
82
|
+
outcome: 'crashed';
|
|
83
|
+
exitCode: number | null;
|
|
84
|
+
detail: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Execute `code` with `verify --check --step` in ONE bounded child process: a single spawn yields
|
|
88
|
+
* both the `auto`-signal report (parsed from stdout) and the STEP the judge renders — one kernel
|
|
89
|
+
* boot, not the two a `runProgram` + `exportProgram` pair would cost. The caller owns `stepOutPath`;
|
|
90
|
+
* only the temp program dir is cleaned up.
|
|
91
|
+
*/
|
|
92
|
+
export declare function runProgramWithStep(code: string, stepOutPath: string, opts?: RunProgramOptions): Promise<RunProgramWithStepResult>;
|
|
72
93
|
/**
|
|
73
94
|
* Execute `code` and export artifacts to `outDir`. Artifacts persist in `outDir` (the caller owns
|
|
74
95
|
* it); only the temp program directory is cleaned up. Returns the written paths and any errors.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brepjs-verify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Agent skill + verify/preview tooling for authoring parametric brepjs CAD code",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -47,6 +47,9 @@
|
|
|
47
47
|
"test": "vitest run",
|
|
48
48
|
"eval": "tsx bench/run.ts",
|
|
49
49
|
"eval:live": "tsx bench/live.ts",
|
|
50
|
+
"eval:push": "tsx bench/pushScorecard.ts",
|
|
51
|
+
"eval:combine": "tsx bench/combineShards.ts",
|
|
52
|
+
"eval:dataset:sync": "tsx bench/syncDataset.ts",
|
|
50
53
|
"smoke": "node dist/cli/main.js verify tests/fixtures/validBox.brep.ts | node -e \"const r=JSON.parse(require('fs').readFileSync(0,'utf8'));if(r.ok!==true||!(r.measurements.volume>0))process.exit(1)\"",
|
|
51
54
|
"smoke:standalone": "node scripts/smokeStandalone.mjs",
|
|
52
55
|
"prepack": "npm run build"
|
|
@@ -63,6 +66,10 @@
|
|
|
63
66
|
},
|
|
64
67
|
"devDependencies": {
|
|
65
68
|
"@anthropic-ai/sdk": "0.102.0",
|
|
69
|
+
"@langfuse/client": "5.5.3",
|
|
70
|
+
"@langfuse/otel": "5.5.3",
|
|
71
|
+
"@langfuse/tracing": "5.5.3",
|
|
72
|
+
"@opentelemetry/sdk-node": "0.219.0",
|
|
66
73
|
"@react-three/drei": "^10.7.7",
|
|
67
74
|
"@react-three/fiber": "^9.6.1",
|
|
68
75
|
"@types/node": "25.9.2",
|
package/reference/llms-full.txt
CHANGED
|
@@ -736,10 +736,10 @@ import {
|
|
|
736
736
|
measureDistance
|
|
737
737
|
} from 'brepjs';
|
|
738
738
|
|
|
739
|
-
measureVolume(solid); // number
|
|
740
|
-
measureArea(face); // number
|
|
741
|
-
measureLength(edge); // number
|
|
742
|
-
measureDistance(shape1, shape2);
|
|
739
|
+
measureVolume(solid); // Result<number> — unwrap()
|
|
740
|
+
measureArea(face); // Result<number> — unwrap()
|
|
741
|
+
measureLength(edge); // Result<number> — unwrap()
|
|
742
|
+
measureDistance(shape1, shape2); // Result<number> — unwrap()
|
|
743
743
|
```
|
|
744
744
|
|
|
745
745
|
Functional measurement API:
|