@wix/pathgrade 1.0.26 → 1.0.28

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.
Files changed (73) hide show
  1. package/README.md +14 -21
  2. package/dist/adapters/jest/invocation-adapter.js +5 -1
  3. package/dist/adapters/jest/reporter.js +4 -1
  4. package/dist/adapters/jest/results.js +8 -0
  5. package/dist/adapters/node-test/index.d.ts +5 -0
  6. package/dist/adapters/node-test/index.js +35 -7
  7. package/dist/adapters/node-test/invocation-adapter.js +3 -1
  8. package/dist/adapters/node-test/runner-adapter.js +22 -15
  9. package/dist/adapters/vitest/reporter.js +4 -1
  10. package/dist/agents/claude/sdk-message-projector.js +5 -0
  11. package/dist/agents/codex-app-server/agent.js +7 -57
  12. package/dist/agents/codex-app-server/turn-notifications.d.ts +6 -0
  13. package/dist/agents/codex-app-server/turn-notifications.js +51 -0
  14. package/dist/agents/codex-app-server/turn-state.d.ts +19 -0
  15. package/dist/agents/codex-app-server/turn-state.js +1 -0
  16. package/dist/agents/codex.js +1 -0
  17. package/dist/agents/cursor.js +1 -0
  18. package/dist/agents/opencode/protocol.d.ts +7 -0
  19. package/dist/agents/opencode/protocol.js +47 -0
  20. package/dist/agents/opencode.js +5 -49
  21. package/dist/analytics/engine.js +5 -2
  22. package/dist/commands/report.d.ts +10 -2
  23. package/dist/commands/report.js +41 -6
  24. package/dist/commands/run-args.d.ts +1 -0
  25. package/dist/commands/run-args.js +13 -0
  26. package/dist/commands/run-changed.js +5 -15
  27. package/dist/config/pathgrade.d.ts +3 -0
  28. package/dist/config/pathgrade.js +33 -1
  29. package/dist/pathgrade.js +32 -3
  30. package/dist/reporters/cli.js +13 -6
  31. package/dist/reporters/github-comment.d.ts +12 -3
  32. package/dist/reporters/github-comment.js +92 -18
  33. package/dist/reporters/loader.d.ts +1 -0
  34. package/dist/reporters/loader.js +27 -2
  35. package/dist/reporters/report-summary.js +13 -5
  36. package/dist/reporting/artifacts.js +5 -2
  37. package/dist/reporting/core.d.ts +1 -0
  38. package/dist/reporting/core.js +183 -105
  39. package/dist/reporting/types.d.ts +19 -3
  40. package/dist/runners/adapter-loader.js +17 -12
  41. package/dist/runners/direct-reporter-attempts.d.ts +1 -0
  42. package/dist/runners/direct-reporter-attempts.js +7 -0
  43. package/dist/runners/invocation.d.ts +2 -0
  44. package/dist/runners/model-builders.js +1 -0
  45. package/dist/runners/model-validation.js +31 -0
  46. package/dist/runners/model.d.ts +4 -0
  47. package/dist/runners/orchestrator.d.ts +2 -0
  48. package/dist/runners/orchestrator.js +11 -1
  49. package/dist/runners/repeated-attempts.d.ts +7 -0
  50. package/dist/runners/repeated-attempts.js +149 -0
  51. package/dist/runners/repeated-invocation.d.ts +7 -0
  52. package/dist/runners/repeated-invocation.js +129 -0
  53. package/dist/runners/report-projection.js +16 -6
  54. package/dist/runners/vitest-adapter.js +10 -0
  55. package/dist/runners/vitest-invocation.js +2 -0
  56. package/dist/sdk/agent-runtime-options.d.ts +12 -0
  57. package/dist/sdk/agent-runtime-options.js +67 -0
  58. package/dist/sdk/agent.js +11 -59
  59. package/dist/sdk/case-context.js +7 -2
  60. package/dist/sdk/evaluate.d.ts +2 -0
  61. package/dist/sdk/evaluate.js +14 -9
  62. package/dist/sdk/index.d.ts +2 -0
  63. package/dist/sdk/index.js +2 -0
  64. package/dist/sdk/lifecycle.js +16 -6
  65. package/dist/sdk/result-capture.js +4 -1
  66. package/dist/sdk/types.d.ts +2 -0
  67. package/dist/tool-event-results.d.ts +1 -1
  68. package/dist/tool-event-results.js +2 -1
  69. package/dist/tool-events.d.ts +5 -0
  70. package/dist/tool-events.js +5 -0
  71. package/dist/types.d.ts +34 -5
  72. package/dist/viewer.html +19 -19
  73. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { extractSkillsFromLog, extractToolEventsFromLog } from '../tool-events.js';
1
+ import { countShellCommandsFromLog, extractSkillsFromLog, extractToolEventsFromLog } from '../tool-events.js';
2
2
  import { getRuntime } from './eval-runtime.js';
3
3
  import { emitEvalResult } from './result-capture.js';
4
4
  import { runJudgePipeline } from './judge-pipeline.js';
@@ -33,7 +33,7 @@ export class EvalScorerError extends Error {
33
33
  // Local per-call: no module-level state, no cleanup export needed.
34
34
  function makeEvaluateAgent() {
35
35
  const conversationAttributed = new WeakSet();
36
- return async function evaluateAgent(agent, scorers, opts) {
36
+ return async function evaluateAgent(agent, scorers, opts, attributeConversation = true) {
37
37
  const toolEvents = extractToolEventsFromLog(agent.log);
38
38
  const ctx = {
39
39
  workspace: agent.workspace,
@@ -46,12 +46,12 @@ function makeEvaluateAgent() {
46
46
  };
47
47
  const trackedLLM = opts?.llm ?? agent.llm;
48
48
  // Snapshot conversation tokens BEFORE running scorers, for first-eval attribution.
49
- const before = trackedLLM.tokenUsage ?? { inputTokens: 0, outputTokens: 0 };
49
+ const before = agent.llm.tokenUsage ?? { inputTokens: 0, outputTokens: 0 };
50
50
  // Snapshot conversation cost too. AgentImpl's `sendTurn` accumulates
51
51
  // per-turn `costUsd` onto `trackedLLM` via `addCost`, so by the time
52
52
  // `evaluate()` runs the pre-evaluate cost is the conversation's
53
53
  // accumulated agent-turn cost.
54
- const beforeCostUsd = trackedLLM.costUsd ?? 0;
54
+ const beforeCostUsd = agent.llm.costUsd ?? 0;
55
55
  // measure() returns the delta consumed by this evaluate call.
56
56
  const scoringStartedAt = performance.now();
57
57
  const { result: evalResult, tokens: deltaTokenUsage } = trackedLLM.measure
@@ -60,12 +60,13 @@ function makeEvaluateAgent() {
60
60
  const r = await evaluateWithContext(ctx, scorers, { ...opts, llm: trackedLLM });
61
61
  return { result: r, tokens: r.tokenUsage ?? { inputTokens: 0, outputTokens: 0 } };
62
62
  })();
63
- // Attribute conversation tokens on the first evaluate() for this agent.
64
- const isFirstEval = !conversationAttributed.has(agent);
63
+ // Attribute conversation tokens on the first public evaluate() for this agent.
64
+ const isFirstEval = attributeConversation && !conversationAttributed.has(agent);
65
65
  const conversationTokens = isFirstEval && (before.inputTokens > 0 || before.outputTokens > 0)
66
66
  ? { conversation_input_tokens: before.inputTokens, conversation_output_tokens: before.outputTokens }
67
67
  : undefined;
68
- // Same first-eval attribution rule for cost. Omitted entirely when
68
+ // Agent usage is independent of the optional scorer LLM. Same
69
+ // first-eval attribution rule for cost; omit it when unavailable.
69
70
  // no conversation cost was captured (Codex / Cursor today).
70
71
  const conversationCost = isFirstEval && beforeCostUsd > 0
71
72
  ? { conversation_cost_usd: beforeCostUsd }
@@ -89,6 +90,10 @@ function makeEvaluateAgent() {
89
90
  };
90
91
  }
91
92
  const evaluateAgent = makeEvaluateAgent();
93
+ /** Internal runConversation hook: step scorers must not consume final-run attribution. */
94
+ export function evaluateStepScorers(agent, scorers, opts) {
95
+ return evaluateAgent(agent, scorers, opts, false);
96
+ }
92
97
  async function fromSnapshot(snapshotPath, scorers, opts) {
93
98
  const snapshot = await loadRunSnapshot(snapshotPath);
94
99
  const trackedLLM = opts?.llm ?? createLLMClient({ adapters: [{
@@ -268,7 +273,7 @@ function matchesArtifactPattern(artifactPath, pattern) {
268
273
  return pattern.test(artifactPath);
269
274
  }
270
275
  function buildTrialResult(log, result, scenarioEvidence, conversationTokens, conversationCost) {
271
- const nCommands = log.filter((entry) => entry.type === 'command').length;
276
+ const nCommands = countShellCommandsFromLog(log);
272
277
  const skills = extractSkillsFromLog(log);
273
278
  return {
274
279
  trial_id: 0,
@@ -300,7 +305,7 @@ function getProcessEnv() {
300
305
  }
301
306
  return env;
302
307
  }
303
- export const evaluate = Object.assign(evaluateAgent, {
308
+ export const evaluate = Object.assign((agent, scorers, opts) => evaluateAgent(agent, scorers, opts), {
304
309
  fromSnapshot,
305
310
  });
306
311
  function toTrialScorerResult(result) {
@@ -19,6 +19,8 @@ export { previewReactions } from './reaction-preview.js';
19
19
  export { setRuntime, resetRuntime } from './eval-runtime.js';
20
20
  export { DEFAULT_COPY_IGNORE } from '../providers/copy-filter.js';
21
21
  export { extractToolEventsFromLog } from '../tool-events.js';
22
+ export { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../tool-event-results.js';
23
+ export { parseEnvFile } from '../utils/env.js';
22
24
  export { createAskBus, requireAskBusForLiveBatches, AskBusTimeoutError } from './ask-bus/bus.js';
23
25
  export { toAskUserToolEvent } from './ask-bus/projection.js';
24
26
  export type { AskUserToolEvent, AskUserToolEventArguments, AskUserToolEventQuestionArgument, } from './ask-bus/projection.js';
package/dist/sdk/index.js CHANGED
@@ -17,6 +17,8 @@ export { previewReactions } from './reaction-preview.js';
17
17
  export { setRuntime, resetRuntime } from './eval-runtime.js';
18
18
  export { DEFAULT_COPY_IGNORE } from '../providers/copy-filter.js';
19
19
  export { extractToolEventsFromLog } from '../tool-events.js';
20
+ export { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../tool-event-results.js';
21
+ export { parseEnvFile } from '../utils/env.js';
20
22
  export { createAskBus, requireAskBusForLiveBatches, AskBusTimeoutError } from './ask-bus/bus.js';
21
23
  export { toAskUserToolEvent } from './ask-bus/projection.js';
22
24
  export { buildAskBatchLogEntries } from './agent-result-log.js';
@@ -1,8 +1,14 @@
1
1
  import { getCurrentCaseContext } from './case-context.js';
2
2
  import { buildDiagnosticsReport } from './diagnostics.js';
3
- const pendingAgents = new Set();
4
- const agentOwners = new WeakMap();
5
- const agentResults = new WeakMap();
3
+ import { countShellCommandsFromLog } from '../tool-events.js';
4
+ const lifecycleStateKey = Symbol.for('@wix/pathgrade/lifecycle-state');
5
+ const sharedLifecycle = globalThis;
6
+ const lifecycleState = sharedLifecycle[lifecycleStateKey] ?? (sharedLifecycle[lifecycleStateKey] = {
7
+ pendingAgents: new Set(),
8
+ agentOwners: new WeakMap(),
9
+ agentResults: new WeakMap(),
10
+ });
11
+ const { pendingAgents, agentOwners, agentResults } = lifecycleState;
6
12
  function currentAgentOwner() {
7
13
  const current = getCurrentCaseContext();
8
14
  if (current.status !== 'active')
@@ -131,7 +137,7 @@ async function flushCase(input) {
131
137
  function synthesizeTrialFromAgent(agent) {
132
138
  if (agent.log.length === 0)
133
139
  return null;
134
- const nCommands = agent.log.filter((entry) => entry.type === 'command').length;
140
+ const nCommands = countShellCommandsFromLog(agent.log);
135
141
  const tokenUsage = agent.llm.tokenUsage;
136
142
  const conversationEnd = [...agent.log].reverse().find((entry) => entry.type === 'conversation_end');
137
143
  const completionReason = conversationEnd?.completion_reason ?? (agent.log.some((entry) => entry.type === 'agent_result') ? 'completed' : undefined);
@@ -147,8 +153,12 @@ function synthesizeTrialFromAgent(agent) {
147
153
  n_commands: nCommands,
148
154
  input_tokens: 0,
149
155
  output_tokens: 0,
150
- conversation_input_tokens: tokenUsage?.inputTokens ?? 0,
151
- conversation_output_tokens: tokenUsage?.outputTokens ?? 0,
156
+ ...(tokenUsage && (tokenUsage.inputTokens > 0 || tokenUsage.outputTokens > 0)
157
+ ? {
158
+ conversation_input_tokens: tokenUsage.inputTokens,
159
+ conversation_output_tokens: tokenUsage.outputTokens,
160
+ }
161
+ : {}),
152
162
  session_log: [...agent.log],
153
163
  },
154
164
  diagnostics: buildDiagnosticsReport({
@@ -1,5 +1,8 @@
1
1
  import { getCurrentCaseContext } from './case-context.js';
2
- const observers = new Set();
2
+ const observerRegistryKey = Symbol.for('@wix/pathgrade/eval-result-observers');
3
+ const globalRegistry = globalThis;
4
+ const observers = globalRegistry[observerRegistryKey]
5
+ ?? (globalRegistry[observerRegistryKey] = new Set());
3
6
  export function subscribeToEvalResults(observer, options = {}) {
4
7
  if (options.owner === 'adapter' && options.key) {
5
8
  removeObserverByOwnerAndKey(options.owner, options.key);
@@ -17,6 +17,8 @@ export interface AgentExecutionMetadata {
17
17
  name: AgentName;
18
18
  /** Model override requested by the caller; not necessarily the provider-resolved model. */
19
19
  requestedModel?: string;
20
+ /** Concrete model observed or selected for the executed turn. */
21
+ resolvedModel?: string;
20
22
  transport?: AgentExecutionTransport;
21
23
  interactionMode?: AgentInteractionMode;
22
24
  }
@@ -1,6 +1,6 @@
1
1
  import type { ToolEventResult } from './tool-events.js';
2
2
  export declare const TOOL_RESULT_MAX_CHARS: number;
3
- export declare function collectSensitiveEnvValues(env?: Readonly<Record<string, string>>): string[];
3
+ export declare function collectSensitiveEnvValues(env?: Readonly<Record<string, string | undefined>>): string[];
4
4
  /**
5
5
  * Clone a persistence payload while removing secrets from both structured
6
6
  * containers and any other strings that repeat their values. The second pass
@@ -41,6 +41,7 @@ const NON_SECRET_ENVIRONMENT_KEY_NAMES = new Set(['tokencount', 'tokenizersparal
41
41
  const BOUNDARY_ONLY_SENSITIVE_VALUE_MAX_LENGTH = 1;
42
42
  export function collectSensitiveEnvValues(env) {
43
43
  return [...new Set(Object.entries(env ?? {})
44
+ .filter((entry) => typeof entry[1] === 'string')
44
45
  .filter(([key, value]) => isSecretEnvironmentKey(key) && value.length > 0)
45
46
  .map(([, value]) => value))]
46
47
  .sort((a, b) => b.length - a.length);
@@ -153,7 +154,7 @@ function redactCredentialShapes(value, sensitiveValues, explicitSensitiveValues)
153
154
  redacted = redacted.replace(/([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s/@]+(@)/gi, '$1<redacted>$2');
154
155
  }
155
156
  if (redacted.includes('=') || redacted.includes(':')) {
156
- redacted = redacted.replace(/(\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|passwd|session(?:id)?|cookie)\s*[=:]\s*)[^\s,;"'}]+/gi, '$1<redacted>');
157
+ redacted = redacted.replace(/(\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|token|secret|auth|password|passwd|session(?:id)?|cookie)\s*[=:]\s*)[^\s,;"'}]+/gi, '$1<redacted>');
157
158
  }
158
159
  return redacted;
159
160
  }
@@ -64,6 +64,11 @@ export declare function extractToolEventsFromLog(log: ReadonlyArray<{
64
64
  type: string;
65
65
  tool_event?: ToolEvent;
66
66
  }>): ToolEvent[];
67
+ /** Count shell commands across legacy command logs and provider tool events. */
68
+ export declare function countShellCommandsFromLog(log: ReadonlyArray<{
69
+ type: string;
70
+ tool_event?: ToolEvent;
71
+ }>): number;
67
72
  /**
68
73
  * Extract deduplicated skill names from tool events that have action 'use_skill'.
69
74
  */
@@ -101,6 +101,11 @@ export function extractToolEventsFromLog(log) {
101
101
  .filter((e) => e.type === 'tool_event' && e.tool_event != null)
102
102
  .map((e) => e.tool_event);
103
103
  }
104
+ /** Count shell commands across legacy command logs and provider tool events. */
105
+ export function countShellCommandsFromLog(log) {
106
+ return log.filter((entry) => entry.type === 'command'
107
+ || (entry.type === 'tool_event' && entry.tool_event?.action === 'run_shell')).length;
108
+ }
104
109
  /**
105
110
  * Extract deduplicated skill names from tool events that have action 'use_skill'.
106
111
  */
package/dist/types.d.ts CHANGED
@@ -150,6 +150,14 @@ export interface LogEntry {
150
150
  }
151
151
  export interface TrialResult {
152
152
  trial_id: number;
153
+ /** Stable semantic case identity in schema-v2 reports. */
154
+ case_id?: string;
155
+ /** Unique identity for one runner-owned execution of the case. */
156
+ attempt_id?: string;
157
+ /** One-based invocation index. */
158
+ attempt_index?: number;
159
+ /** Display-safe runner outcome for this specific attempt. */
160
+ runner_outcome?: import('./runners/model.js').AttemptOutcome['kind'];
153
161
  name?: string;
154
162
  reward?: number;
155
163
  scorer_results: ScorerResult[];
@@ -192,9 +200,22 @@ export interface TrialResult {
192
200
  }
193
201
  export interface EvalReport {
194
202
  task: string;
195
- pass_rate: number;
196
- pass_at_k: number;
197
- pass_pow_k: number;
203
+ /** Canonical status for this report or consolidated group when available. */
204
+ status?: 'pass' | 'fail';
205
+ /** Schema-v2 canonical average score. */
206
+ mean_reward?: number;
207
+ /** Binary success frequency, absent when attempts are ineligible. */
208
+ success_rate?: number;
209
+ /** Finite-sample pass@k values keyed by k in schema v2; number only for v1 reads. */
210
+ pass_at_k?: Record<string, number> | number;
211
+ pass_at_k_method?: 'finite_sample_unbiased';
212
+ pass_at_k_unavailable_reason?: PassAtKUnavailableReason;
213
+ eligible_case_count?: number;
214
+ attempts_per_case?: number;
215
+ /** @deprecated Version-1 artifact compatibility only. */
216
+ pass_rate?: number;
217
+ /** @deprecated Version-1 artifact compatibility only. New writers omit it. */
218
+ pass_pow_k?: number;
198
219
  trials: TrialResult[];
199
220
  skills_used: string[];
200
221
  }
@@ -208,7 +229,8 @@ export type StrippedTrialResult = Omit<TrialResult, 'session_log' | 'conversatio
208
229
  /**
209
230
  * Per-group entry in the consolidated `.pathgrade/results.json` report.
210
231
  */
211
- export type PathgradeGroupReport = Omit<EvalReport, 'trials'> & {
232
+ export type PathgradeGroupReport = Omit<EvalReport, 'trials' | 'status'> & {
233
+ status?: 'pass' | 'fail';
212
234
  trials: StrippedTrialResult[];
213
235
  /** Relative path from `.pathgrade/` to the trace file for this group. */
214
236
  trace_file: string;
@@ -239,12 +261,16 @@ export interface PathgradeSelectionReport {
239
261
  * external consumers can gate on schema revisions.
240
262
  */
241
263
  export interface PathgradeReport {
242
- version: 1;
264
+ version: 1 | 2;
243
265
  timestamp: string;
244
266
  /** `ci.threshold` from Pathgrade config or legacy plugin config, if configured. */
245
267
  threshold?: number;
246
268
  /** Weighted average of every individual trial score across all groups. */
247
269
  overall_pass_rate: number;
270
+ /** Canonical schema-v2 average reward. */
271
+ overall_mean_reward?: number;
272
+ attempts_requested?: number;
273
+ attempts_completed?: number;
248
274
  /**
249
275
  * Threshold check result. When `threshold` is set: `'pass'` iff
250
276
  * `overall_pass_rate >= threshold`. Otherwise: `'pass'` iff every trial
@@ -259,6 +285,7 @@ export interface PathgradeReport {
259
285
  */
260
286
  selection?: PathgradeSelectionReport;
261
287
  }
288
+ export type PassAtKUnavailableReason = 'non_binary_reward' | 'incomplete_attempts' | 'case_identity_mismatch';
262
289
  export interface TrialPaths {
263
290
  root: string;
264
291
  workspace: string;
@@ -291,6 +318,8 @@ export interface AgentTurnResult {
291
318
  timedOut?: boolean;
292
319
  toolEvents: import('./tool-events.js').ToolEvent[];
293
320
  runtimePoliciesApplied?: RuntimePolicyDescriptor[];
321
+ /** Concrete model that executed this turn, as resolved by the provider or runner. */
322
+ resolvedModel?: string;
294
323
  inputTokens?: number;
295
324
  outputTokens?: number;
296
325
  /**
package/dist/viewer.html CHANGED
@@ -838,12 +838,13 @@
838
838
  applyTheme(getTheme());
839
839
 
840
840
  // ─── State ─────────────────────────────────────────────────
841
- let reports = [];
842
- let sortKey = 'date';
843
- let sortDir = 'desc';
841
+ let reports = [], sortKey = 'date', sortDir = 'desc';
844
842
  const $ = id => document.getElementById(id);
845
843
  const esc = s => s ? String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : '';
846
-
844
+ const meanReward = r => r.mean_reward ?? r.pass_rate ?? 0;
845
+ const reportStatus = r => r.status === 'pass' || r.status === 'fail' ? r.status : (meanReward(r) >= 0.5 ? 'pass' : 'fail');
846
+ const passAtKSortValue = r => typeof r.pass_at_k === 'number' ? r.pass_at_k : (r.pass_at_k ? Math.max(...Object.values(r.pass_at_k)) : -1);
847
+ const passAtKDisplay = r => typeof r.pass_at_k === 'number' ? (r.pass_at_k * 100).toFixed(0) + '% v1' : r.pass_at_k ? Object.entries(r.pass_at_k).map(([k, value]) => '@' + k + ' ' + (value * 100).toFixed(0) + '%').join(' · ') : r.pass_at_k_unavailable_reason ? '— ' + r.pass_at_k_unavailable_reason.replaceAll('_', ' ') : '—';
847
848
  function parseDate(file) {
848
849
  // filename format: 2026-04-08T14-25-03-134Z → 2026-04-08T14:25:03.134
849
850
  const m = file.match(/(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d+)/);
@@ -877,10 +878,10 @@
877
878
  switch (sortKey) {
878
879
  case 'task': va = (a.task || '').toLowerCase(); vb = (b.task || '').toLowerCase(); break;
879
880
  case 'date': va = parseDate(a.file); vb = parseDate(b.file); break;
880
- case 'pass_rate': va = a.pass_rate || 0; vb = b.pass_rate || 0; break;
881
- case 'pass_at_k': va = a.pass_at_k ?? -1; vb = b.pass_at_k ?? -1; break;
882
- case 'pass_pow_k': va = a.pass_pow_k ?? -1; vb = b.pass_pow_k ?? -1; break;
883
- case 'status': va = a.pass_rate >= 0.5 ? 1 : 0; vb = b.pass_rate >= 0.5 ? 1 : 0; break;
881
+ case 'mean_reward': va = meanReward(a); vb = meanReward(b); break;
882
+ case 'success_rate': va = a.success_rate ?? -1; vb = b.success_rate ?? -1; break;
883
+ case 'pass_at_k': va = passAtKSortValue(a); vb = passAtKSortValue(b); break;
884
+ case 'status': va = reportStatus(a) === 'pass' ? 1 : 0; vb = reportStatus(b) === 'pass' ? 1 : 0; break;
884
885
  default: return 0;
885
886
  }
886
887
  if (va < vb) return sortDir === 'asc' ? -1 : 1;
@@ -936,24 +937,23 @@
936
937
  const header = `<div class="reports-header">
937
938
  ${colHeader('task', 'Task')}
938
939
  ${colHeader('date', 'Date')}
939
- ${colHeader('pass_rate', 'Pass Rate', 'center')}
940
+ ${colHeader('mean_reward', 'Mean Reward', 'center')}
941
+ ${colHeader('success_rate', 'Success Rate', 'center')}
940
942
  ${colHeader('pass_at_k', 'pass@k', 'center')}
941
- ${colHeader('pass_pow_k', 'pass^k', 'center')}
942
943
  ${colHeader('status', 'Status', 'center')}
943
944
  <div></div>
944
945
  </div>`;
945
946
 
946
947
  const rows = sorted.map(r => {
947
- const pr = (r.pass_rate * 100).toFixed(0);
948
- const isPass = r.pass_rate >= 0.5;
948
+ const reward = (meanReward(r) * 100).toFixed(0), isPass = reportStatus(r) === 'pass';
949
949
  const ts = r.timestamp || parseDate(r.file);
950
950
 
951
951
  return `<a class="report-row" onclick="navigate('detail','${esc(r.file)}')" href="javascript:void(0)">
952
952
  <div class="report-task">${esc(r.task)}</div>
953
953
  <div class="report-date">${formatDate(ts)}</div>
954
- <div class="report-metric"><div class="val">${pr}%</div></div>
955
- <div class="report-metric"><div class="val">${r.pass_at_k != null ? (r.pass_at_k * 100).toFixed(0) + '%' : '—'}</div></div>
956
- <div class="report-metric"><div class="val">${r.pass_pow_k != null ? (r.pass_pow_k * 100).toFixed(0) + '%' : '—'}</div></div>
954
+ <div class="report-metric"><div class="val">${reward}%</div></div>
955
+ <div class="report-metric"><div class="val">${r.success_rate != null ? (r.success_rate * 100).toFixed(0) + '%' : '—'}</div></div>
956
+ <div class="report-metric"><div class="val">${esc(passAtKDisplay(r))}</div></div>
957
957
  <div style="text-align:center"><span class="badge ${isPass ? 'badge-pass' : 'badge-fail'}">${isPass ? 'PASS' : 'FAIL'}</span></div>
958
958
  <button class="delete-btn" onclick="event.preventDefault();event.stopPropagation();deleteReport('${esc(r.file)}')" title="Delete report">&#x2715;</button>
959
959
  </a>`;
@@ -975,9 +975,9 @@
975
975
  const totalTokens = r.trials.reduce((s, t) => s + (t.input_tokens || 0) + (t.output_tokens || 0) + (t.conversation_input_tokens || 0) + (t.conversation_output_tokens || 0), 0);
976
976
 
977
977
  const stats = `
978
- <div class="stat-pill"><div class="val">${(r.pass_rate * 100).toFixed(1)}%</div><div class="lbl">Pass Rate</div></div>
979
- <div class="stat-pill"><div class="val">${r.pass_at_k != null ? (r.pass_at_k * 100).toFixed(1) + '%' : '—'}</div><div class="lbl">pass@k</div></div>
980
- <div class="stat-pill"><div class="val">${r.pass_pow_k != null ? (r.pass_pow_k * 100).toFixed(1) + '%' : '—'}</div><div class="lbl">pass^k</div></div>
978
+ <div class="stat-pill"><div class="val">${(meanReward(r) * 100).toFixed(1)}%</div><div class="lbl">Mean Reward</div></div>
979
+ <div class="stat-pill"><div class="val">${r.success_rate != null ? (r.success_rate * 100).toFixed(1) + '%' : '—'}</div><div class="lbl">Success Rate</div></div>
980
+ <div class="stat-pill"><div class="val">${esc(passAtKDisplay(r))}</div><div class="lbl">pass@k</div></div>
981
981
  <div class="stat-pill"><div class="val">${(avgDur / 1000).toFixed(1)}s</div><div class="lbl">Avg Duration</div></div>
982
982
  <div class="stat-pill"><div class="val">~${totalTokens}</div><div class="lbl">Total Tokens</div></div>
983
983
  <div class="stat-pill"><div class="val">${r.skills_used?.join(', ') || 'none'}</div><div class="lbl">Skills</div></div>
@@ -999,7 +999,7 @@
999
999
  }
1000
1000
  function renderTrial(t, idx) {
1001
1001
  const evaluated = t.reward !== undefined;
1002
- const pass = evaluated && t.reward >= 0.5;
1002
+ const pass = evaluated && (t.runner_outcome === undefined ? t.reward >= 0.5 : t.runner_outcome === 'passed' && t.reward === 1);
1003
1003
  const dur = ((t.duration_ms || 0) / 1000).toFixed(1);
1004
1004
  const tokens = (t.input_tokens || 0) + (t.output_tokens || 0);
1005
1005
  const convTokens = (t.conversation_input_tokens || 0) + (t.conversation_output_tokens || 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/pathgrade",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "packageManager": "yarn@4.12.0",
5
5
  "description": "Evaluate whether AI agents discover and use your skills correctly",
6
6
  "exports": {
@@ -140,5 +140,5 @@
140
140
  "typescript": "^5.9.3",
141
141
  "zod": "4.3.6"
142
142
  },
143
- "falconPackageHash": "23390180e5694ad3ec546fb637b601045310b46b4d7f10927a63e0db"
143
+ "falconPackageHash": "f8df1d8950a1ce263fb945ab13722fdb8328a2f761153de0df90d0e9"
144
144
  }