@ikarmic/forge-eval 0.0.0-stage → 4.5.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/LICENSE +21 -0
- package/README.md +95 -2
- package/dist/bin/forge-eval.d.ts +3 -0
- package/dist/bin/forge-eval.d.ts.map +1 -0
- package/dist/bin/forge-eval.js +138 -0
- package/dist/bin/forge-eval.js.map +1 -0
- package/dist/graders/behavior.d.ts +57 -0
- package/dist/graders/behavior.d.ts.map +1 -0
- package/dist/graders/behavior.js +154 -0
- package/dist/graders/behavior.js.map +1 -0
- package/dist/graders/content.d.ts +59 -0
- package/dist/graders/content.d.ts.map +1 -0
- package/dist/graders/content.js +135 -0
- package/dist/graders/content.js.map +1 -0
- package/dist/graders/fs.d.ts +24 -0
- package/dist/graders/fs.d.ts.map +1 -0
- package/dist/graders/fs.js +134 -0
- package/dist/graders/fs.js.map +1 -0
- package/dist/graders/index.d.ts +15 -0
- package/dist/graders/index.d.ts.map +1 -0
- package/dist/graders/index.js +19 -0
- package/dist/graders/index.js.map +1 -0
- package/dist/graders/metrics.d.ts +59 -0
- package/dist/graders/metrics.d.ts.map +1 -0
- package/dist/graders/metrics.js +70 -0
- package/dist/graders/metrics.js.map +1 -0
- package/dist/graders/schema.d.ts +87 -0
- package/dist/graders/schema.d.ts.map +1 -0
- package/dist/graders/schema.js +157 -0
- package/dist/graders/schema.js.map +1 -0
- package/dist/graders/shell.d.ts +36 -0
- package/dist/graders/shell.d.ts.map +1 -0
- package/dist/graders/shell.js +72 -0
- package/dist/graders/shell.js.map +1 -0
- package/dist/graders/text.d.ts +28 -0
- package/dist/graders/text.d.ts.map +1 -0
- package/dist/graders/text.js +70 -0
- package/dist/graders/text.js.map +1 -0
- package/dist/graders/types.d.ts +81 -0
- package/dist/graders/types.d.ts.map +1 -0
- package/dist/graders/types.js +2 -0
- package/dist/graders/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/report.d.ts +39 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +120 -0
- package/dist/report.js.map +1 -0
- package/dist/runner.d.ts +59 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +217 -0
- package/dist/runner.js.map +1 -0
- package/dist/tasks/edit.remove-section.d.ts +15 -0
- package/dist/tasks/edit.remove-section.d.ts.map +1 -0
- package/dist/tasks/edit.remove-section.js +31 -0
- package/dist/tasks/edit.remove-section.js.map +1 -0
- package/dist/tasks/edit.rename-symbol.d.ts +16 -0
- package/dist/tasks/edit.rename-symbol.d.ts.map +1 -0
- package/dist/tasks/edit.rename-symbol.js +34 -0
- package/dist/tasks/edit.rename-symbol.js.map +1 -0
- package/dist/tasks/edit.replace-greeting.d.ts +16 -0
- package/dist/tasks/edit.replace-greeting.d.ts.map +1 -0
- package/dist/tasks/edit.replace-greeting.js +29 -0
- package/dist/tasks/edit.replace-greeting.js.map +1 -0
- package/dist/tasks/edit.write-hello.d.ts +15 -0
- package/dist/tasks/edit.write-hello.d.ts.map +1 -0
- package/dist/tasks/edit.write-hello.js +22 -0
- package/dist/tasks/edit.write-hello.js.map +1 -0
- package/dist/tasks/index.d.ts +9 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +37 -0
- package/dist/tasks/index.js.map +1 -0
- package/dist/tasks/memory.recall-relevant.d.ts +19 -0
- package/dist/tasks/memory.recall-relevant.d.ts.map +1 -0
- package/dist/tasks/memory.recall-relevant.js +36 -0
- package/dist/tasks/memory.recall-relevant.js.map +1 -0
- package/dist/tasks/multi.refactor-move.d.ts +16 -0
- package/dist/tasks/multi.refactor-move.d.ts.map +1 -0
- package/dist/tasks/multi.refactor-move.js +37 -0
- package/dist/tasks/multi.refactor-move.js.map +1 -0
- package/dist/tasks/multi.run-and-report.d.ts +16 -0
- package/dist/tasks/multi.run-and-report.d.ts.map +1 -0
- package/dist/tasks/multi.run-and-report.js +26 -0
- package/dist/tasks/multi.run-and-report.js.map +1 -0
- package/dist/tasks/multi.summarize.d.ts +16 -0
- package/dist/tasks/multi.summarize.d.ts.map +1 -0
- package/dist/tasks/multi.summarize.js +35 -0
- package/dist/tasks/multi.summarize.js.map +1 -0
- package/dist/tasks/read.list-files.d.ts +15 -0
- package/dist/tasks/read.list-files.d.ts.map +1 -0
- package/dist/tasks/read.list-files.js +28 -0
- package/dist/tasks/read.list-files.js.map +1 -0
- package/dist/tasks/read.package-version.d.ts +15 -0
- package/dist/tasks/read.package-version.d.ts.map +1 -0
- package/dist/tasks/read.package-version.js +25 -0
- package/dist/tasks/read.package-version.js.map +1 -0
- package/dist/tasks/read.search-todos.d.ts +16 -0
- package/dist/tasks/read.search-todos.d.ts.map +1 -0
- package/dist/tasks/read.search-todos.js +30 -0
- package/dist/tasks/read.search-todos.js.map +1 -0
- package/package.json +57 -3
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { loadSchemaFile } from '@ikarmic/forge-core';
|
|
2
|
+
/**
|
|
3
|
+
* Grader that parses `finalText` as JSON and validates it against the
|
|
4
|
+
* supplied schema. Returns `{ passed: true }` on a successful
|
|
5
|
+
* validation, `{ passed: false, details }` with the validation error
|
|
6
|
+
* + truncated text otherwise.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { z } from 'zod';
|
|
10
|
+
* import { jsonSchema } from '@ikarmic/forge-eval';
|
|
11
|
+
*
|
|
12
|
+
* const prSchema = z.object({
|
|
13
|
+
* title: z.string(),
|
|
14
|
+
* body: z.string().min(20),
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* const task = defineTask({
|
|
18
|
+
* id: 'summarize-pr',
|
|
19
|
+
* graders: [jsonSchema(prSchema)],
|
|
20
|
+
* ...
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Or load the same schema the agent uses via --schema:
|
|
24
|
+
* const task = defineTask({
|
|
25
|
+
* id: 'extract-pr',
|
|
26
|
+
* graders: [jsonSchema('./schemas/pr-summary.ts')],
|
|
27
|
+
* ...
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function jsonSchema(schema, options = {}) {
|
|
32
|
+
const maxDetailChars = options.maxDetailChars ?? 400;
|
|
33
|
+
if (typeof schema === 'string' && options.schemaIsPath === false) {
|
|
34
|
+
throw new Error('jsonSchema: schema was a string but schemaIsPath is false: string schemas are always loaded as a file path.');
|
|
35
|
+
}
|
|
36
|
+
// Resolved lazily (loading a path requires a dynamic import, which is
|
|
37
|
+
// async) and cached across grader invocations. When `schema` is already
|
|
38
|
+
// a Zod schema object, `resolved` is set up-front and the grader stays
|
|
39
|
+
// fully synchronous: no behavior change for existing callers.
|
|
40
|
+
let resolved = typeof schema === 'string' ? undefined : schema;
|
|
41
|
+
let loadError;
|
|
42
|
+
return (ctx) => {
|
|
43
|
+
if (resolved !== undefined) {
|
|
44
|
+
return gradeAgainstSchema(resolved, ctx, maxDetailChars);
|
|
45
|
+
}
|
|
46
|
+
if (loadError !== undefined) {
|
|
47
|
+
return { passed: false, message: 'failed to load schema file', details: loadError };
|
|
48
|
+
}
|
|
49
|
+
return loadSchemaFile(schema).then((s) => {
|
|
50
|
+
resolved = s;
|
|
51
|
+
return gradeAgainstSchema(s, ctx, maxDetailChars);
|
|
52
|
+
}, (err) => {
|
|
53
|
+
loadError = err.message;
|
|
54
|
+
return { passed: false, message: 'failed to load schema file', details: loadError };
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function gradeAgainstSchema(zodSchema, ctx, maxDetailChars) {
|
|
59
|
+
const text = ctx.result.finalText ?? '';
|
|
60
|
+
const parsed = tryParseJson(text);
|
|
61
|
+
if (parsed.parseError) {
|
|
62
|
+
return {
|
|
63
|
+
passed: false,
|
|
64
|
+
message: 'finalText is not valid JSON',
|
|
65
|
+
details: `${parsed.parseError}\n\ntext (first ${maxDetailChars} chars):\n${truncate(text, maxDetailChars)}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const result = zodSchema.safeParse(parsed.value);
|
|
69
|
+
if (result.success) {
|
|
70
|
+
return { passed: true, message: 'JSON schema matched' };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
passed: false,
|
|
74
|
+
message: 'JSON schema mismatch',
|
|
75
|
+
details: formatZodError(result.error, maxDetailChars, text),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Grader that validates the parsed JSON's *subset* of fields: useful
|
|
80
|
+
* when the agent may add extra fields you don't want to grade but
|
|
81
|
+
* the core fields must match. Behaves like {@link jsonSchema} but
|
|
82
|
+
* wraps the user schema in `passthrough()` before validating, so any
|
|
83
|
+
* extra fields are ignored rather than triggering a strict failure.
|
|
84
|
+
*
|
|
85
|
+
* Note: when `schema` is a path, it can't be inspected synchronously,
|
|
86
|
+
* so the `passthrough()` wrapping is only applied to a Zod schema
|
|
87
|
+
* object passed directly: a path-loaded schema is graded as-is.
|
|
88
|
+
*/
|
|
89
|
+
export function jsonSchemaPartial(schema, options = {}) {
|
|
90
|
+
// `passthrough()` keeps the schema's own type but ignores unknown
|
|
91
|
+
// keys. For object schemas this means extra fields don't fail;
|
|
92
|
+
// for non-object schemas it's a no-op. `passthrough` is only on
|
|
93
|
+
// ZodObject, so we duck-type-check.
|
|
94
|
+
const obj = schema;
|
|
95
|
+
const tolerant = typeof obj.passthrough === 'function'
|
|
96
|
+
? obj.passthrough()
|
|
97
|
+
: schema;
|
|
98
|
+
return jsonSchema(tolerant, options);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Try to parse `text` as JSON. Falls back to extracting the content
|
|
102
|
+
* of a single ```json ... ``` (or bare ``` ... ```) fenced block.
|
|
103
|
+
* Mirrors `validateFinalText` from `@ikarmic/forge-core`'s structured
|
|
104
|
+
* module: duplicated here so this leaf package doesn't pull in core.
|
|
105
|
+
*/
|
|
106
|
+
function tryParseJson(text) {
|
|
107
|
+
const trimmed = text.trim();
|
|
108
|
+
if (trimmed.length === 0) {
|
|
109
|
+
return { value: undefined, parseError: 'Response was empty' };
|
|
110
|
+
}
|
|
111
|
+
// Try raw text first.
|
|
112
|
+
try {
|
|
113
|
+
return { value: JSON.parse(trimmed), parseError: null };
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Try a single fenced block.
|
|
117
|
+
}
|
|
118
|
+
const fence = extractFencedJson(trimmed);
|
|
119
|
+
if (fence !== null) {
|
|
120
|
+
try {
|
|
121
|
+
return { value: JSON.parse(fence), parseError: null };
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
return { value: undefined, parseError: `Found a json fence but its contents are not valid JSON: ${err.message}` };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
value: undefined,
|
|
129
|
+
parseError: 'Response is not valid JSON (and contains no ```json fence)',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function extractFencedJson(text) {
|
|
133
|
+
const m = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
134
|
+
if (m && typeof m[1] === 'string')
|
|
135
|
+
return m[1].trim();
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Render a ZodError as a one-issue-per-line summary plus a truncated
|
|
140
|
+
* version of the offending text. Designed for the report view, not
|
|
141
|
+
* the model: humans read this.
|
|
142
|
+
*/
|
|
143
|
+
function formatZodError(err, maxDetailChars, originalText) {
|
|
144
|
+
const lines = err.issues
|
|
145
|
+
.map((issue) => {
|
|
146
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
147
|
+
return ` - ${path}: ${issue.message}`;
|
|
148
|
+
})
|
|
149
|
+
.join('\n');
|
|
150
|
+
return `Validation errors:\n${lines}\n\ntext (first ${maxDetailChars} chars):\n${truncate(originalText, maxDetailChars)}`;
|
|
151
|
+
}
|
|
152
|
+
function truncate(s, max) {
|
|
153
|
+
if (s.length <= max)
|
|
154
|
+
return s;
|
|
155
|
+
return s.slice(0, max) + '…';
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/graders/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Grader } from './types.js';
|
|
2
|
+
/** Options for {@link commandRuns}. */
|
|
3
|
+
export interface CommandRunsOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The command to run, as a string (parsed with `shell: true`) or as
|
|
6
|
+
* `[program, ...args]` for direct execution. Strings are easier for
|
|
7
|
+
* shell pipelines; arrays are safer (no shell interpolation).
|
|
8
|
+
*/
|
|
9
|
+
command: string | readonly string[];
|
|
10
|
+
/** Wall-clock cap in ms. Default 30s. */
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Optional substring or regex the command's stdout must match. The command
|
|
14
|
+
* must still exit 0: this is an *additional* check.
|
|
15
|
+
*/
|
|
16
|
+
contains?: string;
|
|
17
|
+
/** Optional regex for stdout. */
|
|
18
|
+
matches?: RegExp;
|
|
19
|
+
/** When true, check stderr instead of stdout for `contains` / `matches`. */
|
|
20
|
+
fromStderr?: boolean;
|
|
21
|
+
/** Additional env vars to set for the command. */
|
|
22
|
+
env?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Grader that runs a shell command in the task's cwd and passes when the
|
|
26
|
+
* command exits 0 (and optionally matches a stdout/stderr predicate).
|
|
27
|
+
*
|
|
28
|
+
* Use this for tasks like "run the test suite" or "execute the script and
|
|
29
|
+
* report exit code" where the side effect itself is the thing being graded.
|
|
30
|
+
*
|
|
31
|
+
* Cross-platform note: shell strings use `cmd.exe` on Windows and `sh`
|
|
32
|
+
* elsewhere. For Windows-friendly commands prefer `node -e "..."` or a
|
|
33
|
+
* `[program, ...args]` array.
|
|
34
|
+
*/
|
|
35
|
+
export declare function commandRuns(options: CommandRunsOptions): Grader;
|
|
36
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/graders/shell.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAItE,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACpC,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAkC/D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
/**
|
|
6
|
+
* Grader that runs a shell command in the task's cwd and passes when the
|
|
7
|
+
* command exits 0 (and optionally matches a stdout/stderr predicate).
|
|
8
|
+
*
|
|
9
|
+
* Use this for tasks like "run the test suite" or "execute the script and
|
|
10
|
+
* report exit code" where the side effect itself is the thing being graded.
|
|
11
|
+
*
|
|
12
|
+
* Cross-platform note: shell strings use `cmd.exe` on Windows and `sh`
|
|
13
|
+
* elsewhere. For Windows-friendly commands prefer `node -e "..."` or a
|
|
14
|
+
* `[program, ...args]` array.
|
|
15
|
+
*/
|
|
16
|
+
export function commandRuns(options) {
|
|
17
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
18
|
+
return async (ctx) => {
|
|
19
|
+
const cwd = resolve(ctx.cwd);
|
|
20
|
+
try {
|
|
21
|
+
const { stdout, stderr } = await runCommand(options.command, cwd, timeoutMs, options.env);
|
|
22
|
+
const stream = options.fromStderr ? stderr : stdout;
|
|
23
|
+
if (options.contains !== undefined && !stream.includes(options.contains)) {
|
|
24
|
+
return {
|
|
25
|
+
passed: false,
|
|
26
|
+
message: `command ran but output missing "${options.contains}"`,
|
|
27
|
+
details: truncate(stream),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (options.matches && !options.matches.test(stream)) {
|
|
31
|
+
return {
|
|
32
|
+
passed: false,
|
|
33
|
+
message: `command ran but output did not match ${options.matches}`,
|
|
34
|
+
details: truncate(stream),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
passed: true,
|
|
39
|
+
message: `command ok: ${summarize(options.command)}`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44
|
+
return {
|
|
45
|
+
passed: false,
|
|
46
|
+
message: `command failed: ${summarize(options.command)}`,
|
|
47
|
+
details: message,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function runCommand(command, cwd, timeoutMs, extraEnv) {
|
|
53
|
+
const env = { ...process.env, ...(extraEnv ?? {}) };
|
|
54
|
+
if (typeof command === 'string') {
|
|
55
|
+
// Use the platform default shell so users can write shell idioms
|
|
56
|
+
// (pipelines, redirection, etc.). shell-quoting is the caller's job.
|
|
57
|
+
return execFileAsync(command, { cwd, timeout: timeoutMs, env, shell: true, maxBuffer: 4 * 1024 * 1024 });
|
|
58
|
+
}
|
|
59
|
+
const [program, ...args] = command;
|
|
60
|
+
if (!program)
|
|
61
|
+
throw new Error('commandRuns: empty command array');
|
|
62
|
+
return execFileAsync(program, args, { cwd, timeout: timeoutMs, env, maxBuffer: 4 * 1024 * 1024 });
|
|
63
|
+
}
|
|
64
|
+
function summarize(cmd) {
|
|
65
|
+
return typeof cmd === 'string' ? cmd : cmd.join(' ');
|
|
66
|
+
}
|
|
67
|
+
function truncate(s, max = 240) {
|
|
68
|
+
if (s.length <= max)
|
|
69
|
+
return s;
|
|
70
|
+
return s.slice(0, max) + '…';
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/graders/shell.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Grader } from './types.js';
|
|
2
|
+
/** Options for {@link regex}. */
|
|
3
|
+
export interface RegexGraderOptions {
|
|
4
|
+
/** Match flags. Default: none. 'i' for case-insensitive. */
|
|
5
|
+
flags?: string;
|
|
6
|
+
/**
|
|
7
|
+
* When true (default), treat `result.finalText` as multiline so `^` / `$`
|
|
8
|
+
* match line boundaries. Useful when the model emits bulleted answers.
|
|
9
|
+
*/
|
|
10
|
+
multiline?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Grader that passes when {@link AgentRunResult.finalText} matches a regex.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* graders: [regex(/version.*?\d+\.\d+\.\d+/i)]
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function regex(pattern: RegExp, options?: RegexGraderOptions): Grader;
|
|
20
|
+
/** Grader that passes when `finalText` includes a substring (case-sensitive). */
|
|
21
|
+
export declare function contains(substring: string): Grader;
|
|
22
|
+
/**
|
|
23
|
+
* Grader that passes when `finalText.trim()` exactly equals `expected.trim()`.
|
|
24
|
+
* Use this for tasks where the answer is deterministic (e.g. the agent is
|
|
25
|
+
* supposed to read a single file and report a single value).
|
|
26
|
+
*/
|
|
27
|
+
export declare function equals(expected: string): Grader;
|
|
28
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/graders/text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE,iCAAiC;AACjC,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAc/E;AAED,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY/C"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grader that passes when {@link AgentRunResult.finalText} matches a regex.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* graders: [regex(/version.*?\d+\.\d+\.\d+/i)]
|
|
6
|
+
* ```
|
|
7
|
+
*/
|
|
8
|
+
export function regex(pattern, options = {}) {
|
|
9
|
+
const { flags = '', multiline = true } = options;
|
|
10
|
+
const merged = mergeFlags(pattern, flags, multiline);
|
|
11
|
+
return (_ctx) => {
|
|
12
|
+
const text = _ctx.result.finalText ?? '';
|
|
13
|
+
const matched = merged.test(text);
|
|
14
|
+
return matched
|
|
15
|
+
? { passed: true, message: `regex ${merged} matched` }
|
|
16
|
+
: {
|
|
17
|
+
passed: false,
|
|
18
|
+
message: `regex ${merged} did not match`,
|
|
19
|
+
details: truncate(text),
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Grader that passes when `finalText` includes a substring (case-sensitive). */
|
|
24
|
+
export function contains(substring) {
|
|
25
|
+
return (ctx) => {
|
|
26
|
+
const text = ctx.result.finalText ?? '';
|
|
27
|
+
return text.includes(substring)
|
|
28
|
+
? { passed: true, message: `contains "${substring}"` }
|
|
29
|
+
: { passed: false, message: `does not contain "${substring}"`, details: truncate(text) };
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Grader that passes when `finalText.trim()` exactly equals `expected.trim()`.
|
|
34
|
+
* Use this for tasks where the answer is deterministic (e.g. the agent is
|
|
35
|
+
* supposed to read a single file and report a single value).
|
|
36
|
+
*/
|
|
37
|
+
export function equals(expected) {
|
|
38
|
+
return (ctx) => {
|
|
39
|
+
const actual = (ctx.result.finalText ?? '').trim();
|
|
40
|
+
const want = expected.trim();
|
|
41
|
+
return actual === want
|
|
42
|
+
? { passed: true, message: `equals "${want}"` }
|
|
43
|
+
: {
|
|
44
|
+
passed: false,
|
|
45
|
+
message: `text mismatch`,
|
|
46
|
+
details: `expected: ${JSON.stringify(want)}\nactual: ${JSON.stringify(actual)}`,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Merge new flags with an existing RegExp's flags, deduplicating. Order doesn't
|
|
52
|
+
* matter for the regex engine; we just need to not pass `g/.../g` which would
|
|
53
|
+
* change `test()` to stateful behaviour.
|
|
54
|
+
*/
|
|
55
|
+
function mergeFlags(src, extra, multiline) {
|
|
56
|
+
const set = new Set(src.flags.split(''));
|
|
57
|
+
for (const c of extra)
|
|
58
|
+
set.add(c);
|
|
59
|
+
if (multiline)
|
|
60
|
+
set.add('m');
|
|
61
|
+
// `g` would make `.test()` stateful across graders: strip it.
|
|
62
|
+
set.delete('g');
|
|
63
|
+
return new RegExp(src.source, Array.from(set).join(''));
|
|
64
|
+
}
|
|
65
|
+
function truncate(s, max = 240) {
|
|
66
|
+
if (s.length <= max)
|
|
67
|
+
return s;
|
|
68
|
+
return s.slice(0, max) + '…';
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/graders/text.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { AgentRunResult } from '@ikarmic/forge-core';
|
|
2
|
+
/**
|
|
3
|
+
* One tool invocation captured during a task run. Built up from the agent's
|
|
4
|
+
* `tool_call` + `tool_result` events, correlated by id, so graders can inspect
|
|
5
|
+
* what the model actually did (not just what it said).
|
|
6
|
+
*/
|
|
7
|
+
export interface ToolInvocation {
|
|
8
|
+
/** Tool name as exposed to the model (e.g. "read_file", "edit_file"). */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Raw arguments the model passed. */
|
|
11
|
+
args: unknown;
|
|
12
|
+
/** Tool's result (after redaction), or the error message if `isError` is true. */
|
|
13
|
+
result: unknown;
|
|
14
|
+
/** True when the tool threw or returned an error. */
|
|
15
|
+
isError: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The context a grader runs in. Everything a grader might need to make a
|
|
19
|
+
* verdict: where the task ran, what the model said, what tools it called,
|
|
20
|
+
* the original task description.
|
|
21
|
+
*/
|
|
22
|
+
export interface GraderContext {
|
|
23
|
+
/** Absolute path to the task's isolated working directory. */
|
|
24
|
+
cwd: string;
|
|
25
|
+
/** The task that was just run (in case the grader wants metadata). */
|
|
26
|
+
task: TaskInfo;
|
|
27
|
+
/** The agent's run result: finalText, iterations, tokens, etc. */
|
|
28
|
+
result: AgentRunResult;
|
|
29
|
+
/** Ordered list of every tool the agent invoked, with args + results. */
|
|
30
|
+
toolInvocations: ToolInvocation[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Lightweight task descriptor surfaced to graders. Full task defs include
|
|
34
|
+
* the `setup` function which isn't relevant to grading.
|
|
35
|
+
*/
|
|
36
|
+
export interface TaskInfo {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
category: TaskCategory;
|
|
40
|
+
description: string;
|
|
41
|
+
}
|
|
42
|
+
export type TaskCategory = 'read' | 'edit' | 'multi-step';
|
|
43
|
+
/** What a grader returns. `passed` drives the score; `message` shows in the report. */
|
|
44
|
+
export interface GraderResult {
|
|
45
|
+
passed: boolean;
|
|
46
|
+
/** Short label for the report ("regex matched", "file exists", ...). */
|
|
47
|
+
message: string;
|
|
48
|
+
/** Optional longer explanation when failing: included in the markdown report. */
|
|
49
|
+
details?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A grader is just an async function. Sync return is wrapped automatically.
|
|
53
|
+
*/
|
|
54
|
+
export type Grader = (ctx: GraderContext) => Promise<GraderResult> | GraderResult;
|
|
55
|
+
/**
|
|
56
|
+
* Internal type used by `defineTask`: keeps the grader list type-safe while
|
|
57
|
+
* allowing setup/task to be optional in user code.
|
|
58
|
+
*/
|
|
59
|
+
export interface TaskDefinition {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
category: TaskCategory;
|
|
63
|
+
description: string;
|
|
64
|
+
/** The user prompt given to the agent. */
|
|
65
|
+
task: string;
|
|
66
|
+
/**
|
|
67
|
+
* Optional setup: writes fixture files into the task's cwd before the agent
|
|
68
|
+
* starts. The cwd is a fresh temp directory per run.
|
|
69
|
+
*/
|
|
70
|
+
setup?: (cwd: string) => Promise<void> | void;
|
|
71
|
+
/**
|
|
72
|
+
* Run with project memory enabled. Default off: memory would add noise to the other tasks.
|
|
73
|
+
* The user's global memory is hidden for these runs so the result does not depend on the host.
|
|
74
|
+
*/
|
|
75
|
+
memory?: boolean;
|
|
76
|
+
/** Graders run after the agent finishes. All must pass for the task to pass. */
|
|
77
|
+
graders: Grader[];
|
|
78
|
+
/** Optional tags for filtering (e.g. "windows", "needs-network"). */
|
|
79
|
+
tags?: string[];
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graders/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,IAAI,EAAE,QAAQ,CAAC;IACf,kEAAkE;IAClE,MAAM,EAAE,cAAc,CAAC;IACvB,yEAAyE;IACzE,eAAe,EAAE,cAAc,EAAE,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,CAAC;AAE1D,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAElF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9C;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/graders/types.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { regex, contains, equals, fileExists, fileMatches, fileMissing, directoryHas, commandRuns, toolWasCalled, toolNotCalled, finalTextNonEmpty, completed, defineTask, } from './graders/index.js';
|
|
2
|
+
export type { Grader, GraderContext, GraderResult, TaskCategory, TaskDefinition, TaskInfo, ToolInvocation, RegexGraderOptions, CommandRunsOptions, } from './graders/index.js';
|
|
3
|
+
export { TASKS, findTask } from './tasks/index.js';
|
|
4
|
+
export { runTask, runSuite, resolveModel } from './runner.js';
|
|
5
|
+
export type { TaskRunResult, SuiteRunResult, RunTaskOptions } from './runner.js';
|
|
6
|
+
export { formatJsonReport, formatMarkdownReport, formatTerminalSummary } from './report.js';
|
|
7
|
+
export type { JsonReport } from './report.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,SAAS,EACT,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGjF,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC5F,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Graders
|
|
2
|
+
export { regex, contains, equals, fileExists, fileMatches, fileMissing, directoryHas, commandRuns, toolWasCalled, toolNotCalled, finalTextNonEmpty, completed, defineTask, } from './graders/index.js';
|
|
3
|
+
// Tasks
|
|
4
|
+
export { TASKS, findTask } from './tasks/index.js';
|
|
5
|
+
// Runner
|
|
6
|
+
export { runTask, runSuite, resolveModel } from './runner.js';
|
|
7
|
+
// Report
|
|
8
|
+
export { formatJsonReport, formatMarkdownReport, formatTerminalSummary } from './report.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,OAAO,EACL,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,SAAS,EACT,UAAU,GACX,MAAM,oBAAoB,CAAC;AAa5B,QAAQ;AACR,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEnD,SAAS;AACT,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG9D,SAAS;AACT,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SuiteRunResult, TaskRunResult } from './runner.js';
|
|
2
|
+
/** Render a human-friendly markdown table for a suite run. */
|
|
3
|
+
export declare function formatMarkdownReport(suite: SuiteRunResult): string;
|
|
4
|
+
/** Render a compact single-line summary for the terminal. */
|
|
5
|
+
export declare function formatTerminalSummary(suite: SuiteRunResult): string;
|
|
6
|
+
/** JSON shape: stable, machine-readable, includes everything graders saw. */
|
|
7
|
+
export interface JsonReport {
|
|
8
|
+
model: {
|
|
9
|
+
provider: string;
|
|
10
|
+
model: string;
|
|
11
|
+
};
|
|
12
|
+
startedAt: string;
|
|
13
|
+
finishedAt: string;
|
|
14
|
+
summary: SuiteRunResult['summary'];
|
|
15
|
+
tasks: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
category: string;
|
|
19
|
+
passed: boolean;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
iterations: number;
|
|
22
|
+
reason: string;
|
|
23
|
+
graderResults: Array<{
|
|
24
|
+
passed: boolean;
|
|
25
|
+
message: string;
|
|
26
|
+
details?: string;
|
|
27
|
+
}>;
|
|
28
|
+
toolInvocations: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
args: unknown;
|
|
31
|
+
isError: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
finalText: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export declare function formatJsonReport(suite: SuiteRunResult): JsonReport;
|
|
38
|
+
export type { TaskRunResult };
|
|
39
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAOjE,8DAA8D;AAC9D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAoElE;AAED,6DAA6D;AAC7D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAenE;AAED,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,OAAO,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,OAAO,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC7E,eAAe,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,OAAO,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAC1E,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,UAAU,CAwBlE;AAaD,YAAY,EAAE,aAAa,EAAE,CAAC"}
|