@wix/pathgrade 0.29.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.
Files changed (238) hide show
  1. package/bin/pathgrade.js +2 -0
  2. package/dist/affected/anchor.d.ts +18 -0
  3. package/dist/affected/anchor.js +42 -0
  4. package/dist/affected/config.d.ts +25 -0
  5. package/dist/affected/config.js +98 -0
  6. package/dist/affected/format.d.ts +45 -0
  7. package/dist/affected/format.js +87 -0
  8. package/dist/affected/git.d.ts +33 -0
  9. package/dist/affected/git.js +68 -0
  10. package/dist/affected/glob.d.ts +17 -0
  11. package/dist/affected/glob.js +25 -0
  12. package/dist/affected/meta.d.ts +26 -0
  13. package/dist/affected/meta.js +158 -0
  14. package/dist/affected/select.d.ts +30 -0
  15. package/dist/affected/select.js +101 -0
  16. package/dist/affected/sidecar.d.ts +32 -0
  17. package/dist/affected/sidecar.js +86 -0
  18. package/dist/affected/types.d.ts +43 -0
  19. package/dist/affected/types.js +7 -0
  20. package/dist/agents/claude/ask-user-answer-store.d.ts +28 -0
  21. package/dist/agents/claude/ask-user-answer-store.js +29 -0
  22. package/dist/agents/claude/ask-user-bridge.d.ts +56 -0
  23. package/dist/agents/claude/ask-user-bridge.js +162 -0
  24. package/dist/agents/claude/sdk-message-projector.d.ts +43 -0
  25. package/dist/agents/claude/sdk-message-projector.js +208 -0
  26. package/dist/agents/claude/sdk-options.d.ts +52 -0
  27. package/dist/agents/claude/sdk-options.js +79 -0
  28. package/dist/agents/claude.d.ts +56 -0
  29. package/dist/agents/claude.js +143 -0
  30. package/dist/agents/codex-app-server/agent.d.ts +35 -0
  31. package/dist/agents/codex-app-server/agent.js +388 -0
  32. package/dist/agents/codex-app-server/fixtures/app-server-client.d.ts +19 -0
  33. package/dist/agents/codex-app-server/fixtures/app-server-client.js +104 -0
  34. package/dist/agents/codex-app-server/fixtures/run-gate.d.ts +25 -0
  35. package/dist/agents/codex-app-server/fixtures/run-gate.js +48 -0
  36. package/dist/agents/codex-app-server/protocol/ClientRequest.d.ts +27 -0
  37. package/dist/agents/codex-app-server/protocol/ClientRequest.js +7 -0
  38. package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.d.ts +9 -0
  39. package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.js +4 -0
  40. package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.d.ts +6 -0
  41. package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.js +7 -0
  42. package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.d.ts +23 -0
  43. package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.js +8 -0
  44. package/dist/agents/codex-app-server/protocol/Op.d.ts +11 -0
  45. package/dist/agents/codex-app-server/protocol/Op.js +5 -0
  46. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.d.ts +10 -0
  47. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.js +6 -0
  48. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.d.ts +15 -0
  49. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.js +4 -0
  50. package/dist/agents/codex-app-server/protocol/SandboxMode.d.ts +1 -0
  51. package/dist/agents/codex-app-server/protocol/SandboxMode.js +4 -0
  52. package/dist/agents/codex-app-server/protocol/ServerRequest.d.ts +42 -0
  53. package/dist/agents/codex-app-server/protocol/ServerRequest.js +7 -0
  54. package/dist/agents/codex-app-server/protocol/ThreadStartParams.d.ts +45 -0
  55. package/dist/agents/codex-app-server/protocol/ThreadStartParams.js +10 -0
  56. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.d.ts +6 -0
  57. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.js +4 -0
  58. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.d.ts +7 -0
  59. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.js +4 -0
  60. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.d.ts +10 -0
  61. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.js +4 -0
  62. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.d.ts +12 -0
  63. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.js +4 -0
  64. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.d.ts +9 -0
  65. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.js +4 -0
  66. package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.d.ts +5 -0
  67. package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.js +4 -0
  68. package/dist/agents/codex-app-server/protocol/index.d.ts +16 -0
  69. package/dist/agents/codex-app-server/protocol/index.js +11 -0
  70. package/dist/agents/codex-app-server/transport.d.ts +84 -0
  71. package/dist/agents/codex-app-server/transport.js +246 -0
  72. package/dist/agents/codex-app-server/wire-translators.d.ts +19 -0
  73. package/dist/agents/codex-app-server/wire-translators.js +52 -0
  74. package/dist/agents/codex.d.ts +7 -0
  75. package/dist/agents/codex.js +164 -0
  76. package/dist/agents/cursor.d.ts +39 -0
  77. package/dist/agents/cursor.js +233 -0
  78. package/dist/agents/registry.d.ts +17 -0
  79. package/dist/agents/registry.js +27 -0
  80. package/dist/agents/transcript-agent.d.ts +14 -0
  81. package/dist/agents/transcript-agent.js +59 -0
  82. package/dist/analytics/engine.d.ts +18 -0
  83. package/dist/analytics/engine.js +66 -0
  84. package/dist/commands/affected.d.ts +31 -0
  85. package/dist/commands/affected.js +150 -0
  86. package/dist/commands/analyze.d.ts +21 -0
  87. package/dist/commands/analyze.js +167 -0
  88. package/dist/commands/init.d.ts +3 -0
  89. package/dist/commands/init.js +242 -0
  90. package/dist/commands/preview-reactions.d.ts +10 -0
  91. package/dist/commands/preview-reactions.js +88 -0
  92. package/dist/commands/preview.d.ts +5 -0
  93. package/dist/commands/preview.js +17 -0
  94. package/dist/commands/report.d.ts +39 -0
  95. package/dist/commands/report.js +108 -0
  96. package/dist/commands/run-args.d.ts +22 -0
  97. package/dist/commands/run-args.js +69 -0
  98. package/dist/commands/run-changed.d.ts +26 -0
  99. package/dist/commands/run-changed.js +138 -0
  100. package/dist/commands/validate.d.ts +13 -0
  101. package/dist/commands/validate.js +233 -0
  102. package/dist/core/mcp-mock.d.ts +3 -0
  103. package/dist/core/mcp-mock.js +23 -0
  104. package/dist/core/mcp-mock.types.d.ts +15 -0
  105. package/dist/core/mcp-mock.types.js +1 -0
  106. package/dist/core/skills.d.ts +15 -0
  107. package/dist/core/skills.js +76 -0
  108. package/dist/evals/discovery.d.ts +7 -0
  109. package/dist/evals/discovery.js +69 -0
  110. package/dist/mcp-mock-server.d.ts +1 -0
  111. package/dist/mcp-mock-server.js +116 -0
  112. package/dist/pathgrade.d.ts +10 -0
  113. package/dist/pathgrade.js +243 -0
  114. package/dist/plugin/index.d.ts +9 -0
  115. package/dist/plugin/index.js +81 -0
  116. package/dist/plugin/lifecycle.d.ts +45 -0
  117. package/dist/plugin/lifecycle.js +146 -0
  118. package/dist/plugin/reporter.d.ts +36 -0
  119. package/dist/plugin/reporter.js +275 -0
  120. package/dist/plugin/setup.d.ts +1 -0
  121. package/dist/plugin/setup.js +3 -0
  122. package/dist/providers/copy-filter.d.ts +18 -0
  123. package/dist/providers/copy-filter.js +66 -0
  124. package/dist/providers/credentials.d.ts +36 -0
  125. package/dist/providers/credentials.js +202 -0
  126. package/dist/providers/mcp-config.d.ts +34 -0
  127. package/dist/providers/mcp-config.js +55 -0
  128. package/dist/providers/sandbox-exec.d.ts +8 -0
  129. package/dist/providers/sandbox-exec.js +82 -0
  130. package/dist/providers/sandbox.d.ts +22 -0
  131. package/dist/providers/sandbox.js +158 -0
  132. package/dist/providers/sandboxed-claude-spawn.d.ts +24 -0
  133. package/dist/providers/sandboxed-claude-spawn.js +57 -0
  134. package/dist/providers/workspace.d.ts +15 -0
  135. package/dist/providers/workspace.js +73 -0
  136. package/dist/reporters/browser.d.ts +2 -0
  137. package/dist/reporters/browser.js +85 -0
  138. package/dist/reporters/cli.d.ts +2 -0
  139. package/dist/reporters/cli.js +131 -0
  140. package/dist/reporters/diagnostics.d.ts +46 -0
  141. package/dist/reporters/diagnostics.js +147 -0
  142. package/dist/reporters/github-comment.d.ts +67 -0
  143. package/dist/reporters/github-comment.js +252 -0
  144. package/dist/reporters/loader.d.ts +8 -0
  145. package/dist/reporters/loader.js +36 -0
  146. package/dist/reporters/results-path.d.ts +2 -0
  147. package/dist/reporters/results-path.js +10 -0
  148. package/dist/reporters/verbose-emitter.d.ts +69 -0
  149. package/dist/reporters/verbose-emitter.js +103 -0
  150. package/dist/sdk/agent-crash.d.ts +19 -0
  151. package/dist/sdk/agent-crash.js +20 -0
  152. package/dist/sdk/agent-resolution.d.ts +12 -0
  153. package/dist/sdk/agent-resolution.js +21 -0
  154. package/dist/sdk/agent-result-log.d.ts +19 -0
  155. package/dist/sdk/agent-result-log.js +45 -0
  156. package/dist/sdk/agent.d.ts +4 -0
  157. package/dist/sdk/agent.js +354 -0
  158. package/dist/sdk/ask-bus/bus.d.ts +21 -0
  159. package/dist/sdk/ask-bus/bus.js +129 -0
  160. package/dist/sdk/ask-bus/handler.d.ts +31 -0
  161. package/dist/sdk/ask-bus/handler.js +244 -0
  162. package/dist/sdk/ask-bus/parsers.d.ts +9 -0
  163. package/dist/sdk/ask-bus/parsers.js +30 -0
  164. package/dist/sdk/ask-bus/projection.d.ts +26 -0
  165. package/dist/sdk/ask-bus/projection.js +45 -0
  166. package/dist/sdk/ask-bus/types.d.ts +69 -0
  167. package/dist/sdk/ask-bus/types.js +1 -0
  168. package/dist/sdk/chat.d.ts +29 -0
  169. package/dist/sdk/chat.js +93 -0
  170. package/dist/sdk/conversation-window.d.ts +14 -0
  171. package/dist/sdk/conversation-window.js +38 -0
  172. package/dist/sdk/converse.d.ts +42 -0
  173. package/dist/sdk/converse.js +329 -0
  174. package/dist/sdk/eval-runtime.d.ts +10 -0
  175. package/dist/sdk/eval-runtime.js +17 -0
  176. package/dist/sdk/evaluate.d.ts +13 -0
  177. package/dist/sdk/evaluate.js +325 -0
  178. package/dist/sdk/index.d.ts +31 -0
  179. package/dist/sdk/index.js +20 -0
  180. package/dist/sdk/judge-pipeline.d.ts +7 -0
  181. package/dist/sdk/judge-pipeline.js +178 -0
  182. package/dist/sdk/judge-prompt-builder.d.ts +7 -0
  183. package/dist/sdk/judge-prompt-builder.js +76 -0
  184. package/dist/sdk/judge-tool-runner.d.ts +19 -0
  185. package/dist/sdk/judge-tool-runner.js +51 -0
  186. package/dist/sdk/judge-tool-session.d.ts +47 -0
  187. package/dist/sdk/judge-tool-session.js +213 -0
  188. package/dist/sdk/judge-tools.d.ts +29 -0
  189. package/dist/sdk/judge-tools.js +283 -0
  190. package/dist/sdk/managed-session.d.ts +50 -0
  191. package/dist/sdk/managed-session.js +110 -0
  192. package/dist/sdk/persona.d.ts +5 -0
  193. package/dist/sdk/persona.js +33 -0
  194. package/dist/sdk/reaction-loader.d.ts +4 -0
  195. package/dist/sdk/reaction-loader.js +119 -0
  196. package/dist/sdk/reaction-preview.d.ts +6 -0
  197. package/dist/sdk/reaction-preview.js +94 -0
  198. package/dist/sdk/run-scorer.d.ts +10 -0
  199. package/dist/sdk/run-scorer.js +124 -0
  200. package/dist/sdk/runtime-policy.d.ts +21 -0
  201. package/dist/sdk/runtime-policy.js +89 -0
  202. package/dist/sdk/scorer-utils.d.ts +6 -0
  203. package/dist/sdk/scorer-utils.js +39 -0
  204. package/dist/sdk/scorers.d.ts +64 -0
  205. package/dist/sdk/scorers.js +80 -0
  206. package/dist/sdk/snapshots.d.ts +41 -0
  207. package/dist/sdk/snapshots.js +148 -0
  208. package/dist/sdk/types.d.ts +416 -0
  209. package/dist/sdk/types.js +17 -0
  210. package/dist/sdk/visible-turn.d.ts +4 -0
  211. package/dist/sdk/visible-turn.js +23 -0
  212. package/dist/tool-events.d.ts +45 -0
  213. package/dist/tool-events.js +167 -0
  214. package/dist/types.d.ts +372 -0
  215. package/dist/types.js +37 -0
  216. package/dist/utils/cli.d.ts +55 -0
  217. package/dist/utils/cli.js +131 -0
  218. package/dist/utils/env.d.ts +8 -0
  219. package/dist/utils/env.js +26 -0
  220. package/dist/utils/llm-mocks.d.ts +43 -0
  221. package/dist/utils/llm-mocks.js +108 -0
  222. package/dist/utils/llm-providers/anthropic.d.ts +2 -0
  223. package/dist/utils/llm-providers/anthropic.js +132 -0
  224. package/dist/utils/llm-providers/cli.d.ts +20 -0
  225. package/dist/utils/llm-providers/cli.js +162 -0
  226. package/dist/utils/llm-providers/openai.d.ts +2 -0
  227. package/dist/utils/llm-providers/openai.js +56 -0
  228. package/dist/utils/llm-types.d.ts +107 -0
  229. package/dist/utils/llm-types.js +1 -0
  230. package/dist/utils/llm.d.ts +36 -0
  231. package/dist/utils/llm.js +209 -0
  232. package/dist/utils/shutdown.d.ts +15 -0
  233. package/dist/utils/shutdown.js +41 -0
  234. package/dist/utils/timeout.d.ts +8 -0
  235. package/dist/utils/timeout.js +32 -0
  236. package/dist/viewer.html +1178 -0
  237. package/package.json +88 -0
  238. package/templates/eval.ts.template +28 -0
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pathgrade CLI — thin wrapper around vitest
4
+ *
5
+ * Usage:
6
+ * pathgrade run [-- vitest-args] Run evals via vitest (loads .env, validates API keys)
7
+ * pathgrade init [--force] Generate eval scaffolding
8
+ * pathgrade preview [browser] View results (CLI default, or browser)
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { spawn } from 'child_process';
13
+ import { runInit } from './commands/init.js';
14
+ import { runAnalyze } from './commands/analyze.js';
15
+ import { runValidate, runValidateAffected } from './commands/validate.js';
16
+ import { parsePathgradeRunArgs } from './commands/run-args.js';
17
+ import { runPreview } from './commands/preview.js';
18
+ import { runPreviewReactions } from './commands/preview-reactions.js';
19
+ import { runReport } from './commands/report.js';
20
+ import { runAffected } from './commands/affected.js';
21
+ import { runChanged } from './commands/run-changed.js';
22
+ import { clearSidecar } from './affected/sidecar.js';
23
+ import { fmt } from './utils/cli.js';
24
+ import { shutdown } from './utils/shutdown.js';
25
+ function loadDotenv() {
26
+ const envPath = path.resolve(process.cwd(), '.env');
27
+ if (!fs.existsSync(envPath))
28
+ return;
29
+ const content = fs.readFileSync(envPath, 'utf-8');
30
+ for (const line of content.split('\n')) {
31
+ const trimmed = line.trim();
32
+ if (!trimmed || trimmed.startsWith('#'))
33
+ continue;
34
+ const eqIdx = trimmed.indexOf('=');
35
+ if (eqIdx === -1)
36
+ continue;
37
+ const key = trimmed.slice(0, eqIdx).trim();
38
+ let value = trimmed.slice(eqIdx + 1).trim();
39
+ // Strip surrounding quotes
40
+ if ((value.startsWith('"') && value.endsWith('"')) ||
41
+ (value.startsWith("'") && value.endsWith("'"))) {
42
+ value = value.slice(1, -1);
43
+ }
44
+ if (!(key in process.env)) {
45
+ process.env[key] = value;
46
+ }
47
+ }
48
+ }
49
+ function validateApiKeys() {
50
+ const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
51
+ const hasOpenAI = !!process.env.OPENAI_API_KEY;
52
+ const hasClaude = !!process.env.HOME; // Claude CLI uses OS keychain, just check it exists
53
+ if (!hasAnthropic && !hasOpenAI) {
54
+ console.log(`\n ${fmt.dim('warning:')} No API keys found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY in .env or environment.\n` +
55
+ ` ${fmt.dim(' Claude CLI auth (keychain) and Codex cached login (~/.codex/auth.json) may still work if installed.')}\n`);
56
+ }
57
+ }
58
+ async function main() {
59
+ shutdown.install();
60
+ const args = process.argv.slice(2);
61
+ const command = args[0];
62
+ if (command === '--help' || command === '-h') {
63
+ printHelp();
64
+ return;
65
+ }
66
+ if (command === '--version' || command === '-v') {
67
+ const pkg = JSON.parse(await import('fs').then(fs => fs.promises.readFile(new URL('../package.json', import.meta.url), 'utf-8')));
68
+ console.log(pkg.version);
69
+ return;
70
+ }
71
+ if (command === 'analyze') {
72
+ const skillFlag = args.find(a => a.startsWith('--skill='));
73
+ const dirFlag = args.find(a => a.startsWith('--dir='));
74
+ const skill = skillFlag ? skillFlag.split('=')[1] : undefined;
75
+ const dir = dirFlag ? dirFlag.split('=')[1] : undefined;
76
+ const exitCode = await runAnalyze(process.cwd(), { skill, dir });
77
+ process.exitCode = exitCode;
78
+ return;
79
+ }
80
+ if (command === 'validate') {
81
+ const validateArgs = args.slice(1);
82
+ if (validateArgs.includes('--affected')) {
83
+ const exitCode = await runValidateAffected(process.cwd());
84
+ process.exitCode = exitCode;
85
+ return;
86
+ }
87
+ const filePath = validateArgs[0];
88
+ if (!filePath) {
89
+ console.error('Usage: pathgrade validate <file.eval.ts> | pathgrade validate --affected');
90
+ process.exitCode = 1;
91
+ return;
92
+ }
93
+ const exitCode = await runValidate(path.resolve(filePath));
94
+ process.exitCode = exitCode;
95
+ return;
96
+ }
97
+ if (command === 'init') {
98
+ const hasForce = args.includes('--force');
99
+ await runInit(process.cwd(), { force: hasForce });
100
+ return;
101
+ }
102
+ if (command === 'preview') {
103
+ const previewArgs = args.slice(1);
104
+ const mode = previewArgs.includes('browser') ? 'browser' : 'cli';
105
+ const lastFlag = previewArgs.find(a => a.startsWith('--last='));
106
+ const filterFlag = previewArgs.find(a => a.startsWith('--filter='));
107
+ const last = lastFlag ? parseInt(lastFlag.split('=')[1], 10) : undefined;
108
+ const filter = filterFlag ? filterFlag.split('=')[1] : undefined;
109
+ await runPreview(process.cwd(), mode, undefined, { last, filter });
110
+ return;
111
+ }
112
+ if (command === 'preview-reactions') {
113
+ await runPreviewReactions(args.slice(1));
114
+ return;
115
+ }
116
+ if (command === 'affected') {
117
+ const affectedArgs = args.slice(1);
118
+ const changedFilesFlag = affectedArgs.find(a => a.startsWith('--changed-files='));
119
+ const sinceFlag = affectedArgs.find(a => a.startsWith('--since='));
120
+ const changedFilesPath = changedFilesFlag
121
+ ? changedFilesFlag.split('=').slice(1).join('=')
122
+ : undefined;
123
+ const since = sinceFlag ? sinceFlag.split('=').slice(1).join('=') : undefined;
124
+ const explain = affectedArgs.includes('--explain');
125
+ const json = affectedArgs.includes('--json');
126
+ const exitCode = await runAffected({
127
+ cwd: process.cwd(),
128
+ changedFilesPath,
129
+ since,
130
+ explain,
131
+ json,
132
+ });
133
+ process.exitCode = exitCode;
134
+ return;
135
+ }
136
+ if (command === 'report') {
137
+ const reportArgs = args.slice(1);
138
+ const resultsPathFlag = reportArgs.find(a => a.startsWith('--results-path='));
139
+ const commentIdFlag = reportArgs.find(a => a.startsWith('--comment-id='));
140
+ const noComment = reportArgs.includes('--no-comment');
141
+ const resultsPath = resultsPathFlag ? resultsPathFlag.split('=').slice(1).join('=') : undefined;
142
+ const commentId = commentIdFlag ? commentIdFlag.split('=').slice(1).join('=') : undefined;
143
+ await runReport(process.cwd(), { resultsPath, commentId, noComment });
144
+ return;
145
+ }
146
+ if (command === 'run' || !command || command.startsWith('-')) {
147
+ // pathgrade run [--changed [--since=…|--changed-files=…]] [--] [vitest-args]
148
+ loadDotenv();
149
+ validateApiKeys();
150
+ const parsed = parsePathgradeRunArgs(command === 'run' ? args.slice(1) : args);
151
+ for (const warning of parsed.warnings ?? []) {
152
+ console.error(`pathgrade: ${warning}`);
153
+ }
154
+ if (parsed.changed) {
155
+ const exitCode = await runChanged({
156
+ cwd: process.cwd(),
157
+ parsed,
158
+ spawnVitest: ({ argv }) => new Promise(resolve => {
159
+ const child = spawn('npx', ['vitest', ...argv], {
160
+ stdio: 'inherit',
161
+ env: {
162
+ ...process.env,
163
+ ...(parsed.forceDiagnostics ? { PATHGRADE_DIAGNOSTICS: '1' } : {}),
164
+ ...(parsed.forceVerbose ? { PATHGRADE_VERBOSE: '1' } : {}),
165
+ },
166
+ shell: true,
167
+ });
168
+ child.on('close', code => resolve(code ?? 0));
169
+ }),
170
+ });
171
+ process.exitCode = exitCode;
172
+ return;
173
+ }
174
+ // Plain `pathgrade run` — clear any stale selection sidecar from a
175
+ // previous `--changed` run so it doesn't leak into this full-suite
176
+ // run (the reporter would otherwise merge old metadata).
177
+ await clearSidecar(process.cwd());
178
+ const child = spawn('npx', ['vitest', 'run', ...parsed.vitestArgs], {
179
+ stdio: 'inherit',
180
+ env: {
181
+ ...process.env,
182
+ ...(parsed.forceDiagnostics ? { PATHGRADE_DIAGNOSTICS: '1' } : {}),
183
+ ...(parsed.forceVerbose ? { PATHGRADE_VERBOSE: '1' } : {}),
184
+ },
185
+ shell: true,
186
+ });
187
+ child.on('close', (code) => {
188
+ process.exitCode = code ?? 0;
189
+ });
190
+ return;
191
+ }
192
+ console.error(`Unknown command: ${command}`);
193
+ console.error('Run "pathgrade --help" for usage.');
194
+ process.exitCode = 1;
195
+ }
196
+ function printHelp() {
197
+ console.log(`
198
+ pathgrade - Evaluate AI agent skills with vitest
199
+
200
+ Usage:
201
+ pathgrade run [-- vitest-args] Run evals (loads .env, delegates to vitest)
202
+ [--changed] Run only evals affected by the current PR/change-set
203
+ [--since=<ref>] Override base ref (implies git mode)
204
+ [--changed-files=<path>] Use an explicit newline-delimited file list
205
+ [--quiet] Suppress the run-start summary
206
+ [--verbose|-v] Stream live per-turn events to stderr during the run
207
+ pathgrade init [--force] Generate eval scaffolding
208
+ pathgrade analyze [--skill=X] Analyze skills and output JSON
209
+ pathgrade validate <file> Validate an .eval.ts file
210
+ pathgrade validate --affected Strict: every eval must be anchored or have valid __pathgradeMeta
211
+ pathgrade preview [browser] View results (CLI default, or browser)
212
+ [--last=N] Show only the N most recent reports
213
+ [--filter=X] Filter reports by test name (substring)
214
+ pathgrade preview-reactions Preview reactions against a snapshot
215
+ pathgrade report Format .pathgrade/results.json as a markdown PR comment
216
+ [--results-path=<path>] Override results.json location
217
+ [--no-comment] Print markdown to stdout; do not post
218
+ [--comment-id=<id>] Override comment marker (default: $GITHUB_WORKFLOW:$GITHUB_JOB)
219
+ pathgrade affected Print eval files affected by a change-set (one per line)
220
+ [--since=<ref>] Diff <ref>...HEAD (overrides git auto-detection)
221
+ [--changed-files=<path>] Newline-delimited repo-relative file list
222
+ [--explain] Print human-readable per-eval decision to stderr
223
+ [--json] Emit structured JSON (snake_case) to stdout
224
+
225
+ Environment:
226
+ PATHGRADE_AGENT=codex Override agent for all trials
227
+ ANTHROPIC_API_KEY API key for Claude
228
+ OPENAI_API_KEY API key for Codex (optional if Codex CLI is already logged in)
229
+
230
+ Examples:
231
+ pathgrade run # run all *.eval.ts files
232
+ pathgrade run --diagnostics # print full diagnostics for passing evals too
233
+ pathgrade run --verbose # stream per-turn events live to stderr while evals run
234
+ pathgrade run -- --grep superlint # filter by test name
235
+ pathgrade init # scaffold eval files
236
+ pathgrade preview browser # open web UI
237
+ pathgrade preview-reactions --snapshot ./pathgrade-debug/run-snapshot.json --reactions ./reactions.ts
238
+ `);
239
+ }
240
+ main().catch(err => {
241
+ console.error(err);
242
+ process.exit(1);
243
+ });
@@ -0,0 +1,9 @@
1
+ import type { PathgradePluginOptions } from '../sdk/types.js';
2
+ export type { PathgradePluginOptions };
3
+ /**
4
+ * Vitest plugin for pathgrade evaluations.
5
+ *
6
+ * Configures test file patterns, timeout, auto-dispose lifecycle hooks,
7
+ * and the pathgrade reporter for aggregate statistics.
8
+ */
9
+ export declare function pathgrade(opts?: PathgradePluginOptions): any;
@@ -0,0 +1,81 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { configDefaults } from 'vitest/config';
4
+ import { PathgradeReporter } from './reporter.js';
5
+ import { discoverPathgradeEvalFiles } from '../evals/discovery.js';
6
+ const DEFAULT_EXCLUDE = [...configDefaults.exclude, '.worktrees/**', 'worktrees/**'];
7
+ function resolveSetupFile() {
8
+ // Prefer .ts (running from source via vitest) over .js (built dist)
9
+ const tsPath = path.resolve(import.meta.dirname, 'setup.ts');
10
+ if (fs.existsSync(tsPath))
11
+ return tsPath;
12
+ return path.resolve(import.meta.dirname, 'setup.js');
13
+ }
14
+ /**
15
+ * Vitest plugin for pathgrade evaluations.
16
+ *
17
+ * Configures test file patterns, timeout, auto-dispose lifecycle hooks,
18
+ * and the pathgrade reporter for aggregate statistics.
19
+ */
20
+ export function pathgrade(opts) {
21
+ const timeoutSec = opts?.timeout ?? 300;
22
+ // Verbose live-streaming seam: the SDK (createAgent) reads
23
+ // `process.env.PATHGRADE_VERBOSE` to decide whether to emit. Wiring the
24
+ // plugin option through the env var keeps a single source of truth and
25
+ // preserves CLI/env precedence — we only set the env if the user hasn't
26
+ // already set it via their shell or the `pathgrade run --verbose` CLI.
27
+ if (opts?.verbose === true && process.env.PATHGRADE_VERBOSE === undefined) {
28
+ process.env.PATHGRADE_VERBOSE = '1';
29
+ }
30
+ return {
31
+ name: 'pathgrade',
32
+ // Surface the user's options to pre-vitest CLI commands
33
+ // (`pathgrade affected`, `pathgrade run --changed`) so they can
34
+ // read `affected.global` after loading vitest.config.ts.
35
+ // See `src/affected/config.ts`.
36
+ __pathgradeOptions: opts ?? {},
37
+ config() {
38
+ return {
39
+ resolve: {
40
+ alias: resolveLocalAliases(),
41
+ },
42
+ test: {
43
+ include: opts?.include ?? ['**/*.eval.ts'],
44
+ exclude: opts?.exclude ?? DEFAULT_EXCLUDE,
45
+ testTimeout: (timeoutSec + 30) * 1000,
46
+ setupFiles: [resolveSetupFile()],
47
+ reporters: [
48
+ 'default',
49
+ new PathgradeReporter(opts),
50
+ ],
51
+ },
52
+ };
53
+ },
54
+ configResolved(resolved) {
55
+ const root = resolved.root ?? process.cwd();
56
+ const include = resolved.test?.include ?? opts?.include ?? ['**/*.eval.ts'];
57
+ const exclude = resolved.test?.exclude ?? opts?.exclude ?? DEFAULT_EXCLUDE;
58
+ const discovered = discoverPathgradeEvalFiles({ cwd: root, include, exclude });
59
+ if (!resolved.test) {
60
+ resolved.test = {};
61
+ }
62
+ resolved.test.include = discovered;
63
+ },
64
+ };
65
+ }
66
+ /**
67
+ * When running from source (not installed via npm), resolve
68
+ * `@wix/pathgrade` imports to the local src/ directory so examples
69
+ * can use canonical package imports while developing locally.
70
+ */
71
+ function resolveLocalAliases() {
72
+ const sdkSource = path.resolve(import.meta.dirname, '..', 'sdk', 'index.ts');
73
+ if (!fs.existsSync(sdkSource))
74
+ return undefined;
75
+ const src = path.resolve(import.meta.dirname, '..');
76
+ return {
77
+ '@wix/pathgrade/mcp-mock': path.join(src, 'core', 'mcp-mock.ts'),
78
+ '@wix/pathgrade/plugin': path.join(src, 'plugin', 'index.ts'),
79
+ '@wix/pathgrade': path.join(src, 'sdk', 'index.ts'),
80
+ };
81
+ }
@@ -0,0 +1,45 @@
1
+ import type { Agent, PathgradeTestMeta, RecordedEvalResult } from '../sdk/types.js';
2
+ declare module 'vitest' {
3
+ interface TaskMeta {
4
+ pathgrade?: PathgradeTestMeta[];
5
+ }
6
+ }
7
+ type AfterEachFn = (fn: (ctx: {
8
+ task: {
9
+ id: string;
10
+ meta: Record<string, unknown>;
11
+ };
12
+ }) => Promise<void>) => void;
13
+ type AfterAllFn = (fn: () => Promise<void>) => void;
14
+ declare function trackAgent(agent: Agent): void;
15
+ declare function untrackAgent(agent: Agent): void;
16
+ /**
17
+ * Flush results and dispose agents for the current test. Agents created in
18
+ * this test's scope are disposed after flushing. Shared agents (created in a
19
+ * different scope, e.g. module-level or beforeAll) have their pending results
20
+ * flushed but are kept alive for subsequent tests.
21
+ */
22
+ declare function flush(task: {
23
+ id: string;
24
+ meta: Pick<import('vitest').TaskMeta, 'pathgrade'>;
25
+ }): Promise<void>;
26
+ declare function onResult(result: RecordedEvalResult, agent: Agent): void;
27
+ /**
28
+ * Dispose any agents still pending at end-of-file. Shared agents (created at
29
+ * module scope or in beforeAll) have a stored task id that won't match any
30
+ * per-test id in afterEach, so afterAll is their only disposal opportunity.
31
+ * Without this, sandboxes leak and `debug: true` folders never get written.
32
+ */
33
+ declare function flushAll(): Promise<void>;
34
+ declare function reset(): void;
35
+ declare function install(afterEach: AfterEachFn, afterAll?: AfterAllFn): void;
36
+ export declare const lifecycle: {
37
+ trackAgent: typeof trackAgent;
38
+ untrackAgent: typeof untrackAgent;
39
+ flush: typeof flush;
40
+ flushAll: typeof flushAll;
41
+ onResult: typeof onResult;
42
+ reset: typeof reset;
43
+ install: typeof install;
44
+ };
45
+ export {};
@@ -0,0 +1,146 @@
1
+ import { setRuntime } from '../sdk/eval-runtime.js';
2
+ import { buildDiagnosticsReport } from '../reporters/diagnostics.js';
3
+ // Agent tracking: each test creates its own agent(s) and calls evaluate() on them.
4
+ // Results are keyed by agent reference, which is unique per test.
5
+ const pendingAgents = new Set();
6
+ const agentTaskIds = new WeakMap();
7
+ const agentResults = new WeakMap();
8
+ function currentTaskId() {
9
+ try {
10
+ return globalThis.__vitest_worker__?.current?.id ?? '';
11
+ }
12
+ catch {
13
+ return '';
14
+ }
15
+ }
16
+ function trackAgent(agent) {
17
+ pendingAgents.add(agent);
18
+ const taskId = currentTaskId();
19
+ if (taskId)
20
+ agentTaskIds.set(agent, taskId);
21
+ }
22
+ function untrackAgent(agent) {
23
+ pendingAgents.delete(agent);
24
+ }
25
+ /**
26
+ * Flush results and dispose agents for the current test. Agents created in
27
+ * this test's scope are disposed after flushing. Shared agents (created in a
28
+ * different scope, e.g. module-level or beforeAll) have their pending results
29
+ * flushed but are kept alive for subsequent tests.
30
+ */
31
+ async function flush(task) {
32
+ const results = [];
33
+ const toDispose = [];
34
+ for (const agent of pendingAgents) {
35
+ const storedId = agentTaskIds.get(agent);
36
+ const belongsToThisTest = !storedId || storedId === task.id;
37
+ const meta = agentResults.get(agent);
38
+ if (meta && meta.length > 0) {
39
+ // Always flush pending results, even for shared agents
40
+ results.push(...meta);
41
+ agentResults.delete(agent);
42
+ }
43
+ if (belongsToThisTest) {
44
+ if (!meta || meta.length === 0) {
45
+ // evaluate() was never called — synthesize a trial from agent data
46
+ // so the reporter still surfaces token usage and command counts.
47
+ const synthTrial = synthesizeTrialFromAgent(agent);
48
+ if (synthTrial) {
49
+ results.push(synthTrial);
50
+ }
51
+ }
52
+ pendingAgents.delete(agent);
53
+ toDispose.push(agent);
54
+ }
55
+ }
56
+ if (results.length > 0) {
57
+ task.meta.pathgrade = results;
58
+ }
59
+ await Promise.all(toDispose.map((a) => a.dispose().catch(() => { })));
60
+ }
61
+ function onResult(result, agent) {
62
+ if (!agentResults.has(agent)) {
63
+ agentResults.set(agent, []);
64
+ }
65
+ const conversationEnd = [...agent.log].reverse().find((entry) => entry.type === 'conversation_end');
66
+ const completionReason = conversationEnd?.completion_reason ?? (agent.log.some((entry) => entry.type === 'agent_result') ? 'completed' : undefined);
67
+ agentResults.get(agent).push({
68
+ score: result.score,
69
+ scorers: result.scorers,
70
+ trial: result.trial,
71
+ diagnostics: buildDiagnosticsReport({
72
+ completionReason,
73
+ completionDetail: conversationEnd?.completion_detail,
74
+ turnDetails: conversationEnd?.turn_details,
75
+ reactionsFired: conversationEnd?.reactions_fired,
76
+ score: result.score,
77
+ scorers: result.scorers,
78
+ log: agent.log,
79
+ }),
80
+ });
81
+ }
82
+ /**
83
+ * When evaluate() is never called, synthesize a minimal trial from the agent's
84
+ * log so the reporter still surfaces command count and token usage.
85
+ */
86
+ function synthesizeTrialFromAgent(agent) {
87
+ // Only synthesize if the agent actually ran (has log entries)
88
+ if (agent.log.length === 0)
89
+ return null;
90
+ const nCommands = agent.log.filter((e) => e.type === 'command').length;
91
+ const tokenUsage = agent.llm.tokenUsage;
92
+ const conversationEnd = [...agent.log].reverse().find((entry) => entry.type === 'conversation_end');
93
+ const completionReason = conversationEnd?.completion_reason ?? (agent.log.some((entry) => entry.type === 'agent_result') ? 'completed' : undefined);
94
+ return {
95
+ score: 1, // Will be overridden by vitest pass/fail in the reporter
96
+ scorers: [],
97
+ trial: {
98
+ trial_id: 0,
99
+ reward: 1,
100
+ scorer_results: [],
101
+ duration_ms: 0,
102
+ n_commands: nCommands,
103
+ input_tokens: tokenUsage?.inputTokens ?? 0,
104
+ output_tokens: tokenUsage?.outputTokens ?? 0,
105
+ session_log: [...agent.log],
106
+ },
107
+ diagnostics: buildDiagnosticsReport({
108
+ completionReason,
109
+ completionDetail: conversationEnd?.completion_detail,
110
+ turnDetails: conversationEnd?.turn_details,
111
+ reactionsFired: conversationEnd?.reactions_fired,
112
+ score: 1,
113
+ scorers: [],
114
+ log: agent.log,
115
+ }),
116
+ };
117
+ }
118
+ /**
119
+ * Dispose any agents still pending at end-of-file. Shared agents (created at
120
+ * module scope or in beforeAll) have a stored task id that won't match any
121
+ * per-test id in afterEach, so afterAll is their only disposal opportunity.
122
+ * Without this, sandboxes leak and `debug: true` folders never get written.
123
+ */
124
+ async function flushAll() {
125
+ const toDispose = [...pendingAgents];
126
+ pendingAgents.clear();
127
+ await Promise.all(toDispose.map((a) => a.dispose().catch(() => { })));
128
+ }
129
+ function reset() {
130
+ pendingAgents.clear();
131
+ }
132
+ function install(afterEach, afterAll) {
133
+ setRuntime({ onResult });
134
+ afterEach(async ({ task }) => flush(task));
135
+ if (afterAll)
136
+ afterAll(async () => flushAll());
137
+ }
138
+ export const lifecycle = {
139
+ trackAgent,
140
+ untrackAgent,
141
+ flush,
142
+ flushAll,
143
+ onResult,
144
+ reset,
145
+ install,
146
+ };
@@ -0,0 +1,36 @@
1
+ import type { Reporter, TestModule } from 'vitest/node';
2
+ import type { PathgradePluginOptions } from '../sdk/types.js';
3
+ /**
4
+ * Custom vitest reporter that layers pathgrade aggregate statistics
5
+ * on top of vitest's default output.
6
+ */
7
+ export declare class PathgradeReporter implements Reporter {
8
+ private opts;
9
+ constructor(opts?: PathgradePluginOptions);
10
+ onTestRunEnd(testModules: ReadonlyArray<TestModule>): Promise<void>;
11
+ private collectGroups;
12
+ /**
13
+ * Group key is the parent suite's full name (file + describe hierarchy).
14
+ * Tests without a describe wrapper are grouped by module path.
15
+ */
16
+ private getGroupKey;
17
+ private toTestEntry;
18
+ private printCliSummary;
19
+ private writeJsonResults;
20
+ private buildEvalReport;
21
+ private toTrialResult;
22
+ private slug;
23
+ private openBrowserViewer;
24
+ private computePassRate;
25
+ /**
26
+ * pass@k = 1 - (1 - p)^k
27
+ * Probability of at least 1 success in k trials.
28
+ */
29
+ private computePassAtK;
30
+ /**
31
+ * pass^k = p^k
32
+ * Probability of all k trials succeeding.
33
+ */
34
+ private computePassPowK;
35
+ private computeAvgDuration;
36
+ }