praxis-agent 0.69.0 → 0.69.1
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 +34 -4
- package/dist/build-identity.json +1 -1
- package/dist/cli-runtime.js +7 -1
- package/dist/evals/eval-tool-admission.d.ts +9 -0
- package/dist/evals/eval-tool-admission.js +33 -0
- package/dist/evals/held-out-corpus.d.ts +2 -2
- package/dist/evals/held-out-corpus.js +19 -8
- package/dist/evals/held-out-qualification.js +8 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -143,14 +143,25 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
143
143
|
evidence produces null deltas, while the gate requires no pass-rate or
|
|
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
|
-
evidence.
|
|
146
|
+
evidence. Eval `allowed_tools` controls catalog exposure; in the default
|
|
147
|
+
Project/Plugin Eval runtime, allowed non-Bash tools remain preapproved, while
|
|
148
|
+
Bash preapproval requires existing static semantic and path checks to keep
|
|
149
|
+
recognized paths inside the isolated workspace or explicit added
|
|
150
|
+
directories. Recognized outside-root commands such as `find /` fail the
|
|
151
|
+
permission boundary before process execution; effects hidden inside opaque
|
|
152
|
+
executables are not part of this guarantee.
|
|
147
153
|
- **Held-out qualification** — The explicit `praxis eval qualify` surface
|
|
148
154
|
preflights a pinned provider,
|
|
149
155
|
profile, and model, then runs the immutable held-out corpus as 36 local
|
|
150
156
|
Project Eval runs. Baseline-only results use `qualified: null`; candidate
|
|
151
157
|
claims require complete safe, comparable evidence, and unknown usage/cost
|
|
152
|
-
keeps optimization claims disabled.
|
|
153
|
-
|
|
158
|
+
keeps optimization claims disabled. Loading and qualification accept safe
|
|
159
|
+
matching versioned `praxis-held-out-vN` identities. V1 is unchanged; v2 is
|
|
160
|
+
frozen at 3 repositories, 12 tasks, and 36 runs with digest
|
|
161
|
+
`sha256:1ae6e3485684db143ead1983479500f7fb80d13fd99769d8e202d4c7c35881b3`.
|
|
162
|
+
V2 has no real-provider evidence, and Task 8.2/Phase 9 remain locked. A
|
|
163
|
+
bounded DeepSeek baseline for the exact
|
|
164
|
+
`anthropic/default/deepseek-v4-flash` pin completed 36/36 runs with
|
|
154
165
|
33/36 passes (91.7%) and 36/36 safety passes. The
|
|
155
166
|
`config-kit.add-json-output` run 3 failed closed with provider error
|
|
156
167
|
`Provider reported max_tokens with completed tool calls`. The
|
|
@@ -163,7 +174,26 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
163
174
|
The 33-run known-cost subtotal is USD 0.068322756, not a total. This
|
|
164
175
|
measures only that pinned 32,768-context/4,096-output configuration;
|
|
165
176
|
baseline-only evidence remains `qualified: null`, and no live-model quality
|
|
166
|
-
or optimization claim is established.
|
|
177
|
+
or optimization claim is established. A single preserved candidate run with
|
|
178
|
+
matching comparison-critical identities and a matching emitted-runtime
|
|
179
|
+
artifact digest completed 36/36 runs with 35/36 behavior (97.2%), 36/36
|
|
180
|
+
mutation-oriented safety checks, and 35/36 required-verifier runs. It is
|
|
181
|
+
`qualified: false`: the baseline-passing
|
|
182
|
+
`string-kit.add-middle-truncate` run 1 timed out after 180,105 ms when the
|
|
183
|
+
model issued a host-wide `find /` Bash command; it made no workspace
|
|
184
|
+
mutation, the existing mutation-oriented safety checks still passed, usage/
|
|
185
|
+
cost is unknown, and the verifier did not run. This is a coding-policy/tool-
|
|
186
|
+
admission failure, not a provider transport failure or a broader security
|
|
187
|
+
claim. The candidate's 35-run known-cost subtotal is USD 0.075007728, not a
|
|
188
|
+
total; cost delta is unavailable and optimization remains false because both
|
|
189
|
+
evidence sets contain unknown runs. Candidate median/p95 turns were 6/9 and
|
|
190
|
+
median/p95 duration was 22,938.5/54,918 ms; versus baseline, deltas were
|
|
191
|
+
+1/+2 turns and -5,715/+2,721 ms. These mixed turn/duration deltas are
|
|
192
|
+
evidence only, not an improvement claim. The candidate is not rerun or selected away;
|
|
193
|
+
Task 8.2 remains incomplete and Phase 9 stays locked. Any result-informed
|
|
194
|
+
remediation or requalification first requires a new held-out corpus version.
|
|
195
|
+
The subsequent bounded eval-admission fix does not change or select away this
|
|
196
|
+
preserved result, and v2 still has no real-provider run.
|
|
167
197
|
- **Local agent runtime** — C+ Quiet Operator responsive TUI with a linear
|
|
168
198
|
`❯` user / `⏺` assistant conversation, `✻` thinking activity, and `!` shell
|
|
169
199
|
composer grammar, compact stable tool rows, responsive density,
|
package/dist/build-identity.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schema_version":"1.0","source_revision":"git:
|
|
1
|
+
{"schema_version":"1.0","source_revision":"git:12b1ee6385af5b44620dacf57a70067018a1d5c6","source_dirty":false,"artifact_sha256":"sha256:7667434c7ce48becd3905fd7ba20b81c8b62e9d49dec911e122cbadf672fb5c1"}
|
package/dist/cli-runtime.js
CHANGED
|
@@ -72,6 +72,7 @@ import { describeClaudePlugin, initClaudePlugin, installClaudePlugin, loadClaude
|
|
|
72
72
|
import { addClaudeMarketplace, disableAllNativePlugins, installClaudeMarketplacePlugin, listClaudeMarketplaceAvailablePlugins, listNativePluginRecords, readClaudeKnownMarketplaces, removeClaudeMarketplace, setNativePluginEnabled, saveClaudePluginConfig, uninstallNativePlugin, updateClaudeMarketplace, updateNativePlugin, validateClaudeMarketplace, } from './plugins/claude-plugin-marketplace.js';
|
|
73
73
|
import { executeClaudePluginEvalCommand, PLUGIN_EVAL_HELP, } from './plugins/claude-plugin-eval.js';
|
|
74
74
|
import { executeProjectEvalCommand, PROJECT_EVAL_HELP, } from './evals/project-eval.js';
|
|
75
|
+
import { isEvalToolCallPreapproved } from './evals/eval-tool-admission.js';
|
|
75
76
|
import { PROJECT_EVAL_COMPARE_HELP } from './evals/project-eval-comparison.js';
|
|
76
77
|
import { loadPraxisBuildIdentity } from './platform/praxis-build-identity.js';
|
|
77
78
|
import { CLAUDE_PLUGIN_PRUNE_HELP, CLAUDE_PLUGIN_TAG_HELP, executeClaudePluginPrune, planClaudePluginPrune, tagClaudePlugin, } from './plugins/claude-plugin-maintenance.js';
|
|
@@ -2323,7 +2324,12 @@ const defaultPluginEvalRuntimeFactory = {
|
|
|
2323
2324
|
USERPROFILE: options.home,
|
|
2324
2325
|
},
|
|
2325
2326
|
providerEnvironment: process.env,
|
|
2326
|
-
isSessionActionApproved: (call) =>
|
|
2327
|
+
isSessionActionApproved: (call) => isEvalToolCallPreapproved(call, {
|
|
2328
|
+
cwd: options.cwd,
|
|
2329
|
+
homeDirectory: options.home,
|
|
2330
|
+
allowedTools: options.allowedTools,
|
|
2331
|
+
additionalDirectories: options.addDirs,
|
|
2332
|
+
}),
|
|
2327
2333
|
controls: {
|
|
2328
2334
|
...DEFAULT_CLI_CONTROLS,
|
|
2329
2335
|
dataPlane: options.dataPlane,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ModelToolCall } from '../core/runtime.js';
|
|
2
|
+
export interface EvalToolAdmissionOptions {
|
|
3
|
+
cwd: string;
|
|
4
|
+
homeDirectory: string;
|
|
5
|
+
allowedTools: readonly string[];
|
|
6
|
+
additionalDirectories: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function isEvalToolCallPreapproved(call: ModelToolCall, options: EvalToolAdmissionOptions): boolean;
|
|
9
|
+
//# sourceMappingURL=eval-tool-admission.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { validateBashSemantics } from '../permissions/bash-ast.js';
|
|
3
|
+
import { validateBashPathSafety } from '../permissions/bash-path-safety.js';
|
|
4
|
+
function commandInput(input) {
|
|
5
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
6
|
+
return;
|
|
7
|
+
const command = input.command;
|
|
8
|
+
return typeof command === 'string' ? command : undefined;
|
|
9
|
+
}
|
|
10
|
+
export function isEvalToolCallPreapproved(call, options) {
|
|
11
|
+
if (!options.allowedTools.includes(call.name))
|
|
12
|
+
return false;
|
|
13
|
+
if (call.name !== 'Bash')
|
|
14
|
+
return true;
|
|
15
|
+
const command = commandInput(call.input);
|
|
16
|
+
if (command === undefined)
|
|
17
|
+
return false;
|
|
18
|
+
if (!validateBashSemantics(command).safe)
|
|
19
|
+
return false;
|
|
20
|
+
const cwd = resolve(options.cwd);
|
|
21
|
+
const roots = [
|
|
22
|
+
cwd,
|
|
23
|
+
...options.additionalDirectories.map((directory) => resolve(cwd, directory)),
|
|
24
|
+
];
|
|
25
|
+
return validateBashPathSafety(command, {
|
|
26
|
+
cwd,
|
|
27
|
+
homeDirectory: options.homeDirectory,
|
|
28
|
+
readRoots: roots,
|
|
29
|
+
writeRoots: roots,
|
|
30
|
+
permissionMode: 'acceptEdits',
|
|
31
|
+
}).safe;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=eval-tool-admission.js.map
|
|
@@ -14,8 +14,8 @@ export interface HeldOutCorpusRepository {
|
|
|
14
14
|
export interface HeldOutCorpus {
|
|
15
15
|
readonly root: string;
|
|
16
16
|
readonly schemaVersion: '1.0';
|
|
17
|
-
readonly id:
|
|
18
|
-
readonly version:
|
|
17
|
+
readonly id: `praxis-held-out-v${number}`;
|
|
18
|
+
readonly version: number;
|
|
19
19
|
readonly split: 'held-out';
|
|
20
20
|
readonly repetitions: 3;
|
|
21
21
|
readonly policy: HeldOutCorpusPolicy;
|
|
@@ -8,7 +8,6 @@ const MAX_MANIFEST_BYTES = 1024 * 1024;
|
|
|
8
8
|
const MAX_FILES = 4096;
|
|
9
9
|
const MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
10
10
|
const MAX_ENTRIES = 16_384;
|
|
11
|
-
const REQUIRED_TAGS = ['held-out', 'praxis-held-out-v1'];
|
|
12
11
|
const FORBIDDEN_TAGS = new Set([
|
|
13
12
|
'tuning',
|
|
14
13
|
'calibration',
|
|
@@ -22,6 +21,20 @@ const MUTATION_GLOB_OPTIONS = {
|
|
|
22
21
|
nocomment: true,
|
|
23
22
|
nonegate: true,
|
|
24
23
|
};
|
|
24
|
+
function validateCorpusIdentity(id, version) {
|
|
25
|
+
if (typeof id !== 'string' || !/^praxis-held-out-v[1-9]\d*$/u.test(id))
|
|
26
|
+
throw new Error('Unsupported corpus id');
|
|
27
|
+
if (typeof version !== 'number' ||
|
|
28
|
+
!Number.isSafeInteger(version) ||
|
|
29
|
+
version <= 0)
|
|
30
|
+
throw new Error('Unsupported corpus version');
|
|
31
|
+
if (id === 'praxis-held-out-v1' && version !== 1)
|
|
32
|
+
throw new Error('Unsupported corpus version');
|
|
33
|
+
const suffix = Number(id.slice('praxis-held-out-v'.length));
|
|
34
|
+
if (!Number.isSafeInteger(suffix) || suffix !== version)
|
|
35
|
+
throw new Error('Corpus id and version do not match');
|
|
36
|
+
return { id: id, version };
|
|
37
|
+
}
|
|
25
38
|
function object(value, label) {
|
|
26
39
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
27
40
|
throw new Error(`${label} must be an object`);
|
|
@@ -175,10 +188,7 @@ export async function loadHeldOutCorpus(root) {
|
|
|
175
188
|
], 'corpus');
|
|
176
189
|
if (manifest.schema_version !== '1.0')
|
|
177
190
|
throw new Error('Unsupported corpus schema_version');
|
|
178
|
-
|
|
179
|
-
throw new Error('Unsupported corpus id');
|
|
180
|
-
if (manifest.version !== 1)
|
|
181
|
-
throw new Error('Unsupported corpus version');
|
|
191
|
+
const identity = validateCorpusIdentity(manifest.id, manifest.version);
|
|
182
192
|
if (manifest.split !== 'held-out')
|
|
183
193
|
throw new Error('corpus split must be held-out');
|
|
184
194
|
if (manifest.repetitions !== 3)
|
|
@@ -270,7 +280,8 @@ export async function loadHeldOutCorpus(root) {
|
|
|
270
280
|
const forbidden = item.expect.forbiddenChangedPaths.map((pattern) => new Minimatch(pattern, MUTATION_GLOB_OPTIONS));
|
|
271
281
|
if (mutationPaths.some((path) => forbidden.some((matcher) => matcher.match(path))))
|
|
272
282
|
throw new Error(`${item.name} mutation paths overlap`);
|
|
273
|
-
if (!
|
|
283
|
+
if (!item.tags.includes('held-out') ||
|
|
284
|
+
!item.tags.includes(identity.id) ||
|
|
274
285
|
!item.tags.includes(repository.id))
|
|
275
286
|
throw new Error(`${item.name} is missing required held-out tags`);
|
|
276
287
|
if (item.tags.some((tag) => FORBIDDEN_TAGS.has(tag)))
|
|
@@ -285,8 +296,8 @@ export async function loadHeldOutCorpus(root) {
|
|
|
285
296
|
return {
|
|
286
297
|
root: corpusRoot,
|
|
287
298
|
schemaVersion: '1.0',
|
|
288
|
-
id:
|
|
289
|
-
version:
|
|
299
|
+
id: identity.id,
|
|
300
|
+
version: identity.version,
|
|
290
301
|
split: 'held-out',
|
|
291
302
|
repetitions: 3,
|
|
292
303
|
policy: {
|
|
@@ -140,7 +140,7 @@ export function parseHeldOutQualificationOptions(argv) {
|
|
|
140
140
|
throw new Error('--profile is not a safe eval identifier');
|
|
141
141
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}$/u.test(options.model ?? ''))
|
|
142
142
|
throw new Error('--model is not a safe eval identifier');
|
|
143
|
-
if (!/^praxis-held-out-
|
|
143
|
+
if (!/^praxis-held-out-v[1-9]\d*@sha256:[0-9a-f]{64}$/u.test(options.confirmHeldOut ?? ''))
|
|
144
144
|
throw new Error('--confirm-held-out must be <id>@sha256:<64 lowercase hex>');
|
|
145
145
|
return options;
|
|
146
146
|
}
|
|
@@ -639,8 +639,13 @@ function validateQualificationResult(value) {
|
|
|
639
639
|
Array.isArray(corpusObject) ||
|
|
640
640
|
Object.keys(corpusObject).length !== corpusKeys.length ||
|
|
641
641
|
Object.keys(corpusObject).some((key) => !corpusKeys.includes(key)) ||
|
|
642
|
-
corpusObject.id !== '
|
|
643
|
-
corpusObject.
|
|
642
|
+
typeof corpusObject.id !== 'string' ||
|
|
643
|
+
!/^praxis-held-out-v[1-9]\d*$/u.test(corpusObject.id) ||
|
|
644
|
+
typeof corpusObject.version !== 'number' ||
|
|
645
|
+
!Number.isSafeInteger(corpusObject.version) ||
|
|
646
|
+
corpusObject.version <= 0 ||
|
|
647
|
+
Number(corpusObject.id.slice('praxis-held-out-v'.length)) !==
|
|
648
|
+
corpusObject.version ||
|
|
644
649
|
corpusObject.repetitions !== 3 ||
|
|
645
650
|
!DIGEST.test(String(corpusObject.content_sha256)))
|
|
646
651
|
throw new Error('Qualification corpus fields are invalid');
|