@kody-ade/kody-engine 0.4.139 → 0.4.141
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
|
}
|
|
@@ -672,28 +672,28 @@ var loadMemoryContext_exports = {};
|
|
|
672
672
|
__export(loadMemoryContext_exports, {
|
|
673
673
|
loadMemoryContext: () => loadMemoryContext
|
|
674
674
|
});
|
|
675
|
-
import * as
|
|
676
|
-
import * as
|
|
675
|
+
import * as fs31 from "fs";
|
|
676
|
+
import * as path29 from "path";
|
|
677
677
|
function collectPages(memoryAbs) {
|
|
678
678
|
const out = [];
|
|
679
679
|
walkMd(memoryAbs, (file) => {
|
|
680
680
|
let stat;
|
|
681
681
|
try {
|
|
682
|
-
stat =
|
|
682
|
+
stat = fs31.statSync(file);
|
|
683
683
|
} catch {
|
|
684
684
|
return;
|
|
685
685
|
}
|
|
686
686
|
let raw;
|
|
687
687
|
try {
|
|
688
|
-
raw =
|
|
688
|
+
raw = fs31.readFileSync(file, "utf-8");
|
|
689
689
|
} catch {
|
|
690
690
|
return;
|
|
691
691
|
}
|
|
692
692
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
693
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
693
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path29.basename(file, ".md");
|
|
694
694
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
695
695
|
out.push({
|
|
696
|
-
relPath:
|
|
696
|
+
relPath: path29.relative(memoryAbs, file),
|
|
697
697
|
title,
|
|
698
698
|
updated,
|
|
699
699
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
@@ -761,16 +761,16 @@ function walkMd(root, visit) {
|
|
|
761
761
|
const dir = stack.pop();
|
|
762
762
|
let names;
|
|
763
763
|
try {
|
|
764
|
-
names =
|
|
764
|
+
names = fs31.readdirSync(dir);
|
|
765
765
|
} catch {
|
|
766
766
|
continue;
|
|
767
767
|
}
|
|
768
768
|
for (const name of names) {
|
|
769
769
|
if (name.startsWith(".")) continue;
|
|
770
|
-
const full =
|
|
770
|
+
const full = path29.join(dir, name);
|
|
771
771
|
let stat;
|
|
772
772
|
try {
|
|
773
|
-
stat =
|
|
773
|
+
stat = fs31.statSync(full);
|
|
774
774
|
} catch {
|
|
775
775
|
continue;
|
|
776
776
|
}
|
|
@@ -793,8 +793,8 @@ var init_loadMemoryContext = __esm({
|
|
|
793
793
|
TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
794
794
|
loadMemoryContext = async (ctx) => {
|
|
795
795
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
796
|
-
const memoryAbs =
|
|
797
|
-
if (!
|
|
796
|
+
const memoryAbs = path29.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
797
|
+
if (!fs31.existsSync(memoryAbs)) {
|
|
798
798
|
ctx.data.memoryContext = "";
|
|
799
799
|
return;
|
|
800
800
|
}
|
|
@@ -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.141",
|
|
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",
|
|
@@ -982,9 +982,9 @@ var package_default = {
|
|
|
982
982
|
};
|
|
983
983
|
|
|
984
984
|
// src/chat-cli.ts
|
|
985
|
-
import { execFileSync as
|
|
986
|
-
import * as
|
|
987
|
-
import * as
|
|
985
|
+
import { execFileSync as execFileSync30 } from "child_process";
|
|
986
|
+
import * as fs41 from "fs";
|
|
987
|
+
import * as path37 from "path";
|
|
988
988
|
|
|
989
989
|
// src/chat/events.ts
|
|
990
990
|
import * as fs from "fs";
|
|
@@ -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 {
|
|
@@ -2533,9 +2533,9 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
2533
2533
|
}
|
|
2534
2534
|
|
|
2535
2535
|
// src/kody-cli.ts
|
|
2536
|
-
import { execFileSync as
|
|
2537
|
-
import * as
|
|
2538
|
-
import * as
|
|
2536
|
+
import { execFileSync as execFileSync29 } from "child_process";
|
|
2537
|
+
import * as fs40 from "fs";
|
|
2538
|
+
import * as path36 from "path";
|
|
2539
2539
|
|
|
2540
2540
|
// src/dispatch.ts
|
|
2541
2541
|
import * as fs11 from "fs";
|
|
@@ -2867,9 +2867,9 @@ function coerceBare(spec, value) {
|
|
|
2867
2867
|
init_issue();
|
|
2868
2868
|
|
|
2869
2869
|
// src/executor.ts
|
|
2870
|
-
import { execFileSync as
|
|
2871
|
-
import * as
|
|
2872
|
-
import * as
|
|
2870
|
+
import { execFileSync as execFileSync28, spawn as spawn9 } from "child_process";
|
|
2871
|
+
import * as fs39 from "fs";
|
|
2872
|
+
import * as path35 from "path";
|
|
2873
2873
|
|
|
2874
2874
|
// src/discipline.ts
|
|
2875
2875
|
var DISCIPLINE = `# Working discipline (applies to this entire task)
|
|
@@ -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) => {
|
|
@@ -4978,43 +5047,181 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
4978
5047
|
}
|
|
4979
5048
|
};
|
|
4980
5049
|
|
|
4981
|
-
// src/
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
5050
|
+
// src/goal/stateStore.ts
|
|
5051
|
+
init_issue();
|
|
5052
|
+
|
|
5053
|
+
// src/stateBranch.ts
|
|
5054
|
+
init_issue();
|
|
5055
|
+
var STATE_BRANCH = "kody-state";
|
|
5056
|
+
function is404(err) {
|
|
5057
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5058
|
+
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5059
|
+
}
|
|
5060
|
+
function ensureStateBranch(owner, repo, cwd) {
|
|
4988
5061
|
try {
|
|
4989
|
-
|
|
5062
|
+
gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
|
|
5063
|
+
return;
|
|
4990
5064
|
} catch (err) {
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
`
|
|
5065
|
+
if (!is404(err)) throw err;
|
|
5066
|
+
}
|
|
5067
|
+
const repoInfo = JSON.parse(gh(["api", `/repos/${owner}/${repo}`], { cwd }));
|
|
5068
|
+
const defaultBranch2 = repoInfo.default_branch;
|
|
5069
|
+
if (!defaultBranch2) {
|
|
5070
|
+
throw new Error(`ensureStateBranch: could not resolve default branch for ${owner}/${repo}`);
|
|
5071
|
+
}
|
|
5072
|
+
const headRef = JSON.parse(
|
|
5073
|
+
gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${defaultBranch2}`], { cwd })
|
|
5074
|
+
);
|
|
5075
|
+
const sha = headRef.object?.sha;
|
|
5076
|
+
if (!sha) {
|
|
5077
|
+
throw new Error(`ensureStateBranch: could not resolve head sha for ${owner}/${repo}@${defaultBranch2}`);
|
|
5078
|
+
}
|
|
5079
|
+
try {
|
|
5080
|
+
gh(["api", "--method", "POST", `/repos/${owner}/${repo}/git/refs`, "--input", "-"], {
|
|
5081
|
+
cwd,
|
|
5082
|
+
input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
|
|
5083
|
+
});
|
|
5084
|
+
} catch (err) {
|
|
5085
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5086
|
+
if (/already exists/i.test(msg) || /HTTP 422/i.test(msg)) return;
|
|
5087
|
+
throw err;
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
// src/goal/state.ts
|
|
5092
|
+
import * as fs21 from "fs";
|
|
5093
|
+
import * as path20 from "path";
|
|
5094
|
+
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
5095
|
+
var GoalStateError = class extends Error {
|
|
5096
|
+
constructor(path38, message) {
|
|
5097
|
+
super(`Invalid goal state at ${path38}:
|
|
5098
|
+
${message}`);
|
|
5099
|
+
this.path = path38;
|
|
5100
|
+
this.name = "GoalStateError";
|
|
5101
|
+
}
|
|
5102
|
+
path;
|
|
5103
|
+
};
|
|
5104
|
+
function parseGoalState(filePath, raw) {
|
|
5105
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5106
|
+
throw new GoalStateError(filePath, "must be a JSON object");
|
|
5107
|
+
}
|
|
5108
|
+
const r = raw;
|
|
5109
|
+
const stateValue = r.state;
|
|
5110
|
+
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5111
|
+
throw new GoalStateError(
|
|
5112
|
+
filePath,
|
|
5113
|
+
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
4994
5114
|
);
|
|
4995
|
-
return;
|
|
4996
5115
|
}
|
|
5116
|
+
const parsed = {
|
|
5117
|
+
state: stateValue,
|
|
5118
|
+
extra: {}
|
|
5119
|
+
};
|
|
5120
|
+
if (typeof r.mergeApproved === "boolean") {
|
|
5121
|
+
parsed.mergeApproved = r.mergeApproved;
|
|
5122
|
+
}
|
|
5123
|
+
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
5124
|
+
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
5125
|
+
}
|
|
5126
|
+
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
5127
|
+
const v = r[ts];
|
|
5128
|
+
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5129
|
+
}
|
|
5130
|
+
const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
|
|
5131
|
+
for (const [k, v] of Object.entries(r)) {
|
|
5132
|
+
if (!known.has(k)) parsed.extra[k] = v;
|
|
5133
|
+
}
|
|
5134
|
+
return parsed;
|
|
5135
|
+
}
|
|
5136
|
+
function serializeGoalState(s) {
|
|
5137
|
+
const obj = { ...s.extra, state: s.state };
|
|
5138
|
+
if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
|
|
5139
|
+
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
5140
|
+
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5141
|
+
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5142
|
+
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5143
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
5144
|
+
`;
|
|
5145
|
+
}
|
|
5146
|
+
function nowIso() {
|
|
5147
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5148
|
+
}
|
|
5149
|
+
|
|
5150
|
+
// src/goal/stateStore.ts
|
|
5151
|
+
function statePath(goalId) {
|
|
5152
|
+
return `.kody/goals/${goalId}/state.json`;
|
|
5153
|
+
}
|
|
5154
|
+
function is4042(err) {
|
|
5155
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5156
|
+
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5157
|
+
}
|
|
5158
|
+
function fetchGoalState(owner, repo, goalId, cwd) {
|
|
5159
|
+
const filePath = statePath(goalId);
|
|
5160
|
+
let raw;
|
|
4997
5161
|
try {
|
|
4998
|
-
|
|
5162
|
+
raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5163
|
+
} catch (err) {
|
|
5164
|
+
if (is4042(err)) return null;
|
|
5165
|
+
throw err;
|
|
5166
|
+
}
|
|
5167
|
+
const o = JSON.parse(raw);
|
|
5168
|
+
if (!o.content) return null;
|
|
5169
|
+
const decoded = Buffer.from(o.content, "base64").toString("utf-8");
|
|
5170
|
+
return parseGoalState(filePath, JSON.parse(decoded));
|
|
5171
|
+
}
|
|
5172
|
+
function putGoalState(owner, repo, goalId, state, message, cwd) {
|
|
5173
|
+
ensureStateBranch(owner, repo, cwd);
|
|
5174
|
+
const filePath = statePath(goalId);
|
|
5175
|
+
const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
|
|
5176
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
5177
|
+
let sha;
|
|
5178
|
+
try {
|
|
5179
|
+
const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5180
|
+
const o = JSON.parse(cur);
|
|
5181
|
+
if (o.sha) sha = o.sha;
|
|
5182
|
+
} catch (err) {
|
|
5183
|
+
if (!is4042(err)) throw err;
|
|
5184
|
+
}
|
|
5185
|
+
const payload = { message, content, branch: STATE_BRANCH };
|
|
5186
|
+
if (sha) payload.sha = sha;
|
|
5187
|
+
try {
|
|
5188
|
+
gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
|
|
5189
|
+
cwd,
|
|
5190
|
+
input: JSON.stringify(payload)
|
|
5191
|
+
});
|
|
5192
|
+
return;
|
|
5193
|
+
} catch (err) {
|
|
5194
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5195
|
+
const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
5196
|
+
if (!conflict || attempt === 3) throw err;
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
}
|
|
5200
|
+
|
|
5201
|
+
// src/scripts/commitGoalState.ts
|
|
5202
|
+
var commitGoalState = async (ctx) => {
|
|
5203
|
+
const goal = ctx.data.goal;
|
|
5204
|
+
if (!goal) return;
|
|
5205
|
+
if (ctx.data.goalPersistChanged !== true) return;
|
|
5206
|
+
const updated = ctx.data.goalPersistState;
|
|
5207
|
+
if (!updated) return;
|
|
5208
|
+
const owner = ctx.config.github?.owner;
|
|
5209
|
+
const repo = ctx.config.github?.repo;
|
|
5210
|
+
if (!owner || !repo) {
|
|
5211
|
+
process.stderr.write(`[goal-tick] commitGoalState: missing github owner/repo; cannot persist ${goal.id}
|
|
5212
|
+
`);
|
|
4999
5213
|
return;
|
|
5000
|
-
} catch {
|
|
5001
5214
|
}
|
|
5002
|
-
const msg = describeCommitMessage(goal);
|
|
5003
5215
|
try {
|
|
5004
|
-
|
|
5216
|
+
putGoalState(owner, repo, goal.id, updated, describeCommitMessage(goal), ctx.cwd);
|
|
5005
5217
|
} catch (err) {
|
|
5006
5218
|
process.stderr.write(
|
|
5007
|
-
`[goal-tick] commitGoalState:
|
|
5219
|
+
`[goal-tick] commitGoalState: persist to ${STATE_BRANCH_LABEL} failed (${err instanceof Error ? err.message : String(err)}); will retry next tick
|
|
5008
5220
|
`
|
|
5009
5221
|
);
|
|
5010
|
-
return;
|
|
5011
|
-
}
|
|
5012
|
-
const result = pushWithRetry({ cwd: ctx.cwd });
|
|
5013
|
-
if (!result.ok) {
|
|
5014
|
-
process.stderr.write(`[goal-tick] commitGoalState: push failed (${result.reason}); will retry next tick
|
|
5015
|
-
`);
|
|
5016
5222
|
}
|
|
5017
5223
|
};
|
|
5224
|
+
var STATE_BRANCH_LABEL = "kody-state";
|
|
5018
5225
|
function describeCommitMessage(goal) {
|
|
5019
5226
|
if (goal.state === "closed") return `chore(goals): abandon ${goal.id} (cleanup complete)`;
|
|
5020
5227
|
if (goal.state === "awaiting-merge") return `chore(goals): park ${goal.id} awaiting merge`;
|
|
@@ -5029,7 +5236,7 @@ function describeCommitMessage(goal) {
|
|
|
5029
5236
|
}
|
|
5030
5237
|
|
|
5031
5238
|
// src/scripts/composePrompt.ts
|
|
5032
|
-
import * as
|
|
5239
|
+
import * as fs22 from "fs";
|
|
5033
5240
|
import * as path21 from "path";
|
|
5034
5241
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
5035
5242
|
var composePrompt = async (ctx, profile) => {
|
|
@@ -5042,7 +5249,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
5042
5249
|
].filter(Boolean);
|
|
5043
5250
|
let templatePath = "";
|
|
5044
5251
|
for (const c of candidates) {
|
|
5045
|
-
if (
|
|
5252
|
+
if (fs22.existsSync(c)) {
|
|
5046
5253
|
templatePath = c;
|
|
5047
5254
|
break;
|
|
5048
5255
|
}
|
|
@@ -5050,7 +5257,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
5050
5257
|
if (!templatePath) {
|
|
5051
5258
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
5052
5259
|
}
|
|
5053
|
-
const template =
|
|
5260
|
+
const template = fs22.readFileSync(templatePath, "utf-8");
|
|
5054
5261
|
const tokens = {
|
|
5055
5262
|
...stringifyAll(ctx.args, "args."),
|
|
5056
5263
|
...stringifyAll(ctx.data, ""),
|
|
@@ -5128,9 +5335,6 @@ function formatToolsUsage(profile) {
|
|
|
5128
5335
|
|
|
5129
5336
|
// src/scripts/createQaGoal.ts
|
|
5130
5337
|
init_issue();
|
|
5131
|
-
import { execFileSync as execFileSync11 } from "child_process";
|
|
5132
|
-
import * as fs22 from "fs";
|
|
5133
|
-
import * as path22 from "path";
|
|
5134
5338
|
|
|
5135
5339
|
// src/scripts/postReviewResult.ts
|
|
5136
5340
|
init_issue();
|
|
@@ -5382,104 +5586,6 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
|
5382
5586
|
if (!m) throw new Error(`gh issue create returned unexpected output: ${out}`);
|
|
5383
5587
|
return { number: Number(m[1]), created: true };
|
|
5384
5588
|
}
|
|
5385
|
-
function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
5386
|
-
const dir = path22.join(cwd, ".kody", "goals", goalId);
|
|
5387
|
-
fs22.mkdirSync(dir, { recursive: true });
|
|
5388
|
-
const state = {
|
|
5389
|
-
version: 1,
|
|
5390
|
-
state: "active",
|
|
5391
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5392
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5393
|
-
...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
|
|
5394
|
-
};
|
|
5395
|
-
const filePath = path22.join(dir, "state.json");
|
|
5396
|
-
fs22.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
|
|
5397
|
-
`);
|
|
5398
|
-
return filePath;
|
|
5399
|
-
}
|
|
5400
|
-
function gitTry(args, cwd) {
|
|
5401
|
-
const env = { ...process.env, SKIP_HOOKS: "1", HUSKY: "0" };
|
|
5402
|
-
try {
|
|
5403
|
-
execFileSync11("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], env });
|
|
5404
|
-
return { ok: true, stderr: "" };
|
|
5405
|
-
} catch (err) {
|
|
5406
|
-
const e = err;
|
|
5407
|
-
const stderr = typeof e?.stderr === "string" ? e.stderr : Buffer.isBuffer(e?.stderr) ? e.stderr.toString("utf8") : e?.message ?? "";
|
|
5408
|
-
return { ok: false, stderr: stderr.trim() };
|
|
5409
|
-
}
|
|
5410
|
-
}
|
|
5411
|
-
function commitAndPushState(filePath, goalId, cwd) {
|
|
5412
|
-
const add = gitTry(["add", filePath], cwd);
|
|
5413
|
-
if (!add.ok) {
|
|
5414
|
-
process.stderr.write(`[createQaGoal] git add failed: ${add.stderr.slice(-400) || "(no stderr)"}
|
|
5415
|
-
`);
|
|
5416
|
-
return;
|
|
5417
|
-
}
|
|
5418
|
-
const diff = gitTry(["diff", "--cached", "--quiet"], cwd);
|
|
5419
|
-
if (diff.ok) {
|
|
5420
|
-
process.stderr.write(`[createQaGoal] state.json unchanged \u2014 nothing to commit
|
|
5421
|
-
`);
|
|
5422
|
-
return;
|
|
5423
|
-
}
|
|
5424
|
-
const commit = gitTry(["commit", "-m", `chore(goals): activate ${goalId}`, "--quiet"], cwd);
|
|
5425
|
-
if (!commit.ok) {
|
|
5426
|
-
process.stderr.write(`[createQaGoal] git commit failed: ${commit.stderr.slice(-400) || "(no stderr)"}
|
|
5427
|
-
`);
|
|
5428
|
-
return;
|
|
5429
|
-
}
|
|
5430
|
-
const push = gitTry(["push", "--quiet"], cwd);
|
|
5431
|
-
if (push.ok) return;
|
|
5432
|
-
const stderr = push.stderr;
|
|
5433
|
-
const tail = stderr.slice(-400) || "(no stderr captured)";
|
|
5434
|
-
if (/non-fast-forward|rejected|fetch first|behind/i.test(stderr)) {
|
|
5435
|
-
process.stderr.write(`[createQaGoal] push rejected (non-fast-forward) \u2014 pulling --rebase and retrying
|
|
5436
|
-
`);
|
|
5437
|
-
const rebase = gitTry(["pull", "--rebase", "--autostash", "--quiet"], cwd);
|
|
5438
|
-
if (!rebase.ok) {
|
|
5439
|
-
process.stderr.write(
|
|
5440
|
-
`[createQaGoal] rebase failed (manual recovery required): ${rebase.stderr.slice(-400) || "(no stderr)"}
|
|
5441
|
-
`
|
|
5442
|
-
);
|
|
5443
|
-
return;
|
|
5444
|
-
}
|
|
5445
|
-
const retryPush = gitTry(["push", "--quiet"], cwd);
|
|
5446
|
-
if (retryPush.ok) {
|
|
5447
|
-
process.stderr.write(`[createQaGoal] push succeeded after rebase
|
|
5448
|
-
`);
|
|
5449
|
-
return;
|
|
5450
|
-
}
|
|
5451
|
-
process.stderr.write(
|
|
5452
|
-
`[createQaGoal] push still failed after rebase: ${retryPush.stderr.slice(-400) || "(no stderr)"}
|
|
5453
|
-
`
|
|
5454
|
-
);
|
|
5455
|
-
return;
|
|
5456
|
-
}
|
|
5457
|
-
if (/pre-push|hook|husky/i.test(stderr)) {
|
|
5458
|
-
process.stderr.write(`[createQaGoal] push rejected by pre-push hook \u2014 retrying with --no-verify
|
|
5459
|
-
`);
|
|
5460
|
-
process.stderr.write(`[createQaGoal] hook output:
|
|
5461
|
-
${tail}
|
|
5462
|
-
`);
|
|
5463
|
-
const noVerify = gitTry(["push", "--no-verify", "--quiet"], cwd);
|
|
5464
|
-
if (noVerify.ok) {
|
|
5465
|
-
process.stderr.write(`[createQaGoal] push succeeded with --no-verify (consider adding kody artifacts to ignore configs)
|
|
5466
|
-
`);
|
|
5467
|
-
return;
|
|
5468
|
-
}
|
|
5469
|
-
process.stderr.write(
|
|
5470
|
-
`[createQaGoal] --no-verify push also failed: ${noVerify.stderr.slice(-400) || "(no stderr)"}
|
|
5471
|
-
`
|
|
5472
|
-
);
|
|
5473
|
-
return;
|
|
5474
|
-
}
|
|
5475
|
-
process.stderr.write(
|
|
5476
|
-
`[createQaGoal] state.json commit landed but push failed.
|
|
5477
|
-
[createQaGoal] The goal will not be visible to goal-scheduler in CI until you run 'git push' manually.
|
|
5478
|
-
[createQaGoal] git stderr:
|
|
5479
|
-
${tail}
|
|
5480
|
-
`
|
|
5481
|
-
);
|
|
5482
|
-
}
|
|
5483
5589
|
function createTaskIssue(finding, goalId, manifestNumber, cwd) {
|
|
5484
5590
|
const labels = [`goal:${goalId}`, severityLabel(finding.severity), FINDING_LABEL];
|
|
5485
5591
|
ensureLabel(`goal:${goalId}`, "1d76db", `goal: ${goalId}`, cwd);
|
|
@@ -5514,36 +5620,46 @@ var createQaGoal = async (ctx, _profile, agentResult) => {
|
|
|
5514
5620
|
ctx.data.action = failedAction2("empty report body");
|
|
5515
5621
|
return;
|
|
5516
5622
|
}
|
|
5623
|
+
const { markdown } = splitReport(finalText);
|
|
5624
|
+
const verdict = detectVerdict(markdown);
|
|
5625
|
+
const existingIssue = ctx.args.issue;
|
|
5626
|
+
if (typeof existingIssue === "number" && existingIssue > 0) {
|
|
5627
|
+
try {
|
|
5628
|
+
postIssueComment(existingIssue, finalText, ctx.cwd);
|
|
5629
|
+
} catch (err) {
|
|
5630
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5631
|
+
ctx.output.exitCode = 4;
|
|
5632
|
+
ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
|
|
5633
|
+
ctx.data.action = failedAction2(ctx.output.reason);
|
|
5634
|
+
return;
|
|
5635
|
+
}
|
|
5636
|
+
process.stdout.write(
|
|
5637
|
+
`
|
|
5638
|
+
QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
|
|
5639
|
+
`
|
|
5640
|
+
);
|
|
5641
|
+
ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
|
|
5642
|
+
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
5643
|
+
return;
|
|
5644
|
+
}
|
|
5645
|
+
await promoteReportToGoal(
|
|
5646
|
+
ctx,
|
|
5647
|
+
finalText,
|
|
5648
|
+
ctx.args.scope,
|
|
5649
|
+
ctx.args.goal
|
|
5650
|
+
);
|
|
5651
|
+
};
|
|
5652
|
+
async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
|
|
5517
5653
|
const { markdown, data, jsonError } = splitReport(finalText);
|
|
5518
5654
|
const verdict = detectVerdict(markdown);
|
|
5519
5655
|
const findings = data?.findings ?? [];
|
|
5520
|
-
const existingIssue = ctx.args.issue;
|
|
5521
5656
|
if (findings.length === 0 || jsonError) {
|
|
5522
5657
|
if (jsonError) {
|
|
5523
|
-
process.stderr.write(`[
|
|
5658
|
+
process.stderr.write(`[promoteReportToGoal] JSON parse: ${jsonError} \u2014 falling back to single-issue mode
|
|
5524
5659
|
`);
|
|
5525
5660
|
}
|
|
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
5661
|
ensureLabel(FINDING_LABEL, "ededed", "kody: QA finding", ctx.cwd);
|
|
5546
|
-
const scope2 =
|
|
5662
|
+
const scope2 = scopeArg;
|
|
5547
5663
|
const title = `QA [${verdict}]: ${scope2?.trim() || "smoke"} \u2014 ${todayIso()}`.slice(0, 240);
|
|
5548
5664
|
let url = "";
|
|
5549
5665
|
try {
|
|
@@ -5571,8 +5687,8 @@ QA_REPORT_POSTED=${url} (verdict: ${verdict})
|
|
|
5571
5687
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
5572
5688
|
return;
|
|
5573
5689
|
}
|
|
5574
|
-
const explicitGoal =
|
|
5575
|
-
const scope =
|
|
5690
|
+
const explicitGoal = explicitGoalArg?.trim();
|
|
5691
|
+
const scope = scopeArg;
|
|
5576
5692
|
let goalId;
|
|
5577
5693
|
let manifestIssueNumber = null;
|
|
5578
5694
|
let manifestCreated = false;
|
|
@@ -5639,8 +5755,17 @@ ${markdown}`,
|
|
|
5639
5755
|
`);
|
|
5640
5756
|
}
|
|
5641
5757
|
}
|
|
5642
|
-
const
|
|
5643
|
-
|
|
5758
|
+
const now = nowIso();
|
|
5759
|
+
const goalState = { state: "active", startedAt: now, updatedAt: now, extra: { version: 1 } };
|
|
5760
|
+
try {
|
|
5761
|
+
putGoalState(ctx.config.github.owner, ctx.config.github.repo, goalId, goalState, `chore(goals): activate ${goalId}`, ctx.cwd);
|
|
5762
|
+
} catch (err) {
|
|
5763
|
+
process.stderr.write(
|
|
5764
|
+
`[createQaGoal] failed to persist goal state to kody-state: ${err instanceof Error ? err.message : String(err)}
|
|
5765
|
+
[createQaGoal] goal-scheduler will not see ${goalId} until this succeeds.
|
|
5766
|
+
`
|
|
5767
|
+
);
|
|
5768
|
+
}
|
|
5644
5769
|
const repoUrl = `https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}`;
|
|
5645
5770
|
if (manifestIssueNumber !== null) {
|
|
5646
5771
|
const verb = manifestUpdated ? manifestCreated ? "OPENED" : "UPDATED" : "TARGETED";
|
|
@@ -5670,7 +5795,7 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
|
5670
5795
|
mode: explicitGoal ? "goal-attach" : manifestCreated ? "goal-create" : "goal-append"
|
|
5671
5796
|
});
|
|
5672
5797
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
5673
|
-
}
|
|
5798
|
+
}
|
|
5674
5799
|
|
|
5675
5800
|
// src/goal/operations.ts
|
|
5676
5801
|
init_issue();
|
|
@@ -5909,13 +6034,13 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
|
5909
6034
|
}
|
|
5910
6035
|
|
|
5911
6036
|
// src/scripts/diagMcp.ts
|
|
5912
|
-
import { execFileSync as
|
|
6037
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
5913
6038
|
import * as fs23 from "fs";
|
|
5914
6039
|
import * as os4 from "os";
|
|
5915
|
-
import * as
|
|
6040
|
+
import * as path22 from "path";
|
|
5916
6041
|
var diagMcp = async (_ctx) => {
|
|
5917
6042
|
const home = os4.homedir();
|
|
5918
|
-
const cacheDir =
|
|
6043
|
+
const cacheDir = path22.join(home, ".cache", "ms-playwright");
|
|
5919
6044
|
let entries = [];
|
|
5920
6045
|
try {
|
|
5921
6046
|
entries = fs23.readdirSync(cacheDir);
|
|
@@ -5929,7 +6054,7 @@ var diagMcp = async (_ctx) => {
|
|
|
5929
6054
|
process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
|
|
5930
6055
|
`);
|
|
5931
6056
|
try {
|
|
5932
|
-
const v =
|
|
6057
|
+
const v = execFileSync10("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
|
|
5933
6058
|
stdio: "pipe",
|
|
5934
6059
|
timeout: 6e4,
|
|
5935
6060
|
encoding: "utf8"
|
|
@@ -5945,16 +6070,16 @@ var diagMcp = async (_ctx) => {
|
|
|
5945
6070
|
|
|
5946
6071
|
// src/scripts/discoverQaContext.ts
|
|
5947
6072
|
import * as fs25 from "fs";
|
|
5948
|
-
import * as
|
|
6073
|
+
import * as path24 from "path";
|
|
5949
6074
|
|
|
5950
6075
|
// src/scripts/frameworkDetectors.ts
|
|
5951
6076
|
import * as fs24 from "fs";
|
|
5952
|
-
import * as
|
|
6077
|
+
import * as path23 from "path";
|
|
5953
6078
|
function detectFrameworks(cwd) {
|
|
5954
6079
|
const out = [];
|
|
5955
6080
|
let deps = {};
|
|
5956
6081
|
try {
|
|
5957
|
-
const pkg = JSON.parse(fs24.readFileSync(
|
|
6082
|
+
const pkg = JSON.parse(fs24.readFileSync(path23.join(cwd, "package.json"), "utf-8"));
|
|
5958
6083
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5959
6084
|
} catch {
|
|
5960
6085
|
return out;
|
|
@@ -5991,7 +6116,7 @@ function detectFrameworks(cwd) {
|
|
|
5991
6116
|
}
|
|
5992
6117
|
function findFile(cwd, candidates) {
|
|
5993
6118
|
for (const c of candidates) {
|
|
5994
|
-
if (fs24.existsSync(
|
|
6119
|
+
if (fs24.existsSync(path23.join(cwd, c))) return c;
|
|
5995
6120
|
}
|
|
5996
6121
|
return null;
|
|
5997
6122
|
}
|
|
@@ -6004,7 +6129,7 @@ var COLLECTION_DIRS = [
|
|
|
6004
6129
|
function discoverPayloadCollections(cwd) {
|
|
6005
6130
|
const out = [];
|
|
6006
6131
|
for (const dir of COLLECTION_DIRS) {
|
|
6007
|
-
const full =
|
|
6132
|
+
const full = path23.join(cwd, dir);
|
|
6008
6133
|
if (!fs24.existsSync(full)) continue;
|
|
6009
6134
|
let files;
|
|
6010
6135
|
try {
|
|
@@ -6014,7 +6139,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
6014
6139
|
}
|
|
6015
6140
|
for (const file of files) {
|
|
6016
6141
|
try {
|
|
6017
|
-
const filePath =
|
|
6142
|
+
const filePath = path23.join(full, file);
|
|
6018
6143
|
const content = fs24.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
6019
6144
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
6020
6145
|
if (!slugMatch) continue;
|
|
@@ -6029,7 +6154,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
6029
6154
|
out.push({
|
|
6030
6155
|
name,
|
|
6031
6156
|
slug,
|
|
6032
|
-
filePath:
|
|
6157
|
+
filePath: path23.relative(cwd, filePath),
|
|
6033
6158
|
fields: fields.slice(0, 20),
|
|
6034
6159
|
hasAdmin
|
|
6035
6160
|
});
|
|
@@ -6043,7 +6168,7 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
6043
6168
|
function discoverAdminComponents(cwd, collections) {
|
|
6044
6169
|
const out = [];
|
|
6045
6170
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
6046
|
-
const full =
|
|
6171
|
+
const full = path23.join(cwd, dir);
|
|
6047
6172
|
if (!fs24.existsSync(full)) continue;
|
|
6048
6173
|
let entries;
|
|
6049
6174
|
try {
|
|
@@ -6052,19 +6177,19 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6052
6177
|
continue;
|
|
6053
6178
|
}
|
|
6054
6179
|
for (const entry of entries) {
|
|
6055
|
-
const entryPath =
|
|
6180
|
+
const entryPath = path23.join(full, entry.name);
|
|
6056
6181
|
let name;
|
|
6057
6182
|
let filePath;
|
|
6058
6183
|
if (entry.isDirectory()) {
|
|
6059
6184
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
6060
|
-
(f) => fs24.existsSync(
|
|
6185
|
+
(f) => fs24.existsSync(path23.join(entryPath, f))
|
|
6061
6186
|
);
|
|
6062
6187
|
if (!indexFile) continue;
|
|
6063
6188
|
name = entry.name;
|
|
6064
|
-
filePath =
|
|
6189
|
+
filePath = path23.relative(cwd, path23.join(entryPath, indexFile));
|
|
6065
6190
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
6066
6191
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
6067
|
-
filePath =
|
|
6192
|
+
filePath = path23.relative(cwd, entryPath);
|
|
6068
6193
|
} else {
|
|
6069
6194
|
continue;
|
|
6070
6195
|
}
|
|
@@ -6072,7 +6197,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6072
6197
|
if (collections) {
|
|
6073
6198
|
for (const col of collections) {
|
|
6074
6199
|
try {
|
|
6075
|
-
const colContent = fs24.readFileSync(
|
|
6200
|
+
const colContent = fs24.readFileSync(path23.join(cwd, col.filePath), "utf-8");
|
|
6076
6201
|
if (colContent.includes(name)) {
|
|
6077
6202
|
usedInCollection = col.slug;
|
|
6078
6203
|
break;
|
|
@@ -6091,7 +6216,7 @@ function scanApiRoutes(cwd) {
|
|
|
6091
6216
|
const out = [];
|
|
6092
6217
|
const appDirs = ["src/app", "app"];
|
|
6093
6218
|
for (const appDir of appDirs) {
|
|
6094
|
-
const apiDir =
|
|
6219
|
+
const apiDir = path23.join(cwd, appDir, "api");
|
|
6095
6220
|
if (!fs24.existsSync(apiDir)) continue;
|
|
6096
6221
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
6097
6222
|
break;
|
|
@@ -6108,7 +6233,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6108
6233
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
6109
6234
|
if (routeFile) {
|
|
6110
6235
|
try {
|
|
6111
|
-
const content = fs24.readFileSync(
|
|
6236
|
+
const content = fs24.readFileSync(path23.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
6112
6237
|
const methods = HTTP_METHODS.filter(
|
|
6113
6238
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
6114
6239
|
);
|
|
@@ -6116,7 +6241,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6116
6241
|
out.push({
|
|
6117
6242
|
path: prefix,
|
|
6118
6243
|
methods,
|
|
6119
|
-
filePath:
|
|
6244
|
+
filePath: path23.relative(cwd, path23.join(dir, routeFile.name))
|
|
6120
6245
|
});
|
|
6121
6246
|
}
|
|
6122
6247
|
} catch {
|
|
@@ -6127,7 +6252,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6127
6252
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
6128
6253
|
let segment = entry.name;
|
|
6129
6254
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
6130
|
-
walkApiRoutes(
|
|
6255
|
+
walkApiRoutes(path23.join(dir, entry.name), prefix, cwd, out);
|
|
6131
6256
|
continue;
|
|
6132
6257
|
}
|
|
6133
6258
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -6135,7 +6260,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6135
6260
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
6136
6261
|
segment = `:${segment.slice(1, -1)}`;
|
|
6137
6262
|
}
|
|
6138
|
-
walkApiRoutes(
|
|
6263
|
+
walkApiRoutes(path23.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
6139
6264
|
}
|
|
6140
6265
|
}
|
|
6141
6266
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -6155,7 +6280,7 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
6155
6280
|
function scanEnvVars(cwd) {
|
|
6156
6281
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
6157
6282
|
for (const envFile of candidates) {
|
|
6158
|
-
const envPath =
|
|
6283
|
+
const envPath = path23.join(cwd, envFile);
|
|
6159
6284
|
if (!fs24.existsSync(envPath)) continue;
|
|
6160
6285
|
try {
|
|
6161
6286
|
const content = fs24.readFileSync(envPath, "utf-8");
|
|
@@ -6206,9 +6331,9 @@ function runQaDiscovery(cwd) {
|
|
|
6206
6331
|
}
|
|
6207
6332
|
function detectDevServer(cwd, out) {
|
|
6208
6333
|
try {
|
|
6209
|
-
const pkg = JSON.parse(fs25.readFileSync(
|
|
6334
|
+
const pkg = JSON.parse(fs25.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
|
|
6210
6335
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6211
|
-
const pm = fs25.existsSync(
|
|
6336
|
+
const pm = fs25.existsSync(path24.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs25.existsSync(path24.join(cwd, "yarn.lock")) ? "yarn" : fs25.existsSync(path24.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
6212
6337
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
6213
6338
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
6214
6339
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -6218,7 +6343,7 @@ function detectDevServer(cwd, out) {
|
|
|
6218
6343
|
function scanFrontendRoutes(cwd, out) {
|
|
6219
6344
|
const appDirs = ["src/app", "app"];
|
|
6220
6345
|
for (const appDir of appDirs) {
|
|
6221
|
-
const full =
|
|
6346
|
+
const full = path24.join(cwd, appDir);
|
|
6222
6347
|
if (!fs25.existsSync(full)) continue;
|
|
6223
6348
|
walkFrontendRoutes(full, "", out);
|
|
6224
6349
|
break;
|
|
@@ -6244,7 +6369,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
6244
6369
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
6245
6370
|
let segment = entry.name;
|
|
6246
6371
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
6247
|
-
walkFrontendRoutes(
|
|
6372
|
+
walkFrontendRoutes(path24.join(dir, entry.name), prefix, out);
|
|
6248
6373
|
continue;
|
|
6249
6374
|
}
|
|
6250
6375
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -6252,7 +6377,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
6252
6377
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
6253
6378
|
segment = `:${segment.slice(1, -1)}`;
|
|
6254
6379
|
}
|
|
6255
|
-
walkFrontendRoutes(
|
|
6380
|
+
walkFrontendRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
6256
6381
|
}
|
|
6257
6382
|
}
|
|
6258
6383
|
function detectAuthFiles(cwd, out) {
|
|
@@ -6269,13 +6394,13 @@ function detectAuthFiles(cwd, out) {
|
|
|
6269
6394
|
"src/app/api/oauth"
|
|
6270
6395
|
];
|
|
6271
6396
|
for (const c of candidates) {
|
|
6272
|
-
if (fs25.existsSync(
|
|
6397
|
+
if (fs25.existsSync(path24.join(cwd, c))) out.authFiles.push(c);
|
|
6273
6398
|
}
|
|
6274
6399
|
}
|
|
6275
6400
|
function detectRoles(cwd, out) {
|
|
6276
6401
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
6277
6402
|
for (const rp of rolePaths) {
|
|
6278
|
-
const dir =
|
|
6403
|
+
const dir = path24.join(cwd, rp);
|
|
6279
6404
|
if (!fs25.existsSync(dir)) continue;
|
|
6280
6405
|
let files;
|
|
6281
6406
|
try {
|
|
@@ -6285,7 +6410,7 @@ function detectRoles(cwd, out) {
|
|
|
6285
6410
|
}
|
|
6286
6411
|
for (const f of files) {
|
|
6287
6412
|
try {
|
|
6288
|
-
const content = fs25.readFileSync(
|
|
6413
|
+
const content = fs25.readFileSync(path24.join(dir, f), "utf-8").slice(0, 5e3);
|
|
6289
6414
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
6290
6415
|
if (roleMatches) {
|
|
6291
6416
|
for (const m of roleMatches) {
|
|
@@ -6369,7 +6494,7 @@ var discoverQaContext = async (ctx) => {
|
|
|
6369
6494
|
};
|
|
6370
6495
|
|
|
6371
6496
|
// src/scripts/dispatch.ts
|
|
6372
|
-
import { execFileSync as
|
|
6497
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
6373
6498
|
var API_TIMEOUT_MS4 = 3e4;
|
|
6374
6499
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
6375
6500
|
const next = args?.next;
|
|
@@ -6405,7 +6530,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
6405
6530
|
const sub = usePr ? "pr" : "issue";
|
|
6406
6531
|
const body = `@kody ${next}`;
|
|
6407
6532
|
try {
|
|
6408
|
-
|
|
6533
|
+
execFileSync11("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
6409
6534
|
timeout: API_TIMEOUT_MS4,
|
|
6410
6535
|
cwd: ctx.cwd,
|
|
6411
6536
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6425,7 +6550,7 @@ function parsePr(url) {
|
|
|
6425
6550
|
}
|
|
6426
6551
|
|
|
6427
6552
|
// src/scripts/dispatchClassified.ts
|
|
6428
|
-
import { execFileSync as
|
|
6553
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
6429
6554
|
var API_TIMEOUT_MS5 = 3e4;
|
|
6430
6555
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
6431
6556
|
var dispatchClassified = async (ctx) => {
|
|
@@ -6449,7 +6574,7 @@ ${auditLine}
|
|
|
6449
6574
|
|
|
6450
6575
|
${stateBody}`;
|
|
6451
6576
|
try {
|
|
6452
|
-
|
|
6577
|
+
execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
6453
6578
|
cwd: ctx.cwd,
|
|
6454
6579
|
timeout: API_TIMEOUT_MS5,
|
|
6455
6580
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6470,7 +6595,7 @@ function failedAction3(reason) {
|
|
|
6470
6595
|
|
|
6471
6596
|
// src/scripts/dispatchJobFileTicks.ts
|
|
6472
6597
|
import * as fs27 from "fs";
|
|
6473
|
-
import * as
|
|
6598
|
+
import * as path26 from "path";
|
|
6474
6599
|
|
|
6475
6600
|
// src/scripts/jobFrontmatter.ts
|
|
6476
6601
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -6563,44 +6688,6 @@ function stripQuotes(value) {
|
|
|
6563
6688
|
// src/scripts/jobState/contentsApiBackend.ts
|
|
6564
6689
|
init_issue();
|
|
6565
6690
|
|
|
6566
|
-
// src/stateBranch.ts
|
|
6567
|
-
init_issue();
|
|
6568
|
-
var STATE_BRANCH = "kody-state";
|
|
6569
|
-
function is404(err) {
|
|
6570
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6571
|
-
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
6572
|
-
}
|
|
6573
|
-
function ensureStateBranch(owner, repo, cwd) {
|
|
6574
|
-
try {
|
|
6575
|
-
gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
|
|
6576
|
-
return;
|
|
6577
|
-
} catch (err) {
|
|
6578
|
-
if (!is404(err)) throw err;
|
|
6579
|
-
}
|
|
6580
|
-
const repoInfo = JSON.parse(gh(["api", `/repos/${owner}/${repo}`], { cwd }));
|
|
6581
|
-
const defaultBranch2 = repoInfo.default_branch;
|
|
6582
|
-
if (!defaultBranch2) {
|
|
6583
|
-
throw new Error(`ensureStateBranch: could not resolve default branch for ${owner}/${repo}`);
|
|
6584
|
-
}
|
|
6585
|
-
const headRef = JSON.parse(
|
|
6586
|
-
gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${defaultBranch2}`], { cwd })
|
|
6587
|
-
);
|
|
6588
|
-
const sha = headRef.object?.sha;
|
|
6589
|
-
if (!sha) {
|
|
6590
|
-
throw new Error(`ensureStateBranch: could not resolve head sha for ${owner}/${repo}@${defaultBranch2}`);
|
|
6591
|
-
}
|
|
6592
|
-
try {
|
|
6593
|
-
gh(["api", "--method", "POST", `/repos/${owner}/${repo}/git/refs`, "--input", "-"], {
|
|
6594
|
-
cwd,
|
|
6595
|
-
input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
|
|
6596
|
-
});
|
|
6597
|
-
} catch (err) {
|
|
6598
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6599
|
-
if (/already exists/i.test(msg) || /HTTP 422/i.test(msg)) return;
|
|
6600
|
-
throw err;
|
|
6601
|
-
}
|
|
6602
|
-
}
|
|
6603
|
-
|
|
6604
6691
|
// src/scripts/issueStateComment.ts
|
|
6605
6692
|
init_issue();
|
|
6606
6693
|
function isStateEnvelope(x) {
|
|
@@ -6778,7 +6865,7 @@ var ContentsApiBackend = class {
|
|
|
6778
6865
|
|
|
6779
6866
|
// src/scripts/jobState/localFileBackend.ts
|
|
6780
6867
|
import * as fs26 from "fs";
|
|
6781
|
-
import * as
|
|
6868
|
+
import * as path25 from "path";
|
|
6782
6869
|
var LocalFileBackend = class {
|
|
6783
6870
|
name = "local-file";
|
|
6784
6871
|
cwd;
|
|
@@ -6793,7 +6880,7 @@ var LocalFileBackend = class {
|
|
|
6793
6880
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
6794
6881
|
this.cwd = opts.cwd;
|
|
6795
6882
|
this.jobsDir = opts.jobsDir;
|
|
6796
|
-
this.absDir =
|
|
6883
|
+
this.absDir = path25.join(opts.cwd, opts.jobsDir);
|
|
6797
6884
|
this.owner = opts.owner;
|
|
6798
6885
|
this.repo = opts.repo;
|
|
6799
6886
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -6853,7 +6940,7 @@ var LocalFileBackend = class {
|
|
|
6853
6940
|
}
|
|
6854
6941
|
load(slug) {
|
|
6855
6942
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
6856
|
-
const absPath =
|
|
6943
|
+
const absPath = path25.join(this.cwd, relPath);
|
|
6857
6944
|
if (!fs26.existsSync(absPath)) {
|
|
6858
6945
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
6859
6946
|
}
|
|
@@ -6874,8 +6961,8 @@ var LocalFileBackend = class {
|
|
|
6874
6961
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
6875
6962
|
return false;
|
|
6876
6963
|
}
|
|
6877
|
-
const absPath =
|
|
6878
|
-
fs26.mkdirSync(
|
|
6964
|
+
const absPath = path25.join(this.cwd, loaded.path);
|
|
6965
|
+
fs26.mkdirSync(path25.dirname(absPath), { recursive: true });
|
|
6879
6966
|
const body = JSON.stringify(next, null, 2) + "\n";
|
|
6880
6967
|
fs26.writeFileSync(absPath, body, "utf-8");
|
|
6881
6968
|
return true;
|
|
@@ -6955,7 +7042,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
6955
7042
|
await backend.hydrate();
|
|
6956
7043
|
}
|
|
6957
7044
|
try {
|
|
6958
|
-
const slugs = listJobSlugs(
|
|
7045
|
+
const slugs = listJobSlugs(path26.join(ctx.cwd, jobsDir));
|
|
6959
7046
|
ctx.data.jobSlugCount = slugs.length;
|
|
6960
7047
|
if (slugs.length === 0) {
|
|
6961
7048
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -7068,7 +7155,7 @@ function formatAgo(ms) {
|
|
|
7068
7155
|
}
|
|
7069
7156
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
7070
7157
|
try {
|
|
7071
|
-
const raw = fs27.readFileSync(
|
|
7158
|
+
const raw = fs27.readFileSync(path26.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
7072
7159
|
return splitFrontmatter2(raw).frontmatter;
|
|
7073
7160
|
} catch {
|
|
7074
7161
|
return {};
|
|
@@ -7605,7 +7692,7 @@ var finalizeTerminal = async (ctx) => {
|
|
|
7605
7692
|
|
|
7606
7693
|
// src/scripts/finishFlow.ts
|
|
7607
7694
|
init_issue();
|
|
7608
|
-
import { execFileSync as
|
|
7695
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
7609
7696
|
var TERMINAL_PHASE = {
|
|
7610
7697
|
"review-passed": { phase: "shipped", status: "succeeded" },
|
|
7611
7698
|
"fix-applied": { phase: "shipped", status: "succeeded" },
|
|
@@ -7645,7 +7732,7 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
|
|
|
7645
7732
|
**PR:** ${state.core.prUrl}` : "";
|
|
7646
7733
|
const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
7647
7734
|
try {
|
|
7648
|
-
|
|
7735
|
+
execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
7649
7736
|
timeout: API_TIMEOUT_MS6,
|
|
7650
7737
|
cwd: ctx.cwd,
|
|
7651
7738
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7675,9 +7762,9 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
|
|
|
7675
7762
|
};
|
|
7676
7763
|
|
|
7677
7764
|
// src/branch.ts
|
|
7678
|
-
import { execFileSync as
|
|
7765
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
7679
7766
|
function git2(args, cwd) {
|
|
7680
|
-
return
|
|
7767
|
+
return execFileSync14("git", args, {
|
|
7681
7768
|
encoding: "utf-8",
|
|
7682
7769
|
timeout: 3e4,
|
|
7683
7770
|
cwd,
|
|
@@ -7694,11 +7781,11 @@ function getCurrentBranch(cwd) {
|
|
|
7694
7781
|
}
|
|
7695
7782
|
function resetWorkingTree(cwd) {
|
|
7696
7783
|
try {
|
|
7697
|
-
|
|
7784
|
+
execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
7698
7785
|
} catch {
|
|
7699
7786
|
}
|
|
7700
7787
|
try {
|
|
7701
|
-
|
|
7788
|
+
execFileSync14("git", ["clean", "-fd"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
7702
7789
|
} catch {
|
|
7703
7790
|
}
|
|
7704
7791
|
}
|
|
@@ -7710,14 +7797,14 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
7710
7797
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
7711
7798
|
};
|
|
7712
7799
|
try {
|
|
7713
|
-
|
|
7800
|
+
execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
7714
7801
|
} catch {
|
|
7715
7802
|
}
|
|
7716
7803
|
try {
|
|
7717
|
-
|
|
7804
|
+
execFileSync14("git", ["clean", "-fd"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
7718
7805
|
} catch {
|
|
7719
7806
|
}
|
|
7720
|
-
|
|
7807
|
+
execFileSync14("gh", ["pr", "checkout", String(prNumber)], {
|
|
7721
7808
|
cwd,
|
|
7722
7809
|
env,
|
|
7723
7810
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -7843,7 +7930,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
|
|
|
7843
7930
|
}
|
|
7844
7931
|
|
|
7845
7932
|
// src/gha.ts
|
|
7846
|
-
import { execFileSync as
|
|
7933
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
7847
7934
|
import * as fs28 from "fs";
|
|
7848
7935
|
function getRunUrl() {
|
|
7849
7936
|
const server = process.env.GITHUB_SERVER_URL;
|
|
@@ -7886,7 +7973,7 @@ function reactToTriggerComment(cwd) {
|
|
|
7886
7973
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
7887
7974
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
7888
7975
|
try {
|
|
7889
|
-
|
|
7976
|
+
execFileSync15("gh", args, opts);
|
|
7890
7977
|
return;
|
|
7891
7978
|
} catch (err) {
|
|
7892
7979
|
lastErr = err;
|
|
@@ -7899,7 +7986,7 @@ function reactToTriggerComment(cwd) {
|
|
|
7899
7986
|
}
|
|
7900
7987
|
function sleepMs(ms) {
|
|
7901
7988
|
try {
|
|
7902
|
-
|
|
7989
|
+
execFileSync15("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
7903
7990
|
} catch {
|
|
7904
7991
|
}
|
|
7905
7992
|
}
|
|
@@ -7908,7 +7995,7 @@ function sleepMs(ms) {
|
|
|
7908
7995
|
init_issue();
|
|
7909
7996
|
|
|
7910
7997
|
// src/workflow.ts
|
|
7911
|
-
import { execFileSync as
|
|
7998
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
7912
7999
|
var GH_TIMEOUT_MS = 3e4;
|
|
7913
8000
|
function ghToken3() {
|
|
7914
8001
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -7916,7 +8003,7 @@ function ghToken3() {
|
|
|
7916
8003
|
function gh3(args, cwd) {
|
|
7917
8004
|
const token = ghToken3();
|
|
7918
8005
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
7919
|
-
return
|
|
8006
|
+
return execFileSync16("gh", args, {
|
|
7920
8007
|
encoding: "utf-8",
|
|
7921
8008
|
timeout: GH_TIMEOUT_MS,
|
|
7922
8009
|
cwd,
|
|
@@ -8151,13 +8238,13 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
8151
8238
|
};
|
|
8152
8239
|
|
|
8153
8240
|
// src/scripts/initFlow.ts
|
|
8154
|
-
import { execFileSync as
|
|
8241
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
8155
8242
|
import * as fs29 from "fs";
|
|
8156
|
-
import * as
|
|
8243
|
+
import * as path27 from "path";
|
|
8157
8244
|
function detectPackageManager(cwd) {
|
|
8158
|
-
if (fs29.existsSync(
|
|
8159
|
-
if (fs29.existsSync(
|
|
8160
|
-
if (fs29.existsSync(
|
|
8245
|
+
if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
8246
|
+
if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
|
|
8247
|
+
if (fs29.existsSync(path27.join(cwd, "bun.lockb"))) return "bun";
|
|
8161
8248
|
return "npm";
|
|
8162
8249
|
}
|
|
8163
8250
|
function qualityCommandsFor(pm) {
|
|
@@ -8177,7 +8264,7 @@ function schemaUrlFromPkg() {
|
|
|
8177
8264
|
function detectOwnerRepo(cwd) {
|
|
8178
8265
|
let url;
|
|
8179
8266
|
try {
|
|
8180
|
-
url =
|
|
8267
|
+
url = execFileSync17("git", ["remote", "get-url", "origin"], {
|
|
8181
8268
|
cwd,
|
|
8182
8269
|
encoding: "utf-8",
|
|
8183
8270
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8262,7 +8349,7 @@ jobs:
|
|
|
8262
8349
|
`;
|
|
8263
8350
|
function defaultBranchFromGit(cwd) {
|
|
8264
8351
|
try {
|
|
8265
|
-
const ref =
|
|
8352
|
+
const ref = execFileSync17("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
8266
8353
|
cwd,
|
|
8267
8354
|
encoding: "utf-8",
|
|
8268
8355
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8270,7 +8357,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
8270
8357
|
return ref.replace("refs/remotes/origin/", "");
|
|
8271
8358
|
} catch {
|
|
8272
8359
|
try {
|
|
8273
|
-
return
|
|
8360
|
+
return execFileSync17("git", ["branch", "--show-current"], {
|
|
8274
8361
|
cwd,
|
|
8275
8362
|
encoding: "utf-8",
|
|
8276
8363
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8286,7 +8373,7 @@ function performInit(cwd, force) {
|
|
|
8286
8373
|
const pm = detectPackageManager(cwd);
|
|
8287
8374
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
8288
8375
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
8289
|
-
const configPath =
|
|
8376
|
+
const configPath = path27.join(cwd, "kody.config.json");
|
|
8290
8377
|
if (fs29.existsSync(configPath) && !force) {
|
|
8291
8378
|
skipped.push("kody.config.json");
|
|
8292
8379
|
} else {
|
|
@@ -8295,8 +8382,8 @@ function performInit(cwd, force) {
|
|
|
8295
8382
|
`);
|
|
8296
8383
|
wrote.push("kody.config.json");
|
|
8297
8384
|
}
|
|
8298
|
-
const workflowDir =
|
|
8299
|
-
const workflowPath =
|
|
8385
|
+
const workflowDir = path27.join(cwd, ".github", "workflows");
|
|
8386
|
+
const workflowPath = path27.join(workflowDir, "kody.yml");
|
|
8300
8387
|
if (fs29.existsSync(workflowPath) && !force) {
|
|
8301
8388
|
skipped.push(".github/workflows/kody.yml");
|
|
8302
8389
|
} else {
|
|
@@ -8306,11 +8393,11 @@ function performInit(cwd, force) {
|
|
|
8306
8393
|
}
|
|
8307
8394
|
const builtinJobs = listBuiltinJobs();
|
|
8308
8395
|
if (builtinJobs.length > 0) {
|
|
8309
|
-
const jobsDir =
|
|
8396
|
+
const jobsDir = path27.join(cwd, ".kody", "duties");
|
|
8310
8397
|
fs29.mkdirSync(jobsDir, { recursive: true });
|
|
8311
8398
|
for (const job of builtinJobs) {
|
|
8312
|
-
const rel =
|
|
8313
|
-
const target =
|
|
8399
|
+
const rel = path27.join(".kody", "duties", `${job.slug}.md`);
|
|
8400
|
+
const target = path27.join(cwd, rel);
|
|
8314
8401
|
if (fs29.existsSync(target) && !force) {
|
|
8315
8402
|
skipped.push(rel);
|
|
8316
8403
|
continue;
|
|
@@ -8327,7 +8414,7 @@ function performInit(cwd, force) {
|
|
|
8327
8414
|
continue;
|
|
8328
8415
|
}
|
|
8329
8416
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
8330
|
-
const target =
|
|
8417
|
+
const target = path27.join(workflowDir, `kody-${exe.name}.yml`);
|
|
8331
8418
|
if (fs29.existsSync(target) && !force) {
|
|
8332
8419
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
8333
8420
|
continue;
|
|
@@ -8409,86 +8496,6 @@ Nothing to do. All files already present. (Use --force to overwrite.)
|
|
|
8409
8496
|
init_loadConventions();
|
|
8410
8497
|
init_loadCoverageRules();
|
|
8411
8498
|
|
|
8412
|
-
// src/goal/state.ts
|
|
8413
|
-
import * as fs30 from "fs";
|
|
8414
|
-
import * as path29 from "path";
|
|
8415
|
-
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
8416
|
-
var GoalStateError = class extends Error {
|
|
8417
|
-
constructor(path40, message) {
|
|
8418
|
-
super(`Invalid goal state at ${path40}:
|
|
8419
|
-
${message}`);
|
|
8420
|
-
this.path = path40;
|
|
8421
|
-
this.name = "GoalStateError";
|
|
8422
|
-
}
|
|
8423
|
-
path;
|
|
8424
|
-
};
|
|
8425
|
-
function parseGoalState(filePath, raw) {
|
|
8426
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
8427
|
-
throw new GoalStateError(filePath, "must be a JSON object");
|
|
8428
|
-
}
|
|
8429
|
-
const r = raw;
|
|
8430
|
-
const stateValue = r.state;
|
|
8431
|
-
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
8432
|
-
throw new GoalStateError(
|
|
8433
|
-
filePath,
|
|
8434
|
-
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
8435
|
-
);
|
|
8436
|
-
}
|
|
8437
|
-
const parsed = {
|
|
8438
|
-
state: stateValue,
|
|
8439
|
-
extra: {}
|
|
8440
|
-
};
|
|
8441
|
-
if (typeof r.mergeApproved === "boolean") {
|
|
8442
|
-
parsed.mergeApproved = r.mergeApproved;
|
|
8443
|
-
}
|
|
8444
|
-
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
8445
|
-
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
8446
|
-
}
|
|
8447
|
-
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
8448
|
-
const v = r[ts];
|
|
8449
|
-
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
8450
|
-
}
|
|
8451
|
-
const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
|
|
8452
|
-
for (const [k, v] of Object.entries(r)) {
|
|
8453
|
-
if (!known.has(k)) parsed.extra[k] = v;
|
|
8454
|
-
}
|
|
8455
|
-
return parsed;
|
|
8456
|
-
}
|
|
8457
|
-
function serializeGoalState(s) {
|
|
8458
|
-
const obj = { ...s.extra, state: s.state };
|
|
8459
|
-
if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
|
|
8460
|
-
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
8461
|
-
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
8462
|
-
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
8463
|
-
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
8464
|
-
return `${JSON.stringify(obj, null, 2)}
|
|
8465
|
-
`;
|
|
8466
|
-
}
|
|
8467
|
-
function goalStatePath(cwd, goalId) {
|
|
8468
|
-
return path29.join(cwd, ".kody", "goals", goalId, "state.json");
|
|
8469
|
-
}
|
|
8470
|
-
function readGoalState(cwd, goalId) {
|
|
8471
|
-
const file = goalStatePath(cwd, goalId);
|
|
8472
|
-
if (!fs30.existsSync(file)) {
|
|
8473
|
-
throw new GoalStateError(file, "file not found");
|
|
8474
|
-
}
|
|
8475
|
-
let raw;
|
|
8476
|
-
try {
|
|
8477
|
-
raw = JSON.parse(fs30.readFileSync(file, "utf-8"));
|
|
8478
|
-
} catch (err) {
|
|
8479
|
-
throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
8480
|
-
}
|
|
8481
|
-
return parseGoalState(file, raw);
|
|
8482
|
-
}
|
|
8483
|
-
function writeGoalState(cwd, goalId, state) {
|
|
8484
|
-
const file = goalStatePath(cwd, goalId);
|
|
8485
|
-
fs30.mkdirSync(path29.dirname(file), { recursive: true });
|
|
8486
|
-
fs30.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
8487
|
-
}
|
|
8488
|
-
function nowIso() {
|
|
8489
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8490
|
-
}
|
|
8491
|
-
|
|
8492
8499
|
// src/scripts/loadGoalState.ts
|
|
8493
8500
|
var loadGoalState = async (ctx) => {
|
|
8494
8501
|
const goalId = ctx.args.goal;
|
|
@@ -8504,8 +8511,24 @@ var loadGoalState = async (ctx) => {
|
|
|
8504
8511
|
ctx.output.reason = "invalid goal id (no slashes or '..' allowed)";
|
|
8505
8512
|
return;
|
|
8506
8513
|
}
|
|
8514
|
+
const owner = ctx.config.github?.owner;
|
|
8515
|
+
const repo = ctx.config.github?.repo;
|
|
8516
|
+
if (!owner || !repo) {
|
|
8517
|
+
ctx.skipAgent = true;
|
|
8518
|
+
ctx.output.exitCode = 1;
|
|
8519
|
+
ctx.output.reason = "missing github owner/repo in config";
|
|
8520
|
+
return;
|
|
8521
|
+
}
|
|
8507
8522
|
try {
|
|
8508
|
-
const state =
|
|
8523
|
+
const state = fetchGoalState(owner, repo, goalId, ctx.cwd);
|
|
8524
|
+
if (!state) {
|
|
8525
|
+
process.stdout.write(`[goal-tick] no goal state for ${goalId} on ${owner}/${repo} \u2014 nothing to tick
|
|
8526
|
+
`);
|
|
8527
|
+
ctx.skipAgent = true;
|
|
8528
|
+
ctx.output.exitCode = 0;
|
|
8529
|
+
ctx.output.reason = "no goal state to tick";
|
|
8530
|
+
return;
|
|
8531
|
+
}
|
|
8509
8532
|
ctx.data.goal = {
|
|
8510
8533
|
id: goalId,
|
|
8511
8534
|
state: state.state,
|
|
@@ -8580,8 +8603,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
8580
8603
|
};
|
|
8581
8604
|
|
|
8582
8605
|
// src/scripts/loadJobFromFile.ts
|
|
8583
|
-
import * as
|
|
8584
|
-
import * as
|
|
8606
|
+
import * as fs30 from "fs";
|
|
8607
|
+
import * as path28 from "path";
|
|
8585
8608
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
8586
8609
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
8587
8610
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
@@ -8590,11 +8613,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8590
8613
|
if (!slug) {
|
|
8591
8614
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
8592
8615
|
}
|
|
8593
|
-
const absPath =
|
|
8594
|
-
if (!
|
|
8616
|
+
const absPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
8617
|
+
if (!fs30.existsSync(absPath)) {
|
|
8595
8618
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
8596
8619
|
}
|
|
8597
|
-
const raw =
|
|
8620
|
+
const raw = fs30.readFileSync(absPath, "utf-8");
|
|
8598
8621
|
const { title, body } = parseJobFile(raw, slug);
|
|
8599
8622
|
const frontmatter = splitFrontmatter2(raw).frontmatter;
|
|
8600
8623
|
const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
@@ -8602,13 +8625,13 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8602
8625
|
let workerTitle = "";
|
|
8603
8626
|
let workerPersona = "";
|
|
8604
8627
|
if (workerSlug) {
|
|
8605
|
-
const workerPath =
|
|
8606
|
-
if (!
|
|
8628
|
+
const workerPath = path28.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8629
|
+
if (!fs30.existsSync(workerPath)) {
|
|
8607
8630
|
throw new Error(
|
|
8608
8631
|
`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`
|
|
8609
8632
|
);
|
|
8610
8633
|
}
|
|
8611
|
-
const workerRaw =
|
|
8634
|
+
const workerRaw = fs30.readFileSync(workerPath, "utf-8");
|
|
8612
8635
|
const parsed = parseJobFile(workerRaw, workerSlug);
|
|
8613
8636
|
workerTitle = parsed.title;
|
|
8614
8637
|
workerPersona = parsed.body;
|
|
@@ -8651,18 +8674,18 @@ init_loadMemoryContext();
|
|
|
8651
8674
|
init_loadPriorArt();
|
|
8652
8675
|
|
|
8653
8676
|
// src/scripts/loadQaContext.ts
|
|
8654
|
-
import * as
|
|
8655
|
-
import * as
|
|
8677
|
+
import * as fs33 from "fs";
|
|
8678
|
+
import * as path31 from "path";
|
|
8656
8679
|
|
|
8657
8680
|
// src/scripts/kodyVariables.ts
|
|
8658
|
-
import * as
|
|
8659
|
-
import * as
|
|
8681
|
+
import * as fs32 from "fs";
|
|
8682
|
+
import * as path30 from "path";
|
|
8660
8683
|
var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
|
|
8661
8684
|
function readKodyVariables(cwd) {
|
|
8662
|
-
const full =
|
|
8685
|
+
const full = path30.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
8663
8686
|
let raw;
|
|
8664
8687
|
try {
|
|
8665
|
-
raw =
|
|
8688
|
+
raw = fs32.readFileSync(full, "utf-8");
|
|
8666
8689
|
} catch {
|
|
8667
8690
|
return {};
|
|
8668
8691
|
}
|
|
@@ -8711,18 +8734,18 @@ function readProfileStaff(raw) {
|
|
|
8711
8734
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
8712
8735
|
}
|
|
8713
8736
|
function readProfile(cwd) {
|
|
8714
|
-
const dir =
|
|
8715
|
-
if (!
|
|
8737
|
+
const dir = path31.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
8738
|
+
if (!fs33.existsSync(dir)) return "";
|
|
8716
8739
|
let entries;
|
|
8717
8740
|
try {
|
|
8718
|
-
entries =
|
|
8741
|
+
entries = fs33.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
8719
8742
|
} catch {
|
|
8720
8743
|
return "";
|
|
8721
8744
|
}
|
|
8722
8745
|
const blocks = [];
|
|
8723
8746
|
for (const file of entries) {
|
|
8724
8747
|
try {
|
|
8725
|
-
const raw =
|
|
8748
|
+
const raw = fs33.readFileSync(path31.join(dir, file), "utf-8");
|
|
8726
8749
|
const { staff, body } = readProfileStaff(raw);
|
|
8727
8750
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
8728
8751
|
blocks.push(`## ${file}
|
|
@@ -8759,8 +8782,8 @@ var loadQaContext = async (ctx) => {
|
|
|
8759
8782
|
init_events();
|
|
8760
8783
|
|
|
8761
8784
|
// src/taskContext.ts
|
|
8762
|
-
import * as
|
|
8763
|
-
import * as
|
|
8785
|
+
import * as fs34 from "fs";
|
|
8786
|
+
import * as path32 from "path";
|
|
8764
8787
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
8765
8788
|
function buildTaskContext(args) {
|
|
8766
8789
|
return {
|
|
@@ -8776,10 +8799,10 @@ function buildTaskContext(args) {
|
|
|
8776
8799
|
}
|
|
8777
8800
|
function persistTaskContext(cwd, ctx) {
|
|
8778
8801
|
try {
|
|
8779
|
-
const dir =
|
|
8780
|
-
|
|
8781
|
-
const file =
|
|
8782
|
-
|
|
8802
|
+
const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
|
|
8803
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
8804
|
+
const file = path32.join(dir, "task-context.json");
|
|
8805
|
+
fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
8783
8806
|
`);
|
|
8784
8807
|
return file;
|
|
8785
8808
|
} catch (err) {
|
|
@@ -8827,19 +8850,19 @@ var loadTaskState = async (ctx) => {
|
|
|
8827
8850
|
};
|
|
8828
8851
|
|
|
8829
8852
|
// src/scripts/loadWorkerAdhoc.ts
|
|
8830
|
-
import * as
|
|
8831
|
-
import * as
|
|
8853
|
+
import * as fs35 from "fs";
|
|
8854
|
+
import * as path33 from "path";
|
|
8832
8855
|
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
8833
8856
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
8834
8857
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
8835
8858
|
if (!workerSlug) {
|
|
8836
8859
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
8837
8860
|
}
|
|
8838
|
-
const workerPath =
|
|
8839
|
-
if (!
|
|
8861
|
+
const workerPath = path33.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8862
|
+
if (!fs35.existsSync(workerPath)) {
|
|
8840
8863
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
8841
8864
|
}
|
|
8842
|
-
const { title, body } = parsePersona(
|
|
8865
|
+
const { title, body } = parsePersona(fs35.readFileSync(workerPath, "utf-8"), workerSlug);
|
|
8843
8866
|
const message = resolveMessage(ctx.args.message);
|
|
8844
8867
|
if (!message) {
|
|
8845
8868
|
throw new Error(
|
|
@@ -8859,9 +8882,9 @@ function resolveMessage(messageArg) {
|
|
|
8859
8882
|
}
|
|
8860
8883
|
function readCommentBody() {
|
|
8861
8884
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
8862
|
-
if (!eventPath || !
|
|
8885
|
+
if (!eventPath || !fs35.existsSync(eventPath)) return "";
|
|
8863
8886
|
try {
|
|
8864
|
-
const event = JSON.parse(
|
|
8887
|
+
const event = JSON.parse(fs35.readFileSync(eventPath, "utf-8"));
|
|
8865
8888
|
return String(event.comment?.body ?? "");
|
|
8866
8889
|
} catch {
|
|
8867
8890
|
return "";
|
|
@@ -9024,7 +9047,7 @@ var mergeFlow = async (ctx) => {
|
|
|
9024
9047
|
};
|
|
9025
9048
|
|
|
9026
9049
|
// src/scripts/mergeReleasePr.ts
|
|
9027
|
-
import { execFileSync as
|
|
9050
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
9028
9051
|
var API_TIMEOUT_MS7 = 6e4;
|
|
9029
9052
|
var mergeReleasePr = async (ctx) => {
|
|
9030
9053
|
const state = ctx.data.taskState;
|
|
@@ -9043,7 +9066,7 @@ var mergeReleasePr = async (ctx) => {
|
|
|
9043
9066
|
process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
|
|
9044
9067
|
`);
|
|
9045
9068
|
try {
|
|
9046
|
-
const out =
|
|
9069
|
+
const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
9047
9070
|
timeout: API_TIMEOUT_MS7,
|
|
9048
9071
|
cwd: ctx.cwd,
|
|
9049
9072
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9382,6 +9405,17 @@ var parseJobStateFromAgentResult = async (ctx, _profile, agentResult, args) => {
|
|
|
9382
9405
|
}
|
|
9383
9406
|
const result = extractNextStateFromText(agentResult.finalText, fenceLabel, prevRev);
|
|
9384
9407
|
if (result.error) {
|
|
9408
|
+
const cleanFinishNoBlock = result.error.startsWith("missing `") && agentResult.outcome === "completed" && loaded != null;
|
|
9409
|
+
if (cleanFinishNoBlock) {
|
|
9410
|
+
ctx.data.nextJobState = {
|
|
9411
|
+
version: 1,
|
|
9412
|
+
rev: prevRev + 1,
|
|
9413
|
+
cursor: loaded.state.cursor,
|
|
9414
|
+
data: loaded.state.data,
|
|
9415
|
+
done: loaded.state.done
|
|
9416
|
+
};
|
|
9417
|
+
return;
|
|
9418
|
+
}
|
|
9385
9419
|
ctx.data.nextStateParseError = result.error.startsWith("missing `") ? `agent did not emit a \`${fenceLabel}\` fenced block` : result.error;
|
|
9386
9420
|
return;
|
|
9387
9421
|
}
|
|
@@ -9502,7 +9536,7 @@ var persistFlowState = async (ctx) => {
|
|
|
9502
9536
|
};
|
|
9503
9537
|
|
|
9504
9538
|
// src/scripts/poolServe.ts
|
|
9505
|
-
import { spawn as
|
|
9539
|
+
import { spawn as spawn4 } from "child_process";
|
|
9506
9540
|
import { createServer as createServer2 } from "http";
|
|
9507
9541
|
|
|
9508
9542
|
// src/pool/fly.ts
|
|
@@ -9520,8 +9554,8 @@ var FlyClient = class {
|
|
|
9520
9554
|
get fetch() {
|
|
9521
9555
|
return this.opts.fetchImpl ?? fetch;
|
|
9522
9556
|
}
|
|
9523
|
-
async call(
|
|
9524
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
9557
|
+
async call(path38, init = {}) {
|
|
9558
|
+
const res = await this.fetch(`${FLY_API_BASE}${path38}`, {
|
|
9525
9559
|
method: init.method ?? "GET",
|
|
9526
9560
|
headers: {
|
|
9527
9561
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -9532,7 +9566,7 @@ var FlyClient = class {
|
|
|
9532
9566
|
if (res.status === 404 && init.allow404) return null;
|
|
9533
9567
|
if (!res.ok) {
|
|
9534
9568
|
const text = await res.text().catch(() => "");
|
|
9535
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
9569
|
+
throw new Error(`Fly API ${res.status} on ${path38}: ${text.slice(0, 200) || res.statusText}`);
|
|
9536
9570
|
}
|
|
9537
9571
|
if (res.status === 204) return null;
|
|
9538
9572
|
const raw = await res.text();
|
|
@@ -10072,14 +10106,14 @@ function sendJson2(res, status, body) {
|
|
|
10072
10106
|
res.end(JSON.stringify(body));
|
|
10073
10107
|
}
|
|
10074
10108
|
function readJsonBody2(req) {
|
|
10075
|
-
return new Promise((
|
|
10109
|
+
return new Promise((resolve5, reject) => {
|
|
10076
10110
|
const chunks = [];
|
|
10077
10111
|
req.on("data", (c) => chunks.push(c));
|
|
10078
10112
|
req.on("end", () => {
|
|
10079
10113
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
10080
|
-
if (!raw.trim()) return
|
|
10114
|
+
if (!raw.trim()) return resolve5({});
|
|
10081
10115
|
try {
|
|
10082
|
-
|
|
10116
|
+
resolve5(JSON.parse(raw));
|
|
10083
10117
|
} catch (err) {
|
|
10084
10118
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
10085
10119
|
}
|
|
@@ -10121,7 +10155,7 @@ function superviseLitellm() {
|
|
|
10121
10155
|
let restarts = 0;
|
|
10122
10156
|
const start = () => {
|
|
10123
10157
|
log(`starting litellm child (port ${port}, host ${host})`);
|
|
10124
|
-
const child =
|
|
10158
|
+
const child = spawn4("litellm", ["--config", config, "--port", port, "--host", host], {
|
|
10125
10159
|
stdio: "inherit"
|
|
10126
10160
|
});
|
|
10127
10161
|
child.on("exit", (code) => {
|
|
@@ -10226,10 +10260,10 @@ var poolServe = async (ctx) => {
|
|
|
10226
10260
|
}
|
|
10227
10261
|
});
|
|
10228
10262
|
const apiHost = process.env.POOL_API_HOST ?? "::";
|
|
10229
|
-
await new Promise((
|
|
10263
|
+
await new Promise((resolve5) => {
|
|
10230
10264
|
server.listen(apiPort, apiHost, () => {
|
|
10231
10265
|
log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
|
|
10232
|
-
|
|
10266
|
+
resolve5();
|
|
10233
10267
|
});
|
|
10234
10268
|
});
|
|
10235
10269
|
const shutdown = (signal) => {
|
|
@@ -10431,7 +10465,7 @@ ${body}`;
|
|
|
10431
10465
|
}
|
|
10432
10466
|
|
|
10433
10467
|
// src/scripts/recordClassification.ts
|
|
10434
|
-
import { execFileSync as
|
|
10468
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
10435
10469
|
var API_TIMEOUT_MS8 = 3e4;
|
|
10436
10470
|
var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
10437
10471
|
var recordClassification = async (ctx) => {
|
|
@@ -10479,7 +10513,7 @@ function parseClassification(prSummary) {
|
|
|
10479
10513
|
}
|
|
10480
10514
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
10481
10515
|
try {
|
|
10482
|
-
|
|
10516
|
+
execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
10483
10517
|
cwd,
|
|
10484
10518
|
timeout: API_TIMEOUT_MS8,
|
|
10485
10519
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -10593,7 +10627,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
10593
10627
|
};
|
|
10594
10628
|
|
|
10595
10629
|
// src/scripts/resolveFlow.ts
|
|
10596
|
-
import { execFileSync as
|
|
10630
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
10597
10631
|
init_issue();
|
|
10598
10632
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
10599
10633
|
var resolveFlow = async (ctx) => {
|
|
@@ -10687,7 +10721,7 @@ function buildPreferBlock(prefer, baseBranch) {
|
|
|
10687
10721
|
}
|
|
10688
10722
|
function getConflictedFiles(cwd) {
|
|
10689
10723
|
try {
|
|
10690
|
-
const out =
|
|
10724
|
+
const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
10691
10725
|
encoding: "utf-8",
|
|
10692
10726
|
cwd,
|
|
10693
10727
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -10702,7 +10736,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
10702
10736
|
let total = 0;
|
|
10703
10737
|
for (const f of files) {
|
|
10704
10738
|
try {
|
|
10705
|
-
const content =
|
|
10739
|
+
const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
10706
10740
|
const snippet = `### ${f}
|
|
10707
10741
|
|
|
10708
10742
|
\`\`\`
|
|
@@ -10726,12 +10760,12 @@ function tryPostPr3(prNumber, body, cwd) {
|
|
|
10726
10760
|
function pushEmptyCommit(branch, cwd) {
|
|
10727
10761
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
10728
10762
|
try {
|
|
10729
|
-
|
|
10763
|
+
execFileSync20(
|
|
10730
10764
|
"git",
|
|
10731
10765
|
["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
|
|
10732
10766
|
{ cwd, env, stdio: ["ignore", "pipe", "pipe"] }
|
|
10733
10767
|
);
|
|
10734
|
-
|
|
10768
|
+
execFileSync20("git", ["push", "-u", "origin", branch], {
|
|
10735
10769
|
cwd,
|
|
10736
10770
|
env,
|
|
10737
10771
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -10742,6 +10776,46 @@ function pushEmptyCommit(branch, cwd) {
|
|
|
10742
10776
|
}
|
|
10743
10777
|
}
|
|
10744
10778
|
|
|
10779
|
+
// src/scripts/promoteQaGoal.ts
|
|
10780
|
+
init_issue();
|
|
10781
|
+
var REPORT_JSON_OPEN2 = "<!-- KODY_QA_REPORT_JSON";
|
|
10782
|
+
var promoteQaGoal = async (ctx) => {
|
|
10783
|
+
ctx.skipAgent = true;
|
|
10784
|
+
const issueNum = ctx.args.issue;
|
|
10785
|
+
if (typeof issueNum !== "number" || issueNum <= 0) {
|
|
10786
|
+
ctx.output.exitCode = 2;
|
|
10787
|
+
ctx.output.reason = "qa-goal requires --issue <n>";
|
|
10788
|
+
process.stderr.write("[qa-goal] missing --issue\n");
|
|
10789
|
+
return;
|
|
10790
|
+
}
|
|
10791
|
+
let report;
|
|
10792
|
+
try {
|
|
10793
|
+
const issue = getIssue(issueNum, ctx.cwd);
|
|
10794
|
+
const reportComment = [...issue.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
|
|
10795
|
+
if (!reportComment) {
|
|
10796
|
+
ctx.output.exitCode = 3;
|
|
10797
|
+
ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
|
|
10798
|
+
process.stderr.write(`[qa-goal] ${ctx.output.reason}
|
|
10799
|
+
`);
|
|
10800
|
+
return;
|
|
10801
|
+
}
|
|
10802
|
+
report = reportComment.body;
|
|
10803
|
+
} catch (err) {
|
|
10804
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10805
|
+
ctx.output.exitCode = 3;
|
|
10806
|
+
ctx.output.reason = `failed to read issue #${issueNum}: ${msg}`;
|
|
10807
|
+
process.stderr.write(`[qa-goal] ${ctx.output.reason}
|
|
10808
|
+
`);
|
|
10809
|
+
return;
|
|
10810
|
+
}
|
|
10811
|
+
await promoteReportToGoal(
|
|
10812
|
+
ctx,
|
|
10813
|
+
report,
|
|
10814
|
+
ctx.args.scope,
|
|
10815
|
+
ctx.args.goal
|
|
10816
|
+
);
|
|
10817
|
+
};
|
|
10818
|
+
|
|
10745
10819
|
// src/deployments.ts
|
|
10746
10820
|
init_issue();
|
|
10747
10821
|
function findPreviewDeploymentUrl(prNumber, cwd) {
|
|
@@ -10822,10 +10896,10 @@ var resolvePreviewUrl = async (ctx) => {
|
|
|
10822
10896
|
};
|
|
10823
10897
|
|
|
10824
10898
|
// src/scripts/resolveQaUrl.ts
|
|
10825
|
-
import { execFileSync as
|
|
10899
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
10826
10900
|
function ghQuery(args, cwd) {
|
|
10827
10901
|
try {
|
|
10828
|
-
const out =
|
|
10902
|
+
const out = execFileSync21("gh", args, {
|
|
10829
10903
|
cwd,
|
|
10830
10904
|
stdio: ["ignore", "pipe", "pipe"],
|
|
10831
10905
|
encoding: "utf-8",
|
|
@@ -10895,7 +10969,7 @@ var resolveQaUrl = async (ctx) => {
|
|
|
10895
10969
|
};
|
|
10896
10970
|
|
|
10897
10971
|
// src/scripts/revertFlow.ts
|
|
10898
|
-
import { execFileSync as
|
|
10972
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
10899
10973
|
init_issue();
|
|
10900
10974
|
var SHA_RE = /^[0-9a-f]{4,40}$/i;
|
|
10901
10975
|
var revertFlow = async (ctx) => {
|
|
@@ -10978,7 +11052,7 @@ function buildPrSummary(resolved) {
|
|
|
10978
11052
|
return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
|
|
10979
11053
|
}
|
|
10980
11054
|
function git3(args, cwd) {
|
|
10981
|
-
return
|
|
11055
|
+
return execFileSync22("git", args, {
|
|
10982
11056
|
encoding: "utf-8",
|
|
10983
11057
|
timeout: 3e4,
|
|
10984
11058
|
cwd,
|
|
@@ -10988,7 +11062,7 @@ function git3(args, cwd) {
|
|
|
10988
11062
|
}
|
|
10989
11063
|
function isAncestorOfHead(sha, cwd) {
|
|
10990
11064
|
try {
|
|
10991
|
-
|
|
11065
|
+
execFileSync22("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
|
|
10992
11066
|
cwd,
|
|
10993
11067
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
10994
11068
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -11083,9 +11157,9 @@ function resolveBaseOverride(value) {
|
|
|
11083
11157
|
}
|
|
11084
11158
|
|
|
11085
11159
|
// src/scripts/runnerServe.ts
|
|
11086
|
-
import { spawn as
|
|
11160
|
+
import { spawn as spawn5 } from "child_process";
|
|
11087
11161
|
import { createServer as createServer3 } from "http";
|
|
11088
|
-
import * as
|
|
11162
|
+
import * as fs36 from "fs";
|
|
11089
11163
|
var DEFAULT_PORT2 = 8080;
|
|
11090
11164
|
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
11091
11165
|
function getApiKey2() {
|
|
@@ -11107,17 +11181,17 @@ function authOk2(req, expected) {
|
|
|
11107
11181
|
return false;
|
|
11108
11182
|
}
|
|
11109
11183
|
function readJsonBody3(req) {
|
|
11110
|
-
return new Promise((
|
|
11184
|
+
return new Promise((resolve5, reject) => {
|
|
11111
11185
|
const chunks = [];
|
|
11112
11186
|
req.on("data", (c) => chunks.push(c));
|
|
11113
11187
|
req.on("end", () => {
|
|
11114
11188
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
11115
11189
|
if (!raw.trim()) {
|
|
11116
|
-
|
|
11190
|
+
resolve5({});
|
|
11117
11191
|
return;
|
|
11118
11192
|
}
|
|
11119
11193
|
try {
|
|
11120
|
-
|
|
11194
|
+
resolve5(JSON.parse(raw));
|
|
11121
11195
|
} catch (err) {
|
|
11122
11196
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
11123
11197
|
}
|
|
@@ -11166,8 +11240,8 @@ async function defaultRunJob(job) {
|
|
|
11166
11240
|
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
11167
11241
|
const branch = job.ref ?? "main";
|
|
11168
11242
|
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
11169
|
-
|
|
11170
|
-
|
|
11243
|
+
fs36.rmSync(workdir, { recursive: true, force: true });
|
|
11244
|
+
fs36.mkdirSync(workdir, { recursive: true });
|
|
11171
11245
|
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
11172
11246
|
const interactive = job.mode === "interactive";
|
|
11173
11247
|
const childEnv = {
|
|
@@ -11192,13 +11266,13 @@ async function defaultRunJob(job) {
|
|
|
11192
11266
|
...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
|
|
11193
11267
|
...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
|
|
11194
11268
|
};
|
|
11195
|
-
const run = (cmd, args, cwd) => new Promise((
|
|
11196
|
-
const child =
|
|
11197
|
-
child.on("exit", (code) =>
|
|
11269
|
+
const run = (cmd, args, cwd) => new Promise((resolve5) => {
|
|
11270
|
+
const child = spawn5(cmd, args, { stdio: "inherit", env: childEnv, cwd });
|
|
11271
|
+
child.on("exit", (code) => resolve5(code ?? 0));
|
|
11198
11272
|
child.on("error", (err) => {
|
|
11199
11273
|
process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
|
|
11200
11274
|
`);
|
|
11201
|
-
|
|
11275
|
+
resolve5(1);
|
|
11202
11276
|
});
|
|
11203
11277
|
});
|
|
11204
11278
|
process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
|
|
@@ -11285,11 +11359,11 @@ var runnerServe = async (ctx) => {
|
|
|
11285
11359
|
const port = Number(process.env.PORT ?? DEFAULT_PORT2);
|
|
11286
11360
|
const server = buildServer2({ apiKey });
|
|
11287
11361
|
const host = process.env.RUNNER_HOST ?? "::";
|
|
11288
|
-
await new Promise((
|
|
11362
|
+
await new Promise((resolve5) => {
|
|
11289
11363
|
server.listen(port, host, () => {
|
|
11290
11364
|
process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
|
|
11291
11365
|
`);
|
|
11292
|
-
|
|
11366
|
+
resolve5();
|
|
11293
11367
|
});
|
|
11294
11368
|
});
|
|
11295
11369
|
const shutdown = (signal) => {
|
|
@@ -11304,9 +11378,9 @@ var runnerServe = async (ctx) => {
|
|
|
11304
11378
|
};
|
|
11305
11379
|
|
|
11306
11380
|
// src/scripts/runTickScript.ts
|
|
11307
|
-
import { spawnSync } from "child_process";
|
|
11308
|
-
import * as
|
|
11309
|
-
import * as
|
|
11381
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
11382
|
+
import * as fs37 from "fs";
|
|
11383
|
+
import * as path34 from "path";
|
|
11310
11384
|
var runTickScript = async (ctx, _profile, args) => {
|
|
11311
11385
|
ctx.skipAgent = true;
|
|
11312
11386
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -11318,13 +11392,13 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
11318
11392
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
11319
11393
|
return;
|
|
11320
11394
|
}
|
|
11321
|
-
const jobPath =
|
|
11322
|
-
if (!
|
|
11395
|
+
const jobPath = path34.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
11396
|
+
if (!fs37.existsSync(jobPath)) {
|
|
11323
11397
|
ctx.output.exitCode = 99;
|
|
11324
11398
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
11325
11399
|
return;
|
|
11326
11400
|
}
|
|
11327
|
-
const raw =
|
|
11401
|
+
const raw = fs37.readFileSync(jobPath, "utf-8");
|
|
11328
11402
|
const { frontmatter } = splitFrontmatter2(raw);
|
|
11329
11403
|
const tickScript = frontmatter.tickScript;
|
|
11330
11404
|
if (!tickScript) {
|
|
@@ -11332,8 +11406,8 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
11332
11406
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
11333
11407
|
return;
|
|
11334
11408
|
}
|
|
11335
|
-
const scriptPath =
|
|
11336
|
-
if (!
|
|
11409
|
+
const scriptPath = path34.isAbsolute(tickScript) ? tickScript : path34.join(ctx.cwd, tickScript);
|
|
11410
|
+
if (!fs37.existsSync(scriptPath)) {
|
|
11337
11411
|
ctx.output.exitCode = 99;
|
|
11338
11412
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
11339
11413
|
return;
|
|
@@ -11350,7 +11424,7 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
11350
11424
|
ctx.data.jobSlug = slug;
|
|
11351
11425
|
ctx.data.jobState = loaded;
|
|
11352
11426
|
const childEnv = buildChildEnv(process.env, Boolean(ctx.args.force));
|
|
11353
|
-
const result =
|
|
11427
|
+
const result = spawnSync2("bash", [scriptPath], {
|
|
11354
11428
|
cwd: ctx.cwd,
|
|
11355
11429
|
env: childEnv,
|
|
11356
11430
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -11449,7 +11523,8 @@ var saveGoalState = async (ctx) => {
|
|
|
11449
11523
|
lastDispatchedIssue: goal.lastDispatchedIssue,
|
|
11450
11524
|
updatedAt: changed ? nowIso() : prev?.updatedAt
|
|
11451
11525
|
};
|
|
11452
|
-
|
|
11526
|
+
ctx.data.goalPersistState = updated;
|
|
11527
|
+
ctx.data.goalPersistChanged = changed;
|
|
11453
11528
|
ctx.skipAgent = true;
|
|
11454
11529
|
};
|
|
11455
11530
|
|
|
@@ -11483,7 +11558,7 @@ function synthesizeAction(ctx) {
|
|
|
11483
11558
|
}
|
|
11484
11559
|
|
|
11485
11560
|
// src/scripts/serveFlow.ts
|
|
11486
|
-
import { spawn as
|
|
11561
|
+
import { spawn as spawn6 } from "child_process";
|
|
11487
11562
|
function parseTarget(positional) {
|
|
11488
11563
|
if (!Array.isArray(positional) || positional.length === 0) return "none";
|
|
11489
11564
|
const first = String(positional[0]).toLowerCase();
|
|
@@ -11532,15 +11607,15 @@ var serveFlow = async (ctx) => {
|
|
|
11532
11607
|
if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
|
|
11533
11608
|
`);
|
|
11534
11609
|
const args = ["--dangerously-skip-permissions", "--model", model.model];
|
|
11535
|
-
const child =
|
|
11536
|
-
const exitCode = await new Promise((
|
|
11537
|
-
child.on("exit", (code) =>
|
|
11610
|
+
const child = spawn6("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
|
|
11611
|
+
const exitCode = await new Promise((resolve5) => {
|
|
11612
|
+
child.on("exit", (code) => resolve5(code ?? 0));
|
|
11538
11613
|
child.on("error", (err) => {
|
|
11539
11614
|
process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
|
|
11540
11615
|
`);
|
|
11541
11616
|
process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
|
|
11542
11617
|
`);
|
|
11543
|
-
|
|
11618
|
+
resolve5(1);
|
|
11544
11619
|
});
|
|
11545
11620
|
});
|
|
11546
11621
|
killProxy();
|
|
@@ -11553,7 +11628,7 @@ var serveFlow = async (ctx) => {
|
|
|
11553
11628
|
if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
|
|
11554
11629
|
`);
|
|
11555
11630
|
try {
|
|
11556
|
-
const code =
|
|
11631
|
+
const code = spawn6("code", [ctx.cwd], { stdio: "inherit", env: editorEnv, detached: true });
|
|
11557
11632
|
code.on("error", (err) => {
|
|
11558
11633
|
process.stderr.write(`[kody serve] failed to launch VS Code: ${err.message}
|
|
11559
11634
|
`);
|
|
@@ -11630,11 +11705,11 @@ var skipAgent = async (ctx) => {
|
|
|
11630
11705
|
};
|
|
11631
11706
|
|
|
11632
11707
|
// src/scripts/stageMergeConflicts.ts
|
|
11633
|
-
import { execFileSync as
|
|
11708
|
+
import { execFileSync as execFileSync23 } from "child_process";
|
|
11634
11709
|
var stageMergeConflicts = async (ctx) => {
|
|
11635
11710
|
if (ctx.data.agentDone === false) return;
|
|
11636
11711
|
try {
|
|
11637
|
-
|
|
11712
|
+
execFileSync23("git", ["add", "-A"], {
|
|
11638
11713
|
cwd: ctx.cwd,
|
|
11639
11714
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
11640
11715
|
stdio: "pipe"
|
|
@@ -11645,7 +11720,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
11645
11720
|
|
|
11646
11721
|
// src/scripts/startFlow.ts
|
|
11647
11722
|
init_issue();
|
|
11648
|
-
import { execFileSync as
|
|
11723
|
+
import { execFileSync as execFileSync24 } from "child_process";
|
|
11649
11724
|
var API_TIMEOUT_MS9 = 3e4;
|
|
11650
11725
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
11651
11726
|
const entry = args?.entry;
|
|
@@ -11679,7 +11754,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
11679
11754
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
11680
11755
|
const body = `@kody ${next}`;
|
|
11681
11756
|
try {
|
|
11682
|
-
|
|
11757
|
+
execFileSync24("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
11683
11758
|
timeout: API_TIMEOUT_MS9,
|
|
11684
11759
|
cwd,
|
|
11685
11760
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -11693,7 +11768,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
11693
11768
|
}
|
|
11694
11769
|
|
|
11695
11770
|
// src/scripts/syncFlow.ts
|
|
11696
|
-
import { execFileSync as
|
|
11771
|
+
import { execFileSync as execFileSync25 } from "child_process";
|
|
11697
11772
|
init_issue();
|
|
11698
11773
|
var syncFlow = async (ctx, _profile, args) => {
|
|
11699
11774
|
const announceOnSuccess = Boolean(args?.announceOnSuccess);
|
|
@@ -11758,7 +11833,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
11758
11833
|
}
|
|
11759
11834
|
function revParseHead(cwd) {
|
|
11760
11835
|
try {
|
|
11761
|
-
return
|
|
11836
|
+
return execFileSync25("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
11762
11837
|
} catch {
|
|
11763
11838
|
return "";
|
|
11764
11839
|
}
|
|
@@ -11808,7 +11883,7 @@ var verify = async (ctx) => {
|
|
|
11808
11883
|
};
|
|
11809
11884
|
|
|
11810
11885
|
// src/scripts/verifyReproFails.ts
|
|
11811
|
-
import { spawn as
|
|
11886
|
+
import { spawn as spawn7 } from "child_process";
|
|
11812
11887
|
var TEST_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
11813
11888
|
var TAIL_CHARS2 = 8e3;
|
|
11814
11889
|
var ANSI_RE2 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
@@ -11876,8 +11951,8 @@ function stripAnsi2(s) {
|
|
|
11876
11951
|
return s.replace(ANSI_RE2, "");
|
|
11877
11952
|
}
|
|
11878
11953
|
function runCommand2(command, cwd) {
|
|
11879
|
-
return new Promise((
|
|
11880
|
-
const child =
|
|
11954
|
+
return new Promise((resolve5) => {
|
|
11955
|
+
const child = spawn7(command, {
|
|
11881
11956
|
cwd,
|
|
11882
11957
|
shell: true,
|
|
11883
11958
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
|
|
@@ -11903,11 +11978,11 @@ function runCommand2(command, cwd) {
|
|
|
11903
11978
|
}, TEST_TIMEOUT_MS);
|
|
11904
11979
|
child.on("exit", (code) => {
|
|
11905
11980
|
clearTimeout(timer);
|
|
11906
|
-
|
|
11981
|
+
resolve5({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
|
|
11907
11982
|
});
|
|
11908
11983
|
child.on("error", (err) => {
|
|
11909
11984
|
clearTimeout(timer);
|
|
11910
|
-
|
|
11985
|
+
resolve5({ exitCode: -1, output: err.message });
|
|
11911
11986
|
});
|
|
11912
11987
|
});
|
|
11913
11988
|
}
|
|
@@ -12015,7 +12090,7 @@ var verifyWithRetry = async (ctx) => {
|
|
|
12015
12090
|
|
|
12016
12091
|
// src/scripts/waitForCi.ts
|
|
12017
12092
|
init_issue();
|
|
12018
|
-
import { execFileSync as
|
|
12093
|
+
import { execFileSync as execFileSync26 } from "child_process";
|
|
12019
12094
|
var API_TIMEOUT_MS10 = 3e4;
|
|
12020
12095
|
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
12021
12096
|
const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
|
|
@@ -12093,7 +12168,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
12093
12168
|
};
|
|
12094
12169
|
function fetchChecks(prNumber, cwd) {
|
|
12095
12170
|
try {
|
|
12096
|
-
const raw =
|
|
12171
|
+
const raw = execFileSync26("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
|
|
12097
12172
|
encoding: "utf-8",
|
|
12098
12173
|
timeout: API_TIMEOUT_MS10,
|
|
12099
12174
|
cwd,
|
|
@@ -12206,7 +12281,7 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
|
|
|
12206
12281
|
};
|
|
12207
12282
|
|
|
12208
12283
|
// src/scripts/warmupMcp.ts
|
|
12209
|
-
import { spawn as
|
|
12284
|
+
import { spawn as spawn8 } from "child_process";
|
|
12210
12285
|
var PER_SERVER_TIMEOUT_MS = 6e4;
|
|
12211
12286
|
var PER_REQUEST_TIMEOUT_MS = 2e4;
|
|
12212
12287
|
var warmupMcp = async (_ctx, profile) => {
|
|
@@ -12228,7 +12303,7 @@ var warmupMcp = async (_ctx, profile) => {
|
|
|
12228
12303
|
}
|
|
12229
12304
|
};
|
|
12230
12305
|
async function warmupOne(command, args, env) {
|
|
12231
|
-
const child =
|
|
12306
|
+
const child = spawn8(command, args, {
|
|
12232
12307
|
stdio: ["pipe", "pipe", "pipe"],
|
|
12233
12308
|
env: env ? { ...process.env, ...env } : process.env
|
|
12234
12309
|
});
|
|
@@ -12329,20 +12404,20 @@ function lineStream(stream) {
|
|
|
12329
12404
|
tryDeliver();
|
|
12330
12405
|
});
|
|
12331
12406
|
return {
|
|
12332
|
-
next: (timeoutMs) => new Promise((
|
|
12407
|
+
next: (timeoutMs) => new Promise((resolve5) => {
|
|
12333
12408
|
if (queue.length > 0) {
|
|
12334
|
-
|
|
12409
|
+
resolve5(queue.shift());
|
|
12335
12410
|
return;
|
|
12336
12411
|
}
|
|
12337
12412
|
if (ended) {
|
|
12338
|
-
|
|
12413
|
+
resolve5(null);
|
|
12339
12414
|
return;
|
|
12340
12415
|
}
|
|
12341
|
-
waiter =
|
|
12416
|
+
waiter = resolve5;
|
|
12342
12417
|
const t = setTimeout(() => {
|
|
12343
|
-
if (waiter ===
|
|
12418
|
+
if (waiter === resolve5) {
|
|
12344
12419
|
waiter = null;
|
|
12345
|
-
|
|
12420
|
+
resolve5(null);
|
|
12346
12421
|
}
|
|
12347
12422
|
}, Math.max(0, timeoutMs));
|
|
12348
12423
|
t.unref?.();
|
|
@@ -12440,7 +12515,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
|
|
|
12440
12515
|
};
|
|
12441
12516
|
|
|
12442
12517
|
// src/scripts/writeRunSummary.ts
|
|
12443
|
-
import * as
|
|
12518
|
+
import * as fs38 from "fs";
|
|
12444
12519
|
var writeRunSummary = async (ctx, profile) => {
|
|
12445
12520
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
12446
12521
|
if (!summaryPath) return;
|
|
@@ -12462,7 +12537,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
12462
12537
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
12463
12538
|
lines.push("");
|
|
12464
12539
|
try {
|
|
12465
|
-
|
|
12540
|
+
fs38.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
12466
12541
|
`);
|
|
12467
12542
|
} catch {
|
|
12468
12543
|
}
|
|
@@ -12495,6 +12570,7 @@ var preflightScripts = {
|
|
|
12495
12570
|
discoverQaContext,
|
|
12496
12571
|
resolvePreviewUrl,
|
|
12497
12572
|
resolveQaUrl,
|
|
12573
|
+
promoteQaGoal,
|
|
12498
12574
|
composePrompt,
|
|
12499
12575
|
setCommentTarget,
|
|
12500
12576
|
setLifecycleLabel,
|
|
@@ -12565,7 +12641,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
12565
12641
|
]);
|
|
12566
12642
|
|
|
12567
12643
|
// src/tools.ts
|
|
12568
|
-
import { execFileSync as
|
|
12644
|
+
import { execFileSync as execFileSync27 } from "child_process";
|
|
12569
12645
|
function verifyCliTools(tools, cwd) {
|
|
12570
12646
|
const out = [];
|
|
12571
12647
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -12598,7 +12674,7 @@ function verifyOne(tool3, cwd) {
|
|
|
12598
12674
|
}
|
|
12599
12675
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
12600
12676
|
try {
|
|
12601
|
-
|
|
12677
|
+
execFileSync27("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
12602
12678
|
return true;
|
|
12603
12679
|
} catch {
|
|
12604
12680
|
return false;
|
|
@@ -12706,9 +12782,9 @@ async function runExecutable(profileName, input) {
|
|
|
12706
12782
|
})
|
|
12707
12783
|
};
|
|
12708
12784
|
})() : null;
|
|
12709
|
-
const ndjsonDir =
|
|
12785
|
+
const ndjsonDir = path35.join(input.cwd, ".kody");
|
|
12710
12786
|
const invokeAgent = async (prompt) => {
|
|
12711
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
12787
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path35.isAbsolute(p) ? p : path35.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
12712
12788
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
12713
12789
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
12714
12790
|
const agents = loadSubagents(profile);
|
|
@@ -12920,7 +12996,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
12920
12996
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
12921
12997
|
try {
|
|
12922
12998
|
const profilePath = resolveProfilePath(profileName);
|
|
12923
|
-
if (!
|
|
12999
|
+
if (!fs39.existsSync(profilePath)) return null;
|
|
12924
13000
|
return loadProfile(profilePath).inputs;
|
|
12925
13001
|
} catch {
|
|
12926
13002
|
return null;
|
|
@@ -12929,17 +13005,17 @@ function getProfileInputsForChild(profileName, _cwd) {
|
|
|
12929
13005
|
function resolveProfilePath(profileName) {
|
|
12930
13006
|
const found = resolveExecutable(profileName);
|
|
12931
13007
|
if (found) return found;
|
|
12932
|
-
const here =
|
|
13008
|
+
const here = path35.dirname(new URL(import.meta.url).pathname);
|
|
12933
13009
|
const candidates = [
|
|
12934
|
-
|
|
13010
|
+
path35.join(here, "executables", profileName, "profile.json"),
|
|
12935
13011
|
// same-dir sibling (dev)
|
|
12936
|
-
|
|
13012
|
+
path35.join(here, "..", "executables", profileName, "profile.json"),
|
|
12937
13013
|
// up one (prod: dist/bin → dist/executables)
|
|
12938
|
-
|
|
13014
|
+
path35.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
12939
13015
|
// fallback
|
|
12940
13016
|
];
|
|
12941
13017
|
for (const c of candidates) {
|
|
12942
|
-
if (
|
|
13018
|
+
if (fs39.existsSync(c)) return c;
|
|
12943
13019
|
}
|
|
12944
13020
|
return candidates[0];
|
|
12945
13021
|
}
|
|
@@ -13039,8 +13115,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
13039
13115
|
var SIGKILL_GRACE_MS = 5e3;
|
|
13040
13116
|
async function runShellEntry(entry, ctx, profile) {
|
|
13041
13117
|
const shellName = entry.shell;
|
|
13042
|
-
const shellPath =
|
|
13043
|
-
if (!
|
|
13118
|
+
const shellPath = path35.join(profile.dir, shellName);
|
|
13119
|
+
if (!fs39.existsSync(shellPath)) {
|
|
13044
13120
|
ctx.skipAgent = true;
|
|
13045
13121
|
ctx.output.exitCode = 99;
|
|
13046
13122
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -13056,7 +13132,7 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
13056
13132
|
env[`KODY_CFG_${k}`] = v;
|
|
13057
13133
|
}
|
|
13058
13134
|
const timeoutMs = resolveShellTimeoutMs(entry);
|
|
13059
|
-
const child =
|
|
13135
|
+
const child = spawn9("bash", [shellPath, ...positional], {
|
|
13060
13136
|
cwd: ctx.cwd,
|
|
13061
13137
|
env,
|
|
13062
13138
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -13078,14 +13154,14 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
13078
13154
|
let killTimer;
|
|
13079
13155
|
let escalateTimer;
|
|
13080
13156
|
const result = await new Promise(
|
|
13081
|
-
(
|
|
13157
|
+
(resolve5) => {
|
|
13082
13158
|
let settled = false;
|
|
13083
13159
|
const settle = (code, signal, spawnErr) => {
|
|
13084
13160
|
if (settled) return;
|
|
13085
13161
|
settled = true;
|
|
13086
13162
|
if (killTimer) clearTimeout(killTimer);
|
|
13087
13163
|
if (escalateTimer) clearTimeout(escalateTimer);
|
|
13088
|
-
|
|
13164
|
+
resolve5({ code, signal, spawnErr });
|
|
13089
13165
|
};
|
|
13090
13166
|
child.on("error", (err) => settle(null, null, err));
|
|
13091
13167
|
child.on("close", (code, signal) => settle(code, signal));
|
|
@@ -13367,7 +13443,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
13367
13443
|
}
|
|
13368
13444
|
function resetWorkingTree2(cwd) {
|
|
13369
13445
|
try {
|
|
13370
|
-
|
|
13446
|
+
execFileSync28("git", ["reset", "--hard", "HEAD"], {
|
|
13371
13447
|
cwd,
|
|
13372
13448
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13373
13449
|
timeout: 3e4
|
|
@@ -13519,14 +13595,14 @@ function resolveAuthToken(env = process.env) {
|
|
|
13519
13595
|
return token;
|
|
13520
13596
|
}
|
|
13521
13597
|
function detectPackageManager2(cwd) {
|
|
13522
|
-
if (
|
|
13523
|
-
if (
|
|
13524
|
-
if (
|
|
13598
|
+
if (fs40.existsSync(path36.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
13599
|
+
if (fs40.existsSync(path36.join(cwd, "yarn.lock"))) return "yarn";
|
|
13600
|
+
if (fs40.existsSync(path36.join(cwd, "bun.lockb"))) return "bun";
|
|
13525
13601
|
return "npm";
|
|
13526
13602
|
}
|
|
13527
13603
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
13528
13604
|
try {
|
|
13529
|
-
|
|
13605
|
+
execFileSync29(cmd, args, {
|
|
13530
13606
|
cwd,
|
|
13531
13607
|
stdio: stream ? "inherit" : "pipe",
|
|
13532
13608
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -13539,7 +13615,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
13539
13615
|
}
|
|
13540
13616
|
function isOnPath(bin) {
|
|
13541
13617
|
try {
|
|
13542
|
-
|
|
13618
|
+
execFileSync29("which", [bin], { stdio: "pipe" });
|
|
13543
13619
|
return true;
|
|
13544
13620
|
} catch {
|
|
13545
13621
|
return false;
|
|
@@ -13580,7 +13656,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
13580
13656
|
} catch {
|
|
13581
13657
|
}
|
|
13582
13658
|
try {
|
|
13583
|
-
|
|
13659
|
+
execFileSync29("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
13584
13660
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
13585
13661
|
return 0;
|
|
13586
13662
|
} catch {
|
|
@@ -13590,16 +13666,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
13590
13666
|
}
|
|
13591
13667
|
function configureGitIdentity(cwd) {
|
|
13592
13668
|
try {
|
|
13593
|
-
const name =
|
|
13669
|
+
const name = execFileSync29("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
13594
13670
|
if (name) return;
|
|
13595
13671
|
} catch {
|
|
13596
13672
|
}
|
|
13597
13673
|
try {
|
|
13598
|
-
|
|
13674
|
+
execFileSync29("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
13599
13675
|
} catch {
|
|
13600
13676
|
}
|
|
13601
13677
|
try {
|
|
13602
|
-
|
|
13678
|
+
execFileSync29("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
13603
13679
|
cwd,
|
|
13604
13680
|
stdio: "pipe"
|
|
13605
13681
|
});
|
|
@@ -13608,11 +13684,11 @@ function configureGitIdentity(cwd) {
|
|
|
13608
13684
|
}
|
|
13609
13685
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
13610
13686
|
if (!issueNumber) return;
|
|
13611
|
-
const logPath =
|
|
13687
|
+
const logPath = path36.join(cwd, ".kody", "last-run.jsonl");
|
|
13612
13688
|
let tail = "";
|
|
13613
13689
|
try {
|
|
13614
|
-
if (
|
|
13615
|
-
const content =
|
|
13690
|
+
if (fs40.existsSync(logPath)) {
|
|
13691
|
+
const content = fs40.readFileSync(logPath, "utf-8");
|
|
13616
13692
|
tail = content.slice(-3e3);
|
|
13617
13693
|
}
|
|
13618
13694
|
} catch {
|
|
@@ -13637,7 +13713,7 @@ async function runCi(argv) {
|
|
|
13637
13713
|
return 0;
|
|
13638
13714
|
}
|
|
13639
13715
|
const args = parseCiArgs(argv);
|
|
13640
|
-
const cwd = args.cwd ?
|
|
13716
|
+
const cwd = args.cwd ? path36.resolve(args.cwd) : process.cwd();
|
|
13641
13717
|
let earlyConfig;
|
|
13642
13718
|
try {
|
|
13643
13719
|
earlyConfig = loadConfig(cwd);
|
|
@@ -13647,9 +13723,9 @@ async function runCi(argv) {
|
|
|
13647
13723
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
13648
13724
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
13649
13725
|
let manualWorkflowDispatch = false;
|
|
13650
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
13726
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs40.existsSync(dispatchEventPath)) {
|
|
13651
13727
|
try {
|
|
13652
|
-
const evt = JSON.parse(
|
|
13728
|
+
const evt = JSON.parse(fs40.readFileSync(dispatchEventPath, "utf-8"));
|
|
13653
13729
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
13654
13730
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
13655
13731
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -13908,17 +13984,17 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
13908
13984
|
return result;
|
|
13909
13985
|
}
|
|
13910
13986
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
13911
|
-
const sessionFile =
|
|
13912
|
-
const eventsFile =
|
|
13987
|
+
const sessionFile = path37.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
13988
|
+
const eventsFile = path37.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
13913
13989
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
13914
|
-
const tasksDir =
|
|
13990
|
+
const tasksDir = path37.join(".kody", "tasks", safeSession);
|
|
13915
13991
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
13916
|
-
const paths = candidatePaths.filter((p) =>
|
|
13992
|
+
const paths = candidatePaths.filter((p) => fs41.existsSync(path37.join(cwd, p)));
|
|
13917
13993
|
if (paths.length === 0) return;
|
|
13918
13994
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
13919
13995
|
try {
|
|
13920
|
-
|
|
13921
|
-
|
|
13996
|
+
execFileSync30("git", ["add", "-f", ...paths], opts);
|
|
13997
|
+
execFileSync30("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
13922
13998
|
} catch (err) {
|
|
13923
13999
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13924
14000
|
process.stderr.write(`[kody:chat] commit skipped: ${msg}
|
|
@@ -13957,7 +14033,7 @@ async function runChat(argv) {
|
|
|
13957
14033
|
${CHAT_HELP}`);
|
|
13958
14034
|
return 64;
|
|
13959
14035
|
}
|
|
13960
|
-
const cwd = args.cwd ?
|
|
14036
|
+
const cwd = args.cwd ? path37.resolve(args.cwd) : process.cwd();
|
|
13961
14037
|
const sessionId = args.sessionId;
|
|
13962
14038
|
const unpackedSecrets = unpackAllSecrets();
|
|
13963
14039
|
if (unpackedSecrets > 0) {
|
|
@@ -14009,7 +14085,7 @@ ${CHAT_HELP}`);
|
|
|
14009
14085
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
14010
14086
|
const meta = readMeta(sessionFile);
|
|
14011
14087
|
process.stdout.write(
|
|
14012
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
14088
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs41.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
14013
14089
|
`
|
|
14014
14090
|
);
|
|
14015
14091
|
try {
|