@ikarmic/forge-eval 0.0.0-stage → 4.4.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 (99) 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 +76 -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 +207 -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 +34 -0
  74. package/dist/tasks/index.js.map +1 -0
  75. package/dist/tasks/multi.refactor-move.d.ts +16 -0
  76. package/dist/tasks/multi.refactor-move.d.ts.map +1 -0
  77. package/dist/tasks/multi.refactor-move.js +37 -0
  78. package/dist/tasks/multi.refactor-move.js.map +1 -0
  79. package/dist/tasks/multi.run-and-report.d.ts +16 -0
  80. package/dist/tasks/multi.run-and-report.d.ts.map +1 -0
  81. package/dist/tasks/multi.run-and-report.js +26 -0
  82. package/dist/tasks/multi.run-and-report.js.map +1 -0
  83. package/dist/tasks/multi.summarize.d.ts +16 -0
  84. package/dist/tasks/multi.summarize.d.ts.map +1 -0
  85. package/dist/tasks/multi.summarize.js +35 -0
  86. package/dist/tasks/multi.summarize.js.map +1 -0
  87. package/dist/tasks/read.list-files.d.ts +15 -0
  88. package/dist/tasks/read.list-files.d.ts.map +1 -0
  89. package/dist/tasks/read.list-files.js +28 -0
  90. package/dist/tasks/read.list-files.js.map +1 -0
  91. package/dist/tasks/read.package-version.d.ts +15 -0
  92. package/dist/tasks/read.package-version.d.ts.map +1 -0
  93. package/dist/tasks/read.package-version.js +25 -0
  94. package/dist/tasks/read.package-version.js.map +1 -0
  95. package/dist/tasks/read.search-todos.d.ts +16 -0
  96. package/dist/tasks/read.search-todos.d.ts.map +1 -0
  97. package/dist/tasks/read.search-todos.js +30 -0
  98. package/dist/tasks/read.search-todos.js.map +1 -0
  99. package/package.json +57 -3
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Grader that extracts the first <code>```&lt;lang&gt; ... ```</code>
3
+ * fenced block from `finalText` and passes when `predicate(block)`
4
+ * returns true. Language matching is case-insensitive.
5
+ *
6
+ * Use this for tasks like "agent produced a runnable Python snippet
7
+ * with a `solve()` function" without parsing the whole response
8
+ * manually.
9
+ *
10
+ * ```ts
11
+ * graders: [
12
+ * languageBlock('ts', (code) => /export function/.test(code)),
13
+ * ]
14
+ * ```
15
+ */
16
+ export function languageBlock(lang, predicate, options = {}) {
17
+ const maxDetailChars = options.maxDetailChars ?? 320;
18
+ const requireBlock = options.requireBlock ?? true;
19
+ return (ctx) => {
20
+ const text = ctx.result.finalText ?? '';
21
+ const block = extractFencedBlock(text, lang);
22
+ if (block === null) {
23
+ return requireBlock
24
+ ? {
25
+ passed: false,
26
+ message: `no \`\`\`${lang} ... \`\`\` block found in finalText`,
27
+ details: truncate(text, maxDetailChars),
28
+ }
29
+ : { passed: true, message: `no \`\`\`${lang} block (not required)` };
30
+ }
31
+ return predicate(block)
32
+ ? { passed: true, message: `\`\`\`${lang} block matched predicate` }
33
+ : {
34
+ passed: false,
35
+ message: `\`\`\`${lang} block did not match predicate`,
36
+ details: truncate(block, maxDetailChars),
37
+ };
38
+ };
39
+ }
40
+ /**
41
+ * Predicate-driven JSON shape check. Parses `finalText` as JSON: raw
42
+ * text first, then a single fenced block: then calls
43
+ * `predicate(value)`. Passes when the predicate accepts.
44
+ *
45
+ * Use this instead of {@link jsonSchema} when the field set is loose,
46
+ * you want to assert a single property, or you don't want a Zod
47
+ * dependency in this task's grading.
48
+ *
49
+ * ```ts
50
+ * jsonContains((v) => {
51
+ * return v && typeof v === 'object' &&
52
+ * 'name' in v && 'price' in v && (v as any).price > 0;
53
+ * })
54
+ * ```
55
+ */
56
+ export function jsonContains(predicate) {
57
+ return (ctx) => {
58
+ const text = ctx.result.finalText ?? '';
59
+ const parsed = tryParseJsonLoose(text);
60
+ if (parsed.parseError) {
61
+ return {
62
+ passed: false,
63
+ message: 'finalText is not valid JSON',
64
+ details: `${parsed.parseError}\n\ntext: ${truncate(text, 240)}`,
65
+ };
66
+ }
67
+ return predicate(parsed.value)
68
+ ? { passed: true, message: 'JSON matched predicate' }
69
+ : {
70
+ passed: false,
71
+ message: 'JSON did not match predicate',
72
+ details: `value: ${truncate(JSON.stringify(parsed.value), 240)}`,
73
+ };
74
+ };
75
+ }
76
+ // Helpers
77
+ /**
78
+ * Extract the first fenced code block matching the language tag.
79
+ * Case-insensitive. The same regex is used for both the typed
80
+ * (`` ```ts ```) and generic (`` ``` ```) cases: passing an empty
81
+ * `lang` matches any fenced block, which `jsonContains` uses as a
82
+ * fallback when the raw text isn't valid JSON.
83
+ */
84
+ function extractFencedBlock(text, lang) {
85
+ if (lang === '') {
86
+ const fallback = text.match(/```[^\n]*\n?([\s\S]*?)```/);
87
+ if (fallback && typeof fallback[1] === 'string')
88
+ return fallback[1].trim();
89
+ return null;
90
+ }
91
+ const pattern = new RegExp('```' + escapeRegex(lang.toLowerCase()) + '[\\s\\S]*?\\n([\\s\\S]*?)```', 'i');
92
+ const m = text.match(pattern);
93
+ if (m && typeof m[1] === 'string')
94
+ return m[1].trim();
95
+ return null;
96
+ }
97
+ /**
98
+ * Try to parse `text` as JSON. Falls back to extracting a fenced
99
+ * block first. Mirrors `validateFinalText` from `forge-core`'s
100
+ * structured module so the two stay in sync structurally (without
101
+ * forcing this leaf package to depend on core).
102
+ */
103
+ function tryParseJsonLoose(text) {
104
+ const trimmed = text.trim();
105
+ if (trimmed.length === 0)
106
+ return { value: undefined, parseError: 'response was empty' };
107
+ try {
108
+ return { value: JSON.parse(trimmed), parseError: null };
109
+ }
110
+ catch {
111
+ /* try fenced block */
112
+ }
113
+ const fence = extractFencedBlock(trimmed, '');
114
+ if (fence !== null) {
115
+ try {
116
+ return { value: JSON.parse(fence), parseError: null };
117
+ }
118
+ catch (err) {
119
+ return {
120
+ value: undefined,
121
+ parseError: `fenced block not valid JSON: ${err.message}`,
122
+ };
123
+ }
124
+ }
125
+ return { value: undefined, parseError: 'no JSON found (and no fenced block)' };
126
+ }
127
+ function escapeRegex(s) {
128
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
129
+ }
130
+ function truncate(s, max) {
131
+ if (s.length <= max)
132
+ return s;
133
+ return s.slice(0, max) + '…';
134
+ }
135
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/graders/content.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Grader } from './types.js';
2
+ /**
3
+ * Grader that passes when `cwd/<relPath>` exists. Resolves to an absolute
4
+ * path so paths with `..` can't escape the task's cwd.
5
+ */
6
+ export declare function fileExists(relPath: string): Grader;
7
+ /**
8
+ * Grader that passes when `cwd/<relPath>` exists AND its contents match the
9
+ * given predicate (substring, regex, or equality).
10
+ */
11
+ export declare function fileMatches(relPath: string, predicate: string | RegExp | {
12
+ equals: string;
13
+ }): Grader;
14
+ /**
15
+ * Grader that passes when `cwd/<relPath>` does NOT exist. Inverse of
16
+ * {@link fileExists} for tasks like "delete stale.txt".
17
+ */
18
+ export declare function fileMissing(relPath: string): Grader;
19
+ /**
20
+ * Grader that passes when `cwd/<relPath>` is a directory containing at least
21
+ * `min` files matching the (optional) extension filter.
22
+ */
23
+ export declare function directoryHas(relPath: string, min?: number, ext?: string): Grader;
24
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/graders/fs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBlD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9C,MAAM,CAsCR;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUnD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAI,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CA0B3E"}
@@ -0,0 +1,134 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { resolve, sep } from 'node:path';
3
+ /**
4
+ * Grader that passes when `cwd/<relPath>` exists. Resolves to an absolute
5
+ * path so paths with `..` can't escape the task's cwd.
6
+ */
7
+ export function fileExists(relPath) {
8
+ return (ctx) => {
9
+ const abs = safeResolve(ctx.cwd, relPath);
10
+ if (abs === null) {
11
+ return {
12
+ passed: false,
13
+ message: `path "${relPath}" escaped task cwd`,
14
+ details: `cwd: ${ctx.cwd}`,
15
+ };
16
+ }
17
+ if (existsSync(abs)) {
18
+ return { passed: true, message: `file exists: ${relPath}` };
19
+ }
20
+ return {
21
+ passed: false,
22
+ message: `file missing: ${relPath}`,
23
+ details: `expected at: ${abs}`,
24
+ };
25
+ };
26
+ }
27
+ /**
28
+ * Grader that passes when `cwd/<relPath>` exists AND its contents match the
29
+ * given predicate (substring, regex, or equality).
30
+ */
31
+ export function fileMatches(relPath, predicate) {
32
+ return (ctx) => {
33
+ const abs = safeResolve(ctx.cwd, relPath);
34
+ if (abs === null) {
35
+ return { passed: false, message: `path "${relPath}" escaped task cwd` };
36
+ }
37
+ if (!existsSync(abs)) {
38
+ return { passed: false, message: `file missing: ${relPath}`, details: `expected at: ${abs}` };
39
+ }
40
+ const text = readFileSync(abs, 'utf8');
41
+ if (typeof predicate === 'string') {
42
+ return text.includes(predicate)
43
+ ? { passed: true, message: `file ${relPath} contains "${predicate}"` }
44
+ : {
45
+ passed: false,
46
+ message: `file ${relPath} does not contain "${predicate}"`,
47
+ details: truncate(text),
48
+ };
49
+ }
50
+ if (predicate instanceof RegExp) {
51
+ return predicate.test(text)
52
+ ? { passed: true, message: `file ${relPath} matches ${predicate}` }
53
+ : {
54
+ passed: false,
55
+ message: `file ${relPath} does not match ${predicate}`,
56
+ details: truncate(text),
57
+ };
58
+ }
59
+ const want = predicate.equals.trim();
60
+ const got = text.trim();
61
+ return got === want
62
+ ? { passed: true, message: `file ${relPath} equals expected` }
63
+ : {
64
+ passed: false,
65
+ message: `file ${relPath} content mismatch`,
66
+ details: `expected: ${JSON.stringify(want)}\nactual: ${JSON.stringify(got)}`,
67
+ };
68
+ };
69
+ }
70
+ /**
71
+ * Grader that passes when `cwd/<relPath>` does NOT exist. Inverse of
72
+ * {@link fileExists} for tasks like "delete stale.txt".
73
+ */
74
+ export function fileMissing(relPath) {
75
+ return (ctx) => {
76
+ const abs = safeResolve(ctx.cwd, relPath);
77
+ if (abs === null) {
78
+ return { passed: true, message: `path "${relPath}" not in cwd (treated as missing)` };
79
+ }
80
+ return !existsSync(abs)
81
+ ? { passed: true, message: `file absent: ${relPath}` }
82
+ : { passed: false, message: `file should be missing: ${relPath}`, details: `still exists at: ${abs}` };
83
+ };
84
+ }
85
+ /**
86
+ * Grader that passes when `cwd/<relPath>` is a directory containing at least
87
+ * `min` files matching the (optional) extension filter.
88
+ */
89
+ export function directoryHas(relPath, min = 1, ext) {
90
+ return (ctx) => {
91
+ const abs = safeResolve(ctx.cwd, relPath);
92
+ if (abs === null) {
93
+ return { passed: false, message: `path "${relPath}" escaped task cwd` };
94
+ }
95
+ if (!existsSync(abs)) {
96
+ return { passed: false, message: `directory missing: ${relPath}` };
97
+ }
98
+ const st = statSync(abs);
99
+ if (!st.isDirectory()) {
100
+ return { passed: false, message: `not a directory: ${relPath}` };
101
+ }
102
+ const entries = readdirSync(abs);
103
+ const matched = ext ? entries.filter((e) => e.endsWith(ext)) : entries;
104
+ if (matched.length >= min) {
105
+ return {
106
+ passed: true,
107
+ message: `directory ${relPath} has ${matched.length} entries${ext ? ` (${ext})` : ''}`,
108
+ };
109
+ }
110
+ return {
111
+ passed: false,
112
+ message: `directory ${relPath} has only ${matched.length} entries (need ${min}${ext ? ` ${ext}` : ''})`,
113
+ };
114
+ };
115
+ }
116
+ /**
117
+ * Resolve `relPath` against `cwd` and reject any escape attempts (`..`
118
+ * segments, absolute paths, symlink games are not in scope: we trust the
119
+ * grader author to use safe paths; this just defends against typos).
120
+ */
121
+ function safeResolve(cwd, relPath) {
122
+ const abs = resolve(cwd, relPath);
123
+ const root = resolve(cwd);
124
+ if (abs !== root && !abs.startsWith(root + sep)) {
125
+ return null;
126
+ }
127
+ return abs;
128
+ }
129
+ function truncate(s, max = 240) {
130
+ if (s.length <= max)
131
+ return s;
132
+ return s.slice(0, max) + '…';
133
+ }
134
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/graders/fs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export type { Grader, GraderContext, GraderResult, TaskCategory, TaskDefinition, TaskInfo, ToolInvocation, } from './types.js';
2
+ export { regex, contains, equals, type RegexGraderOptions } from './text.js';
3
+ export { fileExists, fileMatches, fileMissing, directoryHas } from './fs.js';
4
+ export { commandRuns, type CommandRunsOptions } from './shell.js';
5
+ export { toolWasCalled, toolNotCalled, toolCalledWith, toolCount, noToolErrors, finalTextNonEmpty, completed, type ToolCountOptions, } from './behavior.js';
6
+ export { jsonSchema, jsonSchemaPartial, type JsonSchemaGraderOptions } from './schema.js';
7
+ export { tokensUnder, costUnder, durationUnder, iterationsUnder, type TokensUnderOptions, } from './metrics.js';
8
+ export { languageBlock, jsonContains, type LanguageBlockOptions, } from './content.js';
9
+ /**
10
+ * Helper for task authors: stamps a `TaskDefinition` with sensible defaults
11
+ * and gives the call site a single import. Functionally identical to the
12
+ * raw object: purely ergonomic.
13
+ */
14
+ export declare function defineTask<T extends import('./types.js').TaskDefinition>(def: T): T;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/graders/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE1F,OAAO,EACL,WAAW,EACX,SAAS,EACT,aAAa,EACb,eAAe,EACf,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,aAAa,EACb,YAAY,EACZ,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,YAAY,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEnF"}
@@ -0,0 +1,19 @@
1
+ export { regex, contains, equals } from './text.js';
2
+ export { fileExists, fileMatches, fileMissing, directoryHas } from './fs.js';
3
+ export { commandRuns } from './shell.js';
4
+ export { toolWasCalled, toolNotCalled, toolCalledWith, toolCount, noToolErrors, finalTextNonEmpty, completed, } from './behavior.js';
5
+ // v2.12: JSON-schema / Zod-schema graders for structured-output tasks.
6
+ export { jsonSchema, jsonSchemaPartial } from './schema.js';
7
+ // v2.13: Budget / throughput graders over AgentRunResult aggregates.
8
+ export { tokensUnder, costUnder, durationUnder, iterationsUnder, } from './metrics.js';
9
+ // v2.13: Content extraction graders (fenced code blocks, predicate JSON).
10
+ export { languageBlock, jsonContains, } from './content.js';
11
+ /**
12
+ * Helper for task authors: stamps a `TaskDefinition` with sensible defaults
13
+ * and gives the call site a single import. Functionally identical to the
14
+ * raw object: purely ergonomic.
15
+ */
16
+ export function defineTask(def) {
17
+ return def;
18
+ }
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/graders/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAA2B,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,WAAW,EAA2B,MAAM,YAAY,CAAC;AAClE,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,SAAS,GAEV,MAAM,eAAe,CAAC;AACvB,uEAAuE;AACvE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAgC,MAAM,aAAa,CAAC;AAC1F,qEAAqE;AACrE,OAAO,EACL,WAAW,EACX,SAAS,EACT,aAAa,EACb,eAAe,GAEhB,MAAM,cAAc,CAAC;AACtB,0EAA0E;AAC1E,OAAO,EACL,aAAa,EACb,YAAY,GAEb,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAgD,GAAM;IAC9E,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Budget / throughput graders for the eval harness. v2.13.
3
+ *
4
+ * These graders inspect {@link AgentRunResult} aggregate fields
5
+ * (tokens, cost, duration, iterations) and pass when they fall under
6
+ * a caller-supplied ceiling. They let eval tasks answer questions
7
+ * like "did the agent come in under budget?" without writing custom
8
+ * predicates against raw numbers.
9
+ *
10
+ * Why a budget grader family?
11
+ * - Cost is a first-class eval signal for an LLM agent. If a task
12
+ * routinely hits 10x expected cost, that's a regression worth
13
+ * catching even if the answer is technically correct.
14
+ * - Latency ceilinging catches "agent goes into a long loop" bugs
15
+ * that correctness graders don't.
16
+ * - Pair `tokensUnder` / `costUnder` with `completed()` to assert
17
+ * "this task finishes correctly AND cheaply".
18
+ */
19
+ import type { Grader } from './types.js';
20
+ /** Options for {@link tokensUnder}. */
21
+ export interface TokensUnderOptions {
22
+ /**
23
+ * Which token counter to budget against.
24
+ * - `'total'` (default): input + output, the most useful general signal
25
+ * - `'input'`: "agent didn't shovel huge context"
26
+ * - `'output'`: "agent didn't ramble"
27
+ */
28
+ kind?: 'input' | 'output' | 'total';
29
+ }
30
+ /**
31
+ * Grader that passes when the chosen token counter is `<= max`.
32
+ * The cap is a soft ceiling; the grader doesn't measure cost, just
33
+ * raw token counts. Use {@link costUnder} for budget enforcement in
34
+ * dollars.
35
+ *
36
+ * ```ts
37
+ * graders: [
38
+ * completed(),
39
+ * tokensUnder(2000), // total <= 2000
40
+ * tokensUnder(800, { kind: 'input' }),// input alone <= 800
41
+ * ],
42
+ * ```
43
+ */
44
+ export declare function tokensUnder(max: number, options?: TokensUnderOptions): Grader;
45
+ /**
46
+ * Grader that passes when total cost is `<= maxUsd`. Fails when the
47
+ * agent's model has no known pricing (the cost isn't knowable, so
48
+ * "under budget" can't be verified).
49
+ *
50
+ * Used with cost-tracking models; for free-tier / on-device models
51
+ * pricing is unknown and this grader will always fail, so omit it
52
+ * from those tasks.
53
+ */
54
+ export declare function costUnder(maxUsd: number): Grader;
55
+ /** Grader that passes when the wall-clock run duration is `<= maxMs`. */
56
+ export declare function durationUnder(maxMs: number): Grader;
57
+ /** Grader that passes when the agent finished in `<= max` iterations. */
58
+ export declare function iterationsUnder(max: number): Grader;
59
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/graders/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAUjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgBhD;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOnD"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Grader that passes when the chosen token counter is `<= max`.
3
+ * The cap is a soft ceiling; the grader doesn't measure cost, just
4
+ * raw token counts. Use {@link costUnder} for budget enforcement in
5
+ * dollars.
6
+ *
7
+ * ```ts
8
+ * graders: [
9
+ * completed(),
10
+ * tokensUnder(2000), // total <= 2000
11
+ * tokensUnder(800, { kind: 'input' }),// input alone <= 800
12
+ * ],
13
+ * ```
14
+ */
15
+ export function tokensUnder(max, options = {}) {
16
+ const kind = options.kind ?? 'total';
17
+ return (ctx) => {
18
+ const i = ctx.result.inputTokens;
19
+ const o = ctx.result.outputTokens;
20
+ const actual = kind === 'input' ? i : kind === 'output' ? o : i + o;
21
+ return actual <= max
22
+ ? { passed: true, message: `${kind} tokens ${actual} <= ${max}` }
23
+ : { passed: false, message: `${kind} tokens ${actual} exceeded ${max}` };
24
+ };
25
+ }
26
+ /**
27
+ * Grader that passes when total cost is `<= maxUsd`. Fails when the
28
+ * agent's model has no known pricing (the cost isn't knowable, so
29
+ * "under budget" can't be verified).
30
+ *
31
+ * Used with cost-tracking models; for free-tier / on-device models
32
+ * pricing is unknown and this grader will always fail, so omit it
33
+ * from those tasks.
34
+ */
35
+ export function costUnder(maxUsd) {
36
+ return (ctx) => {
37
+ const r = ctx.result;
38
+ if (!r.costKnown) {
39
+ return {
40
+ passed: false,
41
+ message: `cost unknown for model: cannot enforce <= $${maxUsd}`,
42
+ };
43
+ }
44
+ return r.costUsd <= maxUsd
45
+ ? { passed: true, message: `cost $${r.costUsd.toFixed(6)} <= $${maxUsd}` }
46
+ : {
47
+ passed: false,
48
+ message: `cost $${r.costUsd.toFixed(6)} exceeded $${maxUsd}`,
49
+ };
50
+ };
51
+ }
52
+ /** Grader that passes when the wall-clock run duration is `<= maxMs`. */
53
+ export function durationUnder(maxMs) {
54
+ return (ctx) => {
55
+ const actual = ctx.result.durationMs;
56
+ return actual <= maxMs
57
+ ? { passed: true, message: `duration ${actual}ms <= ${maxMs}ms` }
58
+ : { passed: false, message: `duration ${actual}ms exceeded ${maxMs}ms` };
59
+ };
60
+ }
61
+ /** Grader that passes when the agent finished in `<= max` iterations. */
62
+ export function iterationsUnder(max) {
63
+ return (ctx) => {
64
+ const actual = ctx.result.iterations;
65
+ return actual <= max
66
+ ? { passed: true, message: `iterations ${actual} <= ${max}` }
67
+ : { passed: false, message: `iterations ${actual} exceeded ${max}` };
68
+ };
69
+ }
70
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/graders/metrics.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,UAA8B,EAAE;IACvE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC;IACrC,OAAO,CAAC,GAAkB,EAAgB,EAAE;QAC1C,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;QACjC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACpE,OAAO,MAAM,IAAI,GAAG;YAClB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,WAAW,MAAM,OAAO,GAAG,EAAE,EAAE;YACjE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,WAAW,MAAM,aAAa,GAAG,EAAE,EAAE,CAAC;IAC7E,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,CAAC,GAAkB,EAAgB,EAAE;QAC1C,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;YACjB,OAAO;gBACL,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,8CAA8C,MAAM,EAAE;aAChE,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,CAAC,OAAO,IAAI,MAAM;YACxB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,EAAE;YAC1E,CAAC,CAAC;gBACE,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,MAAM,EAAE;aAC7D,CAAC;IACR,CAAC,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,CAAC,GAAkB,EAAgB,EAAE;QAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;QACrC,OAAO,MAAM,IAAI,KAAK;YACpB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,MAAM,SAAS,KAAK,IAAI,EAAE;YACjE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,MAAM,eAAe,KAAK,IAAI,EAAE,CAAC;IAC7E,CAAC,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,CAAC,GAAkB,EAAgB,EAAE;QAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;QACrC,OAAO,MAAM,IAAI,GAAG;YAClB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,MAAM,OAAO,GAAG,EAAE,EAAE;YAC7D,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,MAAM,aAAa,GAAG,EAAE,EAAE,CAAC;IACzE,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * JSON-schema / Zod-schema grader for the eval harness. v2.12.
3
+ *
4
+ * Validates `AgentRunResult.finalText` against a user-supplied Zod
5
+ * schema. Pairs naturally with the v2.8 `--schema` mode: run the
6
+ * agent in structured-output mode and grade the result with
7
+ * `jsonSchema` for a fully deterministic CI pipeline (no LLM-as-judge
8
+ * for tasks that have a clear JSON contract).
9
+ *
10
+ * The grader accepts the schema in two forms:
11
+ * - A Zod schema object (`z.object({...})`): the most ergonomic
12
+ * option, full TypeScript inference, no extra parse step.
13
+ * - A path to a `.json` / `.ts` / `.mjs` schema file: the most
14
+ * portable, lets the same schema be reused by the agent's
15
+ * `--schema` flag AND the grader.
16
+ *
17
+ * Extraction follows the same rules as the agent's structured-output
18
+ * mode: try the raw text first, then a single ```json ... ``` fenced
19
+ * block. See {@link validateFinalText} in `@ikarmic/forge-core` for
20
+ * the reference implementation: this grader re-implements it
21
+ * directly so the eval harness stays a leaf package (no dep on the
22
+ * core's internal helpers).
23
+ */
24
+ import type { z } from 'zod';
25
+ import type { Grader } from './types.js';
26
+ /** Options for {@link jsonSchema}. */
27
+ export interface JsonSchemaGraderOptions {
28
+ /**
29
+ * Only relevant when `schema` is a string. Default true: the
30
+ * string is treated as a path to a schema file (`.ts` / `.js` /
31
+ * `.mjs` / `.cjs` / `.json`) and loaded via {@link loadSchemaFile}
32
+ * from `@ikarmic/forge-core` (dynamic import for JS/TS modules,
33
+ * JSON-Schema-to-Zod conversion for `.json`). Set to false to make
34
+ * the grader throw immediately instead of attempting to load the
35
+ * string as a path.
36
+ */
37
+ schemaIsPath?: boolean;
38
+ /**
39
+ * Truncate the raw `finalText` in failure details to this many
40
+ * characters. Default 400: long enough to see the offending JSON,
41
+ * short enough to keep reports scannable.
42
+ */
43
+ maxDetailChars?: number;
44
+ }
45
+ /**
46
+ * Grader that parses `finalText` as JSON and validates it against the
47
+ * supplied schema. Returns `{ passed: true }` on a successful
48
+ * validation, `{ passed: false, details }` with the validation error
49
+ * + truncated text otherwise.
50
+ *
51
+ * ```ts
52
+ * import { z } from 'zod';
53
+ * import { jsonSchema } from '@ikarmic/forge-eval';
54
+ *
55
+ * const prSchema = z.object({
56
+ * title: z.string(),
57
+ * body: z.string().min(20),
58
+ * });
59
+ *
60
+ * const task = defineTask({
61
+ * id: 'summarize-pr',
62
+ * graders: [jsonSchema(prSchema)],
63
+ * ...
64
+ * });
65
+ *
66
+ * // Or load the same schema the agent uses via --schema:
67
+ * const task = defineTask({
68
+ * id: 'extract-pr',
69
+ * graders: [jsonSchema('./schemas/pr-summary.ts')],
70
+ * ...
71
+ * });
72
+ * ```
73
+ */
74
+ export declare function jsonSchema(schema: z.ZodTypeAny | string, options?: JsonSchemaGraderOptions): Grader;
75
+ /**
76
+ * Grader that validates the parsed JSON's *subset* of fields: useful
77
+ * when the agent may add extra fields you don't want to grade but
78
+ * the core fields must match. Behaves like {@link jsonSchema} but
79
+ * wraps the user schema in `passthrough()` before validating, so any
80
+ * extra fields are ignored rather than triggering a strict failure.
81
+ *
82
+ * Note: when `schema` is a path, it can't be inspected synchronously,
83
+ * so the `passthrough()` wrapping is only applied to a Zod schema
84
+ * object passed directly: a path-loaded schema is graded as-is.
85
+ */
86
+ export declare function jsonSchemaPartial(schema: z.ZodTypeAny | string, options?: JsonSchemaGraderOptions): Grader;
87
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/graders/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAI7B,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AAEtE,sCAAsC;AACtC,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,MAAM,EAC7B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CAgCR;AA2BD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,MAAM,EAC7B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CAUR"}