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.
@@ -0,0 +1,238 @@
1
+ import { join, relative, resolve } from 'node:path';
2
+ import { writeFileAtomically } from '../platform/atomic-write.js';
3
+ import { runProjectEvalCase, } from './project-eval-runner.js';
4
+ import { executeProjectEvalCompareCommand } from './project-eval-comparison.js';
5
+ import { discoverProjectEvalCases } from './project-eval-schema.js';
6
+ export const PROJECT_EVAL_HELP = `Usage: praxis eval [options] <target>
7
+
8
+ Run deterministic project outcome evaluations in isolated workspaces.
9
+
10
+ Options:
11
+ --case <glob> Filter case names
12
+ --tag <tag[,tag]> Filter tags; repeatable
13
+ --runs <1..50> Override run count
14
+ --model <model> Override model
15
+ --allow-tools <rules> Grant gated tools; comma-separated and repeatable
16
+ --run-verification Enable verifier subprocesses
17
+ --output-dir <dir> Write artifacts to this directory
18
+ --keep-temp Preserve temporary workspaces
19
+ --json Print exactly one aggregate JSON value
20
+ --verbose Print run progress to stderr
21
+ -h, --help Display help
22
+
23
+ Use praxis eval compare --help to compare two completed aggregate artifacts.`;
24
+ function takeValue(argv, index, option) {
25
+ const value = argv[index + 1];
26
+ if (!value || value.startsWith('-'))
27
+ throw new Error(`${option} requires a value`);
28
+ return value;
29
+ }
30
+ function listValues(value, option) {
31
+ const values = value.split(',').map((item) => item.trim());
32
+ if (values.some((item) => item.length === 0))
33
+ throw new Error(`${option} contains an empty value`);
34
+ return values;
35
+ }
36
+ export function parseProjectEvalOptions(argv) {
37
+ const options = {
38
+ tags: [],
39
+ allowTools: [],
40
+ runVerification: false,
41
+ keepTemp: false,
42
+ json: false,
43
+ verbose: false,
44
+ };
45
+ const operands = [];
46
+ for (let index = 0; index < argv.length; index += 1) {
47
+ const value = argv[index];
48
+ if (!value)
49
+ continue;
50
+ if (value === '-h' || value === '--help')
51
+ return { ...options, help: true };
52
+ if (value === '--run-verification')
53
+ options.runVerification = true;
54
+ else if (value === '--keep-temp')
55
+ options.keepTemp = true;
56
+ else if (value === '--json')
57
+ options.json = true;
58
+ else if (value === '--verbose')
59
+ options.verbose = true;
60
+ else if (value === '--case' ||
61
+ value === '--model' ||
62
+ value === '--output-dir' ||
63
+ value === '--runs' ||
64
+ value === '--tag' ||
65
+ value === '--allow-tools') {
66
+ const selected = takeValue(argv, index, value);
67
+ index += 1;
68
+ if (value === '--case')
69
+ options.caseGlob = selected;
70
+ else if (value === '--model')
71
+ options.model = selected;
72
+ else if (value === '--output-dir')
73
+ options.outputDir = selected;
74
+ else if (value === '--runs') {
75
+ const runs = Number(selected);
76
+ if (!Number.isInteger(runs) || runs < 1 || runs > 50)
77
+ throw new Error('--runs must be an integer from 1 to 50');
78
+ options.runs = runs;
79
+ }
80
+ else if (value === '--tag')
81
+ options.tags.push(...listValues(selected, value));
82
+ else
83
+ options.allowTools.push(...listValues(selected, value));
84
+ }
85
+ else if (value.startsWith('-'))
86
+ throw new Error(`Unknown eval option: ${value}`);
87
+ else
88
+ operands.push(value);
89
+ }
90
+ if (operands.length !== 1)
91
+ throw new Error('eval requires one target');
92
+ const target = operands[0];
93
+ if (!target)
94
+ throw new Error('eval requires one target');
95
+ options.target = target;
96
+ return options;
97
+ }
98
+ function aggregateUsage(results) {
99
+ const totals = {
100
+ input_tokens: 0,
101
+ output_tokens: 0,
102
+ cache_read_input_tokens: 0,
103
+ cache_creation_input_tokens: 0,
104
+ web_search_requests: 0,
105
+ };
106
+ for (const result of results) {
107
+ if (!result.usage)
108
+ continue;
109
+ totals.input_tokens += result.usage.inputTokens;
110
+ totals.output_tokens += result.usage.outputTokens;
111
+ totals.cache_read_input_tokens += result.usage.cacheReadInputTokens ?? 0;
112
+ totals.cache_creation_input_tokens +=
113
+ result.usage.cacheCreationInputTokens ?? 0;
114
+ totals.web_search_requests += result.usage.webSearchRequests ?? 0;
115
+ }
116
+ return totals;
117
+ }
118
+ function runSummary(result, outputDirectory) {
119
+ return {
120
+ case: result.case,
121
+ run: result.run,
122
+ model: result.model,
123
+ passed: result.passed,
124
+ score: result.score,
125
+ turns: result.turns,
126
+ usage: result.usage,
127
+ cost_usd: result.cost_usd,
128
+ cost_known: result.cost_known,
129
+ duration_ms: result.duration_ms,
130
+ termination: result.termination,
131
+ safety_passed: result.safety_passed,
132
+ permission_decisions: result.permission_decisions,
133
+ tool_errors: result.tool_errors,
134
+ retries: result.retries,
135
+ error: result.error,
136
+ artifact_dir: relative(outputDirectory, join(outputDirectory, result.case, `run-${result.run}`)).replaceAll('\\', '/'),
137
+ };
138
+ }
139
+ export async function executeProjectEvalCommand(argv, io, dependencies, callerCwd = process.cwd(), signal) {
140
+ if (argv[0] === 'compare')
141
+ return executeProjectEvalCompareCommand(argv.slice(1), io, callerCwd, signal);
142
+ const options = parseProjectEvalOptions(argv);
143
+ if (options.help) {
144
+ io.stdout(PROJECT_EVAL_HELP);
145
+ return 0;
146
+ }
147
+ if (!options.target)
148
+ throw new Error('eval requires one target');
149
+ const target = resolve(callerCwd, options.target);
150
+ const cases = await discoverProjectEvalCases(target, options.caseGlob, options.tags);
151
+ const outputDirectory = options.outputDir
152
+ ? resolve(callerCwd, options.outputDir)
153
+ : join(dependencies.configRoot, 'evals', 'results', new Date().toISOString().replaceAll(':', '-'));
154
+ const plannedRunCount = cases.reduce((total, definition) => total + (options.runs ?? definition.runs), 0);
155
+ const results = [];
156
+ const started = Date.now();
157
+ let interrupted = signal?.aborted ?? false;
158
+ runs: for (const definition of cases) {
159
+ for (let runIndex = 1; runIndex <= (options.runs ?? definition.runs); runIndex += 1) {
160
+ if (signal?.aborted) {
161
+ interrupted = true;
162
+ break runs;
163
+ }
164
+ const result = await runProjectEvalCase({
165
+ case: definition,
166
+ factory: dependencies.runtimeFactory,
167
+ run: runIndex,
168
+ allowTools: options.allowTools,
169
+ ...(options.model === undefined ? {} : { model: options.model }),
170
+ keepTemp: options.keepTemp,
171
+ runVerification: options.runVerification,
172
+ outputDir: outputDirectory,
173
+ version: dependencies.version ?? 'unknown',
174
+ ...(signal === undefined ? {} : { signal }),
175
+ });
176
+ results.push(result);
177
+ if (options.verbose)
178
+ io.stderr(`${definition.name} run ${runIndex}: ${result.passed ? 'passed' : 'failed'}\n`);
179
+ if (signal?.aborted || result.termination === 'interrupted') {
180
+ interrupted = true;
181
+ break runs;
182
+ }
183
+ }
184
+ }
185
+ const passed = results.filter((result) => result.passed).length;
186
+ const knownCostResults = results.filter((result) => result.cost_known);
187
+ const aggregate = {
188
+ schema_version: '1.0',
189
+ version: dependencies.version ?? 'unknown',
190
+ start: new Date(started).toISOString(),
191
+ duration_ms: Date.now() - started,
192
+ target,
193
+ output_dir: outputDirectory,
194
+ model: options.model ?? null,
195
+ case_count: cases.length,
196
+ planned_run_count: plannedRunCount,
197
+ completed_run_count: results.length,
198
+ run_count: results.length,
199
+ passed,
200
+ failed: results.length - passed,
201
+ pass_rate: results.length === 0 ? 0 : passed / results.length,
202
+ total_turns: results.reduce((total, result) => total + result.turns, 0),
203
+ usage_totals: aggregateUsage(results),
204
+ usage_known_runs: results.filter((result) => result.usage !== null).length,
205
+ usage_unknown_runs: results.filter((result) => result.usage === null)
206
+ .length,
207
+ known_cost_total_usd: knownCostResults.length === 0
208
+ ? null
209
+ : knownCostResults.reduce((total, result) => total + (result.cost_usd ?? 0), 0),
210
+ known_cost_runs: knownCostResults.length,
211
+ unknown_cost_runs: results.length - knownCostResults.length,
212
+ safety_passed: results.filter((result) => result.safety_passed).length,
213
+ safety_failed: results.filter((result) => !result.safety_passed).length,
214
+ permission_decisions: {
215
+ allow: results.reduce((total, result) => total + result.permission_decisions.allow, 0),
216
+ ask: results.reduce((total, result) => total + result.permission_decisions.ask, 0),
217
+ deny: results.reduce((total, result) => total + result.permission_decisions.deny, 0),
218
+ },
219
+ tool_errors: results.reduce((total, result) => total + result.tool_errors, 0),
220
+ retries: results.reduce((total, result) => total + result.retries, 0),
221
+ terminations: {
222
+ completed: results.filter((result) => result.termination === null).length,
223
+ timeout: results.filter((result) => result.termination === 'timeout')
224
+ .length,
225
+ interrupted: results.filter((result) => result.termination === 'interrupted').length,
226
+ },
227
+ partial: interrupted || results.length < plannedRunCount,
228
+ interrupted,
229
+ runs: results.map((result) => runSummary(result, outputDirectory)),
230
+ };
231
+ await writeFileAtomically(join(outputDirectory, 'aggregate-result.json'), JSON.stringify(aggregate, null, 2));
232
+ if (options.json)
233
+ io.stdout(`${JSON.stringify(aggregate)}\n`);
234
+ else
235
+ io.stdout(`${passed}/${results.length} passed\n`);
236
+ return interrupted ? 130 : passed === results.length ? 0 : 1;
237
+ }
238
+ //# sourceMappingURL=project-eval.js.map
@@ -19,6 +19,8 @@ export interface RunProcessOptions {
19
19
  signal?: AbortSignal;
20
20
  onOutput?: (output: string) => void | Promise<void>;
21
21
  env?: Readonly<Record<string, string>>;
22
+ inheritEnvironment?: boolean;
23
+ redactExplicitEnvironment?: boolean;
22
24
  controlOutputBytes?: number;
23
25
  controlOutputFd?: number;
24
26
  scriptInput?: string;
@@ -84,7 +84,7 @@ export class BoundedProcessRunner {
84
84
  if (options.signal?.aborted)
85
85
  return Promise.reject(abortError());
86
86
  return new Promise((resolve, reject) => {
87
- const sensitiveValues = sensitiveEnvironmentValues(process.env);
87
+ const sensitiveValues = sensitiveEnvironmentValues(process.env, ...(options.redactExplicitEnvironment ? [options.env ?? {}] : []));
88
88
  const longestSensitiveValueBytes = sensitiveValues.reduce((longest, value) => Math.max(longest, Buffer.byteLength(value)), 0);
89
89
  const rawOutputLimit = this.options.maxOutputBytes + Math.max(3, longestSensitiveValueBytes);
90
90
  const controlOutputFd = options.controlOutputBytes === undefined
@@ -108,7 +108,9 @@ export class BoundedProcessRunner {
108
108
  const child = spawn(options.command, options.args, {
109
109
  cwd: options.cwd ?? this.options.cwd,
110
110
  detached: process.platform !== 'win32',
111
- env: { ...sanitizeChildEnvironment(), ...options.env },
111
+ env: options.inheritEnvironment === false
112
+ ? sanitizeChildEnvironment(options.env ?? {}, {})
113
+ : sanitizeChildEnvironment(options.env ?? {}),
112
114
  stdio,
113
115
  });
114
116
  const chunks = { stdout: [], stderr: [] };
@@ -1,37 +1,6 @@
1
1
  import type { ClaudePluginEvalCase } from './claude-plugin-eval-schema.js';
2
- export interface EvalTraceEvent {
3
- type: string;
4
- tool?: string;
5
- input?: Record<string, unknown>;
6
- [key: string]: unknown;
7
- }
8
- export interface EvalRunArtifacts {
9
- lastMessage: string;
10
- trace: readonly EvalTraceEvent[];
11
- cwd: string;
12
- }
13
- export interface EvalGraderResult {
14
- name: string;
15
- passed: boolean;
16
- weight: number;
17
- explanation: string;
18
- judge_votes?: readonly boolean[];
19
- evidence?: string;
20
- with_only?: boolean;
21
- }
22
- export interface EvalJudge {
23
- vote(request: {
24
- criteria: string;
25
- focus: string;
26
- baseline?: string;
27
- model: string;
28
- signal?: AbortSignal;
29
- }): Promise<{
30
- passed: boolean;
31
- explanation?: string;
32
- costUsd: number;
33
- }>;
34
- }
2
+ export type { EvalTraceEvent, EvalRunArtifacts, EvalGraderResult, EvalJudge, } from '../evals/eval-contract.js';
3
+ import type { EvalRunArtifacts, EvalGraderResult, EvalJudge } from '../evals/eval-contract.js';
35
4
  export declare function gradeClaudePluginEvalRun(options: {
36
5
  case: ClaudePluginEvalCase;
37
6
  artifacts: EvalRunArtifacts;
@@ -1,20 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { minimatch } from 'minimatch';
3
2
  import { resolveContainedPath } from './claude-plugin-eval-schema.js';
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
- }
3
+ import { gradeDeterministicEvalRun } from '../evals/eval-graders.js';
18
4
  async function focusText(focus, artifacts) {
19
5
  if (focus === 'last_message')
20
6
  return artifacts.lastMessage;
@@ -32,56 +18,6 @@ async function focusText(focus, artifacts) {
32
18
  }
33
19
  return readFile(await resolveContainedPath(artifacts.cwd, focus.path, 'Grader focus'), 'utf8');
34
20
  }
35
- async function freeGrade(grader, artifacts) {
36
- let passed = false;
37
- let evidence = '';
38
- if (grader.type === 'regex') {
39
- const target = await focusText(grader.target, artifacts);
40
- const expression = new RegExp(grader.pattern, grader.flags.includes('g') ? grader.flags : `${grader.flags}g`);
41
- const count = [...target.matchAll(expression)].length;
42
- passed =
43
- grader.match === 'contains'
44
- ? count > 0
45
- : grader.match === 'not_contains'
46
- ? count === 0
47
- : count === Number(grader.match.slice(6));
48
- evidence = `matches=${count}`;
49
- }
50
- else if (grader.type === 'tool_used') {
51
- const count = artifacts.trace.filter((event) => matches(event, {
52
- tool: grader.tool,
53
- ...(grader.input_match ? { input_match: grader.input_match } : {}),
54
- })).length;
55
- passed = count >= grader.min && count <= grader.max;
56
- evidence = `uses=${count}`;
57
- }
58
- else if (grader.type === 'tool_order') {
59
- const before = artifacts.trace.findIndex((event) => matches(event, grader.before));
60
- const after = artifacts.trace.findIndex((event, index) => index > before && matches(event, grader.after));
61
- passed = before >= 0 && after > before;
62
- evidence = `before=${before},after=${after}`;
63
- }
64
- else {
65
- const { glob } = await import('node:fs/promises');
66
- let count = 0;
67
- for await (const path of glob('**/*', { cwd: artifacts.cwd })) {
68
- if (!minimatch(path, grader.path))
69
- continue;
70
- await resolveContainedPath(artifacts.cwd, path, 'file_exists match');
71
- count += 1;
72
- }
73
- passed = grader.exists ? count > 0 : count === 0;
74
- evidence = `files=${count}`;
75
- }
76
- return {
77
- name: grader.name,
78
- passed,
79
- weight: grader.weight,
80
- explanation: passed ? 'passed' : 'failed',
81
- evidence,
82
- ...(grader.arm === 'with-only' ? { with_only: true } : {}),
83
- };
84
- }
85
21
  export async function gradeClaudePluginEvalRun(options) {
86
22
  const results = [];
87
23
  let skippedPaidGraders = false;
@@ -90,7 +26,12 @@ export async function gradeClaudePluginEvalRun(options) {
90
26
  if (options.arm === 'without' && item.arm === 'with-only')
91
27
  continue;
92
28
  if (item.type !== 'llm' && item.type !== 'baseline') {
93
- results.push(await freeGrade(item, options.artifacts));
29
+ const [result] = await gradeDeterministicEvalRun({
30
+ graders: [item],
31
+ artifacts: options.artifacts,
32
+ });
33
+ if (result)
34
+ results.push(result);
94
35
  continue;
95
36
  }
96
37
  if (options.skipPaid) {
@@ -1,33 +1,9 @@
1
- import type { RuntimeEvent } from '../core/runtime.js';
2
1
  import type { DataPlane } from '../persistence/data-plane.js';
3
2
  import type { ClaudePluginEvalCase } from './claude-plugin-eval-schema.js';
4
- import type { EvalRunArtifacts } from './claude-plugin-eval-graders.js';
5
- export declare const DEFAULT_EVAL_ALLOWED_TOOLS: readonly ["Read", "Glob", "Grep", "Skill"];
6
- export interface PluginEvalRuntime {
7
- run(prompt: string, signal: AbortSignal): Promise<{
8
- text: string;
9
- turns: number;
10
- costUsd?: number;
11
- }>;
12
- close?(): Promise<void>;
13
- }
14
- export interface PluginEvalRuntimeFactory {
15
- create(options: {
16
- dataPlane: DataPlane;
17
- cwd: string;
18
- configRoot: string;
19
- home: string;
20
- model?: string;
21
- maxTurns: number;
22
- pluginDirectories: readonly string[];
23
- allowedTools: readonly string[];
24
- appendSystemPrompt?: string;
25
- historyFile?: string;
26
- addDirs: readonly string[];
27
- env: Readonly<Record<string, string>>;
28
- eventSink(event: RuntimeEvent): void;
29
- }): Promise<PluginEvalRuntime>;
30
- }
3
+ import type { EvalRunArtifacts, EvalRuntime, EvalRuntimeFactory } from '../evals/eval-contract.js';
4
+ export { DEFAULT_EVAL_ALLOWED_TOOLS, resolveEvalAllowedTools, } from '../evals/eval-contract.js';
5
+ export type PluginEvalRuntime = EvalRuntime;
6
+ export type PluginEvalRuntimeFactory = EvalRuntimeFactory;
31
7
  export interface EvalSingleRunResult {
32
8
  text: string;
33
9
  turns: number;
@@ -39,7 +15,6 @@ export interface EvalSingleRunResult {
39
15
  termination: 'timeout' | 'interrupted' | null;
40
16
  tempRoot?: string;
41
17
  }
42
- export declare function resolveEvalAllowedTools(requested: readonly string[], grants: readonly string[]): string[];
43
18
  export declare function runClaudePluginEvalOnce(options: {
44
19
  case: ClaudePluginEvalCase;
45
20
  factory: PluginEvalRuntimeFactory;
@@ -3,50 +3,11 @@ import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { BoundedProcessRunner, joinedProcessOutput, } from '../platform/bounded-process-runner.js';
5
5
  import { resolveContainedPath } from './claude-plugin-eval-schema.js';
6
- export const DEFAULT_EVAL_ALLOWED_TOOLS = [
7
- 'Read',
8
- 'Glob',
9
- 'Grep',
10
- 'Skill',
11
- ];
6
+ import { normalizeEvalTraceEvent, resolveEvalAllowedTools, } from '../evals/eval-contract.js';
7
+ export { DEFAULT_EVAL_ALLOWED_TOOLS, resolveEvalAllowedTools, } from '../evals/eval-contract.js';
12
8
  function abortError() {
13
9
  return new DOMException('Eval run timed out', 'AbortError');
14
10
  }
15
- function toolName(rule) {
16
- const separator = rule.indexOf('(');
17
- return separator < 0 ? rule : rule.slice(0, separator);
18
- }
19
- function gatedTool(rule) {
20
- const name = toolName(rule);
21
- return (['Bash', 'Write', 'Edit', 'NotebookEdit', 'WebFetch', 'WebSearch'].includes(name) || name.startsWith('mcp__'));
22
- }
23
- function operatorGrants(requested, grants) {
24
- const name = toolName(requested);
25
- const wildcardMatch = (pattern) => {
26
- const parts = pattern.split('*');
27
- if (parts.length === 1)
28
- return false;
29
- if (!requested.startsWith(parts[0] ?? ''))
30
- return false;
31
- let cursor = parts[0]?.length ?? 0;
32
- for (const part of parts.slice(1, -1)) {
33
- const index = requested.indexOf(part, cursor);
34
- if (index < 0)
35
- return false;
36
- cursor = index + part.length;
37
- }
38
- const last = parts.at(-1) ?? '';
39
- return last.length === 0 || requested.slice(cursor).endsWith(last);
40
- };
41
- return grants.some((grant) => grant === requested || grant === name || wildcardMatch(grant));
42
- }
43
- export function resolveEvalAllowedTools(requested, grants) {
44
- const selected = requested.length ? requested : DEFAULT_EVAL_ALLOWED_TOOLS;
45
- for (const rule of selected)
46
- if (gatedTool(rule) && !operatorGrants(rule, grants))
47
- throw new Error(`Eval case requests gated tool ${rule}; grant it with --allow-tools`);
48
- return [...new Set(selected)];
49
- }
50
11
  async function scaffold(caseDef, cwd, home, signal) {
51
12
  if (!caseDef.context.scaffoldScript)
52
13
  return;
@@ -70,23 +31,7 @@ async function scaffold(caseDef, cwd, home, signal) {
70
31
  if (result.code !== 0)
71
32
  throw new Error(`Scaffold failed (${result.code}): ${joinedProcessOutput(result)}`);
72
33
  }
73
- function traceEvent(event) {
74
- if (event.type === 'tool-call')
75
- return {
76
- type: 'tool-call',
77
- tool: event.call.name,
78
- input: event.call.input,
79
- id: event.call.id,
80
- };
81
- if (event.type === 'tool-result')
82
- return {
83
- type: 'tool-result',
84
- callId: event.callId,
85
- content: event.content,
86
- isError: event.isError,
87
- };
88
- return event;
89
- }
34
+ const traceEvent = normalizeEvalTraceEvent;
90
35
  export async function runClaudePluginEvalOnce(options) {
91
36
  const tempRoot = await mkdtemp(join(tmpdir(), 'praxis-eval-'));
92
37
  const cwd = join(tempRoot, 'cwd');
@@ -1,3 +1,4 @@
1
+ import type { EvalArm as SharedEvalArm, EvalDeterministicGrader, EvalFocus as SharedEvalFocus, EvalToolMatch as SharedEvalToolMatch } from '../evals/eval-contract.js';
1
2
  export declare const EVAL_SCHEMA_VERSION = "1.0";
2
3
  export declare const MAX_EVAL_FILE_BYTES: number;
3
4
  export declare const MAX_EVAL_GRADERS = 256;
@@ -5,48 +6,23 @@ export declare const MAX_EVAL_ARRAY_ITEMS = 256;
5
6
  export declare const MAX_EVAL_STRING_LENGTH: number;
6
7
  export declare const MAX_EVAL_OBJECT_DEPTH = 16;
7
8
  export declare const MAX_EVAL_OBJECT_NODES = 4096;
8
- export type EvalFocus = 'last_message' | 'trace' | 'files' | {
9
- source: 'file';
10
- path: string;
11
- };
12
- export type EvalArm = 'with-only' | 'both';
13
- interface EvalGraderBase {
9
+ export type EvalFocus = SharedEvalFocus;
10
+ export type EvalArm = SharedEvalArm;
11
+ export type EvalToolMatch = SharedEvalToolMatch;
12
+ export type ClaudePluginEvalGrader = EvalDeterministicGrader | {
14
13
  name: string;
15
14
  weight: number;
16
15
  arm?: EvalArm;
17
- }
18
- export type ClaudePluginEvalGrader = (EvalGraderBase & {
19
- type: 'regex';
20
- target: EvalFocus;
21
- pattern: string;
22
- flags: string;
23
- match: 'contains' | 'not_contains' | `count:${number}`;
24
- }) | (EvalGraderBase & {
25
- type: 'tool_order';
26
- before: EvalToolMatch;
27
- after: EvalToolMatch;
28
- }) | (EvalGraderBase & {
29
- type: 'tool_used';
30
- tool: string;
31
- input_match?: Record<string, unknown>;
32
- min: number;
33
- max: number;
34
- }) | (EvalGraderBase & {
35
- type: 'file_exists';
36
- path: string;
37
- exists: boolean;
38
- }) | (EvalGraderBase & {
39
16
  type: 'llm';
40
17
  criteria: string;
41
18
  focus: EvalFocus;
42
- }) | (EvalGraderBase & {
19
+ } | {
20
+ name: string;
21
+ weight: number;
22
+ arm?: EvalArm;
43
23
  type: 'baseline';
44
24
  baseline_file: string;
45
25
  criteria: string;
46
- });
47
- export type EvalToolMatch = string | {
48
- tool: string;
49
- input_match?: Record<string, unknown>;
50
26
  };
51
27
  export interface ClaudePluginEvalCase {
52
28
  schemaVersion: string;
@@ -76,5 +52,4 @@ export interface ClaudePluginEvalCase {
76
52
  }
77
53
  export declare function loadClaudePluginEvalCase(caseDir: string): Promise<ClaudePluginEvalCase>;
78
54
  export declare function resolveContainedPath(root: string, candidate: string, label: string): Promise<string>;
79
- export {};
80
55
  //# sourceMappingURL=claude-plugin-eval-schema.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.46.5",
3
+ "version": "0.48.0",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",
@@ -61,7 +61,8 @@
61
61
  "verify:release-automation": "node scripts/verify-release-automation.mjs",
62
62
  "verify:ci-coverage": "node scripts/verify-ci-coverage.mjs",
63
63
  "verify:fixture-contracts": "node scripts/verify-fixture-contracts.mjs",
64
- "test:fixtures": "node scripts/run-fixture-contracts.mjs"
64
+ "test:fixtures": "node scripts/run-fixture-contracts.mjs",
65
+ "test:eval:baseline": "vitest run src/evals/coding-baseline.test.ts src/evals/project-eval-comparison.test.ts"
65
66
  },
66
67
  "engines": {
67
68
  "node": ">=24"