@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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -2
  3. package/dist/bin/forge-eval.d.ts +3 -0
  4. package/dist/bin/forge-eval.d.ts.map +1 -0
  5. package/dist/bin/forge-eval.js +138 -0
  6. package/dist/bin/forge-eval.js.map +1 -0
  7. package/dist/graders/behavior.d.ts +57 -0
  8. package/dist/graders/behavior.d.ts.map +1 -0
  9. package/dist/graders/behavior.js +154 -0
  10. package/dist/graders/behavior.js.map +1 -0
  11. package/dist/graders/content.d.ts +59 -0
  12. package/dist/graders/content.d.ts.map +1 -0
  13. package/dist/graders/content.js +135 -0
  14. package/dist/graders/content.js.map +1 -0
  15. package/dist/graders/fs.d.ts +24 -0
  16. package/dist/graders/fs.d.ts.map +1 -0
  17. package/dist/graders/fs.js +134 -0
  18. package/dist/graders/fs.js.map +1 -0
  19. package/dist/graders/index.d.ts +15 -0
  20. package/dist/graders/index.d.ts.map +1 -0
  21. package/dist/graders/index.js +19 -0
  22. package/dist/graders/index.js.map +1 -0
  23. package/dist/graders/metrics.d.ts +59 -0
  24. package/dist/graders/metrics.d.ts.map +1 -0
  25. package/dist/graders/metrics.js +70 -0
  26. package/dist/graders/metrics.js.map +1 -0
  27. package/dist/graders/schema.d.ts +87 -0
  28. package/dist/graders/schema.d.ts.map +1 -0
  29. package/dist/graders/schema.js +157 -0
  30. package/dist/graders/schema.js.map +1 -0
  31. package/dist/graders/shell.d.ts +36 -0
  32. package/dist/graders/shell.d.ts.map +1 -0
  33. package/dist/graders/shell.js +72 -0
  34. package/dist/graders/shell.js.map +1 -0
  35. package/dist/graders/text.d.ts +28 -0
  36. package/dist/graders/text.d.ts.map +1 -0
  37. package/dist/graders/text.js +70 -0
  38. package/dist/graders/text.js.map +1 -0
  39. package/dist/graders/types.d.ts +81 -0
  40. package/dist/graders/types.d.ts.map +1 -0
  41. package/dist/graders/types.js +2 -0
  42. package/dist/graders/types.js.map +1 -0
  43. package/dist/index.d.ts +8 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +9 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/report.d.ts +39 -0
  48. package/dist/report.d.ts.map +1 -0
  49. package/dist/report.js +120 -0
  50. package/dist/report.js.map +1 -0
  51. package/dist/runner.d.ts +59 -0
  52. package/dist/runner.d.ts.map +1 -0
  53. package/dist/runner.js +217 -0
  54. package/dist/runner.js.map +1 -0
  55. package/dist/tasks/edit.remove-section.d.ts +15 -0
  56. package/dist/tasks/edit.remove-section.d.ts.map +1 -0
  57. package/dist/tasks/edit.remove-section.js +31 -0
  58. package/dist/tasks/edit.remove-section.js.map +1 -0
  59. package/dist/tasks/edit.rename-symbol.d.ts +16 -0
  60. package/dist/tasks/edit.rename-symbol.d.ts.map +1 -0
  61. package/dist/tasks/edit.rename-symbol.js +34 -0
  62. package/dist/tasks/edit.rename-symbol.js.map +1 -0
  63. package/dist/tasks/edit.replace-greeting.d.ts +16 -0
  64. package/dist/tasks/edit.replace-greeting.d.ts.map +1 -0
  65. package/dist/tasks/edit.replace-greeting.js +29 -0
  66. package/dist/tasks/edit.replace-greeting.js.map +1 -0
  67. package/dist/tasks/edit.write-hello.d.ts +15 -0
  68. package/dist/tasks/edit.write-hello.d.ts.map +1 -0
  69. package/dist/tasks/edit.write-hello.js +22 -0
  70. package/dist/tasks/edit.write-hello.js.map +1 -0
  71. package/dist/tasks/index.d.ts +9 -0
  72. package/dist/tasks/index.d.ts.map +1 -0
  73. package/dist/tasks/index.js +37 -0
  74. package/dist/tasks/index.js.map +1 -0
  75. package/dist/tasks/memory.recall-relevant.d.ts +19 -0
  76. package/dist/tasks/memory.recall-relevant.d.ts.map +1 -0
  77. package/dist/tasks/memory.recall-relevant.js +36 -0
  78. package/dist/tasks/memory.recall-relevant.js.map +1 -0
  79. package/dist/tasks/multi.refactor-move.d.ts +16 -0
  80. package/dist/tasks/multi.refactor-move.d.ts.map +1 -0
  81. package/dist/tasks/multi.refactor-move.js +37 -0
  82. package/dist/tasks/multi.refactor-move.js.map +1 -0
  83. package/dist/tasks/multi.run-and-report.d.ts +16 -0
  84. package/dist/tasks/multi.run-and-report.d.ts.map +1 -0
  85. package/dist/tasks/multi.run-and-report.js +26 -0
  86. package/dist/tasks/multi.run-and-report.js.map +1 -0
  87. package/dist/tasks/multi.summarize.d.ts +16 -0
  88. package/dist/tasks/multi.summarize.d.ts.map +1 -0
  89. package/dist/tasks/multi.summarize.js +35 -0
  90. package/dist/tasks/multi.summarize.js.map +1 -0
  91. package/dist/tasks/read.list-files.d.ts +15 -0
  92. package/dist/tasks/read.list-files.d.ts.map +1 -0
  93. package/dist/tasks/read.list-files.js +28 -0
  94. package/dist/tasks/read.list-files.js.map +1 -0
  95. package/dist/tasks/read.package-version.d.ts +15 -0
  96. package/dist/tasks/read.package-version.d.ts.map +1 -0
  97. package/dist/tasks/read.package-version.js +25 -0
  98. package/dist/tasks/read.package-version.js.map +1 -0
  99. package/dist/tasks/read.search-todos.d.ts +16 -0
  100. package/dist/tasks/read.search-todos.d.ts.map +1 -0
  101. package/dist/tasks/read.search-todos.js +30 -0
  102. package/dist/tasks/read.search-todos.js.map +1 -0
  103. package/package.json +57 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 IKarmic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,96 @@
1
- # Temporary Holding Version
1
+ # @ikarmic/forge-eval
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Eval harness for [IKarmic AI Forge](https://github.com/ikarmicai/ikarmic-forge). Runs agent
4
+ tasks against isolated workspaces and grades the results with deterministic checks:
5
+ no API keys needed to *grade*, only to *run*.
6
+
7
+ ## What's in the box
8
+
9
+ - **5 free graders**: `regex`, `contains`, `equals`, `file-exists`, `command-runs`.
10
+ Deterministic, no LLM-as-judge, no API spend.
11
+ - **10 curated tasks** spanning three categories:
12
+ - **read**: list / read / search style tasks
13
+ - **edit**: write / modify / delete style tasks
14
+ - **multi-step**: tasks that need search + edit, run + report, or read + summarize
15
+ - **Per-task isolated cwd**: every task runs in its own temp directory so tasks can't
16
+ affect each other or your real project.
17
+ - **CLI**: `forge-eval run [--model X] [--task Y] [--json]` plus
18
+ `forge-eval list` for browsing the task catalog.
19
+ - **Markdown + JSON reports** so you can diff runs over time.
20
+
21
+ ## Install / build
22
+
23
+ This package lives in the Forge monorepo. From the repo root:
24
+
25
+ ```bash
26
+ pnpm -r build
27
+ pnpm --filter @ikarmic/forge-eval test
28
+ ```
29
+
30
+ ## CLI usage
31
+
32
+ ```bash
33
+ # Run the full suite against whatever model FORGE_DEFAULT_* resolves to.
34
+ pnpm forge-eval run
35
+
36
+ # Run a single task by id.
37
+ pnpm forge-eval run --task read-package-version
38
+
39
+ # Pin a specific model.
40
+ pnpm forge-eval run --model anthropic/claude-3-5-haiku-latest
41
+
42
+ # JSON output (one event per line) for piping into scripts.
43
+ pnpm forge-eval run --json | tee run.jsonl
44
+
45
+ # Browse the task catalog.
46
+ pnpm forge-eval list
47
+ ```
48
+
49
+ The CLI exits 0 if all tasks passed, 1 if any failed, 2 if the run couldn't start
50
+ (missing model, bad task id, etc.).
51
+
52
+ ## Task authoring
53
+
54
+ A task is just an object: setup writes fixture files into a temp dir, the agent runs
55
+ against that dir, then graders inspect the result + filesystem.
56
+
57
+ ```ts
58
+ import { defineTask, regex, fileExists, commandRuns } from '@ikarmic/forge-eval';
59
+
60
+ export default defineTask({
61
+ id: 'my-task',
62
+ name: 'My task',
63
+ category: 'edit',
64
+ description: 'Does X then Y',
65
+ task: 'Write "hi" to greeting.txt',
66
+ setup: async (cwd) => {
67
+ await fs.writeFile(path.join(cwd, '.gitkeep'), '');
68
+ },
69
+ graders: [
70
+ fileExists('greeting.txt'),
71
+ commandRuns({ command: 'node -e "require(\'fs\').readFileSync(\'greeting.txt\',\'utf8\')"', contains: 'hi' }),
72
+ ],
73
+ });
74
+ ```
75
+
76
+ Drop the task file in `src/tasks/your-task.ts`, register it in `src/tasks/index.ts`, done.
77
+
78
+ ## Grader reference
79
+
80
+ | Grader | What it checks |
81
+ |---|---|
82
+ | `regex(pattern, opts?)` | `result.finalText` matches a regex (and optionally case-insensitive / multiline) |
83
+ | `contains(substring)` | `result.finalText` contains a substring (case-sensitive) |
84
+ | `equals(text)` | `result.finalText.trim()` equals `text.trim()` |
85
+ | `fileExists(relPath)` | A file exists at `cwd/relPath` |
86
+ | `commandRuns(opts)` | Runs a shell command via `child_process.exec` in `cwd`; checks exit code 0 + optional stdout predicate |
87
+
88
+ All graders receive `{ cwd, task, result, toolInvocations }` so you can also write
89
+ custom graders against `result.messages` or the recorded tool calls.
90
+
91
+ ## Limitations
92
+
93
+ - Free graders only: no LLM-as-judge in v1 (that needs API keys per task).
94
+ - 10 tasks in v1: the catalog is intentionally small. More tasks are welcome
95
+ via PR.
96
+ - Tasks assume a writable temp dir and a working `node` on PATH (for `command-runs`).
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=forge-eval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forge-eval.d.ts","sourceRoot":"","sources":["../../src/bin/forge-eval.ts"],"names":[],"mappings":""}
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ import process from 'node:process';
3
+ import { findTask, TASKS } from '../tasks/index.js';
4
+ import { formatJsonReport, formatMarkdownReport, formatTerminalSummary } from '../report.js';
5
+ import { resolveModel, runSuite } from '../runner.js';
6
+ function parseArgs(argv) {
7
+ const args = argv.slice(2);
8
+ const out = {
9
+ command: 'run',
10
+ json: false,
11
+ markdown: false,
12
+ help: false,
13
+ };
14
+ const takeNext = (flag) => {
15
+ const v = args.shift();
16
+ if (v === undefined || v.startsWith('--')) {
17
+ throw new Error(`${flag} requires a value`);
18
+ }
19
+ return v;
20
+ };
21
+ // Subcommand.
22
+ if (args[0] === 'list') {
23
+ args.shift();
24
+ out.command = 'list';
25
+ }
26
+ else if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
27
+ args.shift();
28
+ out.command = 'help';
29
+ out.help = true;
30
+ }
31
+ else if (args[0] === 'run') {
32
+ args.shift();
33
+ out.command = 'run';
34
+ }
35
+ while (args.length > 0) {
36
+ const a = args.shift();
37
+ if (a === '--model') {
38
+ out.model = takeNext('--model');
39
+ }
40
+ else if (a === '--task') {
41
+ out.task = takeNext('--task');
42
+ }
43
+ else if (a === '--json') {
44
+ out.json = true;
45
+ }
46
+ else if (a === '--markdown') {
47
+ out.markdown = true;
48
+ }
49
+ else if (a === '--help' || a === '-h') {
50
+ out.help = true;
51
+ }
52
+ else {
53
+ throw new Error(`Unknown argument: ${a}`);
54
+ }
55
+ }
56
+ return out;
57
+ }
58
+ function printHelp() {
59
+ process.stdout.write(`forge-eval: run Forge agent tasks and grade the results.\n\n` +
60
+ `Usage:\n` +
61
+ ` forge-eval run [--model <provider>/<model>] [--task <id>] [--json | --markdown]\n` +
62
+ ` forge-eval list\n` +
63
+ ` forge-eval help\n\n` +
64
+ `Flags:\n` +
65
+ ` --model <p>/<m> Override the model. Defaults to FORGE_DEFAULT_MODEL / provider env vars.\n` +
66
+ ` --task <id> Run only the named task (see \`forge-eval list\`).\n` +
67
+ ` --json Emit a single JSON report on stdout.\n` +
68
+ ` --markdown Emit a markdown report on stdout (in addition to terminal summary).\n\n` +
69
+ `Exit codes: 0 = all passed, 1 = any failed, 2 = run couldn't start.\n`);
70
+ }
71
+ function printList() {
72
+ process.stdout.write(`# Tasks (${TASKS.length})\n\n`);
73
+ for (const t of TASKS) {
74
+ process.stdout.write(`## ${t.id}: ${t.name} [${t.category}]\n${t.description}\n\n`);
75
+ }
76
+ }
77
+ async function main() {
78
+ const args = parseArgs(process.argv);
79
+ if (args.command === 'help' || args.help) {
80
+ printHelp();
81
+ return;
82
+ }
83
+ if (args.command === 'list') {
84
+ printList();
85
+ return;
86
+ }
87
+ // command === 'run' from here on
88
+ // Task lookup first: it doesn't need a model, so we can fail fast on a
89
+ // bogus --task id before bothering to resolve credentials.
90
+ let tasks;
91
+ if (args.task) {
92
+ const t = findTask(args.task);
93
+ if (!t) {
94
+ process.stderr.write(`forge-eval: unknown task id "${args.task}". Run \`forge-eval list\` to see options.\n`);
95
+ process.exit(2);
96
+ return;
97
+ }
98
+ tasks = [t];
99
+ }
100
+ else {
101
+ tasks = TASKS;
102
+ }
103
+ let model;
104
+ try {
105
+ model = resolveModel(args.model);
106
+ }
107
+ catch (err) {
108
+ process.stderr.write(`forge-eval: ${err.message}\n`);
109
+ process.exit(2);
110
+ return;
111
+ }
112
+ if (!args.json) {
113
+ process.stderr.write(`forge-eval: running ${tasks.length} task(s) against ${model.provider}/${model.model}...\n`);
114
+ }
115
+ try {
116
+ const suite = await runSuite(tasks, model);
117
+ if (args.json) {
118
+ const report = formatJsonReport(suite);
119
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
120
+ }
121
+ else {
122
+ process.stdout.write('\n' + formatTerminalSummary(suite) + '\n');
123
+ if (args.markdown) {
124
+ process.stdout.write('\n' + formatMarkdownReport(suite) + '\n');
125
+ }
126
+ }
127
+ process.exit(suite.summary.failed === 0 ? 0 : 1);
128
+ }
129
+ catch (err) {
130
+ process.stderr.write(`forge-eval: ${err.message}\n`);
131
+ process.exit(2);
132
+ }
133
+ }
134
+ main().catch((err) => {
135
+ process.stderr.write(`forge-eval: ${err.message}\n`);
136
+ process.exit(1);
137
+ });
138
+ //# sourceMappingURL=forge-eval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forge-eval.js","sourceRoot":"","sources":["../../src/bin/forge-eval.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { Grader } from './types.js';
2
+ /**
3
+ * Grader that passes when the agent called a given tool at least `min` times
4
+ * during the run. Use this to assert the model took the right path (vs. just
5
+ * guessing in its final text).
6
+ */
7
+ export declare function toolWasCalled(name: string, min?: number): Grader;
8
+ /**
9
+ * Grader that passes when the agent never called a given tool. Useful for
10
+ * "the agent should have done this without writing a file" tasks.
11
+ */
12
+ export declare function toolNotCalled(name: string): Grader;
13
+ /**
14
+ * Grader that passes when `finalText` is non-empty. Catches "model went
15
+ * silent" runs that some graders above would also catch indirectly.
16
+ */
17
+ export declare function finalTextNonEmpty(): Grader;
18
+ /**
19
+ * Grader that passes when the agent finished with `reason === 'completed'`.
20
+ * Catches runs that aborted early (max_iterations, error, etc.): even if
21
+ * the other graders happened to pass.
22
+ */
23
+ export declare function completed(): Grader;
24
+ /**
25
+ * Options for {@link toolCount}.
26
+ *
27
+ * At least one of `min` / `max` / `exact` must be provided. If
28
+ * multiple are passed they're AND-ed together: `exact` is the
29
+ * strongest constraint.
30
+ */
31
+ export interface ToolCountOptions {
32
+ min?: number;
33
+ max?: number;
34
+ exact?: number;
35
+ }
36
+ /**
37
+ * Grader that passes when the call count for `name` falls within
38
+ * `min` / `max` (inclusive). Use this to assert a budget
39
+ * ("didn't spam the read tool") or a minimum ("read at least once
40
+ * before guessing").
41
+ */
42
+ export declare function toolCount(name: string, options?: ToolCountOptions): Grader;
43
+ /**
44
+ * Grader that passes when at least one call to `name` had
45
+ * `predicate(args)` return true. Use this to assert the agent
46
+ * invoked a tool with the right arguments (not just the right tool
47
+ * name): e.g. "the agent called `edit_file` with a non-empty diff"
48
+ * vs. "the agent called `edit_file` 5 times on autopilot".
49
+ */
50
+ export declare function toolCalledWith(name: string, predicate: (args: unknown) => boolean): Grader;
51
+ /**
52
+ * Grader that passes when no tool call during the run returned an
53
+ * error. Useful as a baseline guard: any task that says "agent did
54
+ * something" should also assert it didn't fall over partway.
55
+ */
56
+ export declare function noToolErrors(): Grader;
57
+ //# sourceMappingURL=behavior.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behavior.d.ts","sourceRoot":"","sources":["../../src/graders/behavior.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAI,GAAG,MAAM,CAO3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAO1C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAMlC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAEhB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CA0B9E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,GACpC,MAAM,CAsBR;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAcrC"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Grader that passes when the agent called a given tool at least `min` times
3
+ * during the run. Use this to assert the model took the right path (vs. just
4
+ * guessing in its final text).
5
+ */
6
+ export function toolWasCalled(name, min = 1) {
7
+ return (ctx) => {
8
+ const calls = ctx.toolInvocations.filter((t) => t.name === name).length;
9
+ return calls >= min
10
+ ? { passed: true, message: `tool ${name} called ${calls}x` }
11
+ : { passed: false, message: `tool ${name} called ${calls}x (need ${min})` };
12
+ };
13
+ }
14
+ /**
15
+ * Grader that passes when the agent never called a given tool. Useful for
16
+ * "the agent should have done this without writing a file" tasks.
17
+ */
18
+ export function toolNotCalled(name) {
19
+ return (ctx) => {
20
+ const calls = ctx.toolInvocations.filter((t) => t.name === name).length;
21
+ return calls === 0
22
+ ? { passed: true, message: `tool ${name} was not called` }
23
+ : { passed: false, message: `tool ${name} was called ${calls}x but should not have been` };
24
+ };
25
+ }
26
+ /**
27
+ * Grader that passes when `finalText` is non-empty. Catches "model went
28
+ * silent" runs that some graders above would also catch indirectly.
29
+ */
30
+ export function finalTextNonEmpty() {
31
+ return (ctx) => {
32
+ const text = ctx.result.finalText?.trim() ?? '';
33
+ return text.length > 0
34
+ ? { passed: true, message: 'finalText is non-empty' }
35
+ : { passed: false, message: 'finalText is empty' };
36
+ };
37
+ }
38
+ /**
39
+ * Grader that passes when the agent finished with `reason === 'completed'`.
40
+ * Catches runs that aborted early (max_iterations, error, etc.): even if
41
+ * the other graders happened to pass.
42
+ */
43
+ export function completed() {
44
+ return (ctx) => {
45
+ return ctx.result.reason === 'completed'
46
+ ? { passed: true, message: `reason: completed` }
47
+ : { passed: false, message: `reason: ${ctx.result.reason}` };
48
+ };
49
+ }
50
+ /**
51
+ * Grader that passes when the call count for `name` falls within
52
+ * `min` / `max` (inclusive). Use this to assert a budget
53
+ * ("didn't spam the read tool") or a minimum ("read at least once
54
+ * before guessing").
55
+ */
56
+ export function toolCount(name, options = {}) {
57
+ if (options.min === undefined && options.max === undefined && options.exact === undefined) {
58
+ throw new Error('toolCount requires at least one of min/max/exact');
59
+ }
60
+ const { min = 0, max = Number.POSITIVE_INFINITY, exact } = options;
61
+ const effectiveMin = exact ?? min;
62
+ const effectiveMax = exact ?? max;
63
+ return (ctx) => {
64
+ const calls = ctx.toolInvocations.filter((t) => t.name === name).length;
65
+ if (calls < effectiveMin) {
66
+ return {
67
+ passed: false,
68
+ message: `tool ${name} called ${calls}x (need at least ${effectiveMin})`,
69
+ };
70
+ }
71
+ if (calls > effectiveMax) {
72
+ return {
73
+ passed: false,
74
+ message: `tool ${name} called ${calls}x (at most ${effectiveMax})`,
75
+ };
76
+ }
77
+ return {
78
+ passed: true,
79
+ message: `tool ${name} called ${calls}x (in [${effectiveMin}, ${effectiveMax}])`,
80
+ };
81
+ };
82
+ }
83
+ /**
84
+ * Grader that passes when at least one call to `name` had
85
+ * `predicate(args)` return true. Use this to assert the agent
86
+ * invoked a tool with the right arguments (not just the right tool
87
+ * name): e.g. "the agent called `edit_file` with a non-empty diff"
88
+ * vs. "the agent called `edit_file` 5 times on autopilot".
89
+ */
90
+ export function toolCalledWith(name, predicate) {
91
+ return (ctx) => {
92
+ const calls = ctx.toolInvocations.filter((t) => t.name === name);
93
+ if (calls.length === 0) {
94
+ return { passed: false, message: `tool ${name} was never called` };
95
+ }
96
+ const matching = calls.filter((t) => predicate(t.args));
97
+ if (matching.length > 0) {
98
+ return {
99
+ passed: true,
100
+ message: `tool ${name} called with matching args ${matching.length}x of ${calls.length}`,
101
+ };
102
+ }
103
+ return {
104
+ passed: false,
105
+ message: `tool ${name} called ${calls.length}x but no call's args matched predicate`,
106
+ details: `observed arg samples: ${calls
107
+ .slice(0, 3)
108
+ .map((c) => summarizeArgs(c.args))
109
+ .join(' | ')}`,
110
+ };
111
+ };
112
+ }
113
+ /**
114
+ * Grader that passes when no tool call during the run returned an
115
+ * error. Useful as a baseline guard: any task that says "agent did
116
+ * something" should also assert it didn't fall over partway.
117
+ */
118
+ export function noToolErrors() {
119
+ return (ctx) => {
120
+ const errs = ctx.toolInvocations.filter((t) => t.isError);
121
+ if (errs.length === 0) {
122
+ return { passed: true, message: 'no tool errors' };
123
+ }
124
+ return {
125
+ passed: false,
126
+ message: `${errs.length} tool call(s) errored`,
127
+ details: errs
128
+ .map((e) => ` - ${e.name}: ${shortError(e.result)}`)
129
+ .join('\n'),
130
+ };
131
+ };
132
+ }
133
+ function shortError(result) {
134
+ if (result instanceof Error)
135
+ return result.message;
136
+ if (typeof result === 'string')
137
+ return result;
138
+ try {
139
+ return JSON.stringify(result).slice(0, 200);
140
+ }
141
+ catch {
142
+ return '<unserializable>';
143
+ }
144
+ }
145
+ function summarizeArgs(args) {
146
+ try {
147
+ const s = JSON.stringify(args);
148
+ return s.length > 80 ? s.slice(0, 77) + '…' : s;
149
+ }
150
+ catch {
151
+ return '<unserializable>';
152
+ }
153
+ }
154
+ //# sourceMappingURL=behavior.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behavior.js","sourceRoot":"","sources":["../../src/graders/behavior.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Structured content extraction graders. v2.13.
3
+ *
4
+ * Helpers for tasks where the agent's final text is expected to
5
+ * contain extractable structured content (a JSON object, a fenced
6
+ * code block in a specific language). These complement the
7
+ * full-schema graders in `./schema.ts`: where `jsonSchema` requires a
8
+ * Zod schema, `jsonContains` accepts a plain predicate so you don't
9
+ * need to write a schema for "the response is roughly shaped like X".
10
+ */
11
+ import type { Grader } from './types.js';
12
+ /** Options for {@link languageBlock}. */
13
+ export interface LanguageBlockOptions {
14
+ /**
15
+ * Truncate the extracted block content in failure details to this
16
+ * many characters. Default 320.
17
+ */
18
+ maxDetailChars?: number;
19
+ /**
20
+ * When true (default), the grader fails when the fenced block is
21
+ * missing. When false, a missing block is acceptable and yields a
22
+ * pass.
23
+ */
24
+ requireBlock?: boolean;
25
+ }
26
+ /**
27
+ * Grader that extracts the first <code>```&lt;lang&gt; ... ```</code>
28
+ * fenced block from `finalText` and passes when `predicate(block)`
29
+ * returns true. Language matching is case-insensitive.
30
+ *
31
+ * Use this for tasks like "agent produced a runnable Python snippet
32
+ * with a `solve()` function" without parsing the whole response
33
+ * manually.
34
+ *
35
+ * ```ts
36
+ * graders: [
37
+ * languageBlock('ts', (code) => /export function/.test(code)),
38
+ * ]
39
+ * ```
40
+ */
41
+ export declare function languageBlock(lang: string, predicate: (code: string) => boolean, options?: LanguageBlockOptions): Grader;
42
+ /**
43
+ * Predicate-driven JSON shape check. Parses `finalText` as JSON: raw
44
+ * text first, then a single fenced block: then calls
45
+ * `predicate(value)`. Passes when the predicate accepts.
46
+ *
47
+ * Use this instead of {@link jsonSchema} when the field set is loose,
48
+ * you want to assert a single property, or you don't want a Zod
49
+ * dependency in this task's grading.
50
+ *
51
+ * ```ts
52
+ * jsonContains((v) => {
53
+ * return v && typeof v === 'object' &&
54
+ * 'name' in v && 'price' in v && (v as any).price > 0;
55
+ * })
56
+ * ```
57
+ */
58
+ export declare function jsonContains(predicate: (value: unknown) => boolean): Grader;
59
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/graders/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,EACpC,OAAO,GAAE,oBAAyB,GACjC,MAAM,CAuBR;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,MAAM,CAmB3E"}