@wix/pathgrade 0.29.0 → 0.31.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.
@@ -67,13 +67,18 @@ export function buildClaudeSdkOptions(inputs) {
67
67
  // then layer driver-owned hermetic overrides on top — `CLAUDE_CONFIG_DIR`
68
68
  // wins on collision so an upstream leak (or a user-supplied env value)
69
69
  // cannot weaken the per-trial isolation invariant.
70
+ const useLocalOAuth = inputs.runtimeEnv.PATHGRADE_CLAUDE_LOCAL_OAUTH === '1';
70
71
  const env = {};
71
72
  for (const [key, value] of Object.entries(inputs.runtimeEnv)) {
72
73
  if (value === undefined)
73
74
  continue;
75
+ if (key === 'PATHGRADE_CLAUDE_LOCAL_OAUTH')
76
+ continue;
74
77
  env[key] = value;
75
78
  }
76
- env.CLAUDE_CONFIG_DIR = path.join(inputs.workspacePath, CLAUDE_CONFIG_SUBDIR);
79
+ if (!useLocalOAuth) {
80
+ env.CLAUDE_CONFIG_DIR = path.join(inputs.workspacePath, CLAUDE_CONFIG_SUBDIR);
81
+ }
77
82
  opts.env = env;
78
83
  return opts;
79
84
  }
@@ -19,7 +19,8 @@
19
19
  */
20
20
  import * as path from 'path';
21
21
  import fs from 'fs-extra';
22
- import { formatReportMarkdown, MISSING_RESULTS_BODY, postOrUpdateComment, resolvePrContext, } from '../reporters/github-comment.js';
22
+ import { readSidecar } from '../affected/sidecar.js';
23
+ import { formatNoAffectedEvalsMarkdown, formatReportMarkdown, MISSING_RESULTS_BODY, postOrUpdateComment, resolvePrContext, } from '../reporters/github-comment.js';
23
24
  const DEFAULT_RESULTS_PATH = path.join('.pathgrade', 'results.json');
24
25
  /**
25
26
  * Resolve the comment-id used for the dedup marker.
@@ -85,6 +86,23 @@ export async function runReport(cwd, opts = {}) {
85
86
  loadError = err instanceof Error ? err.message : String(err);
86
87
  }
87
88
  if (loadError) {
89
+ const selection = await readSidecar(cwd, msg => {
90
+ console.error(`pathgrade report: ${msg}`);
91
+ });
92
+ if (selection && selection.selected.length === 0) {
93
+ const markdown = formatNoAffectedEvalsMarkdown(selection);
94
+ if (prContext) {
95
+ await postOrUpdateComment(prContext, {
96
+ commentId,
97
+ body: markdown,
98
+ });
99
+ console.log('0');
100
+ }
101
+ else {
102
+ printMarkdownAndPassRate(markdown, 0);
103
+ }
104
+ return;
105
+ }
88
106
  console.error(`pathgrade report: ${loadError}`);
89
107
  if (prContext) {
90
108
  await postOrUpdateComment(prContext, {
@@ -85,11 +85,19 @@ async function resolveClaude(userEnv, ports) {
85
85
  // Keychain OAuth token unusable — it's scoped to direct Anthropic, not to
86
86
  // a proxy. Skip the Keychain branch so the host-forward fallback wins.
87
87
  const hostSignalsProxy = !!ports.hostEnv('ANTHROPIC_BASE_URL');
88
- // On macOS, prefer Keychain OAuth (direct Anthropic token).
88
+ // On macOS, prefer Claude Code's native claude.ai OAuth. Do not expose
89
+ // the OAuth access token as ANTHROPIC_API_KEY: Claude Code treats that env
90
+ // var as an external API-key auth path, and first-party OAuth tokens are
91
+ // rejected there as invalid API keys.
89
92
  if (ports.platform === 'darwin' && !hostSignalsProxy) {
90
93
  const token = await ports.readKeychainToken('Claude Code-credentials');
91
94
  if (token) {
92
- return { env: { ANTHROPIC_API_KEY: token }, setupCommands: [], copyFromHome: [] };
95
+ return {
96
+ env: { PATHGRADE_CLAUDE_LOCAL_OAUTH: '1' },
97
+ setupCommands: [],
98
+ copyFromHome: ['.claude.json'],
99
+ linkFromHome: ['Library/Keychains'],
100
+ };
93
101
  }
94
102
  }
95
103
  // Fallback: forward host API key and base URL.
@@ -13,6 +13,7 @@ export interface FormatOptions {
13
13
  }
14
14
  /** Minimal body posted when `.pathgrade/results.json` is missing. */
15
15
  export declare const MISSING_RESULTS_BODY = "Pathgrade evals did not produce results. Check the workflow logs.";
16
+ export declare function formatNoAffectedEvalsMarkdown(selection: PathgradeSelectionReport): string;
16
17
  export declare function commentMarker(commentId: string): string;
17
18
  /**
18
19
  * Format a pathgrade PR comment as markdown.
@@ -10,6 +10,21 @@
10
10
  import * as fs from 'fs';
11
11
  /** Minimal body posted when `.pathgrade/results.json` is missing. */
12
12
  export const MISSING_RESULTS_BODY = 'Pathgrade evals did not produce results. Check the workflow logs.';
13
+ export function formatNoAffectedEvalsMarkdown(selection) {
14
+ const total = selection.selected.length + selection.skipped.length;
15
+ const lines = [];
16
+ lines.push('### Pathgrade report');
17
+ lines.push('');
18
+ lines.push('No affected evals found for this change set.');
19
+ lines.push('');
20
+ lines.push(`Base: \`${selection.base_ref}\` | Changed files: **${selection.changed_files_count}**`);
21
+ if (total > 0) {
22
+ lines.push('');
23
+ lines.push(formatSelectionSection(selection));
24
+ }
25
+ lines.push('');
26
+ return lines.join('\n');
27
+ }
13
28
  export function commentMarker(commentId) {
14
29
  return `<!-- pathgrade:${commentId} -->`;
15
30
  }
@@ -11,6 +11,9 @@ async function hydrateTraces(report, traceFile, resolved) {
11
11
  }
12
12
  export async function loadReports(resultsDir, opts) {
13
13
  const resolved = path.resolve(resultsDir);
14
+ if (!await fs.pathExists(resolved)) {
15
+ return [];
16
+ }
14
17
  const files = (await fs.readdir(resolved))
15
18
  .filter(f => f.endsWith('.json'))
16
19
  .reverse();
package/dist/sdk/agent.js CHANGED
@@ -91,6 +91,14 @@ class AgentImpl {
91
91
  const turns = maxTurns ?? 30;
92
92
  return Math.ceil((turns * 80_000 + 200_000) / 1000);
93
93
  }
94
+ accumulateTurnUsage(turnResult) {
95
+ if (turnResult.inputTokens || turnResult.outputTokens) {
96
+ this.llm.addTokens?.(turnResult.inputTokens ?? 0, turnResult.outputTokens ?? 0);
97
+ }
98
+ if (turnResult.costUsd !== undefined) {
99
+ this.llm.addCost?.(turnResult.costUsd);
100
+ }
101
+ }
94
102
  async executeLoggedTurnResult(session, message, turnNumber, kind) {
95
103
  const timestamp = () => new Date().toISOString();
96
104
  this._messages.push({ role: 'user', content: message });
@@ -103,6 +111,7 @@ class AgentImpl {
103
111
  this.verbose.turnStart({ turn: turnNumber, kind, message });
104
112
  const turnStart = Date.now();
105
113
  const turnResult = await session.executeTurn(message);
114
+ this.accumulateTurnUsage(turnResult);
106
115
  const response = getVisibleAssistantMessage(turnResult);
107
116
  const durationMs = Date.now() - turnStart;
108
117
  this._log.push(buildModelAgentResultLogEntry({
@@ -132,7 +141,16 @@ class AgentImpl {
132
141
  if (turnResult.timedOut) {
133
142
  throw new Error(`Agent (limit: ${timeoutSec}s) timed out (agent killed)`);
134
143
  }
135
- throw new Error(`Agent exited with code ${turnResult.exitCode}`);
144
+ // Surface the underlying detail (e.g. an ask-bus rejection message
145
+ // from the Claude SDK driver) and the typed `errorSubtype` so a
146
+ // failing `prompt()`/`startChat()` does not collapse to a bare
147
+ // "Agent exited with code 1" — matching what `runConversation`
148
+ // already does via `converse.ts:buildTurnExitError`.
149
+ const detail = turnResult.rawOutput?.trim();
150
+ const subtype = turnResult.errorSubtype;
151
+ const subtypeTag = subtype ? ` (${subtype})` : '';
152
+ const suffix = detail ? `: ${detail}` : '';
153
+ throw new Error(`Agent exited with code ${turnResult.exitCode}${subtypeTag}${suffix}`);
136
154
  }
137
155
  return turnResult;
138
156
  }
@@ -165,7 +183,11 @@ class AgentImpl {
165
183
  messages: this._messages,
166
184
  log: this._log,
167
185
  exec: (cmd) => this.exec(cmd),
168
- sendTurn: (message) => ms.executeTurn(message),
186
+ sendTurn: async (message) => {
187
+ const turnResult = await ms.executeTurn(message);
188
+ this.accumulateTurnUsage(turnResult);
189
+ return turnResult;
190
+ },
169
191
  verbose: this.verbose,
170
192
  });
171
193
  }
@@ -180,17 +202,10 @@ class AgentImpl {
180
202
  const sendTurn = async (message) => {
181
203
  const turnResult = await ms.executeTurn(message);
182
204
  turnNumber++;
183
- // Accumulate agent turn tokens (from CLI stream-json) on the shared tracker
184
- if (turnResult.inputTokens || turnResult.outputTokens) {
185
- this.llm.addTokens?.(turnResult.inputTokens ?? 0, turnResult.outputTokens ?? 0);
186
- }
187
- // Accumulate agent turn cost on the same shared tracker when
188
- // the upstream provider reports it (Claude SDK populates
189
- // `costUsd` from `total_cost_usd`; Codex/Cursor leave it
190
- // undefined, in which case this is a no-op).
191
- if (turnResult.costUsd !== undefined) {
192
- this.llm.addCost?.(turnResult.costUsd);
193
- }
205
+ // Accumulate agent turn tokens (from CLI stream-json) on the shared tracker.
206
+ // Cost is populated by Claude SDK (from `total_cost_usd`); Codex/Cursor leave
207
+ // it undefined, in which case this is a no-op.
208
+ this.accumulateTurnUsage(turnResult);
194
209
  for (const toolEvent of turnResult.toolEvents) {
195
210
  this._log.push({
196
211
  type: 'tool_event',
package/dist/sdk/chat.js CHANGED
@@ -78,7 +78,15 @@ export class ChatSessionImpl {
78
78
  });
79
79
  if (turnResult.exitCode !== 0) {
80
80
  this._done = true;
81
- throw new Error(`Agent exited with code ${turnResult.exitCode}`);
81
+ // Match `agent.ts:executeLoggedTurnResult` append the driver's
82
+ // captured `rawOutput` and typed `errorSubtype` so chat-session
83
+ // failures expose actionable detail (e.g. ask-bus rejection from
84
+ // the Claude SDK driver) instead of a bare exit-code string.
85
+ const detail = turnResult.rawOutput?.trim();
86
+ const subtype = turnResult.errorSubtype;
87
+ const subtypeTag = subtype ? ` (${subtype})` : '';
88
+ const suffix = detail ? `: ${detail}` : '';
89
+ throw new Error(`Agent exited with code ${turnResult.exitCode}${subtypeTag}${suffix}`);
82
90
  }
83
91
  this._turn = turnNumber;
84
92
  this._lastMessage = response;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/pathgrade",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "description": "Evaluate whether AI agents discover and use your skills correctly",
5
5
  "exports": {
6
6
  ".": {
@@ -84,5 +84,5 @@
84
84
  "typescript": "^5.9.3",
85
85
  "zod": "4.3.6"
86
86
  },
87
- "falconPackageHash": "d5f15991255bd9d6a592b9cf3624be70aea80aedb73561fee73bef2e"
87
+ "falconPackageHash": "b0a110f03d01c39e2f2677c5a1dc76d86cdcd6adba98e45f5ed56fbb"
88
88
  }