@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.
|
|
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
|
|
1144
|
-
const
|
|
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
|
|
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
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
5486
|
+
process.stderr.write(`[kody] postflight "${label}" crashed: ${msg}
|
|
5468
5487
|
`);
|
|
5469
|
-
if (!ctx.output.reason) ctx.output.reason = `postflight ${
|
|
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"
|
|
@@ -159,7 +159,20 @@ export interface CliToolSpec {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
export interface ScriptEntry {
|
|
162
|
-
|
|
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.
|
|
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",
|