@wix/pathgrade 1.0.30 → 1.0.32
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/adapters/jest/invocation-adapter.d.ts +2 -2
- package/dist/adapters/jest/invocation-adapter.js +1 -1
- package/dist/adapters/jest/lifecycle.d.ts +3 -3
- package/dist/adapters/jest/lifecycle.js +1 -1
- package/dist/adapters/jest/metadata.d.ts +0 -1
- package/dist/adapters/jest/metadata.js +1 -1
- package/dist/adapters/jest/results.d.ts +4 -2
- package/dist/adapters/jest/results.js +6 -0
- package/dist/adapters/jest/runner-adapter.js +3 -0
- package/dist/adapters/node-test/index.js +2 -0
- package/dist/adapters/node-test/runner-adapter.js +6 -5
- package/dist/affected/format.d.ts +0 -23
- package/dist/affected/format.js +1 -1
- package/dist/affected/sidecar.d.ts +0 -1
- package/dist/affected/sidecar.js +1 -1
- package/dist/affected/types.d.ts +2 -17
- package/dist/agents/claude/ask-user-answer-store.d.ts +2 -1
- package/dist/agents/claude/tool-results.d.ts +1 -1
- package/dist/agents/claude/tool-results.js +1 -1
- package/dist/agents/claude.d.ts +2 -1
- package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.d.ts +2 -1
- package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.d.ts +3 -2
- package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.d.ts +3 -2
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.d.ts +3 -2
- package/dist/agents/codex-app-server/protocol/ThreadStartParams.d.ts +7 -6
- package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.d.ts +2 -1
- package/dist/agents/codex-app-server/transport.d.ts +2 -2
- package/dist/commands/analyze.d.ts +0 -13
- package/dist/commands/report.d.ts +0 -7
- package/dist/commands/report.js +1 -1
- package/dist/core/generated-mcp-protocol.d.ts +0 -2
- package/dist/core/generated-mcp-protocol.js +2 -2
- package/dist/evals/discovery.d.ts +0 -1
- package/dist/evals/discovery.js +1 -1
- package/dist/internal/direct-mcp-v2/acp-client.d.ts +0 -8
- package/dist/internal/direct-mcp-v2/acp-direct-mcp.d.ts +2 -1
- package/dist/internal/direct-mcp-v2/claude-direct-mcp.d.ts +1 -1
- package/dist/internal/direct-mcp-v2/claude-profile.d.ts +0 -10
- package/dist/internal/direct-mcp-v2/claude-profile.js +3 -3
- package/dist/internal/direct-mcp-v2/codex-direct-mcp.d.ts +2 -1
- package/dist/internal/direct-mcp-v2/compiler.d.ts +0 -1
- package/dist/internal/direct-mcp-v2/compiler.js +1 -1
- package/dist/internal/direct-mcp-v2/json.d.ts +0 -2
- package/dist/internal/direct-mcp-v2/json.js +2 -2
- package/dist/internal/direct-mcp-v2/runtime-lock.d.ts +2 -1
- package/dist/internal/direct-mcp-v2/runtime-profile.d.ts +0 -1
- package/dist/internal/direct-mcp-v2/runtime-profile.js +1 -1
- package/dist/internal/direct-mcp-v2/types.d.ts +5 -4
- package/dist/providers/mcp-config.d.ts +0 -2
- package/dist/providers/mcp-config.js +2 -2
- package/dist/providers/mcp-runtime-mounting.d.ts +1 -9
- package/dist/providers/sandbox-lifecycle.d.ts +0 -2
- package/dist/providers/sandbox-lifecycle.js +2 -2
- package/dist/providers/scripted-mcp-mock-host.d.ts +1 -1
- package/dist/providers/workspace.d.ts +0 -1
- package/dist/reporters/github-comment.d.ts +0 -11
- package/dist/reporters/github-comment.js +1 -1
- package/dist/reporters/verbose-emitter.d.ts +7 -19
- package/dist/reporters/verbose-emitter.js +5 -5
- package/dist/reporting/comparison-contract.d.ts +10 -0
- package/dist/reporting/comparison-contract.js +127 -0
- package/dist/reporting/core.js +14 -1
- package/dist/reporting/report-parser.d.ts +2 -0
- package/dist/reporting/report-parser.js +112 -0
- package/dist/reporting/source-metadata.d.ts +5 -0
- package/dist/reporting/source-metadata.js +20 -0
- package/dist/reporting/types.d.ts +6 -1
- package/dist/runners/adapter-loader.d.ts +0 -3
- package/dist/runners/model-builders.d.ts +3 -1
- package/dist/runners/model-builders.js +19 -7
- package/dist/runners/model-validation.d.ts +2 -1
- package/dist/runners/model.d.ts +4 -0
- package/dist/runners/repeated-attempts.d.ts +0 -5
- package/dist/runners/repeated-attempts.js +1 -1
- package/dist/runners/report-projection.js +15 -1
- package/dist/runners/vitest-adapter.js +9 -3
- package/dist/runners/vitest-invocation.d.ts +2 -2
- package/dist/runners/vitest-invocation.js +1 -1
- package/dist/runners/vitest-lifecycle.d.ts +0 -2
- package/dist/runners/vitest-lifecycle.js +1 -1
- package/dist/sdk/ask-bus/handler.d.ts +3 -2
- package/dist/sdk/diagnostics.d.ts +4 -3
- package/dist/sdk/evaluate.js +5 -0
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +1 -0
- package/dist/sdk/judge-tool-runner.d.ts +1 -2
- package/dist/sdk/lifecycle.js +1 -0
- package/dist/sdk/mcp-mock-approvals.d.ts +3 -2
- package/dist/sdk/reaction-loader.d.ts +1 -2
- package/dist/sdk/reaction-preview.d.ts +1 -4
- package/dist/sdk/reaction-preview.js +3 -3
- package/dist/sdk/run-scorer.d.ts +1 -4
- package/dist/sdk/run-scorer.js +3 -3
- package/dist/sdk/runtime-policy.d.ts +0 -1
- package/dist/sdk/runtime-policy.js +1 -1
- package/dist/sdk/types.d.ts +3 -0
- package/dist/tool-events.d.ts +0 -1
- package/dist/tool-events.js +1 -1
- package/dist/types.d.ts +22 -26
- package/dist/types.js +1 -1
- package/dist/utils/cli.d.ts +0 -15
- package/dist/utils/cli.js +4 -4
- package/dist/utils/llm-mocks.d.ts +3 -2
- package/dist/utils/llm-providers/cli.d.ts +0 -1
- package/dist/utils/llm-providers/cli.js +1 -1
- package/dist/utils/llm-types.d.ts +4 -3
- package/dist/utils/llm.d.ts +2 -2
- package/dist/utils/llm.js +1 -1
- package/package.json +2 -2
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { JsonValue } from './types.js';
|
|
2
|
-
export declare function assertJsonValue(value: unknown, path?: string, ancestors?: Set<object>): asserts value is JsonValue;
|
|
3
2
|
export declare function canonicalJson(value: unknown): string;
|
|
4
3
|
export declare function sha256Canonical(value: unknown): string;
|
|
5
|
-
export declare function escapePointerToken(token: string): string;
|
|
6
4
|
export declare function parsePointer(pointer: string): string[];
|
|
7
5
|
export declare const MISSING: unique symbol;
|
|
8
6
|
export declare function resolvePointer(value: JsonValue, pointer: string): JsonValue | typeof MISSING;
|
|
@@ -13,7 +13,7 @@ function assertValidString(value, path) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
function assertJsonValue(value, path = '$', ancestors = new Set()) {
|
|
17
17
|
if (value === null || typeof value === 'boolean')
|
|
18
18
|
return;
|
|
19
19
|
if (typeof value === 'string')
|
|
@@ -69,7 +69,7 @@ export function canonicalJson(value) {
|
|
|
69
69
|
export function sha256Canonical(value) {
|
|
70
70
|
return createHash('sha256').update(canonicalJson(value)).digest('hex');
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
function escapePointerToken(token) {
|
|
73
73
|
return token.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
74
74
|
}
|
|
75
75
|
export function parsePointer(pointer) {
|
|
@@ -32,7 +32,7 @@ export type RuntimeLockAdmissionContext<TControlPlane extends RuntimeLockControl
|
|
|
32
32
|
commandVersion?: (commandPath: string, args: readonly string[]) => Promise<string>;
|
|
33
33
|
initializeControlPlane(commandPath: string, args: readonly string[]): Promise<TControlPlane>;
|
|
34
34
|
};
|
|
35
|
-
|
|
35
|
+
type RuntimeLockIdentity = {
|
|
36
36
|
provider: RuntimeLock['provider'];
|
|
37
37
|
platform: string;
|
|
38
38
|
architecture: string;
|
|
@@ -49,3 +49,4 @@ export type RuntimeLockAdmission<TControlPlane extends RuntimeLockControlPlane =
|
|
|
49
49
|
controlPlane: TControlPlane;
|
|
50
50
|
};
|
|
51
51
|
export declare function admitRuntimeLock<TControlPlane extends RuntimeLockControlPlane>(rawLock: RuntimeLock, context: RuntimeLockAdmissionContext<TControlPlane>): Promise<RuntimeLockAdmission<TControlPlane>>;
|
|
52
|
+
export {};
|
|
@@ -24,5 +24,4 @@ export type RuntimeAdmissionResult = {
|
|
|
24
24
|
};
|
|
25
25
|
export declare function profileFingerprint(profile: Omit<RuntimeCapabilityProfile, 'profileFingerprint' | 'runtimeCapabilityFingerprint'>): string;
|
|
26
26
|
export declare function runtimeCapabilityFingerprint(normalizedHandshake: JsonObject): string;
|
|
27
|
-
export declare function parseRuntimeProfile(input: string | unknown): RuntimeCapabilityProfile;
|
|
28
27
|
export declare function admitRuntimeProfile(rawProfile: string | unknown, context: RuntimeAdmissionContext): Promise<RuntimeAdmissionResult>;
|
|
@@ -28,7 +28,7 @@ export function profileFingerprint(profile) {
|
|
|
28
28
|
export function runtimeCapabilityFingerprint(normalizedHandshake) {
|
|
29
29
|
return sha256Canonical(normalizedHandshake);
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
function parseRuntimeProfile(input) {
|
|
32
32
|
const value = typeof input === 'string' ? parseStrictJson(input) : input;
|
|
33
33
|
canonicalJson(value);
|
|
34
34
|
if (!object(value))
|
|
@@ -3,8 +3,8 @@ export type JsonValue = JsonPrimitive | JsonValue[] | JsonObject;
|
|
|
3
3
|
export type JsonObject = {
|
|
4
4
|
[key: string]: JsonValue;
|
|
5
5
|
};
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
type Sha256 = string;
|
|
7
|
+
type HmacSha256 = string;
|
|
8
8
|
export type ProviderId = 'claude' | 'codex' | 'cursor' | 'opencode';
|
|
9
9
|
export type MatchPredicate = {
|
|
10
10
|
pointer: string;
|
|
@@ -72,7 +72,7 @@ export type ScenarioDiagnostic = {
|
|
|
72
72
|
path: string;
|
|
73
73
|
message: string;
|
|
74
74
|
};
|
|
75
|
-
|
|
75
|
+
type CompiledTool = {
|
|
76
76
|
qualifiedToolId: string;
|
|
77
77
|
serverId: string;
|
|
78
78
|
toolId: string;
|
|
@@ -80,7 +80,7 @@ export type CompiledTool = {
|
|
|
80
80
|
otherwise: ScenarioRejection;
|
|
81
81
|
effectful: boolean;
|
|
82
82
|
};
|
|
83
|
-
|
|
83
|
+
type ScenarioIR = {
|
|
84
84
|
version: 2;
|
|
85
85
|
scenarioId: string;
|
|
86
86
|
initialState: string;
|
|
@@ -231,3 +231,4 @@ export type ProviderCorroboration = {
|
|
|
231
231
|
phase: string;
|
|
232
232
|
order: number;
|
|
233
233
|
};
|
|
234
|
+
export {};
|
|
@@ -29,9 +29,7 @@ export type McpDeclaration = {
|
|
|
29
29
|
} | {
|
|
30
30
|
mock: MockMcpServerDescriptor | MockMcpServerDescriptor[];
|
|
31
31
|
};
|
|
32
|
-
export declare const MCP_CONFIG_FILENAME = ".pathgrade-mcp.json";
|
|
33
32
|
export declare function readStagedMcpServers(workspacePath: string, mcpConfigPath?: string): Promise<Record<string, McpConfigFileEntry> | undefined>;
|
|
34
|
-
export declare function missingMcpSecretReferences(mcpServers: Record<string, McpConfigFileEntry> | undefined, env: Record<string, string | undefined>): string[];
|
|
35
33
|
export declare function assertMcpSecretReferencesReady(opts: {
|
|
36
34
|
workspacePath: string;
|
|
37
35
|
mcpConfigPath?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
|
|
3
|
+
const MCP_CONFIG_FILENAME = '.pathgrade-mcp.json';
|
|
4
4
|
const GENERATED_MOCK_SERVER_FILENAME = '.pathgrade-mcp-mock-server.cjs';
|
|
5
5
|
function isRecord(value) {
|
|
6
6
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -76,7 +76,7 @@ export async function readStagedMcpServers(workspacePath, mcpConfigPath = MCP_CO
|
|
|
76
76
|
return undefined;
|
|
77
77
|
return readMcpServers(await fs.readJson(configPath));
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
function missingMcpSecretReferences(mcpServers, env) {
|
|
80
80
|
if (!mcpServers)
|
|
81
81
|
return [];
|
|
82
82
|
const missing = [];
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import type { McpStdioEntry } from './mcp-config.js';
|
|
2
2
|
import type { McpSafetyOptions } from '../sdk/mcp-safety.js';
|
|
3
|
-
|
|
3
|
+
type ClaudeSdkMcpServerEntry = McpStdioEntry | {
|
|
4
4
|
type: 'http';
|
|
5
5
|
url: string;
|
|
6
6
|
headers?: Record<string, string>;
|
|
7
7
|
};
|
|
8
8
|
/** Object form of MCP server entries the SDK driver passes to `Options.mcpServers`. */
|
|
9
9
|
export type McpServersObject = Record<string, ClaudeSdkMcpServerEntry>;
|
|
10
|
-
export type CodexMcpServerEntry = McpStdioEntry | {
|
|
11
|
-
url: string;
|
|
12
|
-
http_headers?: Record<string, string>;
|
|
13
|
-
env_http_headers?: Record<string, string>;
|
|
14
|
-
bearer_token_env_var?: string;
|
|
15
|
-
startup_timeout_sec?: number;
|
|
16
|
-
tool_timeout_sec?: number;
|
|
17
|
-
};
|
|
18
10
|
export interface CodexAppServerMcpConfig extends Record<string, unknown> {
|
|
19
11
|
mcp_servers: Record<string, unknown>;
|
|
20
12
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export declare const SANDBOX_PREFIX = "pathgrade-";
|
|
2
1
|
export declare const SANDBOX_MARKER = ".pathgrade-sandbox.json";
|
|
3
|
-
export declare const STALE_SANDBOX_AGE_MS: number;
|
|
4
2
|
interface RemoveSandboxRootOptions {
|
|
5
3
|
remove?: (target: string) => Promise<void>;
|
|
6
4
|
sleep?: (ms: number) => Promise<void>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
|
|
4
|
+
const SANDBOX_PREFIX = 'pathgrade-';
|
|
5
5
|
export const SANDBOX_MARKER = '.pathgrade-sandbox.json';
|
|
6
|
-
|
|
6
|
+
const STALE_SANDBOX_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
7
7
|
let startupCleanup;
|
|
8
8
|
const SANDBOX_REMOVE_RETRY_DELAYS_MS = [
|
|
9
9
|
50,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolEvent } from '../tool-events.js';
|
|
2
2
|
import { type CompiledMcpMockSession } from '../sdk/mcp-mock-approvals.js';
|
|
3
|
-
|
|
3
|
+
interface ScriptedMcpDecision {
|
|
4
4
|
decision: 'approve' | 'deny';
|
|
5
5
|
outcome: 'matched' | 'unmatched' | 'protocol_error';
|
|
6
6
|
reason: 'rule_match' | 'no_matching_rule' | 'protocol_error';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type SandboxConfig } from './sandbox.js';
|
|
2
2
|
import type { CommandResult, McpConfigOrigin } from '../types.js';
|
|
3
|
-
export type { McpDeclaration } from './mcp-config.js';
|
|
4
3
|
export interface Workspace {
|
|
5
4
|
readonly path: string;
|
|
6
5
|
readonly mcpConfigPath: string | undefined;
|
|
@@ -26,17 +26,6 @@ export declare function commentMarker(commentId: string): string;
|
|
|
26
26
|
* posted as a GitHub issue comment body.
|
|
27
27
|
*/
|
|
28
28
|
export declare function formatReportMarkdown(report: PathgradeReport, opts: FormatOptions): string;
|
|
29
|
-
/**
|
|
30
|
-
* Render the `### Selection` section for the PR comment. Pure: no fs,
|
|
31
|
-
* no network, no reporter side effects. Three variants:
|
|
32
|
-
*
|
|
33
|
-
* - `global_match` set → "global trigger fired" one-liner, no collapsible.
|
|
34
|
-
* - `skipped` empty + `selected` non-empty → "every eval had a matching
|
|
35
|
-
* change" one-liner.
|
|
36
|
-
* - otherwise → "Ran **X of Y**" line + `<details>` block listing
|
|
37
|
-
* skipped eval paths as inline code.
|
|
38
|
-
*/
|
|
39
|
-
export declare function formatSelectionSection(selection: PathgradeSelectionReport): string;
|
|
40
29
|
export interface PrContext {
|
|
41
30
|
owner: string;
|
|
42
31
|
repo: string;
|
|
@@ -114,7 +114,7 @@ export function formatReportMarkdown(report, opts) {
|
|
|
114
114
|
* - otherwise → "Ran **X of Y**" line + `<details>` block listing
|
|
115
115
|
* skipped eval paths as inline code.
|
|
116
116
|
*/
|
|
117
|
-
|
|
117
|
+
function formatSelectionSection(selection) {
|
|
118
118
|
const out = [];
|
|
119
119
|
out.push('### Selection');
|
|
120
120
|
const total = selection.selected.length + selection.skipped.length;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/** Maximum characters retained in a prompt/reply preview before truncation. */
|
|
2
|
-
export declare const PREVIEW_MAX_CHARS = 80;
|
|
3
|
-
/** Maximum characters retained in a retry error message before truncation. */
|
|
4
|
-
export declare const RETRY_ERROR_MAX_CHARS = 120;
|
|
5
|
-
/** Ellipsis appended to truncated strings. */
|
|
6
|
-
export declare const TRUNCATION_SUFFIX = "\u2026";
|
|
7
|
-
/** Newline replacement in single-line previews. */
|
|
8
|
-
export declare const NEWLINE_REPLACEMENT = "\u23CE";
|
|
9
1
|
export interface VerboseSink {
|
|
10
2
|
write(line: string): void;
|
|
11
3
|
}
|
|
@@ -19,33 +11,33 @@ export interface VerboseEmitterOptions {
|
|
|
19
11
|
*/
|
|
20
12
|
testName?: string;
|
|
21
13
|
}
|
|
22
|
-
|
|
14
|
+
interface TurnStartArgs {
|
|
23
15
|
turn: number;
|
|
24
16
|
kind: 'agent_start' | 'user_reply';
|
|
25
17
|
message: string;
|
|
26
18
|
}
|
|
27
|
-
|
|
19
|
+
interface ToolEventArgs {
|
|
28
20
|
action: string;
|
|
29
21
|
summary: string;
|
|
30
22
|
}
|
|
31
|
-
|
|
23
|
+
interface TurnEndArgs {
|
|
32
24
|
turn: number;
|
|
33
25
|
durationMs: number;
|
|
34
26
|
outputLines: number;
|
|
35
27
|
messagePreview: string;
|
|
36
28
|
}
|
|
37
|
-
|
|
29
|
+
interface RetryArgs {
|
|
38
30
|
attempt: number;
|
|
39
31
|
maxAttempts: number;
|
|
40
32
|
errorMessage: string;
|
|
41
33
|
}
|
|
42
|
-
|
|
34
|
+
interface ReactionFiredArgs {
|
|
43
35
|
turn: number;
|
|
44
36
|
reactionIndex: number;
|
|
45
37
|
pattern: string;
|
|
46
38
|
reply: string;
|
|
47
39
|
}
|
|
48
|
-
|
|
40
|
+
interface ConversationEndArgs {
|
|
49
41
|
reason: string;
|
|
50
42
|
turns: number;
|
|
51
43
|
durationMs: number;
|
|
@@ -61,9 +53,5 @@ export interface VerboseEmitter {
|
|
|
61
53
|
reactionFired(args: ReactionFiredArgs): void;
|
|
62
54
|
conversationEnd(args: ConversationEndArgs): void;
|
|
63
55
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Build a preview string: strip newlines, truncate to a max length.
|
|
66
|
-
* `null` / `undefined` becomes an empty string so formatting never throws.
|
|
67
|
-
*/
|
|
68
|
-
export declare function preview(text: string | null | undefined, max?: number): string;
|
|
69
56
|
export declare function createVerboseEmitter(opts: VerboseEmitterOptions): VerboseEmitter;
|
|
57
|
+
export {};
|
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { fmt } from '../utils/cli.js';
|
|
12
12
|
/** Maximum characters retained in a prompt/reply preview before truncation. */
|
|
13
|
-
|
|
13
|
+
const PREVIEW_MAX_CHARS = 80;
|
|
14
14
|
/** Maximum characters retained in a retry error message before truncation. */
|
|
15
|
-
|
|
15
|
+
const RETRY_ERROR_MAX_CHARS = 120;
|
|
16
16
|
/** Ellipsis appended to truncated strings. */
|
|
17
|
-
|
|
17
|
+
const TRUNCATION_SUFFIX = '…';
|
|
18
18
|
/** Newline replacement in single-line previews. */
|
|
19
|
-
|
|
19
|
+
const NEWLINE_REPLACEMENT = '⏎';
|
|
20
20
|
/**
|
|
21
21
|
* Build a preview string: strip newlines, truncate to a max length.
|
|
22
22
|
* `null` / `undefined` becomes an empty string so formatting never throws.
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
function preview(text, max = PREVIEW_MAX_CHARS) {
|
|
25
25
|
const normalized = (text ?? '').replace(/\r?\n/g, NEWLINE_REPLACEMENT);
|
|
26
26
|
if (normalized.length <= max)
|
|
27
27
|
return normalized;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComparisonContract, EvalReport } from '../types.js';
|
|
2
|
+
import type { Scorer } from '../sdk/types.js';
|
|
3
|
+
import type { ReportGroupInput } from './types.js';
|
|
4
|
+
export declare function buildComparisonContract(input: {
|
|
5
|
+
group: ReportGroupInput;
|
|
6
|
+
report: EvalReport;
|
|
7
|
+
attemptsRequested: number;
|
|
8
|
+
attemptsCompleted: number;
|
|
9
|
+
}): ComparisonContract;
|
|
10
|
+
export declare function createScorerRevision(scorers: readonly Scorer[]): string | undefined;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalizeJson } from '../core/canonical-json.js';
|
|
3
|
+
export function buildComparisonContract(input) {
|
|
4
|
+
const unavailable = new Set();
|
|
5
|
+
const evaluations = input.group.cases.flatMap(testCase => {
|
|
6
|
+
const caseIdentity = testCase.repeatKey ?? testCase.caseId ?? testCase.name;
|
|
7
|
+
return (testCase.attempts?.flatMap(attempt => attempt.evaluations ?? [])
|
|
8
|
+
?? testCase.evaluations
|
|
9
|
+
?? [])
|
|
10
|
+
.filter(evaluation => evaluation.resultKind !== 'synthetic_no_evaluation')
|
|
11
|
+
.map((evaluation, index) => ({
|
|
12
|
+
case: caseIdentity,
|
|
13
|
+
definition: evaluation.evaluationDefinitionKey ?? `position:${index}`,
|
|
14
|
+
scorer: evaluation.scorerRevision,
|
|
15
|
+
runtime: runtimeIdentity(evaluation.agent ?? evaluation.trial?.agent),
|
|
16
|
+
}));
|
|
17
|
+
});
|
|
18
|
+
const definitionRevision = input.group.sourceRevision
|
|
19
|
+
? revision('definition', {
|
|
20
|
+
source: input.group.sourceRevision,
|
|
21
|
+
evaluations: uniqueSorted(evaluations.map(evaluation => ({
|
|
22
|
+
case: evaluation.case, definition: evaluation.definition,
|
|
23
|
+
}))),
|
|
24
|
+
})
|
|
25
|
+
: undefined;
|
|
26
|
+
if (!definitionRevision)
|
|
27
|
+
unavailable.add('definition_metadata_missing');
|
|
28
|
+
const scorerRevision = evaluations.length > 0 && evaluations.every(evaluation => evaluation.scorer)
|
|
29
|
+
? revision('scorers', uniqueSorted(evaluations.map(evaluation => ({
|
|
30
|
+
case: evaluation.case,
|
|
31
|
+
definition: evaluation.definition,
|
|
32
|
+
scorer: evaluation.scorer,
|
|
33
|
+
}))))
|
|
34
|
+
: undefined;
|
|
35
|
+
if (!scorerRevision)
|
|
36
|
+
unavailable.add('scorer_metadata_missing');
|
|
37
|
+
const runtimeComplete = evaluations.length > 0
|
|
38
|
+
&& evaluations.every(evaluation => Object.values(evaluation.runtime)
|
|
39
|
+
.every(value => value !== undefined));
|
|
40
|
+
const runtimeRevision = runtimeComplete
|
|
41
|
+
? revision('runtime', uniqueSorted(evaluations.map(evaluation => ({
|
|
42
|
+
case: evaluation.case,
|
|
43
|
+
definition: evaluation.definition,
|
|
44
|
+
runtime: evaluation.runtime,
|
|
45
|
+
}))))
|
|
46
|
+
: undefined;
|
|
47
|
+
if (!runtimeRevision)
|
|
48
|
+
unavailable.add('runtime_metadata_missing');
|
|
49
|
+
const reportableCases = input.group.cases.filter(testCase => testCase.reportable !== false
|
|
50
|
+
&& testCase.state !== 'skipped' && testCase.state !== 'pending');
|
|
51
|
+
const samplingComplete = input.attemptsCompleted === input.attemptsRequested
|
|
52
|
+
&& reportableCases.every(testCase => (testCase.attempts?.length ?? (testCase.evaluations === undefined ? 0 : 1)) === input.attemptsRequested)
|
|
53
|
+
&& reportableCases.every(testCase => testCase.attempts
|
|
54
|
+
? testCase.attempts.every(attempt => hasTerminalEvaluation(attempt.evaluations))
|
|
55
|
+
: hasTerminalEvaluation(testCase.evaluations));
|
|
56
|
+
const samplingRevision = samplingComplete
|
|
57
|
+
? revision('sampling', {
|
|
58
|
+
attempts: input.attemptsRequested,
|
|
59
|
+
cases: reportableCases.map(testCase => testCase.repeatKey ?? testCase.caseId ?? testCase.name).toSorted(),
|
|
60
|
+
})
|
|
61
|
+
: undefined;
|
|
62
|
+
if (!samplingRevision)
|
|
63
|
+
unavailable.add('sampling_incomplete');
|
|
64
|
+
return {
|
|
65
|
+
version: 1,
|
|
66
|
+
...(definitionRevision ? { definition_revision: definitionRevision } : {}),
|
|
67
|
+
...(scorerRevision ? { scorer_revision: scorerRevision } : {}),
|
|
68
|
+
...(runtimeRevision ? { runtime_revision: runtimeRevision } : {}),
|
|
69
|
+
...(samplingRevision ? { sampling_revision: samplingRevision } : {}),
|
|
70
|
+
report_schema_revision: 'pathgrade-results-v2',
|
|
71
|
+
...(unavailable.size > 0 ? { unavailable_reasons: [...unavailable].sort() } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function hasTerminalEvaluation(evaluations) {
|
|
75
|
+
return evaluations?.some(evaluation => evaluation.resultKind !== 'synthetic_no_evaluation') === true;
|
|
76
|
+
}
|
|
77
|
+
export function createScorerRevision(scorers) {
|
|
78
|
+
try {
|
|
79
|
+
return revision('scorer-declaration', scorers
|
|
80
|
+
.map(scorer => canonicalizeJson(scorerDeclaration(scorer))).toSorted());
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function runtimeIdentity(agent) {
|
|
87
|
+
return {
|
|
88
|
+
name: agent?.name,
|
|
89
|
+
model: agent?.resolvedModel,
|
|
90
|
+
transport: agent?.transport,
|
|
91
|
+
interaction: agent?.interactionMode,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function scorerDeclaration(scorer) {
|
|
95
|
+
const common = { type: scorer.type, name: scorer.name.normalize('NFC'), weight: scorer.weight };
|
|
96
|
+
if (scorer.type === 'check' || scorer.type === 'score') {
|
|
97
|
+
return { ...common, function: scorer.fn.toString() };
|
|
98
|
+
}
|
|
99
|
+
if (scorer.type === 'tool_usage') {
|
|
100
|
+
return {
|
|
101
|
+
...common,
|
|
102
|
+
expectations: scorer.expectations
|
|
103
|
+
.toSorted((left, right) => canonicalizeJson(left).localeCompare(canonicalizeJson(right))),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
...common,
|
|
108
|
+
rubric: scorer.rubric,
|
|
109
|
+
model: scorer.model ?? null,
|
|
110
|
+
retry: scorer.retry ?? null,
|
|
111
|
+
includeToolEvents: scorer.includeToolEvents ?? null,
|
|
112
|
+
input: typeof scorer.input === 'function' ? scorer.input.toString() : scorer.input ?? null,
|
|
113
|
+
tools: scorer.tools?.toSorted() ?? null,
|
|
114
|
+
maxRounds: scorer.maxRounds ?? null,
|
|
115
|
+
cacheControl: scorer.cacheControl ?? null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function revision(namespace, value) {
|
|
119
|
+
return `sha256:${createHash('sha256')
|
|
120
|
+
.update(canonicalizeJson(['pathgrade-comparison', 1, namespace, value]))
|
|
121
|
+
.digest('hex')}`;
|
|
122
|
+
}
|
|
123
|
+
function uniqueSorted(values) {
|
|
124
|
+
return [...new Map(values.map(value => [canonicalizeJson(value), value])).entries()]
|
|
125
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
126
|
+
.map(([, value]) => value);
|
|
127
|
+
}
|
package/dist/reporting/core.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildDiagnosticsReport } from '../sdk/diagnostics.js';
|
|
2
2
|
import { extractSkillsFromLog } from '../tool-events.js';
|
|
3
|
+
import { buildComparisonContract } from './comparison-contract.js';
|
|
3
4
|
export function buildPathgradeReport(input) {
|
|
4
5
|
const warnings = [];
|
|
5
6
|
const consolidatedGroups = [];
|
|
@@ -8,6 +9,7 @@ export function buildPathgradeReport(input) {
|
|
|
8
9
|
const attemptsRequested = input.attemptsRequested ?? 1;
|
|
9
10
|
const attemptsCompleted = input.attemptsCompleted ?? attemptsRequested;
|
|
10
11
|
const builtGroups = input.groups.map(group => ({
|
|
12
|
+
input: group,
|
|
11
13
|
groupName: group.groupName,
|
|
12
14
|
cases: group.cases.map(toBuiltCase),
|
|
13
15
|
}));
|
|
@@ -32,7 +34,18 @@ export function buildPathgradeReport(input) {
|
|
|
32
34
|
return rest;
|
|
33
35
|
});
|
|
34
36
|
const { trials: _trials, ...rest } = report;
|
|
35
|
-
consolidatedGroups.push({
|
|
37
|
+
consolidatedGroups.push({
|
|
38
|
+
...rest,
|
|
39
|
+
trials: strippedTrials,
|
|
40
|
+
trace_file: traceFile,
|
|
41
|
+
...(group.input.sourceFile ? { source_file: group.input.sourceFile } : {}),
|
|
42
|
+
comparison_contract: buildComparisonContract({
|
|
43
|
+
group: group.input,
|
|
44
|
+
report,
|
|
45
|
+
attemptsRequested,
|
|
46
|
+
attemptsCompleted,
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
36
49
|
}
|
|
37
50
|
const scores = reportableGroups.flatMap(group => group.cases.flatMap(testCase => (testCase.attempts
|
|
38
51
|
.filter(attempt => attempt.resultKind !== 'synthetic_no_evaluation')
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export function parsePathgradeReport(value) {
|
|
2
|
+
if (!isRecord(value)
|
|
3
|
+
|| (value.version !== 1 && value.version !== 2)
|
|
4
|
+
|| typeof value.timestamp !== 'string'
|
|
5
|
+
|| typeof value.overall_pass_rate !== 'number'
|
|
6
|
+
|| (value.version === 2 && (typeof value.overall_mean_reward !== 'number' || !validAttemptCounts(value)))
|
|
7
|
+
|| !optionalNumber(value.threshold)
|
|
8
|
+
|| (value.status !== 'pass' && value.status !== 'fail')
|
|
9
|
+
|| (value.run_kind !== undefined && value.run_kind !== 'evaluation' && value.run_kind !== 'no-affected')
|
|
10
|
+
|| !Array.isArray(value.groups)
|
|
11
|
+
|| !value.groups.every(group => isReportGroup(group, value.version))
|
|
12
|
+
|| !isSelection(value.selection)) {
|
|
13
|
+
throw new Error('PathGrade results.json is missing or has an unsupported schema');
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function isReportGroup(value, version) {
|
|
18
|
+
return isRecord(value)
|
|
19
|
+
&& typeof value.task === 'string'
|
|
20
|
+
&& (version === 1 ? legacyMetrics(value) : schemaV2Metrics(value))
|
|
21
|
+
&& optionalString(value.source_file)
|
|
22
|
+
&& typeof value.trace_file === 'string'
|
|
23
|
+
&& Array.isArray(value.skills_used)
|
|
24
|
+
&& value.skills_used.every(item => typeof item === 'string')
|
|
25
|
+
&& Array.isArray(value.trials)
|
|
26
|
+
&& value.trials.every(isTrial)
|
|
27
|
+
&& (value.comparison_contract === undefined || isComparisonContract(value.comparison_contract));
|
|
28
|
+
}
|
|
29
|
+
function isTrial(value) {
|
|
30
|
+
return isRecord(value)
|
|
31
|
+
&& typeof value.trial_id === 'number'
|
|
32
|
+
&& typeof value.duration_ms === 'number'
|
|
33
|
+
&& typeof value.input_tokens === 'number'
|
|
34
|
+
&& typeof value.output_tokens === 'number'
|
|
35
|
+
&& optionalNumber(value.reward)
|
|
36
|
+
&& optionalNumber(value.conversation_input_tokens)
|
|
37
|
+
&& optionalNumber(value.conversation_output_tokens)
|
|
38
|
+
&& optionalNumber(value.conversation_cost_usd)
|
|
39
|
+
&& Array.isArray(value.scorer_results)
|
|
40
|
+
&& value.scorer_results.every(scorer => isRecord(scorer)
|
|
41
|
+
&& typeof scorer.scorer_type === 'string'
|
|
42
|
+
&& typeof scorer.score === 'number'
|
|
43
|
+
&& typeof scorer.weight === 'number');
|
|
44
|
+
}
|
|
45
|
+
function legacyMetrics(value) {
|
|
46
|
+
return typeof value.pass_rate === 'number'
|
|
47
|
+
&& typeof value.pass_at_k === 'number'
|
|
48
|
+
&& typeof value.pass_pow_k === 'number';
|
|
49
|
+
}
|
|
50
|
+
function schemaV2Metrics(value) {
|
|
51
|
+
return (value.status === 'pass' || value.status === 'fail')
|
|
52
|
+
&& typeof value.mean_reward === 'number'
|
|
53
|
+
&& optionalNumber(value.success_rate)
|
|
54
|
+
&& (value.pass_at_k === undefined || (isRecord(value.pass_at_k)
|
|
55
|
+
&& Object.values(value.pass_at_k).every(item => typeof item === 'number')))
|
|
56
|
+
&& (value.pass_at_k_method === undefined || value.pass_at_k_method === 'finite_sample_unbiased')
|
|
57
|
+
&& (value.pass_at_k_unavailable_reason === undefined
|
|
58
|
+
|| value.pass_at_k_unavailable_reason === 'non_binary_reward'
|
|
59
|
+
|| value.pass_at_k_unavailable_reason === 'incomplete_attempts'
|
|
60
|
+
|| value.pass_at_k_unavailable_reason === 'case_identity_mismatch')
|
|
61
|
+
&& optionalNonNegativeInteger(value.eligible_case_count)
|
|
62
|
+
&& optionalPositiveInteger(value.attempts_per_case);
|
|
63
|
+
}
|
|
64
|
+
function validAttemptCounts(value) {
|
|
65
|
+
return typeof value.attempts_requested === 'number'
|
|
66
|
+
&& Number.isSafeInteger(value.attempts_requested)
|
|
67
|
+
&& value.attempts_requested >= 1
|
|
68
|
+
&& typeof value.attempts_completed === 'number'
|
|
69
|
+
&& Number.isSafeInteger(value.attempts_completed)
|
|
70
|
+
&& value.attempts_completed >= 0
|
|
71
|
+
&& value.attempts_completed <= value.attempts_requested;
|
|
72
|
+
}
|
|
73
|
+
function isSelection(value) {
|
|
74
|
+
return value === undefined || (isRecord(value)
|
|
75
|
+
&& typeof value.base_ref === 'string'
|
|
76
|
+
&& typeof value.changed_files_count === 'number'
|
|
77
|
+
&& Array.isArray(value.selected)
|
|
78
|
+
&& value.selected.every(item => typeof item === 'string')
|
|
79
|
+
&& Array.isArray(value.skipped)
|
|
80
|
+
&& value.skipped.every(item => isRecord(item)
|
|
81
|
+
&& typeof item.file === 'string'
|
|
82
|
+
&& item.reason === 'no-matching-deps'));
|
|
83
|
+
}
|
|
84
|
+
function isComparisonContract(value) {
|
|
85
|
+
return isRecord(value)
|
|
86
|
+
&& value.version === 1
|
|
87
|
+
&& optionalString(value.definition_revision)
|
|
88
|
+
&& optionalString(value.scorer_revision)
|
|
89
|
+
&& optionalString(value.runtime_revision)
|
|
90
|
+
&& optionalString(value.sampling_revision)
|
|
91
|
+
&& value.report_schema_revision === 'pathgrade-results-v2'
|
|
92
|
+
&& (value.unavailable_reasons === undefined || (Array.isArray(value.unavailable_reasons)
|
|
93
|
+
&& value.unavailable_reasons.every(reason => reason === 'definition_metadata_missing'
|
|
94
|
+
|| reason === 'scorer_metadata_missing'
|
|
95
|
+
|| reason === 'runtime_metadata_missing'
|
|
96
|
+
|| reason === 'sampling_incomplete')));
|
|
97
|
+
}
|
|
98
|
+
function optionalString(value) {
|
|
99
|
+
return value === undefined || typeof value === 'string';
|
|
100
|
+
}
|
|
101
|
+
function optionalNumber(value) {
|
|
102
|
+
return value === undefined || typeof value === 'number';
|
|
103
|
+
}
|
|
104
|
+
function optionalNonNegativeInteger(value) {
|
|
105
|
+
return value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0);
|
|
106
|
+
}
|
|
107
|
+
function optionalPositiveInteger(value) {
|
|
108
|
+
return value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 1);
|
|
109
|
+
}
|
|
110
|
+
function isRecord(value) {
|
|
111
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
112
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
export function resolveSourceMetadata(filePath, cwd) {
|
|
5
|
+
if (!filePath)
|
|
6
|
+
return {};
|
|
7
|
+
const absolute = path.resolve(cwd, filePath);
|
|
8
|
+
const relative = path.relative(cwd, absolute).replaceAll(path.sep, '/');
|
|
9
|
+
if (!relative || relative === '..' || relative.startsWith('../'))
|
|
10
|
+
return {};
|
|
11
|
+
try {
|
|
12
|
+
return {
|
|
13
|
+
sourceFile: relative,
|
|
14
|
+
sourceRevision: `sha256:${createHash('sha256').update(readFileSync(absolute)).digest('hex')}`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return { sourceFile: relative };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -12,6 +12,8 @@ export interface ReportRunInput {
|
|
|
12
12
|
}
|
|
13
13
|
export interface ReportGroupInput {
|
|
14
14
|
groupName: string;
|
|
15
|
+
sourceFile?: string;
|
|
16
|
+
sourceRevision?: string;
|
|
15
17
|
cases: ReportCaseInput[];
|
|
16
18
|
}
|
|
17
19
|
export interface ReportCaseInput {
|
|
@@ -45,6 +47,8 @@ export interface ReportEvaluationInput {
|
|
|
45
47
|
scoringDurationMs?: number;
|
|
46
48
|
recordedAt?: string;
|
|
47
49
|
agent?: AgentExecutionMetadata;
|
|
50
|
+
evaluationDefinitionKey?: string;
|
|
51
|
+
scorerRevision?: string;
|
|
48
52
|
}
|
|
49
53
|
export interface PathgradeReportBuildResult {
|
|
50
54
|
report: PathgradeReport;
|
|
@@ -71,9 +75,10 @@ export interface ReportSummaryGroup {
|
|
|
71
75
|
trial_count: number;
|
|
72
76
|
diagnostics: ReportSummaryDiagnostics[];
|
|
73
77
|
}
|
|
74
|
-
|
|
78
|
+
interface ReportSummaryDiagnostics {
|
|
75
79
|
caseName: string;
|
|
76
80
|
state: ReportCaseState;
|
|
77
81
|
resultKind?: EvaluationResultKind;
|
|
78
82
|
report: DiagnosticsReport;
|
|
79
83
|
}
|
|
84
|
+
export {};
|