praxis-agent 0.65.0 → 0.66.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/README.md +12 -7
- package/dist/build-identity.json +1 -0
- package/dist/cli-runtime.js +2 -0
- package/dist/evals/project-eval-comparison.d.ts +4 -1
- package/dist/evals/project-eval-comparison.js +234 -78
- package/dist/evals/project-eval-identity.d.ts +4 -1
- package/dist/evals/project-eval-identity.js +9 -2
- package/dist/evals/project-eval-runner.d.ts +24 -2
- package/dist/evals/project-eval-runner.js +118 -59
- package/dist/evals/project-eval-schema.d.ts +8 -2
- package/dist/evals/project-eval-schema.js +27 -11
- package/dist/evals/project-eval.d.ts +22 -2
- package/dist/evals/project-eval.js +34 -1
- package/dist/platform/praxis-build-identity.d.ts +14 -0
- package/dist/platform/praxis-build-identity.js +204 -0
- package/package.json +2 -2
|
@@ -7,7 +7,7 @@ import { createProjectEvalIdentity, } from './project-eval-identity.js';
|
|
|
7
7
|
import { cleanupProjectEvalWorkspace, createProjectEvalWorkspace, diffProjectEvalWorkspace, } from './project-eval-workspace.js';
|
|
8
8
|
import { minimatch } from 'minimatch';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
-
const
|
|
10
|
+
export const PROJECT_EVAL_SAFETY_CHECKS = [
|
|
11
11
|
'trace-bounds',
|
|
12
12
|
'runtime-close',
|
|
13
13
|
'workspace-manifest',
|
|
@@ -33,7 +33,7 @@ function runEvidence(trace, checks) {
|
|
|
33
33
|
retries += 1;
|
|
34
34
|
}
|
|
35
35
|
return {
|
|
36
|
-
safety_passed:
|
|
36
|
+
safety_passed: PROJECT_EVAL_SAFETY_CHECKS.every((name) => checks.find((check) => check.name === name)?.passed === true),
|
|
37
37
|
permission_decisions,
|
|
38
38
|
tool_errors,
|
|
39
39
|
retries,
|
|
@@ -144,6 +144,7 @@ export async function runProjectEvalCase(options) {
|
|
|
144
144
|
effectiveTools: allowedTools,
|
|
145
145
|
runVerification: options.runVerification ?? false,
|
|
146
146
|
praxisVersion: options.version,
|
|
147
|
+
buildIdentity: options.buildIdentity,
|
|
147
148
|
});
|
|
148
149
|
}
|
|
149
150
|
catch (error) {
|
|
@@ -224,67 +225,114 @@ export async function runProjectEvalCase(options) {
|
|
|
224
225
|
cleanupErrors.push(`Runtime cleanup failed: ${runtimeCloseError}`);
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
228
|
+
const addNotRunVerifier = (verifier, reason) => {
|
|
229
|
+
verifications.push({
|
|
230
|
+
schema_version: '1.1',
|
|
231
|
+
name: verifier.name,
|
|
232
|
+
required: true,
|
|
233
|
+
expect: 'pass',
|
|
234
|
+
status: 'not_run',
|
|
235
|
+
command: verifier.command,
|
|
236
|
+
argv: [...verifier.args],
|
|
237
|
+
exit_code: null,
|
|
238
|
+
timed_out: false,
|
|
239
|
+
stdout: '',
|
|
240
|
+
stderr: '',
|
|
241
|
+
truncated: false,
|
|
242
|
+
duration_ms: 0,
|
|
243
|
+
passed: false,
|
|
244
|
+
error: reason,
|
|
245
|
+
});
|
|
246
|
+
verifierChecks.push(check(`verifier:${verifier.name}`, false, reason));
|
|
247
|
+
};
|
|
248
|
+
let verifierInterrupted = false;
|
|
249
|
+
for (const verifier of options.case.verification) {
|
|
250
|
+
if (verifierInterrupted ||
|
|
251
|
+
!runtimeCompleted ||
|
|
252
|
+
runtimeCloseError ||
|
|
253
|
+
termination) {
|
|
254
|
+
addNotRunVerifier(verifier, verifierInterrupted
|
|
255
|
+
? 'Verifier not run: previous verifier interrupted'
|
|
256
|
+
: runtimeCloseError
|
|
257
|
+
? 'Verifier not run: runtime cleanup failed'
|
|
258
|
+
: termination
|
|
259
|
+
? `Verifier not run: runtime ${termination}`
|
|
260
|
+
: 'Verifier not run: runtime did not complete');
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const verifierStarted = Date.now();
|
|
264
|
+
try {
|
|
265
|
+
const result = await new BoundedProcessRunner({
|
|
266
|
+
cwd: workspace.cwd,
|
|
267
|
+
maxOutputBytes: 64 * 1024,
|
|
268
|
+
}).run({
|
|
269
|
+
command: verifier.command,
|
|
270
|
+
args: [...verifier.args],
|
|
271
|
+
cwd: workspace.cwd,
|
|
272
|
+
timeoutMs: verifier.timeoutSeconds * 1000,
|
|
273
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
274
|
+
env: verifierEnvironment(options.case.execution.env, workspace.home, workspace.root),
|
|
275
|
+
inheritEnvironment: false,
|
|
276
|
+
redactExplicitEnvironment: true,
|
|
277
|
+
});
|
|
278
|
+
const interrupted = options.signal?.aborted === true;
|
|
279
|
+
if (interrupted) {
|
|
280
|
+
termination = 'interrupted';
|
|
281
|
+
verifierInterrupted = true;
|
|
282
|
+
}
|
|
283
|
+
const passed = result.code === 0 && !result.timedOut && !interrupted;
|
|
284
|
+
const verifierError = interrupted
|
|
285
|
+
? 'Verifier interrupted'
|
|
286
|
+
: result.timedOut
|
|
246
287
|
? 'Verifier timed out'
|
|
247
288
|
: result.code === 0
|
|
248
289
|
? null
|
|
249
290
|
: `Verifier exited with code ${result.code}`;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
timed_out: false,
|
|
277
|
-
stdout: '',
|
|
278
|
-
stderr: '',
|
|
279
|
-
truncated: false,
|
|
280
|
-
duration_ms: Date.now() - verifierStarted,
|
|
281
|
-
passed: false,
|
|
282
|
-
error: message,
|
|
283
|
-
});
|
|
284
|
-
verifierChecks.push(check(`verifier:${verifier.name}`, false, message));
|
|
285
|
-
if (termination === 'interrupted')
|
|
286
|
-
break;
|
|
291
|
+
const status = passed ? 'passed' : 'failed';
|
|
292
|
+
verifications.push({
|
|
293
|
+
schema_version: '1.1',
|
|
294
|
+
name: verifier.name,
|
|
295
|
+
required: true,
|
|
296
|
+
expect: 'pass',
|
|
297
|
+
status,
|
|
298
|
+
command: verifier.command,
|
|
299
|
+
argv: [...verifier.args],
|
|
300
|
+
exit_code: result.code,
|
|
301
|
+
timed_out: result.timedOut,
|
|
302
|
+
stdout: result.stdout,
|
|
303
|
+
stderr: result.stderr,
|
|
304
|
+
truncated: result.truncated,
|
|
305
|
+
duration_ms: Date.now() - verifierStarted,
|
|
306
|
+
passed,
|
|
307
|
+
error: verifierError,
|
|
308
|
+
});
|
|
309
|
+
verifierChecks.push(check(`verifier:${verifier.name}`, passed, passed ? 'Verifier passed' : (verifierError ?? 'Verifier failed')));
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
const message = errorText(error);
|
|
313
|
+
const interrupted = options.signal?.aborted === true;
|
|
314
|
+
if (interrupted) {
|
|
315
|
+
termination = 'interrupted';
|
|
316
|
+
verifierInterrupted = true;
|
|
287
317
|
}
|
|
318
|
+
verifications.push({
|
|
319
|
+
schema_version: '1.1',
|
|
320
|
+
name: verifier.name,
|
|
321
|
+
required: true,
|
|
322
|
+
expect: 'pass',
|
|
323
|
+
status: 'failed',
|
|
324
|
+
command: verifier.command,
|
|
325
|
+
argv: [...verifier.args],
|
|
326
|
+
exit_code: null,
|
|
327
|
+
timed_out: false,
|
|
328
|
+
stdout: '',
|
|
329
|
+
stderr: '',
|
|
330
|
+
truncated: false,
|
|
331
|
+
duration_ms: Date.now() - verifierStarted,
|
|
332
|
+
passed: false,
|
|
333
|
+
error: interrupted ? 'Verifier interrupted' : message,
|
|
334
|
+
});
|
|
335
|
+
verifierChecks.push(check(`verifier:${verifier.name}`, false, interrupted ? 'Verifier interrupted' : message));
|
|
288
336
|
}
|
|
289
337
|
}
|
|
290
338
|
const verifiersPassed = verifierChecks.every((item) => item.passed);
|
|
@@ -374,6 +422,15 @@ export async function runProjectEvalCase(options) {
|
|
|
374
422
|
: `Temporary workspace cleanup failed: ${tempCleanupError}`));
|
|
375
423
|
const passed = checks.every((item) => item.passed);
|
|
376
424
|
const evidence = runEvidence(trace, checks);
|
|
425
|
+
const verification = {
|
|
426
|
+
outcomes: verifications.map(({ name, status }) => ({
|
|
427
|
+
name,
|
|
428
|
+
required: true,
|
|
429
|
+
expect: 'pass',
|
|
430
|
+
status,
|
|
431
|
+
})),
|
|
432
|
+
satisfied: verifications.every((verification) => verification.status === 'passed'),
|
|
433
|
+
};
|
|
377
434
|
const primaryError = runtimeError ??
|
|
378
435
|
graderError ??
|
|
379
436
|
verifications.find((verification) => verification.error)?.error ??
|
|
@@ -381,8 +438,9 @@ export async function runProjectEvalCase(options) {
|
|
|
381
438
|
checks.find((item) => !item.passed)?.explanation ??
|
|
382
439
|
null;
|
|
383
440
|
const result = {
|
|
384
|
-
schema_version: '1.
|
|
441
|
+
schema_version: '1.2',
|
|
385
442
|
case: options.case.name,
|
|
443
|
+
risk: options.case.risk,
|
|
386
444
|
run: options.run,
|
|
387
445
|
version: options.version,
|
|
388
446
|
model: identity.model_id,
|
|
@@ -398,6 +456,7 @@ export async function runProjectEvalCase(options) {
|
|
|
398
456
|
...evidence,
|
|
399
457
|
graders,
|
|
400
458
|
checks,
|
|
459
|
+
verification,
|
|
401
460
|
artifacts: {
|
|
402
461
|
trace: 'trace.jsonl',
|
|
403
462
|
workspace_diff: 'workspace-diff.json',
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { EvalDeterministicGrader } from './eval-contract.js';
|
|
2
|
-
export declare const PROJECT_EVAL_SCHEMA_VERSION = "1.
|
|
2
|
+
export declare const PROJECT_EVAL_SCHEMA_VERSION = "1.1";
|
|
3
|
+
export declare const PROJECT_EVAL_RISKS: readonly ["low", "medium", "high", "release"];
|
|
4
|
+
export type ProjectEvalRisk = (typeof PROJECT_EVAL_RISKS)[number];
|
|
5
|
+
export declare const PROJECT_EVAL_MAX_ITEMS = 256;
|
|
3
6
|
export interface ProjectEvalCase {
|
|
4
|
-
schemaVersion: '1.
|
|
7
|
+
schemaVersion: '1.1';
|
|
5
8
|
name: string;
|
|
9
|
+
risk: ProjectEvalRisk;
|
|
6
10
|
dir: string;
|
|
7
11
|
fixture: string;
|
|
8
12
|
tags: readonly string[];
|
|
@@ -21,6 +25,8 @@ export interface ProjectEvalCase {
|
|
|
21
25
|
command: string;
|
|
22
26
|
args: readonly string[];
|
|
23
27
|
timeoutSeconds: number;
|
|
28
|
+
required: true;
|
|
29
|
+
expect: 'pass';
|
|
24
30
|
}[];
|
|
25
31
|
graders: readonly EvalDeterministicGrader[];
|
|
26
32
|
expect: {
|
|
@@ -2,9 +2,10 @@ import { lstat, readFile, readdir, realpath } from 'node:fs/promises';
|
|
|
2
2
|
import { isAbsolute, join, resolve, sep } from 'node:path';
|
|
3
3
|
import { parse as parseYaml } from 'yaml';
|
|
4
4
|
import { Minimatch, minimatch } from 'minimatch';
|
|
5
|
-
export const PROJECT_EVAL_SCHEMA_VERSION = '1.
|
|
5
|
+
export const PROJECT_EVAL_SCHEMA_VERSION = '1.1';
|
|
6
|
+
export const PROJECT_EVAL_RISKS = ['low', 'medium', 'high', 'release'];
|
|
6
7
|
const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
7
|
-
const
|
|
8
|
+
export const PROJECT_EVAL_MAX_ITEMS = 256;
|
|
8
9
|
const MAX_STRING = 16 * 1024;
|
|
9
10
|
const MAX_BYTES = 1024 * 1024;
|
|
10
11
|
const DEFAULT_PROJECT_EVAL_ALLOWED_TOOLS = ['Read', 'Glob', 'Grep'];
|
|
@@ -17,14 +18,14 @@ function validateBounds(value, label = 'case', depth = 0, state = { nodes: 0 })
|
|
|
17
18
|
if (typeof value === 'string' && value.length > MAX_STRING)
|
|
18
19
|
throw new Error(`${label} contains oversized string`);
|
|
19
20
|
if (Array.isArray(value)) {
|
|
20
|
-
if (value.length >
|
|
21
|
+
if (value.length > PROJECT_EVAL_MAX_ITEMS)
|
|
21
22
|
throw new Error(`${label} contains oversized array`);
|
|
22
23
|
for (const item of value)
|
|
23
24
|
validateBounds(item, label, depth + 1, state);
|
|
24
25
|
}
|
|
25
26
|
else if (value && typeof value === 'object') {
|
|
26
27
|
const entries = Object.entries(value);
|
|
27
|
-
if (entries.length >
|
|
28
|
+
if (entries.length > PROJECT_EVAL_MAX_ITEMS)
|
|
28
29
|
throw new Error(`${label} contains oversized object`);
|
|
29
30
|
for (const [key, item] of entries) {
|
|
30
31
|
if (key.length > 256)
|
|
@@ -64,7 +65,7 @@ function list(v, label) {
|
|
|
64
65
|
if (v === undefined)
|
|
65
66
|
return [];
|
|
66
67
|
if (!Array.isArray(v) ||
|
|
67
|
-
v.length >
|
|
68
|
+
v.length > PROJECT_EVAL_MAX_ITEMS ||
|
|
68
69
|
v.some((x) => typeof x !== 'string' || !x || x.length > MAX_STRING))
|
|
69
70
|
throw new Error(`${label} must be a bounded string array`);
|
|
70
71
|
return v.map((x) => String(x));
|
|
@@ -216,6 +217,7 @@ export function parseProjectEvalCase(raw, dir) {
|
|
|
216
217
|
strict(r, [
|
|
217
218
|
'schema_version',
|
|
218
219
|
'name',
|
|
220
|
+
'risk',
|
|
219
221
|
'tags',
|
|
220
222
|
'runs',
|
|
221
223
|
'fixture',
|
|
@@ -224,11 +226,13 @@ export function parseProjectEvalCase(raw, dir) {
|
|
|
224
226
|
'graders',
|
|
225
227
|
'expect',
|
|
226
228
|
], 'case');
|
|
227
|
-
if (r.schema_version !== '1.
|
|
229
|
+
if (r.schema_version !== '1.1')
|
|
228
230
|
throw new Error(`Unsupported schema_version: ${String(r.schema_version)}`);
|
|
229
231
|
const name = str(r.name, 'name');
|
|
230
232
|
if (!NAME.test(name))
|
|
231
233
|
throw new Error('name must be a safe eval identifier');
|
|
234
|
+
if (!PROJECT_EVAL_RISKS.includes(r.risk))
|
|
235
|
+
throw new Error('risk must be one of: low, medium, high, release');
|
|
232
236
|
const ex = obj(r.execution, 'execution');
|
|
233
237
|
strict(ex, [
|
|
234
238
|
'prompt',
|
|
@@ -252,27 +256,39 @@ export function parseProjectEvalCase(raw, dir) {
|
|
|
252
256
|
if (!/^EVAL_[A-Z0-9_]+$/u.test(k) || typeof v !== 'string')
|
|
253
257
|
throw new Error(`Invalid eval environment variable: ${k}`);
|
|
254
258
|
const vr = r.verification === undefined ? [] : r.verification;
|
|
255
|
-
if (!Array.isArray(vr) || vr.length >
|
|
259
|
+
if (!Array.isArray(vr) || vr.length > PROJECT_EVAL_MAX_ITEMS)
|
|
256
260
|
throw new Error('verification must be a bounded array');
|
|
257
261
|
const verification = vr.map((x, i) => {
|
|
258
262
|
const q = obj(x, `verification[${i}]`);
|
|
259
|
-
strict(q, ['name', 'command', 'args', 'timeout_seconds'], `verification[${i}]`);
|
|
263
|
+
strict(q, ['name', 'command', 'args', 'timeout_seconds', 'required', 'expect'], `verification[${i}]`);
|
|
264
|
+
const verifierName = str(q.name, `verification[${i}].name`);
|
|
265
|
+
if (!NAME.test(verifierName))
|
|
266
|
+
throw new Error(`verification[${i}].name must be a safe eval identifier`);
|
|
267
|
+
if (q.required !== true)
|
|
268
|
+
throw new Error(`verification[${i}].required must be true`);
|
|
269
|
+
if (q.expect !== 'pass')
|
|
270
|
+
throw new Error(`verification[${i}].expect must be pass`);
|
|
260
271
|
return {
|
|
261
|
-
name:
|
|
272
|
+
name: verifierName,
|
|
262
273
|
command: str(q.command, `verification[${i}].command`),
|
|
263
274
|
args: list(q.args, `verification[${i}].args`),
|
|
264
275
|
timeoutSeconds: integer(q.timeout_seconds, `verification[${i}].timeout_seconds`, 120, 1, 3600),
|
|
276
|
+
required: true,
|
|
277
|
+
expect: 'pass',
|
|
265
278
|
};
|
|
266
279
|
});
|
|
280
|
+
if (new Set(verification.map((item) => item.name)).size !== verification.length)
|
|
281
|
+
throw new Error('Project eval verifier names must be unique');
|
|
267
282
|
const gradersRaw = r.graders === undefined ? [] : r.graders;
|
|
268
|
-
if (!Array.isArray(gradersRaw) || gradersRaw.length >
|
|
283
|
+
if (!Array.isArray(gradersRaw) || gradersRaw.length > PROJECT_EVAL_MAX_ITEMS)
|
|
269
284
|
throw new Error('graders must be a bounded array');
|
|
270
285
|
const graders = gradersRaw.map(projectGrader);
|
|
271
286
|
if (new Set(graders.map((item) => item.name)).size !== graders.length)
|
|
272
287
|
throw new Error('Project grader names must be unique');
|
|
273
288
|
return {
|
|
274
|
-
schemaVersion: '1.
|
|
289
|
+
schemaVersion: '1.1',
|
|
275
290
|
name,
|
|
291
|
+
risk: r.risk,
|
|
276
292
|
dir,
|
|
277
293
|
fixture,
|
|
278
294
|
tags: list(r.tags, 'tags'),
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { ModelUsage } from '../core/runtime.js';
|
|
2
2
|
import type { IdentifiedEvalRuntimeFactory } from './eval-contract.js';
|
|
3
|
-
import { type ProjectEvalRunResult } from './project-eval-runner.js';
|
|
3
|
+
import { type ProjectEvalCheckSummary, type ProjectEvalVerificationEvidence, type ProjectEvalRunResult } from './project-eval-runner.js';
|
|
4
|
+
export type { ProjectEvalCheckSummary, ProjectEvalVerifierOutcome, ProjectEvalVerificationEvidence, } from './project-eval-runner.js';
|
|
4
5
|
import { type ProjectEvalIdentity } from './project-eval-identity.js';
|
|
6
|
+
import type { PraxisBuildIdentity } from '../platform/praxis-build-identity.js';
|
|
5
7
|
export declare const PROJECT_EVAL_HELP = "Usage: praxis eval [options] <target>\n\nRun deterministic project outcome evaluations in isolated workspaces.\n\nOptions:\n --case <glob> Filter case names\n --tag <tag[,tag]> Filter tags; repeatable\n --runs <1..50> Override run count\n --model <model> Override model\n --allow-tools <rules> Grant gated tools; comma-separated and repeatable\n --run-verification Enable verifier subprocesses\n --output-dir <dir> Write artifacts to this directory\n --keep-temp Preserve temporary workspaces\n --json Print exactly one aggregate JSON value\n --verbose Print run progress to stderr\n -h, --help Display help\n\nUse praxis eval compare --help to compare two completed aggregate artifacts.";
|
|
6
8
|
export interface ProjectEvalDependencies {
|
|
7
9
|
runtimeFactory: IdentifiedEvalRuntimeFactory;
|
|
10
|
+
loadBuildIdentity: () => Promise<PraxisBuildIdentity>;
|
|
8
11
|
version?: string;
|
|
9
12
|
configRoot: string;
|
|
10
13
|
}
|
|
@@ -30,8 +33,10 @@ export interface ProjectEvalUsageTotals {
|
|
|
30
33
|
web_search_requests: number;
|
|
31
34
|
}
|
|
32
35
|
export interface ProjectEvalRunSummary {
|
|
36
|
+
schema_version: '1.2';
|
|
33
37
|
case: string;
|
|
34
38
|
run: number;
|
|
39
|
+
risk: ProjectEvalRunResult['risk'];
|
|
35
40
|
model: string;
|
|
36
41
|
passed: boolean;
|
|
37
42
|
score: 0 | 1;
|
|
@@ -52,9 +57,11 @@ export interface ProjectEvalRunSummary {
|
|
|
52
57
|
error: string | null;
|
|
53
58
|
artifact_dir: string;
|
|
54
59
|
identity: ProjectEvalIdentity;
|
|
60
|
+
verification: ProjectEvalVerificationEvidence;
|
|
61
|
+
checks: readonly ProjectEvalCheckSummary[];
|
|
55
62
|
}
|
|
56
63
|
export interface ProjectEvalAggregate {
|
|
57
|
-
schema_version: '1.
|
|
64
|
+
schema_version: '1.2';
|
|
58
65
|
version: string;
|
|
59
66
|
start: string;
|
|
60
67
|
duration_ms: number;
|
|
@@ -84,6 +91,19 @@ export interface ProjectEvalAggregate {
|
|
|
84
91
|
};
|
|
85
92
|
tool_errors: number;
|
|
86
93
|
retries: number;
|
|
94
|
+
verification_totals: {
|
|
95
|
+
declared: number;
|
|
96
|
+
passed: number;
|
|
97
|
+
failed: number;
|
|
98
|
+
not_run: number;
|
|
99
|
+
satisfied_runs: number;
|
|
100
|
+
unsatisfied_runs: number;
|
|
101
|
+
};
|
|
102
|
+
risk_tiers: Record<ProjectEvalRunResult['risk'], {
|
|
103
|
+
runs: number;
|
|
104
|
+
passed: number;
|
|
105
|
+
failed: number;
|
|
106
|
+
}>;
|
|
87
107
|
terminations: {
|
|
88
108
|
completed: number;
|
|
89
109
|
timeout: number;
|
|
@@ -118,8 +118,10 @@ function aggregateUsage(results) {
|
|
|
118
118
|
}
|
|
119
119
|
function runSummary(result, outputDirectory) {
|
|
120
120
|
return {
|
|
121
|
+
schema_version: '1.2',
|
|
121
122
|
case: result.case,
|
|
122
123
|
run: result.run,
|
|
124
|
+
risk: result.risk,
|
|
123
125
|
model: result.model,
|
|
124
126
|
passed: result.passed,
|
|
125
127
|
score: result.score,
|
|
@@ -136,6 +138,8 @@ function runSummary(result, outputDirectory) {
|
|
|
136
138
|
error: result.error,
|
|
137
139
|
artifact_dir: relative(outputDirectory, join(outputDirectory, result.case, `run-${result.run}`)).replaceAll('\\', '/'),
|
|
138
140
|
identity: result.identity,
|
|
141
|
+
verification: result.verification,
|
|
142
|
+
checks: result.checks.map(({ name, passed }) => ({ name, passed })),
|
|
139
143
|
};
|
|
140
144
|
}
|
|
141
145
|
export async function executeProjectEvalCommand(argv, io, dependencies, callerCwd = process.cwd(), signal) {
|
|
@@ -150,6 +154,7 @@ export async function executeProjectEvalCommand(argv, io, dependencies, callerCw
|
|
|
150
154
|
throw new Error('eval requires one target');
|
|
151
155
|
const target = resolve(callerCwd, options.target);
|
|
152
156
|
const cases = await discoverProjectEvalCases(target, options.caseGlob, options.tags);
|
|
157
|
+
const buildIdentity = await dependencies.loadBuildIdentity();
|
|
153
158
|
const outputDirectory = options.outputDir
|
|
154
159
|
? resolve(callerCwd, options.outputDir)
|
|
155
160
|
: join(dependencies.configRoot, 'evals', 'results', new Date().toISOString().replaceAll(':', '-'));
|
|
@@ -173,6 +178,7 @@ export async function executeProjectEvalCommand(argv, io, dependencies, callerCw
|
|
|
173
178
|
runVerification: options.runVerification,
|
|
174
179
|
outputDir: outputDirectory,
|
|
175
180
|
version: dependencies.version ?? 'unknown',
|
|
181
|
+
buildIdentity,
|
|
176
182
|
...(signal === undefined ? {} : { signal }),
|
|
177
183
|
});
|
|
178
184
|
results.push(result);
|
|
@@ -187,8 +193,33 @@ export async function executeProjectEvalCommand(argv, io, dependencies, callerCw
|
|
|
187
193
|
const passed = results.filter((result) => result.passed).length;
|
|
188
194
|
const knownCostResults = results.filter((result) => result.cost_known);
|
|
189
195
|
const summaries = results.map((result) => runSummary(result, outputDirectory));
|
|
196
|
+
const verificationTotals = {
|
|
197
|
+
declared: summaries.reduce((total, summary) => total + summary.verification.outcomes.length, 0),
|
|
198
|
+
passed: summaries.reduce((total, summary) => total +
|
|
199
|
+
summary.verification.outcomes.filter(({ status }) => status === 'passed').length, 0),
|
|
200
|
+
failed: summaries.reduce((total, summary) => total +
|
|
201
|
+
summary.verification.outcomes.filter(({ status }) => status === 'failed').length, 0),
|
|
202
|
+
not_run: summaries.reduce((total, summary) => total +
|
|
203
|
+
summary.verification.outcomes.filter(({ status }) => status === 'not_run').length, 0),
|
|
204
|
+
satisfied_runs: summaries.filter((summary) => summary.verification.satisfied).length,
|
|
205
|
+
unsatisfied_runs: summaries.filter((summary) => !summary.verification.satisfied).length,
|
|
206
|
+
};
|
|
207
|
+
const riskTiers = {
|
|
208
|
+
low: { runs: 0, passed: 0, failed: 0 },
|
|
209
|
+
medium: { runs: 0, passed: 0, failed: 0 },
|
|
210
|
+
high: { runs: 0, passed: 0, failed: 0 },
|
|
211
|
+
release: { runs: 0, passed: 0, failed: 0 },
|
|
212
|
+
};
|
|
213
|
+
for (const summary of summaries) {
|
|
214
|
+
const tier = riskTiers[summary.risk];
|
|
215
|
+
tier.runs += 1;
|
|
216
|
+
if (summary.passed)
|
|
217
|
+
tier.passed += 1;
|
|
218
|
+
else
|
|
219
|
+
tier.failed += 1;
|
|
220
|
+
}
|
|
190
221
|
const aggregate = {
|
|
191
|
-
schema_version: '1.
|
|
222
|
+
schema_version: '1.2',
|
|
192
223
|
version: dependencies.version ?? 'unknown',
|
|
193
224
|
start: new Date(started).toISOString(),
|
|
194
225
|
duration_ms: Date.now() - started,
|
|
@@ -224,6 +255,8 @@ export async function executeProjectEvalCommand(argv, io, dependencies, callerCw
|
|
|
224
255
|
},
|
|
225
256
|
tool_errors: results.reduce((total, result) => total + result.tool_errors, 0),
|
|
226
257
|
retries: results.reduce((total, result) => total + result.retries, 0),
|
|
258
|
+
verification_totals: verificationTotals,
|
|
259
|
+
risk_tiers: riskTiers,
|
|
227
260
|
terminations: {
|
|
228
261
|
completed: results.filter((result) => result.termination === null).length,
|
|
229
262
|
timeout: results.filter((result) => result.termination === 'timeout')
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const PRAXIS_BUILD_IDENTITY_SCHEMA_VERSION: "1.0";
|
|
2
|
+
export interface PraxisBuildIdentity {
|
|
3
|
+
schema_version: typeof PRAXIS_BUILD_IDENTITY_SCHEMA_VERSION;
|
|
4
|
+
source_revision: `git:${string}` | 'unavailable';
|
|
5
|
+
source_dirty: boolean | null;
|
|
6
|
+
artifact_sha256: `sha256:${string}`;
|
|
7
|
+
}
|
|
8
|
+
export declare function writePraxisBuildIdentity({ sourceRoot, outputRoot, }: {
|
|
9
|
+
sourceRoot: string;
|
|
10
|
+
outputRoot: string;
|
|
11
|
+
}): Promise<PraxisBuildIdentity>;
|
|
12
|
+
export declare function validatePraxisBuildIdentity(value: unknown): PraxisBuildIdentity;
|
|
13
|
+
export declare function loadPraxisBuildIdentity(outputRoot?: string): Promise<PraxisBuildIdentity>;
|
|
14
|
+
//# sourceMappingURL=praxis-build-identity.d.ts.map
|