@kody-ade/kody-engine 0.4.592 → 0.4.594

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.592",
18
+ version: "0.4.594",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -234,6 +234,32 @@ function parseModelRuntimeConfig(modelSpec, rawConfig) {
234
234
  if (protocol === "openai") out.litellmProvider = "openai";
235
235
  return out;
236
236
  }
237
+ function parseAutomaticModels(raw) {
238
+ if (raw === void 0) return void 0;
239
+ if (!Array.isArray(raw)) throw new Error("kody.config.json: agent.automaticModels must be an array");
240
+ const models = raw.map((entry, index) => {
241
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
242
+ throw new Error(`kody.config.json: agent.automaticModels[${index}] must be an object`);
243
+ }
244
+ const record2 = entry;
245
+ const spec = optionalRuntimeString(record2, "spec");
246
+ const modelName = optionalRuntimeString(record2, "modelName");
247
+ const provider = optionalRuntimeString(record2, "provider");
248
+ if (!spec || !modelName || !provider) {
249
+ throw new Error(`kody.config.json: agent.automaticModels[${index}] requires spec, provider, and modelName`);
250
+ }
251
+ const model = { provider, model: modelName, spec };
252
+ const protocol = optionalRuntimeString(record2, "protocol");
253
+ const baseURL = optionalRuntimeString(record2, "baseURL");
254
+ const apiKeyEnvVar = optionalRuntimeString(record2, "apiKeyEnvVar");
255
+ if (protocol) model.protocol = protocol;
256
+ if (baseURL) model.baseURL = baseURL;
257
+ if (apiKeyEnvVar) model.apiKeyEnvVar = apiKeyEnvVar;
258
+ if (protocol === "openai") model.litellmProvider = "openai";
259
+ return model;
260
+ });
261
+ return models.length > 0 ? models : void 0;
262
+ }
237
263
  function litellmModelGroup(model) {
238
264
  return model.spec?.trim() || model.model;
239
265
  }
@@ -267,6 +293,10 @@ function loadConfig(projectDir = process.cwd()) {
267
293
  if (!github.owner || !github.repo) {
268
294
  throw new Error(`kody.config.json: github.owner and github.repo are required`);
269
295
  }
296
+ const automaticModels = parseAutomaticModels(agent.automaticModels);
297
+ if (agent.model === "automatic" && (!automaticModels || automaticModels.length < 2)) {
298
+ throw new Error("kody.config.json: Automatic requires at least two agent.automaticModels");
299
+ }
270
300
  return {
271
301
  quality: {
272
302
  typecheck: typeof quality.typecheck === "string" ? quality.typecheck : "",
@@ -284,6 +314,7 @@ function loadConfig(projectDir = process.cwd()) {
284
314
  },
285
315
  agent: {
286
316
  model: String(agent.model),
317
+ ...automaticModels ? { automaticModels } : {},
287
318
  ...parsePerImplementation(agent.perImplementation),
288
319
  ...parsePerImplementationReasoningEffort(agent.perImplementationReasoningEffort),
289
320
  ...parseAgentReasoningEffort(agent.reasoningEffort)
@@ -4169,6 +4200,7 @@ async function runAgent(opts) {
4169
4200
  const missingParentWriteGuard = createMissingParentWriteGuard(opts.cwd);
4170
4201
  const outputContractPostWriteHook = opts.outputContract ? createOutputContractPostWriteHook(opts.outputContract) : null;
4171
4202
  const outputContractStopHook = opts.outputContract ? createOutputContractStopHook(opts.outputContract) : null;
4203
+ let finalSafeToReplay = true;
4172
4204
  for (let attempt = 0; ; attempt++) {
4173
4205
  let ndjsonWriteFailed = false;
4174
4206
  let ndjsonWriteError;
@@ -4188,6 +4220,12 @@ async function runAgent(opts) {
4188
4220
  let sawTerminalSuccess = false;
4189
4221
  let sawLoginRequired = false;
4190
4222
  let noWorkSuccess = false;
4223
+ const sdkAbortController = opts.stopOnRateLimit ? new AbortController() : opts.abortController;
4224
+ const forwardOwnerAbort = () => sdkAbortController?.abort(opts.abortController?.signal.reason);
4225
+ if (opts.stopOnRateLimit && opts.abortController) {
4226
+ if (opts.abortController.signal.aborted) forwardOwnerAbort();
4227
+ else opts.abortController.signal.addEventListener("abort", forwardOwnerAbort, { once: true });
4228
+ }
4191
4229
  try {
4192
4230
  const queryOptions = {
4193
4231
  model: opts.litellmUrl ? litellmModelGroup(opts.model) : opts.model.model,
@@ -4346,7 +4384,7 @@ async function runAgent(opts) {
4346
4384
  };
4347
4385
  }
4348
4386
  queryOptions.settingSources = opts.settingSources ?? ["project", "local"];
4349
- if (opts.abortController) queryOptions.abortController = opts.abortController;
4387
+ if (sdkAbortController) queryOptions.abortController = sdkAbortController;
4350
4388
  const stableBinary = ensureStableClaudeBinary();
4351
4389
  if (stableBinary) {
4352
4390
  const sdkBinaryPathOption = ["pathToClaudeCode", "Exec", "utable"].join("");
@@ -4409,6 +4447,22 @@ async function runAgent(opts) {
4409
4447
  `);
4410
4448
  }
4411
4449
  const m = msg;
4450
+ if (opts.stopOnRateLimit && m.type === "system" && m.subtype === "api_retry" && Number(m.error_status) === 429) {
4451
+ outcome = "failed";
4452
+ outcomeKind = "rate_limit";
4453
+ errorMessage2 = "model rate limited (429)";
4454
+ sdkAbortController?.abort(new Error(errorMessage2));
4455
+ if (typeof iterator.return === "function") {
4456
+ try {
4457
+ await Promise.race([
4458
+ iterator.return(void 0).catch(() => void 0),
4459
+ new Promise((resolve24) => setTimeout(resolve24, 1e3).unref())
4460
+ ]);
4461
+ } catch {
4462
+ }
4463
+ }
4464
+ break;
4465
+ }
4412
4466
  if (opts.onProgress) {
4413
4467
  const blocks = m.message?.content ?? [];
4414
4468
  for (const block of blocks) {
@@ -4496,10 +4550,14 @@ async function runAgent(opts) {
4496
4550
  errorMessage2 = e instanceof Error ? e.message : String(e);
4497
4551
  } else {
4498
4552
  outcome = "failed";
4499
- outcomeKind = "model_error";
4553
+ const message = e instanceof Error ? e.message : String(e);
4554
+ outcomeKind = /\b429\b|rate[ _-]?limit|too many requests/i.test(message) ? "rate_limit" : "model_error";
4500
4555
  errorMessage2 = e instanceof Error ? e.message : String(e);
4501
4556
  }
4502
4557
  } finally {
4558
+ if (opts.stopOnRateLimit && opts.abortController) {
4559
+ opts.abortController.signal.removeEventListener("abort", forwardOwnerAbort);
4560
+ }
4503
4561
  try {
4504
4562
  fullLog.end();
4505
4563
  } catch {
@@ -4528,6 +4586,7 @@ async function runAgent(opts) {
4528
4586
  }
4529
4587
  }
4530
4588
  const shouldRetry = outcome === "failed" && attempt < MAX_CONNECTION_RETRIES && !sawMutatingTool && (isTransientConnectionError(errorMessage2) || noWorkSuccess);
4589
+ finalSafeToReplay = !sawMutatingTool;
4531
4590
  if (!shouldRetry) break;
4532
4591
  const delayMs = CONNECTION_RETRY_BASE_MS * 2 ** attempt;
4533
4592
  process.stderr.write(
@@ -4548,6 +4607,7 @@ async function runAgent(opts) {
4548
4607
  return {
4549
4608
  outcome,
4550
4609
  outcomeKind,
4610
+ safeToReplay: finalSafeToReplay,
4551
4611
  finalText,
4552
4612
  ...submittedState ? { submittedState } : {},
4553
4613
  error: errorMessage2,
@@ -22286,17 +22346,18 @@ async function runImplementation(profileName, input) {
22286
22346
  const modelSpec = perImplementationModel ? perImplementationModel : profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
22287
22347
  const profileHasThinkingTokens = typeof profile.claudeCode.maxThinkingTokens === "number" && profile.claudeCode.maxThinkingTokens > 0;
22288
22348
  const reasoningEffort = config.agent.perImplementationReasoningEffort?.[profileName] ?? profile.claudeCode.reasoningEffort ?? (profileHasThinkingTokens ? void 0 : config.agent.reasoningEffort);
22289
- let model;
22349
+ let modelCandidates;
22290
22350
  try {
22291
- model = parseProviderModel(modelSpec);
22351
+ modelCandidates = modelSpec === "automatic" ? config.agent.automaticModels ?? [] : [parseProviderModel(modelSpec)];
22352
+ if (modelCandidates.length === 0) throw new Error("Automatic has no configured models");
22292
22353
  } catch (err) {
22293
22354
  return finishAndEnd({
22294
22355
  exitCode: 99,
22295
22356
  reason: `agent.model invalid: ${err instanceof Error ? err.message : String(err)}`
22296
22357
  });
22297
22358
  }
22298
- let litellm;
22299
- let runtimeModelEnvironment;
22359
+ const model = modelCandidates[0];
22360
+ const activeLiteLLM = /* @__PURE__ */ new Set();
22300
22361
  const ctx = {
22301
22362
  args,
22302
22363
  cwd: input.cwd,
@@ -22393,90 +22454,108 @@ async function runImplementation(profileName, input) {
22393
22454
  const syntheticPath = ctx.data.syntheticPluginPath;
22394
22455
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
22395
22456
  const agents = loadSubagents(profile);
22396
- if (runtimeModelEnvironment === void 0) {
22397
- runtimeModelEnvironment = {};
22398
- if (needsLitellmProxy(model)) {
22399
- const resolved2 = await resolveRuntimeModelEnvironment(model, ctx);
22457
+ let finalResult;
22458
+ for (let candidateIndex = 0; candidateIndex < modelCandidates.length; candidateIndex++) {
22459
+ const candidate = modelCandidates[candidateIndex];
22460
+ let runtimeModelEnvironment = {};
22461
+ if (needsLitellmProxy(candidate)) {
22462
+ const resolved2 = await resolveRuntimeModelEnvironment(candidate, ctx);
22400
22463
  runtimeModelEnvironment = resolved2.environment;
22401
22464
  for (const warning of resolved2.warnings) process.stderr.write(`\u26A0 ${warning}
22402
22465
  `);
22403
22466
  }
22404
- }
22405
- if (litellm === void 0) {
22467
+ let lm;
22406
22468
  try {
22407
- litellm = await startLitellmIfNeeded(model, input.cwd, void 0, runtimeModelEnvironment);
22469
+ lm = await startLitellmIfNeeded(candidate, input.cwd, void 0, runtimeModelEnvironment);
22470
+ if (lm) activeLiteLLM.add(lm);
22408
22471
  } catch (err) {
22409
22472
  throw new Error(`litellm startup failed: ${err instanceof Error ? err.message : String(err)}`);
22410
22473
  }
22474
+ ctx.data.jobModelProvider = candidate.provider;
22475
+ ctx.data.jobModelName = candidate.model;
22476
+ const result = await runAgent({
22477
+ prompt,
22478
+ model: candidate,
22479
+ cwd: input.cwd,
22480
+ environment: {
22481
+ ...ctx.data.capabilityEnvironment && typeof ctx.data.capabilityEnvironment === "object" && !Array.isArray(ctx.data.capabilityEnvironment) ? ctx.data.capabilityEnvironment : {},
22482
+ ...runtimeModelEnvironment
22483
+ },
22484
+ litellmUrl: lm?.url ?? null,
22485
+ // On a connection drop mid-run, restart the (possibly crashed) proxy
22486
+ // before the agent retries. No-op for direct-Anthropic runs (lm null).
22487
+ ensureBackend: lm ? () => lm.ensureHealthy().then(() => void 0) : void 0,
22488
+ // Pure liveness probe so the agent can spot a hollow "success" (proxy
22489
+ // crashed mid-request, SDK still reported success). No-op when lm null.
22490
+ isBackendHealthy: lm ? () => lm.isHealthy() : void 0,
22491
+ verbose: input.verbose,
22492
+ quiet: input.quiet,
22493
+ abortController: input.abortController,
22494
+ stopOnRateLimit: candidateIndex < modelCandidates.length - 1,
22495
+ deadlineAtMs: input.deadlineAtMs,
22496
+ ndjsonDir,
22497
+ additionalDirectories: agentTaskArtifacts ? [agentTaskArtifacts.absDir] : void 0,
22498
+ allowedToolsOverride: profile.claudeCode.tools,
22499
+ disallowedToolsOverride: profile.claudeCode.disallowedTools,
22500
+ permissionModeOverride: profile.claudeCode.permissionMode,
22501
+ mcpServers: profile.claudeCode.mcpServers.length > 0 ? profile.claudeCode.mcpServers : void 0,
22502
+ pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
22503
+ agents,
22504
+ maxTurns: profile.claudeCode.maxTurns,
22505
+ reasoningEffort,
22506
+ maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
22507
+ maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
22508
+ // DISCIPLINE leads so the stable, role-agnostic block sits at the front
22509
+ // of the cacheable system-prompt prefix; profile/task appends follow.
22510
+ systemPromptAppend: [
22511
+ DISCIPLINE,
22512
+ agentIdentityBlock,
22513
+ jobRefBlock,
22514
+ jobWhyBlock,
22515
+ profile.claudeCode.systemPromptAppend,
22516
+ agentTaskArtifacts?.promptAddendum
22517
+ ].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
22518
+ cacheable: profile.claudeCode.cacheable,
22519
+ enableVerifyTool: profile.claudeCode.enableVerifyTool,
22520
+ enableSubmitTool: profile.claudeCode.enableSubmitTool,
22521
+ // Locked-toolbox capability mode: `loadJobFromFile` flips `ctx.data.capabilityTools`
22522
+ // when a capability declares `tools` in profile.json. The executor doesn't need
22523
+ // to know the palette — it just forwards the flag so agent.ts can spin
22524
+ // up the in-process `kody-capability` MCP server with the right context.
22525
+ enableCapabilityTool: Array.isArray(ctx.data.capabilityTools) && ctx.data.capabilityTools.length > 0,
22526
+ capabilityOperatorMention: typeof ctx.data.capabilityOperatorMention === "string" ? ctx.data.capabilityOperatorMention : void 0,
22527
+ // Stamp the running capability's slug onto recommendations so the dashboard
22528
+ // keys trust per capability (not per agent). `jobSlug` is set by loadJobFromFile.
22529
+ capabilitySlug: typeof ctx.data.jobSlug === "string" ? ctx.data.jobSlug : void 0,
22530
+ capabilityDefaultBranch: config.git.defaultBranch,
22531
+ // owner/repo from kody.config.json; envelope falls back to GITHUB_REPOSITORY
22532
+ // for tester repos that don't set config.github (the file isn't always
22533
+ // checked in). Either way, capabilityMcp needs "owner/name" to hit the compare API.
22534
+ capabilityRepoSlug: config.github?.owner && config.github?.repo ? `${config.github.owner}/${config.github.repo}` : process.env.GITHUB_REPOSITORY?.trim() || void 0,
22535
+ verifyToolMaxAttempts: profile.claudeCode.verifyAttempts ?? null,
22536
+ verifyConfig: profile.claudeCode.enableVerifyTool ? config : void 0,
22537
+ implementationName: profileName,
22538
+ settingSources: profile.claudeCode.settingSources,
22539
+ outputContract: typeof ctx.data.capabilityOutputPath === "string" && ctx.data.capabilityOutputSchema && typeof ctx.data.capabilityOutputSchema === "object" && !Array.isArray(ctx.data.capabilityOutputSchema) ? {
22540
+ path: ctx.data.capabilityOutputPath,
22541
+ schema: ctx.data.capabilityOutputSchema
22542
+ } : void 0
22543
+ });
22544
+ finalResult = result;
22545
+ const next = modelCandidates[candidateIndex + 1];
22546
+ if (!next || result.outcome !== "failed" || result.outcomeKind !== "rate_limit" || result.safeToReplay !== true) {
22547
+ return result;
22548
+ }
22549
+ process.stderr.write(
22550
+ `[kody agent] ${candidate.spec ?? candidate.model} is rate limited; continuing with ${next.spec ?? next.model}
22551
+ `
22552
+ );
22553
+ if (lm) {
22554
+ lm.kill();
22555
+ activeLiteLLM.delete(lm);
22556
+ }
22411
22557
  }
22412
- const lm = litellm;
22413
- return runAgent({
22414
- prompt,
22415
- model,
22416
- cwd: input.cwd,
22417
- environment: {
22418
- ...ctx.data.capabilityEnvironment && typeof ctx.data.capabilityEnvironment === "object" && !Array.isArray(ctx.data.capabilityEnvironment) ? ctx.data.capabilityEnvironment : {},
22419
- ...runtimeModelEnvironment
22420
- },
22421
- litellmUrl: lm?.url ?? null,
22422
- // On a connection drop mid-run, restart the (possibly crashed) proxy
22423
- // before the agent retries. No-op for direct-Anthropic runs (lm null).
22424
- ensureBackend: lm ? () => lm.ensureHealthy().then(() => void 0) : void 0,
22425
- // Pure liveness probe so the agent can spot a hollow "success" (proxy
22426
- // crashed mid-request, SDK still reported success). No-op when lm null.
22427
- isBackendHealthy: lm ? () => lm.isHealthy() : void 0,
22428
- verbose: input.verbose,
22429
- quiet: input.quiet,
22430
- abortController: input.abortController,
22431
- deadlineAtMs: input.deadlineAtMs,
22432
- ndjsonDir,
22433
- additionalDirectories: agentTaskArtifacts ? [agentTaskArtifacts.absDir] : void 0,
22434
- allowedToolsOverride: profile.claudeCode.tools,
22435
- disallowedToolsOverride: profile.claudeCode.disallowedTools,
22436
- permissionModeOverride: profile.claudeCode.permissionMode,
22437
- mcpServers: profile.claudeCode.mcpServers.length > 0 ? profile.claudeCode.mcpServers : void 0,
22438
- pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
22439
- agents,
22440
- maxTurns: profile.claudeCode.maxTurns,
22441
- reasoningEffort,
22442
- maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
22443
- maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
22444
- // DISCIPLINE leads so the stable, role-agnostic block sits at the front
22445
- // of the cacheable system-prompt prefix; profile/task appends follow.
22446
- systemPromptAppend: [
22447
- DISCIPLINE,
22448
- agentIdentityBlock,
22449
- jobRefBlock,
22450
- jobWhyBlock,
22451
- profile.claudeCode.systemPromptAppend,
22452
- agentTaskArtifacts?.promptAddendum
22453
- ].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
22454
- cacheable: profile.claudeCode.cacheable,
22455
- enableVerifyTool: profile.claudeCode.enableVerifyTool,
22456
- enableSubmitTool: profile.claudeCode.enableSubmitTool,
22457
- // Locked-toolbox capability mode: `loadJobFromFile` flips `ctx.data.capabilityTools`
22458
- // when a capability declares `tools` in profile.json. The executor doesn't need
22459
- // to know the palette — it just forwards the flag so agent.ts can spin
22460
- // up the in-process `kody-capability` MCP server with the right context.
22461
- enableCapabilityTool: Array.isArray(ctx.data.capabilityTools) && ctx.data.capabilityTools.length > 0,
22462
- capabilityOperatorMention: typeof ctx.data.capabilityOperatorMention === "string" ? ctx.data.capabilityOperatorMention : void 0,
22463
- // Stamp the running capability's slug onto recommendations so the dashboard
22464
- // keys trust per capability (not per agent). `jobSlug` is set by loadJobFromFile.
22465
- capabilitySlug: typeof ctx.data.jobSlug === "string" ? ctx.data.jobSlug : void 0,
22466
- capabilityDefaultBranch: config.git.defaultBranch,
22467
- // owner/repo from kody.config.json; envelope falls back to GITHUB_REPOSITORY
22468
- // for tester repos that don't set config.github (the file isn't always
22469
- // checked in). Either way, capabilityMcp needs "owner/name" to hit the compare API.
22470
- capabilityRepoSlug: config.github?.owner && config.github?.repo ? `${config.github.owner}/${config.github.repo}` : process.env.GITHUB_REPOSITORY?.trim() || void 0,
22471
- verifyToolMaxAttempts: profile.claudeCode.verifyAttempts ?? null,
22472
- verifyConfig: profile.claudeCode.enableVerifyTool ? config : void 0,
22473
- implementationName: profileName,
22474
- settingSources: profile.claudeCode.settingSources,
22475
- outputContract: typeof ctx.data.capabilityOutputPath === "string" && ctx.data.capabilityOutputSchema && typeof ctx.data.capabilityOutputSchema === "object" && !Array.isArray(ctx.data.capabilityOutputSchema) ? {
22476
- path: ctx.data.capabilityOutputPath,
22477
- schema: ctx.data.capabilityOutputSchema
22478
- } : void 0
22479
- });
22558
+ return finalResult;
22480
22559
  };
22481
22560
  ctx.data.__invokeAgent = invokeAgent;
22482
22561
  const cc = profile.claudeCode;
@@ -22708,7 +22787,7 @@ async function runImplementation(profileName, input) {
22708
22787
  }
22709
22788
  }
22710
22789
  try {
22711
- litellm?.kill();
22790
+ for (const proxy of activeLiteLLM) proxy.kill();
22712
22791
  } catch {
22713
22792
  }
22714
22793
  }
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.592",
3
+ "version": "0.4.594",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,30 +12,6 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
- "scripts": {
16
- "kody:run": "tsx bin/kody.ts",
17
- "serve": "tsx bin/kody.ts serve",
18
- "serve:vscode": "tsx bin/kody.ts serve vscode",
19
- "serve:claude": "tsx bin/kody.ts serve claude",
20
- "clean:dist": "node scripts/clean-dist.cjs",
21
- "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
22
- "check:modularity": "tsx scripts/check-script-modularity.ts",
23
- "pretest": "pnpm check:modularity",
24
- "test": "vitest run tests/unit tests/int --coverage",
25
- "posttest": "tsx scripts/check-coverage-floor.ts",
26
- "test:smoke": "vitest run tests/smoke --no-coverage",
27
- "test:e2e": "vitest run tests/e2e --no-coverage",
28
- "verify:live-release": "tsx scripts/live-release-gate.ts",
29
- "test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
30
- "test:all": "vitest run tests --no-coverage",
31
- "typecheck": "tsc --noEmit",
32
- "lint": "biome check",
33
- "lint:fix": "biome check --write",
34
- "format": "biome format --write",
35
- "verify:package": "node scripts/verify-package-tarball.cjs",
36
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
37
- "prepublishOnly": "pnpm typecheck && pnpm test:runtime-services && pnpm build && pnpm verify:package"
38
- },
39
15
  "dependencies": {
40
16
  "@actions/cache": "^6.0.0",
41
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -62,5 +38,28 @@
62
38
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
63
39
  },
64
40
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
65
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
66
- }
41
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
42
+ "scripts": {
43
+ "kody:run": "tsx bin/kody.ts",
44
+ "serve": "tsx bin/kody.ts serve",
45
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
46
+ "serve:claude": "tsx bin/kody.ts serve claude",
47
+ "clean:dist": "node scripts/clean-dist.cjs",
48
+ "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
49
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
50
+ "pretest": "pnpm check:modularity",
51
+ "test": "vitest run tests/unit tests/int --coverage",
52
+ "posttest": "tsx scripts/check-coverage-floor.ts",
53
+ "test:smoke": "vitest run tests/smoke --no-coverage",
54
+ "test:e2e": "vitest run tests/e2e --no-coverage",
55
+ "verify:live-release": "tsx scripts/live-release-gate.ts",
56
+ "test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
57
+ "test:all": "vitest run tests --no-coverage",
58
+ "typecheck": "tsc --noEmit",
59
+ "lint": "biome check",
60
+ "lint:fix": "biome check --write",
61
+ "format": "biome format --write",
62
+ "verify:package": "node scripts/verify-package-tarball.cjs",
63
+ "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
64
+ }
65
+ }