@kody-ade/kody-engine 0.4.108 → 0.4.110
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 +791 -138
- package/dist/executables/goal-scheduler/scheduler.sh +0 -0
- package/dist/executables/pool-serve/profile.json +28 -0
- package/dist/executables/release-deploy/deploy.sh +0 -0
- package/dist/executables/release-prepare/prepare.sh +0 -0
- package/dist/executables/release-publish/publish.sh +0 -0
- package/dist/executables/resolve/apply-prefer.sh +0 -0
- package/dist/executables/revert/revert.sh +0 -0
- package/dist/executables/review/agents/review-architecture.md +33 -0
- package/dist/executables/review/agents/review-security.md +5 -2
- package/dist/executables/review/profile.json +1 -1
- package/dist/executables/review/prompt.md +8 -7
- package/dist/executables/runner-serve/profile.json +28 -0
- package/package.json +20 -19
- package/templates/kody.yml +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -469,16 +469,16 @@ var init_issue = __esm({
|
|
|
469
469
|
});
|
|
470
470
|
|
|
471
471
|
// src/prompt.ts
|
|
472
|
-
import * as
|
|
472
|
+
import * as fs20 from "fs";
|
|
473
473
|
import * as path18 from "path";
|
|
474
474
|
function loadProjectConventions(projectDir) {
|
|
475
475
|
const out = [];
|
|
476
476
|
for (const rel of CONVENTION_FILES) {
|
|
477
477
|
const abs = path18.join(projectDir, rel);
|
|
478
|
-
if (!
|
|
478
|
+
if (!fs20.existsSync(abs)) continue;
|
|
479
479
|
let content;
|
|
480
480
|
try {
|
|
481
|
-
content =
|
|
481
|
+
content = fs20.readFileSync(abs, "utf-8");
|
|
482
482
|
} catch {
|
|
483
483
|
continue;
|
|
484
484
|
}
|
|
@@ -626,20 +626,20 @@ var loadMemoryContext_exports = {};
|
|
|
626
626
|
__export(loadMemoryContext_exports, {
|
|
627
627
|
loadMemoryContext: () => loadMemoryContext
|
|
628
628
|
});
|
|
629
|
-
import * as
|
|
629
|
+
import * as fs35 from "fs";
|
|
630
630
|
import * as path33 from "path";
|
|
631
631
|
function collectPages(memoryAbs) {
|
|
632
632
|
const out = [];
|
|
633
633
|
walkMd(memoryAbs, (file) => {
|
|
634
634
|
let stat;
|
|
635
635
|
try {
|
|
636
|
-
stat =
|
|
636
|
+
stat = fs35.statSync(file);
|
|
637
637
|
} catch {
|
|
638
638
|
return;
|
|
639
639
|
}
|
|
640
640
|
let raw;
|
|
641
641
|
try {
|
|
642
|
-
raw =
|
|
642
|
+
raw = fs35.readFileSync(file, "utf-8");
|
|
643
643
|
} catch {
|
|
644
644
|
return;
|
|
645
645
|
}
|
|
@@ -715,7 +715,7 @@ function walkMd(root, visit) {
|
|
|
715
715
|
const dir = stack.pop();
|
|
716
716
|
let names;
|
|
717
717
|
try {
|
|
718
|
-
names =
|
|
718
|
+
names = fs35.readdirSync(dir);
|
|
719
719
|
} catch {
|
|
720
720
|
continue;
|
|
721
721
|
}
|
|
@@ -724,7 +724,7 @@ function walkMd(root, visit) {
|
|
|
724
724
|
const full = path33.join(dir, name);
|
|
725
725
|
let stat;
|
|
726
726
|
try {
|
|
727
|
-
stat =
|
|
727
|
+
stat = fs35.statSync(full);
|
|
728
728
|
} catch {
|
|
729
729
|
continue;
|
|
730
730
|
}
|
|
@@ -748,7 +748,7 @@ var init_loadMemoryContext = __esm({
|
|
|
748
748
|
loadMemoryContext = async (ctx) => {
|
|
749
749
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
750
750
|
const memoryAbs = path33.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
751
|
-
if (!
|
|
751
|
+
if (!fs35.existsSync(memoryAbs)) {
|
|
752
752
|
ctx.data.memoryContext = "";
|
|
753
753
|
return;
|
|
754
754
|
}
|
|
@@ -880,7 +880,7 @@ var init_loadPriorArt = __esm({
|
|
|
880
880
|
// package.json
|
|
881
881
|
var package_default = {
|
|
882
882
|
name: "@kody-ade/kody-engine",
|
|
883
|
-
version: "0.4.
|
|
883
|
+
version: "0.4.110",
|
|
884
884
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
885
885
|
license: "MIT",
|
|
886
886
|
type: "module",
|
|
@@ -935,7 +935,7 @@ var package_default = {
|
|
|
935
935
|
|
|
936
936
|
// src/chat-cli.ts
|
|
937
937
|
import { execFileSync as execFileSync32 } from "child_process";
|
|
938
|
-
import * as
|
|
938
|
+
import * as fs41 from "fs";
|
|
939
939
|
import * as path38 from "path";
|
|
940
940
|
|
|
941
941
|
// src/chat/events.ts
|
|
@@ -2386,7 +2386,7 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
2386
2386
|
|
|
2387
2387
|
// src/kody-cli.ts
|
|
2388
2388
|
import { execFileSync as execFileSync31 } from "child_process";
|
|
2389
|
-
import * as
|
|
2389
|
+
import * as fs40 from "fs";
|
|
2390
2390
|
import * as path37 from "path";
|
|
2391
2391
|
|
|
2392
2392
|
// src/dispatch.ts
|
|
@@ -2708,8 +2708,8 @@ function coerceBare(spec, value) {
|
|
|
2708
2708
|
init_issue();
|
|
2709
2709
|
|
|
2710
2710
|
// src/executor.ts
|
|
2711
|
-
import { execFileSync as execFileSync30, spawn as
|
|
2712
|
-
import * as
|
|
2711
|
+
import { execFileSync as execFileSync30, spawn as spawn8 } from "child_process";
|
|
2712
|
+
import * as fs39 from "fs";
|
|
2713
2713
|
import * as path36 from "path";
|
|
2714
2714
|
|
|
2715
2715
|
// src/discipline.ts
|
|
@@ -4461,10 +4461,10 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4461
4461
|
litellmUrl: opts.litellmUrl,
|
|
4462
4462
|
sink
|
|
4463
4463
|
}).catch((err) => {
|
|
4464
|
-
const
|
|
4465
|
-
process.stderr.write(`[brain-serve] chat turn failed: ${
|
|
4464
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
4465
|
+
process.stderr.write(`[brain-serve] chat turn failed: ${errMsg3}
|
|
4466
4466
|
`);
|
|
4467
|
-
endTurnIfUnterminated(opts.cwd, chatId,
|
|
4467
|
+
endTurnIfUnterminated(opts.cwd, chatId, errMsg3);
|
|
4468
4468
|
}).finally(() => {
|
|
4469
4469
|
endTurnIfUnterminated(
|
|
4470
4470
|
opts.cwd,
|
|
@@ -4581,6 +4581,657 @@ var brainServe = async (ctx) => {
|
|
|
4581
4581
|
});
|
|
4582
4582
|
};
|
|
4583
4583
|
|
|
4584
|
+
// src/scripts/runnerServe.ts
|
|
4585
|
+
import { spawn as spawn3 } from "child_process";
|
|
4586
|
+
import { createServer as createServer2 } from "http";
|
|
4587
|
+
import * as fs19 from "fs";
|
|
4588
|
+
var DEFAULT_PORT2 = 8080;
|
|
4589
|
+
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
4590
|
+
function getApiKey2() {
|
|
4591
|
+
const key = (process.env.RUNNER_API_KEY ?? "").trim();
|
|
4592
|
+
if (!key) {
|
|
4593
|
+
throw new Error(
|
|
4594
|
+
"RUNNER_API_KEY env var is required \u2014 set it on the pooled machine before boot."
|
|
4595
|
+
);
|
|
4596
|
+
}
|
|
4597
|
+
return key;
|
|
4598
|
+
}
|
|
4599
|
+
function authOk2(req, expected) {
|
|
4600
|
+
const xApiKey = req.headers["x-api-key"]?.trim();
|
|
4601
|
+
if (xApiKey && xApiKey === expected) return true;
|
|
4602
|
+
const auth = req.headers["authorization"]?.trim();
|
|
4603
|
+
if (auth && auth.toLowerCase().startsWith("bearer ")) {
|
|
4604
|
+
return auth.slice(7).trim() === expected;
|
|
4605
|
+
}
|
|
4606
|
+
return false;
|
|
4607
|
+
}
|
|
4608
|
+
function readJsonBody2(req) {
|
|
4609
|
+
return new Promise((resolve4, reject) => {
|
|
4610
|
+
const chunks = [];
|
|
4611
|
+
req.on("data", (c) => chunks.push(c));
|
|
4612
|
+
req.on("end", () => {
|
|
4613
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
4614
|
+
if (!raw.trim()) {
|
|
4615
|
+
resolve4({});
|
|
4616
|
+
return;
|
|
4617
|
+
}
|
|
4618
|
+
try {
|
|
4619
|
+
resolve4(JSON.parse(raw));
|
|
4620
|
+
} catch (err) {
|
|
4621
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4622
|
+
}
|
|
4623
|
+
});
|
|
4624
|
+
req.on("error", reject);
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
4627
|
+
function sendJson2(res, status, body) {
|
|
4628
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
4629
|
+
res.end(JSON.stringify(body));
|
|
4630
|
+
}
|
|
4631
|
+
function parseJob(body) {
|
|
4632
|
+
if (typeof body !== "object" || body === null) return { error: "body must be an object" };
|
|
4633
|
+
const b = body;
|
|
4634
|
+
const jobId = typeof b.jobId === "string" ? b.jobId.trim() : "";
|
|
4635
|
+
if (!jobId) return { error: "jobId required" };
|
|
4636
|
+
const repo = typeof b.repo === "string" ? b.repo.trim() : "";
|
|
4637
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
|
|
4638
|
+
const issueNumber = Number(b.issueNumber);
|
|
4639
|
+
if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
|
|
4640
|
+
return { error: "issueNumber (positive integer) required" };
|
|
4641
|
+
}
|
|
4642
|
+
const githubToken = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
|
|
4643
|
+
if (!githubToken) return { error: "githubToken required" };
|
|
4644
|
+
const job = { jobId, repo, issueNumber, githubToken };
|
|
4645
|
+
if (typeof b.ref === "string" && b.ref.trim()) job.ref = b.ref.trim();
|
|
4646
|
+
if (typeof b.model === "string" && b.model.trim()) job.model = b.model.trim();
|
|
4647
|
+
if (typeof b.sessionId === "string" && b.sessionId.trim()) job.sessionId = b.sessionId.trim();
|
|
4648
|
+
if (typeof b.dashboardUrl === "string" && b.dashboardUrl.trim()) job.dashboardUrl = b.dashboardUrl.trim();
|
|
4649
|
+
if (b.allSecrets && (typeof b.allSecrets === "object" || typeof b.allSecrets === "string")) {
|
|
4650
|
+
job.allSecrets = b.allSecrets;
|
|
4651
|
+
}
|
|
4652
|
+
return { job };
|
|
4653
|
+
}
|
|
4654
|
+
async function defaultRunJob(job) {
|
|
4655
|
+
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
4656
|
+
const branch = job.ref ?? "main";
|
|
4657
|
+
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
4658
|
+
fs19.rmSync(workdir, { recursive: true, force: true });
|
|
4659
|
+
fs19.mkdirSync(workdir, { recursive: true });
|
|
4660
|
+
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
4661
|
+
const childEnv = {
|
|
4662
|
+
...process.env,
|
|
4663
|
+
REPO: job.repo,
|
|
4664
|
+
REF: branch,
|
|
4665
|
+
GITHUB_TOKEN: job.githubToken,
|
|
4666
|
+
ISSUE_NUMBER: String(job.issueNumber),
|
|
4667
|
+
ALL_SECRETS: allSecrets,
|
|
4668
|
+
SESSION_ID: job.sessionId ?? "",
|
|
4669
|
+
DASHBOARD_URL: job.dashboardUrl ?? "",
|
|
4670
|
+
MODEL: job.model ?? ""
|
|
4671
|
+
};
|
|
4672
|
+
const run = (cmd, args, cwd) => new Promise((resolve4) => {
|
|
4673
|
+
const child = spawn3(cmd, args, { stdio: "inherit", env: childEnv, cwd });
|
|
4674
|
+
child.on("exit", (code) => resolve4(code ?? 0));
|
|
4675
|
+
child.on("error", (err) => {
|
|
4676
|
+
process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
|
|
4677
|
+
`);
|
|
4678
|
+
resolve4(1);
|
|
4679
|
+
});
|
|
4680
|
+
});
|
|
4681
|
+
process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
|
|
4682
|
+
`);
|
|
4683
|
+
const cloneCode = await run("git", [
|
|
4684
|
+
"clone",
|
|
4685
|
+
"--depth=1",
|
|
4686
|
+
"--single-branch",
|
|
4687
|
+
"--branch",
|
|
4688
|
+
branch,
|
|
4689
|
+
authUrl,
|
|
4690
|
+
workdir
|
|
4691
|
+
]);
|
|
4692
|
+
if (cloneCode !== 0) {
|
|
4693
|
+
process.stderr.write(`[runner-serve] job ${job.jobId}: clone failed (${cloneCode})
|
|
4694
|
+
`);
|
|
4695
|
+
process.exit(cloneCode);
|
|
4696
|
+
return;
|
|
4697
|
+
}
|
|
4698
|
+
process.stdout.write(`[runner-serve] job ${job.jobId}: running issue #${job.issueNumber}
|
|
4699
|
+
`);
|
|
4700
|
+
const runCode = await run("kody", ["run", "--issue", String(job.issueNumber)], workdir);
|
|
4701
|
+
process.stdout.write(`[runner-serve] job ${job.jobId}: finished (exit ${runCode})
|
|
4702
|
+
`);
|
|
4703
|
+
process.exit(runCode);
|
|
4704
|
+
}
|
|
4705
|
+
function buildServer2(opts) {
|
|
4706
|
+
const runJob = opts.runJob ?? defaultRunJob;
|
|
4707
|
+
let busy = false;
|
|
4708
|
+
return createServer2(async (req, res) => {
|
|
4709
|
+
if (!req.method || !req.url) {
|
|
4710
|
+
sendJson2(res, 400, { error: "bad request" });
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4713
|
+
const url = new URL(req.url, "http://localhost");
|
|
4714
|
+
if (req.method === "GET" && url.pathname === "/healthz") {
|
|
4715
|
+
sendJson2(res, 200, { ok: true, busy });
|
|
4716
|
+
return;
|
|
4717
|
+
}
|
|
4718
|
+
if (!authOk2(req, opts.apiKey)) {
|
|
4719
|
+
sendJson2(res, 401, { error: "unauthorized" });
|
|
4720
|
+
return;
|
|
4721
|
+
}
|
|
4722
|
+
if (req.method === "POST" && url.pathname === "/run") {
|
|
4723
|
+
if (busy) {
|
|
4724
|
+
sendJson2(res, 409, { error: "runner busy" });
|
|
4725
|
+
return;
|
|
4726
|
+
}
|
|
4727
|
+
let body;
|
|
4728
|
+
try {
|
|
4729
|
+
body = await readJsonBody2(req);
|
|
4730
|
+
} catch {
|
|
4731
|
+
sendJson2(res, 400, { error: "invalid JSON body" });
|
|
4732
|
+
return;
|
|
4733
|
+
}
|
|
4734
|
+
const parsed = parseJob(body);
|
|
4735
|
+
if ("error" in parsed) {
|
|
4736
|
+
sendJson2(res, 400, { error: parsed.error });
|
|
4737
|
+
return;
|
|
4738
|
+
}
|
|
4739
|
+
busy = true;
|
|
4740
|
+
sendJson2(res, 202, { ok: true, jobId: parsed.job.jobId, started: true });
|
|
4741
|
+
void runJob(parsed.job).catch((err) => {
|
|
4742
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4743
|
+
process.stderr.write(`[runner-serve] job ${parsed.job.jobId} crashed: ${msg}
|
|
4744
|
+
`);
|
|
4745
|
+
process.exit(1);
|
|
4746
|
+
});
|
|
4747
|
+
return;
|
|
4748
|
+
}
|
|
4749
|
+
sendJson2(res, 404, { error: "not found" });
|
|
4750
|
+
});
|
|
4751
|
+
}
|
|
4752
|
+
var runnerServe = async (ctx) => {
|
|
4753
|
+
ctx.skipAgent = true;
|
|
4754
|
+
const apiKey = getApiKey2();
|
|
4755
|
+
const port = Number(process.env.PORT ?? DEFAULT_PORT2);
|
|
4756
|
+
const server = buildServer2({ apiKey });
|
|
4757
|
+
await new Promise((resolve4) => {
|
|
4758
|
+
server.listen(port, "0.0.0.0", () => {
|
|
4759
|
+
process.stdout.write(`[runner-serve] listening on 0.0.0.0:${port} (idle, awaiting job)
|
|
4760
|
+
`);
|
|
4761
|
+
resolve4();
|
|
4762
|
+
});
|
|
4763
|
+
});
|
|
4764
|
+
const shutdown = (signal) => {
|
|
4765
|
+
process.stdout.write(`[runner-serve] ${signal} \u2014 shutting down
|
|
4766
|
+
`);
|
|
4767
|
+
server.close(() => process.exit(0));
|
|
4768
|
+
};
|
|
4769
|
+
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
4770
|
+
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
4771
|
+
await new Promise(() => {
|
|
4772
|
+
});
|
|
4773
|
+
};
|
|
4774
|
+
|
|
4775
|
+
// src/scripts/poolServe.ts
|
|
4776
|
+
import { spawn as spawn4 } from "child_process";
|
|
4777
|
+
import { createServer as createServer3 } from "http";
|
|
4778
|
+
|
|
4779
|
+
// src/pool/fly.ts
|
|
4780
|
+
var FLY_API_BASE = "https://api.machines.dev/v1";
|
|
4781
|
+
var POOL_METADATA_KEY = "kody_pool";
|
|
4782
|
+
var POOL_METADATA_VALUE = "1";
|
|
4783
|
+
var FlyClient = class {
|
|
4784
|
+
constructor(opts) {
|
|
4785
|
+
this.opts = opts;
|
|
4786
|
+
if (!opts.token?.trim()) throw new Error("FlyClient: token required");
|
|
4787
|
+
if (!opts.app?.trim()) throw new Error("FlyClient: app required");
|
|
4788
|
+
}
|
|
4789
|
+
opts;
|
|
4790
|
+
get fetch() {
|
|
4791
|
+
return this.opts.fetchImpl ?? fetch;
|
|
4792
|
+
}
|
|
4793
|
+
async call(path39, init = {}) {
|
|
4794
|
+
const res = await this.fetch(`${FLY_API_BASE}${path39}`, {
|
|
4795
|
+
method: init.method ?? "GET",
|
|
4796
|
+
headers: {
|
|
4797
|
+
Authorization: `Bearer ${this.opts.token}`,
|
|
4798
|
+
"Content-Type": "application/json"
|
|
4799
|
+
},
|
|
4800
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
4801
|
+
});
|
|
4802
|
+
if (res.status === 404 && init.allow404) return null;
|
|
4803
|
+
if (!res.ok) {
|
|
4804
|
+
const text = await res.text().catch(() => "");
|
|
4805
|
+
throw new Error(`Fly API ${res.status} on ${path39}: ${text.slice(0, 200) || res.statusText}`);
|
|
4806
|
+
}
|
|
4807
|
+
if (res.status === 204) return null;
|
|
4808
|
+
const raw = await res.text();
|
|
4809
|
+
if (!raw.trim()) return null;
|
|
4810
|
+
return JSON.parse(raw);
|
|
4811
|
+
}
|
|
4812
|
+
/** Create + start a pooled machine in serve mode. */
|
|
4813
|
+
async createPooled(input) {
|
|
4814
|
+
const body = {
|
|
4815
|
+
region: input.region,
|
|
4816
|
+
config: {
|
|
4817
|
+
image: input.image,
|
|
4818
|
+
guest: input.guest,
|
|
4819
|
+
auto_destroy: true,
|
|
4820
|
+
restart: { policy: "no" },
|
|
4821
|
+
init: { entrypoint: ["/usr/local/bin/entrypoint-serve.sh"] },
|
|
4822
|
+
metadata: { [POOL_METADATA_KEY]: POOL_METADATA_VALUE },
|
|
4823
|
+
env: {
|
|
4824
|
+
RUNNER_API_KEY: input.runnerApiKey,
|
|
4825
|
+
KODY_LITELLM_URL: input.litellmUrl,
|
|
4826
|
+
PORT: String(input.port ?? 8080)
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
};
|
|
4830
|
+
const m = await this.call(`/apps/${enc(this.opts.app)}/machines`, { method: "POST", body });
|
|
4831
|
+
if (!m?.id) throw new Error("Fly API: createPooled returned no machine id");
|
|
4832
|
+
return m;
|
|
4833
|
+
}
|
|
4834
|
+
async get(id) {
|
|
4835
|
+
return this.call(`/apps/${enc(this.opts.app)}/machines/${enc(id)}`, { allow404: true });
|
|
4836
|
+
}
|
|
4837
|
+
/** List the app's pooled (kody_pool) machines, excluding destroyed/destroying. */
|
|
4838
|
+
async listPooled() {
|
|
4839
|
+
const all = await this.call(`/apps/${enc(this.opts.app)}/machines`, { allow404: true }) ?? [];
|
|
4840
|
+
return all.filter(
|
|
4841
|
+
(m) => m.config?.metadata?.[POOL_METADATA_KEY] === POOL_METADATA_VALUE && m.state !== "destroyed" && m.state !== "destroying"
|
|
4842
|
+
);
|
|
4843
|
+
}
|
|
4844
|
+
/** Suspend (freeze) a machine — wakes in ~1s from the snapshot. */
|
|
4845
|
+
async suspend(id) {
|
|
4846
|
+
await this.call(`/apps/${enc(this.opts.app)}/machines/${enc(id)}/suspend`, { method: "POST", allow404: true });
|
|
4847
|
+
}
|
|
4848
|
+
/** Start (wake) a suspended/stopped machine. */
|
|
4849
|
+
async start(id) {
|
|
4850
|
+
await this.call(`/apps/${enc(this.opts.app)}/machines/${enc(id)}/start`, { method: "POST", allow404: true });
|
|
4851
|
+
}
|
|
4852
|
+
async destroy(id) {
|
|
4853
|
+
await this.call(`/apps/${enc(this.opts.app)}/machines/${enc(id)}?force=true`, { method: "DELETE", allow404: true });
|
|
4854
|
+
}
|
|
4855
|
+
/**
|
|
4856
|
+
* Wait for `GET <baseUrl>/healthz` to return 200, polling until timeout.
|
|
4857
|
+
* baseUrl is the machine's private 6PN address (http://[ip]:port).
|
|
4858
|
+
*/
|
|
4859
|
+
async waitHealthy(baseUrl, opts = {}) {
|
|
4860
|
+
const timeoutMs = opts.timeoutMs ?? 12e4;
|
|
4861
|
+
const intervalMs = opts.intervalMs ?? 1e3;
|
|
4862
|
+
const deadline = Date.now() + timeoutMs;
|
|
4863
|
+
while (Date.now() < deadline) {
|
|
4864
|
+
try {
|
|
4865
|
+
const res = await this.fetch(`${baseUrl}/healthz`, { signal: AbortSignal.timeout(4e3) });
|
|
4866
|
+
if (res.ok) return true;
|
|
4867
|
+
} catch {
|
|
4868
|
+
}
|
|
4869
|
+
await sleep2(intervalMs);
|
|
4870
|
+
}
|
|
4871
|
+
return false;
|
|
4872
|
+
}
|
|
4873
|
+
};
|
|
4874
|
+
function enc(s) {
|
|
4875
|
+
return encodeURIComponent(s);
|
|
4876
|
+
}
|
|
4877
|
+
function sleep2(ms) {
|
|
4878
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4881
|
+
// src/pool/manager.ts
|
|
4882
|
+
var PoolManager = class {
|
|
4883
|
+
constructor(deps) {
|
|
4884
|
+
this.deps = deps;
|
|
4885
|
+
this.postRun = deps.postRun ?? defaultPostRun;
|
|
4886
|
+
this.log = deps.log ?? (() => {
|
|
4887
|
+
});
|
|
4888
|
+
}
|
|
4889
|
+
deps;
|
|
4890
|
+
free = [];
|
|
4891
|
+
booting = 0;
|
|
4892
|
+
claimsInFlight = 0;
|
|
4893
|
+
refilling = false;
|
|
4894
|
+
postRun;
|
|
4895
|
+
log;
|
|
4896
|
+
status() {
|
|
4897
|
+
return {
|
|
4898
|
+
min: this.deps.config.min,
|
|
4899
|
+
free: this.free.length,
|
|
4900
|
+
booting: this.booting,
|
|
4901
|
+
claimsInFlight: this.claimsInFlight,
|
|
4902
|
+
total: this.free.length + this.booting + this.claimsInFlight
|
|
4903
|
+
};
|
|
4904
|
+
}
|
|
4905
|
+
/**
|
|
4906
|
+
* Adopt existing pooled machines on owner (re)start: suspended ones become
|
|
4907
|
+
* free; anything else is left to finish/auto-destroy. Then refill to `min`.
|
|
4908
|
+
*/
|
|
4909
|
+
async reconcile() {
|
|
4910
|
+
const machines = await this.deps.fly.listPooled();
|
|
4911
|
+
this.free = [];
|
|
4912
|
+
for (const m of machines) {
|
|
4913
|
+
if ((m.state === "suspended" || m.state === "suspending") && m.private_ip) {
|
|
4914
|
+
this.free.push({ id: m.id, privateIp: m.private_ip });
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4917
|
+
this.log(`reconcile: adopted ${this.free.length} suspended machine(s)`);
|
|
4918
|
+
await this.refill();
|
|
4919
|
+
}
|
|
4920
|
+
/**
|
|
4921
|
+
* Claim a warm machine for a job. Returns ok:false (caller falls back to
|
|
4922
|
+
* create-fresh) when the pool is empty or the woken machine fails to take
|
|
4923
|
+
* the job. The pick is synchronous — the atomic step.
|
|
4924
|
+
*/
|
|
4925
|
+
async claim(job) {
|
|
4926
|
+
const machine = this.free.shift();
|
|
4927
|
+
if (!machine) {
|
|
4928
|
+
this.log("claim: pool empty");
|
|
4929
|
+
void this.refill();
|
|
4930
|
+
return { ok: false, reason: "pool empty" };
|
|
4931
|
+
}
|
|
4932
|
+
this.claimsInFlight++;
|
|
4933
|
+
try {
|
|
4934
|
+
await this.deps.fly.start(machine.id);
|
|
4935
|
+
const base = this.baseUrl(machine);
|
|
4936
|
+
const healthy = await this.deps.fly.waitHealthy(base, { timeoutMs: this.deps.config.healthTimeoutMs });
|
|
4937
|
+
if (!healthy) {
|
|
4938
|
+
this.log(`claim: machine ${machine.id} unhealthy after wake \u2014 destroying`);
|
|
4939
|
+
await this.safeDestroy(machine.id);
|
|
4940
|
+
return { ok: false, reason: "woken machine unhealthy" };
|
|
4941
|
+
}
|
|
4942
|
+
const accepted = await this.postRun(machine, job, this.deps.config);
|
|
4943
|
+
if (!accepted) {
|
|
4944
|
+
this.log(`claim: machine ${machine.id} rejected job \u2014 destroying`);
|
|
4945
|
+
await this.safeDestroy(machine.id);
|
|
4946
|
+
return { ok: false, reason: "machine rejected job" };
|
|
4947
|
+
}
|
|
4948
|
+
this.log(`claim: machine ${machine.id} took job ${job.jobId}`);
|
|
4949
|
+
return { ok: true, machineId: machine.id };
|
|
4950
|
+
} catch (err) {
|
|
4951
|
+
this.log(`claim: error on ${machine.id}: ${errMsg2(err)} \u2014 destroying`);
|
|
4952
|
+
await this.safeDestroy(machine.id);
|
|
4953
|
+
return { ok: false, reason: errMsg2(err) };
|
|
4954
|
+
} finally {
|
|
4955
|
+
this.claimsInFlight--;
|
|
4956
|
+
void this.refill();
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
/** Top up free machines to `min`. Serialized so it never overshoots. */
|
|
4960
|
+
async refill() {
|
|
4961
|
+
if (this.refilling) return;
|
|
4962
|
+
this.refilling = true;
|
|
4963
|
+
try {
|
|
4964
|
+
while (this.free.length + this.booting < this.deps.config.min) {
|
|
4965
|
+
this.booting++;
|
|
4966
|
+
try {
|
|
4967
|
+
await this.bootOne();
|
|
4968
|
+
} catch (err) {
|
|
4969
|
+
this.log(`refill: boot failed: ${errMsg2(err)}`);
|
|
4970
|
+
this.booting--;
|
|
4971
|
+
break;
|
|
4972
|
+
}
|
|
4973
|
+
this.booting--;
|
|
4974
|
+
}
|
|
4975
|
+
} finally {
|
|
4976
|
+
this.refilling = false;
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
async bootOne() {
|
|
4980
|
+
const cfg = this.deps.config;
|
|
4981
|
+
const m = await this.deps.fly.createPooled({
|
|
4982
|
+
image: cfg.image,
|
|
4983
|
+
region: cfg.region,
|
|
4984
|
+
guest: cfg.guest,
|
|
4985
|
+
runnerApiKey: cfg.runnerApiKey,
|
|
4986
|
+
litellmUrl: cfg.litellmUrl,
|
|
4987
|
+
port: cfg.port
|
|
4988
|
+
});
|
|
4989
|
+
if (!m.private_ip) {
|
|
4990
|
+
const refreshed = await this.deps.fly.get(m.id);
|
|
4991
|
+
if (refreshed?.private_ip) m.private_ip = refreshed.private_ip;
|
|
4992
|
+
}
|
|
4993
|
+
if (!m.private_ip) {
|
|
4994
|
+
await this.safeDestroy(m.id);
|
|
4995
|
+
throw new Error(`machine ${m.id} has no private_ip`);
|
|
4996
|
+
}
|
|
4997
|
+
const free = { id: m.id, privateIp: m.private_ip };
|
|
4998
|
+
const healthy = await this.deps.fly.waitHealthy(this.baseUrl(free), { timeoutMs: cfg.healthTimeoutMs });
|
|
4999
|
+
if (!healthy) {
|
|
5000
|
+
await this.safeDestroy(m.id);
|
|
5001
|
+
throw new Error(`machine ${m.id} never became healthy`);
|
|
5002
|
+
}
|
|
5003
|
+
await this.deps.fly.suspend(m.id);
|
|
5004
|
+
this.free.push(free);
|
|
5005
|
+
this.log(`refill: machine ${m.id} booted, frozen, ready (free=${this.free.length})`);
|
|
5006
|
+
}
|
|
5007
|
+
baseUrl(m) {
|
|
5008
|
+
return `http://[${m.privateIp}]:${this.deps.config.port}`;
|
|
5009
|
+
}
|
|
5010
|
+
async safeDestroy(id) {
|
|
5011
|
+
try {
|
|
5012
|
+
await this.deps.fly.destroy(id);
|
|
5013
|
+
} catch (err) {
|
|
5014
|
+
this.log(`destroy ${id} failed: ${errMsg2(err)}`);
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
};
|
|
5018
|
+
async function defaultPostRun(m, job, cfg) {
|
|
5019
|
+
const res = await fetch(`http://[${m.privateIp}]:${cfg.port}/run`, {
|
|
5020
|
+
method: "POST",
|
|
5021
|
+
headers: {
|
|
5022
|
+
"content-type": "application/json",
|
|
5023
|
+
"x-api-key": cfg.runnerApiKey
|
|
5024
|
+
},
|
|
5025
|
+
body: JSON.stringify(job),
|
|
5026
|
+
signal: AbortSignal.timeout(15e3)
|
|
5027
|
+
});
|
|
5028
|
+
return res.status === 202;
|
|
5029
|
+
}
|
|
5030
|
+
function errMsg2(err) {
|
|
5031
|
+
return err instanceof Error ? err.message : String(err);
|
|
5032
|
+
}
|
|
5033
|
+
|
|
5034
|
+
// src/pool/keys.ts
|
|
5035
|
+
import { hkdfSync } from "crypto";
|
|
5036
|
+
var POOL_API_KEY_INFO = "kody-pool-api:v1";
|
|
5037
|
+
var RUNNER_API_KEY_INFO = "kody-runner-api:v1";
|
|
5038
|
+
function masterKeyBytes(raw) {
|
|
5039
|
+
const v = raw.trim();
|
|
5040
|
+
if (!v) throw new Error("KODY_MASTER_KEY is empty");
|
|
5041
|
+
if (/^[0-9a-fA-F]+$/.test(v) && v.length === 64) {
|
|
5042
|
+
return Buffer.from(v, "hex");
|
|
5043
|
+
}
|
|
5044
|
+
return Buffer.from(v.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
5045
|
+
}
|
|
5046
|
+
function deriveKey(master, info, length = 32) {
|
|
5047
|
+
return Buffer.from(hkdfSync("sha256", master, Buffer.alloc(0), info, length)).toString("hex");
|
|
5048
|
+
}
|
|
5049
|
+
function derivePoolApiKey(master) {
|
|
5050
|
+
return deriveKey(master, POOL_API_KEY_INFO);
|
|
5051
|
+
}
|
|
5052
|
+
function deriveRunnerApiKey(master) {
|
|
5053
|
+
return deriveKey(master, RUNNER_API_KEY_INFO);
|
|
5054
|
+
}
|
|
5055
|
+
function bearerOk(headerAuth, xApiKey, expected) {
|
|
5056
|
+
const x = (xApiKey ?? "").trim();
|
|
5057
|
+
if (x && timingEqual(x, expected)) return true;
|
|
5058
|
+
const a = (headerAuth ?? "").trim();
|
|
5059
|
+
if (a.toLowerCase().startsWith("bearer ")) {
|
|
5060
|
+
return timingEqual(a.slice(7).trim(), expected);
|
|
5061
|
+
}
|
|
5062
|
+
return false;
|
|
5063
|
+
}
|
|
5064
|
+
function timingEqual(a, b) {
|
|
5065
|
+
if (a.length !== b.length) return false;
|
|
5066
|
+
let diff = 0;
|
|
5067
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
5068
|
+
return diff === 0;
|
|
5069
|
+
}
|
|
5070
|
+
|
|
5071
|
+
// src/scripts/poolServe.ts
|
|
5072
|
+
var PERF_GUEST = {
|
|
5073
|
+
low: { cpu_kind: "shared", cpus: 2, memory_mb: 2048 },
|
|
5074
|
+
medium: { cpu_kind: "performance", cpus: 1, memory_mb: 2048 },
|
|
5075
|
+
high: { cpu_kind: "performance", cpus: 2, memory_mb: 4096 }
|
|
5076
|
+
};
|
|
5077
|
+
function envInt(name, dflt) {
|
|
5078
|
+
const v = Number(process.env[name]);
|
|
5079
|
+
return Number.isFinite(v) && v > 0 ? v : dflt;
|
|
5080
|
+
}
|
|
5081
|
+
function log(msg) {
|
|
5082
|
+
process.stdout.write(`[pool-serve] ${msg}
|
|
5083
|
+
`);
|
|
5084
|
+
}
|
|
5085
|
+
function sendJson3(res, status, body) {
|
|
5086
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
5087
|
+
res.end(JSON.stringify(body));
|
|
5088
|
+
}
|
|
5089
|
+
function readJsonBody3(req) {
|
|
5090
|
+
return new Promise((resolve4, reject) => {
|
|
5091
|
+
const chunks = [];
|
|
5092
|
+
req.on("data", (c) => chunks.push(c));
|
|
5093
|
+
req.on("end", () => {
|
|
5094
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
5095
|
+
if (!raw.trim()) return resolve4({});
|
|
5096
|
+
try {
|
|
5097
|
+
resolve4(JSON.parse(raw));
|
|
5098
|
+
} catch (err) {
|
|
5099
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
5100
|
+
}
|
|
5101
|
+
});
|
|
5102
|
+
req.on("error", reject);
|
|
5103
|
+
});
|
|
5104
|
+
}
|
|
5105
|
+
function parsePoolJob(body) {
|
|
5106
|
+
if (typeof body !== "object" || body === null) return { error: "body must be an object" };
|
|
5107
|
+
const b = body;
|
|
5108
|
+
const jobId = typeof b.jobId === "string" ? b.jobId.trim() : "";
|
|
5109
|
+
if (!jobId) return { error: "jobId required" };
|
|
5110
|
+
const repo = typeof b.repo === "string" ? b.repo.trim() : "";
|
|
5111
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
|
|
5112
|
+
const issueNumber = Number(b.issueNumber);
|
|
5113
|
+
if (!Number.isInteger(issueNumber) || issueNumber <= 0) return { error: "issueNumber required" };
|
|
5114
|
+
const githubToken = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
|
|
5115
|
+
if (!githubToken) return { error: "githubToken required" };
|
|
5116
|
+
const job = { jobId, repo, issueNumber, githubToken };
|
|
5117
|
+
if (typeof b.ref === "string" && b.ref.trim()) job.ref = b.ref.trim();
|
|
5118
|
+
if (typeof b.model === "string" && b.model.trim()) job.model = b.model.trim();
|
|
5119
|
+
if (typeof b.sessionId === "string" && b.sessionId.trim()) job.sessionId = b.sessionId.trim();
|
|
5120
|
+
if (typeof b.dashboardUrl === "string" && b.dashboardUrl.trim()) job.dashboardUrl = b.dashboardUrl.trim();
|
|
5121
|
+
if (b.allSecrets && typeof b.allSecrets === "object") job.allSecrets = b.allSecrets;
|
|
5122
|
+
return { job };
|
|
5123
|
+
}
|
|
5124
|
+
function superviseLitellm() {
|
|
5125
|
+
if (process.env.POOL_DISABLE_LITELLM === "1") return null;
|
|
5126
|
+
const port = String(envInt("LITELLM_PORT", 4e3));
|
|
5127
|
+
const config = process.env.LITELLM_CONFIG ?? "/app/config.yaml";
|
|
5128
|
+
let restarts = 0;
|
|
5129
|
+
const start = () => {
|
|
5130
|
+
log(`starting litellm child (port ${port})`);
|
|
5131
|
+
const child = spawn4("litellm", ["--config", config, "--port", port, "--host", "0.0.0.0"], {
|
|
5132
|
+
stdio: "inherit"
|
|
5133
|
+
});
|
|
5134
|
+
child.on("exit", (code) => {
|
|
5135
|
+
restarts++;
|
|
5136
|
+
if (restarts > 50) {
|
|
5137
|
+
process.stderr.write("[pool-serve] litellm restarted too many times \u2014 giving up\n");
|
|
5138
|
+
return;
|
|
5139
|
+
}
|
|
5140
|
+
log(`litellm exited (${code}) \u2014 restarting in 2s`);
|
|
5141
|
+
setTimeout(start, 2e3);
|
|
5142
|
+
});
|
|
5143
|
+
child.on("error", (err) => process.stderr.write(`[pool-serve] litellm spawn error: ${err.message}
|
|
5144
|
+
`));
|
|
5145
|
+
return child;
|
|
5146
|
+
};
|
|
5147
|
+
return start();
|
|
5148
|
+
}
|
|
5149
|
+
var poolServe = async (ctx) => {
|
|
5150
|
+
ctx.skipAgent = true;
|
|
5151
|
+
const masterRaw = process.env.KODY_MASTER_KEY?.trim();
|
|
5152
|
+
if (!masterRaw) throw new Error("KODY_MASTER_KEY required for pool-serve");
|
|
5153
|
+
const flyToken = process.env.FLY_API_TOKEN?.trim();
|
|
5154
|
+
if (!flyToken) throw new Error("FLY_API_TOKEN required for pool-serve");
|
|
5155
|
+
const master = masterKeyBytes(masterRaw);
|
|
5156
|
+
const poolApiKey = derivePoolApiKey(master);
|
|
5157
|
+
const runnerApiKey = deriveRunnerApiKey(master);
|
|
5158
|
+
const app = process.env.FLY_APP_NAME ?? "kody-runner";
|
|
5159
|
+
const region = process.env.FLY_REGION ?? "fra";
|
|
5160
|
+
const perf = process.env.POOL_PERF ?? "medium";
|
|
5161
|
+
const guest = PERF_GUEST[perf] ?? PERF_GUEST.medium;
|
|
5162
|
+
const litellmUrl = process.env.KODY_LITELLM_URL ?? "http://kody-litellm.internal:4000";
|
|
5163
|
+
const min = envInt("POOL_MIN", 2);
|
|
5164
|
+
const runnerPort = envInt("RUNNER_PORT", 8080);
|
|
5165
|
+
const apiPort = envInt("POOL_API_PORT", 4100);
|
|
5166
|
+
const healthTimeoutMs = envInt("POOL_HEALTH_TIMEOUT_MS", 12e4);
|
|
5167
|
+
const litellm = superviseLitellm();
|
|
5168
|
+
const fly = new FlyClient({ token: flyToken, app });
|
|
5169
|
+
const manager = new PoolManager({
|
|
5170
|
+
fly,
|
|
5171
|
+
config: { min, image: process.env.FLY_RUNNER_IMAGE ?? "registry.fly.io/kody-runner:latest", region, guest, runnerApiKey, litellmUrl, port: runnerPort, healthTimeoutMs },
|
|
5172
|
+
log
|
|
5173
|
+
});
|
|
5174
|
+
manager.reconcile().catch((err) => log(`reconcile failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
5175
|
+
const refillMs = envInt("POOL_REFILL_INTERVAL_MS", 6e4);
|
|
5176
|
+
const tick = setInterval(() => {
|
|
5177
|
+
manager.refill().catch((err) => log(`refill tick failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
5178
|
+
}, refillMs);
|
|
5179
|
+
const server = createServer3(async (req, res) => {
|
|
5180
|
+
try {
|
|
5181
|
+
if (!req.method || !req.url) return sendJson3(res, 400, { error: "bad request" });
|
|
5182
|
+
const url = new URL(req.url, "http://localhost");
|
|
5183
|
+
if (req.method === "GET" && url.pathname === "/healthz") {
|
|
5184
|
+
return sendJson3(res, 200, { ok: true, litellm: litellm ? "supervised" : "off", pool: manager.status() });
|
|
5185
|
+
}
|
|
5186
|
+
const authed = bearerOk(
|
|
5187
|
+
req.headers["authorization"],
|
|
5188
|
+
req.headers["x-api-key"],
|
|
5189
|
+
poolApiKey
|
|
5190
|
+
);
|
|
5191
|
+
if (!authed) return sendJson3(res, 401, { error: "unauthorized" });
|
|
5192
|
+
if (req.method === "GET" && url.pathname === "/pool/status") {
|
|
5193
|
+
return sendJson3(res, 200, manager.status());
|
|
5194
|
+
}
|
|
5195
|
+
if (req.method === "POST" && url.pathname === "/pool/claim") {
|
|
5196
|
+
let body;
|
|
5197
|
+
try {
|
|
5198
|
+
body = await readJsonBody3(req);
|
|
5199
|
+
} catch {
|
|
5200
|
+
return sendJson3(res, 400, { error: "invalid JSON body" });
|
|
5201
|
+
}
|
|
5202
|
+
const parsed = parsePoolJob(body);
|
|
5203
|
+
if ("error" in parsed) return sendJson3(res, 400, { error: parsed.error });
|
|
5204
|
+
const result = await manager.claim(parsed.job);
|
|
5205
|
+
if (result.ok) return sendJson3(res, 200, { ok: true, machineId: result.machineId });
|
|
5206
|
+
return sendJson3(res, 503, { ok: false, reason: result.reason ?? "pool unavailable" });
|
|
5207
|
+
}
|
|
5208
|
+
return sendJson3(res, 404, { error: "not found" });
|
|
5209
|
+
} catch (err) {
|
|
5210
|
+
process.stderr.write(`[pool-serve] handler error: ${err instanceof Error ? err.message : String(err)}
|
|
5211
|
+
`);
|
|
5212
|
+
try {
|
|
5213
|
+
sendJson3(res, 500, { error: "internal error" });
|
|
5214
|
+
} catch {
|
|
5215
|
+
}
|
|
5216
|
+
}
|
|
5217
|
+
});
|
|
5218
|
+
await new Promise((resolve4) => {
|
|
5219
|
+
server.listen(apiPort, "0.0.0.0", () => {
|
|
5220
|
+
log(`listening on 0.0.0.0:${apiPort} (min=${min}, app=${app}, region=${region})`);
|
|
5221
|
+
resolve4();
|
|
5222
|
+
});
|
|
5223
|
+
});
|
|
5224
|
+
const shutdown = (signal) => {
|
|
5225
|
+
log(`${signal} \u2014 shutting down`);
|
|
5226
|
+
clearInterval(tick);
|
|
5227
|
+
server.close(() => process.exit(0));
|
|
5228
|
+
};
|
|
5229
|
+
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
5230
|
+
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
5231
|
+
await new Promise(() => {
|
|
5232
|
+
});
|
|
5233
|
+
};
|
|
5234
|
+
|
|
4584
5235
|
// src/coverage.ts
|
|
4585
5236
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
4586
5237
|
function patternToRegex(pattern) {
|
|
@@ -4727,7 +5378,7 @@ function defaultLabelMap() {
|
|
|
4727
5378
|
}
|
|
4728
5379
|
|
|
4729
5380
|
// src/scripts/commitAndPush.ts
|
|
4730
|
-
import * as
|
|
5381
|
+
import * as fs21 from "fs";
|
|
4731
5382
|
import * as path19 from "path";
|
|
4732
5383
|
init_events();
|
|
4733
5384
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
@@ -4743,9 +5394,9 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
4743
5394
|
}
|
|
4744
5395
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
4745
5396
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
4746
|
-
if (sentinel &&
|
|
5397
|
+
if (sentinel && fs21.existsSync(sentinel)) {
|
|
4747
5398
|
try {
|
|
4748
|
-
const replay = JSON.parse(
|
|
5399
|
+
const replay = JSON.parse(fs21.readFileSync(sentinel, "utf-8"));
|
|
4749
5400
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
4750
5401
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
4751
5402
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -4798,8 +5449,8 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
4798
5449
|
const result = ctx.data.commitResult;
|
|
4799
5450
|
if (sentinel && result?.committed) {
|
|
4800
5451
|
try {
|
|
4801
|
-
|
|
4802
|
-
|
|
5452
|
+
fs21.mkdirSync(path19.dirname(sentinel), { recursive: true });
|
|
5453
|
+
fs21.writeFileSync(
|
|
4803
5454
|
sentinel,
|
|
4804
5455
|
JSON.stringify(
|
|
4805
5456
|
{
|
|
@@ -4869,7 +5520,7 @@ function describeCommitMessage(goal) {
|
|
|
4869
5520
|
}
|
|
4870
5521
|
|
|
4871
5522
|
// src/scripts/composePrompt.ts
|
|
4872
|
-
import * as
|
|
5523
|
+
import * as fs22 from "fs";
|
|
4873
5524
|
import * as path21 from "path";
|
|
4874
5525
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
4875
5526
|
var composePrompt = async (ctx, profile) => {
|
|
@@ -4882,7 +5533,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
4882
5533
|
].filter(Boolean);
|
|
4883
5534
|
let templatePath = "";
|
|
4884
5535
|
for (const c of candidates) {
|
|
4885
|
-
if (
|
|
5536
|
+
if (fs22.existsSync(c)) {
|
|
4886
5537
|
templatePath = c;
|
|
4887
5538
|
break;
|
|
4888
5539
|
}
|
|
@@ -4890,7 +5541,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
4890
5541
|
if (!templatePath) {
|
|
4891
5542
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
4892
5543
|
}
|
|
4893
|
-
const template =
|
|
5544
|
+
const template = fs22.readFileSync(templatePath, "utf-8");
|
|
4894
5545
|
const tokens = {
|
|
4895
5546
|
...stringifyAll(ctx.args, "args."),
|
|
4896
5547
|
...stringifyAll(ctx.data, ""),
|
|
@@ -4969,7 +5620,7 @@ function formatToolsUsage(profile) {
|
|
|
4969
5620
|
// src/scripts/createQaGoal.ts
|
|
4970
5621
|
init_issue();
|
|
4971
5622
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
4972
|
-
import * as
|
|
5623
|
+
import * as fs23 from "fs";
|
|
4973
5624
|
import * as path22 from "path";
|
|
4974
5625
|
|
|
4975
5626
|
// src/scripts/postReviewResult.ts
|
|
@@ -5224,7 +5875,7 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
|
5224
5875
|
}
|
|
5225
5876
|
function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
5226
5877
|
const dir = path22.join(cwd, ".kody", "goals", goalId);
|
|
5227
|
-
|
|
5878
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
5228
5879
|
const state = {
|
|
5229
5880
|
version: 1,
|
|
5230
5881
|
state: "active",
|
|
@@ -5233,7 +5884,7 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
|
5233
5884
|
...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
|
|
5234
5885
|
};
|
|
5235
5886
|
const filePath = path22.join(dir, "state.json");
|
|
5236
|
-
|
|
5887
|
+
fs23.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
|
|
5237
5888
|
`);
|
|
5238
5889
|
return filePath;
|
|
5239
5890
|
}
|
|
@@ -5742,7 +6393,7 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
|
5742
6393
|
|
|
5743
6394
|
// src/scripts/diagMcp.ts
|
|
5744
6395
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
5745
|
-
import * as
|
|
6396
|
+
import * as fs24 from "fs";
|
|
5746
6397
|
import * as os4 from "os";
|
|
5747
6398
|
import * as path23 from "path";
|
|
5748
6399
|
var diagMcp = async (_ctx) => {
|
|
@@ -5750,7 +6401,7 @@ var diagMcp = async (_ctx) => {
|
|
|
5750
6401
|
const cacheDir = path23.join(home, ".cache", "ms-playwright");
|
|
5751
6402
|
let entries = [];
|
|
5752
6403
|
try {
|
|
5753
|
-
entries =
|
|
6404
|
+
entries = fs24.readdirSync(cacheDir);
|
|
5754
6405
|
} catch {
|
|
5755
6406
|
}
|
|
5756
6407
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -5776,17 +6427,17 @@ var diagMcp = async (_ctx) => {
|
|
|
5776
6427
|
};
|
|
5777
6428
|
|
|
5778
6429
|
// src/scripts/discoverQaContext.ts
|
|
5779
|
-
import * as
|
|
6430
|
+
import * as fs26 from "fs";
|
|
5780
6431
|
import * as path25 from "path";
|
|
5781
6432
|
|
|
5782
6433
|
// src/scripts/frameworkDetectors.ts
|
|
5783
|
-
import * as
|
|
6434
|
+
import * as fs25 from "fs";
|
|
5784
6435
|
import * as path24 from "path";
|
|
5785
6436
|
function detectFrameworks(cwd) {
|
|
5786
6437
|
const out = [];
|
|
5787
6438
|
let deps = {};
|
|
5788
6439
|
try {
|
|
5789
|
-
const pkg = JSON.parse(
|
|
6440
|
+
const pkg = JSON.parse(fs25.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
|
|
5790
6441
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5791
6442
|
} catch {
|
|
5792
6443
|
return out;
|
|
@@ -5823,7 +6474,7 @@ function detectFrameworks(cwd) {
|
|
|
5823
6474
|
}
|
|
5824
6475
|
function findFile(cwd, candidates) {
|
|
5825
6476
|
for (const c of candidates) {
|
|
5826
|
-
if (
|
|
6477
|
+
if (fs25.existsSync(path24.join(cwd, c))) return c;
|
|
5827
6478
|
}
|
|
5828
6479
|
return null;
|
|
5829
6480
|
}
|
|
@@ -5837,17 +6488,17 @@ function discoverPayloadCollections(cwd) {
|
|
|
5837
6488
|
const out = [];
|
|
5838
6489
|
for (const dir of COLLECTION_DIRS) {
|
|
5839
6490
|
const full = path24.join(cwd, dir);
|
|
5840
|
-
if (!
|
|
6491
|
+
if (!fs25.existsSync(full)) continue;
|
|
5841
6492
|
let files;
|
|
5842
6493
|
try {
|
|
5843
|
-
files =
|
|
6494
|
+
files = fs25.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
5844
6495
|
} catch {
|
|
5845
6496
|
continue;
|
|
5846
6497
|
}
|
|
5847
6498
|
for (const file of files) {
|
|
5848
6499
|
try {
|
|
5849
6500
|
const filePath = path24.join(full, file);
|
|
5850
|
-
const content =
|
|
6501
|
+
const content = fs25.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
5851
6502
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
5852
6503
|
if (!slugMatch) continue;
|
|
5853
6504
|
const slug = slugMatch[1];
|
|
@@ -5876,10 +6527,10 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
5876
6527
|
const out = [];
|
|
5877
6528
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
5878
6529
|
const full = path24.join(cwd, dir);
|
|
5879
|
-
if (!
|
|
6530
|
+
if (!fs25.existsSync(full)) continue;
|
|
5880
6531
|
let entries;
|
|
5881
6532
|
try {
|
|
5882
|
-
entries =
|
|
6533
|
+
entries = fs25.readdirSync(full, { withFileTypes: true });
|
|
5883
6534
|
} catch {
|
|
5884
6535
|
continue;
|
|
5885
6536
|
}
|
|
@@ -5889,7 +6540,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
5889
6540
|
let filePath;
|
|
5890
6541
|
if (entry.isDirectory()) {
|
|
5891
6542
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
5892
|
-
(f) =>
|
|
6543
|
+
(f) => fs25.existsSync(path24.join(entryPath, f))
|
|
5893
6544
|
);
|
|
5894
6545
|
if (!indexFile) continue;
|
|
5895
6546
|
name = entry.name;
|
|
@@ -5904,7 +6555,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
5904
6555
|
if (collections) {
|
|
5905
6556
|
for (const col of collections) {
|
|
5906
6557
|
try {
|
|
5907
|
-
const colContent =
|
|
6558
|
+
const colContent = fs25.readFileSync(path24.join(cwd, col.filePath), "utf-8");
|
|
5908
6559
|
if (colContent.includes(name)) {
|
|
5909
6560
|
usedInCollection = col.slug;
|
|
5910
6561
|
break;
|
|
@@ -5924,7 +6575,7 @@ function scanApiRoutes(cwd) {
|
|
|
5924
6575
|
const appDirs = ["src/app", "app"];
|
|
5925
6576
|
for (const appDir of appDirs) {
|
|
5926
6577
|
const apiDir = path24.join(cwd, appDir, "api");
|
|
5927
|
-
if (!
|
|
6578
|
+
if (!fs25.existsSync(apiDir)) continue;
|
|
5928
6579
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
5929
6580
|
break;
|
|
5930
6581
|
}
|
|
@@ -5933,14 +6584,14 @@ function scanApiRoutes(cwd) {
|
|
|
5933
6584
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
5934
6585
|
let entries;
|
|
5935
6586
|
try {
|
|
5936
|
-
entries =
|
|
6587
|
+
entries = fs25.readdirSync(dir, { withFileTypes: true });
|
|
5937
6588
|
} catch {
|
|
5938
6589
|
return;
|
|
5939
6590
|
}
|
|
5940
6591
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
5941
6592
|
if (routeFile) {
|
|
5942
6593
|
try {
|
|
5943
|
-
const content =
|
|
6594
|
+
const content = fs25.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
5944
6595
|
const methods = HTTP_METHODS.filter(
|
|
5945
6596
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
5946
6597
|
);
|
|
@@ -5988,9 +6639,9 @@ function scanEnvVars(cwd) {
|
|
|
5988
6639
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
5989
6640
|
for (const envFile of candidates) {
|
|
5990
6641
|
const envPath = path24.join(cwd, envFile);
|
|
5991
|
-
if (!
|
|
6642
|
+
if (!fs25.existsSync(envPath)) continue;
|
|
5992
6643
|
try {
|
|
5993
|
-
const content =
|
|
6644
|
+
const content = fs25.readFileSync(envPath, "utf-8");
|
|
5994
6645
|
const vars = [];
|
|
5995
6646
|
for (const line of content.split("\n")) {
|
|
5996
6647
|
const trimmed = line.trim();
|
|
@@ -6038,9 +6689,9 @@ function runQaDiscovery(cwd) {
|
|
|
6038
6689
|
}
|
|
6039
6690
|
function detectDevServer(cwd, out) {
|
|
6040
6691
|
try {
|
|
6041
|
-
const pkg = JSON.parse(
|
|
6692
|
+
const pkg = JSON.parse(fs26.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
|
|
6042
6693
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6043
|
-
const pm =
|
|
6694
|
+
const pm = fs26.existsSync(path25.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs26.existsSync(path25.join(cwd, "yarn.lock")) ? "yarn" : fs26.existsSync(path25.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
6044
6695
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
6045
6696
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
6046
6697
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -6051,7 +6702,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
6051
6702
|
const appDirs = ["src/app", "app"];
|
|
6052
6703
|
for (const appDir of appDirs) {
|
|
6053
6704
|
const full = path25.join(cwd, appDir);
|
|
6054
|
-
if (!
|
|
6705
|
+
if (!fs26.existsSync(full)) continue;
|
|
6055
6706
|
walkFrontendRoutes(full, "", out);
|
|
6056
6707
|
break;
|
|
6057
6708
|
}
|
|
@@ -6059,7 +6710,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
6059
6710
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
6060
6711
|
let entries;
|
|
6061
6712
|
try {
|
|
6062
|
-
entries =
|
|
6713
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
6063
6714
|
} catch {
|
|
6064
6715
|
return;
|
|
6065
6716
|
}
|
|
@@ -6101,23 +6752,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
6101
6752
|
"src/app/api/oauth"
|
|
6102
6753
|
];
|
|
6103
6754
|
for (const c of candidates) {
|
|
6104
|
-
if (
|
|
6755
|
+
if (fs26.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
|
|
6105
6756
|
}
|
|
6106
6757
|
}
|
|
6107
6758
|
function detectRoles(cwd, out) {
|
|
6108
6759
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
6109
6760
|
for (const rp of rolePaths) {
|
|
6110
6761
|
const dir = path25.join(cwd, rp);
|
|
6111
|
-
if (!
|
|
6762
|
+
if (!fs26.existsSync(dir)) continue;
|
|
6112
6763
|
let files;
|
|
6113
6764
|
try {
|
|
6114
|
-
files =
|
|
6765
|
+
files = fs26.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
6115
6766
|
} catch {
|
|
6116
6767
|
continue;
|
|
6117
6768
|
}
|
|
6118
6769
|
for (const f of files) {
|
|
6119
6770
|
try {
|
|
6120
|
-
const content =
|
|
6771
|
+
const content = fs26.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
|
|
6121
6772
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
6122
6773
|
if (roleMatches) {
|
|
6123
6774
|
for (const m of roleMatches) {
|
|
@@ -6363,7 +7014,7 @@ function failedAction3(reason) {
|
|
|
6363
7014
|
}
|
|
6364
7015
|
|
|
6365
7016
|
// src/scripts/dispatchJobFileTicks.ts
|
|
6366
|
-
import * as
|
|
7017
|
+
import * as fs28 from "fs";
|
|
6367
7018
|
import * as path27 from "path";
|
|
6368
7019
|
|
|
6369
7020
|
// src/scripts/jobFrontmatter.ts
|
|
@@ -6625,7 +7276,7 @@ var ContentsApiBackend = class {
|
|
|
6625
7276
|
};
|
|
6626
7277
|
|
|
6627
7278
|
// src/scripts/jobState/localFileBackend.ts
|
|
6628
|
-
import * as
|
|
7279
|
+
import * as fs27 from "fs";
|
|
6629
7280
|
import * as path26 from "path";
|
|
6630
7281
|
var LocalFileBackend = class {
|
|
6631
7282
|
name = "local-file";
|
|
@@ -6656,7 +7307,7 @@ var LocalFileBackend = class {
|
|
|
6656
7307
|
`);
|
|
6657
7308
|
return;
|
|
6658
7309
|
}
|
|
6659
|
-
|
|
7310
|
+
fs27.mkdirSync(this.absDir, { recursive: true });
|
|
6660
7311
|
const prefix = this.cacheKeyPrefix();
|
|
6661
7312
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
6662
7313
|
try {
|
|
@@ -6685,7 +7336,7 @@ var LocalFileBackend = class {
|
|
|
6685
7336
|
`);
|
|
6686
7337
|
return;
|
|
6687
7338
|
}
|
|
6688
|
-
if (!
|
|
7339
|
+
if (!fs27.existsSync(this.absDir)) {
|
|
6689
7340
|
return;
|
|
6690
7341
|
}
|
|
6691
7342
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -6702,10 +7353,10 @@ var LocalFileBackend = class {
|
|
|
6702
7353
|
load(slug) {
|
|
6703
7354
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
6704
7355
|
const absPath = path26.join(this.cwd, relPath);
|
|
6705
|
-
if (!
|
|
7356
|
+
if (!fs27.existsSync(absPath)) {
|
|
6706
7357
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
6707
7358
|
}
|
|
6708
|
-
const raw =
|
|
7359
|
+
const raw = fs27.readFileSync(absPath, "utf-8");
|
|
6709
7360
|
let parsed;
|
|
6710
7361
|
try {
|
|
6711
7362
|
parsed = JSON.parse(raw);
|
|
@@ -6723,9 +7374,9 @@ var LocalFileBackend = class {
|
|
|
6723
7374
|
return false;
|
|
6724
7375
|
}
|
|
6725
7376
|
const absPath = path26.join(this.cwd, loaded.path);
|
|
6726
|
-
|
|
7377
|
+
fs27.mkdirSync(path26.dirname(absPath), { recursive: true });
|
|
6727
7378
|
const body = JSON.stringify(next, null, 2) + "\n";
|
|
6728
|
-
|
|
7379
|
+
fs27.writeFileSync(absPath, body, "utf-8");
|
|
6729
7380
|
return true;
|
|
6730
7381
|
}
|
|
6731
7382
|
cacheKeyPrefix() {
|
|
@@ -6916,17 +7567,17 @@ function formatAgo(ms) {
|
|
|
6916
7567
|
}
|
|
6917
7568
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
6918
7569
|
try {
|
|
6919
|
-
const raw =
|
|
7570
|
+
const raw = fs28.readFileSync(path27.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
6920
7571
|
return splitFrontmatter2(raw).frontmatter;
|
|
6921
7572
|
} catch {
|
|
6922
7573
|
return {};
|
|
6923
7574
|
}
|
|
6924
7575
|
}
|
|
6925
7576
|
function listJobSlugs(absDir) {
|
|
6926
|
-
if (!
|
|
7577
|
+
if (!fs28.existsSync(absDir)) return [];
|
|
6927
7578
|
let entries;
|
|
6928
7579
|
try {
|
|
6929
|
-
entries =
|
|
7580
|
+
entries = fs28.readdirSync(absDir, { withFileTypes: true });
|
|
6930
7581
|
} catch {
|
|
6931
7582
|
return [];
|
|
6932
7583
|
}
|
|
@@ -7679,7 +8330,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
|
|
|
7679
8330
|
|
|
7680
8331
|
// src/gha.ts
|
|
7681
8332
|
import { execFileSync as execFileSync17 } from "child_process";
|
|
7682
|
-
import * as
|
|
8333
|
+
import * as fs29 from "fs";
|
|
7683
8334
|
function getRunUrl() {
|
|
7684
8335
|
const server = process.env.GITHUB_SERVER_URL;
|
|
7685
8336
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -7690,10 +8341,10 @@ function getRunUrl() {
|
|
|
7690
8341
|
function reactToTriggerComment(cwd) {
|
|
7691
8342
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
7692
8343
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
7693
|
-
if (!eventPath || !
|
|
8344
|
+
if (!eventPath || !fs29.existsSync(eventPath)) return;
|
|
7694
8345
|
let event = null;
|
|
7695
8346
|
try {
|
|
7696
|
-
event = JSON.parse(
|
|
8347
|
+
event = JSON.parse(fs29.readFileSync(eventPath, "utf-8"));
|
|
7697
8348
|
} catch {
|
|
7698
8349
|
return;
|
|
7699
8350
|
}
|
|
@@ -7986,22 +8637,22 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
7986
8637
|
|
|
7987
8638
|
// src/scripts/initFlow.ts
|
|
7988
8639
|
import { execFileSync as execFileSync19 } from "child_process";
|
|
7989
|
-
import * as
|
|
8640
|
+
import * as fs31 from "fs";
|
|
7990
8641
|
import * as path29 from "path";
|
|
7991
8642
|
|
|
7992
8643
|
// src/scripts/loadQaGuide.ts
|
|
7993
|
-
import * as
|
|
8644
|
+
import * as fs30 from "fs";
|
|
7994
8645
|
import * as path28 from "path";
|
|
7995
8646
|
var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
|
|
7996
8647
|
var loadQaGuide = async (ctx) => {
|
|
7997
8648
|
const full = path28.join(ctx.cwd, QA_GUIDE_REL_PATH);
|
|
7998
|
-
if (!
|
|
8649
|
+
if (!fs30.existsSync(full)) {
|
|
7999
8650
|
ctx.data.qaGuide = "";
|
|
8000
8651
|
ctx.data.qaGuidePath = "";
|
|
8001
8652
|
return;
|
|
8002
8653
|
}
|
|
8003
8654
|
try {
|
|
8004
|
-
ctx.data.qaGuide =
|
|
8655
|
+
ctx.data.qaGuide = fs30.readFileSync(full, "utf-8");
|
|
8005
8656
|
ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
|
|
8006
8657
|
} catch {
|
|
8007
8658
|
ctx.data.qaGuide = "";
|
|
@@ -8011,9 +8662,9 @@ var loadQaGuide = async (ctx) => {
|
|
|
8011
8662
|
|
|
8012
8663
|
// src/scripts/initFlow.ts
|
|
8013
8664
|
function detectPackageManager(cwd) {
|
|
8014
|
-
if (
|
|
8015
|
-
if (
|
|
8016
|
-
if (
|
|
8665
|
+
if (fs31.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
8666
|
+
if (fs31.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
|
|
8667
|
+
if (fs31.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
|
|
8017
8668
|
return "npm";
|
|
8018
8669
|
}
|
|
8019
8670
|
function qualityCommandsFor(pm) {
|
|
@@ -8136,47 +8787,47 @@ function performInit(cwd, force) {
|
|
|
8136
8787
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
8137
8788
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
8138
8789
|
const configPath = path29.join(cwd, "kody.config.json");
|
|
8139
|
-
if (
|
|
8790
|
+
if (fs31.existsSync(configPath) && !force) {
|
|
8140
8791
|
skipped.push("kody.config.json");
|
|
8141
8792
|
} else {
|
|
8142
8793
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
|
|
8143
|
-
|
|
8794
|
+
fs31.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
8144
8795
|
`);
|
|
8145
8796
|
wrote.push("kody.config.json");
|
|
8146
8797
|
}
|
|
8147
8798
|
const workflowDir = path29.join(cwd, ".github", "workflows");
|
|
8148
8799
|
const workflowPath = path29.join(workflowDir, "kody.yml");
|
|
8149
|
-
if (
|
|
8800
|
+
if (fs31.existsSync(workflowPath) && !force) {
|
|
8150
8801
|
skipped.push(".github/workflows/kody.yml");
|
|
8151
8802
|
} else {
|
|
8152
|
-
|
|
8153
|
-
|
|
8803
|
+
fs31.mkdirSync(workflowDir, { recursive: true });
|
|
8804
|
+
fs31.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
8154
8805
|
wrote.push(".github/workflows/kody.yml");
|
|
8155
8806
|
}
|
|
8156
|
-
const hasUi =
|
|
8807
|
+
const hasUi = fs31.existsSync(path29.join(cwd, "src/app")) || fs31.existsSync(path29.join(cwd, "app")) || fs31.existsSync(path29.join(cwd, "pages"));
|
|
8157
8808
|
if (hasUi) {
|
|
8158
8809
|
const qaGuidePath = path29.join(cwd, QA_GUIDE_REL_PATH);
|
|
8159
|
-
if (
|
|
8810
|
+
if (fs31.existsSync(qaGuidePath) && !force) {
|
|
8160
8811
|
skipped.push(QA_GUIDE_REL_PATH);
|
|
8161
8812
|
} else {
|
|
8162
|
-
|
|
8813
|
+
fs31.mkdirSync(path29.dirname(qaGuidePath), { recursive: true });
|
|
8163
8814
|
const discovery = runQaDiscovery(cwd);
|
|
8164
|
-
|
|
8815
|
+
fs31.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
|
|
8165
8816
|
wrote.push(QA_GUIDE_REL_PATH);
|
|
8166
8817
|
}
|
|
8167
8818
|
}
|
|
8168
8819
|
const builtinJobs = listBuiltinJobs();
|
|
8169
8820
|
if (builtinJobs.length > 0) {
|
|
8170
8821
|
const jobsDir = path29.join(cwd, ".kody", "jobs");
|
|
8171
|
-
|
|
8822
|
+
fs31.mkdirSync(jobsDir, { recursive: true });
|
|
8172
8823
|
for (const job of builtinJobs) {
|
|
8173
8824
|
const rel = path29.join(".kody", "jobs", `${job.slug}.md`);
|
|
8174
8825
|
const target = path29.join(cwd, rel);
|
|
8175
|
-
if (
|
|
8826
|
+
if (fs31.existsSync(target) && !force) {
|
|
8176
8827
|
skipped.push(rel);
|
|
8177
8828
|
continue;
|
|
8178
8829
|
}
|
|
8179
|
-
|
|
8830
|
+
fs31.writeFileSync(target, fs31.readFileSync(job.filePath, "utf-8"));
|
|
8180
8831
|
wrote.push(rel);
|
|
8181
8832
|
}
|
|
8182
8833
|
}
|
|
@@ -8189,11 +8840,11 @@ function performInit(cwd, force) {
|
|
|
8189
8840
|
}
|
|
8190
8841
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
8191
8842
|
const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
|
|
8192
|
-
if (
|
|
8843
|
+
if (fs31.existsSync(target) && !force) {
|
|
8193
8844
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
8194
8845
|
continue;
|
|
8195
8846
|
}
|
|
8196
|
-
|
|
8847
|
+
fs31.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
8197
8848
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
8198
8849
|
}
|
|
8199
8850
|
let labels;
|
|
@@ -8271,7 +8922,7 @@ init_loadConventions();
|
|
|
8271
8922
|
init_loadCoverageRules();
|
|
8272
8923
|
|
|
8273
8924
|
// src/goal/state.ts
|
|
8274
|
-
import * as
|
|
8925
|
+
import * as fs32 from "fs";
|
|
8275
8926
|
import * as path30 from "path";
|
|
8276
8927
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
8277
8928
|
var GoalStateError = class extends Error {
|
|
@@ -8330,12 +8981,12 @@ function goalStatePath(cwd, goalId) {
|
|
|
8330
8981
|
}
|
|
8331
8982
|
function readGoalState(cwd, goalId) {
|
|
8332
8983
|
const file = goalStatePath(cwd, goalId);
|
|
8333
|
-
if (!
|
|
8984
|
+
if (!fs32.existsSync(file)) {
|
|
8334
8985
|
throw new GoalStateError(file, "file not found");
|
|
8335
8986
|
}
|
|
8336
8987
|
let raw;
|
|
8337
8988
|
try {
|
|
8338
|
-
raw = JSON.parse(
|
|
8989
|
+
raw = JSON.parse(fs32.readFileSync(file, "utf-8"));
|
|
8339
8990
|
} catch (err) {
|
|
8340
8991
|
throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
8341
8992
|
}
|
|
@@ -8343,8 +8994,8 @@ function readGoalState(cwd, goalId) {
|
|
|
8343
8994
|
}
|
|
8344
8995
|
function writeGoalState(cwd, goalId, state) {
|
|
8345
8996
|
const file = goalStatePath(cwd, goalId);
|
|
8346
|
-
|
|
8347
|
-
|
|
8997
|
+
fs32.mkdirSync(path30.dirname(file), { recursive: true });
|
|
8998
|
+
fs32.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
8348
8999
|
}
|
|
8349
9000
|
function nowIso() {
|
|
8350
9001
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -8441,7 +9092,7 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
8441
9092
|
};
|
|
8442
9093
|
|
|
8443
9094
|
// src/scripts/loadJobFromFile.ts
|
|
8444
|
-
import * as
|
|
9095
|
+
import * as fs33 from "fs";
|
|
8445
9096
|
import * as path31 from "path";
|
|
8446
9097
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
8447
9098
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
@@ -8452,22 +9103,22 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8452
9103
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
8453
9104
|
}
|
|
8454
9105
|
const absPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
8455
|
-
if (!
|
|
9106
|
+
if (!fs33.existsSync(absPath)) {
|
|
8456
9107
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
8457
9108
|
}
|
|
8458
|
-
const raw =
|
|
9109
|
+
const raw = fs33.readFileSync(absPath, "utf-8");
|
|
8459
9110
|
const { title, body } = parseJobFile(raw, slug);
|
|
8460
9111
|
const workerSlug = (splitFrontmatter2(raw).frontmatter.worker ?? "").trim();
|
|
8461
9112
|
let workerTitle = "";
|
|
8462
9113
|
let workerPersona = "";
|
|
8463
9114
|
if (workerSlug) {
|
|
8464
9115
|
const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8465
|
-
if (!
|
|
9116
|
+
if (!fs33.existsSync(workerPath)) {
|
|
8466
9117
|
throw new Error(
|
|
8467
9118
|
`loadJobFromFile: job '${slug}' declares worker '${workerSlug}' but ${workerPath} does not exist`
|
|
8468
9119
|
);
|
|
8469
9120
|
}
|
|
8470
|
-
const workerRaw =
|
|
9121
|
+
const workerRaw = fs33.readFileSync(workerPath, "utf-8");
|
|
8471
9122
|
const parsed = parseJobFile(workerRaw, workerSlug);
|
|
8472
9123
|
workerTitle = parsed.title;
|
|
8473
9124
|
workerPersona = parsed.body;
|
|
@@ -8505,7 +9156,7 @@ function humanizeSlug(slug) {
|
|
|
8505
9156
|
}
|
|
8506
9157
|
|
|
8507
9158
|
// src/scripts/loadWorkerAdhoc.ts
|
|
8508
|
-
import * as
|
|
9159
|
+
import * as fs34 from "fs";
|
|
8509
9160
|
import * as path32 from "path";
|
|
8510
9161
|
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
8511
9162
|
const workersDir = String(args?.workersDir ?? ".kody/workers");
|
|
@@ -8514,10 +9165,10 @@ var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
|
8514
9165
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
8515
9166
|
}
|
|
8516
9167
|
const workerPath = path32.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8517
|
-
if (!
|
|
9168
|
+
if (!fs34.existsSync(workerPath)) {
|
|
8518
9169
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
8519
9170
|
}
|
|
8520
|
-
const { title, body } = parsePersona(
|
|
9171
|
+
const { title, body } = parsePersona(fs34.readFileSync(workerPath, "utf-8"), workerSlug);
|
|
8521
9172
|
const message = resolveMessage(ctx.args.message);
|
|
8522
9173
|
if (!message) {
|
|
8523
9174
|
throw new Error(
|
|
@@ -8537,9 +9188,9 @@ function resolveMessage(messageArg) {
|
|
|
8537
9188
|
}
|
|
8538
9189
|
function readCommentBody() {
|
|
8539
9190
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
8540
|
-
if (!eventPath || !
|
|
9191
|
+
if (!eventPath || !fs34.existsSync(eventPath)) return "";
|
|
8541
9192
|
try {
|
|
8542
|
-
const event = JSON.parse(
|
|
9193
|
+
const event = JSON.parse(fs34.readFileSync(eventPath, "utf-8"));
|
|
8543
9194
|
return String(event.comment?.body ?? "");
|
|
8544
9195
|
} catch {
|
|
8545
9196
|
return "";
|
|
@@ -8585,7 +9236,7 @@ init_loadPriorArt();
|
|
|
8585
9236
|
init_events();
|
|
8586
9237
|
|
|
8587
9238
|
// src/taskContext.ts
|
|
8588
|
-
import * as
|
|
9239
|
+
import * as fs36 from "fs";
|
|
8589
9240
|
import * as path34 from "path";
|
|
8590
9241
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
8591
9242
|
function buildTaskContext(args) {
|
|
@@ -8603,9 +9254,9 @@ function buildTaskContext(args) {
|
|
|
8603
9254
|
function persistTaskContext(cwd, ctx) {
|
|
8604
9255
|
try {
|
|
8605
9256
|
const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
|
|
8606
|
-
|
|
9257
|
+
fs36.mkdirSync(dir, { recursive: true });
|
|
8607
9258
|
const file = path34.join(dir, "task-context.json");
|
|
8608
|
-
|
|
9259
|
+
fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
8609
9260
|
`);
|
|
8610
9261
|
return file;
|
|
8611
9262
|
} catch (err) {
|
|
@@ -9968,7 +10619,7 @@ function resolveBaseOverride(value) {
|
|
|
9968
10619
|
|
|
9969
10620
|
// src/scripts/runTickScript.ts
|
|
9970
10621
|
import { spawnSync } from "child_process";
|
|
9971
|
-
import * as
|
|
10622
|
+
import * as fs37 from "fs";
|
|
9972
10623
|
import * as path35 from "path";
|
|
9973
10624
|
var runTickScript = async (ctx, _profile, args) => {
|
|
9974
10625
|
ctx.skipAgent = true;
|
|
@@ -9982,12 +10633,12 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
9982
10633
|
return;
|
|
9983
10634
|
}
|
|
9984
10635
|
const jobPath = path35.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
9985
|
-
if (!
|
|
10636
|
+
if (!fs37.existsSync(jobPath)) {
|
|
9986
10637
|
ctx.output.exitCode = 99;
|
|
9987
10638
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
9988
10639
|
return;
|
|
9989
10640
|
}
|
|
9990
|
-
const raw =
|
|
10641
|
+
const raw = fs37.readFileSync(jobPath, "utf-8");
|
|
9991
10642
|
const { frontmatter } = splitFrontmatter2(raw);
|
|
9992
10643
|
const tickScript = frontmatter.tickScript;
|
|
9993
10644
|
if (!tickScript) {
|
|
@@ -9996,7 +10647,7 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
9996
10647
|
return;
|
|
9997
10648
|
}
|
|
9998
10649
|
const scriptPath = path35.isAbsolute(tickScript) ? tickScript : path35.join(ctx.cwd, tickScript);
|
|
9999
|
-
if (!
|
|
10650
|
+
if (!fs37.existsSync(scriptPath)) {
|
|
10000
10651
|
ctx.output.exitCode = 99;
|
|
10001
10652
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
10002
10653
|
return;
|
|
@@ -10144,7 +10795,7 @@ function synthesizeAction(ctx) {
|
|
|
10144
10795
|
}
|
|
10145
10796
|
|
|
10146
10797
|
// src/scripts/serveFlow.ts
|
|
10147
|
-
import { spawn as
|
|
10798
|
+
import { spawn as spawn5 } from "child_process";
|
|
10148
10799
|
function parseTarget(positional) {
|
|
10149
10800
|
if (!Array.isArray(positional) || positional.length === 0) return "none";
|
|
10150
10801
|
const first = String(positional[0]).toLowerCase();
|
|
@@ -10193,7 +10844,7 @@ var serveFlow = async (ctx) => {
|
|
|
10193
10844
|
if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
|
|
10194
10845
|
`);
|
|
10195
10846
|
const args = ["--dangerously-skip-permissions", "--model", model.model];
|
|
10196
|
-
const child =
|
|
10847
|
+
const child = spawn5("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
|
|
10197
10848
|
const exitCode = await new Promise((resolve4) => {
|
|
10198
10849
|
child.on("exit", (code) => resolve4(code ?? 0));
|
|
10199
10850
|
child.on("error", (err) => {
|
|
@@ -10214,7 +10865,7 @@ var serveFlow = async (ctx) => {
|
|
|
10214
10865
|
if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
|
|
10215
10866
|
`);
|
|
10216
10867
|
try {
|
|
10217
|
-
const code =
|
|
10868
|
+
const code = spawn5("code", [ctx.cwd], { stdio: "inherit", env: editorEnv, detached: true });
|
|
10218
10869
|
code.on("error", (err) => {
|
|
10219
10870
|
process.stderr.write(`[kody serve] failed to launch VS Code: ${err.message}
|
|
10220
10871
|
`);
|
|
@@ -10469,7 +11120,7 @@ var verify = async (ctx) => {
|
|
|
10469
11120
|
};
|
|
10470
11121
|
|
|
10471
11122
|
// src/scripts/verifyReproFails.ts
|
|
10472
|
-
import { spawn as
|
|
11123
|
+
import { spawn as spawn6 } from "child_process";
|
|
10473
11124
|
var TEST_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
10474
11125
|
var TAIL_CHARS2 = 8e3;
|
|
10475
11126
|
var ANSI_RE2 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
@@ -10538,7 +11189,7 @@ function stripAnsi2(s) {
|
|
|
10538
11189
|
}
|
|
10539
11190
|
function runCommand2(command, cwd) {
|
|
10540
11191
|
return new Promise((resolve4) => {
|
|
10541
|
-
const child =
|
|
11192
|
+
const child = spawn6(command, {
|
|
10542
11193
|
cwd,
|
|
10543
11194
|
shell: true,
|
|
10544
11195
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
|
|
@@ -10691,17 +11342,17 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
10691
11342
|
return;
|
|
10692
11343
|
}
|
|
10693
11344
|
const fixCiAttempts = state?.core.attempts?.["fix-ci"] ?? 0;
|
|
10694
|
-
await
|
|
11345
|
+
await sleep3(initialWaitSeconds * 1e3);
|
|
10695
11346
|
const deadline = Date.now() + timeoutMinutes * 6e4;
|
|
10696
11347
|
let lastSummary = "";
|
|
10697
11348
|
while (Date.now() < deadline) {
|
|
10698
11349
|
const rows = fetchChecks(prNumber, ctx.cwd);
|
|
10699
11350
|
if (rows === null) {
|
|
10700
|
-
await
|
|
11351
|
+
await sleep3(pollSeconds * 1e3);
|
|
10701
11352
|
continue;
|
|
10702
11353
|
}
|
|
10703
11354
|
if (rows.length === 0) {
|
|
10704
|
-
await
|
|
11355
|
+
await sleep3(pollSeconds * 1e3);
|
|
10705
11356
|
continue;
|
|
10706
11357
|
}
|
|
10707
11358
|
const summary = summarize(rows);
|
|
@@ -10744,7 +11395,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
10744
11395
|
tryPostPr7(prNumber, `\u2705 kody waitForCi: all ${rows.length} checks green on PR #${prNumber}`, ctx.cwd);
|
|
10745
11396
|
return;
|
|
10746
11397
|
}
|
|
10747
|
-
await
|
|
11398
|
+
await sleep3(pollSeconds * 1e3);
|
|
10748
11399
|
}
|
|
10749
11400
|
ctx.data.action = mkAction("CI_TIMEOUT", {
|
|
10750
11401
|
reason: `CI did not complete within ${timeoutMinutes} minutes`,
|
|
@@ -10796,12 +11447,12 @@ function tryPostPr7(prNumber, body, cwd) {
|
|
|
10796
11447
|
} catch {
|
|
10797
11448
|
}
|
|
10798
11449
|
}
|
|
10799
|
-
function
|
|
11450
|
+
function sleep3(ms) {
|
|
10800
11451
|
return new Promise((res) => setTimeout(res, ms));
|
|
10801
11452
|
}
|
|
10802
11453
|
|
|
10803
11454
|
// src/scripts/warmupMcp.ts
|
|
10804
|
-
import { spawn as
|
|
11455
|
+
import { spawn as spawn7 } from "child_process";
|
|
10805
11456
|
var PER_SERVER_TIMEOUT_MS = 6e4;
|
|
10806
11457
|
var PER_REQUEST_TIMEOUT_MS = 2e4;
|
|
10807
11458
|
var warmupMcp = async (_ctx, profile) => {
|
|
@@ -10823,7 +11474,7 @@ var warmupMcp = async (_ctx, profile) => {
|
|
|
10823
11474
|
}
|
|
10824
11475
|
};
|
|
10825
11476
|
async function warmupOne(command, args, env) {
|
|
10826
|
-
const child =
|
|
11477
|
+
const child = spawn7(command, args, {
|
|
10827
11478
|
stdio: ["pipe", "pipe", "pipe"],
|
|
10828
11479
|
env: env ? { ...process.env, ...env } : process.env
|
|
10829
11480
|
});
|
|
@@ -11012,7 +11663,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
11012
11663
|
};
|
|
11013
11664
|
|
|
11014
11665
|
// src/scripts/writeRunSummary.ts
|
|
11015
|
-
import * as
|
|
11666
|
+
import * as fs38 from "fs";
|
|
11016
11667
|
var writeRunSummary = async (ctx, profile) => {
|
|
11017
11668
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
11018
11669
|
if (!summaryPath) return;
|
|
@@ -11034,7 +11685,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
11034
11685
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
11035
11686
|
lines.push("");
|
|
11036
11687
|
try {
|
|
11037
|
-
|
|
11688
|
+
fs38.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
11038
11689
|
`);
|
|
11039
11690
|
} catch {
|
|
11040
11691
|
}
|
|
@@ -11078,6 +11729,8 @@ var preflightScripts = {
|
|
|
11078
11729
|
runTickScript,
|
|
11079
11730
|
serveFlow,
|
|
11080
11731
|
brainServe,
|
|
11732
|
+
runnerServe,
|
|
11733
|
+
poolServe,
|
|
11081
11734
|
loadGoalState,
|
|
11082
11735
|
handleAbandonedGoal,
|
|
11083
11736
|
deriveGoalPhase,
|
|
@@ -11487,7 +12140,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
11487
12140
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
11488
12141
|
try {
|
|
11489
12142
|
const profilePath = resolveProfilePath(profileName);
|
|
11490
|
-
if (!
|
|
12143
|
+
if (!fs39.existsSync(profilePath)) return null;
|
|
11491
12144
|
return loadProfile(profilePath).inputs;
|
|
11492
12145
|
} catch {
|
|
11493
12146
|
return null;
|
|
@@ -11506,7 +12159,7 @@ function resolveProfilePath(profileName) {
|
|
|
11506
12159
|
// fallback
|
|
11507
12160
|
];
|
|
11508
12161
|
for (const c of candidates) {
|
|
11509
|
-
if (
|
|
12162
|
+
if (fs39.existsSync(c)) return c;
|
|
11510
12163
|
}
|
|
11511
12164
|
return candidates[0];
|
|
11512
12165
|
}
|
|
@@ -11607,7 +12260,7 @@ var SIGKILL_GRACE_MS = 5e3;
|
|
|
11607
12260
|
async function runShellEntry(entry, ctx, profile) {
|
|
11608
12261
|
const shellName = entry.shell;
|
|
11609
12262
|
const shellPath = path36.join(profile.dir, shellName);
|
|
11610
|
-
if (!
|
|
12263
|
+
if (!fs39.existsSync(shellPath)) {
|
|
11611
12264
|
ctx.skipAgent = true;
|
|
11612
12265
|
ctx.output.exitCode = 99;
|
|
11613
12266
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -11623,7 +12276,7 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
11623
12276
|
env[`KODY_CFG_${k}`] = v;
|
|
11624
12277
|
}
|
|
11625
12278
|
const timeoutMs = resolveShellTimeoutMs(entry);
|
|
11626
|
-
const child =
|
|
12279
|
+
const child = spawn8("bash", [shellPath, ...positional], {
|
|
11627
12280
|
cwd: ctx.cwd,
|
|
11628
12281
|
env,
|
|
11629
12282
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -12086,9 +12739,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
12086
12739
|
return token;
|
|
12087
12740
|
}
|
|
12088
12741
|
function detectPackageManager2(cwd) {
|
|
12089
|
-
if (
|
|
12090
|
-
if (
|
|
12091
|
-
if (
|
|
12742
|
+
if (fs40.existsSync(path37.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
12743
|
+
if (fs40.existsSync(path37.join(cwd, "yarn.lock"))) return "yarn";
|
|
12744
|
+
if (fs40.existsSync(path37.join(cwd, "bun.lockb"))) return "bun";
|
|
12092
12745
|
return "npm";
|
|
12093
12746
|
}
|
|
12094
12747
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -12178,8 +12831,8 @@ function postFailureTail(issueNumber, cwd, reason) {
|
|
|
12178
12831
|
const logPath = path37.join(cwd, ".kody", "last-run.jsonl");
|
|
12179
12832
|
let tail = "";
|
|
12180
12833
|
try {
|
|
12181
|
-
if (
|
|
12182
|
-
const content =
|
|
12834
|
+
if (fs40.existsSync(logPath)) {
|
|
12835
|
+
const content = fs40.readFileSync(logPath, "utf-8");
|
|
12183
12836
|
tail = content.slice(-3e3);
|
|
12184
12837
|
}
|
|
12185
12838
|
} catch {
|
|
@@ -12214,9 +12867,9 @@ async function runCi(argv) {
|
|
|
12214
12867
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
12215
12868
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
12216
12869
|
let manualWorkflowDispatch = false;
|
|
12217
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
12870
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs40.existsSync(dispatchEventPath)) {
|
|
12218
12871
|
try {
|
|
12219
|
-
const evt = JSON.parse(
|
|
12872
|
+
const evt = JSON.parse(fs40.readFileSync(dispatchEventPath, "utf-8"));
|
|
12220
12873
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
12221
12874
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
12222
12875
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -12480,7 +13133,7 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
12480
13133
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
12481
13134
|
const tasksDir = path38.join(".kody", "tasks", safeSession);
|
|
12482
13135
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
12483
|
-
const paths = candidatePaths.filter((p) =>
|
|
13136
|
+
const paths = candidatePaths.filter((p) => fs41.existsSync(path38.join(cwd, p)));
|
|
12484
13137
|
if (paths.length === 0) return;
|
|
12485
13138
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
12486
13139
|
try {
|
|
@@ -12576,7 +13229,7 @@ ${CHAT_HELP}`);
|
|
|
12576
13229
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
12577
13230
|
const meta = readMeta(sessionFile);
|
|
12578
13231
|
process.stdout.write(
|
|
12579
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
13232
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs41.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
12580
13233
|
`
|
|
12581
13234
|
);
|
|
12582
13235
|
try {
|