@kodax-ai/kodax 0.7.95 → 0.7.96-alpha.2

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 (163) hide show
  1. package/CHANGELOG.md +178 -1
  2. package/LICENSE +158 -158
  3. package/README.md +149 -24
  4. package/README_CN.md +117 -24
  5. package/config-templates/config.example.jsonc +2 -1
  6. package/config-templates/integrations/a2a.example.jsonc +91 -91
  7. package/config-templates/integrations/extensions.example.jsonc +7 -7
  8. package/config-templates/integrations/mcp.example.jsonc +16 -16
  9. package/dist/builtin/code-review/SKILL.md +82 -82
  10. package/dist/builtin/git-workflow/SKILL.md +84 -84
  11. package/dist/builtin/skill-creator/SKILL.md +127 -127
  12. package/dist/builtin/skill-creator/agents/analyzer.md +12 -12
  13. package/dist/builtin/skill-creator/agents/comparator.md +13 -13
  14. package/dist/builtin/skill-creator/agents/grader.md +13 -13
  15. package/dist/builtin/skill-creator/references/schemas.md +227 -227
  16. package/dist/builtin/skill-creator/scripts/aggregate-benchmark.d.ts +46 -46
  17. package/dist/builtin/skill-creator/scripts/aggregate-benchmark.js +208 -208
  18. package/dist/builtin/skill-creator/scripts/analyze-benchmark.d.ts +46 -46
  19. package/dist/builtin/skill-creator/scripts/analyze-benchmark.js +286 -286
  20. package/dist/builtin/skill-creator/scripts/compare-runs.d.ts +62 -62
  21. package/dist/builtin/skill-creator/scripts/compare-runs.js +330 -330
  22. package/dist/builtin/skill-creator/scripts/generate-review.d.ts +33 -33
  23. package/dist/builtin/skill-creator/scripts/generate-review.js +414 -414
  24. package/dist/builtin/skill-creator/scripts/grade-evals.d.ts +73 -73
  25. package/dist/builtin/skill-creator/scripts/grade-evals.js +402 -402
  26. package/dist/builtin/skill-creator/scripts/improve-description.d.ts +23 -23
  27. package/dist/builtin/skill-creator/scripts/improve-description.js +160 -160
  28. package/dist/builtin/skill-creator/scripts/init-skill.d.ts +14 -14
  29. package/dist/builtin/skill-creator/scripts/init-skill.js +155 -155
  30. package/dist/builtin/skill-creator/scripts/install-skill.d.ts +29 -29
  31. package/dist/builtin/skill-creator/scripts/install-skill.js +173 -173
  32. package/dist/builtin/skill-creator/scripts/package-skill.d.ts +38 -38
  33. package/dist/builtin/skill-creator/scripts/package-skill.js +121 -121
  34. package/dist/builtin/skill-creator/scripts/quick-validate.d.ts +8 -8
  35. package/dist/builtin/skill-creator/scripts/quick-validate.js +163 -163
  36. package/dist/builtin/skill-creator/scripts/run-eval.d.ts +66 -66
  37. package/dist/builtin/skill-creator/scripts/run-eval.js +353 -353
  38. package/dist/builtin/skill-creator/scripts/run-loop.d.ts +49 -49
  39. package/dist/builtin/skill-creator/scripts/run-loop.js +242 -242
  40. package/dist/builtin/skill-creator/scripts/run-trigger-eval.d.ts +58 -58
  41. package/dist/builtin/skill-creator/scripts/run-trigger-eval.js +224 -224
  42. package/dist/builtin/tdd/SKILL.md +56 -56
  43. package/dist/chunks/agent-4FABF6WK.js +2 -0
  44. package/dist/chunks/argument-completer-72VHB2VL.js +2 -0
  45. package/dist/chunks/{chunk-7JXA3533.js → chunk-4OIQZWA5.js} +223 -226
  46. package/dist/chunks/{chunk-BNVSKIAB.js → chunk-6BVF3HZ7.js} +136 -136
  47. package/dist/chunks/chunk-7JZZIFZH.js +501 -0
  48. package/dist/chunks/chunk-AC7Z2OIY.js +2 -0
  49. package/dist/chunks/{chunk-JS452J2F.js → chunk-C4KYZAFU.js} +2 -2
  50. package/dist/chunks/chunk-CHUPIWIF.js +251 -0
  51. package/dist/chunks/{chunk-C4DTUKTR.js → chunk-GYUU7XLH.js} +5 -4
  52. package/dist/chunks/{chunk-OK4AXDC5.js → chunk-HAX55GOQ.js} +1 -1
  53. package/dist/chunks/chunk-JKTJCGO2.js +418 -0
  54. package/dist/chunks/{chunk-O743AJ5V.js → chunk-LQYETRFK.js} +171 -175
  55. package/dist/chunks/{chunk-2PEDBGKN.js → chunk-LTUZTQ6W.js} +1 -1
  56. package/dist/chunks/chunk-LYWDUSKO.js +2 -0
  57. package/dist/chunks/chunk-NGH6T4FD.js +92 -0
  58. package/dist/chunks/{chunk-SRIJX5ZP.js → chunk-NVRWCSA5.js} +1 -1
  59. package/dist/chunks/chunk-O4DS7RIQ.js +384 -0
  60. package/dist/chunks/chunk-ORXU6OWZ.js +2 -0
  61. package/dist/chunks/chunk-POTW3O65.js +124 -0
  62. package/dist/chunks/{chunk-2TM3W3GK.js → chunk-RIQSS56Y.js} +1 -1
  63. package/dist/chunks/chunk-U27XKEN5.js +407 -0
  64. package/dist/chunks/{chunk-6ABQJUK6.js → chunk-XUX6OUCA.js} +2 -2
  65. package/dist/chunks/{client-JDOS3YJR.js → client-4C456K4O.js} +1 -1
  66. package/dist/chunks/compaction-config-3VQRDFF4.js +2 -0
  67. package/dist/chunks/{construction-bootstrap-YCWHHUYO.js → construction-bootstrap-AHN7EDGG.js} +1 -1
  68. package/dist/chunks/dist-BCBQXAGI.js +2 -0
  69. package/dist/chunks/dist-QK2YDWU6.js +2 -0
  70. package/dist/chunks/host-VIHV7ADG.js +2 -0
  71. package/dist/chunks/run-manager-NBVMU3KV.js +2 -0
  72. package/dist/chunks/utils-MEJZ2IGI.js +2 -0
  73. package/dist/index.d.ts +22 -21
  74. package/dist/index.js +7 -7
  75. package/dist/kodax_bootstrap.js +26 -26
  76. package/dist/kodax_cli.js +1905 -1882
  77. package/dist/native/darwin-arm64/LICENSE-APACHE.txt +13 -0
  78. package/dist/native/darwin-arm64/kodax-text-transaction.node +0 -0
  79. package/dist/native/darwin-arm64/manifest.json +16 -0
  80. package/dist/native/darwin-x64/LICENSE-APACHE.txt +13 -0
  81. package/dist/native/darwin-x64/kodax-text-transaction.node +0 -0
  82. package/dist/native/darwin-x64/manifest.json +16 -0
  83. package/dist/native/linux-arm64/LICENSE-APACHE.txt +13 -0
  84. package/dist/native/linux-arm64/kodax-text-transaction.node +0 -0
  85. package/dist/native/linux-arm64/manifest.json +16 -0
  86. package/dist/native/linux-x64/LICENSE-APACHE.txt +13 -0
  87. package/dist/native/linux-x64/kodax-text-transaction.node +0 -0
  88. package/dist/native/linux-x64/manifest.json +16 -0
  89. package/dist/native/win32-x64/LICENSE-APACHE.txt +13 -0
  90. package/dist/native/win32-x64/NOTICE-windows-sandbox.txt +8 -0
  91. package/dist/native/win32-x64/kodax-windows-sandbox.exe +0 -0
  92. package/dist/native/win32-x64/kodax-windows-text-transaction.node +0 -0
  93. package/dist/native/win32-x64/manifest.json +30 -0
  94. package/dist/provider-capabilities.json +108 -2
  95. package/dist/runtime-worker.js +1745 -1727
  96. package/dist/sandbox-network-broker.js +1715 -0
  97. package/dist/sdk-a2a.d.ts +13 -12
  98. package/dist/sdk-a2a.js +1 -1
  99. package/dist/sdk-agent.d.ts +27 -18
  100. package/dist/sdk-agent.js +1 -1
  101. package/dist/sdk-coding.d.ts +107 -58
  102. package/dist/sdk-coding.js +1 -1
  103. package/dist/sdk-experimental-memory.d.ts +4 -4
  104. package/dist/sdk-experimental-memory.js +1 -1
  105. package/dist/sdk-llm.d.ts +9 -10
  106. package/dist/sdk-llm.js +1 -1
  107. package/dist/sdk-mcp.js +1 -1
  108. package/dist/sdk-media.d.ts +1 -1
  109. package/dist/sdk-media.js +1 -1
  110. package/dist/sdk-repl.d.ts +25 -17
  111. package/dist/sdk-repl.js +1 -1
  112. package/dist/sdk-runtime.d.ts +41 -17
  113. package/dist/sdk-runtime.js +1 -1
  114. package/dist/sdk-sandbox.d.ts +17 -3
  115. package/dist/sdk-sandbox.js +1 -1
  116. package/dist/sdk-session.d.ts +8 -7
  117. package/dist/sdk-session.js +1 -1
  118. package/dist/sdk-skills.js +1 -1
  119. package/dist/semantic-worker.js +61 -61
  120. package/dist/types-chunks/{base.d-vopO1rml.d.ts → base.d-COo8E2dk.d.ts} +18 -27
  121. package/dist/types-chunks/{bash-prefix-extractor.d-B2rxNI4L.d.ts → bash-prefix-extractor.d-DQOlA8cy.d.ts} +72 -52
  122. package/dist/types-chunks/{capability-learning.d-CE1tm29C.d.ts → capability-learning.d-lRPgA3Or.d.ts} +1 -1
  123. package/dist/types-chunks/{capsule.d-Y7QRS4J2.d.ts → capsule.d-0zxsgXg_.d.ts} +3 -3
  124. package/dist/types-chunks/{controller.d-BR-UVeUR.d.ts → controller.d-BTtyIIC1.d.ts} +1 -1
  125. package/dist/types-chunks/{controller.d-C5noq5-w.d.ts → controller.d-C7tXUcXo.d.ts} +2 -2
  126. package/dist/types-chunks/{guardrail.d-DqHD-61O.d.ts → guardrail.d-DKTyEHJa.d.ts} +5 -5
  127. package/dist/types-chunks/{history-retrieval.d-CKU8zmt_.d.ts → history-retrieval.d-CHnL99S7.d.ts} +2 -2
  128. package/dist/types-chunks/{public-api.d-D7fXaWW8.d.ts → public-api.d-Dx3L3W4Z.d.ts} +7 -4
  129. package/dist/types-chunks/{repl.d-BYDb14s6.d.ts → repl.d-mO01olIa.d.ts} +5 -5
  130. package/dist/types-chunks/{resolver.d-CwHUwbdE.d.ts → resolver.d-DlXPjeyx.d.ts} +38 -6
  131. package/dist/types-chunks/{review-inbox.d-BSgFewyj.d.ts → review-inbox.d-WDSe1nze.d.ts} +1 -1
  132. package/dist/types-chunks/{run-manager.d-BVJSntpS.d.ts → run-manager.d-DENbbzgR.d.ts} +1 -1
  133. package/dist/types-chunks/{sdk-session-CqEG84oP.d.ts → sdk-session-DYHGXXH4.d.ts} +3 -3
  134. package/dist/types-chunks/{shell-command-sets.d-DqaaBwhU.d.ts → shell-command-sets.d-CjFqS4dp.d.ts} +1 -1
  135. package/dist/types-chunks/{side-query.d-iH0P9ASy.d.ts → side-query.d-j8A3sFv_.d.ts} +2 -2
  136. package/dist/types-chunks/{types.d-rUOjXych.d.ts → types.d-BbZjR0Wr.d.ts} +1 -1
  137. package/dist/types-chunks/{types.d-CaTXAW_Q.d.ts → types.d-BkTIKdH6.d.ts} +2 -2
  138. package/dist/types-chunks/{types.d-C5xQEFcj.d.ts → types.d-DncLrpu_.d.ts} +4 -4
  139. package/dist/types-chunks/{types.d-CK9A9rpP.d.ts → types.d-Xy0f3x4i.d.ts} +9 -0
  140. package/dist/types-chunks/{utils.d-CWUyLiXo.d.ts → utils.d-CnxefMos.d.ts} +6 -6
  141. package/package.json +10 -3
  142. package/public_docs/README.md +130 -0
  143. package/public_docs/configuration/sandbox.md +289 -0
  144. package/public_docs/sdk/embedder-guide.md +6674 -0
  145. package/scripts/kodax-bin.cjs +28 -28
  146. package/scripts/production-env.cjs +25 -25
  147. package/dist/chunks/agent-XXZMXKKA.js +0 -2
  148. package/dist/chunks/argument-completer-KCYDGTQW.js +0 -2
  149. package/dist/chunks/chunk-77PNP27P.js +0 -92
  150. package/dist/chunks/chunk-7QBWHCIZ.js +0 -2
  151. package/dist/chunks/chunk-BHE66SP3.js +0 -123
  152. package/dist/chunks/chunk-BR2OSB2I.js +0 -722
  153. package/dist/chunks/chunk-DSMONSVB.js +0 -407
  154. package/dist/chunks/chunk-Q5VRWSKG.js +0 -418
  155. package/dist/chunks/chunk-XWRPOGJN.js +0 -4
  156. package/dist/chunks/chunk-Y5VIQBT6.js +0 -383
  157. package/dist/chunks/compaction-config-EGS4577X.js +0 -2
  158. package/dist/chunks/dist-ITTXQHRJ.js +0 -2
  159. package/dist/chunks/dist-KFYN3OKD.js +0 -2
  160. package/dist/chunks/host-VAYADIPW.js +0 -2
  161. package/dist/chunks/run-manager-52IJZMZJ.js +0 -2
  162. package/dist/chunks/utils-P7PBSL3R.js +0 -2
  163. package/dist/sandbox-workspace-session.js +0 -1705
@@ -1,357 +1,357 @@
1
- #!/usr/bin/env node
2
-
3
- import { readFile, writeFile } from 'node:fs/promises';
4
- import path from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import { expandSkillForLLM } from '../../../skill-expander.js';
7
- import { loadFullSkill } from '../../../skill-loader.js';
8
- import { ensureDirectory, loadKodaXSDK } from './utils.js';
9
-
10
- function normalizeAssertion(assertion) {
11
- if (typeof assertion === 'string') {
12
- return { text: assertion };
13
- }
14
- if (assertion && typeof assertion === 'object' && typeof assertion.text === 'string') {
15
- return { text: assertion.text };
16
- }
17
- return null;
18
- }
19
-
20
- function countToolCalls(messages) {
21
- return messages.reduce((total, message) => {
22
- if (!Array.isArray(message.content)) {
23
- return total;
24
- }
25
- return total + message.content.filter((block) => block?.type === 'tool_use').length;
26
- }, 0);
27
- }
28
-
29
- function countToolErrors(messages) {
30
- return messages.reduce((total, message) => {
31
- if (!Array.isArray(message.content)) {
32
- return total;
33
- }
34
- return total + message.content.filter((block) => block?.type === 'tool_result' && block.is_error === true).length;
35
- }, 0);
36
- }
37
-
38
- function renderTranscript(prompt, result) {
39
- const lines = [
40
- '# Skill Eval Transcript',
41
- '',
42
- '## Eval Prompt',
43
- '',
44
- prompt,
45
- '',
46
- '## Final Response',
47
- '',
48
- result.lastText || '(No final text)',
49
- '',
50
- '## Result Flags',
51
- '',
52
- `- success: ${result.success}`,
53
- `- signal: ${result.signal ?? 'none'}`,
54
- `- interrupted: ${result.interrupted === true}`,
55
- `- limit_reached: ${result.limitReached === true}`,
56
- ];
57
-
58
- return `${lines.join('\n')}\n`;
59
- }
60
-
61
- async function readInputFiles(files, options) {
62
- const baseDir = path.dirname(path.resolve(options.evalsPath));
63
- const cwd = path.resolve(options.cwd ?? process.cwd());
64
- const sections = [];
65
-
66
- for (const filePath of files ?? []) {
67
- const resolved = path.isAbsolute(filePath)
68
- ? filePath
69
- : path.resolve(baseDir, filePath);
70
- const fallback = path.resolve(cwd, filePath);
71
- let content = null;
72
- let usedPath = resolved;
73
-
74
- try {
75
- content = await readFile(resolved, 'utf8');
76
- } catch {
77
- content = await readFile(fallback, 'utf8').catch(() => null);
78
- usedPath = fallback;
79
- }
80
-
81
- if (content == null) {
82
- throw new Error(`Input file not found for eval: ${filePath}`);
83
- }
84
-
85
- sections.push(`### ${filePath}\n\`\`\`\n${content}\n\`\`\`\n(Resolved from ${usedPath})`);
86
- }
87
-
88
- if (sections.length === 0) {
89
- return '';
90
- }
91
-
92
- return `## Input Files\n\n${sections.join('\n\n')}\n\n`;
93
- }
94
-
95
- export async function buildEvalPrompt(evalItem, options) {
96
- const prompt = String(evalItem.prompt ?? evalItem.query ?? '').trim();
97
- const fileSection = await readInputFiles(evalItem.files, options);
98
- return `${fileSection}${prompt}`.trim();
99
- }
100
-
101
- async function defaultRunAgent(prompt, options) {
102
- const { runKodaX, estimateTokens } = await loadKodaXSDK();
103
- const startedAt = Date.now();
104
- const result = await runKodaX(
105
- {
106
- provider: options.provider ?? 'anthropic',
107
- model: options.model,
108
- maxIter: options.maxIter ?? 30,
109
- reasoningMode: options.reasoningMode ?? 'off',
110
- thinking: options.reasoningMode ? options.reasoningMode !== 'off' : false,
111
- context: {
112
- gitRoot: path.resolve(options.cwd ?? process.cwd()),
113
- },
114
- },
115
- prompt
116
- );
117
-
118
- return {
119
- result,
120
- totalTokens: estimateTokens(result.messages),
121
- durationMs: Date.now() - startedAt,
122
- };
123
- }
124
-
125
- async function prepareConfigPrompt(configName, skill, taskPrompt, options) {
126
- if (configName === 'with_skill') {
127
- const expanded = await expandSkillForLLM(
128
- skill,
129
- taskPrompt,
130
- {
131
- workingDirectory: path.resolve(options.cwd ?? process.cwd()),
132
- projectRoot: path.resolve(options.cwd ?? process.cwd()),
133
- sessionId: 'skill-eval',
134
- environment: {},
135
- }
136
- );
137
- return expanded.content;
138
- }
139
-
140
- return taskPrompt;
141
- }
142
-
143
- async function writeRunArtifacts(runDir, configName, evalItem, prompt, execution) {
144
- const outputsDir = path.join(runDir, 'outputs');
145
- await ensureDirectory(outputsDir);
146
-
147
- const executionMetrics = {
148
- total_tool_calls: countToolCalls(execution.result.messages),
149
- errors_encountered: countToolErrors(execution.result.messages),
150
- output_chars: execution.result.lastText.length,
151
- };
152
-
153
- await writeFile(path.join(outputsDir, 'result.md'), `${execution.result.lastText}\n`, 'utf8');
154
- await writeFile(path.join(outputsDir, 'prompt.txt'), `${prompt}\n`, 'utf8');
155
- await writeFile(path.join(runDir, 'transcript.md'), renderTranscript(prompt, execution.result), 'utf8');
156
- await writeFile(
157
- path.join(outputsDir, 'metrics.json'),
158
- `${JSON.stringify({
159
- config: configName,
160
- eval_id: evalItem.id ?? null,
161
- session_id: execution.result.sessionId,
162
- ...executionMetrics,
163
- }, null, 2)}\n`,
164
- 'utf8'
165
- );
166
- await writeFile(
167
- path.join(outputsDir, 'messages.json'),
168
- `${JSON.stringify(execution.result.messages, null, 2)}\n`,
169
- 'utf8'
170
- );
171
- await writeFile(
172
- path.join(runDir, 'timing.json'),
173
- `${JSON.stringify({
174
- total_tokens: execution.totalTokens,
175
- duration_ms: execution.durationMs,
176
- total_duration_seconds: Number((execution.durationMs / 1000).toFixed(4)),
177
- }, null, 2)}\n`,
178
- 'utf8'
179
- );
180
- await writeFile(
181
- path.join(runDir, 'result.json'),
182
- `${JSON.stringify({
183
- success: execution.result.success,
184
- signal: execution.result.signal ?? null,
185
- signal_reason: execution.result.signalReason ?? null,
186
- interrupted: execution.result.interrupted === true,
187
- limit_reached: execution.result.limitReached === true,
188
- session_id: execution.result.sessionId,
189
- execution_metrics: executionMetrics,
190
- }, null, 2)}\n`,
191
- 'utf8'
192
- );
193
- }
194
-
195
- export async function runEvalWorkspace(
196
- options,
197
- runner = defaultRunAgent
198
- ) {
199
- const skill = await loadFullSkill(path.resolve(options.skillPath), 'user');
200
- if (!skill) {
201
- throw new Error(`Failed to load skill from ${options.skillPath}`);
202
- }
203
-
204
- const workspaceDir = path.resolve(options.workspaceDir);
205
- await ensureDirectory(workspaceDir);
206
-
207
- const evalDocument = JSON.parse(await readFile(options.evalsPath, 'utf8'));
208
- const evals = Array.isArray(evalDocument.evals) ? evalDocument.evals : [];
209
- const configs = options.configs?.length ? options.configs : ['with_skill', 'without_skill'];
210
- const runsPerConfig = Number.isFinite(options.runsPerConfig) && options.runsPerConfig > 0
211
- ? Math.floor(options.runsPerConfig)
212
- : 1;
213
- const reports = [];
214
-
215
- for (let evalIndex = 0; evalIndex < evals.length; evalIndex += 1) {
216
- const evalItem = evals[evalIndex];
217
- const evalDir = path.join(workspaceDir, `eval-${evalIndex}`);
218
- await ensureDirectory(evalDir);
219
- await writeFile(
220
- path.join(evalDir, 'eval_metadata.json'),
221
- `${JSON.stringify({
222
- eval_id: evalItem.id ?? evalIndex,
223
- eval_name: evalItem.name ?? `eval-${evalIndex}`,
224
- prompt: evalItem.prompt ?? evalItem.query ?? '',
225
- expected_output: evalItem.expected_output ?? '',
226
- assertions: (Array.isArray(evalItem.assertions) ? evalItem.assertions : [])
227
- .map(normalizeAssertion)
228
- .filter(Boolean),
229
- }, null, 2)}\n`,
230
- 'utf8'
231
- );
232
-
233
- const taskPrompt = await buildEvalPrompt(evalItem, options);
234
- const configReports = {};
235
-
236
- for (const configName of configs) {
237
- const configDir = path.join(evalDir, configName);
238
- await ensureDirectory(configDir);
239
- configReports[configName] = [];
240
-
241
- for (let runIndex = 1; runIndex <= runsPerConfig; runIndex += 1) {
242
- const runDir = path.join(configDir, `run-${runIndex}`);
243
- await ensureDirectory(runDir);
244
-
245
- const prompt = await prepareConfigPrompt(configName, skill, taskPrompt, options);
246
- const execution = await runner(prompt, {
247
- ...options,
248
- configName,
249
- evalItem,
250
- runIndex,
251
- });
252
- await writeRunArtifacts(runDir, configName, evalItem, prompt, execution);
253
-
254
- configReports[configName].push({
255
- run_id: `run-${runIndex}`,
256
- session_id: execution.result.sessionId,
257
- success: execution.result.success,
258
- total_tokens: execution.totalTokens,
259
- duration_ms: execution.durationMs,
260
- output_chars: execution.result.lastText.length,
261
- });
262
- }
263
- }
264
-
265
- reports.push({
266
- eval_id: evalItem.id ?? evalIndex,
267
- prompt: evalItem.prompt ?? evalItem.query ?? '',
268
- configs: configReports,
269
- });
270
- }
271
-
272
- return {
273
- workspace: workspaceDir,
274
- skill_name: skill.name,
275
- eval_count: evals.length,
276
- configs,
277
- runs_per_config: runsPerConfig,
278
- reports,
279
- };
280
- }
281
-
282
- function parseArgs(argv) {
283
- const args = {
284
- skillPath: '',
285
- evalsPath: '',
286
- workspaceDir: '',
287
- provider: 'anthropic',
288
- model: undefined,
289
- runsPerConfig: 1,
290
- maxIter: 30,
291
- reasoningMode: 'off',
292
- cwd: process.cwd(),
293
- configs: ['with_skill', 'without_skill'],
294
- output: undefined,
295
- };
296
-
297
- for (let index = 2; index < argv.length; index += 1) {
298
- const token = argv[index];
299
- if (token === '--skill-path' && argv[index + 1]) {
300
- args.skillPath = argv[++index];
301
- } else if (token === '--evals' && argv[index + 1]) {
302
- args.evalsPath = argv[++index];
303
- } else if (token === '--workspace' && argv[index + 1]) {
304
- args.workspaceDir = argv[++index];
305
- } else if (token === '--provider' && argv[index + 1]) {
306
- args.provider = argv[++index];
307
- } else if (token === '--model' && argv[index + 1]) {
308
- args.model = argv[++index];
309
- } else if (token === '--runs' && argv[index + 1]) {
310
- args.runsPerConfig = Number(argv[++index]);
311
- } else if (token === '--max-iter' && argv[index + 1]) {
312
- args.maxIter = Number(argv[++index]);
313
- } else if (token === '--reasoning' && argv[index + 1]) {
314
- args.reasoningMode = argv[++index];
315
- } else if (token === '--cwd' && argv[index + 1]) {
316
- args.cwd = argv[++index];
317
- } else if (token === '--configs' && argv[index + 1]) {
318
- args.configs = argv[++index]
319
- .split(',')
320
- .map((item) => item.trim())
321
- .filter(Boolean);
322
- } else if (token === '--output' && argv[index + 1]) {
323
- args.output = argv[++index];
324
- }
325
- }
326
-
327
- return args;
328
- }
329
-
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile, writeFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { expandSkillForLLM } from '../../../skill-expander.js';
7
+ import { loadFullSkill } from '../../../skill-loader.js';
8
+ import { ensureDirectory, loadKodaXSDK } from './utils.js';
9
+
10
+ function normalizeAssertion(assertion) {
11
+ if (typeof assertion === 'string') {
12
+ return { text: assertion };
13
+ }
14
+ if (assertion && typeof assertion === 'object' && typeof assertion.text === 'string') {
15
+ return { text: assertion.text };
16
+ }
17
+ return null;
18
+ }
19
+
20
+ function countToolCalls(messages) {
21
+ return messages.reduce((total, message) => {
22
+ if (!Array.isArray(message.content)) {
23
+ return total;
24
+ }
25
+ return total + message.content.filter((block) => block?.type === 'tool_use').length;
26
+ }, 0);
27
+ }
28
+
29
+ function countToolErrors(messages) {
30
+ return messages.reduce((total, message) => {
31
+ if (!Array.isArray(message.content)) {
32
+ return total;
33
+ }
34
+ return total + message.content.filter((block) => block?.type === 'tool_result' && block.is_error === true).length;
35
+ }, 0);
36
+ }
37
+
38
+ function renderTranscript(prompt, result) {
39
+ const lines = [
40
+ '# Skill Eval Transcript',
41
+ '',
42
+ '## Eval Prompt',
43
+ '',
44
+ prompt,
45
+ '',
46
+ '## Final Response',
47
+ '',
48
+ result.lastText || '(No final text)',
49
+ '',
50
+ '## Result Flags',
51
+ '',
52
+ `- success: ${result.success}`,
53
+ `- signal: ${result.signal ?? 'none'}`,
54
+ `- interrupted: ${result.interrupted === true}`,
55
+ `- limit_reached: ${result.limitReached === true}`,
56
+ ];
57
+
58
+ return `${lines.join('\n')}\n`;
59
+ }
60
+
61
+ async function readInputFiles(files, options) {
62
+ const baseDir = path.dirname(path.resolve(options.evalsPath));
63
+ const cwd = path.resolve(options.cwd ?? process.cwd());
64
+ const sections = [];
65
+
66
+ for (const filePath of files ?? []) {
67
+ const resolved = path.isAbsolute(filePath)
68
+ ? filePath
69
+ : path.resolve(baseDir, filePath);
70
+ const fallback = path.resolve(cwd, filePath);
71
+ let content = null;
72
+ let usedPath = resolved;
73
+
74
+ try {
75
+ content = await readFile(resolved, 'utf8');
76
+ } catch {
77
+ content = await readFile(fallback, 'utf8').catch(() => null);
78
+ usedPath = fallback;
79
+ }
80
+
81
+ if (content == null) {
82
+ throw new Error(`Input file not found for eval: ${filePath}`);
83
+ }
84
+
85
+ sections.push(`### ${filePath}\n\`\`\`\n${content}\n\`\`\`\n(Resolved from ${usedPath})`);
86
+ }
87
+
88
+ if (sections.length === 0) {
89
+ return '';
90
+ }
91
+
92
+ return `## Input Files\n\n${sections.join('\n\n')}\n\n`;
93
+ }
94
+
95
+ export async function buildEvalPrompt(evalItem, options) {
96
+ const prompt = String(evalItem.prompt ?? evalItem.query ?? '').trim();
97
+ const fileSection = await readInputFiles(evalItem.files, options);
98
+ return `${fileSection}${prompt}`.trim();
99
+ }
100
+
101
+ async function defaultRunAgent(prompt, options) {
102
+ const { runKodaX, estimateTokens } = await loadKodaXSDK();
103
+ const startedAt = Date.now();
104
+ const result = await runKodaX(
105
+ {
106
+ provider: options.provider ?? 'anthropic',
107
+ model: options.model,
108
+ maxIter: options.maxIter ?? 30,
109
+ reasoningMode: options.reasoningMode ?? 'off',
110
+ thinking: options.reasoningMode ? options.reasoningMode !== 'off' : false,
111
+ context: {
112
+ gitRoot: path.resolve(options.cwd ?? process.cwd()),
113
+ },
114
+ },
115
+ prompt
116
+ );
117
+
118
+ return {
119
+ result,
120
+ totalTokens: estimateTokens(result.messages),
121
+ durationMs: Date.now() - startedAt,
122
+ };
123
+ }
124
+
125
+ async function prepareConfigPrompt(configName, skill, taskPrompt, options) {
126
+ if (configName === 'with_skill') {
127
+ const expanded = await expandSkillForLLM(
128
+ skill,
129
+ taskPrompt,
130
+ {
131
+ workingDirectory: path.resolve(options.cwd ?? process.cwd()),
132
+ projectRoot: path.resolve(options.cwd ?? process.cwd()),
133
+ sessionId: 'skill-eval',
134
+ environment: {},
135
+ }
136
+ );
137
+ return expanded.content;
138
+ }
139
+
140
+ return taskPrompt;
141
+ }
142
+
143
+ async function writeRunArtifacts(runDir, configName, evalItem, prompt, execution) {
144
+ const outputsDir = path.join(runDir, 'outputs');
145
+ await ensureDirectory(outputsDir);
146
+
147
+ const executionMetrics = {
148
+ total_tool_calls: countToolCalls(execution.result.messages),
149
+ errors_encountered: countToolErrors(execution.result.messages),
150
+ output_chars: execution.result.lastText.length,
151
+ };
152
+
153
+ await writeFile(path.join(outputsDir, 'result.md'), `${execution.result.lastText}\n`, 'utf8');
154
+ await writeFile(path.join(outputsDir, 'prompt.txt'), `${prompt}\n`, 'utf8');
155
+ await writeFile(path.join(runDir, 'transcript.md'), renderTranscript(prompt, execution.result), 'utf8');
156
+ await writeFile(
157
+ path.join(outputsDir, 'metrics.json'),
158
+ `${JSON.stringify({
159
+ config: configName,
160
+ eval_id: evalItem.id ?? null,
161
+ session_id: execution.result.sessionId,
162
+ ...executionMetrics,
163
+ }, null, 2)}\n`,
164
+ 'utf8'
165
+ );
166
+ await writeFile(
167
+ path.join(outputsDir, 'messages.json'),
168
+ `${JSON.stringify(execution.result.messages, null, 2)}\n`,
169
+ 'utf8'
170
+ );
171
+ await writeFile(
172
+ path.join(runDir, 'timing.json'),
173
+ `${JSON.stringify({
174
+ total_tokens: execution.totalTokens,
175
+ duration_ms: execution.durationMs,
176
+ total_duration_seconds: Number((execution.durationMs / 1000).toFixed(4)),
177
+ }, null, 2)}\n`,
178
+ 'utf8'
179
+ );
180
+ await writeFile(
181
+ path.join(runDir, 'result.json'),
182
+ `${JSON.stringify({
183
+ success: execution.result.success,
184
+ signal: execution.result.signal ?? null,
185
+ signal_reason: execution.result.signalReason ?? null,
186
+ interrupted: execution.result.interrupted === true,
187
+ limit_reached: execution.result.limitReached === true,
188
+ session_id: execution.result.sessionId,
189
+ execution_metrics: executionMetrics,
190
+ }, null, 2)}\n`,
191
+ 'utf8'
192
+ );
193
+ }
194
+
195
+ export async function runEvalWorkspace(
196
+ options,
197
+ runner = defaultRunAgent
198
+ ) {
199
+ const skill = await loadFullSkill(path.resolve(options.skillPath), 'user');
200
+ if (!skill) {
201
+ throw new Error(`Failed to load skill from ${options.skillPath}`);
202
+ }
203
+
204
+ const workspaceDir = path.resolve(options.workspaceDir);
205
+ await ensureDirectory(workspaceDir);
206
+
207
+ const evalDocument = JSON.parse(await readFile(options.evalsPath, 'utf8'));
208
+ const evals = Array.isArray(evalDocument.evals) ? evalDocument.evals : [];
209
+ const configs = options.configs?.length ? options.configs : ['with_skill', 'without_skill'];
210
+ const runsPerConfig = Number.isFinite(options.runsPerConfig) && options.runsPerConfig > 0
211
+ ? Math.floor(options.runsPerConfig)
212
+ : 1;
213
+ const reports = [];
214
+
215
+ for (let evalIndex = 0; evalIndex < evals.length; evalIndex += 1) {
216
+ const evalItem = evals[evalIndex];
217
+ const evalDir = path.join(workspaceDir, `eval-${evalIndex}`);
218
+ await ensureDirectory(evalDir);
219
+ await writeFile(
220
+ path.join(evalDir, 'eval_metadata.json'),
221
+ `${JSON.stringify({
222
+ eval_id: evalItem.id ?? evalIndex,
223
+ eval_name: evalItem.name ?? `eval-${evalIndex}`,
224
+ prompt: evalItem.prompt ?? evalItem.query ?? '',
225
+ expected_output: evalItem.expected_output ?? '',
226
+ assertions: (Array.isArray(evalItem.assertions) ? evalItem.assertions : [])
227
+ .map(normalizeAssertion)
228
+ .filter(Boolean),
229
+ }, null, 2)}\n`,
230
+ 'utf8'
231
+ );
232
+
233
+ const taskPrompt = await buildEvalPrompt(evalItem, options);
234
+ const configReports = {};
235
+
236
+ for (const configName of configs) {
237
+ const configDir = path.join(evalDir, configName);
238
+ await ensureDirectory(configDir);
239
+ configReports[configName] = [];
240
+
241
+ for (let runIndex = 1; runIndex <= runsPerConfig; runIndex += 1) {
242
+ const runDir = path.join(configDir, `run-${runIndex}`);
243
+ await ensureDirectory(runDir);
244
+
245
+ const prompt = await prepareConfigPrompt(configName, skill, taskPrompt, options);
246
+ const execution = await runner(prompt, {
247
+ ...options,
248
+ configName,
249
+ evalItem,
250
+ runIndex,
251
+ });
252
+ await writeRunArtifacts(runDir, configName, evalItem, prompt, execution);
253
+
254
+ configReports[configName].push({
255
+ run_id: `run-${runIndex}`,
256
+ session_id: execution.result.sessionId,
257
+ success: execution.result.success,
258
+ total_tokens: execution.totalTokens,
259
+ duration_ms: execution.durationMs,
260
+ output_chars: execution.result.lastText.length,
261
+ });
262
+ }
263
+ }
264
+
265
+ reports.push({
266
+ eval_id: evalItem.id ?? evalIndex,
267
+ prompt: evalItem.prompt ?? evalItem.query ?? '',
268
+ configs: configReports,
269
+ });
270
+ }
271
+
272
+ return {
273
+ workspace: workspaceDir,
274
+ skill_name: skill.name,
275
+ eval_count: evals.length,
276
+ configs,
277
+ runs_per_config: runsPerConfig,
278
+ reports,
279
+ };
280
+ }
281
+
282
+ function parseArgs(argv) {
283
+ const args = {
284
+ skillPath: '',
285
+ evalsPath: '',
286
+ workspaceDir: '',
287
+ provider: 'anthropic',
288
+ model: undefined,
289
+ runsPerConfig: 1,
290
+ maxIter: 30,
291
+ reasoningMode: 'off',
292
+ cwd: process.cwd(),
293
+ configs: ['with_skill', 'without_skill'],
294
+ output: undefined,
295
+ };
296
+
297
+ for (let index = 2; index < argv.length; index += 1) {
298
+ const token = argv[index];
299
+ if (token === '--skill-path' && argv[index + 1]) {
300
+ args.skillPath = argv[++index];
301
+ } else if (token === '--evals' && argv[index + 1]) {
302
+ args.evalsPath = argv[++index];
303
+ } else if (token === '--workspace' && argv[index + 1]) {
304
+ args.workspaceDir = argv[++index];
305
+ } else if (token === '--provider' && argv[index + 1]) {
306
+ args.provider = argv[++index];
307
+ } else if (token === '--model' && argv[index + 1]) {
308
+ args.model = argv[++index];
309
+ } else if (token === '--runs' && argv[index + 1]) {
310
+ args.runsPerConfig = Number(argv[++index]);
311
+ } else if (token === '--max-iter' && argv[index + 1]) {
312
+ args.maxIter = Number(argv[++index]);
313
+ } else if (token === '--reasoning' && argv[index + 1]) {
314
+ args.reasoningMode = argv[++index];
315
+ } else if (token === '--cwd' && argv[index + 1]) {
316
+ args.cwd = argv[++index];
317
+ } else if (token === '--configs' && argv[index + 1]) {
318
+ args.configs = argv[++index]
319
+ .split(',')
320
+ .map((item) => item.trim())
321
+ .filter(Boolean);
322
+ } else if (token === '--output' && argv[index + 1]) {
323
+ args.output = argv[++index];
324
+ }
325
+ }
326
+
327
+ return args;
328
+ }
329
+
330
330
  export async function main(argv = process.argv) {
331
331
  const args = parseArgs(argv);
332
- if (!args.skillPath || !args.evalsPath || !args.workspaceDir) {
333
- console.error('Usage: node scripts/run-eval.js --skill-path <dir> --evals <evals.json> --workspace <dir> [--provider anthropic] [--runs 1]');
334
- process.exit(1);
335
- }
336
-
337
- const report = await runEvalWorkspace(args);
338
- const outputText = `${JSON.stringify(report, null, 2)}\n`;
339
-
340
- if (args.output) {
341
- await writeFile(args.output, outputText, 'utf8');
342
- console.log(`Wrote ${path.resolve(args.output)}`);
343
- } else {
344
- process.stdout.write(outputText);
345
- }
346
- }
347
-
332
+ if (!args.skillPath || !args.evalsPath || !args.workspaceDir) {
333
+ console.error('Usage: node scripts/run-eval.js --skill-path <dir> --evals <evals.json> --workspace <dir> [--provider anthropic] [--runs 1]');
334
+ process.exit(1);
335
+ }
336
+
337
+ const report = await runEvalWorkspace(args);
338
+ const outputText = `${JSON.stringify(report, null, 2)}\n`;
339
+
340
+ if (args.output) {
341
+ await writeFile(args.output, outputText, 'utf8');
342
+ console.log(`Wrote ${path.resolve(args.output)}`);
343
+ } else {
344
+ process.stdout.write(outputText);
345
+ }
346
+ }
347
+
348
348
  const isDirectRun = process.env.KODAX_MODULE_BUNDLE !== 'true'
349
349
  && process.argv[1]
350
- && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
351
-
352
- if (isDirectRun) {
353
- main().catch((error) => {
354
- console.error(error instanceof Error ? error.message : String(error));
355
- process.exit(1);
356
- });
357
- }
350
+ && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
351
+
352
+ if (isDirectRun) {
353
+ main().catch((error) => {
354
+ console.error(error instanceof Error ? error.message : String(error));
355
+ process.exit(1);
356
+ });
357
+ }