@kody-ade/kody-engine 0.4.407 → 0.4.409
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 +92 -23
- package/package.json +1 -1
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.
|
|
18
|
+
version: "0.4.409",
|
|
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",
|
|
@@ -2990,6 +2990,17 @@ var init_capabilityMcp = __esm({
|
|
|
2990
2990
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
2991
2991
|
import * as fs7 from "fs";
|
|
2992
2992
|
import * as path8 from "path";
|
|
2993
|
+
function buildCloneProcess(repo, token, baseEnv = process.env) {
|
|
2994
|
+
const url = `https://github.com/${repo}.git`;
|
|
2995
|
+
const env = { ...baseEnv };
|
|
2996
|
+
if (!token) return { url, env };
|
|
2997
|
+
const parsedCount = Number.parseInt(env.GIT_CONFIG_COUNT ?? "0", 10);
|
|
2998
|
+
const count = Number.isInteger(parsedCount) && parsedCount >= 0 ? parsedCount : 0;
|
|
2999
|
+
env.GIT_CONFIG_COUNT = String(count + 1);
|
|
3000
|
+
env[`GIT_CONFIG_KEY_${count}`] = "http.https://github.com/.extraHeader";
|
|
3001
|
+
env[`GIT_CONFIG_VALUE_${count}`] = `Authorization: Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`;
|
|
3002
|
+
return { url, env };
|
|
3003
|
+
}
|
|
2993
3004
|
async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
|
|
2994
3005
|
const name = repo?.trim();
|
|
2995
3006
|
if (!name || !REPO_RE.test(name)) return null;
|
|
@@ -3020,17 +3031,19 @@ async function fetchRepo(opts) {
|
|
|
3020
3031
|
}
|
|
3021
3032
|
return dir;
|
|
3022
3033
|
}
|
|
3023
|
-
var REPO_RE, repoClones, defaultCloneRepo;
|
|
3034
|
+
var REPO_RE, repoClones, GIT_CREDENTIAL_HELPER, defaultCloneRepo;
|
|
3024
3035
|
var init_repoWorkspace = __esm({
|
|
3025
3036
|
"src/repoWorkspace.ts"() {
|
|
3026
3037
|
"use strict";
|
|
3027
3038
|
REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
3028
3039
|
repoClones = /* @__PURE__ */ new Map();
|
|
3040
|
+
GIT_CREDENTIAL_HELPER = `!f() { if [ "$1" = get ] && [ -n "$GITHUB_TOKEN" ]; then printf '%s\\n' username=x-access-token "password=$GITHUB_TOKEN"; fi; }; f`;
|
|
3029
3041
|
defaultCloneRepo = (repo, token, dir) => {
|
|
3030
3042
|
fs7.mkdirSync(path8.dirname(dir), { recursive: true });
|
|
3031
|
-
const
|
|
3043
|
+
const clone = buildCloneProcess(repo, token);
|
|
3032
3044
|
return new Promise((resolve16, reject) => {
|
|
3033
|
-
const child = spawn2("git", ["clone", "--depth=1",
|
|
3045
|
+
const child = spawn2("git", ["clone", "--depth=1", clone.url, dir], {
|
|
3046
|
+
env: clone.env,
|
|
3034
3047
|
stdio: "inherit"
|
|
3035
3048
|
});
|
|
3036
3049
|
child.on("exit", (code) => {
|
|
@@ -3043,6 +3056,9 @@ var init_repoWorkspace = __esm({
|
|
|
3043
3056
|
const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
|
|
3044
3057
|
spawnSync("git", ["-C", dir, "config", "user.name", name]);
|
|
3045
3058
|
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
3059
|
+
if (token) {
|
|
3060
|
+
spawnSync("git", ["-C", dir, "config", "credential.helper", GIT_CREDENTIAL_HELPER]);
|
|
3061
|
+
}
|
|
3046
3062
|
} catch {
|
|
3047
3063
|
}
|
|
3048
3064
|
resolve16();
|
|
@@ -3169,24 +3185,26 @@ function stripAgentSecrets(env) {
|
|
|
3169
3185
|
}
|
|
3170
3186
|
return out;
|
|
3171
3187
|
}
|
|
3172
|
-
|
|
3173
|
-
const ndjsonDir = opts.ndjsonDir ?? agentRunDir(opts.cwd);
|
|
3174
|
-
fs8.mkdirSync(ndjsonDir, { recursive: true });
|
|
3175
|
-
const ndjsonPath = path9.join(ndjsonDir, "last-run.jsonl");
|
|
3188
|
+
function buildAgentEnvironment(baseEnv, repoToken) {
|
|
3176
3189
|
const env = stripAgentSecrets({
|
|
3177
|
-
...
|
|
3190
|
+
...baseEnv,
|
|
3178
3191
|
SKIP_HOOKS: "1",
|
|
3179
3192
|
HUSKY: "0",
|
|
3180
|
-
CI:
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
// init while servers are still in `pending`, so their tools never
|
|
3184
|
-
// reach the model. Block until each MCP completes its handshake (or
|
|
3185
|
-
// the timeout below elapses) so the tool list is complete on first
|
|
3186
|
-
// turn.
|
|
3187
|
-
MCP_CONNECTION_NONBLOCKING: process.env.MCP_CONNECTION_NONBLOCKING ?? "false",
|
|
3188
|
-
MCP_TIMEOUT: process.env.MCP_TIMEOUT ?? "60000"
|
|
3193
|
+
CI: baseEnv.CI ?? "1",
|
|
3194
|
+
MCP_CONNECTION_NONBLOCKING: baseEnv.MCP_CONNECTION_NONBLOCKING ?? "false",
|
|
3195
|
+
MCP_TIMEOUT: baseEnv.MCP_TIMEOUT ?? "60000"
|
|
3189
3196
|
});
|
|
3197
|
+
if (repoToken) {
|
|
3198
|
+
env.GITHUB_TOKEN = repoToken;
|
|
3199
|
+
env.GH_TOKEN = repoToken;
|
|
3200
|
+
}
|
|
3201
|
+
return env;
|
|
3202
|
+
}
|
|
3203
|
+
async function runAgent(opts) {
|
|
3204
|
+
const ndjsonDir = opts.ndjsonDir ?? agentRunDir(opts.cwd);
|
|
3205
|
+
fs8.mkdirSync(ndjsonDir, { recursive: true });
|
|
3206
|
+
const ndjsonPath = path9.join(ndjsonDir, "last-run.jsonl");
|
|
3207
|
+
const env = buildAgentEnvironment(process.env, opts.repoToken);
|
|
3190
3208
|
if (opts.litellmUrl) {
|
|
3191
3209
|
env.ANTHROPIC_BASE_URL = opts.litellmUrl;
|
|
3192
3210
|
env.ANTHROPIC_API_KEY = getAnthropicApiKeyOrDummy();
|
|
@@ -11432,12 +11450,54 @@ var init_composePrompt = __esm({
|
|
|
11432
11450
|
});
|
|
11433
11451
|
|
|
11434
11452
|
// src/scripts/postReviewResult.ts
|
|
11453
|
+
function words(value) {
|
|
11454
|
+
return value.trim().split(/\s+/).filter(Boolean);
|
|
11455
|
+
}
|
|
11456
|
+
function removeForbiddenReviewSections(body) {
|
|
11457
|
+
const kept = [];
|
|
11458
|
+
let skipping = false;
|
|
11459
|
+
for (const line of body.split("\n")) {
|
|
11460
|
+
const heading = line.match(/^\s*(?:#{1,6}\s+(.+?)|\*\*(.+?)\*\*)\s*$/);
|
|
11461
|
+
if (heading) {
|
|
11462
|
+
const title = (heading[1] ?? heading[2] ?? "").replace(/[*_`]/g, "").trim();
|
|
11463
|
+
skipping = FORBIDDEN_REVIEW_SECTION.test(title);
|
|
11464
|
+
}
|
|
11465
|
+
if (!skipping) kept.push(line);
|
|
11466
|
+
}
|
|
11467
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
11468
|
+
}
|
|
11469
|
+
function prepareReviewBody(rawBody) {
|
|
11470
|
+
let body = rawBody.trim();
|
|
11471
|
+
const verdict = body.match(/(^|\n)(\s*#{1,6}\s*Verdict\s*:?\s*(?:PASS|CONCERNS|FAIL)\b)/i);
|
|
11472
|
+
if (verdict?.index !== void 0) {
|
|
11473
|
+
body = body.slice(verdict.index + verdict[1].length).trim();
|
|
11474
|
+
}
|
|
11475
|
+
body = removeForbiddenReviewSections(body);
|
|
11476
|
+
const bodyWords = words(body);
|
|
11477
|
+
if (bodyWords.length <= MAX_REVIEW_WORDS) return body;
|
|
11478
|
+
const noteWords = words(REVIEW_TRUNCATION_NOTE);
|
|
11479
|
+
const retainedWordCount = MAX_REVIEW_WORDS - noteWords.length;
|
|
11480
|
+
const matches = [...body.matchAll(/\S+/g)];
|
|
11481
|
+
const retainedEnd = matches[retainedWordCount - 1].index + matches[retainedWordCount - 1][0].length;
|
|
11482
|
+
return `${body.slice(0, retainedEnd).trimEnd()}
|
|
11483
|
+
|
|
11484
|
+
${REVIEW_TRUNCATION_NOTE}`;
|
|
11485
|
+
}
|
|
11435
11486
|
function inferVerdictFromReviewText(body) {
|
|
11436
11487
|
const structuredVerdict = body.match(/"verdict"\s*:\s*"(pass|concerns|fail|partial)"/i);
|
|
11437
11488
|
if (structuredVerdict) {
|
|
11438
11489
|
const value = structuredVerdict[1].toUpperCase();
|
|
11439
11490
|
return value === "PARTIAL" ? "CONCERNS" : value;
|
|
11440
11491
|
}
|
|
11492
|
+
const status = body.match(
|
|
11493
|
+
/(^|\n)\s*(?:#{1,6}\s*)?(?:\*\*Status:\*\*|Status:)\s*(PASS|CONCERNS|FAIL|WARN|NONE|BLOCK|NEEDS_CONTEXT)\b/i
|
|
11494
|
+
);
|
|
11495
|
+
if (status) {
|
|
11496
|
+
const value = status[2].toUpperCase();
|
|
11497
|
+
if (value === "PASS" || value === "NONE") return "PASS";
|
|
11498
|
+
if (value === "CONCERNS" || value === "WARN") return "CONCERNS";
|
|
11499
|
+
return "FAIL";
|
|
11500
|
+
}
|
|
11441
11501
|
if (/\bpartial\b/i.test(body) && /\b(finding|gap|unverified|unverifiable|blocker|issue)s?\b/i.test(body)) {
|
|
11442
11502
|
return "CONCERNS";
|
|
11443
11503
|
}
|
|
@@ -11480,11 +11540,14 @@ function reviewAction(verdict, payload) {
|
|
|
11480
11540
|
function failedAction(reason) {
|
|
11481
11541
|
return { type: "REVIEW_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
11482
11542
|
}
|
|
11483
|
-
var postReviewResult;
|
|
11543
|
+
var MAX_REVIEW_WORDS, REVIEW_TRUNCATION_NOTE, FORBIDDEN_REVIEW_SECTION, postReviewResult;
|
|
11484
11544
|
var init_postReviewResult = __esm({
|
|
11485
11545
|
"src/scripts/postReviewResult.ts"() {
|
|
11486
11546
|
"use strict";
|
|
11487
11547
|
init_issue();
|
|
11548
|
+
MAX_REVIEW_WORDS = 600;
|
|
11549
|
+
REVIEW_TRUNCATION_NOTE = "> Review truncated to the highest-priority findings.";
|
|
11550
|
+
FORBIDDEN_REVIEW_SECTION = /^(?:clean\b|strengths?\b|suggest(?:ion|ed)s?\b|follow[- ]?ups?\b|verification\b|notes?\b|nits?\b|non[- ]issues?\b)/i;
|
|
11488
11551
|
postReviewResult = async (ctx, _profile, agentResult) => {
|
|
11489
11552
|
const prNumber = ctx.data.commentTargetNumber;
|
|
11490
11553
|
if (!prNumber) {
|
|
@@ -11504,7 +11567,7 @@ var init_postReviewResult = __esm({
|
|
|
11504
11567
|
ctx.data.action = failedAction(reason);
|
|
11505
11568
|
return;
|
|
11506
11569
|
}
|
|
11507
|
-
const reviewBody = agentResult.finalText
|
|
11570
|
+
const reviewBody = prepareReviewBody(agentResult.finalText);
|
|
11508
11571
|
if (!reviewBody) {
|
|
11509
11572
|
try {
|
|
11510
11573
|
postPrReviewComment(prNumber, `\u26A0\uFE0F kody review FAILED: agent produced no review body`, ctx.cwd);
|
|
@@ -19833,6 +19896,9 @@ function jobReferenceBlock(profileName, profile, data) {
|
|
|
19833
19896
|
];
|
|
19834
19897
|
return lines.join("\n");
|
|
19835
19898
|
}
|
|
19899
|
+
function shouldPromptForTaskArtifacts(tools) {
|
|
19900
|
+
return tools.some((tool6) => TASK_ARTIFACT_WRITE_TOOLS.has(tool6));
|
|
19901
|
+
}
|
|
19836
19902
|
async function runImplementation(profileName, input) {
|
|
19837
19903
|
const stageStartedAt = Date.now();
|
|
19838
19904
|
let finishRunIndex = null;
|
|
@@ -19983,6 +20049,7 @@ async function runImplementation(profileName, input) {
|
|
|
19983
20049
|
})
|
|
19984
20050
|
};
|
|
19985
20051
|
})() : null;
|
|
20052
|
+
const agentTaskArtifacts = taskArtifacts && shouldPromptForTaskArtifacts(profile.claudeCode.tools) ? taskArtifacts : null;
|
|
19986
20053
|
const ndjsonDir = agentRunDir(input.cwd);
|
|
19987
20054
|
const agentSlug = typeof profile.agent === "string" && profile.agent.length > 0 ? profile.agent : typeof ctx.data.jobAgent === "string" && ctx.data.jobAgent.length > 0 ? ctx.data.jobAgent : null;
|
|
19988
20055
|
const agentIdentityBlock = agentSlug ? frameAgentIdentity(agentSlug, loadAgentIdentity(input.cwd, agentSlug)) : null;
|
|
@@ -20015,7 +20082,7 @@ async function runImplementation(profileName, input) {
|
|
|
20015
20082
|
verbose: input.verbose,
|
|
20016
20083
|
quiet: input.quiet,
|
|
20017
20084
|
ndjsonDir,
|
|
20018
|
-
additionalDirectories:
|
|
20085
|
+
additionalDirectories: agentTaskArtifacts ? [agentTaskArtifacts.absDir] : void 0,
|
|
20019
20086
|
allowedToolsOverride: profile.claudeCode.tools,
|
|
20020
20087
|
permissionModeOverride: profile.claudeCode.permissionMode,
|
|
20021
20088
|
mcpServers: profile.claudeCode.mcpServers.length > 0 ? profile.claudeCode.mcpServers : void 0,
|
|
@@ -20033,7 +20100,7 @@ async function runImplementation(profileName, input) {
|
|
|
20033
20100
|
jobRefBlock,
|
|
20034
20101
|
jobWhyBlock,
|
|
20035
20102
|
profile.claudeCode.systemPromptAppend,
|
|
20036
|
-
|
|
20103
|
+
agentTaskArtifacts?.promptAddendum
|
|
20037
20104
|
].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
|
|
20038
20105
|
cacheable: profile.claudeCode.cacheable,
|
|
20039
20106
|
enableVerifyTool: profile.claudeCode.enableVerifyTool,
|
|
@@ -20690,7 +20757,7 @@ function flattenConfig(obj, prefix = "") {
|
|
|
20690
20757
|
}
|
|
20691
20758
|
return out;
|
|
20692
20759
|
}
|
|
20693
|
-
var MUTATING_POSTFLIGHTS, SHELL_MARKER_RE, MAX_CHAIN_HOPS, DEFAULT_SHELL_TIMEOUT_MS, SIGKILL_GRACE_MS;
|
|
20760
|
+
var MUTATING_POSTFLIGHTS, SHELL_MARKER_RE, TASK_ARTIFACT_WRITE_TOOLS, MAX_CHAIN_HOPS, DEFAULT_SHELL_TIMEOUT_MS, SIGKILL_GRACE_MS;
|
|
20694
20761
|
var init_executor = __esm({
|
|
20695
20762
|
"src/executor.ts"() {
|
|
20696
20763
|
"use strict";
|
|
@@ -20723,6 +20790,7 @@ var init_executor = __esm({
|
|
|
20723
20790
|
"openAgencyModelReviewPr"
|
|
20724
20791
|
]);
|
|
20725
20792
|
SHELL_MARKER_RE = /^KODY_(SKIP_AGENT|PR_URL|REASON|CAPABILITY_REPORT|CAPABILITY_RESULT)=/m;
|
|
20793
|
+
TASK_ARTIFACT_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "NotebookEdit"]);
|
|
20726
20794
|
MAX_CHAIN_HOPS = 60;
|
|
20727
20795
|
DEFAULT_SHELL_TIMEOUT_MS = 3e5;
|
|
20728
20796
|
SIGKILL_GRACE_MS = 5e3;
|
|
@@ -22321,6 +22389,7 @@ async function runChatTurn(opts) {
|
|
|
22321
22389
|
],
|
|
22322
22390
|
systemPromptAppend: systemPrompt,
|
|
22323
22391
|
...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {},
|
|
22392
|
+
...opts.repoToken ? { repoToken: opts.repoToken } : {},
|
|
22324
22393
|
// Cross-repo work is opt-in. Repo Brain's default path remains focused
|
|
22325
22394
|
// on the selected repo even though the server stores clones under
|
|
22326
22395
|
// reposRoot.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.409",
|
|
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",
|