@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.
- package/dist/agents/claude/sdk-options.js +6 -1
- package/dist/commands/report.js +19 -1
- package/dist/providers/credentials.js +10 -2
- package/dist/reporters/github-comment.d.ts +1 -0
- package/dist/reporters/github-comment.js +15 -0
- package/dist/reporters/loader.js +3 -0
- package/dist/sdk/agent.js +28 -13
- package/dist/sdk/chat.js +9 -1
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/commands/report.js
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import * as path from 'path';
|
|
21
21
|
import fs from 'fs-extra';
|
|
22
|
-
import {
|
|
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
|
|
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 {
|
|
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
|
}
|
package/dist/reporters/loader.js
CHANGED
|
@@ -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
|
-
|
|
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) =>
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
87
|
+
"falconPackageHash": "b0a110f03d01c39e2f2677c5a1dc76d86cdcd6adba98e45f5ed56fbb"
|
|
88
88
|
}
|