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 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. A bounded DeepSeek baseline for the
153
- exact `anthropic/default/deepseek-v4-flash` pin completed 36/36 runs with
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,
@@ -1 +1 @@
1
- {"schema_version":"1.0","source_revision":"git:1f78926ebc463fed5e2d7a0f22f49c9569171350","source_dirty":false,"artifact_sha256":"sha256:46543a71fca8edab43c1a8ce3259ec7459bc8e31cdeec7f5028c049d7680350a"}
1
+ {"schema_version":"1.0","source_revision":"git:12b1ee6385af5b44620dacf57a70067018a1d5c6","source_dirty":false,"artifact_sha256":"sha256:7667434c7ce48becd3905fd7ba20b81c8b62e9d49dec911e122cbadf672fb5c1"}
@@ -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) => options.allowedTools.includes(call.name),
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: 'praxis-held-out-v1';
18
- readonly version: 1;
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
- 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');
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 (!REQUIRED_TAGS.every((tag) => item.tags.includes(tag)) ||
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: 'praxis-held-out-v1',
289
- version: 1,
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-v1@sha256:[0-9a-f]{64}$/u.test(options.confirmHeldOut ?? ''))
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 !== 'praxis-held-out-v1' ||
643
- corpusObject.version !== 1 ||
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.69.0",
3
+ "version": "0.69.1",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",