praxis-agent 0.67.2 → 0.69.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 CHANGED
@@ -144,6 +144,26 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
144
144
  safety-rate regression, every candidate verifier to be satisfied, and every
145
145
  candidate `high` or `release` task to pass, and rejects incomplete safety
146
146
  evidence.
147
+ - **Held-out qualification** — The explicit `praxis eval qualify` surface
148
+ preflights a pinned provider,
149
+ profile, and model, then runs the immutable held-out corpus as 36 local
150
+ Project Eval runs. Baseline-only results use `qualified: null`; candidate
151
+ claims require complete safe, comparable evidence, and unknown usage/cost
152
+ keeps optimization claims disabled. A bounded DeepSeek baseline for the
153
+ exact `anthropic/default/deepseek-v4-flash` pin completed 36/36 runs with
154
+ 33/36 passes (91.7%) and 36/36 safety passes. The
155
+ `config-kit.add-json-output` run 3 failed closed with provider error
156
+ `Provider reported max_tokens with completed tool calls`. The
157
+ `config-kit.preserve-zero-values` run 2 and `task-store.fix-completed-filter`
158
+ run 3 failed closed with provider error `Provider transport failed`; their
159
+ terminal results do not establish the transport-error cause. All three were
160
+ non-safety failures with unknown usage/cost and unsatisfied or not-run
161
+ behavior verification.
162
+ Median/p95 turns were 5/7 and median/p95 duration was 28,653.5/52,197 ms.
163
+ The 33-run known-cost subtotal is USD 0.068322756, not a total. This
164
+ measures only that pinned 32,768-context/4,096-output configuration;
165
+ baseline-only evidence remains `qualified: null`, and no live-model quality
166
+ or optimization claim is established.
147
167
  - **Local agent runtime** — C+ Quiet Operator responsive TUI with a linear
148
168
  `❯` user / `⏺` assistant conversation, `✻` thinking activity, and `!` shell
149
169
  composer grammar, compact stable tool rows, responsive density,
@@ -407,7 +427,7 @@ normal/low-capability full-frame p95 budgets of `<16.7/<33 ms`.
407
427
  `npm run test:coverage` measures all production code under `src/**` with V8 and
408
428
  enforces global floors of 79% statements, 70% branches, 85% functions, and 81% lines,
409
429
  and rejects any production runtime module with zero covered statements (while allowing
410
- type-only modules). `npm run test:fixtures` executes the 74-behavior native contract; 66 behaviors
430
+ type-only modules). `npm run test:fixtures` executes the 76-behavior native contract; 68 behaviors
411
431
  are qualified and 8 are explicitly excluded. Schema-v2 risk tiers and executable evidence dimensions
412
432
  are enforced fail-closed. `npm run verify:fixture-contracts` performs the structural check and is part
413
433
  of `npm run check`.
@@ -1 +1 @@
1
- {"schema_version":"1.0","source_revision":"git:cdfb04df03516521ee1f366319665ef07c41fdb7","source_dirty":false,"artifact_sha256":"sha256:506df1f26b80d8092b1748de2a1130ad9b4b9fa423776d31f911d81cd5976610"}
1
+ {"schema_version":"1.0","source_revision":"git:1f78926ebc463fed5e2d7a0f22f49c9569171350","source_dirty":false,"artifact_sha256":"sha256:46543a71fca8edab43c1a8ce3259ec7459bc8e31cdeec7f5028c049d7680350a"}
@@ -2336,6 +2336,10 @@ const defaultPluginEvalRuntimeFactory = {
2336
2336
  tools: [...options.allowedTools],
2337
2337
  permissionMode: 'dontAsk',
2338
2338
  ...(options.model ? { model: options.model } : {}),
2339
+ ...(options.provider ? { provider: options.provider } : {}),
2340
+ ...(options.providerProfile
2341
+ ? { providerProfile: options.providerProfile }
2342
+ : {}),
2339
2343
  ...(options.appendSystemPrompt
2340
2344
  ? { appendSystemPrompt: options.appendSystemPrompt }
2341
2345
  : {}),
@@ -2368,6 +2372,10 @@ const defaultProjectEvalRuntimeFactory = {
2368
2372
  cwd: options.cwd,
2369
2373
  environment,
2370
2374
  ...(options.model === undefined ? {} : { model: options.model }),
2375
+ ...(options.provider === undefined ? {} : { provider: options.provider }),
2376
+ ...(options.providerProfile === undefined
2377
+ ? {}
2378
+ : { profile: options.providerProfile }),
2371
2379
  includeSettings: true,
2372
2380
  includeProjectSettings: false,
2373
2381
  });
@@ -5116,6 +5124,14 @@ async function execute(argv, io, dependencies, signal) {
5116
5124
  io.stdout(PROJECT_EVAL_COMPARE_HELP);
5117
5125
  return 0;
5118
5126
  }
5127
+ if (special.args[1] === 'qualify' &&
5128
+ special.args
5129
+ .slice(2)
5130
+ .some((value) => value === '-h' || value === '--help')) {
5131
+ const { HELD_OUT_QUALIFICATION_HELP } = await import('./evals/held-out-qualification.js');
5132
+ io.stdout(HELD_OUT_QUALIFICATION_HELP);
5133
+ return 0;
5134
+ }
5119
5135
  if (special.args
5120
5136
  .slice(1)
5121
5137
  .some((value) => value === '-h' || value === '--help')) {
@@ -5124,12 +5140,25 @@ async function execute(argv, io, dependencies, signal) {
5124
5140
  }
5125
5141
  if (!dependencies.projectEval)
5126
5142
  throw new Error('Project eval unavailable');
5127
- return executeProjectEvalCommand([
5143
+ const prefixFlags = [
5128
5144
  ...(specialPrefix?.model === undefined
5129
5145
  ? []
5130
5146
  : ['--model', specialPrefix.model]),
5131
- ...special.args.slice(1),
5132
- ], io, dependencies.projectEval, process.cwd(), signal);
5147
+ ...(specialPrefix?.provider === undefined
5148
+ ? []
5149
+ : ['--provider', specialPrefix.provider]),
5150
+ ...(specialPrefix?.providerProfile === undefined
5151
+ ? []
5152
+ : ['--profile', specialPrefix.providerProfile]),
5153
+ ];
5154
+ if (special.args[1] === 'compare' && prefixFlags.length > 0)
5155
+ throw new Error('eval compare does not accept global provider, profile, or model options');
5156
+ const evalArgs = special.args[1] === 'qualify'
5157
+ ? ['qualify', ...prefixFlags, ...special.args.slice(2)]
5158
+ : special.args[1] === 'compare'
5159
+ ? ['compare', ...special.args.slice(2)]
5160
+ : [...prefixFlags, ...special.args.slice(1)];
5161
+ return executeProjectEvalCommand(evalArgs, io, dependencies.projectEval, process.cwd(), signal);
5133
5162
  }
5134
5163
  if ((special.args[0] === 'plugin' || special.args[0] === 'plugins') &&
5135
5164
  special.args[1] === 'eval') {
@@ -14,6 +14,8 @@ export interface EvalRuntimeFactoryOptions {
14
14
  cwd: string;
15
15
  configRoot: string;
16
16
  home: string;
17
+ provider?: string;
18
+ providerProfile?: string;
17
19
  model?: string;
18
20
  maxTurns: number;
19
21
  pluginDirectories?: readonly string[];
@@ -0,0 +1,28 @@
1
+ import { type ProjectEvalCase } from './project-eval-schema.js';
2
+ export interface HeldOutCorpusPolicy {
3
+ readonly execution: 'opt-in-only';
4
+ readonly tuning: 'forbidden';
5
+ readonly resultInformedChanges: 'require-new-version';
6
+ }
7
+ export interface HeldOutCorpusRepository {
8
+ readonly id: string;
9
+ readonly path: string;
10
+ readonly target: string;
11
+ readonly tasks: readonly string[];
12
+ readonly cases: readonly ProjectEvalCase[];
13
+ }
14
+ export interface HeldOutCorpus {
15
+ readonly root: string;
16
+ readonly schemaVersion: '1.0';
17
+ readonly id: 'praxis-held-out-v1';
18
+ readonly version: 1;
19
+ readonly split: 'held-out';
20
+ readonly repetitions: 3;
21
+ readonly policy: HeldOutCorpusPolicy;
22
+ readonly contentSha256: `sha256:${string}`;
23
+ readonly repositories: readonly HeldOutCorpusRepository[];
24
+ readonly taskCount: number;
25
+ readonly plannedRunCount: number;
26
+ }
27
+ export declare function loadHeldOutCorpus(root: string): Promise<HeldOutCorpus>;
28
+ //# sourceMappingURL=held-out-corpus.d.ts.map
@@ -0,0 +1,303 @@
1
+ import { lstat, opendir, readFile, realpath } from 'node:fs/promises';
2
+ import { createHash } from 'node:crypto';
3
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ import { Minimatch } from 'minimatch';
5
+ import { parse as parseYaml } from 'yaml';
6
+ import { discoverProjectEvalCases, } from './project-eval-schema.js';
7
+ const MAX_MANIFEST_BYTES = 1024 * 1024;
8
+ const MAX_FILES = 4096;
9
+ const MAX_TOTAL_BYTES = 64 * 1024 * 1024;
10
+ const MAX_ENTRIES = 16_384;
11
+ const REQUIRED_TAGS = ['held-out', 'praxis-held-out-v1'];
12
+ const FORBIDDEN_TAGS = new Set([
13
+ 'tuning',
14
+ 'calibration',
15
+ 'baseline',
16
+ 'candidate',
17
+ 'admission',
18
+ ]);
19
+ const MUTATION_GLOB_OPTIONS = {
20
+ dot: true,
21
+ magicalBraces: true,
22
+ nocomment: true,
23
+ nonegate: true,
24
+ };
25
+ function object(value, label) {
26
+ if (!value || typeof value !== 'object' || Array.isArray(value))
27
+ throw new Error(`${label} must be an object`);
28
+ return value;
29
+ }
30
+ function exactKeys(value, keys, label) {
31
+ const expected = new Set(keys);
32
+ const actual = Object.keys(value);
33
+ if (actual.some((key) => !expected.has(key)) ||
34
+ actual.length !== expected.size)
35
+ throw new Error(`${label} has unexpected or missing fields`);
36
+ }
37
+ function bounded(value, label = 'manifest', depth = 0, state = { nodes: 0 }) {
38
+ state.nodes += 1;
39
+ if (state.nodes > 4096)
40
+ throw new Error(`${label} exceeds object node limit`);
41
+ if (depth > 16)
42
+ throw new Error(`${label} exceeds object depth limit`);
43
+ if (typeof value === 'string' && value.length > 16 * 1024)
44
+ throw new Error(`${label} contains oversized string`);
45
+ if (Array.isArray(value)) {
46
+ if (value.length > 256)
47
+ throw new Error(`${label} contains oversized collection`);
48
+ for (const item of value)
49
+ bounded(item, label, depth + 1, state);
50
+ }
51
+ else if (value && typeof value === 'object') {
52
+ const entries = Object.entries(value);
53
+ if (entries.length > 256)
54
+ throw new Error(`${label} contains oversized collection`);
55
+ for (const [key, item] of entries) {
56
+ if (key.length > 256)
57
+ throw new Error(`${label} contains oversized key`);
58
+ bounded(item, label, depth + 1, state);
59
+ }
60
+ }
61
+ }
62
+ function text(value, label) {
63
+ if (typeof value !== 'string' || !value.trim() || value.length > 16 * 1024)
64
+ throw new Error(`${label} must be a non-empty string`);
65
+ return value;
66
+ }
67
+ function compareStrings(left, right) {
68
+ return left < right ? -1 : left > right ? 1 : 0;
69
+ }
70
+ function safeRelativePath(value, label) {
71
+ const input = text(value, label);
72
+ const normalized = input.replaceAll('\\', '/');
73
+ if (isAbsolute(input) ||
74
+ /^[A-Za-z]:\//u.test(normalized) ||
75
+ normalized.includes('\0') ||
76
+ normalized.split('/').some((part) => !part || part === '.' || part === '..'))
77
+ throw new Error(`${label} must be a contained relative path`);
78
+ return normalized;
79
+ }
80
+ function contained(root, candidate, label) {
81
+ if (candidate !== root && !candidate.startsWith(`${root}${sep}`))
82
+ throw new Error(`${label} escapes corpus root`);
83
+ }
84
+ async function regularContainedPath(root, path, label) {
85
+ const candidate = resolve(root, path);
86
+ contained(root, candidate, label);
87
+ const parts = relative(root, candidate).split(sep);
88
+ let current = root;
89
+ for (const part of parts) {
90
+ current = join(current, part);
91
+ const component = await lstat(current).catch(() => null);
92
+ if (!component)
93
+ throw new Error(`${label} does not exist`);
94
+ if (component.isSymbolicLink())
95
+ throw new Error(`${label} contains symlink`);
96
+ }
97
+ const info = await lstat(candidate);
98
+ if (!info.isDirectory())
99
+ throw new Error(`${label} must be a directory`);
100
+ const canonical = await realpath(candidate);
101
+ contained(root, canonical, label);
102
+ return canonical;
103
+ }
104
+ async function enumerateFiles(root, repositoryRoots) {
105
+ const files = [];
106
+ let totalBytes = 0;
107
+ let visitedEntries = 0;
108
+ async function walk(directory) {
109
+ const handle = await opendir(directory);
110
+ for await (const entry of handle) {
111
+ visitedEntries += 1;
112
+ if (visitedEntries > MAX_ENTRIES)
113
+ throw new Error('Corpus exceeds directory entry limit');
114
+ if (entry.name === '.git' || entry.name === 'node_modules')
115
+ throw new Error(`Corpus contains forbidden entry: ${entry.name}`);
116
+ const path = join(directory, entry.name);
117
+ const info = await lstat(path);
118
+ if (info.isSymbolicLink())
119
+ throw new Error(`Corpus contains symlink: ${path}`);
120
+ if (info.isDirectory()) {
121
+ await walk(path);
122
+ continue;
123
+ }
124
+ if (!info.isFile())
125
+ throw new Error(`Corpus contains unsupported entry: ${path}`);
126
+ if (files.length >= MAX_FILES)
127
+ throw new Error('Corpus exceeds file limit');
128
+ totalBytes += info.size;
129
+ if (totalBytes > MAX_TOTAL_BYTES)
130
+ throw new Error('Corpus exceeds byte limit');
131
+ const content = await readFile(path);
132
+ const digest = createHash('sha256').update(content).digest('hex');
133
+ const rel = relative(root, path).split(sep).join('/');
134
+ files.push({
135
+ path: rel,
136
+ mode: info.mode & 0o777,
137
+ size: info.size,
138
+ digest,
139
+ });
140
+ }
141
+ }
142
+ for (const repositoryRoot of repositoryRoots)
143
+ await walk(repositoryRoot);
144
+ return files;
145
+ }
146
+ function contentDigest(files) {
147
+ const records = [...files]
148
+ .sort((a, b) => compareStrings(a.path, b.path))
149
+ .map((file) => `${file.path}\0${file.mode.toString(8)}\0${file.size}\0${file.digest}\n`)
150
+ .join('');
151
+ return `sha256:${createHash('sha256').update(records, 'utf8').digest('hex')}`;
152
+ }
153
+ export async function loadHeldOutCorpus(root) {
154
+ const corpusRoot = await realpath(resolve(root));
155
+ const manifestPath = join(corpusRoot, 'corpus.yaml');
156
+ const manifestInfo = await lstat(manifestPath);
157
+ if (manifestInfo.isSymbolicLink() || !manifestInfo.isFile())
158
+ throw new Error('corpus.yaml must be a regular file');
159
+ if (manifestInfo.size > MAX_MANIFEST_BYTES)
160
+ throw new Error('corpus.yaml exceeds 1 MiB');
161
+ const raw = parseYaml(await readFile(manifestPath, 'utf8'), {
162
+ maxAliasCount: 20,
163
+ });
164
+ bounded(raw);
165
+ const manifest = object(raw, 'corpus');
166
+ exactKeys(manifest, [
167
+ 'schema_version',
168
+ 'id',
169
+ 'version',
170
+ 'split',
171
+ 'repetitions',
172
+ 'policy',
173
+ 'content_sha256',
174
+ 'repositories',
175
+ ], 'corpus');
176
+ if (manifest.schema_version !== '1.0')
177
+ throw new Error('Unsupported corpus schema_version');
178
+ if (manifest.id !== 'praxis-held-out-v1')
179
+ throw new Error('Unsupported corpus id');
180
+ if (manifest.version !== 1)
181
+ throw new Error('Unsupported corpus version');
182
+ if (manifest.split !== 'held-out')
183
+ throw new Error('corpus split must be held-out');
184
+ if (manifest.repetitions !== 3)
185
+ throw new Error('corpus repetitions must be 3');
186
+ const policyRaw = object(manifest.policy, 'corpus.policy');
187
+ exactKeys(policyRaw, ['execution', 'tuning', 'result_informed_changes'], 'corpus.policy');
188
+ if (policyRaw.execution !== 'opt-in-only' ||
189
+ policyRaw.tuning !== 'forbidden' ||
190
+ policyRaw.result_informed_changes !== 'require-new-version')
191
+ throw new Error('corpus policy is invalid');
192
+ const declaredDigest = text(manifest.content_sha256, 'corpus.content_sha256');
193
+ if (!/^sha256:[0-9a-f]{64}$/u.test(declaredDigest))
194
+ throw new Error('corpus.content_sha256 is invalid');
195
+ if (!Array.isArray(manifest.repositories) || manifest.repositories.length < 3)
196
+ throw new Error('corpus must declare at least three repositories');
197
+ const repositories = [];
198
+ const ids = new Set();
199
+ const targets = [];
200
+ const globalTasks = new Set();
201
+ for (const [index, value] of manifest.repositories.entries()) {
202
+ const item = object(value, `repositories[${index}]`);
203
+ exactKeys(item, ['id', 'path', 'tasks'], `repositories[${index}]`);
204
+ const id = text(item.id, `repositories[${index}].id`);
205
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(id) || ids.has(id))
206
+ throw new Error('repository IDs must be unique lowercase dash-delimited identifiers');
207
+ ids.add(id);
208
+ const path = safeRelativePath(item.path, `repositories[${index}].path`);
209
+ const tasksRaw = item.tasks;
210
+ if (!Array.isArray(tasksRaw) ||
211
+ tasksRaw.length < 4 ||
212
+ tasksRaw.some((task) => typeof task !== 'string'))
213
+ throw new Error(`repositories[${index}].tasks must contain at least four task names`);
214
+ const tasks = tasksRaw.map((task) => text(task, `repositories[${index}].tasks`));
215
+ if (new Set(tasks).size !== tasks.length ||
216
+ [...tasks].sort(compareStrings).some((task, i) => task !== tasks[i]))
217
+ throw new Error(`repositories[${index}].tasks must be unique and sorted`);
218
+ for (const task of tasks) {
219
+ if (globalTasks.has(task))
220
+ throw new Error(`Task name is duplicated across repositories: ${task}`);
221
+ globalTasks.add(task);
222
+ }
223
+ const target = await regularContainedPath(corpusRoot, path, `repositories[${index}].path`);
224
+ targets.push(target);
225
+ repositories.push({ id, path, target, tasks, cases: [] });
226
+ }
227
+ if (globalTasks.size < 12)
228
+ throw new Error('corpus must contain at least twelve tasks');
229
+ for (let i = 0; i < targets.length; i += 1) {
230
+ for (let j = i + 1; j < targets.length; j += 1) {
231
+ const left = targets[i];
232
+ const right = targets[j];
233
+ if (left === undefined || right === undefined)
234
+ continue;
235
+ if (left === right ||
236
+ left.startsWith(`${right}${sep}`) ||
237
+ right.startsWith(`${left}${sep}`))
238
+ throw new Error('repository roots must be unique and non-nested');
239
+ }
240
+ }
241
+ // Preflight every repository before Project Eval discovery. This bounds the
242
+ // tree before the existing discovery walker inspects any case definitions.
243
+ const files = await enumerateFiles(corpusRoot, targets);
244
+ const finalRepositories = [];
245
+ for (const repository of repositories) {
246
+ const cases = await discoverProjectEvalCases(repository.target);
247
+ cases.sort((left, right) => compareStrings(left.name, right.name));
248
+ const names = cases.map((item) => item.name);
249
+ if (names.length !== repository.tasks.length ||
250
+ names.some((name, index) => name !== repository.tasks[index]))
251
+ throw new Error(`Repository ${repository.id} task declaration does not match discovery`);
252
+ for (const item of cases) {
253
+ if (item.runs !== 3)
254
+ throw new Error(`${item.name} must have three repetitions`);
255
+ if (!item.verification.some((verifier) => verifier.required))
256
+ throw new Error(`${item.name} must have a required verifier`);
257
+ if (!item.expect.allowedChangedPaths.length ||
258
+ !item.expect.expectedChangedPaths.length ||
259
+ !item.expect.forbiddenChangedPaths.length)
260
+ throw new Error(`${item.name} must declare allowed, expected, and forbidden mutations`);
261
+ if (item.execution.model !== undefined)
262
+ throw new Error(`${item.name} must not pin a model`);
263
+ const allowed = new Set(item.expect.allowedChangedPaths);
264
+ const expected = new Set(item.expect.expectedChangedPaths);
265
+ const mutationPaths = [...allowed, ...expected];
266
+ if (mutationPaths.some((path) => new Minimatch(path, MUTATION_GLOB_OPTIONS).hasMagic()))
267
+ throw new Error(`${item.name} allowed and expected mutations must use exact paths`);
268
+ if ([...expected].some((path) => !allowed.has(path)))
269
+ throw new Error(`${item.name} expected mutations must be allowed`);
270
+ const forbidden = item.expect.forbiddenChangedPaths.map((pattern) => new Minimatch(pattern, MUTATION_GLOB_OPTIONS));
271
+ if (mutationPaths.some((path) => forbidden.some((matcher) => matcher.match(path))))
272
+ throw new Error(`${item.name} mutation paths overlap`);
273
+ if (!REQUIRED_TAGS.every((tag) => item.tags.includes(tag)) ||
274
+ !item.tags.includes(repository.id))
275
+ throw new Error(`${item.name} is missing required held-out tags`);
276
+ if (item.tags.some((tag) => FORBIDDEN_TAGS.has(tag)))
277
+ throw new Error(`${item.name} contains a forbidden tuning tag`);
278
+ }
279
+ finalRepositories.push({ ...repository, cases });
280
+ }
281
+ const actualDigest = contentDigest(files);
282
+ if (actualDigest !== declaredDigest)
283
+ throw new Error(`corpus content digest mismatch: expected ${declaredDigest}, got ${actualDigest}`);
284
+ const taskCount = globalTasks.size;
285
+ return {
286
+ root: corpusRoot,
287
+ schemaVersion: '1.0',
288
+ id: 'praxis-held-out-v1',
289
+ version: 1,
290
+ split: 'held-out',
291
+ repetitions: 3,
292
+ policy: {
293
+ execution: 'opt-in-only',
294
+ tuning: 'forbidden',
295
+ resultInformedChanges: 'require-new-version',
296
+ },
297
+ contentSha256: actualDigest,
298
+ repositories: finalRepositories,
299
+ taskCount,
300
+ plannedRunCount: taskCount * 3,
301
+ };
302
+ }
303
+ //# sourceMappingURL=held-out-corpus.js.map
@@ -0,0 +1,140 @@
1
+ import { type PraxisBuildIdentity } from '../platform/praxis-build-identity.js';
2
+ import { type ProjectEvalIdentity } from './project-eval-identity.js';
3
+ import { type ProjectEvalComparisonMetric } from './project-eval-comparison.js';
4
+ import type { ProjectEvalDependencies, ProjectEvalRunSummary } from './project-eval.js';
5
+ /**
6
+ * Qualification orchestration for the immutable held-out corpus.
7
+ *
8
+ * This module owns the qualification contract and evidence envelope; actual
9
+ * case execution remains in the Project Eval runner.
10
+ */
11
+ export declare const HELD_OUT_QUALIFICATION_HELP = "Usage: praxis eval qualify [options] <corpus>\n\nQualify an explicitly pinned provider/model against the immutable held-out corpus.\n\nOptions:\n\nRequired:\n --provider <id> Provider identifier\n --profile <id> Provider profile identifier\n --model <id> Model identifier\n --confirm-held-out <id@sha256> Confirm the exact held-out corpus digest\n --run-verification Run the declared case verifiers\n --output-dir <dir> Write qualification artifacts here\n --allow-tools <rules> Grant gated tools; comma-separated and repeatable\n\nOptional:\n --baseline <qualification-result.json> Compare against a completed result\n --keep-temp Preserve temporary workspaces\n --json Print exactly one qualification JSON value\n --verbose Print run progress to stderr\n -h, --help Display help";
12
+ export interface HeldOutQualificationOptions {
13
+ corpus?: string;
14
+ provider?: string;
15
+ profile?: string;
16
+ model?: string;
17
+ confirmHeldOut?: string;
18
+ allowTools: string[];
19
+ runVerification: boolean;
20
+ outputDir?: string;
21
+ baseline?: string;
22
+ keepTemp: boolean;
23
+ json: boolean;
24
+ verbose: boolean;
25
+ help?: true;
26
+ }
27
+ /** Parse the strict, side-effect-free qualification command line. */
28
+ export declare function parseHeldOutQualificationOptions(argv: readonly string[]): HeldOutQualificationOptions;
29
+ export interface HeldOutQualificationAggregateReference {
30
+ repository: string;
31
+ path: string;
32
+ sha256: `sha256:${string}`;
33
+ identity_sha256: `sha256:${string}`;
34
+ }
35
+ export interface HeldOutQualificationRun {
36
+ repository: string;
37
+ case: string;
38
+ run: number;
39
+ passed: boolean;
40
+ safety_passed: boolean;
41
+ verifier_satisfied: boolean;
42
+ turns: number;
43
+ duration_ms: number;
44
+ usage_known: boolean;
45
+ cost_known: boolean;
46
+ usage: ProjectEvalRunSummary['usage'];
47
+ cost_usd: number | null;
48
+ identity: ProjectEvalIdentity;
49
+ }
50
+ export interface HeldOutQualificationCaseSummary {
51
+ repository: string;
52
+ case: string;
53
+ repetitions: number;
54
+ passed: number;
55
+ failed: number;
56
+ safety_passed: number;
57
+ safety_failed: number;
58
+ verifier_satisfied_runs: number;
59
+ verifier_unsatisfied_runs: number;
60
+ passed_all: boolean;
61
+ safety_passed_all: boolean;
62
+ verifier_satisfied: boolean;
63
+ }
64
+ export interface HeldOutQualificationResult {
65
+ schema_version: '1.0';
66
+ corpus: {
67
+ id: string;
68
+ version: number;
69
+ content_sha256: `sha256:${string}`;
70
+ repository_count: number;
71
+ task_count: number;
72
+ repetitions: number;
73
+ planned_run_count: number;
74
+ };
75
+ provider: string;
76
+ profile: string;
77
+ protocol: string;
78
+ model: string;
79
+ endpoint_sha256: `sha256:${string}`;
80
+ plan_sha256: `sha256:${string}`;
81
+ praxis_version: string;
82
+ build: PraxisBuildIdentity;
83
+ node_version: string;
84
+ platform: string;
85
+ architecture: string;
86
+ start: string;
87
+ duration_ms: number;
88
+ aggregates: readonly HeldOutQualificationAggregateReference[];
89
+ planned_run_count: number;
90
+ completed_run_count: number;
91
+ passed: number;
92
+ failed: number;
93
+ safety_passed: number;
94
+ safety_failed: number;
95
+ verifier_satisfied_runs: number;
96
+ verifier_unsatisfied_runs: number;
97
+ usage_known_runs: number;
98
+ usage_unknown_runs: number;
99
+ cost_known_runs: number;
100
+ cost_unknown_runs: number;
101
+ usage_totals: {
102
+ input_tokens: number;
103
+ output_tokens: number;
104
+ cache_read_input_tokens: number;
105
+ cache_creation_input_tokens: number;
106
+ web_search_requests: number;
107
+ } | null;
108
+ known_cost_total_usd: number | null;
109
+ median_turns: number;
110
+ p95_turns: number;
111
+ median_duration_ms: number;
112
+ p95_duration_ms: number;
113
+ cases: readonly HeldOutQualificationCaseSummary[];
114
+ runs: readonly HeldOutQualificationRun[];
115
+ baseline?: {
116
+ source_sha256: `sha256:${string}`;
117
+ summary: {
118
+ passed: number;
119
+ failed: number;
120
+ pass_rate: number;
121
+ safety_pass_rate: number;
122
+ planned_run_count: number;
123
+ };
124
+ };
125
+ regressions: readonly {
126
+ case: string;
127
+ run: number;
128
+ baseline_passed: boolean;
129
+ candidate_passed: boolean;
130
+ }[];
131
+ metric_deltas: Readonly<Record<string, ProjectEvalComparisonMetric<number | null>>>;
132
+ qualified: boolean | null;
133
+ optimization_claim_allowed: boolean;
134
+ }
135
+ export type HeldOutQualificationDependencies = ProjectEvalDependencies;
136
+ export declare function executeHeldOutQualificationCommand(argv: readonly string[], io: {
137
+ stdout(message: string): void;
138
+ stderr(message: string): void;
139
+ }, dependencies: HeldOutQualificationDependencies, callerCwd?: string, signal?: AbortSignal): Promise<number>;
140
+ //# sourceMappingURL=held-out-qualification.d.ts.map