@kody-ade/kody-engine 0.3.10 → 0.3.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/dist/bin/kody.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.3.10",
6
+ version: "0.3.12",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -795,6 +795,7 @@ function coerceBare(spec, value) {
795
795
  }
796
796
 
797
797
  // src/executor.ts
798
+ import { spawnSync as spawnSync2 } from "child_process";
798
799
  import * as fs21 from "fs";
799
800
  import * as path18 from "path";
800
801
 
@@ -977,7 +978,7 @@ function loadProfile(profilePath) {
977
978
  function validateScriptReferences(profile, registeredScripts) {
978
979
  const missing = [];
979
980
  for (const e of [...profile.scripts.preflight, ...profile.scripts.postflight]) {
980
- if (!registeredScripts.has(e.script)) missing.push(e.script);
981
+ if (e.script && !registeredScripts.has(e.script)) missing.push(e.script);
981
982
  }
982
983
  return missing;
983
984
  }
@@ -1137,11 +1138,20 @@ function parseScriptList(p, key, raw) {
1137
1138
  const out = [];
1138
1139
  for (const [i, item] of raw.entries()) {
1139
1140
  if (!item || typeof item !== "object") {
1140
- throw new ProfileError(p, `scripts.${key}[${i}] must be an object like { script, runWhen? }`);
1141
+ throw new ProfileError(p, `scripts.${key}[${i}] must be an object like { script, runWhen? } or { shell, runWhen? }`);
1141
1142
  }
1142
1143
  const r = item;
1143
- const script = requireString(p, r, "script");
1144
- const entry = { script };
1144
+ const hasScript = typeof r.script === "string" && r.script.length > 0;
1145
+ const hasShell = typeof r.shell === "string" && r.shell.length > 0;
1146
+ if (hasScript && hasShell) {
1147
+ throw new ProfileError(p, `scripts.${key}[${i}] cannot set both "script" and "shell" \u2014 pick one`);
1148
+ }
1149
+ if (!hasScript && !hasShell) {
1150
+ throw new ProfileError(p, `scripts.${key}[${i}] must set "script" (registered TS function) or "shell" (filename in executable dir)`);
1151
+ }
1152
+ const entry = {};
1153
+ if (hasScript) entry.script = r.script;
1154
+ if (hasShell) entry.shell = r.shell;
1145
1155
  if (r.runWhen && typeof r.runWhen === "object") {
1146
1156
  entry.runWhen = r.runWhen;
1147
1157
  }
@@ -2870,7 +2880,7 @@ function ensurePr(opts) {
2870
2880
 
2871
2881
  // src/scripts/ensurePr.ts
2872
2882
  var ensurePr2 = async (ctx) => {
2873
- if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
2883
+ if (ctx.skipAgent && ctx.output.exitCode !== void 0) {
2874
2884
  return;
2875
2885
  }
2876
2886
  const commitResult = ctx.data.commitResult;
@@ -5441,9 +5451,13 @@ async function runExecutable(profileName, input) {
5441
5451
  try {
5442
5452
  for (const entry of profile.scripts.preflight) {
5443
5453
  if (!shouldRun(entry, ctx)) continue;
5444
- const fn = preflightScripts[entry.script];
5445
- if (!fn) return finish({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
5446
- await fn(ctx, profile, entry.with);
5454
+ if (entry.shell) {
5455
+ runShellEntry(entry, ctx, profile);
5456
+ } else {
5457
+ const fn = preflightScripts[entry.script];
5458
+ if (!fn) return finish({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
5459
+ await fn(ctx, profile, entry.with);
5460
+ }
5447
5461
  if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
5448
5462
  return finish(ctx.output);
5449
5463
  }
@@ -5458,15 +5472,20 @@ async function runExecutable(profileName, input) {
5458
5472
  }
5459
5473
  for (const entry of profile.scripts.postflight) {
5460
5474
  if (!shouldRun(entry, ctx)) continue;
5461
- const fn = postflightScripts[entry.script];
5462
- if (!fn) return finish({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
5475
+ const label = entry.script ?? entry.shell ?? "<unknown>";
5463
5476
  try {
5464
- await fn(ctx, profile, agentResult, entry.with);
5477
+ if (entry.shell) {
5478
+ runShellEntry(entry, ctx, profile);
5479
+ } else {
5480
+ const fn = postflightScripts[entry.script];
5481
+ if (!fn) return finish({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
5482
+ await fn(ctx, profile, agentResult, entry.with);
5483
+ }
5465
5484
  } catch (err) {
5466
5485
  const msg = err instanceof Error ? err.message : String(err);
5467
- process.stderr.write(`[kody] postflight script "${entry.script}" crashed: ${msg}
5486
+ process.stderr.write(`[kody] postflight "${label}" crashed: ${msg}
5468
5487
  `);
5469
- if (!ctx.output.reason) ctx.output.reason = `postflight ${entry.script} crashed: ${msg}`;
5488
+ if (!ctx.output.reason) ctx.output.reason = `postflight ${label} crashed: ${msg}`;
5470
5489
  if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
5471
5490
  }
5472
5491
  }
@@ -5583,6 +5602,49 @@ function finish(out) {
5583
5602
  `);
5584
5603
  return out;
5585
5604
  }
5605
+ var SHELL_TIMEOUT_MS = 3e5;
5606
+ function runShellEntry(entry, ctx, profile) {
5607
+ const shellName = entry.shell;
5608
+ const shellPath = path18.join(profile.dir, shellName);
5609
+ if (!fs21.existsSync(shellPath)) {
5610
+ ctx.skipAgent = true;
5611
+ ctx.output.exitCode = 99;
5612
+ ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
5613
+ return;
5614
+ }
5615
+ const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
5616
+ const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
5617
+ for (const [k, v] of Object.entries(ctx.args)) {
5618
+ if (v === void 0 || v === null) continue;
5619
+ env[`KODY_ARG_${k.toUpperCase().replace(/-/g, "_")}`] = String(v);
5620
+ }
5621
+ const r = spawnSync2("bash", [shellPath, ...positional], {
5622
+ cwd: ctx.cwd,
5623
+ encoding: "utf-8",
5624
+ env,
5625
+ stdio: ["pipe", "pipe", "pipe"],
5626
+ timeout: SHELL_TIMEOUT_MS
5627
+ });
5628
+ const stdout = r.stdout ?? "";
5629
+ const stderr = r.stderr ?? "";
5630
+ if (stdout) process.stdout.write(stdout);
5631
+ if (stderr) process.stderr.write(stderr);
5632
+ if (/^KODY_SKIP_AGENT=true\s*$/m.test(stdout)) {
5633
+ ctx.skipAgent = true;
5634
+ if (ctx.output.exitCode === void 0) ctx.output.exitCode = 0;
5635
+ }
5636
+ const exit = r.status ?? -1;
5637
+ if (exit !== 0) {
5638
+ ctx.skipAgent = true;
5639
+ if (ctx.output.exitCode === void 0 || ctx.output.exitCode === 0) {
5640
+ ctx.output.exitCode = exit;
5641
+ }
5642
+ if (!ctx.output.reason) {
5643
+ const tail = (stderr || stdout).slice(-800);
5644
+ ctx.output.reason = `shell '${shellName}' exited ${exit}${tail ? `: ${tail}` : ""}`;
5645
+ }
5646
+ }
5647
+ }
5586
5648
 
5587
5649
  // src/kody-cli.ts
5588
5650
  var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Deterministic conflict resolution for `kody resolve --prefer ours|theirs`.
4
+ #
5
+ # Runs as a preflight shell entry (see src/executables/resolve/profile.json).
6
+ # Gated by runWhen on args.prefer, so it only fires when the user requested
7
+ # a side. Reads the side from $KODY_ARG_PREFER (the executor exposes every
8
+ # ctx.args.<key> as an env var).
9
+ #
10
+ # Preconditions set by the prior `resolveFlow` preflight:
11
+ # - cwd is on the PR branch.
12
+ # - `git merge origin/<base> --no-edit --no-ff` already ran and conflicted.
13
+ # - Working tree has unmerged paths.
14
+ #
15
+ # Behavior: for each unmerged file, git checkout --ours (or --theirs), add,
16
+ # commit the merge, push the branch. Prints `KODY_SKIP_AGENT=true` on
17
+ # success so the executor bypasses the agent entirely.
18
+ #
19
+ # Exits:
20
+ # 0 — resolved + pushed (or nothing to resolve)
21
+ # 64 — invalid side value
22
+ # 1+ — git operation failed (executor will surface stderr)
23
+
24
+ set -euo pipefail
25
+
26
+ side="${KODY_ARG_PREFER:-}"
27
+ if [[ "$side" != "ours" && "$side" != "theirs" ]]; then
28
+ echo "apply-prefer: expected KODY_ARG_PREFER=ours|theirs, got '$side'" >&2
29
+ exit 64
30
+ fi
31
+
32
+ unmerged=$(git diff --name-only --diff-filter=U)
33
+ if [[ -z "$unmerged" ]]; then
34
+ echo "apply-prefer: no unmerged paths — nothing to resolve"
35
+ echo "KODY_SKIP_AGENT=true"
36
+ exit 0
37
+ fi
38
+
39
+ count=0
40
+ while IFS= read -r f; do
41
+ [[ -z "$f" ]] && continue
42
+ git checkout "--$side" -- "$f"
43
+ git add -- "$f"
44
+ count=$((count + 1))
45
+ done <<< "$unmerged"
46
+ echo "apply-prefer: resolved $count file(s) via --$side"
47
+
48
+ # Complete the merge. git merge left MERGE_MSG in place; --no-edit uses it.
49
+ HUSKY=0 SKIP_HOOKS=1 git -c commit.gpgsign=false commit --no-edit
50
+
51
+ branch=$(git rev-parse --abbrev-ref HEAD)
52
+ git push origin "$branch"
53
+ echo "apply-prefer: pushed $branch"
54
+ echo "KODY_SKIP_AGENT=true"
@@ -53,6 +53,10 @@
53
53
  {
54
54
  "script": "resolveFlow"
55
55
  },
56
+ {
57
+ "shell": "apply-prefer.sh",
58
+ "runWhen": { "args.prefer": ["ours", "theirs"] }
59
+ },
56
60
  {
57
61
  "script": "loadTaskState"
58
62
  },
@@ -159,7 +159,20 @@ export interface CliToolSpec {
159
159
  }
160
160
 
161
161
  export interface ScriptEntry {
162
- script: string
162
+ /**
163
+ * Name of a registered TS function in src/scripts/index.ts. Mutually
164
+ * exclusive with `shell` — exactly one must be set.
165
+ */
166
+ script?: string
167
+ /**
168
+ * Filename of a shell script colocated with the executable
169
+ * (e.g. "apply-prefer.sh"). Resolved relative to the profile's
170
+ * directory. Invoked via `bash <path> <with-args>` with ctx.args
171
+ * exposed as env vars (KODY_ARG_<UPPER_NAME>=<value>). A stdout
172
+ * line `KODY_SKIP_AGENT=true` signals the executor to bypass the
173
+ * agent. Non-zero exit is treated as a preflight failure.
174
+ */
175
+ shell?: string
163
176
  /**
164
177
  * Optional conditional. Keys are dotted paths into the context (e.g.
165
178
  * "args.mode"). Values are a single primitive or an array of primitives.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",