@kody-ade/kody-engine 0.2.11 → 0.2.12

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/README.md CHANGED
@@ -13,12 +13,12 @@
13
13
  │ kody2 CLI (@kody-ade/kody-engine) │
14
14
  │ bin/kody2.ts — parses argv │
15
15
  │ src/executor.ts — runs one profile │
16
- │ src/executables/build/profile.json
16
+ │ src/executables/<name>/profile.json
17
17
  │ src/scripts/*.ts — named hook catalog │
18
18
  └─────────────────────────────────────────────┘
19
19
  ```
20
20
 
21
- `run`/`fix`/`fix-ci`/`resolve` are four modes of the same `build` executable, selected by `args.mode` via `runWhen` on preflight script entries. Executor knows nothing about any specific mode.
21
+ Every top-level command is its own auto-discovered executable (`run`, `fix`, `fix-ci`, `resolve`, `review`, `plan`, `orchestrator`, `release`, `watch-*`, `init`). The router has no hardcoded command switch beyond `ci`/`help`/`version` drop a new `src/executables/<name>/` directory with a `profile.json` + `prompt.md` and `kody2 <name>` starts working. The executor knows nothing about any specific command.
22
22
 
23
23
  ## Install in a consumer repo
24
24
 
@@ -31,13 +31,14 @@
31
31
  ## Commands
32
32
 
33
33
  ```
34
- kody2 run --issue <N> # implement an issue
34
+ kody2 run --issue <N> # implement an issue
35
35
  kody2 fix --pr <N> [--feedback ...] # apply PR review feedback
36
36
  kody2 fix-ci --pr <N> [--run-id <ID>] # fix failing CI
37
37
  kody2 resolve --pr <N> # merge default branch in, resolve conflicts
38
+ kody2 review --pr <N> # read-only structured PR review
38
39
  kody2 ci --issue <N> # CI preflight + run
39
40
  ```
40
41
 
41
42
  ## Profiles
42
43
 
43
- A profile is declarative JSON + an adjacent prompt. See `src/executables/build/profile.json`. Adding a new role = new profile + new prompt + registering any new scripts under `src/scripts/`. No executor changes.
44
+ A profile is declarative JSON + an adjacent `prompt.md`. See any directory under `src/executables/` for examples. Adding a new command = new directory + profile + prompt + registering any new scripts under `src/scripts/`. No executor, entry, or dispatch changes.
package/dist/bin/kody2.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.2.11",
6
+ version: "0.2.12",
7
7
  description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -17,7 +17,7 @@ var package_default = {
17
17
  ],
18
18
  scripts: {
19
19
  kody2: "tsx bin/kody2.ts",
20
- build: `tsup && node -e "require('fs').cpSync('src/executables', 'dist/executables', { recursive: true })"`,
20
+ build: `tsup && node -e "const fs=require('fs');fs.rmSync('dist/executables',{recursive:true,force:true});fs.cpSync('src/executables','dist/executables',{recursive:true})"`,
21
21
  test: "vitest run tests/unit tests/int --no-coverage",
22
22
  "test:e2e": "vitest run tests/e2e --no-coverage",
23
23
  "test:all": "vitest run tests --no-coverage",
@@ -49,6 +49,15 @@ var package_default = {
49
49
  bugs: "https://github.com/aharonyaircohen/kody-engine/issues"
50
50
  };
51
51
 
52
+ // src/executor.ts
53
+ import * as fs13 from "fs";
54
+ import * as path11 from "path";
55
+
56
+ // src/agent.ts
57
+ import * as fs2 from "fs";
58
+ import * as path2 from "path";
59
+ import { query } from "@anthropic-ai/claude-agent-sdk";
60
+
52
61
  // src/config.ts
53
62
  import * as fs from "fs";
54
63
  import * as path from "path";
@@ -151,15 +160,6 @@ function getAnthropicApiKeyOrDummy() {
151
160
  return process.env.ANTHROPIC_API_KEY || `sk-ant-api03-${"0".repeat(64)}`;
152
161
  }
153
162
 
154
- // src/executor.ts
155
- import * as fs13 from "fs";
156
- import * as path11 from "path";
157
-
158
- // src/agent.ts
159
- import * as fs2 from "fs";
160
- import * as path2 from "path";
161
- import { query } from "@anthropic-ai/claude-agent-sdk";
162
-
163
163
  // src/format.ts
164
164
  function renderEvent(msg, opts = {}) {
165
165
  if (opts.quiet) {
@@ -919,13 +919,14 @@ function hasCommitsAhead(branch, defaultBranch, cwd) {
919
919
  }
920
920
 
921
921
  // src/scripts/commitAndPush.ts
922
- var commitAndPush2 = async (ctx) => {
922
+ var commitAndPush2 = async (ctx, profile) => {
923
923
  const branch = ctx.data.branch;
924
924
  if (!branch) {
925
925
  ctx.data.commitResult = { committed: false, pushed: false };
926
926
  return;
927
927
  }
928
- if (ctx.args.mode === "resolve") {
928
+ const kind = profile.name;
929
+ if (kind === "resolve") {
929
930
  try {
930
931
  execFileSync4("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
931
932
  } catch {
@@ -937,7 +938,7 @@ var commitAndPush2 = async (ctx) => {
937
938
  `);
938
939
  }
939
940
  }
940
- const fallbackMsg = defaultCommitMessage(ctx.args.mode, ctx.data);
941
+ const fallbackMsg = defaultCommitMessage(kind, ctx.data);
941
942
  const message = ctx.data.commitMessage || fallbackMsg;
942
943
  try {
943
944
  const result = commitAndPush(branch, message, ctx.cwd);
@@ -1724,11 +1725,11 @@ function parseGenericFlags(argv) {
1724
1725
  }
1725
1726
  const key = arg.slice(2);
1726
1727
  const next = argv[i + 1];
1727
- if (next !== void 0 && !next.startsWith("--")) {
1728
- args[key] = next;
1729
- i++;
1730
- } else {
1731
- args[key] = true;
1728
+ const value = next !== void 0 && !next.startsWith("--") ? (i++, next) : true;
1729
+ args[key] = value;
1730
+ if (key.includes("-")) {
1731
+ const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
1732
+ if (camel !== key && args[camel] === void 0) args[camel] = value;
1732
1733
  }
1733
1734
  }
1734
1735
  if (positional.length > 0) args._ = positional;
@@ -2949,10 +2950,10 @@ var watchStalePrsFlow = async (ctx) => {
2949
2950
 
2950
2951
  // src/scripts/writeRunSummary.ts
2951
2952
  import * as fs12 from "fs";
2952
- var writeRunSummary = async (ctx) => {
2953
+ var writeRunSummary = async (ctx, profile) => {
2953
2954
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
2954
2955
  if (!summaryPath) return;
2955
- const mode = ctx.args.mode;
2956
+ const executable = profile.name;
2956
2957
  const issue = ctx.args.issue;
2957
2958
  const pr = ctx.args.pr;
2958
2959
  const target = issue ? `issue #${issue}` : pr ? `PR #${pr}` : "(unknown)";
@@ -2961,9 +2962,9 @@ var writeRunSummary = async (ctx) => {
2961
2962
  const reason = ctx.output.reason;
2962
2963
  const status = exitCode === 0 ? "\u2705 success" : exitCode === 3 ? "\u23ED\uFE0F no-op" : "\u26A0\uFE0F failed";
2963
2964
  const lines = [];
2964
- lines.push(`## kody2 run \u2014 ${status}`);
2965
+ lines.push(`## kody2 ${executable} \u2014 ${status}`);
2965
2966
  lines.push("");
2966
- lines.push(`- **Mode:** \`${mode ?? "?"}\``);
2967
+ lines.push(`- **Executable:** \`${executable}\``);
2967
2968
  lines.push(`- **Target:** ${target}`);
2968
2969
  if (prUrl) lines.push(`- **PR:** ${prUrl}`);
2969
2970
  lines.push(`- **Exit code:** ${exitCode}`);
@@ -3068,7 +3069,24 @@ async function runExecutable(profileName, input) {
3068
3069
  if (firstFail) {
3069
3070
  return finish({ exitCode: 99, reason: `required CLI tool check failed: ${firstFail.error}` });
3070
3071
  }
3071
- const modelSpec = profile.claudeCode.model === "inherit" ? input.config.agent.model : profile.claudeCode.model;
3072
+ let config;
3073
+ if (input.config) {
3074
+ config = input.config;
3075
+ } else if (input.skipConfig) {
3076
+ config = {
3077
+ quality: { typecheck: "", lint: "", testUnit: "" },
3078
+ git: { defaultBranch: "main" },
3079
+ github: { owner: "", repo: "" },
3080
+ agent: { model: "claude/claude-haiku-4-5-20251001" }
3081
+ };
3082
+ } else {
3083
+ try {
3084
+ config = loadConfig(input.cwd);
3085
+ } catch (err) {
3086
+ return finish({ exitCode: 99, reason: `config error: ${err instanceof Error ? err.message : String(err)}` });
3087
+ }
3088
+ }
3089
+ const modelSpec = profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
3072
3090
  let model;
3073
3091
  try {
3074
3092
  model = parseProviderModel(modelSpec);
@@ -3087,7 +3105,7 @@ async function runExecutable(profileName, input) {
3087
3105
  const ctx = {
3088
3106
  args,
3089
3107
  cwd: input.cwd,
3090
- config: input.config,
3108
+ config,
3091
3109
  verbose: input.verbose,
3092
3110
  quiet: input.quiet,
3093
3111
  data: {},
@@ -3168,6 +3186,20 @@ function resolveProfilePath(profileName) {
3168
3186
  }
3169
3187
  function validateInputs(specs, raw) {
3170
3188
  const out = {};
3189
+ const allowedKeys = /* @__PURE__ */ new Set(["_", "cwd", "verbose", "quiet"]);
3190
+ for (const spec of specs) {
3191
+ const flagKey = spec.flag.replace(/^--/, "");
3192
+ allowedKeys.add(spec.name);
3193
+ allowedKeys.add(flagKey);
3194
+ if (flagKey.includes("-")) {
3195
+ allowedKeys.add(flagKey.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase()));
3196
+ }
3197
+ }
3198
+ for (const key of Object.keys(raw)) {
3199
+ if (!allowedKeys.has(key)) {
3200
+ throw new Error(`unknown arg: --${key}`);
3201
+ }
3202
+ }
3171
3203
  for (const spec of specs) {
3172
3204
  const v = raw[spec.name];
3173
3205
  if (v === void 0 || v === null) continue;
@@ -3250,8 +3282,8 @@ function autoDispatch(opts) {
3250
3282
  const explicit = opts?.explicit;
3251
3283
  if (explicit?.issueNumber && explicit.issueNumber > 0) {
3252
3284
  return {
3253
- executable: "build",
3254
- cliArgs: { mode: "run", issue: explicit.issueNumber },
3285
+ executable: "run",
3286
+ cliArgs: { issue: explicit.issueNumber },
3255
3287
  target: explicit.issueNumber
3256
3288
  };
3257
3289
  }
@@ -3267,7 +3299,7 @@ function autoDispatch(opts) {
3267
3299
  if (eventName === "workflow_dispatch") {
3268
3300
  const n = parseInt(String(event.inputs?.issue_number ?? ""), 10);
3269
3301
  if (!Number.isNaN(n) && n > 0) {
3270
- return { executable: "build", cliArgs: { mode: "run", issue: n }, target: n };
3302
+ return { executable: "run", cliArgs: { issue: n }, target: n };
3271
3303
  }
3272
3304
  return null;
3273
3305
  }
@@ -3279,35 +3311,35 @@ function autoDispatch(opts) {
3279
3311
  const afterTag = extractAfterTag(body);
3280
3312
  if (isPr) {
3281
3313
  if (/\bfix-ci\b/.test(afterTag)) {
3282
- return { executable: "build", cliArgs: { mode: "fix-ci", pr: targetNum }, target: targetNum };
3314
+ return { executable: "fix-ci", cliArgs: { pr: targetNum }, target: targetNum };
3283
3315
  }
3284
3316
  if (/\bresolve\b/.test(afterTag)) {
3285
- return { executable: "build", cliArgs: { mode: "resolve", pr: targetNum }, target: targetNum };
3317
+ return { executable: "resolve", cliArgs: { pr: targetNum }, target: targetNum };
3318
+ }
3319
+ if (/\breview\b/.test(afterTag)) {
3320
+ return { executable: "review", cliArgs: { pr: targetNum }, target: targetNum };
3286
3321
  }
3287
3322
  const feedback = extractFeedback(afterTag);
3288
3323
  return {
3289
- executable: "build",
3290
- cliArgs: { mode: "fix", pr: targetNum, ...feedback ? { feedback } : {} },
3324
+ executable: "fix",
3325
+ cliArgs: { pr: targetNum, ...feedback ? { feedback } : {} },
3291
3326
  target: targetNum
3292
3327
  };
3293
3328
  }
3294
3329
  const sub = extractSubcommand(afterTag);
3295
- const defaultExec = opts?.config?.defaultExecutable ?? "build";
3330
+ const defaultExec = opts?.config?.defaultExecutable ?? "run";
3296
3331
  if (!sub) {
3297
3332
  return asDispatch(defaultExec, targetNum);
3298
3333
  }
3299
- if (sub === "build") {
3300
- return { executable: "build", cliArgs: { mode: "run", issue: targetNum }, target: targetNum };
3301
- }
3302
3334
  if (sub === "orchestrate" || sub === "orchestrator") {
3303
3335
  return { executable: "orchestrator", cliArgs: { issue: targetNum }, target: targetNum };
3304
3336
  }
3337
+ if (sub === "build") {
3338
+ return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
3339
+ }
3305
3340
  return asDispatch(sub, targetNum);
3306
3341
  }
3307
3342
  function asDispatch(executable, target) {
3308
- if (executable === "build") {
3309
- return { executable, cliArgs: { mode: "run", issue: target }, target };
3310
- }
3311
3343
  return { executable, cliArgs: { issue: target }, target };
3312
3344
  }
3313
3345
  function extractAfterTag(body) {
@@ -3537,8 +3569,8 @@ ${CI_HELP}`);
3537
3569
  return 64;
3538
3570
  }
3539
3571
  const dispatch = autoFallback ?? {
3540
- executable: "build",
3541
- cliArgs: { mode: "run", issue: args.issueNumber },
3572
+ executable: "run",
3573
+ cliArgs: { issue: args.issueNumber },
3542
3574
  target: args.issueNumber
3543
3575
  };
3544
3576
  const issueNumber = dispatch.target;
@@ -3610,16 +3642,19 @@ ${CI_HELP}`);
3610
3642
  var HELP_TEXT = `kody2 \u2014 single-session autonomous engineer
3611
3643
 
3612
3644
  Usage:
3613
- kody2 run --issue <N> [--cwd <path>] [--verbose|--quiet] [--dry-run]
3614
- kody2 ci --issue <N> [preflight flags \u2014 see: kody2 ci --help]
3645
+ kody2 run --issue <N> [--cwd <path>] [--verbose|--quiet]
3615
3646
  kody2 fix --pr <N> [--feedback "..."] [--cwd <path>] [--verbose|--quiet]
3616
3647
  kody2 fix-ci --pr <N> [--run-id <ID>] [--cwd <path>] [--verbose|--quiet]
3617
3648
  kody2 resolve --pr <N> [--cwd <path>] [--verbose|--quiet]
3649
+ kody2 review --pr <N> [--cwd <path>] [--verbose|--quiet]
3650
+ kody2 <other> [--cwd <path>] [--verbose|--quiet]
3651
+ kody2 ci --issue <N> [preflight flags \u2014 see: kody2 ci --help]
3618
3652
  kody2 help
3619
3653
  kody2 version
3620
3654
 
3621
- All commands dispatch to the Build executable with a specific mode. The
3622
- executable is defined by \`src/executables/build/profile.json\`.
3655
+ Each top-level command (run, fix, fix-ci, resolve, review, \u2026) is a discovered
3656
+ executable under \`src/executables/<name>/profile.json\`. Drop in a new
3657
+ directory to add a new command.
3623
3658
 
3624
3659
  Exit codes:
3625
3660
  0 success (PR opened, verify passed \u2014 or resolve produced a merge commit)
@@ -3640,11 +3675,6 @@ function parseArgs(argv) {
3640
3675
  if (cmd === "ci") {
3641
3676
  return { ...result, command: "ci", ciArgv: argv.slice(1) };
3642
3677
  }
3643
- if (cmd === "run" || cmd === "fix" || cmd === "fix-ci" || cmd === "resolve") {
3644
- result.command = cmd;
3645
- parseCommandArgs(cmd, argv.slice(1), result);
3646
- return result;
3647
- }
3648
3678
  if (hasExecutable(cmd)) {
3649
3679
  result.command = "__executable__";
3650
3680
  result.executableName = cmd;
@@ -3654,38 +3684,11 @@ function parseArgs(argv) {
3654
3684
  if (result.cliArgs.quiet === true) result.quiet = true;
3655
3685
  return result;
3656
3686
  }
3657
- const discovered = listExecutables().map((e) => e.name).filter((n) => n !== "build");
3658
- const available = ["run", "fix", "fix-ci", "resolve", "ci", "help", "version", ...discovered];
3687
+ const discovered = listExecutables().map((e) => e.name);
3688
+ const available = ["ci", "help", "version", ...discovered];
3659
3689
  result.errors.push(`unknown command: ${cmd} (available: ${available.join(", ")})`);
3660
3690
  return result;
3661
3691
  }
3662
- function parseCommandArgs(cmd, rest, result) {
3663
- for (let i = 0; i < rest.length; i++) {
3664
- const arg = rest[i];
3665
- if (arg === "--issue") {
3666
- const n = parseInt(rest[++i] ?? "", 10);
3667
- if (Number.isNaN(n) || n <= 0) result.errors.push("--issue requires a positive integer");
3668
- else result.issueNumber = n;
3669
- } else if (arg === "--pr") {
3670
- const n = parseInt(rest[++i] ?? "", 10);
3671
- if (Number.isNaN(n) || n <= 0) result.errors.push("--pr requires a positive integer");
3672
- else result.prNumber = n;
3673
- } else if (arg === "--feedback") {
3674
- result.feedback = rest[++i];
3675
- } else if (arg === "--run-id") {
3676
- result.runId = rest[++i];
3677
- } else if (arg === "--cwd") {
3678
- result.cwd = rest[++i];
3679
- } else if (arg === "--verbose") result.verbose = true;
3680
- else if (arg === "--quiet") result.quiet = true;
3681
- else if (arg === "--dry-run") result.dryRun = true;
3682
- else result.errors.push(`unknown arg: ${arg}`);
3683
- }
3684
- if (cmd === "run" && !result.issueNumber) result.errors.push("--issue <N> is required for run");
3685
- if (cmd === "fix" && !result.prNumber) result.errors.push("--pr <N> is required for fix");
3686
- if (cmd === "fix-ci" && !result.prNumber) result.errors.push("--pr <N> is required for fix-ci");
3687
- if (cmd === "resolve" && !result.prNumber) result.errors.push("--pr <N> is required for resolve");
3688
- }
3689
3692
  async function main(argv = process.argv.slice(2)) {
3690
3693
  const args = parseArgs(argv);
3691
3694
  if (args.errors.length > 0) {
@@ -3718,69 +3721,27 @@ ${HELP_TEXT}`);
3718
3721
  }
3719
3722
  const cwd = args.cwd ?? process.cwd();
3720
3723
  const configlessCommands = /* @__PURE__ */ new Set(["init"]);
3721
- const needsConfig = !(args.command === "__executable__" && configlessCommands.has(args.executableName ?? ""));
3722
- let config;
3723
- if (needsConfig) {
3724
- try {
3725
- config = loadConfig(cwd);
3726
- } catch (err) {
3727
- const msg = err instanceof Error ? err.message : String(err);
3728
- process.stderr.write(`[kody2] config error: ${msg}
3729
- `);
3730
- process.stdout.write(`PR_URL=FAILED: config error: ${msg}
3731
- `);
3732
- return 99;
3733
- }
3734
- } else {
3735
- config = {
3736
- quality: { typecheck: "", lint: "", testUnit: "" },
3737
- git: { defaultBranch: "main" },
3738
- github: { owner: "", repo: "" },
3739
- agent: { model: "claude/claude-haiku-4-5-20251001" }
3740
- };
3741
- }
3742
- if (args.command === "__executable__") {
3743
- try {
3744
- const result = await runExecutable(args.executableName, {
3745
- cliArgs: args.cliArgs ?? {},
3746
- cwd,
3747
- config,
3748
- verbose: args.verbose,
3749
- quiet: args.quiet
3750
- });
3751
- return result.exitCode;
3752
- } catch (err) {
3753
- const msg = err instanceof Error ? err.message : String(err);
3754
- process.stderr.write(`[kody2] ${args.executableName} crashed: ${msg}
3755
- `);
3756
- if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
3757
- `);
3758
- process.stdout.write(`PR_URL=FAILED: ${args.executableName} crashed: ${msg}
3759
- `);
3760
- return 99;
3761
- }
3762
- }
3763
- const cliArgs = { mode: args.command };
3764
- if (args.issueNumber !== void 0) cliArgs.issue = args.issueNumber;
3765
- if (args.prNumber !== void 0) cliArgs.pr = args.prNumber;
3766
- if (args.feedback !== void 0) cliArgs.feedback = args.feedback;
3767
- if (args.runId !== void 0) cliArgs.runId = args.runId;
3724
+ const skipConfig = configlessCommands.has(args.executableName ?? "");
3768
3725
  try {
3769
- const result = await runExecutable("build", {
3770
- cliArgs,
3726
+ const result = await runExecutable(args.executableName, {
3727
+ cliArgs: args.cliArgs ?? {},
3771
3728
  cwd,
3772
- config,
3729
+ skipConfig,
3773
3730
  verbose: args.verbose,
3774
3731
  quiet: args.quiet
3775
3732
  });
3733
+ if (result.exitCode !== 0 && result.reason) {
3734
+ process.stderr.write(`error: ${result.reason}
3735
+ `);
3736
+ }
3776
3737
  return result.exitCode;
3777
3738
  } catch (err) {
3778
3739
  const msg = err instanceof Error ? err.message : String(err);
3779
- process.stderr.write(`[kody2] wrapper crashed: ${msg}
3740
+ process.stderr.write(`[kody2] ${args.executableName} crashed: ${msg}
3780
3741
  `);
3781
3742
  if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
3782
3743
  `);
3783
- process.stdout.write(`PR_URL=FAILED: wrapper crashed: ${msg}
3744
+ process.stdout.write(`PR_URL=FAILED: ${args.executableName} crashed: ${msg}
3784
3745
  `);
3785
3746
  return 99;
3786
3747
  }
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "fix",
3
+ "describe": "Apply review feedback to an existing PR branch.",
4
+
5
+ "inputs": [
6
+ {
7
+ "name": "pr",
8
+ "flag": "--pr",
9
+ "type": "int",
10
+ "required": true,
11
+ "describe": "GitHub PR number to apply feedback to."
12
+ },
13
+ {
14
+ "name": "feedback",
15
+ "flag": "--feedback",
16
+ "type": "string",
17
+ "required": false,
18
+ "describe": "Inline override. If absent, the flow reads the latest PR review comment."
19
+ }
20
+ ],
21
+
22
+ "claudeCode": {
23
+ "model": "inherit",
24
+ "permissionMode": "acceptEdits",
25
+ "maxTurns": null,
26
+ "systemPromptAppend": null,
27
+ "tools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
28
+ "hooks": {
29
+ "PreToolUse": [],
30
+ "PostToolUse": [],
31
+ "Stop": []
32
+ },
33
+ "skills": [],
34
+ "commands": [],
35
+ "subagents": [],
36
+ "plugins": [],
37
+ "mcpServers": []
38
+ },
39
+
40
+ "cliTools": [],
41
+
42
+ "scripts": {
43
+ "preflight": [
44
+ { "script": "fixFlow" },
45
+ { "script": "loadTaskState" },
46
+ { "script": "loadConventions" },
47
+ { "script": "loadCoverageRules" },
48
+ { "script": "composePrompt" }
49
+ ],
50
+ "postflight": [
51
+ { "script": "parseAgentResult" },
52
+ { "script": "verify" },
53
+ { "script": "checkCoverageWithRetry" },
54
+ { "script": "commitAndPush" },
55
+ { "script": "ensurePr" },
56
+ { "script": "postIssueComment" },
57
+ { "script": "writeRunSummary" },
58
+ { "script": "saveTaskState" }
59
+ ]
60
+ },
61
+ "output": {
62
+ "actionTypes": [
63
+ "FIX_COMPLETED",
64
+ "FIX_FAILED",
65
+ "AGENT_NOT_RUN"
66
+ ]
67
+ }
68
+ }
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "fix-ci",
3
+ "describe": "Fix a failing CI workflow on an existing PR.",
4
+
5
+ "inputs": [
6
+ {
7
+ "name": "pr",
8
+ "flag": "--pr",
9
+ "type": "int",
10
+ "required": true,
11
+ "describe": "GitHub PR number whose CI is failing."
12
+ },
13
+ {
14
+ "name": "runId",
15
+ "flag": "--run-id",
16
+ "type": "string",
17
+ "required": false,
18
+ "describe": "Specific failed workflow run ID. Defaults to latest failed run on the PR branch."
19
+ }
20
+ ],
21
+
22
+ "claudeCode": {
23
+ "model": "inherit",
24
+ "permissionMode": "acceptEdits",
25
+ "maxTurns": null,
26
+ "systemPromptAppend": null,
27
+ "tools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
28
+ "hooks": {
29
+ "PreToolUse": [],
30
+ "PostToolUse": [],
31
+ "Stop": []
32
+ },
33
+ "skills": [],
34
+ "commands": [],
35
+ "subagents": [],
36
+ "plugins": [],
37
+ "mcpServers": []
38
+ },
39
+
40
+ "cliTools": [],
41
+
42
+ "scripts": {
43
+ "preflight": [
44
+ { "script": "fixCiFlow" },
45
+ { "script": "loadTaskState" },
46
+ { "script": "loadConventions" },
47
+ { "script": "loadCoverageRules" },
48
+ { "script": "composePrompt" }
49
+ ],
50
+ "postflight": [
51
+ { "script": "parseAgentResult" },
52
+ { "script": "verify" },
53
+ { "script": "checkCoverageWithRetry" },
54
+ { "script": "commitAndPush" },
55
+ { "script": "ensurePr" },
56
+ { "script": "postIssueComment" },
57
+ { "script": "writeRunSummary" },
58
+ { "script": "saveTaskState" }
59
+ ]
60
+ },
61
+ "output": {
62
+ "actionTypes": [
63
+ "FIX_CI_COMPLETED",
64
+ "FIX_CI_FAILED",
65
+ "AGENT_NOT_RUN"
66
+ ]
67
+ }
68
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "resolve",
3
+ "describe": "Resolve merge conflicts between a PR branch and the default branch.",
4
+
5
+ "inputs": [
6
+ {
7
+ "name": "pr",
8
+ "flag": "--pr",
9
+ "type": "int",
10
+ "required": true,
11
+ "describe": "GitHub PR number whose branch has conflicts with the default branch."
12
+ }
13
+ ],
14
+
15
+ "claudeCode": {
16
+ "model": "inherit",
17
+ "permissionMode": "acceptEdits",
18
+ "maxTurns": null,
19
+ "systemPromptAppend": null,
20
+ "tools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
21
+ "hooks": {
22
+ "PreToolUse": [],
23
+ "PostToolUse": [],
24
+ "Stop": []
25
+ },
26
+ "skills": [],
27
+ "commands": [],
28
+ "subagents": [],
29
+ "plugins": [],
30
+ "mcpServers": []
31
+ },
32
+
33
+ "cliTools": [],
34
+
35
+ "scripts": {
36
+ "preflight": [
37
+ { "script": "resolveFlow" },
38
+ { "script": "loadTaskState" },
39
+ { "script": "loadConventions" },
40
+ { "script": "loadCoverageRules" },
41
+ { "script": "composePrompt" }
42
+ ],
43
+ "postflight": [
44
+ { "script": "parseAgentResult" },
45
+ { "script": "commitAndPush" },
46
+ { "script": "ensurePr" },
47
+ { "script": "postIssueComment" },
48
+ { "script": "writeRunSummary" },
49
+ { "script": "saveTaskState" }
50
+ ]
51
+ },
52
+ "output": {
53
+ "actionTypes": [
54
+ "RESOLVE_COMPLETED",
55
+ "RESOLVE_FAILED",
56
+ "AGENT_NOT_RUN"
57
+ ]
58
+ }
59
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "run",
3
+ "describe": "Implement a GitHub issue end-to-end: branch, code, commit, open PR.",
4
+
5
+ "inputs": [
6
+ {
7
+ "name": "issue",
8
+ "flag": "--issue",
9
+ "type": "int",
10
+ "required": true,
11
+ "describe": "GitHub issue number to implement."
12
+ }
13
+ ],
14
+
15
+ "claudeCode": {
16
+ "model": "inherit",
17
+ "permissionMode": "acceptEdits",
18
+ "maxTurns": null,
19
+ "systemPromptAppend": null,
20
+ "tools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
21
+ "hooks": {
22
+ "PreToolUse": [],
23
+ "PostToolUse": [],
24
+ "Stop": []
25
+ },
26
+ "skills": [],
27
+ "commands": [],
28
+ "subagents": [],
29
+ "plugins": [],
30
+ "mcpServers": []
31
+ },
32
+
33
+ "cliTools": [],
34
+
35
+ "scripts": {
36
+ "preflight": [
37
+ { "script": "runFlow" },
38
+ { "script": "loadTaskState" },
39
+ { "script": "loadConventions" },
40
+ { "script": "loadCoverageRules" },
41
+ { "script": "composePrompt" }
42
+ ],
43
+ "postflight": [
44
+ { "script": "parseAgentResult" },
45
+ { "script": "verify" },
46
+ { "script": "checkCoverageWithRetry" },
47
+ { "script": "commitAndPush" },
48
+ { "script": "ensurePr" },
49
+ { "script": "postIssueComment" },
50
+ { "script": "writeRunSummary" },
51
+ { "script": "saveTaskState" }
52
+ ]
53
+ },
54
+ "output": {
55
+ "actionTypes": [
56
+ "RUN_COMPLETED",
57
+ "RUN_FAILED",
58
+ "AGENT_NOT_RUN"
59
+ ]
60
+ }
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "kody2 — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
35
35
  "scripts": {
36
36
  "kody2": "tsx bin/kody2.ts",
37
- "build": "tsup && node -e \"require('fs').cpSync('src/executables', 'dist/executables', { recursive: true })\"",
37
+ "build": "tsup && node -e \"const fs=require('fs');fs.rmSync('dist/executables',{recursive:true,force:true});fs.cpSync('src/executables','dist/executables',{recursive:true})\"",
38
38
  "test": "vitest run tests/unit tests/int --no-coverage",
39
39
  "test:e2e": "vitest run tests/e2e --no-coverage",
40
40
  "test:all": "vitest run tests --no-coverage",
@@ -1,99 +0,0 @@
1
- {
2
- "name": "build",
3
- "describe": "Implement a GitHub issue or apply PR feedback end-to-end.",
4
-
5
- "inputs": [
6
- {
7
- "name": "mode",
8
- "flag": "--mode",
9
- "type": "enum",
10
- "values": ["run", "fix", "fix-ci", "resolve"],
11
- "required": true,
12
- "describe": "Which Build mode to run."
13
- },
14
- {
15
- "name": "issue",
16
- "flag": "--issue",
17
- "type": "int",
18
- "requiredWhen": { "mode": "run" },
19
- "describe": "GitHub issue number to implement."
20
- },
21
- {
22
- "name": "pr",
23
- "flag": "--pr",
24
- "type": "int",
25
- "requiredWhen": { "mode": ["fix", "fix-ci", "resolve"] },
26
- "describe": "GitHub PR number to operate on."
27
- },
28
- {
29
- "name": "feedback",
30
- "flag": "--feedback",
31
- "type": "string",
32
- "required": false,
33
- "describe": "Inline override for fix mode. If absent, the executor reads the latest PR review comment."
34
- },
35
- {
36
- "name": "runId",
37
- "flag": "--run-id",
38
- "type": "string",
39
- "required": false,
40
- "describe": "Specific failed workflow run ID for fix-ci. Defaults to latest failed run on the PR branch."
41
- }
42
- ],
43
-
44
- "claudeCode": {
45
- "model": "inherit",
46
- "permissionMode": "acceptEdits",
47
- "maxTurns": null,
48
- "systemPromptAppend": null,
49
- "tools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
50
- "hooks": {
51
- "PreToolUse": [],
52
- "PostToolUse": [],
53
- "Stop": []
54
- },
55
- "skills": [],
56
- "commands": [],
57
- "subagents": [],
58
- "plugins": [],
59
- "mcpServers": []
60
- },
61
-
62
- "cliTools": [],
63
-
64
- "scripts": {
65
- "preflight": [
66
- { "script": "runFlow", "runWhen": { "args.mode": "run" } },
67
- { "script": "fixFlow", "runWhen": { "args.mode": "fix" } },
68
- { "script": "fixCiFlow", "runWhen": { "args.mode": "fix-ci" } },
69
- { "script": "resolveFlow", "runWhen": { "args.mode": "resolve" } },
70
- { "script": "loadTaskState" },
71
- { "script": "loadConventions" },
72
- { "script": "loadCoverageRules" },
73
- { "script": "composePrompt" }
74
- ],
75
- "postflight": [
76
- { "script": "parseAgentResult" },
77
- { "script": "verify", "runWhen": { "args.mode": ["run", "fix", "fix-ci"] } },
78
- { "script": "checkCoverageWithRetry", "runWhen": { "args.mode": ["run", "fix", "fix-ci"] } },
79
- { "script": "commitAndPush" },
80
- { "script": "ensurePr" },
81
- { "script": "postIssueComment" },
82
- { "script": "writeRunSummary" },
83
- { "script": "saveTaskState" }
84
- ]
85
- },
86
- "output": {
87
- "actionTypes": [
88
- "RUN_COMPLETED",
89
- "RUN_FAILED",
90
- "FIX_COMPLETED",
91
- "FIX_FAILED",
92
- "FIX_CI_COMPLETED",
93
- "FIX_CI_FAILED",
94
- "RESOLVE_COMPLETED",
95
- "RESOLVE_FAILED",
96
- "AGENT_NOT_RUN"
97
- ]
98
- }
99
- }
@@ -1,56 +0,0 @@
1
- You are the **kody2 orchestrator** for issue #{{issue.number}} on {{repoOwner}}/{{repoName}}.
2
-
3
- Your job: drive a 2-step flow **plan → build** by posting `@kody2 <subcommand>` comments on the issue and watching the state-comment for completion signals. You do NOT edit files. You do NOT run git. You use `gh` (via Bash) only to post comments and read the state-comment.
4
-
5
- ---
6
-
7
- # Issue #{{issue.number}}: {{issue.title}}
8
-
9
- {{issue.body}}
10
-
11
- # Required flow (plan-then-build)
12
-
13
- 1. **Kick off plan.** Post an issue comment with EXACTLY this body:
14
- ```
15
- @kody2 plan
16
- ```
17
- Use: `gh issue comment {{issue.number}} --body "@kody2 plan"` (in the cwd).
18
- 2. **Wait for plan to complete.** Poll the issue's state-comment every ~30s. The state-comment is the one whose body starts with `<!-- kody2:state:v1:begin -->`. Fetch it with:
19
- ```
20
- gh api repos/{{repoOwner}}/{{repoName}}/issues/{{issue.number}}/comments --paginate --jq '.[] | select(.body | contains("kody2:state:v1:begin")) | .body'
21
- ```
22
- Parse the JSON block inside the sentinels. Look for `core.lastOutcome.type == "PLAN_COMPLETED"`.
23
- If `core.lastOutcome.type == "PLAN_FAILED"` OR if 10 minutes pass without completion → abort with:
24
- ```
25
- FAILED: plan did not complete (<reason from state or "timeout">)
26
- ```
27
- 3. **Kick off build.** Post:
28
- ```
29
- @kody2 build
30
- ```
31
- Same `gh issue comment` command.
32
- 4. **Wait for build to complete.** Same poll technique. Look for `core.lastOutcome.type == "RUN_COMPLETED"` (build's success marker) or `RUN_FAILED`. If `RUN_FAILED` or 30 minutes pass → abort with `FAILED: build did not complete (...)`.
33
- 5. **Emit final summary.**
34
-
35
- # Required final output
36
-
37
- On success:
38
-
39
- ```
40
- DONE
41
- COMMIT_MSG: chore(orchestrator): plan-then-build for #{{issue.number}}
42
- PR_SUMMARY:
43
- - Posted `@kody2 plan` and observed PLAN_COMPLETED.
44
- - Posted `@kody2 build` and observed RUN_COMPLETED.
45
- - Final PR: <prUrl from state>
46
- ```
47
-
48
- On failure, a single line: `FAILED: <concrete reason>`.
49
-
50
- # Rules
51
-
52
- - NEVER edit files. Read-only flow.
53
- - NEVER run git. Only `gh` via Bash for comment posting and state polling.
54
- - Between polls, sleep ~30 seconds. Do NOT poll faster than once every 30 seconds.
55
- - Hard cap: 40 turns total across the whole flow. If you're approaching the cap, fail early with `FAILED: turn budget exhausted`.
56
- - If you post an `@kody2` comment and the state-comment does NOT update within the poll window, the child executable likely didn't run — check the GitHub Actions runs tab URL via `gh run list --limit 5 --json conclusion,status,url` to diagnose, then fail with a concrete reason.
@@ -1,42 +0,0 @@
1
- You are a senior engineer producing an **implementation plan** for the GitHub issue below. You will NOT write code. You will NOT run git or gh commands. You will NOT modify files. Your only outputs are:
2
-
3
- 1. Use Read / Grep / Glob / Bash (read-only) to study the codebase as much as needed.
4
- 2. Emit a final message with the plan wrapped in the required markers (see "Required output").
5
-
6
- ---
7
-
8
- # Repo
9
- - {{repoOwner}}/{{repoName}}, default branch: {{defaultBranch}}
10
-
11
- # Issue #{{issue.number}}: {{issue.title}}
12
-
13
- {{issue.body}}
14
-
15
- Recent comments (most recent first, truncated):
16
- {{issue.commentsFormatted}}
17
-
18
- {{conventionsBlock}}
19
-
20
- ---
21
-
22
- # Required output
23
-
24
- Your FINAL message must be exactly this shape (no extra text before or after):
25
-
26
- ```
27
- DONE
28
- COMMIT_MSG: plan: <very short title>
29
- PR_SUMMARY:
30
- <A concrete implementation plan in markdown. Include:
31
- - Files to change (with paths), and the change in each.
32
- - New files to create, with their purpose and rough shape.
33
- - Any ambiguities that need the human to resolve first.
34
- - Verification checklist (typecheck / tests / lint expectations).
35
- Keep to ~60 lines or less. No filler. No marketing language.>
36
- ```
37
-
38
- # Rules
39
- - Read-only. Do NOT modify any file.
40
- - Do NOT run git or gh commands.
41
- - No speculative scope — plan only what the issue asks for.
42
- - If the issue is ambiguous and you cannot make progress without input, output `FAILED: <what's unclear>` instead of a plan.