@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.
- package/bin/pathgrade.js +2 -0
- package/dist/affected/anchor.d.ts +18 -0
- package/dist/affected/anchor.js +42 -0
- package/dist/affected/config.d.ts +25 -0
- package/dist/affected/config.js +98 -0
- package/dist/affected/format.d.ts +45 -0
- package/dist/affected/format.js +87 -0
- package/dist/affected/git.d.ts +33 -0
- package/dist/affected/git.js +68 -0
- package/dist/affected/glob.d.ts +17 -0
- package/dist/affected/glob.js +25 -0
- package/dist/affected/meta.d.ts +26 -0
- package/dist/affected/meta.js +158 -0
- package/dist/affected/select.d.ts +30 -0
- package/dist/affected/select.js +101 -0
- package/dist/affected/sidecar.d.ts +32 -0
- package/dist/affected/sidecar.js +86 -0
- package/dist/affected/types.d.ts +43 -0
- package/dist/affected/types.js +7 -0
- package/dist/agents/claude/ask-user-answer-store.d.ts +28 -0
- package/dist/agents/claude/ask-user-answer-store.js +29 -0
- package/dist/agents/claude/ask-user-bridge.d.ts +56 -0
- package/dist/agents/claude/ask-user-bridge.js +162 -0
- package/dist/agents/claude/sdk-message-projector.d.ts +43 -0
- package/dist/agents/claude/sdk-message-projector.js +208 -0
- package/dist/agents/claude/sdk-options.d.ts +52 -0
- package/dist/agents/claude/sdk-options.js +79 -0
- package/dist/agents/claude.d.ts +56 -0
- package/dist/agents/claude.js +143 -0
- package/dist/agents/codex-app-server/agent.d.ts +35 -0
- package/dist/agents/codex-app-server/agent.js +388 -0
- package/dist/agents/codex-app-server/fixtures/app-server-client.d.ts +19 -0
- package/dist/agents/codex-app-server/fixtures/app-server-client.js +104 -0
- package/dist/agents/codex-app-server/fixtures/run-gate.d.ts +25 -0
- package/dist/agents/codex-app-server/fixtures/run-gate.js +48 -0
- package/dist/agents/codex-app-server/protocol/ClientRequest.d.ts +27 -0
- package/dist/agents/codex-app-server/protocol/ClientRequest.js +7 -0
- package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.d.ts +9 -0
- package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.js +4 -0
- package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.d.ts +6 -0
- package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.js +7 -0
- package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.d.ts +23 -0
- package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.js +8 -0
- package/dist/agents/codex-app-server/protocol/Op.d.ts +11 -0
- package/dist/agents/codex-app-server/protocol/Op.js +5 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.d.ts +10 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.js +6 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.d.ts +15 -0
- package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.js +4 -0
- package/dist/agents/codex-app-server/protocol/SandboxMode.d.ts +1 -0
- package/dist/agents/codex-app-server/protocol/SandboxMode.js +4 -0
- package/dist/agents/codex-app-server/protocol/ServerRequest.d.ts +42 -0
- package/dist/agents/codex-app-server/protocol/ServerRequest.js +7 -0
- package/dist/agents/codex-app-server/protocol/ThreadStartParams.d.ts +45 -0
- package/dist/agents/codex-app-server/protocol/ThreadStartParams.js +10 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.d.ts +6 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.d.ts +7 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.d.ts +10 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.d.ts +12 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.js +4 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.d.ts +9 -0
- package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.js +4 -0
- package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.d.ts +5 -0
- package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.js +4 -0
- package/dist/agents/codex-app-server/protocol/index.d.ts +16 -0
- package/dist/agents/codex-app-server/protocol/index.js +11 -0
- package/dist/agents/codex-app-server/transport.d.ts +84 -0
- package/dist/agents/codex-app-server/transport.js +246 -0
- package/dist/agents/codex-app-server/wire-translators.d.ts +19 -0
- package/dist/agents/codex-app-server/wire-translators.js +52 -0
- package/dist/agents/codex.d.ts +7 -0
- package/dist/agents/codex.js +164 -0
- package/dist/agents/cursor.d.ts +39 -0
- package/dist/agents/cursor.js +233 -0
- package/dist/agents/registry.d.ts +17 -0
- package/dist/agents/registry.js +27 -0
- package/dist/agents/transcript-agent.d.ts +14 -0
- package/dist/agents/transcript-agent.js +59 -0
- package/dist/analytics/engine.d.ts +18 -0
- package/dist/analytics/engine.js +66 -0
- package/dist/commands/affected.d.ts +31 -0
- package/dist/commands/affected.js +150 -0
- package/dist/commands/analyze.d.ts +21 -0
- package/dist/commands/analyze.js +167 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +242 -0
- package/dist/commands/preview-reactions.d.ts +10 -0
- package/dist/commands/preview-reactions.js +88 -0
- package/dist/commands/preview.d.ts +5 -0
- package/dist/commands/preview.js +17 -0
- package/dist/commands/report.d.ts +39 -0
- package/dist/commands/report.js +108 -0
- package/dist/commands/run-args.d.ts +22 -0
- package/dist/commands/run-args.js +69 -0
- package/dist/commands/run-changed.d.ts +26 -0
- package/dist/commands/run-changed.js +138 -0
- package/dist/commands/validate.d.ts +13 -0
- package/dist/commands/validate.js +233 -0
- package/dist/core/mcp-mock.d.ts +3 -0
- package/dist/core/mcp-mock.js +23 -0
- package/dist/core/mcp-mock.types.d.ts +15 -0
- package/dist/core/mcp-mock.types.js +1 -0
- package/dist/core/skills.d.ts +15 -0
- package/dist/core/skills.js +76 -0
- package/dist/evals/discovery.d.ts +7 -0
- package/dist/evals/discovery.js +69 -0
- package/dist/mcp-mock-server.d.ts +1 -0
- package/dist/mcp-mock-server.js +116 -0
- package/dist/pathgrade.d.ts +10 -0
- package/dist/pathgrade.js +243 -0
- package/dist/plugin/index.d.ts +9 -0
- package/dist/plugin/index.js +81 -0
- package/dist/plugin/lifecycle.d.ts +45 -0
- package/dist/plugin/lifecycle.js +146 -0
- package/dist/plugin/reporter.d.ts +36 -0
- package/dist/plugin/reporter.js +275 -0
- package/dist/plugin/setup.d.ts +1 -0
- package/dist/plugin/setup.js +3 -0
- package/dist/providers/copy-filter.d.ts +18 -0
- package/dist/providers/copy-filter.js +66 -0
- package/dist/providers/credentials.d.ts +36 -0
- package/dist/providers/credentials.js +202 -0
- package/dist/providers/mcp-config.d.ts +34 -0
- package/dist/providers/mcp-config.js +55 -0
- package/dist/providers/sandbox-exec.d.ts +8 -0
- package/dist/providers/sandbox-exec.js +82 -0
- package/dist/providers/sandbox.d.ts +22 -0
- package/dist/providers/sandbox.js +158 -0
- package/dist/providers/sandboxed-claude-spawn.d.ts +24 -0
- package/dist/providers/sandboxed-claude-spawn.js +57 -0
- package/dist/providers/workspace.d.ts +15 -0
- package/dist/providers/workspace.js +73 -0
- package/dist/reporters/browser.d.ts +2 -0
- package/dist/reporters/browser.js +85 -0
- package/dist/reporters/cli.d.ts +2 -0
- package/dist/reporters/cli.js +131 -0
- package/dist/reporters/diagnostics.d.ts +46 -0
- package/dist/reporters/diagnostics.js +147 -0
- package/dist/reporters/github-comment.d.ts +67 -0
- package/dist/reporters/github-comment.js +252 -0
- package/dist/reporters/loader.d.ts +8 -0
- package/dist/reporters/loader.js +36 -0
- package/dist/reporters/results-path.d.ts +2 -0
- package/dist/reporters/results-path.js +10 -0
- package/dist/reporters/verbose-emitter.d.ts +69 -0
- package/dist/reporters/verbose-emitter.js +103 -0
- package/dist/sdk/agent-crash.d.ts +19 -0
- package/dist/sdk/agent-crash.js +20 -0
- package/dist/sdk/agent-resolution.d.ts +12 -0
- package/dist/sdk/agent-resolution.js +21 -0
- package/dist/sdk/agent-result-log.d.ts +19 -0
- package/dist/sdk/agent-result-log.js +45 -0
- package/dist/sdk/agent.d.ts +4 -0
- package/dist/sdk/agent.js +354 -0
- package/dist/sdk/ask-bus/bus.d.ts +21 -0
- package/dist/sdk/ask-bus/bus.js +129 -0
- package/dist/sdk/ask-bus/handler.d.ts +31 -0
- package/dist/sdk/ask-bus/handler.js +244 -0
- package/dist/sdk/ask-bus/parsers.d.ts +9 -0
- package/dist/sdk/ask-bus/parsers.js +30 -0
- package/dist/sdk/ask-bus/projection.d.ts +26 -0
- package/dist/sdk/ask-bus/projection.js +45 -0
- package/dist/sdk/ask-bus/types.d.ts +69 -0
- package/dist/sdk/ask-bus/types.js +1 -0
- package/dist/sdk/chat.d.ts +29 -0
- package/dist/sdk/chat.js +93 -0
- package/dist/sdk/conversation-window.d.ts +14 -0
- package/dist/sdk/conversation-window.js +38 -0
- package/dist/sdk/converse.d.ts +42 -0
- package/dist/sdk/converse.js +329 -0
- package/dist/sdk/eval-runtime.d.ts +10 -0
- package/dist/sdk/eval-runtime.js +17 -0
- package/dist/sdk/evaluate.d.ts +13 -0
- package/dist/sdk/evaluate.js +325 -0
- package/dist/sdk/index.d.ts +31 -0
- package/dist/sdk/index.js +20 -0
- package/dist/sdk/judge-pipeline.d.ts +7 -0
- package/dist/sdk/judge-pipeline.js +178 -0
- package/dist/sdk/judge-prompt-builder.d.ts +7 -0
- package/dist/sdk/judge-prompt-builder.js +76 -0
- package/dist/sdk/judge-tool-runner.d.ts +19 -0
- package/dist/sdk/judge-tool-runner.js +51 -0
- package/dist/sdk/judge-tool-session.d.ts +47 -0
- package/dist/sdk/judge-tool-session.js +213 -0
- package/dist/sdk/judge-tools.d.ts +29 -0
- package/dist/sdk/judge-tools.js +283 -0
- package/dist/sdk/managed-session.d.ts +50 -0
- package/dist/sdk/managed-session.js +110 -0
- package/dist/sdk/persona.d.ts +5 -0
- package/dist/sdk/persona.js +33 -0
- package/dist/sdk/reaction-loader.d.ts +4 -0
- package/dist/sdk/reaction-loader.js +119 -0
- package/dist/sdk/reaction-preview.d.ts +6 -0
- package/dist/sdk/reaction-preview.js +94 -0
- package/dist/sdk/run-scorer.d.ts +10 -0
- package/dist/sdk/run-scorer.js +124 -0
- package/dist/sdk/runtime-policy.d.ts +21 -0
- package/dist/sdk/runtime-policy.js +89 -0
- package/dist/sdk/scorer-utils.d.ts +6 -0
- package/dist/sdk/scorer-utils.js +39 -0
- package/dist/sdk/scorers.d.ts +64 -0
- package/dist/sdk/scorers.js +80 -0
- package/dist/sdk/snapshots.d.ts +41 -0
- package/dist/sdk/snapshots.js +148 -0
- package/dist/sdk/types.d.ts +416 -0
- package/dist/sdk/types.js +17 -0
- package/dist/sdk/visible-turn.d.ts +4 -0
- package/dist/sdk/visible-turn.js +23 -0
- package/dist/tool-events.d.ts +45 -0
- package/dist/tool-events.js +167 -0
- package/dist/types.d.ts +372 -0
- package/dist/types.js +37 -0
- package/dist/utils/cli.d.ts +55 -0
- package/dist/utils/cli.js +131 -0
- package/dist/utils/env.d.ts +8 -0
- package/dist/utils/env.js +26 -0
- package/dist/utils/llm-mocks.d.ts +43 -0
- package/dist/utils/llm-mocks.js +108 -0
- package/dist/utils/llm-providers/anthropic.d.ts +2 -0
- package/dist/utils/llm-providers/anthropic.js +132 -0
- package/dist/utils/llm-providers/cli.d.ts +20 -0
- package/dist/utils/llm-providers/cli.js +162 -0
- package/dist/utils/llm-providers/openai.d.ts +2 -0
- package/dist/utils/llm-providers/openai.js +56 -0
- package/dist/utils/llm-types.d.ts +107 -0
- package/dist/utils/llm-types.js +1 -0
- package/dist/utils/llm.d.ts +36 -0
- package/dist/utils/llm.js +209 -0
- package/dist/utils/shutdown.d.ts +15 -0
- package/dist/utils/shutdown.js +41 -0
- package/dist/utils/timeout.d.ts +8 -0
- package/dist/utils/timeout.js +32 -0
- package/dist/viewer.html +1178 -0
- package/package.json +88 -0
- package/templates/eval.ts.template +28 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Calculates Normalized Gain (NG) as defined in the Skill Eval paper.
|
|
5
|
+
* NG = (p_with - p_without) / (1 - p_without)
|
|
6
|
+
*/
|
|
7
|
+
export function calculateNormalizedGain(pWith, pWithout) {
|
|
8
|
+
if (pWithout === 1) {
|
|
9
|
+
return pWith === 1 ? 0 : -1;
|
|
10
|
+
}
|
|
11
|
+
return (pWith - pWithout) / (1 - pWithout);
|
|
12
|
+
}
|
|
13
|
+
export class AnalyticsEngine {
|
|
14
|
+
async loadReports(logDir) {
|
|
15
|
+
const files = await fs.readdir(logDir);
|
|
16
|
+
const reports = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
if (file.endsWith('.json')) {
|
|
19
|
+
const report = await fs.readJSON(path.join(logDir, file));
|
|
20
|
+
reports.push(report);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return reports;
|
|
24
|
+
}
|
|
25
|
+
aggregate(reports) {
|
|
26
|
+
const taskGroups = {};
|
|
27
|
+
for (const report of reports) {
|
|
28
|
+
if (!taskGroups[report.task]) {
|
|
29
|
+
taskGroups[report.task] = { withSkill: [], withoutSkill: [] };
|
|
30
|
+
}
|
|
31
|
+
const hasSkills = report.skills_used && report.skills_used.length > 0;
|
|
32
|
+
if (hasSkills) {
|
|
33
|
+
taskGroups[report.task].withSkill.push(report);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
taskGroups[report.task].withoutSkill.push(report);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const stats = [];
|
|
40
|
+
for (const [task, data] of Object.entries(taskGroups)) {
|
|
41
|
+
const allReports = [...data.withSkill, ...data.withoutSkill];
|
|
42
|
+
const avgWith = data.withSkill.length > 0
|
|
43
|
+
? data.withSkill.reduce((a, b) => a + b.pass_rate, 0) / data.withSkill.length
|
|
44
|
+
: 0;
|
|
45
|
+
const avgWithout = data.withoutSkill.length > 0
|
|
46
|
+
? data.withoutSkill.reduce((a, b) => a + b.pass_rate, 0) / data.withoutSkill.length
|
|
47
|
+
: 0;
|
|
48
|
+
const allTrials = allReports.flatMap(r => r.trials);
|
|
49
|
+
const avgDurationMs = allTrials.length > 0
|
|
50
|
+
? allTrials.reduce((s, t) => s + (t.duration_ms || 0), 0) / allTrials.length
|
|
51
|
+
: 0;
|
|
52
|
+
const avgCommands = allTrials.length > 0
|
|
53
|
+
? allTrials.reduce((s, t) => s + (t.n_commands || 0), 0) / allTrials.length
|
|
54
|
+
: 0;
|
|
55
|
+
stats.push({
|
|
56
|
+
task,
|
|
57
|
+
passRateWithSkill: avgWith,
|
|
58
|
+
passRateNoSkill: avgWithout,
|
|
59
|
+
normalizedGain: calculateNormalizedGain(avgWith, avgWithout),
|
|
60
|
+
avgDurationMs,
|
|
61
|
+
avgCommands
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return stats;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pathgrade affected` — the selection primitive.
|
|
3
|
+
*
|
|
4
|
+
* Input precedence for the change-set (Issue 6):
|
|
5
|
+
* 1. `--changed-files=<path>` wins unconditionally (non-git contexts).
|
|
6
|
+
* 2. `--since=<ref>` diffs `<ref>...HEAD`.
|
|
7
|
+
* 3. No flags → auto-derive base ref from GitHub Actions env + git merge-base.
|
|
8
|
+
*
|
|
9
|
+
* Output contract: one selected path per stdout line, sorted lexicographically.
|
|
10
|
+
* The resolved base-ref one-liner goes to stderr so it doesn't pollute the
|
|
11
|
+
* composable stdout list. Issues 7/8/10 wire warnings, global short-circuit,
|
|
12
|
+
* and `--explain` / `--json` presentation.
|
|
13
|
+
*/
|
|
14
|
+
export interface RunAffectedOptions {
|
|
15
|
+
/** Absolute path to the repo root (CLI passes `process.cwd()`). */
|
|
16
|
+
cwd: string;
|
|
17
|
+
/** Newline-delimited list of repo-relative changed files (overrides git). */
|
|
18
|
+
changedFilesPath?: string;
|
|
19
|
+
/** Git ref to diff against (`<ref>...HEAD`). Overrides auto-detection. */
|
|
20
|
+
since?: string;
|
|
21
|
+
/** Emit a human-readable summary to stderr. Stdout contract unchanged. */
|
|
22
|
+
explain?: boolean;
|
|
23
|
+
/** Emit structured JSON to stdout instead of the plain list. */
|
|
24
|
+
json?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function runAffected(opts: RunAffectedOptions): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* Discover repo-relative `*.eval.ts` files under `cwd`, honoring the same
|
|
29
|
+
* exclude defaults the vitest plugin uses.
|
|
30
|
+
*/
|
|
31
|
+
export declare function discoverEvalFiles(cwd: string): string[];
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pathgrade affected` — the selection primitive.
|
|
3
|
+
*
|
|
4
|
+
* Input precedence for the change-set (Issue 6):
|
|
5
|
+
* 1. `--changed-files=<path>` wins unconditionally (non-git contexts).
|
|
6
|
+
* 2. `--since=<ref>` diffs `<ref>...HEAD`.
|
|
7
|
+
* 3. No flags → auto-derive base ref from GitHub Actions env + git merge-base.
|
|
8
|
+
*
|
|
9
|
+
* Output contract: one selected path per stdout line, sorted lexicographically.
|
|
10
|
+
* The resolved base-ref one-liner goes to stderr so it doesn't pollute the
|
|
11
|
+
* composable stdout list. Issues 7/8/10 wire warnings, global short-circuit,
|
|
12
|
+
* and `--explain` / `--json` presentation.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import { configDefaults } from 'vitest/config';
|
|
16
|
+
import { selectAffected } from '../affected/select.js';
|
|
17
|
+
import { resolveBaseRef, computeChangedFiles } from '../affected/git.js';
|
|
18
|
+
import { loadAffectedConfig } from '../affected/config.js';
|
|
19
|
+
import { formatExplain, formatJson } from '../affected/format.js';
|
|
20
|
+
import { discoverPathgradeEvalFiles } from '../evals/discovery.js';
|
|
21
|
+
const DEFAULT_EXCLUDE = [
|
|
22
|
+
...configDefaults.exclude,
|
|
23
|
+
'.worktrees/**',
|
|
24
|
+
'worktrees/**',
|
|
25
|
+
'**/node_modules/**',
|
|
26
|
+
'**/fixtures/**',
|
|
27
|
+
];
|
|
28
|
+
export async function runAffected(opts) {
|
|
29
|
+
const { cwd } = opts;
|
|
30
|
+
let changes;
|
|
31
|
+
try {
|
|
32
|
+
const resolved = resolveChangeSet(opts);
|
|
33
|
+
if ('error' in resolved) {
|
|
34
|
+
process.stderr.write(`${resolved.error}\n`);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
changes = resolved;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
+
process.stderr.write(`pathgrade affected: ${msg}\n`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
if (changes.baseRefLine) {
|
|
45
|
+
process.stderr.write(`${changes.baseRefLine}\n`);
|
|
46
|
+
}
|
|
47
|
+
const evalFiles = discoverEvalFiles(cwd);
|
|
48
|
+
const config = await loadAffectedConfig(cwd, {
|
|
49
|
+
onWarning: w => process.stderr.write(`${w}\n`),
|
|
50
|
+
});
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = selectAffected({
|
|
54
|
+
evalFiles,
|
|
55
|
+
changedFiles: changes.changedFiles,
|
|
56
|
+
repoRoot: cwd,
|
|
57
|
+
baseRef: changes.baseRef,
|
|
58
|
+
global: config.global,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
// Malformed `__pathgradeMeta` — the PRD requires a hard error.
|
|
63
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
64
|
+
process.stderr.write(`pathgrade affected: ${msg}\n`);
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
// Default mode still prints warnings + global summary to stderr so the
|
|
68
|
+
// Issue 5/6 behavior is preserved when neither flag is set.
|
|
69
|
+
if (!opts.explain) {
|
|
70
|
+
for (const w of result.warnings) {
|
|
71
|
+
process.stderr.write(`warning: ${w}\n`);
|
|
72
|
+
}
|
|
73
|
+
if (result.globalMatch) {
|
|
74
|
+
process.stderr.write(`pathgrade: global match: \`${result.globalMatch}\` — selecting all ${result.selected.length} evals\n`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (opts.explain) {
|
|
78
|
+
process.stderr.write(formatExplain(result));
|
|
79
|
+
}
|
|
80
|
+
if (opts.json) {
|
|
81
|
+
process.stdout.write(formatJson(result));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
printSelectedToStdout(result);
|
|
85
|
+
}
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Decide where the change-set comes from and produce it.
|
|
90
|
+
*
|
|
91
|
+
* Returns `{ error }` when git auto-detection fails (so the caller can
|
|
92
|
+
* surface a clean stderr message); throws only on actual I/O / programmer
|
|
93
|
+
* errors (e.g., unreadable `--changed-files` path).
|
|
94
|
+
*/
|
|
95
|
+
function resolveChangeSet(opts) {
|
|
96
|
+
const { changedFilesPath, since } = opts;
|
|
97
|
+
if (changedFilesPath) {
|
|
98
|
+
let changedFiles;
|
|
99
|
+
try {
|
|
100
|
+
changedFiles = readChangedFilesList(changedFilesPath);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
104
|
+
throw new Error(`could not read --changed-files: ${msg}`);
|
|
105
|
+
}
|
|
106
|
+
return { baseRef: 'explicit', changedFiles };
|
|
107
|
+
}
|
|
108
|
+
if (since) {
|
|
109
|
+
const changedFiles = computeChangedFiles(since);
|
|
110
|
+
return {
|
|
111
|
+
baseRef: `${since}`,
|
|
112
|
+
changedFiles,
|
|
113
|
+
baseRefLine: `pathgrade: base = ${since} (--since)`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Auto-derive from git.
|
|
117
|
+
const resolution = resolveBaseRef();
|
|
118
|
+
if ('error' in resolution)
|
|
119
|
+
return { error: resolution.error };
|
|
120
|
+
const shortSha = resolution.sha.slice(0, 7);
|
|
121
|
+
const changedFiles = computeChangedFiles(resolution.sha);
|
|
122
|
+
return {
|
|
123
|
+
baseRef: `${resolution.base}@${shortSha}`,
|
|
124
|
+
changedFiles,
|
|
125
|
+
baseRefLine: `pathgrade: base = ${resolution.base}@${shortSha} (merge-base with HEAD)`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function readChangedFilesList(filePath) {
|
|
129
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
130
|
+
return content
|
|
131
|
+
.split('\n')
|
|
132
|
+
.map(l => l.trim())
|
|
133
|
+
.filter(l => l.length > 0);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Discover repo-relative `*.eval.ts` files under `cwd`, honoring the same
|
|
137
|
+
* exclude defaults the vitest plugin uses.
|
|
138
|
+
*/
|
|
139
|
+
export function discoverEvalFiles(cwd) {
|
|
140
|
+
return discoverPathgradeEvalFiles({
|
|
141
|
+
cwd,
|
|
142
|
+
include: ['**/*.eval.ts'],
|
|
143
|
+
exclude: DEFAULT_EXCLUDE,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function printSelectedToStdout(result) {
|
|
147
|
+
for (const entry of result.selected) {
|
|
148
|
+
process.stdout.write(`${entry.file}\n`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AnalyzeOptions {
|
|
2
|
+
skill?: string;
|
|
3
|
+
dir?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SkillAnalysis {
|
|
6
|
+
skillName: string;
|
|
7
|
+
description: string;
|
|
8
|
+
procedures: string[];
|
|
9
|
+
expectedOutputs: string[];
|
|
10
|
+
suggestedScorers: Array<{
|
|
11
|
+
type: 'check' | 'judge';
|
|
12
|
+
name: string;
|
|
13
|
+
hint: string;
|
|
14
|
+
}>;
|
|
15
|
+
workspaceHint: string;
|
|
16
|
+
hasFixtures: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run the analyze command. Returns exit code (0 = success, 1 = error).
|
|
20
|
+
*/
|
|
21
|
+
export declare function runAnalyze(dir: string, opts?: AnalyzeOptions): Promise<number>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pathgrade analyze` command.
|
|
3
|
+
*
|
|
4
|
+
* Reads SKILL.md files in a directory and outputs structured JSON analysis to stdout.
|
|
5
|
+
* No LLM calls — purely deterministic parsing.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { detectSkills } from '../core/skills.js';
|
|
10
|
+
/**
|
|
11
|
+
* Run the analyze command. Returns exit code (0 = success, 1 = error).
|
|
12
|
+
*/
|
|
13
|
+
export async function runAnalyze(dir, opts = {}) {
|
|
14
|
+
const targetDir = opts.dir || dir;
|
|
15
|
+
const skills = await detectSkills(targetDir);
|
|
16
|
+
if (skills.length === 0) {
|
|
17
|
+
process.stdout.write(JSON.stringify({
|
|
18
|
+
error: 'no-skill-found',
|
|
19
|
+
message: 'No SKILL.md found. Create one or use --dir to point to a skill directory.',
|
|
20
|
+
}));
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
if (opts.skill) {
|
|
24
|
+
const match = skills.find(s => s.name === opts.skill);
|
|
25
|
+
if (!match) {
|
|
26
|
+
process.stdout.write(JSON.stringify({
|
|
27
|
+
error: 'skill-not-found',
|
|
28
|
+
message: `Skill '${opts.skill}' not found. Available: ${skills.map(s => s.name).join(', ')}`,
|
|
29
|
+
}));
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
const analysis = analyzeSkill(match, targetDir);
|
|
33
|
+
process.stdout.write(JSON.stringify(analysis));
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
if (skills.length === 1) {
|
|
37
|
+
const analysis = analyzeSkill(skills[0], targetDir);
|
|
38
|
+
process.stdout.write(JSON.stringify(analysis));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const analyses = skills.map(s => analyzeSkill(s, targetDir));
|
|
42
|
+
process.stdout.write(JSON.stringify(analyses));
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
function analyzeSkill(skill, dir) {
|
|
47
|
+
const { name, description } = parseFrontmatter(skill.skillMd);
|
|
48
|
+
const procedures = extractProcedures(skill.skillMd);
|
|
49
|
+
const expectedOutputs = extractExpectedOutputs(procedures);
|
|
50
|
+
const suggestedScorers = buildSuggestedScorers(expectedOutputs);
|
|
51
|
+
const workspaceHint = extractWorkspaceHint(procedures);
|
|
52
|
+
const hasFixtures = fs.pathExistsSync(path.join(skill.path, 'fixtures')) ||
|
|
53
|
+
fs.pathExistsSync(path.join(skill.path, 'test', 'fixtures'));
|
|
54
|
+
return {
|
|
55
|
+
skillName: name || skill.name,
|
|
56
|
+
description: description || extractFirstParagraph(skill.skillMd),
|
|
57
|
+
procedures,
|
|
58
|
+
expectedOutputs,
|
|
59
|
+
suggestedScorers,
|
|
60
|
+
workspaceHint,
|
|
61
|
+
hasFixtures,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function parseFrontmatter(content) {
|
|
65
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
66
|
+
if (!fmMatch)
|
|
67
|
+
return {};
|
|
68
|
+
const fm = fmMatch[1];
|
|
69
|
+
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
70
|
+
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
71
|
+
return {
|
|
72
|
+
name: nameMatch ? nameMatch[1].trim().replace(/^['"]|['"]$/g, '') : undefined,
|
|
73
|
+
description: descMatch ? descMatch[1].trim().replace(/^['"]|['"]$/g, '') : undefined,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function extractFirstParagraph(content) {
|
|
77
|
+
// Strip frontmatter
|
|
78
|
+
const stripped = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '');
|
|
79
|
+
const lines = stripped.split('\n');
|
|
80
|
+
let foundHeading = false;
|
|
81
|
+
const paragraphLines = [];
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (line.startsWith('# ') && !foundHeading) {
|
|
84
|
+
foundHeading = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (foundHeading) {
|
|
88
|
+
if (line.trim() === '' && paragraphLines.length > 0)
|
|
89
|
+
break;
|
|
90
|
+
if (line.startsWith('#'))
|
|
91
|
+
break;
|
|
92
|
+
if (line.trim())
|
|
93
|
+
paragraphLines.push(line.trim());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return paragraphLines.join(' ');
|
|
97
|
+
}
|
|
98
|
+
function extractProcedures(content) {
|
|
99
|
+
const lines = content.split('\n');
|
|
100
|
+
const procedures = [];
|
|
101
|
+
let inProcedureSection = false;
|
|
102
|
+
for (const line of lines) {
|
|
103
|
+
// Check for headings containing procedure/step/how to
|
|
104
|
+
if (/^#{1,3}\s+/i.test(line)) {
|
|
105
|
+
const headingText = line.replace(/^#{1,3}\s+/, '').toLowerCase();
|
|
106
|
+
inProcedureSection = /procedure|step|how to/i.test(headingText);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (inProcedureSection) {
|
|
110
|
+
// Numbered list: "1. Do something" or "- Do something"
|
|
111
|
+
const listMatch = line.match(/^\s*(?:\d+\.|[-*])\s+(.+)/);
|
|
112
|
+
if (listMatch) {
|
|
113
|
+
procedures.push(listMatch[1].trim());
|
|
114
|
+
}
|
|
115
|
+
// Empty line after list items ends the section only if we have procedures
|
|
116
|
+
if (line.trim() === '' && procedures.length > 0) {
|
|
117
|
+
// Continue — might be spacing between items
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return procedures;
|
|
122
|
+
}
|
|
123
|
+
const CREATION_VERBS = /\b(?:create|write|generate|save|produce|output)\b/i;
|
|
124
|
+
const FILENAME_PATTERN = /`([^`]+\.[a-zA-Z]{1,5})`|"([^"]+\.[a-zA-Z]{1,5})"|'([^']+\.[a-zA-Z]{1,5})'/g;
|
|
125
|
+
function extractExpectedOutputs(procedures) {
|
|
126
|
+
const outputs = [];
|
|
127
|
+
for (const proc of procedures) {
|
|
128
|
+
if (!CREATION_VERBS.test(proc))
|
|
129
|
+
continue;
|
|
130
|
+
let match;
|
|
131
|
+
FILENAME_PATTERN.lastIndex = 0;
|
|
132
|
+
while ((match = FILENAME_PATTERN.exec(proc)) !== null) {
|
|
133
|
+
const filename = match[1] || match[2] || match[3];
|
|
134
|
+
if (filename && !outputs.includes(filename)) {
|
|
135
|
+
outputs.push(filename);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return outputs;
|
|
140
|
+
}
|
|
141
|
+
function buildSuggestedScorers(expectedOutputs) {
|
|
142
|
+
const scorers = [];
|
|
143
|
+
for (const output of expectedOutputs) {
|
|
144
|
+
const baseName = output.replace(/\.[^.]+$/, '').replace(/[^a-zA-Z0-9]+/g, '-');
|
|
145
|
+
scorers.push({
|
|
146
|
+
type: 'check',
|
|
147
|
+
name: `${baseName}-exists`,
|
|
148
|
+
hint: `Check ${output} exists in workspace`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Always add a judge scorer
|
|
152
|
+
scorers.push({
|
|
153
|
+
type: 'judge',
|
|
154
|
+
name: 'quality',
|
|
155
|
+
hint: 'Evaluate overall quality of the agent\'s work',
|
|
156
|
+
});
|
|
157
|
+
return scorers;
|
|
158
|
+
}
|
|
159
|
+
function extractWorkspaceHint(procedures) {
|
|
160
|
+
const text = procedures.join(' ').toLowerCase();
|
|
161
|
+
const keywords = ['workspace', 'project', 'directory', 'fixture', 'file'];
|
|
162
|
+
const found = keywords.filter(k => text.includes(k));
|
|
163
|
+
if (found.length > 0) {
|
|
164
|
+
return `Skill operates on ${found.join(', ')} resources in the workspace`;
|
|
165
|
+
}
|
|
166
|
+
return '';
|
|
167
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pathgrade init` command.
|
|
3
|
+
*
|
|
4
|
+
* Detects skills in the current directory and generates a *.eval.ts config.
|
|
5
|
+
* With an API key, uses an LLM to generate intelligent eval tasks.
|
|
6
|
+
* Without an API key, generates a well-commented template.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { detectSkills } from '../core/skills.js';
|
|
11
|
+
import { parseEnvFile } from '../utils/env.js';
|
|
12
|
+
import { callLLM, isClaudeCliAvailable } from '../utils/llm.js';
|
|
13
|
+
/** Find any existing eval config file in a directory. */
|
|
14
|
+
async function findExistingEvalFile(dir) {
|
|
15
|
+
const files = await fs.readdir(dir);
|
|
16
|
+
const evalFile = files.find(f => f.endsWith('.eval.ts'));
|
|
17
|
+
if (evalFile)
|
|
18
|
+
return path.join(dir, evalFile);
|
|
19
|
+
const legacy = path.join(dir, 'eval.ts');
|
|
20
|
+
if (await fs.pathExists(legacy))
|
|
21
|
+
return legacy;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export async function runInit(dir, opts = {}) {
|
|
25
|
+
const existing = await findExistingEvalFile(dir);
|
|
26
|
+
if (existing) {
|
|
27
|
+
if (opts.force) {
|
|
28
|
+
await fs.remove(existing);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const name = path.basename(existing);
|
|
32
|
+
console.error(` ${name} already exists. Use --force to overwrite.`);
|
|
33
|
+
throw new Error(`${name} already exists`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
console.log('\npathgrade init\n');
|
|
37
|
+
// Detect skills
|
|
38
|
+
const skills = await detectSkills(dir);
|
|
39
|
+
// Derive eval filename: <skill-name>.eval.ts or <dirname>.eval.ts
|
|
40
|
+
const dirName = path.basename(dir);
|
|
41
|
+
if (skills.length === 0) {
|
|
42
|
+
console.log(' No SKILL.md found. Creating a generic template.');
|
|
43
|
+
console.log(' Place a SKILL.md in this directory for better scaffolding.\n');
|
|
44
|
+
const evalPath = path.join(dir, `${dirName}.eval.ts`);
|
|
45
|
+
await writeTemplate(evalPath, 'my-skill', 'Describe what the agent should do with this skill.');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(` Found ${skills.length} skill(s): ${skills.map(s => s.name).join(', ')}\n`);
|
|
49
|
+
// Load .env file if present
|
|
50
|
+
const envPath = path.join(dir, '.env');
|
|
51
|
+
if (await fs.pathExists(envPath)) {
|
|
52
|
+
const envVars = parseEnvFile(await fs.readFile(envPath, 'utf-8'));
|
|
53
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
54
|
+
if (!process.env[key]) {
|
|
55
|
+
process.env[key] = value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Try LLM-powered scaffold — auto-detect from available API key or CLI
|
|
60
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
61
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
62
|
+
const hasApiKey = !!(anthropicKey || openaiKey);
|
|
63
|
+
const cliAvailable = await isClaudeCliAvailable();
|
|
64
|
+
const evalName = skills.length === 1 ? skills[0].name : dirName;
|
|
65
|
+
const evalPath = path.join(dir, `${evalName}.eval.ts`);
|
|
66
|
+
if (hasApiKey || cliAvailable) {
|
|
67
|
+
const { Spinner, fmt } = await import('../utils/cli.js');
|
|
68
|
+
const label = 'generating eval with available LLM backend';
|
|
69
|
+
const spinner = new Spinner('init', label);
|
|
70
|
+
try {
|
|
71
|
+
const config = await generateWithLLM(skills);
|
|
72
|
+
await fs.writeFile(evalPath, config, 'utf-8');
|
|
73
|
+
spinner.stop(fmt.green(`created ${path.basename(evalPath)}`));
|
|
74
|
+
console.log(` Review and edit the file, then run: pathgrade\n`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
spinner.stop(fmt.red(`AI generation failed: ${err.message}`));
|
|
79
|
+
console.log(' Falling back to template.\n');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(' Install Claude CLI or set an API key for AI-powered eval generation.\n');
|
|
84
|
+
}
|
|
85
|
+
// Fallback: template-based scaffold
|
|
86
|
+
const skill = skills[0];
|
|
87
|
+
const taskName = `test-${skill.name}`;
|
|
88
|
+
const instruction = extractInstructionHint(skill.skillMd);
|
|
89
|
+
await writeTemplate(evalPath, taskName, instruction);
|
|
90
|
+
}
|
|
91
|
+
async function writeTemplate(evalPath, taskName, instruction) {
|
|
92
|
+
const templatePath = path.join(import.meta.dirname, '..', '..', 'templates', 'eval.ts.template');
|
|
93
|
+
let template;
|
|
94
|
+
if (await fs.pathExists(templatePath)) {
|
|
95
|
+
template = await fs.readFile(templatePath, 'utf-8');
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Inline fallback if template file not found (e.g., if installed globally)
|
|
99
|
+
template = getInlineTemplate();
|
|
100
|
+
}
|
|
101
|
+
const result = template
|
|
102
|
+
.replace(/\{\{TASK_NAME\}\}/g, taskName)
|
|
103
|
+
.replace(/\{\{INSTRUCTION\}\}/g, instruction);
|
|
104
|
+
await fs.writeFile(evalPath, result, 'utf-8');
|
|
105
|
+
console.log(` Created ${path.basename(evalPath)}.`);
|
|
106
|
+
console.log(` Edit the file to define your eval tasks, then run: pathgrade\n`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Extract a reasonable instruction hint from SKILL.md content.
|
|
110
|
+
*/
|
|
111
|
+
function extractInstructionHint(skillMd) {
|
|
112
|
+
// Try to get the first paragraph after the main heading
|
|
113
|
+
const lines = skillMd.split('\n');
|
|
114
|
+
let foundHeading = false;
|
|
115
|
+
const paragraphLines = [];
|
|
116
|
+
for (const line of lines) {
|
|
117
|
+
if (line.startsWith('# ') && !foundHeading) {
|
|
118
|
+
foundHeading = true;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (foundHeading) {
|
|
122
|
+
if (line.trim() === '' && paragraphLines.length > 0)
|
|
123
|
+
break;
|
|
124
|
+
if (line.startsWith('#'))
|
|
125
|
+
break;
|
|
126
|
+
if (line.trim())
|
|
127
|
+
paragraphLines.push(line.trim());
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (paragraphLines.length > 0) {
|
|
131
|
+
return `TODO: Write an instruction based on this skill.\n Skill description: ${paragraphLines.join(' ')}`;
|
|
132
|
+
}
|
|
133
|
+
return 'TODO: Write an instruction for the agent.';
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Build the init prompt for eval.ts generation.
|
|
137
|
+
*/
|
|
138
|
+
function buildInitPrompt(skills) {
|
|
139
|
+
const skillSummaries = skills.map(s => `## Skill: ${s.name}\n\n${s.skillMd}`).join('\n\n---\n\n');
|
|
140
|
+
return `You are an expert at creating evaluation tasks for AI agent skills.
|
|
141
|
+
|
|
142
|
+
Given the following skill definition(s), generate a single \`.eval.ts\` file
|
|
143
|
+
using the current PathGrade runtime API from \`@wix/pathgrade\`.
|
|
144
|
+
|
|
145
|
+
For each task:
|
|
146
|
+
- Write a realistic user instruction
|
|
147
|
+
- Define workspace files if needed
|
|
148
|
+
- Create an agent with \`createAgent(...)\`
|
|
149
|
+
- Run the agent with \`agent.prompt(...)\`
|
|
150
|
+
- Score the result with \`evaluate(agent, [...])\`
|
|
151
|
+
- Use deterministic scorers via \`check()\` or \`score()\`
|
|
152
|
+
- Use qualitative scoring via \`judge()\`
|
|
153
|
+
|
|
154
|
+
IMPORTANT SCORING RULES:
|
|
155
|
+
- Deterministic scorers must only check observable outcomes
|
|
156
|
+
- Use \`check()\` for pass/fail assertions and \`score()\` for partial credit
|
|
157
|
+
- Judge scorers use \`judge(name, { rubric, weight })\`
|
|
158
|
+
- For workspace files, only reference files that exist in the skill directory or that the agent will create
|
|
159
|
+
|
|
160
|
+
CRITICAL — FILENAME CONSISTENCY:
|
|
161
|
+
- The instruction MUST tell the agent exactly what filenames to create
|
|
162
|
+
- Deterministic scorers MUST only check filenames explicitly mentioned in the instruction
|
|
163
|
+
- NEVER check for a hardcoded filename that the instruction does not mention
|
|
164
|
+
|
|
165
|
+
${skillSummaries}
|
|
166
|
+
|
|
167
|
+
Respond with ONLY the \`.eval.ts\` file content. Start with the import
|
|
168
|
+
statement. Use this format:
|
|
169
|
+
|
|
170
|
+
import { describe, it, expect } from 'vitest';
|
|
171
|
+
import { createAgent, check, judge, evaluate } from '@wix/pathgrade';
|
|
172
|
+
|
|
173
|
+
describe('<task-name>', () => {
|
|
174
|
+
it('scores the agent result', async () => {
|
|
175
|
+
const agent = await createAgent({
|
|
176
|
+
agent: 'claude',
|
|
177
|
+
timeout: 300,
|
|
178
|
+
skillDir: __dirname,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await agent.prompt(\`<realistic user instruction>\`);
|
|
183
|
+
|
|
184
|
+
const result = await evaluate(agent, [
|
|
185
|
+
check('required artifact exists', async ({ workspace }) => true),
|
|
186
|
+
judge('workflow quality', {
|
|
187
|
+
rubric: \`<evaluation criteria>\`,
|
|
188
|
+
weight: 0.3,
|
|
189
|
+
}),
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
expect(result.score).toBeGreaterThanOrEqual(0);
|
|
193
|
+
} finally {
|
|
194
|
+
await agent.dispose();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});`;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Generate eval.ts content using the shared LLM boundary.
|
|
201
|
+
* This keeps init aligned with the CLI-first provider selection used
|
|
202
|
+
* elsewhere in the runtime.
|
|
203
|
+
*/
|
|
204
|
+
async function generateWithLLM(skills) {
|
|
205
|
+
const prompt = buildInitPrompt(skills);
|
|
206
|
+
const result = await callLLM(prompt);
|
|
207
|
+
// Extract TypeScript from response (strip markdown code fences if present)
|
|
208
|
+
const tsContent = result.text.replace(/```(?:typescript|ts)?\n?/g, '').replace(/```\n?/g, '').trim();
|
|
209
|
+
return tsContent + '\n';
|
|
210
|
+
}
|
|
211
|
+
function getInlineTemplate() {
|
|
212
|
+
return `import { describe, it, expect } from 'vitest';
|
|
213
|
+
import { createAgent, check, judge, evaluate } from '@wix/pathgrade';
|
|
214
|
+
|
|
215
|
+
describe('{{TASK_NAME}}', () => {
|
|
216
|
+
it('scores the agent result', async () => {
|
|
217
|
+
const agent = await createAgent({
|
|
218
|
+
agent: 'claude',
|
|
219
|
+
timeout: 300,
|
|
220
|
+
skillDir: __dirname,
|
|
221
|
+
// workspace: path.join(__dirname, 'test', 'fixtures', 'my-fixture'),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
await agent.prompt(\`{{INSTRUCTION}}\`);
|
|
226
|
+
|
|
227
|
+
const result = await evaluate(agent, [
|
|
228
|
+
check('TODO: add deterministic scorer', async () => false),
|
|
229
|
+
judge('TODO: add rubric scorer', {
|
|
230
|
+
rubric: \`TODO: Write evaluation criteria.\`,
|
|
231
|
+
weight: 0.3,
|
|
232
|
+
}),
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
expect(result.score).toBeGreaterThanOrEqual(0);
|
|
236
|
+
} finally {
|
|
237
|
+
await agent.dispose();
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type PreviewFormat = 'cli' | 'json';
|
|
2
|
+
export interface PreviewReactionsCommandOptions {
|
|
3
|
+
snapshot: string;
|
|
4
|
+
reactions: string;
|
|
5
|
+
format?: PreviewFormat;
|
|
6
|
+
cwd?: string;
|
|
7
|
+
write?: (chunk: string) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function runPreviewReactions(input: string[] | PreviewReactionsCommandOptions): Promise<number>;
|
|
10
|
+
export {};
|