@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,19 @@
1
+ import type { AskQuestion, AskResolution } from '../../sdk/ask-bus/types.js';
2
+ import type { ToolRequestUserInputQuestion, ToolRequestUserInputResponse } from './protocol/index.js';
3
+ /**
4
+ * Upstream `request_user_input` → bus-facing `AskQuestion`.
5
+ * - `header` preserves empty strings (never coerced to undefined).
6
+ * - `options: null` stays null; otherwise maps label+description verbatim
7
+ * (empty-string description preserved).
8
+ * - Strips any fields not in `AskQuestion`; no raw passthrough.
9
+ */
10
+ export declare function normalizeUpstreamQuestion(q: ToolRequestUserInputQuestion): AskQuestion;
11
+ /**
12
+ * Bus-side `AskResolution` → upstream `UserInputAnswer` wire map.
13
+ * - Missing entries emit `{answers: []}` and warn (upstream stalls on
14
+ * missing keys per protocol).
15
+ * - `null` resolution (contract violation) yields an empty map for every
16
+ * question and warns. Driver's outer path should already have errored.
17
+ * - `isSecret` values pass through raw — redaction is the bus snapshot's job.
18
+ */
19
+ export declare function toWireAnswerMap(resolution: AskResolution | null, upstreamQuestions: readonly ToolRequestUserInputQuestion[]): ToolRequestUserInputResponse['answers'];
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Upstream `request_user_input` → bus-facing `AskQuestion`.
3
+ * - `header` preserves empty strings (never coerced to undefined).
4
+ * - `options: null` stays null; otherwise maps label+description verbatim
5
+ * (empty-string description preserved).
6
+ * - Strips any fields not in `AskQuestion`; no raw passthrough.
7
+ */
8
+ export function normalizeUpstreamQuestion(q) {
9
+ return {
10
+ id: q.id,
11
+ header: q.header,
12
+ question: q.question,
13
+ isOther: q.isOther,
14
+ isSecret: q.isSecret,
15
+ options: q.options === null
16
+ ? null
17
+ : q.options.map((o) => ({ label: o.label, description: o.description })),
18
+ };
19
+ }
20
+ /**
21
+ * Bus-side `AskResolution` → upstream `UserInputAnswer` wire map.
22
+ * - Missing entries emit `{answers: []}` and warn (upstream stalls on
23
+ * missing keys per protocol).
24
+ * - `null` resolution (contract violation) yields an empty map for every
25
+ * question and warns. Driver's outer path should already have errored.
26
+ * - `isSecret` values pass through raw — redaction is the bus snapshot's job.
27
+ */
28
+ export function toWireAnswerMap(resolution, upstreamQuestions) {
29
+ const out = {};
30
+ if (resolution === null) {
31
+ console.warn(`toWireAnswerMap: received null resolution — filling empty answers for ${upstreamQuestions.length} question(s)`);
32
+ for (const q of upstreamQuestions) {
33
+ out[q.id] = { answers: [] };
34
+ }
35
+ return out;
36
+ }
37
+ const byId = new Map();
38
+ for (const a of resolution.answers) {
39
+ byId.set(a.questionId, a.values);
40
+ }
41
+ for (const q of upstreamQuestions) {
42
+ const values = byId.get(q.id);
43
+ if (values === undefined) {
44
+ console.warn(`toWireAnswerMap: no answer for question ${q.id} — emitting empty array`);
45
+ out[q.id] = { answers: [] };
46
+ }
47
+ else {
48
+ out[q.id] = { answers: [...values] };
49
+ }
50
+ }
51
+ return out;
52
+ }
@@ -0,0 +1,7 @@
1
+ import { AgentCommandRunner, AgentSessionOptions, AgentTurnResult } from '../types.js';
2
+ import { ToolEvent } from '../tool-events.js';
3
+ import { TranscriptAgent } from './transcript-agent.js';
4
+ export declare class CodexAgent extends TranscriptAgent {
5
+ protected runTurn(instruction: string, runCommand: AgentCommandRunner, options?: AgentSessionOptions): Promise<AgentTurnResult>;
6
+ }
7
+ export declare function extractCodexToolEvents(traceOutput: string, turnNumber?: number): ToolEvent[];
@@ -0,0 +1,164 @@
1
+ import { TOOL_NAME_MAP, buildSummary, inferCodexExecAction, enrichSkillEvents } from '../tool-events.js';
2
+ import { TranscriptAgent } from './transcript-agent.js';
3
+ export class CodexAgent extends TranscriptAgent {
4
+ async runTurn(instruction, runCommand, options) {
5
+ const promptPath = await this.writePromptFile(instruction, runCommand);
6
+ const command = buildCodexExecCommand(promptPath, options?.model);
7
+ const result = await runCommand(command);
8
+ const rawOutput = result.stdout + '\n' + result.stderr;
9
+ const assistantMessage = result.stdout.trim() || rawOutput.trim();
10
+ if (result.exitCode !== 0) {
11
+ console.error('CodexAgent: Codex CLI failed to execute correctly.');
12
+ }
13
+ const toolEvents = extractCodexToolEvents(rawOutput);
14
+ return {
15
+ rawOutput,
16
+ assistantMessage,
17
+ visibleAssistantMessage: assistantMessage,
18
+ visibleAssistantMessageSource: 'assistant_message',
19
+ exitCode: result.exitCode,
20
+ traceOutput: rawOutput,
21
+ toolEvents,
22
+ };
23
+ }
24
+ }
25
+ const DEFAULT_CODEX_MODEL = 'gpt-5.3-codex';
26
+ const CODEX_PROXY_PROVIDER_ID = 'pathgrade_openai_proxy';
27
+ function buildCodexExecCommand(promptPath, model = DEFAULT_CODEX_MODEL) {
28
+ const quotedPromptPath = JSON.stringify(promptPath);
29
+ const quotedModel = JSON.stringify(model);
30
+ const execArgs = `--full-auto --skip-git-repo-check -m ${quotedModel} - < ${quotedPromptPath}`;
31
+ return [
32
+ 'if [ -n "${OPENAI_BASE_URL:-}" ]; then',
33
+ `codex exec ${buildCodexProxyConfigArgs()} ${execArgs};`,
34
+ 'else',
35
+ `codex exec ${execArgs};`,
36
+ 'fi',
37
+ ].join(' ');
38
+ }
39
+ function buildCodexProxyConfigArgs() {
40
+ return [
41
+ `-c 'model_provider="${CODEX_PROXY_PROVIDER_ID}"'`,
42
+ `-c 'model_providers.${CODEX_PROXY_PROVIDER_ID}.name="PathGrade OpenAI Proxy"'`,
43
+ `-c "model_providers.${CODEX_PROXY_PROVIDER_ID}.base_url=\\"$OPENAI_BASE_URL\\""`,
44
+ `-c 'model_providers.${CODEX_PROXY_PROVIDER_ID}.env_key="OPENAI_API_KEY"'`,
45
+ `-c 'model_providers.${CODEX_PROXY_PROVIDER_ID}.wire_api="responses"'`,
46
+ `-c 'model_providers.${CODEX_PROXY_PROVIDER_ID}.supports_websockets=false'`,
47
+ ].join(' ');
48
+ }
49
+ const TOOL_LINE_REGEX = /^tool:\s+(\S+)\s+(\{.*\})\s*$/;
50
+ const CODEX_EXEC_LINE_REGEX = /^(?<command>.+?) in .+? (?:succeeded|failed|exited)\b.*$/;
51
+ const CODEX_EXEC_COMMAND_ONLY_REGEX = /^(?<command>.+?) in .+$/;
52
+ const CODEX_EXEC_STATUS_LINE_REGEX = /^(?:succeeded|failed|exited)\b.*$/;
53
+ const CODEX_FILE_PATH_REGEX = /^[A-Z?]+\s+(?<path>\/.+)$/;
54
+ const CODEX_ABSOLUTE_PATH_REGEX = /^(?<path>\/.+)$/;
55
+ function extractGenericToolLines(traceOutput, turnNumber) {
56
+ const events = [];
57
+ for (const line of traceOutput.split('\n')) {
58
+ const match = line.match(TOOL_LINE_REGEX);
59
+ if (!match)
60
+ continue;
61
+ const providerToolName = match[1];
62
+ const jsonStr = match[2];
63
+ let args;
64
+ try {
65
+ args = JSON.parse(jsonStr);
66
+ }
67
+ catch {
68
+ continue;
69
+ }
70
+ const action = TOOL_NAME_MAP[providerToolName] ?? 'unknown';
71
+ const summary = buildSummary(action, providerToolName, args);
72
+ const rawSnippet = line.length > 200 ? line.slice(0, 200) : line;
73
+ events.push({
74
+ action,
75
+ provider: 'codex',
76
+ providerToolName,
77
+ turnNumber,
78
+ arguments: args,
79
+ summary,
80
+ confidence: 'high',
81
+ rawSnippet,
82
+ });
83
+ }
84
+ return events;
85
+ }
86
+ export function extractCodexToolEvents(traceOutput, turnNumber) {
87
+ const events = extractGenericToolLines(traceOutput, turnNumber);
88
+ const seen = new Set(events.map((event) => `${event.providerToolName}:${event.action}:${event.summary}`));
89
+ const lines = traceOutput.split('\n');
90
+ const pushEvent = (action, providerToolName, summary, args, rawSnippet) => {
91
+ const key = `${providerToolName}:${action}:${summary}`;
92
+ if (seen.has(key))
93
+ return;
94
+ seen.add(key);
95
+ events.push({
96
+ action,
97
+ provider: 'codex',
98
+ providerToolName,
99
+ turnNumber,
100
+ arguments: args,
101
+ summary,
102
+ confidence: action === 'unknown' ? 'low' : 'medium',
103
+ rawSnippet: (rawSnippet || summary).slice(0, 200),
104
+ });
105
+ };
106
+ for (let i = 0; i < lines.length; i++) {
107
+ const line = lines[i].trim();
108
+ if (!line)
109
+ continue;
110
+ if (line === 'exec') {
111
+ const commandLine = lines[i + 1]?.trim();
112
+ if (!commandLine)
113
+ continue;
114
+ const command = extractCommand(commandLine);
115
+ if (!command)
116
+ continue;
117
+ const statusLine = lines[i + 2]?.trim();
118
+ const rawSnippetLines = [line, commandLine];
119
+ if (statusLine && CODEX_EXEC_STATUS_LINE_REGEX.test(statusLine)) {
120
+ rawSnippetLines.push(statusLine);
121
+ i += 2;
122
+ }
123
+ else {
124
+ i += 1;
125
+ }
126
+ pushEvent(inferCodexExecAction(command), 'exec', command, { command }, rawSnippetLines.join('\n'));
127
+ continue;
128
+ }
129
+ if (line === 'file update') {
130
+ const filePath = extractUpdatedPath(lines, i + 1);
131
+ pushEvent('edit_file', 'file update', filePath ? `edit_file ${filePath}` : 'edit_file via file update', filePath ? { path: filePath } : undefined, `${line}\n${lines[i + 1]?.trim() || ''}`);
132
+ i += 1;
133
+ continue;
134
+ }
135
+ if (line === 'apply patch' || line.startsWith('apply_patch(')) {
136
+ const filePath = extractUpdatedPath(lines, i + 1);
137
+ pushEvent('edit_file', line === 'apply patch' ? 'apply patch' : 'apply_patch', filePath ? `edit_file ${filePath}` : 'edit_file via apply_patch', filePath ? { path: filePath } : undefined, `${line}\n${lines[i + 1]?.trim() || ''}`);
138
+ continue;
139
+ }
140
+ }
141
+ return enrichSkillEvents(events);
142
+ }
143
+ function extractCommand(commandLine) {
144
+ const command = commandLine.match(CODEX_EXEC_LINE_REGEX)?.groups?.command
145
+ ?? commandLine.match(CODEX_EXEC_COMMAND_ONLY_REGEX)?.groups?.command;
146
+ if (!command) {
147
+ return undefined;
148
+ }
149
+ const shellWrapped = command.match(/^\/bin\/\w+\s+-lc\s+(['"])(?<inner>[\s\S]+)\1$/);
150
+ return shellWrapped?.groups?.inner ?? command;
151
+ }
152
+ function extractUpdatedPath(lines, startIndex) {
153
+ for (let i = startIndex; i < Math.min(lines.length, startIndex + 5); i++) {
154
+ const line = lines[i]?.trim();
155
+ if (!line)
156
+ continue;
157
+ const filePath = line.match(CODEX_FILE_PATH_REGEX)?.groups?.path
158
+ ?? line.match(CODEX_ABSOLUTE_PATH_REGEX)?.groups?.path;
159
+ if (filePath) {
160
+ return filePath;
161
+ }
162
+ }
163
+ return undefined;
164
+ }
@@ -0,0 +1,39 @@
1
+ import type { TokenUsage } from '../sdk/types.js';
2
+ import { AgentCommandRunner, AgentSession, AgentSessionOptions, BaseAgent, CommandResult, EnvironmentHandle } from '../types.js';
3
+ import { ToolEvent } from '../tool-events.js';
4
+ /**
5
+ * Pure function: scan Cursor NDJSON for `tool_call` events with
6
+ * `subtype: "started"` and emit a normalized `ToolEvent[]`. Enriched via
7
+ * `enrichSkillEvents` so `readToolCall` on a `SKILL.md` is reclassified as
8
+ * `use_skill`, matching Claude/Codex.
9
+ *
10
+ * `completed` subtypes are ignored (noisy, redundant for event-level scoring).
11
+ * `interaction_query` events are ignored (approval metadata, not tool calls).
12
+ */
13
+ export declare function extractCursorStreamJsonEvents(traceOutput: string, turnNumber?: number): ToolEvent[];
14
+ /**
15
+ * Shape returned by `parseCursorStreamJson`. Mirrors Claude's envelope shape
16
+ * plus an `interactionQueryCount` for diagnostics (approval round-trips from
17
+ * `--force` / `--approve-mcps` — discarded here, not surfaced as ToolEvents).
18
+ */
19
+ export interface CursorEnvelopeParseResult {
20
+ sessionId?: string;
21
+ isError: boolean;
22
+ resultText: string;
23
+ tokenUsage?: TokenUsage;
24
+ interactionQueryCount: number;
25
+ }
26
+ /**
27
+ * Pure function: scan Cursor's NDJSON stream-json and return the terminal
28
+ * envelope's key fields. If no `type: "result"` event is present (e.g. the
29
+ * CLI emitted a plain-text "Workspace Trust Required" block), the raw stdout
30
+ * is surfaced as a truncated error payload so the diagnostic points at the
31
+ * real cause instead of a parser crash.
32
+ */
33
+ export declare function parseCursorStreamJson(stdout: string): CursorEnvelopeParseResult;
34
+ export declare class CursorAgent extends BaseAgent {
35
+ createSession(runtime: EnvironmentHandle, runCommand: AgentCommandRunner, options?: AgentSessionOptions): Promise<AgentSession>;
36
+ run(instruction: string, workspacePath: string, runCommand: (cmd: string) => Promise<CommandResult>): Promise<string>;
37
+ private runTurn;
38
+ private sanitizeSessionId;
39
+ }
@@ -0,0 +1,233 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { BaseAgent, getWorkspacePath } from '../types.js';
4
+ import { TOOL_NAME_MAP, buildSummary, enrichSkillEvents } from '../tool-events.js';
5
+ import { getVisibleAssistantMessage } from '../sdk/visible-turn.js';
6
+ import { prependRuntimePolicies } from '../sdk/runtime-policy.js';
7
+ /**
8
+ * Cursor tool_call discriminants observed in the discovery spike. Probed in
9
+ * order — first present key wins. Explicit probing (vs. `Object.keys(…)[0]`)
10
+ * means a future metadata wrapper key like `id` or `ts` cannot silently
11
+ * misclassify.
12
+ */
13
+ const CURSOR_TOOL_DISCRIMINANTS = [
14
+ 'readToolCall',
15
+ 'editToolCall',
16
+ 'globToolCall',
17
+ 'grepToolCall',
18
+ 'shellToolCall',
19
+ 'webFetchToolCall',
20
+ 'updateTodosToolCall',
21
+ ];
22
+ const RAW_SNIPPET_MAX_CHARS = 200;
23
+ /**
24
+ * Pure function: scan Cursor NDJSON for `tool_call` events with
25
+ * `subtype: "started"` and emit a normalized `ToolEvent[]`. Enriched via
26
+ * `enrichSkillEvents` so `readToolCall` on a `SKILL.md` is reclassified as
27
+ * `use_skill`, matching Claude/Codex.
28
+ *
29
+ * `completed` subtypes are ignored (noisy, redundant for event-level scoring).
30
+ * `interaction_query` events are ignored (approval metadata, not tool calls).
31
+ */
32
+ export function extractCursorStreamJsonEvents(traceOutput, turnNumber) {
33
+ const events = [];
34
+ for (const line of traceOutput.split('\n')) {
35
+ if (!line.trim())
36
+ continue;
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(line);
40
+ }
41
+ catch {
42
+ continue;
43
+ }
44
+ if (parsed.type !== 'tool_call' || parsed.subtype !== 'started')
45
+ continue;
46
+ // Later cursor-agent builds nest the tool discriminant under a
47
+ // `tool_call` wrapper; earlier builds (see fixtures/cursor/tool-*.ndjson)
48
+ // place it at the top level. Probe the wrapper first, then the envelope.
49
+ const wrapper = parsed.tool_call && typeof parsed.tool_call === 'object'
50
+ ? parsed.tool_call
51
+ : parsed;
52
+ let discriminantKey;
53
+ for (const candidate of CURSOR_TOOL_DISCRIMINANTS) {
54
+ if (wrapper[candidate] && typeof wrapper[candidate] === 'object') {
55
+ discriminantKey = candidate;
56
+ break;
57
+ }
58
+ }
59
+ // Unknown discriminant — fall back to the first *ToolCall-suffixed key,
60
+ // or the first non-meta object. Preserves providerToolName so it's
61
+ // still visible for triage, action stays 'unknown'.
62
+ if (!discriminantKey) {
63
+ for (const key of Object.keys(wrapper)) {
64
+ if (key === 'type' || key === 'subtype')
65
+ continue;
66
+ if (key === 'id' || key === 'ts')
67
+ continue;
68
+ const value = wrapper[key];
69
+ if (value && typeof value === 'object') {
70
+ discriminantKey = key;
71
+ break;
72
+ }
73
+ }
74
+ }
75
+ if (!discriminantKey)
76
+ continue;
77
+ const block = wrapper[discriminantKey];
78
+ const args = block?.args;
79
+ const action = TOOL_NAME_MAP[discriminantKey] ?? 'unknown';
80
+ const summary = buildSummary(action, discriminantKey, args);
81
+ const rawSnippet = JSON.stringify(block).slice(0, RAW_SNIPPET_MAX_CHARS);
82
+ events.push({
83
+ action,
84
+ provider: 'cursor',
85
+ providerToolName: discriminantKey,
86
+ ...(turnNumber !== undefined ? { turnNumber } : {}),
87
+ ...(args ? { arguments: args } : {}),
88
+ summary,
89
+ confidence: 'high',
90
+ rawSnippet,
91
+ });
92
+ }
93
+ return enrichSkillEvents(events);
94
+ }
95
+ const RAW_ERROR_MAX_BYTES = 2048;
96
+ /**
97
+ * Pure function: scan Cursor's NDJSON stream-json and return the terminal
98
+ * envelope's key fields. If no `type: "result"` event is present (e.g. the
99
+ * CLI emitted a plain-text "Workspace Trust Required" block), the raw stdout
100
+ * is surfaced as a truncated error payload so the diagnostic points at the
101
+ * real cause instead of a parser crash.
102
+ */
103
+ export function parseCursorStreamJson(stdout) {
104
+ let result;
105
+ let interactionQueryCount = 0;
106
+ for (const line of stdout.split('\n')) {
107
+ if (!line.trim())
108
+ continue;
109
+ let parsed;
110
+ try {
111
+ parsed = JSON.parse(line);
112
+ }
113
+ catch {
114
+ continue;
115
+ }
116
+ if (parsed.type === 'interaction_query') {
117
+ interactionQueryCount += 1;
118
+ continue;
119
+ }
120
+ if (parsed.type === 'result') {
121
+ result = parsed;
122
+ }
123
+ }
124
+ if (!result) {
125
+ return {
126
+ isError: true,
127
+ resultText: stdout.slice(0, RAW_ERROR_MAX_BYTES),
128
+ interactionQueryCount,
129
+ };
130
+ }
131
+ const tokenUsage = result.usage
132
+ ? {
133
+ inputTokens: (result.usage.input_tokens ?? 0)
134
+ + (result.usage.cache_creation_input_tokens ?? 0)
135
+ + (result.usage.cache_read_input_tokens ?? 0),
136
+ outputTokens: result.usage.output_tokens ?? 0,
137
+ }
138
+ : undefined;
139
+ return {
140
+ sessionId: result.session_id,
141
+ isError: !!result.is_error,
142
+ resultText: result.result ?? '',
143
+ tokenUsage,
144
+ interactionQueryCount,
145
+ };
146
+ }
147
+ // `cursor-agent --help` (2026.04.17-787b533) treats `--trust` and `--force`
148
+ // as orthogonal: `--trust` unblocks workspace-trust, `--force` bypasses
149
+ // per-command approvals. Discovery spikes saw hangs when `--trust` was
150
+ // dropped, so we pass both by default.
151
+ const CURSOR_EXECUTABLE = 'cursor-agent';
152
+ export class CursorAgent extends BaseAgent {
153
+ async createSession(runtime, runCommand, options) {
154
+ const workspacePath = getWorkspacePath(runtime);
155
+ let sessionId;
156
+ return {
157
+ start: async ({ message }) => {
158
+ const result = await this.runTurn(message, workspacePath, runCommand, sessionId, options);
159
+ sessionId = result.sessionId ?? sessionId;
160
+ return result;
161
+ },
162
+ reply: async ({ message }) => {
163
+ const result = await this.runTurn(message, workspacePath, runCommand, sessionId, options);
164
+ sessionId = result.sessionId ?? sessionId;
165
+ return result;
166
+ },
167
+ };
168
+ }
169
+ async run(instruction, workspacePath, runCommand) {
170
+ const result = await this.runTurn(instruction, workspacePath, runCommand, undefined);
171
+ return getVisibleAssistantMessage(result);
172
+ }
173
+ async runTurn(instruction, workspacePath, runCommand, sessionId, options) {
174
+ // Runtime policies are injected into the first-turn prompt only.
175
+ // On resumed turns (sessionId set) the policy was already delivered,
176
+ // so we skip re-prepending. The Claude SDK driver does not prepend
177
+ // runtime policies at all (its transport is `'reliable'`), so Cursor
178
+ // and the Codex transcript agent are the only consumers of this path.
179
+ const appliedRuntimePolicies = sessionId ? [] : [...(options?.runtimePolicies ?? [])];
180
+ const effectiveInstruction = appliedRuntimePolicies.length > 0
181
+ ? prependRuntimePolicies(instruction, appliedRuntimePolicies, { agent: 'cursor' })
182
+ : instruction;
183
+ // Tempfile-via-argv approach: write the prompt to a shell-escape-safe
184
+ // tempfile and splice via $(cat …). The legacy Claude CLI driver used
185
+ // the same pattern; the SDK-based Claude driver passes `prompt`
186
+ // directly to `query()` and no longer needs this. Cursor still does
187
+ // because it shells out to the `cursor-agent` CLI.
188
+ const promptPath = '"${TMPDIR:-/tmp}/.pathgrade-cursor-prompt.md"';
189
+ const b64 = Buffer.from(effectiveInstruction).toString('base64');
190
+ await runCommand(`mkdir -p "\${TMPDIR:-/tmp}" && echo '${b64}' | base64 -d > ${promptPath}`);
191
+ // Materialize .cursor/mcp.json from pathgrade's MCP config so the
192
+ // CLI's native loader picks it up. Idempotent per turn.
193
+ if (options?.mcpConfigPath) {
194
+ const srcMcp = path.join(workspacePath, options.mcpConfigPath);
195
+ if (await fs.pathExists(srcMcp)) {
196
+ const cursorDir = path.join(workspacePath, '.cursor');
197
+ await fs.ensureDir(cursorDir);
198
+ await fs.copy(srcMcp, path.join(cursorDir, 'mcp.json'), { overwrite: true });
199
+ }
200
+ }
201
+ const modelFlag = options?.model ? ` --model ${options.model}` : '';
202
+ const mcpFlag = options?.mcpConfigPath ? ' --approve-mcps' : '';
203
+ const resumeFlag = sessionId ? ` --resume ${this.sanitizeSessionId(sessionId)}` : '';
204
+ const command = `${CURSOR_EXECUTABLE} -p --output-format stream-json --trust --force --workspace "${workspacePath}"${resumeFlag}${modelFlag}${mcpFlag} "$(cat ${promptPath})" < /dev/null`;
205
+ const result = await runCommand(command);
206
+ const parsed = parseCursorStreamJson(result.stdout);
207
+ const toolEvents = extractCursorStreamJsonEvents(result.stdout);
208
+ const rawOutput = parsed.isError && !parsed.sessionId
209
+ // No parseable result envelope — surface the CLI's raw text so
210
+ // diagnostics point at the real failure (workspace-trust block,
211
+ // auth error, etc.) instead of an empty string.
212
+ ? (result.stdout + (result.stderr ? '\n' + result.stderr : '')).trim()
213
+ : parsed.resultText;
214
+ return {
215
+ rawOutput,
216
+ assistantMessage: parsed.isError ? '' : parsed.resultText,
217
+ visibleAssistantMessage: parsed.isError ? '' : parsed.resultText,
218
+ visibleAssistantMessageSource: 'assistant_message',
219
+ exitCode: result.exitCode,
220
+ sessionId: parsed.sessionId,
221
+ traceOutput: result.stdout,
222
+ timedOut: result.timedOut,
223
+ toolEvents,
224
+ runtimePoliciesApplied: appliedRuntimePolicies,
225
+ ...(parsed.tokenUsage
226
+ ? { inputTokens: parsed.tokenUsage.inputTokens, outputTokens: parsed.tokenUsage.outputTokens }
227
+ : {}),
228
+ };
229
+ }
230
+ sanitizeSessionId(id) {
231
+ return id.replace(/[^a-zA-Z0-9_-]/g, '');
232
+ }
233
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Agent registry — maps agent names to their implementations.
3
+ *
4
+ * Supported agents:
5
+ * - claude: Anthropic Claude Code CLI
6
+ * - codex: OpenAI Codex CLI
7
+ */
8
+ import { BaseAgent } from '../types.js';
9
+ import { AgentName, AgentTransport } from '../sdk/types.js';
10
+ /** Get the list of supported agent names */
11
+ export declare function getAgentNames(): AgentName[];
12
+ /**
13
+ * Create an agent instance by name. Throws if the name is unknown.
14
+ * For `codex`, `transport` selects between `CodexAgent` (exec) and
15
+ * `CodexAppServerAgent` (default / app-server). Ignored for other agents.
16
+ */
17
+ export declare function createAgentEnvironment(name: AgentName, transport?: AgentTransport): BaseAgent;
@@ -0,0 +1,27 @@
1
+ import { ClaudeAgent } from './claude.js';
2
+ import { CodexAgent } from './codex.js';
3
+ import { CodexAppServerAgent } from './codex-app-server/agent.js';
4
+ import { CursorAgent } from './cursor.js';
5
+ /** Registry of available agent implementations. Codex routing is transport-aware. */
6
+ const AGENT_REGISTRY = {
7
+ claude: () => new ClaudeAgent(),
8
+ codex: (transport) => transport === 'exec' ? new CodexAgent() : new CodexAppServerAgent(),
9
+ cursor: () => new CursorAgent(),
10
+ };
11
+ /** Get the list of supported agent names */
12
+ export function getAgentNames() {
13
+ return Object.keys(AGENT_REGISTRY);
14
+ }
15
+ /**
16
+ * Create an agent instance by name. Throws if the name is unknown.
17
+ * For `codex`, `transport` selects between `CodexAgent` (exec) and
18
+ * `CodexAppServerAgent` (default / app-server). Ignored for other agents.
19
+ */
20
+ export function createAgentEnvironment(name, transport) {
21
+ const factory = AGENT_REGISTRY[name];
22
+ if (!factory) {
23
+ const available = getAgentNames().join(', ');
24
+ throw new Error(`Unknown agent "${name}". Available agents: ${available}`);
25
+ }
26
+ return factory(transport);
27
+ }
@@ -0,0 +1,14 @@
1
+ import { AgentCommandRunner, AgentSession, AgentSessionOptions, AgentTurnResult, BaseAgent, CommandResult, EnvironmentHandle } from '../types.js';
2
+ /**
3
+ * Base class for agents that manage multi-turn conversations via transcript
4
+ * re-injection (e.g. Codex). Handles session management, transcript
5
+ * accumulation, and prompt file writing.
6
+ */
7
+ export declare abstract class TranscriptAgent extends BaseAgent {
8
+ createSession(_runtime: EnvironmentHandle, runCommand: AgentCommandRunner, options?: AgentSessionOptions): Promise<AgentSession>;
9
+ run(instruction: string, _workspacePath: string, runCommand: (cmd: string) => Promise<CommandResult>): Promise<string>;
10
+ private buildTranscriptPrompt;
11
+ protected writePromptFile(instruction: string, runCommand: AgentCommandRunner): Promise<string>;
12
+ /** Subclasses implement this to run the CLI command for one turn */
13
+ protected abstract runTurn(instruction: string, runCommand: AgentCommandRunner, options?: AgentSessionOptions): Promise<AgentTurnResult>;
14
+ }
@@ -0,0 +1,59 @@
1
+ import { randomUUID } from 'crypto';
2
+ import fs from 'fs-extra';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { BaseAgent } from '../types.js';
6
+ import { createConversationWindow } from '../sdk/conversation-window.js';
7
+ import { prependRuntimePolicies } from '../sdk/runtime-policy.js';
8
+ import { getVisibleAssistantMessage } from '../sdk/visible-turn.js';
9
+ /**
10
+ * Base class for agents that manage multi-turn conversations via transcript
11
+ * re-injection (e.g. Codex). Handles session management, transcript
12
+ * accumulation, and prompt file writing.
13
+ */
14
+ export class TranscriptAgent extends BaseAgent {
15
+ async createSession(_runtime, runCommand, options) {
16
+ const messages = [];
17
+ const runtimePolicies = options?.runtimePolicies ?? [];
18
+ const window = options?.conversationWindow !== false
19
+ ? createConversationWindow({ ...options?.conversationWindow, llm: options?.llm })
20
+ : null;
21
+ const runTranscriptTurn = async (message) => {
22
+ messages.push({ role: 'user', content: message });
23
+ const prompt = await this.buildTranscriptPrompt(messages, window, runtimePolicies);
24
+ const result = await this.runTurn(prompt, runCommand, options);
25
+ const enrichedResult = runtimePolicies.length > 0
26
+ ? { ...result, runtimePoliciesApplied: [...runtimePolicies] }
27
+ : result;
28
+ messages.push({ role: 'agent', content: getVisibleAssistantMessage(enrichedResult) });
29
+ return enrichedResult;
30
+ };
31
+ return {
32
+ start: async ({ message }) => runTranscriptTurn(message),
33
+ reply: async ({ message }) => runTranscriptTurn(message),
34
+ };
35
+ }
36
+ async run(instruction, _workspacePath, runCommand) {
37
+ const result = await this.runTurn(instruction, runCommand);
38
+ return getVisibleAssistantMessage(result);
39
+ }
40
+ async buildTranscriptPrompt(messages, window, runtimePolicies = []) {
41
+ const history = window
42
+ ? await window.getHistory(messages)
43
+ : messages.map(m => `${m.role === 'user' ? 'User' : 'Agent'}: ${m.content}`).join('\n\n');
44
+ const instruction = [
45
+ 'Continue the conversation below. Respond to the latest user message and do not re-execute previous work unless it is necessary to answer correctly.',
46
+ history,
47
+ ].join('\n\n');
48
+ return runtimePolicies.length > 0
49
+ ? prependRuntimePolicies(instruction, runtimePolicies, { agent: 'codex' })
50
+ : instruction;
51
+ }
52
+ async writePromptFile(instruction, runCommand) {
53
+ void runCommand;
54
+ const promptPath = path.join(process.env.TMPDIR || os.tmpdir(), `.pathgrade-prompt-${randomUUID()}.md`);
55
+ await fs.ensureDir(path.dirname(promptPath));
56
+ await fs.writeFile(promptPath, instruction, 'utf8');
57
+ return promptPath;
58
+ }
59
+ }
@@ -0,0 +1,18 @@
1
+ import { EvalReport } from '../types.js';
2
+ export interface AggregateStats {
3
+ task: string;
4
+ passRateNoSkill: number;
5
+ passRateWithSkill: number;
6
+ normalizedGain: number;
7
+ avgDurationMs: number;
8
+ avgCommands: number;
9
+ }
10
+ /**
11
+ * Calculates Normalized Gain (NG) as defined in the Skill Eval paper.
12
+ * NG = (p_with - p_without) / (1 - p_without)
13
+ */
14
+ export declare function calculateNormalizedGain(pWith: number, pWithout: number): number;
15
+ export declare class AnalyticsEngine {
16
+ loadReports(logDir: string): Promise<EvalReport[]>;
17
+ aggregate(reports: EvalReport[]): AggregateStats[];
18
+ }