praxis-agent 0.46.5 → 0.48.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 +7 -1
- package/dist/cli-runtime.d.ts +2 -0
- package/dist/cli-runtime.js +37 -2
- package/dist/evals/eval-contract.d.ts +102 -0
- package/dist/evals/eval-contract.js +57 -0
- package/dist/evals/eval-graders.d.ts +6 -0
- package/dist/evals/eval-graders.js +94 -0
- package/dist/evals/project-eval-comparison.d.ts +76 -0
- package/dist/evals/project-eval-comparison.js +513 -0
- package/dist/evals/project-eval-runner.d.ts +67 -0
- package/dist/evals/project-eval-runner.js +393 -0
- package/dist/evals/project-eval-schema.d.ts +35 -0
- package/dist/evals/project-eval-schema.js +395 -0
- package/dist/evals/project-eval-workspace.d.ts +30 -0
- package/dist/evals/project-eval-workspace.js +108 -0
- package/dist/evals/project-eval.d.ts +99 -0
- package/dist/evals/project-eval.js +238 -0
- package/dist/platform/bounded-process-runner.d.ts +2 -0
- package/dist/platform/bounded-process-runner.js +4 -2
- package/dist/plugins/claude-plugin-eval-graders.d.ts +2 -33
- package/dist/plugins/claude-plugin-eval-graders.js +7 -66
- package/dist/plugins/claude-plugin-eval-runner.d.ts +4 -29
- package/dist/plugins/claude-plugin-eval-runner.js +3 -58
- package/dist/plugins/claude-plugin-eval-schema.d.ts +9 -34
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -100,6 +100,12 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
100
100
|
|
|
101
101
|
## What Praxis provides
|
|
102
102
|
|
|
103
|
+
- **Outcome-driven evaluation** — `praxis eval <target>` runs contained cases
|
|
104
|
+
in isolated workspaces, requires explicit verifier authorization, and writes
|
|
105
|
+
versioned artifacts locally; usage and cost remain explicitly available or
|
|
106
|
+
unknown. Separate runs can be compared with `praxis eval compare`; unknown
|
|
107
|
+
token/cost evidence produces null deltas, while the gate requires no pass-rate
|
|
108
|
+
or safety-rate regression and rejects incomplete safety evidence.
|
|
103
109
|
- **Local agent runtime** — C+ Quiet Operator responsive TUI with a linear
|
|
104
110
|
`❯` user / `⏺` assistant conversation, `✻` thinking activity, and `!` shell
|
|
105
111
|
composer grammar, compact stable tool rows, responsive density,
|
|
@@ -295,7 +301,7 @@ normal/low-capability full-frame p95 budgets of `<16.7/<33 ms`.
|
|
|
295
301
|
`npm run test:coverage` measures all production code under `src/**` with V8 and
|
|
296
302
|
enforces global floors of 79% statements, 70% branches, 85% functions, and 81% lines,
|
|
297
303
|
and rejects any production runtime module with zero covered statements (while allowing
|
|
298
|
-
type-only modules). `npm run test:fixtures` executes the
|
|
304
|
+
type-only modules). `npm run test:fixtures` executes the 68-behavior native contract; 60 behaviors
|
|
299
305
|
are qualified and 8 are explicitly excluded. `npm run verify:fixture-contracts`
|
|
300
306
|
performs the structural check and is part of `npm run check`.
|
|
301
307
|
`npm run test:core-completion` is retained as a compatibility alias for
|
package/dist/cli-runtime.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { launchTmuxWorktree } from './platform/tmux-worktree.js';
|
|
|
22
22
|
import { type CliControls, type CliRuntimeInfo, type CliElicitationRequest, type CliElicitationResult } from './cli/protocol.js';
|
|
23
23
|
import { executeProviderAuthCommand } from './cli/provider-auth-command.js';
|
|
24
24
|
import { type PluginEvalDependencies } from './plugins/claude-plugin-eval.js';
|
|
25
|
+
import { type ProjectEvalDependencies } from './evals/project-eval.js';
|
|
25
26
|
import { type SelfUpdateResult } from './maintenance/self-update.js';
|
|
26
27
|
export { parseContextEnvironment, parseProviderEnvironment };
|
|
27
28
|
export interface CliIO {
|
|
@@ -153,6 +154,7 @@ export interface CliDependencies extends InteractiveServiceFactory {
|
|
|
153
154
|
statePath?: string;
|
|
154
155
|
}): Promise<ModelProvider>;
|
|
155
156
|
pluginEval?: PluginEvalDependencies;
|
|
157
|
+
projectEval?: ProjectEvalDependencies;
|
|
156
158
|
runInteractive?(options: {
|
|
157
159
|
agent?: string;
|
|
158
160
|
controls?: CliControls;
|
package/dist/cli-runtime.js
CHANGED
|
@@ -70,6 +70,8 @@ import { executeProviderAuthCommand } from './cli/provider-auth-command.js';
|
|
|
70
70
|
import { describeClaudePlugin, initClaudePlugin, installClaudePlugin, loadClaudePlugins, readPluginRegistry, setClaudePluginEnabled, uninstallClaudePlugin, updateClaudePlugin, validateClaudePlugin, } from './plugins/claude-plugin-runtime.js';
|
|
71
71
|
import { addClaudeMarketplace, disableAllNativePlugins, installClaudeMarketplacePlugin, listClaudeMarketplaceAvailablePlugins, listNativePluginRecords, readClaudeKnownMarketplaces, removeClaudeMarketplace, setNativePluginEnabled, saveClaudePluginConfig, uninstallNativePlugin, updateClaudeMarketplace, updateNativePlugin, validateClaudeMarketplace, } from './plugins/claude-plugin-marketplace.js';
|
|
72
72
|
import { executeClaudePluginEvalCommand, PLUGIN_EVAL_HELP, } from './plugins/claude-plugin-eval.js';
|
|
73
|
+
import { executeProjectEvalCommand, PROJECT_EVAL_HELP, } from './evals/project-eval.js';
|
|
74
|
+
import { PROJECT_EVAL_COMPARE_HELP } from './evals/project-eval-comparison.js';
|
|
73
75
|
import { CLAUDE_PLUGIN_PRUNE_HELP, CLAUDE_PLUGIN_TAG_HELP, executeClaudePluginPrune, planClaudePluginPrune, tagClaudePlugin, } from './plugins/claude-plugin-maintenance.js';
|
|
74
76
|
import { formatDoctorReport, runDoctor } from './maintenance/doctor.js';
|
|
75
77
|
import { runSelfUpdate, } from './maintenance/self-update.js';
|
|
@@ -123,6 +125,7 @@ Usage:
|
|
|
123
125
|
praxis mcp <list|get|add|add-json|remove|reset-project-choices|login|logout|serve> ...
|
|
124
126
|
praxis auto-mode <config|defaults|critique>
|
|
125
127
|
praxis plugin|plugins <details|list|install|uninstall|enable|disable|update|init|prune|tag|validate|marketplace> ...
|
|
128
|
+
praxis eval [options] <target>
|
|
126
129
|
praxis doctor [--json]
|
|
127
130
|
praxis auth <status|set-key|login|logout> ...
|
|
128
131
|
praxis import [options] [source]
|
|
@@ -2282,7 +2285,7 @@ const defaultPluginEvalRuntimeFactory = {
|
|
|
2282
2285
|
dataPlane: options.dataPlane,
|
|
2283
2286
|
sessionPersistence: false,
|
|
2284
2287
|
maxTurns: options.maxTurns,
|
|
2285
|
-
pluginDirectories: [...options.pluginDirectories],
|
|
2288
|
+
pluginDirectories: [...(options.pluginDirectories ?? [])],
|
|
2286
2289
|
addDirectories: [...options.addDirs],
|
|
2287
2290
|
allowedTools: [...options.allowedTools],
|
|
2288
2291
|
disallowedTools: [],
|
|
@@ -2305,6 +2308,7 @@ const defaultPluginEvalRuntimeFactory = {
|
|
|
2305
2308
|
...(result.costUsd === undefined
|
|
2306
2309
|
? {}
|
|
2307
2310
|
: { costUsd: result.costUsd }),
|
|
2311
|
+
usage: result.usage,
|
|
2308
2312
|
};
|
|
2309
2313
|
},
|
|
2310
2314
|
close: () => service.close?.() ?? Promise.resolve(),
|
|
@@ -2523,6 +2527,11 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
|
|
|
2523
2527
|
runtimeFactory: defaultPluginEvalRuntimeFactory,
|
|
2524
2528
|
judge: defaultPluginEvalJudge,
|
|
2525
2529
|
},
|
|
2530
|
+
projectEval: {
|
|
2531
|
+
runtimeFactory: defaultPluginEvalRuntimeFactory,
|
|
2532
|
+
version: VERSION,
|
|
2533
|
+
configRoot: resolveDataPlaneRoot(),
|
|
2534
|
+
},
|
|
2526
2535
|
cliPath: entrypoint,
|
|
2527
2536
|
runInteractive: async ({ agent, controls, initialPrompt, resume, signal, }) => {
|
|
2528
2537
|
const { runInteractive } = await import('./cli/interactive.js');
|
|
@@ -4899,6 +4908,7 @@ async function executeTeamCommand(args, io, dependencies, signal) {
|
|
|
4899
4908
|
}
|
|
4900
4909
|
function specialCommandIndex(argv) {
|
|
4901
4910
|
const commands = new Set([
|
|
4911
|
+
'eval',
|
|
4902
4912
|
'team',
|
|
4903
4913
|
'plugin',
|
|
4904
4914
|
'plugins',
|
|
@@ -4963,7 +4973,9 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4963
4973
|
return executeTeamCommand(argv.slice(commandIndex), io, dependencies, signal);
|
|
4964
4974
|
}
|
|
4965
4975
|
const specialPrefix = commandIndex > 0 &&
|
|
4966
|
-
(argv[commandIndex] === 'plugin' ||
|
|
4976
|
+
(argv[commandIndex] === 'plugin' ||
|
|
4977
|
+
argv[commandIndex] === 'plugins' ||
|
|
4978
|
+
argv[commandIndex] === 'eval')
|
|
4967
4979
|
? parseCliInvocation([...argv.slice(0, commandIndex), '__probe__'])
|
|
4968
4980
|
: undefined;
|
|
4969
4981
|
const special = commandIndex >= 0
|
|
@@ -4971,6 +4983,29 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4971
4983
|
args: argv.slice(commandIndex),
|
|
4972
4984
|
}
|
|
4973
4985
|
: { args: [...argv] };
|
|
4986
|
+
if (special.args[0] === 'eval') {
|
|
4987
|
+
if (special.args[1] === 'compare' &&
|
|
4988
|
+
special.args
|
|
4989
|
+
.slice(2)
|
|
4990
|
+
.some((value) => value === '-h' || value === '--help')) {
|
|
4991
|
+
io.stdout(PROJECT_EVAL_COMPARE_HELP);
|
|
4992
|
+
return 0;
|
|
4993
|
+
}
|
|
4994
|
+
if (special.args
|
|
4995
|
+
.slice(1)
|
|
4996
|
+
.some((value) => value === '-h' || value === '--help')) {
|
|
4997
|
+
io.stdout(PROJECT_EVAL_HELP);
|
|
4998
|
+
return 0;
|
|
4999
|
+
}
|
|
5000
|
+
if (!dependencies.projectEval)
|
|
5001
|
+
throw new Error('Project eval unavailable');
|
|
5002
|
+
return executeProjectEvalCommand([
|
|
5003
|
+
...(specialPrefix?.model === undefined
|
|
5004
|
+
? []
|
|
5005
|
+
: ['--model', specialPrefix.model]),
|
|
5006
|
+
...special.args.slice(1),
|
|
5007
|
+
], io, dependencies.projectEval, process.cwd(), signal);
|
|
5008
|
+
}
|
|
4974
5009
|
if ((special.args[0] === 'plugin' || special.args[0] === 'plugins') &&
|
|
4975
5010
|
special.args[1] === 'eval') {
|
|
4976
5011
|
if (!dependencies.pluginEval)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { ModelUsage, RuntimeEvent } from '../core/runtime.js';
|
|
2
|
+
import type { DataPlane } from '../persistence/data-plane.js';
|
|
3
|
+
export interface EvalRuntime {
|
|
4
|
+
run(prompt: string, signal: AbortSignal): Promise<{
|
|
5
|
+
text: string;
|
|
6
|
+
turns: number;
|
|
7
|
+
costUsd?: number;
|
|
8
|
+
usage?: ModelUsage;
|
|
9
|
+
}>;
|
|
10
|
+
close?(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface EvalRuntimeFactoryOptions {
|
|
13
|
+
dataPlane: DataPlane;
|
|
14
|
+
cwd: string;
|
|
15
|
+
configRoot: string;
|
|
16
|
+
home: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
maxTurns: number;
|
|
19
|
+
pluginDirectories?: readonly string[];
|
|
20
|
+
allowedTools: readonly string[];
|
|
21
|
+
appendSystemPrompt?: string;
|
|
22
|
+
historyFile?: string;
|
|
23
|
+
addDirs: readonly string[];
|
|
24
|
+
env: Readonly<Record<string, string>>;
|
|
25
|
+
eventSink(event: RuntimeEvent): void;
|
|
26
|
+
}
|
|
27
|
+
export interface EvalRuntimeFactory {
|
|
28
|
+
create(options: EvalRuntimeFactoryOptions): Promise<EvalRuntime>;
|
|
29
|
+
}
|
|
30
|
+
export interface EvalTraceEvent {
|
|
31
|
+
type: string;
|
|
32
|
+
tool?: string;
|
|
33
|
+
input?: Record<string, unknown>;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
export type EvalFocus = 'last_message' | 'trace' | 'files' | {
|
|
37
|
+
source: 'file';
|
|
38
|
+
path: string;
|
|
39
|
+
};
|
|
40
|
+
export type EvalArm = 'with-only' | 'both';
|
|
41
|
+
export type EvalToolMatch = string | {
|
|
42
|
+
tool: string;
|
|
43
|
+
input_match?: Record<string, unknown>;
|
|
44
|
+
};
|
|
45
|
+
export interface EvalGrader {
|
|
46
|
+
name: string;
|
|
47
|
+
weight: number;
|
|
48
|
+
arm?: EvalArm;
|
|
49
|
+
}
|
|
50
|
+
export type EvalGraderBase = EvalGrader;
|
|
51
|
+
export type EvalDeterministicGrader = (EvalGrader & {
|
|
52
|
+
type: 'regex';
|
|
53
|
+
target: EvalFocus;
|
|
54
|
+
pattern: string;
|
|
55
|
+
flags: string;
|
|
56
|
+
match: 'contains' | 'not_contains' | `count:${number}`;
|
|
57
|
+
}) | (EvalGrader & {
|
|
58
|
+
type: 'tool_order';
|
|
59
|
+
before: EvalToolMatch;
|
|
60
|
+
after: EvalToolMatch;
|
|
61
|
+
}) | (EvalGrader & {
|
|
62
|
+
type: 'tool_used';
|
|
63
|
+
tool: string;
|
|
64
|
+
input_match?: Record<string, unknown>;
|
|
65
|
+
min: number;
|
|
66
|
+
max: number;
|
|
67
|
+
}) | (EvalGrader & {
|
|
68
|
+
type: 'file_exists';
|
|
69
|
+
path: string;
|
|
70
|
+
exists: boolean;
|
|
71
|
+
});
|
|
72
|
+
export interface EvalRunArtifacts {
|
|
73
|
+
lastMessage: string;
|
|
74
|
+
trace: readonly EvalTraceEvent[];
|
|
75
|
+
cwd: string;
|
|
76
|
+
}
|
|
77
|
+
export interface EvalGraderResult {
|
|
78
|
+
name: string;
|
|
79
|
+
passed: boolean;
|
|
80
|
+
weight: number;
|
|
81
|
+
explanation: string;
|
|
82
|
+
judge_votes?: readonly boolean[];
|
|
83
|
+
evidence?: string;
|
|
84
|
+
with_only?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface EvalJudge {
|
|
87
|
+
vote(request: {
|
|
88
|
+
criteria: string;
|
|
89
|
+
focus: string;
|
|
90
|
+
baseline?: string;
|
|
91
|
+
model: string;
|
|
92
|
+
signal?: AbortSignal;
|
|
93
|
+
}): Promise<{
|
|
94
|
+
passed: boolean;
|
|
95
|
+
explanation?: string;
|
|
96
|
+
costUsd: number;
|
|
97
|
+
}>;
|
|
98
|
+
}
|
|
99
|
+
export declare const DEFAULT_EVAL_ALLOWED_TOOLS: readonly ["Read", "Glob", "Grep", "Skill"];
|
|
100
|
+
export declare function resolveEvalAllowedTools(requested: readonly string[], operatorGrants: readonly string[]): string[];
|
|
101
|
+
export declare function normalizeEvalTraceEvent(event: RuntimeEvent): EvalTraceEvent;
|
|
102
|
+
//# sourceMappingURL=eval-contract.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const DEFAULT_EVAL_ALLOWED_TOOLS = [
|
|
2
|
+
'Read',
|
|
3
|
+
'Glob',
|
|
4
|
+
'Grep',
|
|
5
|
+
'Skill',
|
|
6
|
+
];
|
|
7
|
+
function toolName(rule) {
|
|
8
|
+
const i = rule.indexOf('(');
|
|
9
|
+
return i < 0 ? rule : rule.slice(0, i);
|
|
10
|
+
}
|
|
11
|
+
function gatedTool(rule) {
|
|
12
|
+
const name = toolName(rule);
|
|
13
|
+
return (['Bash', 'Write', 'Edit', 'NotebookEdit', 'WebFetch', 'WebSearch'].includes(name) || name.startsWith('mcp__'));
|
|
14
|
+
}
|
|
15
|
+
function grants(requested, allowed) {
|
|
16
|
+
const name = toolName(requested);
|
|
17
|
+
const wildcard = (pattern) => {
|
|
18
|
+
const parts = pattern.split('*');
|
|
19
|
+
if (parts.length === 1 || !requested.startsWith(parts[0] ?? ''))
|
|
20
|
+
return false;
|
|
21
|
+
let cursor = parts[0]?.length ?? 0;
|
|
22
|
+
for (const part of parts.slice(1, -1)) {
|
|
23
|
+
const index = requested.indexOf(part, cursor);
|
|
24
|
+
if (index < 0)
|
|
25
|
+
return false;
|
|
26
|
+
cursor = index + part.length;
|
|
27
|
+
}
|
|
28
|
+
const last = parts.at(-1) ?? '';
|
|
29
|
+
return last.length === 0 || requested.slice(cursor).endsWith(last);
|
|
30
|
+
};
|
|
31
|
+
return allowed.some((grant) => grant === requested || grant === name || wildcard(grant));
|
|
32
|
+
}
|
|
33
|
+
export function resolveEvalAllowedTools(requested, operatorGrants) {
|
|
34
|
+
const selected = requested.length ? requested : DEFAULT_EVAL_ALLOWED_TOOLS;
|
|
35
|
+
for (const rule of selected)
|
|
36
|
+
if (gatedTool(rule) && !grants(rule, operatorGrants))
|
|
37
|
+
throw new Error(`Eval case requests gated tool ${rule}; grant it with --allow-tools`);
|
|
38
|
+
return [...new Set(selected)];
|
|
39
|
+
}
|
|
40
|
+
export function normalizeEvalTraceEvent(event) {
|
|
41
|
+
if (event.type === 'tool-call')
|
|
42
|
+
return {
|
|
43
|
+
type: 'tool-call',
|
|
44
|
+
tool: event.call.name,
|
|
45
|
+
input: event.call.input,
|
|
46
|
+
id: event.call.id,
|
|
47
|
+
};
|
|
48
|
+
if (event.type === 'tool-result')
|
|
49
|
+
return {
|
|
50
|
+
type: 'tool-result',
|
|
51
|
+
callId: event.callId,
|
|
52
|
+
content: event.content,
|
|
53
|
+
isError: event.isError,
|
|
54
|
+
};
|
|
55
|
+
return event;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=eval-contract.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type EvalDeterministicGrader, type EvalGraderResult, type EvalRunArtifacts } from './eval-contract.js';
|
|
2
|
+
export declare function gradeDeterministicEvalRun(options: {
|
|
3
|
+
graders: readonly EvalDeterministicGrader[];
|
|
4
|
+
artifacts: EvalRunArtifacts;
|
|
5
|
+
}): Promise<EvalGraderResult[]>;
|
|
6
|
+
//# sourceMappingURL=eval-graders.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { glob, readFile, realpath } from 'node:fs/promises';
|
|
2
|
+
import { resolve, sep } from 'node:path';
|
|
3
|
+
import { minimatch } from 'minimatch';
|
|
4
|
+
function subset(actual, expected) {
|
|
5
|
+
if (expected && typeof expected === 'object' && !Array.isArray(expected)) {
|
|
6
|
+
if (!actual || typeof actual !== 'object' || Array.isArray(actual))
|
|
7
|
+
return false;
|
|
8
|
+
return Object.entries(expected).every(([key, value]) => subset(actual[key], value));
|
|
9
|
+
}
|
|
10
|
+
return Object.is(actual, expected);
|
|
11
|
+
}
|
|
12
|
+
function matches(event, match) {
|
|
13
|
+
const selected = typeof match === 'string' ? { tool: match } : match;
|
|
14
|
+
return (event.type === 'tool-call' &&
|
|
15
|
+
event.tool === selected.tool &&
|
|
16
|
+
(!selected.input_match || subset(event.input, selected.input_match)));
|
|
17
|
+
}
|
|
18
|
+
async function containedPath(root, candidate) {
|
|
19
|
+
const rootPath = await realpath(resolve(root));
|
|
20
|
+
const path = await realpath(resolve(rootPath, candidate));
|
|
21
|
+
if (path !== rootPath && !path.startsWith(`${rootPath}${sep}`))
|
|
22
|
+
throw new Error(`Grader path escapes workspace: ${candidate}`);
|
|
23
|
+
return path;
|
|
24
|
+
}
|
|
25
|
+
async function focusText(focus, artifacts) {
|
|
26
|
+
if (focus === 'last_message')
|
|
27
|
+
return artifacts.lastMessage;
|
|
28
|
+
if (focus === 'trace')
|
|
29
|
+
return artifacts.trace.map((item) => JSON.stringify(item)).join('\n');
|
|
30
|
+
if (focus === 'files') {
|
|
31
|
+
const names = [];
|
|
32
|
+
for await (const path of glob('**/*', {
|
|
33
|
+
cwd: artifacts.cwd,
|
|
34
|
+
exclude: ['node_modules/**', '.git/**'],
|
|
35
|
+
}))
|
|
36
|
+
names.push(path);
|
|
37
|
+
return names.sort().join('\n');
|
|
38
|
+
}
|
|
39
|
+
return readFile(await containedPath(artifacts.cwd, focus.path), 'utf8');
|
|
40
|
+
}
|
|
41
|
+
export async function gradeDeterministicEvalRun(options) {
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const grader of options.graders) {
|
|
44
|
+
let passed = false;
|
|
45
|
+
let evidence = '';
|
|
46
|
+
if (grader.type === 'regex') {
|
|
47
|
+
const target = await focusText(grader.target, options.artifacts);
|
|
48
|
+
const expression = new RegExp(grader.pattern, grader.flags.includes('g') ? grader.flags : `${grader.flags}g`);
|
|
49
|
+
const count = [...target.matchAll(expression)].length;
|
|
50
|
+
passed =
|
|
51
|
+
grader.match === 'contains'
|
|
52
|
+
? count > 0
|
|
53
|
+
: grader.match === 'not_contains'
|
|
54
|
+
? count === 0
|
|
55
|
+
: count === Number(grader.match.slice(6));
|
|
56
|
+
evidence = `matches=${count}`;
|
|
57
|
+
}
|
|
58
|
+
else if (grader.type === 'tool_used') {
|
|
59
|
+
const count = options.artifacts.trace.filter((event) => matches(event, {
|
|
60
|
+
tool: grader.tool,
|
|
61
|
+
...(grader.input_match ? { input_match: grader.input_match } : {}),
|
|
62
|
+
})).length;
|
|
63
|
+
passed = count >= grader.min && count <= grader.max;
|
|
64
|
+
evidence = `uses=${count}`;
|
|
65
|
+
}
|
|
66
|
+
else if (grader.type === 'tool_order') {
|
|
67
|
+
const before = options.artifacts.trace.findIndex((event) => matches(event, grader.before));
|
|
68
|
+
const after = options.artifacts.trace.findIndex((event, index) => index > before && matches(event, grader.after));
|
|
69
|
+
passed = before >= 0 && after > before;
|
|
70
|
+
evidence = `before=${before},after=${after}`;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
let count = 0;
|
|
74
|
+
for await (const path of glob('**/*', { cwd: options.artifacts.cwd })) {
|
|
75
|
+
if (!minimatch(path, grader.path))
|
|
76
|
+
continue;
|
|
77
|
+
await containedPath(options.artifacts.cwd, path);
|
|
78
|
+
count += 1;
|
|
79
|
+
}
|
|
80
|
+
passed = grader.exists ? count > 0 : count === 0;
|
|
81
|
+
evidence = `files=${count}`;
|
|
82
|
+
}
|
|
83
|
+
results.push({
|
|
84
|
+
name: grader.name,
|
|
85
|
+
passed,
|
|
86
|
+
weight: grader.weight,
|
|
87
|
+
explanation: passed ? 'passed' : 'failed',
|
|
88
|
+
evidence,
|
|
89
|
+
...(grader.arm === 'with-only' ? { with_only: true } : {}),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=eval-graders.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ProjectEvalAggregate } from './project-eval.js';
|
|
2
|
+
export declare const PROJECT_EVAL_COMPARE_HELP = "Usage: praxis eval compare [options]\n\nCompare two completed project evaluation aggregate artifacts.\n\nOptions:\n --baseline <aggregate-result.json> Baseline aggregate artifact\n --baseline-name <name> Name shown for the baseline\n --candidate <aggregate-result.json> Candidate aggregate artifact\n --candidate-name <name> Name shown for the candidate\n --output-dir <dir> Write comparison-result.json here\n --json Print exactly one comparison JSON value\n -h, --help Display help";
|
|
3
|
+
export interface ProjectEvalCompareOptions {
|
|
4
|
+
baseline?: string;
|
|
5
|
+
baselineName?: string;
|
|
6
|
+
candidate?: string;
|
|
7
|
+
candidateName?: string;
|
|
8
|
+
outputDir?: string;
|
|
9
|
+
json: boolean;
|
|
10
|
+
help?: true;
|
|
11
|
+
}
|
|
12
|
+
interface LoadedAggregate {
|
|
13
|
+
aggregate: ProjectEvalAggregate;
|
|
14
|
+
sourcePath: string;
|
|
15
|
+
safetyKnown: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ProjectEvalComparisonMetric<T = number | null> {
|
|
18
|
+
baseline: T;
|
|
19
|
+
candidate: T;
|
|
20
|
+
delta: T;
|
|
21
|
+
}
|
|
22
|
+
export interface ProjectEvalComparisonResult {
|
|
23
|
+
schema_version: '1.0';
|
|
24
|
+
baseline: {
|
|
25
|
+
name: string;
|
|
26
|
+
source_path: string;
|
|
27
|
+
version: string;
|
|
28
|
+
model: string | null;
|
|
29
|
+
};
|
|
30
|
+
candidate: {
|
|
31
|
+
name: string;
|
|
32
|
+
source_path: string;
|
|
33
|
+
version: string;
|
|
34
|
+
model: string | null;
|
|
35
|
+
};
|
|
36
|
+
comparable_run_count: number;
|
|
37
|
+
passed: boolean;
|
|
38
|
+
regressions: readonly {
|
|
39
|
+
case: string;
|
|
40
|
+
run: number;
|
|
41
|
+
baseline_passed: boolean;
|
|
42
|
+
candidate_passed: boolean;
|
|
43
|
+
}[];
|
|
44
|
+
metrics: {
|
|
45
|
+
pass_rate: ProjectEvalComparisonMetric<number>;
|
|
46
|
+
safety_pass_rate: ProjectEvalComparisonMetric<number | null>;
|
|
47
|
+
average_turns: ProjectEvalComparisonMetric<number>;
|
|
48
|
+
input_tokens: ProjectEvalComparisonMetric<number | null>;
|
|
49
|
+
output_tokens: ProjectEvalComparisonMetric<number | null>;
|
|
50
|
+
cache_read_input_tokens: ProjectEvalComparisonMetric<number | null>;
|
|
51
|
+
cache_creation_input_tokens: ProjectEvalComparisonMetric<number | null>;
|
|
52
|
+
known_cost_total_usd: ProjectEvalComparisonMetric<number | null>;
|
|
53
|
+
average_duration_ms: ProjectEvalComparisonMetric<number>;
|
|
54
|
+
permission_decisions: {
|
|
55
|
+
allow: ProjectEvalComparisonMetric<number | null>;
|
|
56
|
+
ask: ProjectEvalComparisonMetric<number | null>;
|
|
57
|
+
deny: ProjectEvalComparisonMetric<number | null>;
|
|
58
|
+
};
|
|
59
|
+
tool_errors: ProjectEvalComparisonMetric<number | null>;
|
|
60
|
+
retries: ProjectEvalComparisonMetric<number | null>;
|
|
61
|
+
terminations: {
|
|
62
|
+
completed: ProjectEvalComparisonMetric<number>;
|
|
63
|
+
timeout: ProjectEvalComparisonMetric<number>;
|
|
64
|
+
interrupted: ProjectEvalComparisonMetric<number>;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export declare function parseProjectEvalCompareOptions(argv: readonly string[]): ProjectEvalCompareOptions;
|
|
69
|
+
export declare function loadProjectEvalAggregate(inputPath: string, callerCwd?: string): Promise<LoadedAggregate>;
|
|
70
|
+
export declare function compareProjectEvalAggregates(baseline: LoadedAggregate, candidate: LoadedAggregate, baselineName: string, candidateName: string): ProjectEvalComparisonResult;
|
|
71
|
+
export declare function executeProjectEvalCompareCommand(argv: readonly string[], io: {
|
|
72
|
+
stdout(message: string): void;
|
|
73
|
+
stderr(message: string): void;
|
|
74
|
+
}, callerCwd?: string, signal?: AbortSignal): Promise<number>;
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=project-eval-comparison.d.ts.map
|