@kody-ade/kody-engine 0.4.139 → 0.4.140
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
|
@@ -93,7 +93,7 @@ var init_events = __esm({
|
|
|
93
93
|
// src/verify.ts
|
|
94
94
|
import { spawn } from "child_process";
|
|
95
95
|
function runCommand(command, cwd) {
|
|
96
|
-
return new Promise((
|
|
96
|
+
return new Promise((resolve5) => {
|
|
97
97
|
const start = Date.now();
|
|
98
98
|
const child = spawn(command, {
|
|
99
99
|
cwd,
|
|
@@ -122,11 +122,11 @@ function runCommand(command, cwd) {
|
|
|
122
122
|
child.on("exit", (code) => {
|
|
123
123
|
clearTimeout(timer);
|
|
124
124
|
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
125
|
-
|
|
125
|
+
resolve5({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
126
126
|
});
|
|
127
127
|
child.on("error", (err) => {
|
|
128
128
|
clearTimeout(timer);
|
|
129
|
-
|
|
129
|
+
resolve5({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
}
|
|
@@ -926,7 +926,7 @@ var init_loadPriorArt = __esm({
|
|
|
926
926
|
// package.json
|
|
927
927
|
var package_default = {
|
|
928
928
|
name: "@kody-ade/kody-engine",
|
|
929
|
-
version: "0.4.
|
|
929
|
+
version: "0.4.140",
|
|
930
930
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
931
931
|
license: "MIT",
|
|
932
932
|
type: "module",
|
|
@@ -1641,10 +1641,10 @@ async function runAgent(opts) {
|
|
|
1641
1641
|
let timer;
|
|
1642
1642
|
let next;
|
|
1643
1643
|
if (turnTimeoutMs > 0) {
|
|
1644
|
-
const timeoutPromise = new Promise((
|
|
1644
|
+
const timeoutPromise = new Promise((resolve5) => {
|
|
1645
1645
|
timer = setTimeout(() => {
|
|
1646
1646
|
timedOut = true;
|
|
1647
|
-
|
|
1647
|
+
resolve5({ done: true, value: void 0 });
|
|
1648
1648
|
}, turnTimeoutMs);
|
|
1649
1649
|
});
|
|
1650
1650
|
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
@@ -2318,7 +2318,7 @@ async function waitForNextUserMessage(opts) {
|
|
|
2318
2318
|
}
|
|
2319
2319
|
}
|
|
2320
2320
|
function sleep(ms) {
|
|
2321
|
-
return new Promise((
|
|
2321
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2322
2322
|
}
|
|
2323
2323
|
function currentBranch(cwd) {
|
|
2324
2324
|
try {
|
|
@@ -2867,7 +2867,7 @@ function coerceBare(spec, value) {
|
|
|
2867
2867
|
init_issue();
|
|
2868
2868
|
|
|
2869
2869
|
// src/executor.ts
|
|
2870
|
-
import { execFileSync as execFileSync30, spawn as
|
|
2870
|
+
import { execFileSync as execFileSync30, spawn as spawn9 } from "child_process";
|
|
2871
2871
|
import * as fs40 from "fs";
|
|
2872
2872
|
import * as path37 from "path";
|
|
2873
2873
|
|
|
@@ -4291,6 +4291,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
4291
4291
|
|
|
4292
4292
|
// src/scripts/brainServe.ts
|
|
4293
4293
|
import { createServer } from "http";
|
|
4294
|
+
import { spawn as spawn3, spawnSync } from "child_process";
|
|
4294
4295
|
import * as fs18 from "fs";
|
|
4295
4296
|
import * as path17 from "path";
|
|
4296
4297
|
|
|
@@ -4473,17 +4474,17 @@ function authOk(req, expected) {
|
|
|
4473
4474
|
return false;
|
|
4474
4475
|
}
|
|
4475
4476
|
function readJsonBody(req) {
|
|
4476
|
-
return new Promise((
|
|
4477
|
+
return new Promise((resolve5, reject) => {
|
|
4477
4478
|
const chunks = [];
|
|
4478
4479
|
req.on("data", (c) => chunks.push(c));
|
|
4479
4480
|
req.on("end", () => {
|
|
4480
4481
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
4481
4482
|
if (!raw.trim()) {
|
|
4482
|
-
|
|
4483
|
+
resolve5({});
|
|
4483
4484
|
return;
|
|
4484
4485
|
}
|
|
4485
4486
|
try {
|
|
4486
|
-
|
|
4487
|
+
resolve5(JSON.parse(raw));
|
|
4487
4488
|
} catch (err) {
|
|
4488
4489
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4489
4490
|
}
|
|
@@ -4491,6 +4492,13 @@ function readJsonBody(req) {
|
|
|
4491
4492
|
req.on("error", reject);
|
|
4492
4493
|
});
|
|
4493
4494
|
}
|
|
4495
|
+
function strField(body, key) {
|
|
4496
|
+
if (typeof body === "object" && body !== null && key in body) {
|
|
4497
|
+
const v = body[key];
|
|
4498
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
4499
|
+
}
|
|
4500
|
+
return void 0;
|
|
4501
|
+
}
|
|
4494
4502
|
function sendJson(res, status, body) {
|
|
4495
4503
|
res.writeHead(status, { "content-type": "application/json" });
|
|
4496
4504
|
res.end(JSON.stringify(body));
|
|
@@ -4588,6 +4596,51 @@ function streamToRes(res, dir, chatId, since) {
|
|
|
4588
4596
|
);
|
|
4589
4597
|
res.on("close", unsubscribe);
|
|
4590
4598
|
}
|
|
4599
|
+
var REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
4600
|
+
var repoClones = /* @__PURE__ */ new Map();
|
|
4601
|
+
async function ensureRepoCwd(opts) {
|
|
4602
|
+
const repo = opts.repo?.trim();
|
|
4603
|
+
if (!repo || !REPO_RE.test(repo)) return opts.baseCwd;
|
|
4604
|
+
const root = path17.resolve(opts.reposRoot);
|
|
4605
|
+
const dir = path17.resolve(root, repo);
|
|
4606
|
+
if (dir !== root && !dir.startsWith(root + path17.sep)) return opts.baseCwd;
|
|
4607
|
+
if (fs18.existsSync(path17.join(dir, ".git"))) return dir;
|
|
4608
|
+
const inflight = repoClones.get(dir);
|
|
4609
|
+
if (inflight) {
|
|
4610
|
+
await inflight;
|
|
4611
|
+
return dir;
|
|
4612
|
+
}
|
|
4613
|
+
const p = opts.cloneRepo(repo, opts.repoToken, dir).finally(() => {
|
|
4614
|
+
if (repoClones.get(dir) === p) repoClones.delete(dir);
|
|
4615
|
+
});
|
|
4616
|
+
repoClones.set(dir, p);
|
|
4617
|
+
await p;
|
|
4618
|
+
return dir;
|
|
4619
|
+
}
|
|
4620
|
+
var defaultCloneRepo = (repo, token, dir) => {
|
|
4621
|
+
fs18.mkdirSync(path17.dirname(dir), { recursive: true });
|
|
4622
|
+
const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
|
|
4623
|
+
return new Promise((resolve5, reject) => {
|
|
4624
|
+
const child = spawn3("git", ["clone", "--depth=1", authUrl, dir], {
|
|
4625
|
+
stdio: "inherit"
|
|
4626
|
+
});
|
|
4627
|
+
child.on("exit", (code) => {
|
|
4628
|
+
if (code !== 0) {
|
|
4629
|
+
reject(new Error(`git clone ${repo} failed (exit ${code})`));
|
|
4630
|
+
return;
|
|
4631
|
+
}
|
|
4632
|
+
try {
|
|
4633
|
+
const name = process.env.GIT_AUTHOR_NAME ?? "Kody Bot";
|
|
4634
|
+
const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
|
|
4635
|
+
spawnSync("git", ["-C", dir, "config", "user.name", name]);
|
|
4636
|
+
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
4637
|
+
} catch {
|
|
4638
|
+
}
|
|
4639
|
+
resolve5();
|
|
4640
|
+
});
|
|
4641
|
+
child.on("error", reject);
|
|
4642
|
+
});
|
|
4643
|
+
};
|
|
4591
4644
|
async function handleChatTurn(req, res, chatId, opts) {
|
|
4592
4645
|
let body;
|
|
4593
4646
|
try {
|
|
@@ -4601,6 +4654,8 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4601
4654
|
sendJson(res, 400, { error: "message required" });
|
|
4602
4655
|
return;
|
|
4603
4656
|
}
|
|
4657
|
+
const repo = strField(body, "repo");
|
|
4658
|
+
const repoToken = strField(body, "repoToken");
|
|
4604
4659
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
4605
4660
|
fs18.mkdirSync(path17.dirname(sessionFile), { recursive: true });
|
|
4606
4661
|
appendTurn(sessionFile, {
|
|
@@ -4611,32 +4666,42 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4611
4666
|
const sinceFloor = getLastSeq(opts.cwd, chatId);
|
|
4612
4667
|
const emitToLog = beginTurn(opts.cwd, chatId);
|
|
4613
4668
|
const sink = new BrokerSink(emitToLog, chatId);
|
|
4614
|
-
void enqueue(
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4669
|
+
void enqueue(chatId, async () => {
|
|
4670
|
+
try {
|
|
4671
|
+
const agentCwd = await ensureRepoCwd({
|
|
4672
|
+
baseCwd: opts.cwd,
|
|
4673
|
+
reposRoot: opts.reposRoot,
|
|
4674
|
+
repo,
|
|
4675
|
+
repoToken,
|
|
4676
|
+
cloneRepo: opts.cloneRepo
|
|
4677
|
+
});
|
|
4678
|
+
await opts.runTurn({
|
|
4679
|
+
sessionId: chatId,
|
|
4680
|
+
sessionFile,
|
|
4681
|
+
cwd: agentCwd,
|
|
4682
|
+
model: opts.model,
|
|
4683
|
+
litellmUrl: opts.litellmUrl,
|
|
4684
|
+
sink
|
|
4685
|
+
});
|
|
4686
|
+
} catch (err) {
|
|
4624
4687
|
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
4625
4688
|
process.stderr.write(`[brain-serve] chat turn failed: ${errMsg3}
|
|
4626
4689
|
`);
|
|
4627
4690
|
endTurnIfUnterminated(opts.cwd, chatId, errMsg3);
|
|
4628
|
-
}
|
|
4691
|
+
} finally {
|
|
4629
4692
|
endTurnIfUnterminated(
|
|
4630
4693
|
opts.cwd,
|
|
4631
4694
|
chatId,
|
|
4632
4695
|
"Brain turn ended without a reply (the machine may have restarted mid-turn) \u2014 please resend your message"
|
|
4633
4696
|
);
|
|
4634
|
-
}
|
|
4635
|
-
);
|
|
4697
|
+
}
|
|
4698
|
+
});
|
|
4636
4699
|
streamToRes(res, opts.cwd, chatId, sinceFloor);
|
|
4637
4700
|
}
|
|
4638
4701
|
function buildServer(opts) {
|
|
4639
4702
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
4703
|
+
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
4704
|
+
const reposRoot = opts.reposRoot ?? path17.join(path17.dirname(path17.resolve(opts.cwd)), "repos");
|
|
4640
4705
|
return createServer(async (req, res) => {
|
|
4641
4706
|
if (!req.method || !req.url) {
|
|
4642
4707
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -4660,6 +4725,8 @@ function buildServer(opts) {
|
|
|
4660
4725
|
}
|
|
4661
4726
|
await handleChatTurn(req, res, chatId, {
|
|
4662
4727
|
cwd: opts.cwd,
|
|
4728
|
+
reposRoot,
|
|
4729
|
+
cloneRepo,
|
|
4663
4730
|
model: opts.model,
|
|
4664
4731
|
litellmUrl: opts.litellmUrl,
|
|
4665
4732
|
runTurn
|
|
@@ -4710,16 +4777,18 @@ var brainServe = async (ctx) => {
|
|
|
4710
4777
|
const server = buildServer({
|
|
4711
4778
|
apiKey,
|
|
4712
4779
|
cwd: ctx.cwd,
|
|
4780
|
+
// Per-repo clones live here; defaults to a `repos` sibling of cwd.
|
|
4781
|
+
reposRoot: process.env.BRAIN_REPOS_ROOT?.trim() || void 0,
|
|
4713
4782
|
model,
|
|
4714
4783
|
litellmUrl
|
|
4715
4784
|
});
|
|
4716
|
-
await new Promise((
|
|
4785
|
+
await new Promise((resolve5) => {
|
|
4717
4786
|
server.listen(port, "0.0.0.0", () => {
|
|
4718
4787
|
process.stdout.write(
|
|
4719
4788
|
`[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
|
|
4720
4789
|
`
|
|
4721
4790
|
);
|
|
4722
|
-
|
|
4791
|
+
resolve5();
|
|
4723
4792
|
});
|
|
4724
4793
|
});
|
|
4725
4794
|
const shutdown = (signal) => {
|
|
@@ -5514,36 +5583,46 @@ var createQaGoal = async (ctx, _profile, agentResult) => {
|
|
|
5514
5583
|
ctx.data.action = failedAction2("empty report body");
|
|
5515
5584
|
return;
|
|
5516
5585
|
}
|
|
5586
|
+
const { markdown } = splitReport(finalText);
|
|
5587
|
+
const verdict = detectVerdict(markdown);
|
|
5588
|
+
const existingIssue = ctx.args.issue;
|
|
5589
|
+
if (typeof existingIssue === "number" && existingIssue > 0) {
|
|
5590
|
+
try {
|
|
5591
|
+
postIssueComment(existingIssue, finalText, ctx.cwd);
|
|
5592
|
+
} catch (err) {
|
|
5593
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5594
|
+
ctx.output.exitCode = 4;
|
|
5595
|
+
ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
|
|
5596
|
+
ctx.data.action = failedAction2(ctx.output.reason);
|
|
5597
|
+
return;
|
|
5598
|
+
}
|
|
5599
|
+
process.stdout.write(
|
|
5600
|
+
`
|
|
5601
|
+
QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
|
|
5602
|
+
`
|
|
5603
|
+
);
|
|
5604
|
+
ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
|
|
5605
|
+
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
5606
|
+
return;
|
|
5607
|
+
}
|
|
5608
|
+
await promoteReportToGoal(
|
|
5609
|
+
ctx,
|
|
5610
|
+
finalText,
|
|
5611
|
+
ctx.args.scope,
|
|
5612
|
+
ctx.args.goal
|
|
5613
|
+
);
|
|
5614
|
+
};
|
|
5615
|
+
async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
|
|
5517
5616
|
const { markdown, data, jsonError } = splitReport(finalText);
|
|
5518
5617
|
const verdict = detectVerdict(markdown);
|
|
5519
5618
|
const findings = data?.findings ?? [];
|
|
5520
|
-
const existingIssue = ctx.args.issue;
|
|
5521
5619
|
if (findings.length === 0 || jsonError) {
|
|
5522
5620
|
if (jsonError) {
|
|
5523
|
-
process.stderr.write(`[
|
|
5621
|
+
process.stderr.write(`[promoteReportToGoal] JSON parse: ${jsonError} \u2014 falling back to single-issue mode
|
|
5524
5622
|
`);
|
|
5525
5623
|
}
|
|
5526
|
-
if (typeof existingIssue === "number" && existingIssue > 0) {
|
|
5527
|
-
try {
|
|
5528
|
-
postIssueComment(existingIssue, finalText, ctx.cwd);
|
|
5529
|
-
} catch (err) {
|
|
5530
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5531
|
-
ctx.output.exitCode = 4;
|
|
5532
|
-
ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
|
|
5533
|
-
ctx.data.action = failedAction2(ctx.output.reason);
|
|
5534
|
-
return;
|
|
5535
|
-
}
|
|
5536
|
-
process.stdout.write(
|
|
5537
|
-
`
|
|
5538
|
-
QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
|
|
5539
|
-
`
|
|
5540
|
-
);
|
|
5541
|
-
ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
|
|
5542
|
-
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
5543
|
-
return;
|
|
5544
|
-
}
|
|
5545
5624
|
ensureLabel(FINDING_LABEL, "ededed", "kody: QA finding", ctx.cwd);
|
|
5546
|
-
const scope2 =
|
|
5625
|
+
const scope2 = scopeArg;
|
|
5547
5626
|
const title = `QA [${verdict}]: ${scope2?.trim() || "smoke"} \u2014 ${todayIso()}`.slice(0, 240);
|
|
5548
5627
|
let url = "";
|
|
5549
5628
|
try {
|
|
@@ -5571,8 +5650,8 @@ QA_REPORT_POSTED=${url} (verdict: ${verdict})
|
|
|
5571
5650
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
5572
5651
|
return;
|
|
5573
5652
|
}
|
|
5574
|
-
const explicitGoal =
|
|
5575
|
-
const scope =
|
|
5653
|
+
const explicitGoal = explicitGoalArg?.trim();
|
|
5654
|
+
const scope = scopeArg;
|
|
5576
5655
|
let goalId;
|
|
5577
5656
|
let manifestIssueNumber = null;
|
|
5578
5657
|
let manifestCreated = false;
|
|
@@ -5670,7 +5749,7 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
|
5670
5749
|
mode: explicitGoal ? "goal-attach" : manifestCreated ? "goal-create" : "goal-append"
|
|
5671
5750
|
});
|
|
5672
5751
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
5673
|
-
}
|
|
5752
|
+
}
|
|
5674
5753
|
|
|
5675
5754
|
// src/goal/operations.ts
|
|
5676
5755
|
init_issue();
|
|
@@ -9382,6 +9461,17 @@ var parseJobStateFromAgentResult = async (ctx, _profile, agentResult, args) => {
|
|
|
9382
9461
|
}
|
|
9383
9462
|
const result = extractNextStateFromText(agentResult.finalText, fenceLabel, prevRev);
|
|
9384
9463
|
if (result.error) {
|
|
9464
|
+
const cleanFinishNoBlock = result.error.startsWith("missing `") && agentResult.outcome === "completed" && loaded != null;
|
|
9465
|
+
if (cleanFinishNoBlock) {
|
|
9466
|
+
ctx.data.nextJobState = {
|
|
9467
|
+
version: 1,
|
|
9468
|
+
rev: prevRev + 1,
|
|
9469
|
+
cursor: loaded.state.cursor,
|
|
9470
|
+
data: loaded.state.data,
|
|
9471
|
+
done: loaded.state.done
|
|
9472
|
+
};
|
|
9473
|
+
return;
|
|
9474
|
+
}
|
|
9385
9475
|
ctx.data.nextStateParseError = result.error.startsWith("missing `") ? `agent did not emit a \`${fenceLabel}\` fenced block` : result.error;
|
|
9386
9476
|
return;
|
|
9387
9477
|
}
|
|
@@ -9502,7 +9592,7 @@ var persistFlowState = async (ctx) => {
|
|
|
9502
9592
|
};
|
|
9503
9593
|
|
|
9504
9594
|
// src/scripts/poolServe.ts
|
|
9505
|
-
import { spawn as
|
|
9595
|
+
import { spawn as spawn4 } from "child_process";
|
|
9506
9596
|
import { createServer as createServer2 } from "http";
|
|
9507
9597
|
|
|
9508
9598
|
// src/pool/fly.ts
|
|
@@ -10072,14 +10162,14 @@ function sendJson2(res, status, body) {
|
|
|
10072
10162
|
res.end(JSON.stringify(body));
|
|
10073
10163
|
}
|
|
10074
10164
|
function readJsonBody2(req) {
|
|
10075
|
-
return new Promise((
|
|
10165
|
+
return new Promise((resolve5, reject) => {
|
|
10076
10166
|
const chunks = [];
|
|
10077
10167
|
req.on("data", (c) => chunks.push(c));
|
|
10078
10168
|
req.on("end", () => {
|
|
10079
10169
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
10080
|
-
if (!raw.trim()) return
|
|
10170
|
+
if (!raw.trim()) return resolve5({});
|
|
10081
10171
|
try {
|
|
10082
|
-
|
|
10172
|
+
resolve5(JSON.parse(raw));
|
|
10083
10173
|
} catch (err) {
|
|
10084
10174
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
10085
10175
|
}
|
|
@@ -10121,7 +10211,7 @@ function superviseLitellm() {
|
|
|
10121
10211
|
let restarts = 0;
|
|
10122
10212
|
const start = () => {
|
|
10123
10213
|
log(`starting litellm child (port ${port}, host ${host})`);
|
|
10124
|
-
const child =
|
|
10214
|
+
const child = spawn4("litellm", ["--config", config, "--port", port, "--host", host], {
|
|
10125
10215
|
stdio: "inherit"
|
|
10126
10216
|
});
|
|
10127
10217
|
child.on("exit", (code) => {
|
|
@@ -10226,10 +10316,10 @@ var poolServe = async (ctx) => {
|
|
|
10226
10316
|
}
|
|
10227
10317
|
});
|
|
10228
10318
|
const apiHost = process.env.POOL_API_HOST ?? "::";
|
|
10229
|
-
await new Promise((
|
|
10319
|
+
await new Promise((resolve5) => {
|
|
10230
10320
|
server.listen(apiPort, apiHost, () => {
|
|
10231
10321
|
log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
|
|
10232
|
-
|
|
10322
|
+
resolve5();
|
|
10233
10323
|
});
|
|
10234
10324
|
});
|
|
10235
10325
|
const shutdown = (signal) => {
|
|
@@ -10742,6 +10832,46 @@ function pushEmptyCommit(branch, cwd) {
|
|
|
10742
10832
|
}
|
|
10743
10833
|
}
|
|
10744
10834
|
|
|
10835
|
+
// src/scripts/promoteQaGoal.ts
|
|
10836
|
+
init_issue();
|
|
10837
|
+
var REPORT_JSON_OPEN2 = "<!-- KODY_QA_REPORT_JSON";
|
|
10838
|
+
var promoteQaGoal = async (ctx) => {
|
|
10839
|
+
ctx.skipAgent = true;
|
|
10840
|
+
const issueNum = ctx.args.issue;
|
|
10841
|
+
if (typeof issueNum !== "number" || issueNum <= 0) {
|
|
10842
|
+
ctx.output.exitCode = 2;
|
|
10843
|
+
ctx.output.reason = "qa-goal requires --issue <n>";
|
|
10844
|
+
process.stderr.write("[qa-goal] missing --issue\n");
|
|
10845
|
+
return;
|
|
10846
|
+
}
|
|
10847
|
+
let report;
|
|
10848
|
+
try {
|
|
10849
|
+
const issue = getIssue(issueNum, ctx.cwd);
|
|
10850
|
+
const reportComment = [...issue.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
|
|
10851
|
+
if (!reportComment) {
|
|
10852
|
+
ctx.output.exitCode = 3;
|
|
10853
|
+
ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
|
|
10854
|
+
process.stderr.write(`[qa-goal] ${ctx.output.reason}
|
|
10855
|
+
`);
|
|
10856
|
+
return;
|
|
10857
|
+
}
|
|
10858
|
+
report = reportComment.body;
|
|
10859
|
+
} catch (err) {
|
|
10860
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10861
|
+
ctx.output.exitCode = 3;
|
|
10862
|
+
ctx.output.reason = `failed to read issue #${issueNum}: ${msg}`;
|
|
10863
|
+
process.stderr.write(`[qa-goal] ${ctx.output.reason}
|
|
10864
|
+
`);
|
|
10865
|
+
return;
|
|
10866
|
+
}
|
|
10867
|
+
await promoteReportToGoal(
|
|
10868
|
+
ctx,
|
|
10869
|
+
report,
|
|
10870
|
+
ctx.args.scope,
|
|
10871
|
+
ctx.args.goal
|
|
10872
|
+
);
|
|
10873
|
+
};
|
|
10874
|
+
|
|
10745
10875
|
// src/deployments.ts
|
|
10746
10876
|
init_issue();
|
|
10747
10877
|
function findPreviewDeploymentUrl(prNumber, cwd) {
|
|
@@ -11083,7 +11213,7 @@ function resolveBaseOverride(value) {
|
|
|
11083
11213
|
}
|
|
11084
11214
|
|
|
11085
11215
|
// src/scripts/runnerServe.ts
|
|
11086
|
-
import { spawn as
|
|
11216
|
+
import { spawn as spawn5 } from "child_process";
|
|
11087
11217
|
import { createServer as createServer3 } from "http";
|
|
11088
11218
|
import * as fs37 from "fs";
|
|
11089
11219
|
var DEFAULT_PORT2 = 8080;
|
|
@@ -11107,17 +11237,17 @@ function authOk2(req, expected) {
|
|
|
11107
11237
|
return false;
|
|
11108
11238
|
}
|
|
11109
11239
|
function readJsonBody3(req) {
|
|
11110
|
-
return new Promise((
|
|
11240
|
+
return new Promise((resolve5, reject) => {
|
|
11111
11241
|
const chunks = [];
|
|
11112
11242
|
req.on("data", (c) => chunks.push(c));
|
|
11113
11243
|
req.on("end", () => {
|
|
11114
11244
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
11115
11245
|
if (!raw.trim()) {
|
|
11116
|
-
|
|
11246
|
+
resolve5({});
|
|
11117
11247
|
return;
|
|
11118
11248
|
}
|
|
11119
11249
|
try {
|
|
11120
|
-
|
|
11250
|
+
resolve5(JSON.parse(raw));
|
|
11121
11251
|
} catch (err) {
|
|
11122
11252
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
11123
11253
|
}
|
|
@@ -11192,13 +11322,13 @@ async function defaultRunJob(job) {
|
|
|
11192
11322
|
...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
|
|
11193
11323
|
...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
|
|
11194
11324
|
};
|
|
11195
|
-
const run = (cmd, args, cwd) => new Promise((
|
|
11196
|
-
const child =
|
|
11197
|
-
child.on("exit", (code) =>
|
|
11325
|
+
const run = (cmd, args, cwd) => new Promise((resolve5) => {
|
|
11326
|
+
const child = spawn5(cmd, args, { stdio: "inherit", env: childEnv, cwd });
|
|
11327
|
+
child.on("exit", (code) => resolve5(code ?? 0));
|
|
11198
11328
|
child.on("error", (err) => {
|
|
11199
11329
|
process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
|
|
11200
11330
|
`);
|
|
11201
|
-
|
|
11331
|
+
resolve5(1);
|
|
11202
11332
|
});
|
|
11203
11333
|
});
|
|
11204
11334
|
process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
|
|
@@ -11285,11 +11415,11 @@ var runnerServe = async (ctx) => {
|
|
|
11285
11415
|
const port = Number(process.env.PORT ?? DEFAULT_PORT2);
|
|
11286
11416
|
const server = buildServer2({ apiKey });
|
|
11287
11417
|
const host = process.env.RUNNER_HOST ?? "::";
|
|
11288
|
-
await new Promise((
|
|
11418
|
+
await new Promise((resolve5) => {
|
|
11289
11419
|
server.listen(port, host, () => {
|
|
11290
11420
|
process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
|
|
11291
11421
|
`);
|
|
11292
|
-
|
|
11422
|
+
resolve5();
|
|
11293
11423
|
});
|
|
11294
11424
|
});
|
|
11295
11425
|
const shutdown = (signal) => {
|
|
@@ -11304,7 +11434,7 @@ var runnerServe = async (ctx) => {
|
|
|
11304
11434
|
};
|
|
11305
11435
|
|
|
11306
11436
|
// src/scripts/runTickScript.ts
|
|
11307
|
-
import { spawnSync } from "child_process";
|
|
11437
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
11308
11438
|
import * as fs38 from "fs";
|
|
11309
11439
|
import * as path36 from "path";
|
|
11310
11440
|
var runTickScript = async (ctx, _profile, args) => {
|
|
@@ -11350,7 +11480,7 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
11350
11480
|
ctx.data.jobSlug = slug;
|
|
11351
11481
|
ctx.data.jobState = loaded;
|
|
11352
11482
|
const childEnv = buildChildEnv(process.env, Boolean(ctx.args.force));
|
|
11353
|
-
const result =
|
|
11483
|
+
const result = spawnSync2("bash", [scriptPath], {
|
|
11354
11484
|
cwd: ctx.cwd,
|
|
11355
11485
|
env: childEnv,
|
|
11356
11486
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -11483,7 +11613,7 @@ function synthesizeAction(ctx) {
|
|
|
11483
11613
|
}
|
|
11484
11614
|
|
|
11485
11615
|
// src/scripts/serveFlow.ts
|
|
11486
|
-
import { spawn as
|
|
11616
|
+
import { spawn as spawn6 } from "child_process";
|
|
11487
11617
|
function parseTarget(positional) {
|
|
11488
11618
|
if (!Array.isArray(positional) || positional.length === 0) return "none";
|
|
11489
11619
|
const first = String(positional[0]).toLowerCase();
|
|
@@ -11532,15 +11662,15 @@ var serveFlow = async (ctx) => {
|
|
|
11532
11662
|
if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
|
|
11533
11663
|
`);
|
|
11534
11664
|
const args = ["--dangerously-skip-permissions", "--model", model.model];
|
|
11535
|
-
const child =
|
|
11536
|
-
const exitCode = await new Promise((
|
|
11537
|
-
child.on("exit", (code) =>
|
|
11665
|
+
const child = spawn6("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
|
|
11666
|
+
const exitCode = await new Promise((resolve5) => {
|
|
11667
|
+
child.on("exit", (code) => resolve5(code ?? 0));
|
|
11538
11668
|
child.on("error", (err) => {
|
|
11539
11669
|
process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
|
|
11540
11670
|
`);
|
|
11541
11671
|
process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
|
|
11542
11672
|
`);
|
|
11543
|
-
|
|
11673
|
+
resolve5(1);
|
|
11544
11674
|
});
|
|
11545
11675
|
});
|
|
11546
11676
|
killProxy();
|
|
@@ -11553,7 +11683,7 @@ var serveFlow = async (ctx) => {
|
|
|
11553
11683
|
if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
|
|
11554
11684
|
`);
|
|
11555
11685
|
try {
|
|
11556
|
-
const code =
|
|
11686
|
+
const code = spawn6("code", [ctx.cwd], { stdio: "inherit", env: editorEnv, detached: true });
|
|
11557
11687
|
code.on("error", (err) => {
|
|
11558
11688
|
process.stderr.write(`[kody serve] failed to launch VS Code: ${err.message}
|
|
11559
11689
|
`);
|
|
@@ -11808,7 +11938,7 @@ var verify = async (ctx) => {
|
|
|
11808
11938
|
};
|
|
11809
11939
|
|
|
11810
11940
|
// src/scripts/verifyReproFails.ts
|
|
11811
|
-
import { spawn as
|
|
11941
|
+
import { spawn as spawn7 } from "child_process";
|
|
11812
11942
|
var TEST_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
11813
11943
|
var TAIL_CHARS2 = 8e3;
|
|
11814
11944
|
var ANSI_RE2 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
@@ -11876,8 +12006,8 @@ function stripAnsi2(s) {
|
|
|
11876
12006
|
return s.replace(ANSI_RE2, "");
|
|
11877
12007
|
}
|
|
11878
12008
|
function runCommand2(command, cwd) {
|
|
11879
|
-
return new Promise((
|
|
11880
|
-
const child =
|
|
12009
|
+
return new Promise((resolve5) => {
|
|
12010
|
+
const child = spawn7(command, {
|
|
11881
12011
|
cwd,
|
|
11882
12012
|
shell: true,
|
|
11883
12013
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
|
|
@@ -11903,11 +12033,11 @@ function runCommand2(command, cwd) {
|
|
|
11903
12033
|
}, TEST_TIMEOUT_MS);
|
|
11904
12034
|
child.on("exit", (code) => {
|
|
11905
12035
|
clearTimeout(timer);
|
|
11906
|
-
|
|
12036
|
+
resolve5({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
|
|
11907
12037
|
});
|
|
11908
12038
|
child.on("error", (err) => {
|
|
11909
12039
|
clearTimeout(timer);
|
|
11910
|
-
|
|
12040
|
+
resolve5({ exitCode: -1, output: err.message });
|
|
11911
12041
|
});
|
|
11912
12042
|
});
|
|
11913
12043
|
}
|
|
@@ -12206,7 +12336,7 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
|
|
|
12206
12336
|
};
|
|
12207
12337
|
|
|
12208
12338
|
// src/scripts/warmupMcp.ts
|
|
12209
|
-
import { spawn as
|
|
12339
|
+
import { spawn as spawn8 } from "child_process";
|
|
12210
12340
|
var PER_SERVER_TIMEOUT_MS = 6e4;
|
|
12211
12341
|
var PER_REQUEST_TIMEOUT_MS = 2e4;
|
|
12212
12342
|
var warmupMcp = async (_ctx, profile) => {
|
|
@@ -12228,7 +12358,7 @@ var warmupMcp = async (_ctx, profile) => {
|
|
|
12228
12358
|
}
|
|
12229
12359
|
};
|
|
12230
12360
|
async function warmupOne(command, args, env) {
|
|
12231
|
-
const child =
|
|
12361
|
+
const child = spawn8(command, args, {
|
|
12232
12362
|
stdio: ["pipe", "pipe", "pipe"],
|
|
12233
12363
|
env: env ? { ...process.env, ...env } : process.env
|
|
12234
12364
|
});
|
|
@@ -12329,20 +12459,20 @@ function lineStream(stream) {
|
|
|
12329
12459
|
tryDeliver();
|
|
12330
12460
|
});
|
|
12331
12461
|
return {
|
|
12332
|
-
next: (timeoutMs) => new Promise((
|
|
12462
|
+
next: (timeoutMs) => new Promise((resolve5) => {
|
|
12333
12463
|
if (queue.length > 0) {
|
|
12334
|
-
|
|
12464
|
+
resolve5(queue.shift());
|
|
12335
12465
|
return;
|
|
12336
12466
|
}
|
|
12337
12467
|
if (ended) {
|
|
12338
|
-
|
|
12468
|
+
resolve5(null);
|
|
12339
12469
|
return;
|
|
12340
12470
|
}
|
|
12341
|
-
waiter =
|
|
12471
|
+
waiter = resolve5;
|
|
12342
12472
|
const t = setTimeout(() => {
|
|
12343
|
-
if (waiter ===
|
|
12473
|
+
if (waiter === resolve5) {
|
|
12344
12474
|
waiter = null;
|
|
12345
|
-
|
|
12475
|
+
resolve5(null);
|
|
12346
12476
|
}
|
|
12347
12477
|
}, Math.max(0, timeoutMs));
|
|
12348
12478
|
t.unref?.();
|
|
@@ -12495,6 +12625,7 @@ var preflightScripts = {
|
|
|
12495
12625
|
discoverQaContext,
|
|
12496
12626
|
resolvePreviewUrl,
|
|
12497
12627
|
resolveQaUrl,
|
|
12628
|
+
promoteQaGoal,
|
|
12498
12629
|
composePrompt,
|
|
12499
12630
|
setCommentTarget,
|
|
12500
12631
|
setLifecycleLabel,
|
|
@@ -13056,7 +13187,7 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
13056
13187
|
env[`KODY_CFG_${k}`] = v;
|
|
13057
13188
|
}
|
|
13058
13189
|
const timeoutMs = resolveShellTimeoutMs(entry);
|
|
13059
|
-
const child =
|
|
13190
|
+
const child = spawn9("bash", [shellPath, ...positional], {
|
|
13060
13191
|
cwd: ctx.cwd,
|
|
13061
13192
|
env,
|
|
13062
13193
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -13078,14 +13209,14 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
13078
13209
|
let killTimer;
|
|
13079
13210
|
let escalateTimer;
|
|
13080
13211
|
const result = await new Promise(
|
|
13081
|
-
(
|
|
13212
|
+
(resolve5) => {
|
|
13082
13213
|
let settled = false;
|
|
13083
13214
|
const settle = (code, signal, spawnErr) => {
|
|
13084
13215
|
if (settled) return;
|
|
13085
13216
|
settled = true;
|
|
13086
13217
|
if (killTimer) clearTimeout(killTimer);
|
|
13087
13218
|
if (escalateTimer) clearTimeout(escalateTimer);
|
|
13088
|
-
|
|
13219
|
+
resolve5({ code, signal, spawnErr });
|
|
13089
13220
|
};
|
|
13090
13221
|
child.on("error", (err) => settle(null, null, err));
|
|
13091
13222
|
child.on("close", (code, signal) => settle(code, signal));
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qa-goal",
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"kind": "oneshot",
|
|
5
|
+
"describe": "Operator-gated half of QA: promotes a QA report (already posted on an issue by qa-engineer) into a goal — manifest entry + one fix-ticket per finding + a committed .kody/goals/<id>/state.json. Deterministic, no agent. The qa / qa-sweep duties surface a `@kody qa-goal --issue <n>` inbox rec; this runs when the operator approves it, so QA never auto-creates goals on its own.",
|
|
6
|
+
"inputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "issue",
|
|
9
|
+
"flag": "--issue",
|
|
10
|
+
"type": "int",
|
|
11
|
+
"required": true,
|
|
12
|
+
"describe": "Issue number carrying qa-engineer's QA report (the comment with the <!-- KODY_QA_REPORT_JSON --> block)."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "scope",
|
|
16
|
+
"flag": "--scope",
|
|
17
|
+
"type": "string",
|
|
18
|
+
"required": false,
|
|
19
|
+
"describe": "Optional scope label for the goal name (e.g. the changelog entry title). Defaults to 'smoke'."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "goal",
|
|
23
|
+
"flag": "--goal",
|
|
24
|
+
"type": "string",
|
|
25
|
+
"required": false,
|
|
26
|
+
"describe": "Optional existing goal id to attach findings to instead of creating a new one."
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"claudeCode": {
|
|
30
|
+
"model": "inherit",
|
|
31
|
+
"permissionMode": "default",
|
|
32
|
+
"maxTurns": null,
|
|
33
|
+
"maxThinkingTokens": null,
|
|
34
|
+
"systemPromptAppend": null,
|
|
35
|
+
"tools": [],
|
|
36
|
+
"hooks": [],
|
|
37
|
+
"skills": [],
|
|
38
|
+
"commands": [],
|
|
39
|
+
"subagents": [],
|
|
40
|
+
"plugins": [],
|
|
41
|
+
"mcpServers": []
|
|
42
|
+
},
|
|
43
|
+
"cliTools": [
|
|
44
|
+
{
|
|
45
|
+
"name": "gh",
|
|
46
|
+
"install": {
|
|
47
|
+
"required": true,
|
|
48
|
+
"checkCommand": "command -v gh"
|
|
49
|
+
},
|
|
50
|
+
"verify": "gh auth status",
|
|
51
|
+
"usage": "Reads the QA report (`gh issue view --json comments`), appends to the goals manifest, and opens fix-ticket issues.",
|
|
52
|
+
"allowedUses": ["issue", "api"]
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"inputArtifacts": [],
|
|
56
|
+
"outputArtifacts": [],
|
|
57
|
+
"scripts": {
|
|
58
|
+
"preflight": [
|
|
59
|
+
{
|
|
60
|
+
"script": "promoteQaGoal"
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"postflight": []
|
|
64
|
+
}
|
|
65
|
+
}
|
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.140",
|
|
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",
|