@kody-ade/kody-engine 0.4.140 → 0.4.142
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 +754 -666
- package/dist/executables/goal-scheduler/scheduler.sh +7 -0
- package/package.json +1 -1
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((resolve6) => {
|
|
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
|
+
resolve6({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
126
126
|
});
|
|
127
127
|
child.on("error", (err) => {
|
|
128
128
|
clearTimeout(timer);
|
|
129
|
-
|
|
129
|
+
resolve6({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
}
|
|
@@ -358,6 +358,141 @@ var init_submitMcp = __esm({
|
|
|
358
358
|
}
|
|
359
359
|
});
|
|
360
360
|
|
|
361
|
+
// src/repoWorkspace.ts
|
|
362
|
+
import { spawn as spawn2, spawnSync } from "child_process";
|
|
363
|
+
import * as fs6 from "fs";
|
|
364
|
+
import * as path6 from "path";
|
|
365
|
+
async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
|
|
366
|
+
const name = repo?.trim();
|
|
367
|
+
if (!name || !REPO_RE.test(name)) return null;
|
|
368
|
+
const root = path6.resolve(reposRoot);
|
|
369
|
+
const dir = path6.resolve(root, name);
|
|
370
|
+
if (dir !== root && !dir.startsWith(root + path6.sep)) return null;
|
|
371
|
+
if (fs6.existsSync(path6.join(dir, ".git"))) return dir;
|
|
372
|
+
const inflight = repoClones.get(dir);
|
|
373
|
+
if (inflight) {
|
|
374
|
+
await inflight;
|
|
375
|
+
return dir;
|
|
376
|
+
}
|
|
377
|
+
const p = cloneRepo(name, repoToken, dir).finally(() => {
|
|
378
|
+
if (repoClones.get(dir) === p) repoClones.delete(dir);
|
|
379
|
+
});
|
|
380
|
+
repoClones.set(dir, p);
|
|
381
|
+
await p;
|
|
382
|
+
return dir;
|
|
383
|
+
}
|
|
384
|
+
async function ensureRepoCwd(opts) {
|
|
385
|
+
const dir = await resolveAndClone(
|
|
386
|
+
opts.reposRoot,
|
|
387
|
+
opts.repo,
|
|
388
|
+
opts.repoToken,
|
|
389
|
+
opts.cloneRepo
|
|
390
|
+
);
|
|
391
|
+
return dir ?? opts.baseCwd;
|
|
392
|
+
}
|
|
393
|
+
async function fetchRepo(opts) {
|
|
394
|
+
const dir = await resolveAndClone(
|
|
395
|
+
opts.reposRoot,
|
|
396
|
+
opts.repo,
|
|
397
|
+
opts.repoToken,
|
|
398
|
+
opts.cloneRepo ?? defaultCloneRepo
|
|
399
|
+
);
|
|
400
|
+
if (!dir) {
|
|
401
|
+
throw new Error(
|
|
402
|
+
`invalid repo "${opts.repo}" \u2014 expected "owner/name" with no path escapes`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return dir;
|
|
406
|
+
}
|
|
407
|
+
var REPO_RE, repoClones, defaultCloneRepo;
|
|
408
|
+
var init_repoWorkspace = __esm({
|
|
409
|
+
"src/repoWorkspace.ts"() {
|
|
410
|
+
"use strict";
|
|
411
|
+
REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
412
|
+
repoClones = /* @__PURE__ */ new Map();
|
|
413
|
+
defaultCloneRepo = (repo, token, dir) => {
|
|
414
|
+
fs6.mkdirSync(path6.dirname(dir), { recursive: true });
|
|
415
|
+
const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
|
|
416
|
+
return new Promise((resolve6, reject) => {
|
|
417
|
+
const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
|
|
418
|
+
stdio: "inherit"
|
|
419
|
+
});
|
|
420
|
+
child.on("exit", (code) => {
|
|
421
|
+
if (code !== 0) {
|
|
422
|
+
reject(new Error(`git clone ${repo} failed (exit ${code})`));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
const name = process.env.GIT_AUTHOR_NAME ?? "Kody Bot";
|
|
427
|
+
const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
|
|
428
|
+
spawnSync("git", ["-C", dir, "config", "user.name", name]);
|
|
429
|
+
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
resolve6();
|
|
433
|
+
});
|
|
434
|
+
child.on("error", reject);
|
|
435
|
+
});
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// src/fetchRepoMcp.ts
|
|
441
|
+
var fetchRepoMcp_exports = {};
|
|
442
|
+
__export(fetchRepoMcp_exports, {
|
|
443
|
+
buildFetchRepoMcpServer: () => buildFetchRepoMcpServer
|
|
444
|
+
});
|
|
445
|
+
import {
|
|
446
|
+
createSdkMcpServer as createSdkMcpServer3,
|
|
447
|
+
tool as tool3
|
|
448
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
449
|
+
import { z as z3 } from "zod";
|
|
450
|
+
function buildFetchRepoMcpServer(opts) {
|
|
451
|
+
const fetchTool = tool3(
|
|
452
|
+
"fetch_repo",
|
|
453
|
+
'Clone another GitHub repository into your workspace so you can read and work on it. Pass `repo` as "owner/name" (e.g. "A-Guy-educ/A-Guy"). Returns the absolute path of the clone \u2014 then use your Read/Grep/Glob/Bash tools at that path to inspect it. Already-fetched repos are reused instantly. Use this whenever the user asks about a repository other than your current one \u2014 you are NOT limited to a single repo.',
|
|
454
|
+
{
|
|
455
|
+
repo: z3.string().describe('GitHub repository as "owner/name", e.g. "A-Guy-educ/A-Guy".')
|
|
456
|
+
},
|
|
457
|
+
async (args) => {
|
|
458
|
+
const repo = String(args.repo ?? "").trim();
|
|
459
|
+
try {
|
|
460
|
+
const dir = await fetchRepo({
|
|
461
|
+
reposRoot: opts.reposRoot,
|
|
462
|
+
repo,
|
|
463
|
+
repoToken: opts.repoToken
|
|
464
|
+
});
|
|
465
|
+
return {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: "text",
|
|
469
|
+
text: `Cloned ${repo} \u2192 ${dir}
|
|
470
|
+
Use Read/Grep/Glob/Bash at that absolute path to explore it. It now lives in your workspace alongside any other repos you've fetched.`
|
|
471
|
+
}
|
|
472
|
+
]
|
|
473
|
+
};
|
|
474
|
+
} catch (err) {
|
|
475
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
476
|
+
return {
|
|
477
|
+
content: [{ type: "text", text: `Could not fetch ${repo}: ${msg}` }],
|
|
478
|
+
isError: true
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
return createSdkMcpServer3({
|
|
484
|
+
name: "kody-fetch-repo",
|
|
485
|
+
version: "0.1.0",
|
|
486
|
+
tools: [fetchTool]
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
var init_fetchRepoMcp = __esm({
|
|
490
|
+
"src/fetchRepoMcp.ts"() {
|
|
491
|
+
"use strict";
|
|
492
|
+
init_repoWorkspace();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
361
496
|
// src/issue.ts
|
|
362
497
|
import { execFileSync } from "child_process";
|
|
363
498
|
function ghToken() {
|
|
@@ -515,16 +650,16 @@ var init_issue = __esm({
|
|
|
515
650
|
});
|
|
516
651
|
|
|
517
652
|
// src/prompt.ts
|
|
518
|
-
import * as
|
|
519
|
-
import * as
|
|
653
|
+
import * as fs20 from "fs";
|
|
654
|
+
import * as path19 from "path";
|
|
520
655
|
function loadProjectConventions(projectDir) {
|
|
521
656
|
const out = [];
|
|
522
657
|
for (const rel of CONVENTION_FILES) {
|
|
523
|
-
const abs =
|
|
524
|
-
if (!
|
|
658
|
+
const abs = path19.join(projectDir, rel);
|
|
659
|
+
if (!fs20.existsSync(abs)) continue;
|
|
525
660
|
let content;
|
|
526
661
|
try {
|
|
527
|
-
content =
|
|
662
|
+
content = fs20.readFileSync(abs, "utf-8");
|
|
528
663
|
} catch {
|
|
529
664
|
continue;
|
|
530
665
|
}
|
|
@@ -673,7 +808,7 @@ __export(loadMemoryContext_exports, {
|
|
|
673
808
|
loadMemoryContext: () => loadMemoryContext
|
|
674
809
|
});
|
|
675
810
|
import * as fs32 from "fs";
|
|
676
|
-
import * as
|
|
811
|
+
import * as path30 from "path";
|
|
677
812
|
function collectPages(memoryAbs) {
|
|
678
813
|
const out = [];
|
|
679
814
|
walkMd(memoryAbs, (file) => {
|
|
@@ -690,10 +825,10 @@ function collectPages(memoryAbs) {
|
|
|
690
825
|
return;
|
|
691
826
|
}
|
|
692
827
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
693
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
828
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path30.basename(file, ".md");
|
|
694
829
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
695
830
|
out.push({
|
|
696
|
-
relPath:
|
|
831
|
+
relPath: path30.relative(memoryAbs, file),
|
|
697
832
|
title,
|
|
698
833
|
updated,
|
|
699
834
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
@@ -767,7 +902,7 @@ function walkMd(root, visit) {
|
|
|
767
902
|
}
|
|
768
903
|
for (const name of names) {
|
|
769
904
|
if (name.startsWith(".")) continue;
|
|
770
|
-
const full =
|
|
905
|
+
const full = path30.join(dir, name);
|
|
771
906
|
let stat;
|
|
772
907
|
try {
|
|
773
908
|
stat = fs32.statSync(full);
|
|
@@ -793,7 +928,7 @@ var init_loadMemoryContext = __esm({
|
|
|
793
928
|
TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
794
929
|
loadMemoryContext = async (ctx) => {
|
|
795
930
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
796
|
-
const memoryAbs =
|
|
931
|
+
const memoryAbs = path30.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
797
932
|
if (!fs32.existsSync(memoryAbs)) {
|
|
798
933
|
ctx.data.memoryContext = "";
|
|
799
934
|
return;
|
|
@@ -926,7 +1061,7 @@ var init_loadPriorArt = __esm({
|
|
|
926
1061
|
// package.json
|
|
927
1062
|
var package_default = {
|
|
928
1063
|
name: "@kody-ade/kody-engine",
|
|
929
|
-
version: "0.4.
|
|
1064
|
+
version: "0.4.142",
|
|
930
1065
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
931
1066
|
license: "MIT",
|
|
932
1067
|
type: "module",
|
|
@@ -982,9 +1117,9 @@ var package_default = {
|
|
|
982
1117
|
};
|
|
983
1118
|
|
|
984
1119
|
// src/chat-cli.ts
|
|
985
|
-
import { execFileSync as
|
|
1120
|
+
import { execFileSync as execFileSync30 } from "child_process";
|
|
986
1121
|
import * as fs42 from "fs";
|
|
987
|
-
import * as
|
|
1122
|
+
import * as path38 from "path";
|
|
988
1123
|
|
|
989
1124
|
// src/chat/events.ts
|
|
990
1125
|
import * as fs from "fs";
|
|
@@ -1050,8 +1185,8 @@ function makeRunId(sessionId, suffix) {
|
|
|
1050
1185
|
}
|
|
1051
1186
|
|
|
1052
1187
|
// src/chat/loop.ts
|
|
1053
|
-
import * as
|
|
1054
|
-
import * as
|
|
1188
|
+
import * as fs10 from "fs";
|
|
1189
|
+
import * as path10 from "path";
|
|
1055
1190
|
|
|
1056
1191
|
// src/task-artifacts.ts
|
|
1057
1192
|
import fs2 from "fs";
|
|
@@ -1142,8 +1277,8 @@ function taskArtifactsPromptAddendum(opts) {
|
|
|
1142
1277
|
}
|
|
1143
1278
|
|
|
1144
1279
|
// src/agent.ts
|
|
1145
|
-
import * as
|
|
1146
|
-
import * as
|
|
1280
|
+
import * as fs7 from "fs";
|
|
1281
|
+
import * as path7 from "path";
|
|
1147
1282
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1148
1283
|
|
|
1149
1284
|
// src/claudeBinary.ts
|
|
@@ -1528,10 +1663,10 @@ function resolveTurnTimeoutMs(opts) {
|
|
|
1528
1663
|
return DEFAULT_TURN_TIMEOUT_MS;
|
|
1529
1664
|
}
|
|
1530
1665
|
async function runAgent(opts) {
|
|
1531
|
-
const ndjsonDir = opts.ndjsonDir ??
|
|
1532
|
-
|
|
1533
|
-
const ndjsonPath =
|
|
1534
|
-
const fullLog =
|
|
1666
|
+
const ndjsonDir = opts.ndjsonDir ?? path7.join(opts.cwd, ".kody");
|
|
1667
|
+
fs7.mkdirSync(ndjsonDir, { recursive: true });
|
|
1668
|
+
const ndjsonPath = path7.join(ndjsonDir, "last-run.jsonl");
|
|
1669
|
+
const fullLog = fs7.createWriteStream(ndjsonPath, { flags: "w" });
|
|
1535
1670
|
const env = {
|
|
1536
1671
|
...process.env,
|
|
1537
1672
|
SKIP_HOOKS: "1",
|
|
@@ -1565,7 +1700,9 @@ async function runAgent(opts) {
|
|
|
1565
1700
|
const queryOptions = {
|
|
1566
1701
|
model: opts.model.model,
|
|
1567
1702
|
cwd: opts.cwd,
|
|
1568
|
-
|
|
1703
|
+
// Fresh array (never mutate the shared DEFAULT_ALLOWED_TOOLS const) so
|
|
1704
|
+
// opt-in tools like fetch_repo can be appended below.
|
|
1705
|
+
allowedTools: [...opts.allowedToolsOverride ?? DEFAULT_ALLOWED_TOOLS],
|
|
1569
1706
|
permissionMode: opts.permissionModeOverride ?? "acceptEdits",
|
|
1570
1707
|
env
|
|
1571
1708
|
};
|
|
@@ -1594,6 +1731,16 @@ async function runAgent(opts) {
|
|
|
1594
1731
|
getSubmitted = submitHandle.getSubmitted;
|
|
1595
1732
|
mcpEntries.push(["kody-submit", submitHandle.server]);
|
|
1596
1733
|
}
|
|
1734
|
+
if (opts.enableFetchRepoTool && opts.reposRoot) {
|
|
1735
|
+
const { buildFetchRepoMcpServer: buildFetchRepoMcpServer2 } = await Promise.resolve().then(() => (init_fetchRepoMcp(), fetchRepoMcp_exports));
|
|
1736
|
+
const fetchServer = buildFetchRepoMcpServer2({
|
|
1737
|
+
reposRoot: opts.reposRoot,
|
|
1738
|
+
repoToken: opts.repoToken
|
|
1739
|
+
});
|
|
1740
|
+
mcpEntries.push(["kody-fetch-repo", fetchServer]);
|
|
1741
|
+
queryOptions.allowedTools.push("mcp__kody-fetch-repo__fetch_repo");
|
|
1742
|
+
queryOptions.additionalDirectories = [opts.reposRoot];
|
|
1743
|
+
}
|
|
1597
1744
|
if (mcpEntries.length > 0) {
|
|
1598
1745
|
queryOptions.mcpServers = Object.fromEntries(mcpEntries);
|
|
1599
1746
|
}
|
|
@@ -1641,10 +1788,10 @@ async function runAgent(opts) {
|
|
|
1641
1788
|
let timer;
|
|
1642
1789
|
let next;
|
|
1643
1790
|
if (turnTimeoutMs > 0) {
|
|
1644
|
-
const timeoutPromise = new Promise((
|
|
1791
|
+
const timeoutPromise = new Promise((resolve6) => {
|
|
1645
1792
|
timer = setTimeout(() => {
|
|
1646
1793
|
timedOut = true;
|
|
1647
|
-
|
|
1794
|
+
resolve6({ done: true, value: void 0 });
|
|
1648
1795
|
}, turnTimeoutMs);
|
|
1649
1796
|
});
|
|
1650
1797
|
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
@@ -1774,48 +1921,48 @@ async function runAgent(opts) {
|
|
|
1774
1921
|
}
|
|
1775
1922
|
|
|
1776
1923
|
// src/registry.ts
|
|
1777
|
-
import * as
|
|
1778
|
-
import * as
|
|
1924
|
+
import * as fs8 from "fs";
|
|
1925
|
+
import * as path8 from "path";
|
|
1779
1926
|
function getExecutablesRoot() {
|
|
1780
|
-
const here =
|
|
1927
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
1781
1928
|
const candidates = [
|
|
1782
|
-
|
|
1929
|
+
path8.join(here, "executables"),
|
|
1783
1930
|
// dev: src/
|
|
1784
|
-
|
|
1931
|
+
path8.join(here, "..", "executables"),
|
|
1785
1932
|
// built: dist/bin → dist/executables
|
|
1786
|
-
|
|
1933
|
+
path8.join(here, "..", "src", "executables")
|
|
1787
1934
|
// fallback
|
|
1788
1935
|
];
|
|
1789
1936
|
for (const c of candidates) {
|
|
1790
|
-
if (
|
|
1937
|
+
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
1791
1938
|
}
|
|
1792
1939
|
return candidates[0];
|
|
1793
1940
|
}
|
|
1794
1941
|
function getProjectExecutablesRoot() {
|
|
1795
|
-
return
|
|
1942
|
+
return path8.join(process.cwd(), ".kody", "executables");
|
|
1796
1943
|
}
|
|
1797
1944
|
function getBuiltinJobsRoot() {
|
|
1798
|
-
const here =
|
|
1945
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
1799
1946
|
const candidates = [
|
|
1800
|
-
|
|
1947
|
+
path8.join(here, "jobs"),
|
|
1801
1948
|
// dev: src/
|
|
1802
|
-
|
|
1949
|
+
path8.join(here, "..", "jobs"),
|
|
1803
1950
|
// built: dist/bin → dist/jobs
|
|
1804
|
-
|
|
1951
|
+
path8.join(here, "..", "src", "jobs")
|
|
1805
1952
|
// fallback
|
|
1806
1953
|
];
|
|
1807
1954
|
for (const c of candidates) {
|
|
1808
|
-
if (
|
|
1955
|
+
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
1809
1956
|
}
|
|
1810
1957
|
return candidates[0];
|
|
1811
1958
|
}
|
|
1812
1959
|
function listBuiltinJobs(root = getBuiltinJobsRoot()) {
|
|
1813
|
-
if (!
|
|
1960
|
+
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
1814
1961
|
const out = [];
|
|
1815
|
-
for (const ent of
|
|
1962
|
+
for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
|
|
1816
1963
|
if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
|
|
1817
1964
|
const slug = ent.name.slice(0, -3);
|
|
1818
|
-
out.push({ slug, filePath:
|
|
1965
|
+
out.push({ slug, filePath: path8.join(root, ent.name) });
|
|
1819
1966
|
}
|
|
1820
1967
|
out.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
1821
1968
|
return out;
|
|
@@ -1828,13 +1975,13 @@ function listExecutables(roots = getExecutableRoots()) {
|
|
|
1828
1975
|
const seen = /* @__PURE__ */ new Set();
|
|
1829
1976
|
const out = [];
|
|
1830
1977
|
for (const root of rootList) {
|
|
1831
|
-
if (!
|
|
1832
|
-
const entries =
|
|
1978
|
+
if (!fs8.existsSync(root)) continue;
|
|
1979
|
+
const entries = fs8.readdirSync(root, { withFileTypes: true });
|
|
1833
1980
|
for (const ent of entries) {
|
|
1834
1981
|
if (!ent.isDirectory()) continue;
|
|
1835
1982
|
if (seen.has(ent.name)) continue;
|
|
1836
|
-
const profilePath =
|
|
1837
|
-
if (
|
|
1983
|
+
const profilePath = path8.join(root, ent.name, "profile.json");
|
|
1984
|
+
if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
|
|
1838
1985
|
out.push({ name: ent.name, profilePath });
|
|
1839
1986
|
seen.add(ent.name);
|
|
1840
1987
|
}
|
|
@@ -1846,8 +1993,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
|
|
|
1846
1993
|
if (!isSafeName(name)) return null;
|
|
1847
1994
|
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
1848
1995
|
for (const root of rootList) {
|
|
1849
|
-
const profilePath =
|
|
1850
|
-
if (
|
|
1996
|
+
const profilePath = path8.join(root, name, "profile.json");
|
|
1997
|
+
if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
|
|
1851
1998
|
return profilePath;
|
|
1852
1999
|
}
|
|
1853
2000
|
}
|
|
@@ -1863,7 +2010,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
|
|
|
1863
2010
|
const profilePath = resolveExecutable(name, roots);
|
|
1864
2011
|
if (!profilePath) return null;
|
|
1865
2012
|
try {
|
|
1866
|
-
const raw = JSON.parse(
|
|
2013
|
+
const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
|
|
1867
2014
|
if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
|
|
1868
2015
|
return raw.inputs;
|
|
1869
2016
|
} catch {
|
|
@@ -1893,14 +2040,14 @@ function parseGenericFlags(argv) {
|
|
|
1893
2040
|
}
|
|
1894
2041
|
|
|
1895
2042
|
// src/chat/session.ts
|
|
1896
|
-
import * as
|
|
1897
|
-
import * as
|
|
2043
|
+
import * as fs9 from "fs";
|
|
2044
|
+
import * as path9 from "path";
|
|
1898
2045
|
function sessionFilePath(cwd, sessionId) {
|
|
1899
|
-
return
|
|
2046
|
+
return path9.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
|
|
1900
2047
|
}
|
|
1901
2048
|
function readMeta(file) {
|
|
1902
|
-
if (!
|
|
1903
|
-
const raw =
|
|
2049
|
+
if (!fs9.existsSync(file)) return null;
|
|
2050
|
+
const raw = fs9.readFileSync(file, "utf-8");
|
|
1904
2051
|
const firstLine2 = raw.split("\n", 1)[0]?.trim();
|
|
1905
2052
|
if (!firstLine2) return null;
|
|
1906
2053
|
try {
|
|
@@ -1913,8 +2060,8 @@ function readMeta(file) {
|
|
|
1913
2060
|
}
|
|
1914
2061
|
}
|
|
1915
2062
|
function readSession(file) {
|
|
1916
|
-
if (!
|
|
1917
|
-
const raw =
|
|
2063
|
+
if (!fs9.existsSync(file)) return [];
|
|
2064
|
+
const raw = fs9.readFileSync(file, "utf-8").trim();
|
|
1918
2065
|
if (!raw) return [];
|
|
1919
2066
|
const turns = [];
|
|
1920
2067
|
for (const line of raw.split("\n")) {
|
|
@@ -1930,14 +2077,14 @@ function readSession(file) {
|
|
|
1930
2077
|
return turns;
|
|
1931
2078
|
}
|
|
1932
2079
|
function appendTurn(file, turn) {
|
|
1933
|
-
|
|
2080
|
+
fs9.mkdirSync(path9.dirname(file), { recursive: true });
|
|
1934
2081
|
const line = JSON.stringify({
|
|
1935
2082
|
role: turn.role,
|
|
1936
2083
|
content: turn.content,
|
|
1937
2084
|
timestamp: turn.timestamp,
|
|
1938
2085
|
toolCalls: turn.toolCalls ?? []
|
|
1939
2086
|
});
|
|
1940
|
-
|
|
2087
|
+
fs9.appendFileSync(file, `${line}
|
|
1941
2088
|
`);
|
|
1942
2089
|
}
|
|
1943
2090
|
function seedInitialMessage(file, message) {
|
|
@@ -2037,6 +2184,15 @@ var CHAT_SYSTEM_PROMPT = [
|
|
|
2037
2184
|
"Do not invent file paths, commit SHAs, line numbers, or command output. If you",
|
|
2038
2185
|
"cite something concrete, you must have just read or run it in this session."
|
|
2039
2186
|
].join("\n");
|
|
2187
|
+
var CROSS_REPO_PROMPT = [
|
|
2188
|
+
"# Working across repositories",
|
|
2189
|
+
"You are NOT limited to the repository at your current working directory. You",
|
|
2190
|
+
'have a `fetch_repo` tool: call fetch_repo("owner/name") to clone another repo',
|
|
2191
|
+
"into your workspace; it returns an absolute path. Then use Read/Grep/Glob/Bash",
|
|
2192
|
+
"at that path to inspect or work on it. Already-fetched repos are reused",
|
|
2193
|
+
"instantly. When the user asks about a different repo \u2014 or to compare repos \u2014",
|
|
2194
|
+
"fetch it instead of saying you are scoped to a single repo."
|
|
2195
|
+
].join("\n");
|
|
2040
2196
|
function buildExecutableCatalog() {
|
|
2041
2197
|
let discovered;
|
|
2042
2198
|
try {
|
|
@@ -2047,7 +2203,7 @@ function buildExecutableCatalog() {
|
|
|
2047
2203
|
const entries = [];
|
|
2048
2204
|
for (const { name, profilePath } of discovered) {
|
|
2049
2205
|
try {
|
|
2050
|
-
const raw = JSON.parse(
|
|
2206
|
+
const raw = JSON.parse(fs10.readFileSync(profilePath, "utf-8"));
|
|
2051
2207
|
const describe = typeof raw.describe === "string" ? raw.describe : "";
|
|
2052
2208
|
const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
|
|
2053
2209
|
entries.push({ name, describe: firstSentence.trim() });
|
|
@@ -2093,11 +2249,13 @@ async function runChatTurn(opts) {
|
|
|
2093
2249
|
const contextBlock = readContextBlock(opts.cwd);
|
|
2094
2250
|
const memoryBlock = readMemoryIndexBlock(opts.cwd);
|
|
2095
2251
|
const instructionsBlock = readInstructionsBlock(opts.cwd);
|
|
2252
|
+
const crossRepoBlock = opts.reposRoot ? CROSS_REPO_PROMPT : null;
|
|
2096
2253
|
const systemPrompt = [
|
|
2097
2254
|
basePrompt,
|
|
2098
2255
|
contextBlock,
|
|
2099
2256
|
memoryBlock,
|
|
2100
2257
|
instructionsBlock,
|
|
2258
|
+
crossRepoBlock,
|
|
2101
2259
|
catalog,
|
|
2102
2260
|
artifactAddendum
|
|
2103
2261
|
].filter((s) => typeof s === "string" && s.length > 0).join("\n\n");
|
|
@@ -2111,6 +2269,14 @@ async function runChatTurn(opts) {
|
|
|
2111
2269
|
verbose: opts.verbose,
|
|
2112
2270
|
quiet: opts.quiet,
|
|
2113
2271
|
systemPromptAppend: systemPrompt,
|
|
2272
|
+
// Let the agent clone + work on OTHER repos mid-conversation (a
|
|
2273
|
+
// repo-less Brain serves many). Enabled whenever we know where repos
|
|
2274
|
+
// live; grants read access to that root via additionalDirectories.
|
|
2275
|
+
...opts.reposRoot ? {
|
|
2276
|
+
enableFetchRepoTool: true,
|
|
2277
|
+
reposRoot: opts.reposRoot,
|
|
2278
|
+
repoToken: opts.repoToken
|
|
2279
|
+
} : {},
|
|
2114
2280
|
onProgress: async (ev) => {
|
|
2115
2281
|
progressSeq += 1;
|
|
2116
2282
|
if (ev.kind === "thinking") {
|
|
@@ -2195,10 +2361,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
2195
2361
|
var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
|
|
2196
2362
|
var MAX_INDEX_BYTES = 8e3;
|
|
2197
2363
|
function readMemoryIndexBlock(cwd) {
|
|
2198
|
-
const indexPath =
|
|
2364
|
+
const indexPath = path10.join(cwd, MEMORY_INDEX_REL);
|
|
2199
2365
|
let raw;
|
|
2200
2366
|
try {
|
|
2201
|
-
raw =
|
|
2367
|
+
raw = fs10.readFileSync(indexPath, "utf-8");
|
|
2202
2368
|
} catch {
|
|
2203
2369
|
return "";
|
|
2204
2370
|
}
|
|
@@ -2216,17 +2382,17 @@ function readMemoryIndexBlock(cwd) {
|
|
|
2216
2382
|
var CONTEXT_DIR_REL = ".kody/context";
|
|
2217
2383
|
var MAX_CONTEXT_BYTES = 12e3;
|
|
2218
2384
|
function readContextBlock(cwd) {
|
|
2219
|
-
const dir =
|
|
2385
|
+
const dir = path10.join(cwd, CONTEXT_DIR_REL);
|
|
2220
2386
|
let files;
|
|
2221
2387
|
try {
|
|
2222
|
-
files =
|
|
2388
|
+
files = fs10.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
2223
2389
|
} catch {
|
|
2224
2390
|
return "";
|
|
2225
2391
|
}
|
|
2226
2392
|
const sections = [];
|
|
2227
2393
|
for (const file of files) {
|
|
2228
2394
|
try {
|
|
2229
|
-
const content =
|
|
2395
|
+
const content = fs10.readFileSync(path10.join(dir, file), "utf-8").trim();
|
|
2230
2396
|
if (content) sections.push(`### ${file.replace(/\.md$/, "")}
|
|
2231
2397
|
|
|
2232
2398
|
${content}`);
|
|
@@ -2247,10 +2413,10 @@ ${content}`);
|
|
|
2247
2413
|
var INSTRUCTIONS_REL = ".kody/instructions.md";
|
|
2248
2414
|
var MAX_INSTRUCTIONS_BYTES = 8e3;
|
|
2249
2415
|
function readInstructionsBlock(cwd) {
|
|
2250
|
-
const instructionsPath =
|
|
2416
|
+
const instructionsPath = path10.join(cwd, INSTRUCTIONS_REL);
|
|
2251
2417
|
let raw;
|
|
2252
2418
|
try {
|
|
2253
|
-
raw =
|
|
2419
|
+
raw = fs10.readFileSync(instructionsPath, "utf-8");
|
|
2254
2420
|
} catch {
|
|
2255
2421
|
return "";
|
|
2256
2422
|
}
|
|
@@ -2269,8 +2435,8 @@ function readInstructionsBlock(cwd) {
|
|
|
2269
2435
|
// src/chat/modes/interactive.ts
|
|
2270
2436
|
init_issue();
|
|
2271
2437
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2272
|
-
import * as
|
|
2273
|
-
import * as
|
|
2438
|
+
import * as fs11 from "fs";
|
|
2439
|
+
import * as path11 from "path";
|
|
2274
2440
|
|
|
2275
2441
|
// src/chat/inbox.ts
|
|
2276
2442
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -2318,7 +2484,7 @@ async function waitForNextUserMessage(opts) {
|
|
|
2318
2484
|
}
|
|
2319
2485
|
}
|
|
2320
2486
|
function sleep(ms) {
|
|
2321
|
-
return new Promise((
|
|
2487
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
2322
2488
|
}
|
|
2323
2489
|
function currentBranch(cwd) {
|
|
2324
2490
|
try {
|
|
@@ -2429,9 +2595,9 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
2429
2595
|
return -1;
|
|
2430
2596
|
}
|
|
2431
2597
|
function commitTurn(cwd, sessionId, _verbose) {
|
|
2432
|
-
const sessionRel =
|
|
2433
|
-
const eventsRel =
|
|
2434
|
-
const rels = [sessionRel, eventsRel].filter((p) =>
|
|
2598
|
+
const sessionRel = path11.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
2599
|
+
const eventsRel = path11.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
2600
|
+
const rels = [sessionRel, eventsRel].filter((p) => fs11.existsSync(path11.join(cwd, p)));
|
|
2435
2601
|
if (rels.length === 0) return;
|
|
2436
2602
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
2437
2603
|
if (!repository) {
|
|
@@ -2443,8 +2609,8 @@ function commitTurn(cwd, sessionId, _verbose) {
|
|
|
2443
2609
|
}
|
|
2444
2610
|
const branch = defaultBranch(cwd) ?? "main";
|
|
2445
2611
|
for (const rel of rels) {
|
|
2446
|
-
const repoPath = rel.split(
|
|
2447
|
-
const localText =
|
|
2612
|
+
const repoPath = rel.split(path11.sep).join("/");
|
|
2613
|
+
const localText = fs11.readFileSync(path11.join(cwd, rel), "utf-8");
|
|
2448
2614
|
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
2449
2615
|
}
|
|
2450
2616
|
}
|
|
@@ -2533,12 +2699,12 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
2533
2699
|
}
|
|
2534
2700
|
|
|
2535
2701
|
// src/kody-cli.ts
|
|
2536
|
-
import { execFileSync as
|
|
2702
|
+
import { execFileSync as execFileSync29 } from "child_process";
|
|
2537
2703
|
import * as fs41 from "fs";
|
|
2538
|
-
import * as
|
|
2704
|
+
import * as path37 from "path";
|
|
2539
2705
|
|
|
2540
2706
|
// src/dispatch.ts
|
|
2541
|
-
import * as
|
|
2707
|
+
import * as fs12 from "fs";
|
|
2542
2708
|
|
|
2543
2709
|
// src/cron-match.ts
|
|
2544
2710
|
var FIELD_BOUNDS = [
|
|
@@ -2622,10 +2788,10 @@ function autoDispatch(opts) {
|
|
|
2622
2788
|
}
|
|
2623
2789
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
2624
2790
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2625
|
-
if (!eventName || !eventPath || !
|
|
2791
|
+
if (!eventName || !eventPath || !fs12.existsSync(eventPath)) return null;
|
|
2626
2792
|
let event = {};
|
|
2627
2793
|
try {
|
|
2628
|
-
event = JSON.parse(
|
|
2794
|
+
event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
|
|
2629
2795
|
} catch {
|
|
2630
2796
|
return null;
|
|
2631
2797
|
}
|
|
@@ -2699,7 +2865,7 @@ function autoDispatchTyped(opts) {
|
|
|
2699
2865
|
if (legacy) return { kind: "route", ...legacy };
|
|
2700
2866
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
2701
2867
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2702
|
-
if (!eventName || !eventPath || !
|
|
2868
|
+
if (!eventName || !eventPath || !fs12.existsSync(eventPath)) {
|
|
2703
2869
|
return { kind: "silent", reason: "no GHA event context" };
|
|
2704
2870
|
}
|
|
2705
2871
|
if (eventName !== "issue_comment") {
|
|
@@ -2707,7 +2873,7 @@ function autoDispatchTyped(opts) {
|
|
|
2707
2873
|
}
|
|
2708
2874
|
let event = {};
|
|
2709
2875
|
try {
|
|
2710
|
-
event = JSON.parse(
|
|
2876
|
+
event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
|
|
2711
2877
|
} catch {
|
|
2712
2878
|
return { kind: "silent", reason: "GHA event payload unreadable" };
|
|
2713
2879
|
}
|
|
@@ -2745,7 +2911,7 @@ function dispatchScheduledWatches(opts) {
|
|
|
2745
2911
|
for (const exe of listExecutables()) {
|
|
2746
2912
|
let raw;
|
|
2747
2913
|
try {
|
|
2748
|
-
raw =
|
|
2914
|
+
raw = fs12.readFileSync(exe.profilePath, "utf-8");
|
|
2749
2915
|
} catch {
|
|
2750
2916
|
continue;
|
|
2751
2917
|
}
|
|
@@ -2867,9 +3033,9 @@ function coerceBare(spec, value) {
|
|
|
2867
3033
|
init_issue();
|
|
2868
3034
|
|
|
2869
3035
|
// src/executor.ts
|
|
2870
|
-
import { execFileSync as
|
|
3036
|
+
import { execFileSync as execFileSync28, spawn as spawn9 } from "child_process";
|
|
2871
3037
|
import * as fs40 from "fs";
|
|
2872
|
-
import * as
|
|
3038
|
+
import * as path36 from "path";
|
|
2873
3039
|
|
|
2874
3040
|
// src/discipline.ts
|
|
2875
3041
|
var DISCIPLINE = `# Working discipline (applies to this entire task)
|
|
@@ -2920,8 +3086,8 @@ init_events();
|
|
|
2920
3086
|
init_issue();
|
|
2921
3087
|
|
|
2922
3088
|
// src/profile.ts
|
|
2923
|
-
import * as
|
|
2924
|
-
import * as
|
|
3089
|
+
import * as fs13 from "fs";
|
|
3090
|
+
import * as path12 from "path";
|
|
2925
3091
|
|
|
2926
3092
|
// src/profile-error.ts
|
|
2927
3093
|
var ProfileError = class extends Error {
|
|
@@ -3089,12 +3255,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
|
3089
3255
|
"preloadContext"
|
|
3090
3256
|
]);
|
|
3091
3257
|
function loadProfile(profilePath) {
|
|
3092
|
-
if (!
|
|
3258
|
+
if (!fs13.existsSync(profilePath)) {
|
|
3093
3259
|
throw new ProfileError(profilePath, "file not found");
|
|
3094
3260
|
}
|
|
3095
3261
|
let raw;
|
|
3096
3262
|
try {
|
|
3097
|
-
raw = JSON.parse(
|
|
3263
|
+
raw = JSON.parse(fs13.readFileSync(profilePath, "utf-8"));
|
|
3098
3264
|
} catch (err) {
|
|
3099
3265
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
3100
3266
|
}
|
|
@@ -3105,7 +3271,7 @@ function loadProfile(profilePath) {
|
|
|
3105
3271
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
3106
3272
|
if (unknownKeys.length > 0) {
|
|
3107
3273
|
process.stderr.write(
|
|
3108
|
-
`[kody profile] ${
|
|
3274
|
+
`[kody profile] ${path12.basename(path12.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
3109
3275
|
`
|
|
3110
3276
|
);
|
|
3111
3277
|
}
|
|
@@ -3166,7 +3332,7 @@ function loadProfile(profilePath) {
|
|
|
3166
3332
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
3167
3333
|
// flip to true after end-to-end verification.
|
|
3168
3334
|
preloadContext: r.preloadContext === true,
|
|
3169
|
-
dir:
|
|
3335
|
+
dir: path12.dirname(profilePath)
|
|
3170
3336
|
};
|
|
3171
3337
|
if (lifecycle) {
|
|
3172
3338
|
applyLifecycle(profile, profilePath);
|
|
@@ -3536,10 +3702,10 @@ function errMsg(err) {
|
|
|
3536
3702
|
}
|
|
3537
3703
|
|
|
3538
3704
|
// src/litellm.ts
|
|
3539
|
-
import { execFileSync as execFileSync4, spawn as
|
|
3540
|
-
import * as
|
|
3705
|
+
import { execFileSync as execFileSync4, spawn as spawn3 } from "child_process";
|
|
3706
|
+
import * as fs14 from "fs";
|
|
3541
3707
|
import * as os2 from "os";
|
|
3542
|
-
import * as
|
|
3708
|
+
import * as path13 from "path";
|
|
3543
3709
|
async function checkLitellmHealth(url) {
|
|
3544
3710
|
try {
|
|
3545
3711
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -3586,20 +3752,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3586
3752
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
3587
3753
|
}
|
|
3588
3754
|
}
|
|
3589
|
-
const configPath =
|
|
3590
|
-
|
|
3755
|
+
const configPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
3756
|
+
fs14.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
3591
3757
|
const portMatch = url.match(/:(\d+)/);
|
|
3592
3758
|
const port = portMatch ? portMatch[1] : "4000";
|
|
3593
3759
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
3594
3760
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
3595
|
-
const logPath =
|
|
3596
|
-
const outFd =
|
|
3597
|
-
const child =
|
|
3761
|
+
const logPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
3762
|
+
const outFd = fs14.openSync(logPath, "w");
|
|
3763
|
+
const child = spawn3(cmd, args, {
|
|
3598
3764
|
stdio: ["ignore", outFd, outFd],
|
|
3599
3765
|
detached: true,
|
|
3600
3766
|
env: stripBlockingEnv({ ...process.env, ...dotenvVars })
|
|
3601
3767
|
});
|
|
3602
|
-
|
|
3768
|
+
fs14.closeSync(outFd);
|
|
3603
3769
|
const timeoutMs = resolveLitellmTimeoutMs();
|
|
3604
3770
|
const deadline = Date.now() + timeoutMs;
|
|
3605
3771
|
while (Date.now() < deadline) {
|
|
@@ -3618,7 +3784,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3618
3784
|
}
|
|
3619
3785
|
let logTail = "";
|
|
3620
3786
|
try {
|
|
3621
|
-
logTail =
|
|
3787
|
+
logTail = fs14.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
3622
3788
|
} catch {
|
|
3623
3789
|
}
|
|
3624
3790
|
try {
|
|
@@ -3630,10 +3796,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3630
3796
|
${logTail}`);
|
|
3631
3797
|
}
|
|
3632
3798
|
function readDotenvApiKeys(projectDir) {
|
|
3633
|
-
const dotenvPath =
|
|
3634
|
-
if (!
|
|
3799
|
+
const dotenvPath = path13.join(projectDir, ".env");
|
|
3800
|
+
if (!fs14.existsSync(dotenvPath)) return {};
|
|
3635
3801
|
const result = {};
|
|
3636
|
-
for (const rawLine of
|
|
3802
|
+
for (const rawLine of fs14.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
3637
3803
|
const line = rawLine.trim();
|
|
3638
3804
|
if (!line || line.startsWith("#")) continue;
|
|
3639
3805
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -3656,25 +3822,25 @@ function stripBlockingEnv(env) {
|
|
|
3656
3822
|
}
|
|
3657
3823
|
|
|
3658
3824
|
// src/subagents.ts
|
|
3659
|
-
import * as
|
|
3660
|
-
import * as
|
|
3825
|
+
import * as fs16 from "fs";
|
|
3826
|
+
import * as path15 from "path";
|
|
3661
3827
|
|
|
3662
3828
|
// src/scripts/buildSyntheticPlugin.ts
|
|
3663
|
-
import * as
|
|
3829
|
+
import * as fs15 from "fs";
|
|
3664
3830
|
import * as os3 from "os";
|
|
3665
|
-
import * as
|
|
3831
|
+
import * as path14 from "path";
|
|
3666
3832
|
function getPluginsCatalogRoot() {
|
|
3667
|
-
const here =
|
|
3833
|
+
const here = path14.dirname(new URL(import.meta.url).pathname);
|
|
3668
3834
|
const candidates = [
|
|
3669
|
-
|
|
3835
|
+
path14.join(here, "..", "plugins"),
|
|
3670
3836
|
// dev: src/scripts → src/plugins
|
|
3671
|
-
|
|
3837
|
+
path14.join(here, "..", "..", "plugins"),
|
|
3672
3838
|
// built: dist/scripts → dist/plugins
|
|
3673
|
-
|
|
3839
|
+
path14.join(here, "..", "..", "src", "plugins")
|
|
3674
3840
|
// fallback
|
|
3675
3841
|
];
|
|
3676
3842
|
for (const c of candidates) {
|
|
3677
|
-
if (
|
|
3843
|
+
if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
|
|
3678
3844
|
}
|
|
3679
3845
|
return candidates[0];
|
|
3680
3846
|
}
|
|
@@ -3684,45 +3850,45 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
3684
3850
|
if (!needsSynthetic) return;
|
|
3685
3851
|
const catalog = getPluginsCatalogRoot();
|
|
3686
3852
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3687
|
-
const root =
|
|
3688
|
-
|
|
3853
|
+
const root = path14.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
3854
|
+
fs15.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
|
|
3689
3855
|
const resolvePart = (bucket, entry) => {
|
|
3690
|
-
const local =
|
|
3691
|
-
if (
|
|
3692
|
-
const central =
|
|
3693
|
-
if (
|
|
3856
|
+
const local = path14.join(profile.dir, bucket, entry);
|
|
3857
|
+
if (fs15.existsSync(local)) return local;
|
|
3858
|
+
const central = path14.join(catalog, bucket, entry);
|
|
3859
|
+
if (fs15.existsSync(central)) return central;
|
|
3694
3860
|
throw new Error(
|
|
3695
3861
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
3696
3862
|
);
|
|
3697
3863
|
};
|
|
3698
3864
|
if (cc.skills.length > 0) {
|
|
3699
|
-
const dst =
|
|
3700
|
-
|
|
3865
|
+
const dst = path14.join(root, "skills");
|
|
3866
|
+
fs15.mkdirSync(dst, { recursive: true });
|
|
3701
3867
|
for (const name of cc.skills) {
|
|
3702
|
-
copyDir(resolvePart("skills", name),
|
|
3868
|
+
copyDir(resolvePart("skills", name), path14.join(dst, name));
|
|
3703
3869
|
}
|
|
3704
3870
|
}
|
|
3705
3871
|
if (cc.commands.length > 0) {
|
|
3706
|
-
const dst =
|
|
3707
|
-
|
|
3872
|
+
const dst = path14.join(root, "commands");
|
|
3873
|
+
fs15.mkdirSync(dst, { recursive: true });
|
|
3708
3874
|
for (const name of cc.commands) {
|
|
3709
|
-
|
|
3875
|
+
fs15.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
|
|
3710
3876
|
}
|
|
3711
3877
|
}
|
|
3712
3878
|
if (cc.hooks.length > 0) {
|
|
3713
|
-
const dst =
|
|
3714
|
-
|
|
3879
|
+
const dst = path14.join(root, "hooks");
|
|
3880
|
+
fs15.mkdirSync(dst, { recursive: true });
|
|
3715
3881
|
const merged = { hooks: {} };
|
|
3716
3882
|
for (const name of cc.hooks) {
|
|
3717
3883
|
const src = resolvePart("hooks", `${name}.json`);
|
|
3718
|
-
const parsed = JSON.parse(
|
|
3884
|
+
const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
|
|
3719
3885
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
3720
3886
|
if (!Array.isArray(entries)) continue;
|
|
3721
3887
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
3722
3888
|
merged.hooks[event].push(...entries);
|
|
3723
3889
|
}
|
|
3724
3890
|
}
|
|
3725
|
-
|
|
3891
|
+
fs15.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
3726
3892
|
`);
|
|
3727
3893
|
}
|
|
3728
3894
|
const manifest = {
|
|
@@ -3732,17 +3898,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
3732
3898
|
};
|
|
3733
3899
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
3734
3900
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
3735
|
-
|
|
3901
|
+
fs15.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
3736
3902
|
`);
|
|
3737
3903
|
ctx.data.syntheticPluginPath = root;
|
|
3738
3904
|
};
|
|
3739
3905
|
function copyDir(src, dst) {
|
|
3740
|
-
|
|
3741
|
-
for (const ent of
|
|
3742
|
-
const s =
|
|
3743
|
-
const d =
|
|
3906
|
+
fs15.mkdirSync(dst, { recursive: true });
|
|
3907
|
+
for (const ent of fs15.readdirSync(src, { withFileTypes: true })) {
|
|
3908
|
+
const s = path14.join(src, ent.name);
|
|
3909
|
+
const d = path14.join(dst, ent.name);
|
|
3744
3910
|
if (ent.isDirectory()) copyDir(s, d);
|
|
3745
|
-
else if (ent.isFile())
|
|
3911
|
+
else if (ent.isFile()) fs15.copyFileSync(s, d);
|
|
3746
3912
|
}
|
|
3747
3913
|
}
|
|
3748
3914
|
|
|
@@ -3759,10 +3925,10 @@ function splitFrontmatter(raw) {
|
|
|
3759
3925
|
return { fm, body: (match[2] ?? "").trim() };
|
|
3760
3926
|
}
|
|
3761
3927
|
function resolveAgentFile(profileDir, name) {
|
|
3762
|
-
const local =
|
|
3763
|
-
if (
|
|
3764
|
-
const central =
|
|
3765
|
-
if (
|
|
3928
|
+
const local = path15.join(profileDir, "agents", `${name}.md`);
|
|
3929
|
+
if (fs16.existsSync(local)) return local;
|
|
3930
|
+
const central = path15.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
3931
|
+
if (fs16.existsSync(central)) return central;
|
|
3766
3932
|
throw new Error(
|
|
3767
3933
|
`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`
|
|
3768
3934
|
);
|
|
@@ -3772,7 +3938,7 @@ function loadSubagents(profile) {
|
|
|
3772
3938
|
if (!names || names.length === 0) return void 0;
|
|
3773
3939
|
const agents = {};
|
|
3774
3940
|
for (const name of names) {
|
|
3775
|
-
const { fm, body } = splitFrontmatter(
|
|
3941
|
+
const { fm, body } = splitFrontmatter(fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
|
|
3776
3942
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
3777
3943
|
const def = {
|
|
3778
3944
|
description: fm.description ?? `Subagent ${name}`,
|
|
@@ -3868,8 +4034,8 @@ function pushWithRetry(opts = {}) {
|
|
|
3868
4034
|
}
|
|
3869
4035
|
|
|
3870
4036
|
// src/commit.ts
|
|
3871
|
-
import * as
|
|
3872
|
-
import * as
|
|
4037
|
+
import * as fs17 from "fs";
|
|
4038
|
+
import * as path16 from "path";
|
|
3873
4039
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
3874
4040
|
".kody/",
|
|
3875
4041
|
".kody-engine/",
|
|
@@ -3925,18 +4091,18 @@ function tryGit(args, cwd) {
|
|
|
3925
4091
|
}
|
|
3926
4092
|
function abortUnfinishedGitOps(cwd) {
|
|
3927
4093
|
const aborted = [];
|
|
3928
|
-
const gitDir =
|
|
3929
|
-
if (!
|
|
3930
|
-
if (
|
|
4094
|
+
const gitDir = path16.join(cwd ?? process.cwd(), ".git");
|
|
4095
|
+
if (!fs17.existsSync(gitDir)) return aborted;
|
|
4096
|
+
if (fs17.existsSync(path16.join(gitDir, "MERGE_HEAD"))) {
|
|
3931
4097
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
3932
4098
|
}
|
|
3933
|
-
if (
|
|
4099
|
+
if (fs17.existsSync(path16.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
3934
4100
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
3935
4101
|
}
|
|
3936
|
-
if (
|
|
4102
|
+
if (fs17.existsSync(path16.join(gitDir, "REVERT_HEAD"))) {
|
|
3937
4103
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
3938
4104
|
}
|
|
3939
|
-
if (
|
|
4105
|
+
if (fs17.existsSync(path16.join(gitDir, "rebase-merge")) || fs17.existsSync(path16.join(gitDir, "rebase-apply"))) {
|
|
3940
4106
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
3941
4107
|
}
|
|
3942
4108
|
try {
|
|
@@ -3992,7 +4158,7 @@ function normalizeCommitMessage(raw) {
|
|
|
3992
4158
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
3993
4159
|
const allChanged = listChangedFiles(cwd);
|
|
3994
4160
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
3995
|
-
const mergeHeadExists =
|
|
4161
|
+
const mergeHeadExists = fs17.existsSync(path16.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
3996
4162
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
3997
4163
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
3998
4164
|
}
|
|
@@ -4290,22 +4456,22 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
4290
4456
|
};
|
|
4291
4457
|
|
|
4292
4458
|
// src/scripts/brainServe.ts
|
|
4459
|
+
init_repoWorkspace();
|
|
4293
4460
|
import { createServer } from "http";
|
|
4294
|
-
import
|
|
4295
|
-
import * as
|
|
4296
|
-
import * as path17 from "path";
|
|
4461
|
+
import * as fs19 from "fs";
|
|
4462
|
+
import * as path18 from "path";
|
|
4297
4463
|
|
|
4298
4464
|
// src/scripts/brainTurnLog.ts
|
|
4299
|
-
import * as
|
|
4300
|
-
import * as
|
|
4465
|
+
import * as fs18 from "fs";
|
|
4466
|
+
import * as path17 from "path";
|
|
4301
4467
|
var live = /* @__PURE__ */ new Map();
|
|
4302
4468
|
function eventsPath(dir, chatId) {
|
|
4303
|
-
return
|
|
4469
|
+
return path17.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
4304
4470
|
}
|
|
4305
4471
|
function lastPersistedSeq(dir, chatId) {
|
|
4306
4472
|
const p = eventsPath(dir, chatId);
|
|
4307
|
-
if (!
|
|
4308
|
-
const lines =
|
|
4473
|
+
if (!fs18.existsSync(p)) return 0;
|
|
4474
|
+
const lines = fs18.readFileSync(p, "utf-8").split("\n").filter(Boolean);
|
|
4309
4475
|
if (lines.length === 0) return 0;
|
|
4310
4476
|
try {
|
|
4311
4477
|
return JSON.parse(lines[lines.length - 1]).seq || 0;
|
|
@@ -4315,9 +4481,9 @@ function lastPersistedSeq(dir, chatId) {
|
|
|
4315
4481
|
}
|
|
4316
4482
|
function readSince(dir, chatId, since) {
|
|
4317
4483
|
const p = eventsPath(dir, chatId);
|
|
4318
|
-
if (!
|
|
4484
|
+
if (!fs18.existsSync(p)) return [];
|
|
4319
4485
|
const out = [];
|
|
4320
|
-
for (const line of
|
|
4486
|
+
for (const line of fs18.readFileSync(p, "utf-8").split("\n")) {
|
|
4321
4487
|
if (!line) continue;
|
|
4322
4488
|
try {
|
|
4323
4489
|
const rec = JSON.parse(line);
|
|
@@ -4343,12 +4509,12 @@ function beginTurn(dir, chatId) {
|
|
|
4343
4509
|
};
|
|
4344
4510
|
live.set(chatId, state);
|
|
4345
4511
|
const p = eventsPath(dir, chatId);
|
|
4346
|
-
|
|
4512
|
+
fs18.mkdirSync(path17.dirname(p), { recursive: true });
|
|
4347
4513
|
return (event) => {
|
|
4348
4514
|
state.seq += 1;
|
|
4349
4515
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
4350
4516
|
try {
|
|
4351
|
-
|
|
4517
|
+
fs18.appendFileSync(p, JSON.stringify(rec) + "\n");
|
|
4352
4518
|
} catch (err) {
|
|
4353
4519
|
process.stderr.write(
|
|
4354
4520
|
`[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -4386,7 +4552,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
|
|
|
4386
4552
|
event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
|
|
4387
4553
|
};
|
|
4388
4554
|
try {
|
|
4389
|
-
|
|
4555
|
+
fs18.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
|
|
4390
4556
|
} catch {
|
|
4391
4557
|
}
|
|
4392
4558
|
state.status = "ended";
|
|
@@ -4474,17 +4640,17 @@ function authOk(req, expected) {
|
|
|
4474
4640
|
return false;
|
|
4475
4641
|
}
|
|
4476
4642
|
function readJsonBody(req) {
|
|
4477
|
-
return new Promise((
|
|
4643
|
+
return new Promise((resolve6, reject) => {
|
|
4478
4644
|
const chunks = [];
|
|
4479
4645
|
req.on("data", (c) => chunks.push(c));
|
|
4480
4646
|
req.on("end", () => {
|
|
4481
4647
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
4482
4648
|
if (!raw.trim()) {
|
|
4483
|
-
|
|
4649
|
+
resolve6({});
|
|
4484
4650
|
return;
|
|
4485
4651
|
}
|
|
4486
4652
|
try {
|
|
4487
|
-
|
|
4653
|
+
resolve6(JSON.parse(raw));
|
|
4488
4654
|
} catch (err) {
|
|
4489
4655
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4490
4656
|
}
|
|
@@ -4596,51 +4762,6 @@ function streamToRes(res, dir, chatId, since) {
|
|
|
4596
4762
|
);
|
|
4597
4763
|
res.on("close", unsubscribe);
|
|
4598
4764
|
}
|
|
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
|
-
};
|
|
4644
4765
|
async function handleChatTurn(req, res, chatId, opts) {
|
|
4645
4766
|
let body;
|
|
4646
4767
|
try {
|
|
@@ -4657,7 +4778,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4657
4778
|
const repo = strField(body, "repo");
|
|
4658
4779
|
const repoToken = strField(body, "repoToken");
|
|
4659
4780
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
4660
|
-
|
|
4781
|
+
fs19.mkdirSync(path18.dirname(sessionFile), { recursive: true });
|
|
4661
4782
|
appendTurn(sessionFile, {
|
|
4662
4783
|
role: "user",
|
|
4663
4784
|
content: message,
|
|
@@ -4681,7 +4802,10 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4681
4802
|
cwd: agentCwd,
|
|
4682
4803
|
model: opts.model,
|
|
4683
4804
|
litellmUrl: opts.litellmUrl,
|
|
4684
|
-
sink
|
|
4805
|
+
sink,
|
|
4806
|
+
// Let the agent clone + work on OTHER repos via the fetch_repo tool.
|
|
4807
|
+
reposRoot: opts.reposRoot,
|
|
4808
|
+
repoToken
|
|
4685
4809
|
});
|
|
4686
4810
|
} catch (err) {
|
|
4687
4811
|
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
@@ -4701,7 +4825,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4701
4825
|
function buildServer(opts) {
|
|
4702
4826
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
4703
4827
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
4704
|
-
const reposRoot = opts.reposRoot ??
|
|
4828
|
+
const reposRoot = opts.reposRoot ?? path18.join(path18.dirname(path18.resolve(opts.cwd)), "repos");
|
|
4705
4829
|
return createServer(async (req, res) => {
|
|
4706
4830
|
if (!req.method || !req.url) {
|
|
4707
4831
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -4782,13 +4906,13 @@ var brainServe = async (ctx) => {
|
|
|
4782
4906
|
model,
|
|
4783
4907
|
litellmUrl
|
|
4784
4908
|
});
|
|
4785
|
-
await new Promise((
|
|
4909
|
+
await new Promise((resolve6) => {
|
|
4786
4910
|
server.listen(port, "0.0.0.0", () => {
|
|
4787
4911
|
process.stdout.write(
|
|
4788
4912
|
`[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
|
|
4789
4913
|
`
|
|
4790
4914
|
);
|
|
4791
|
-
|
|
4915
|
+
resolve6();
|
|
4792
4916
|
});
|
|
4793
4917
|
});
|
|
4794
4918
|
const shutdown = (signal) => {
|
|
@@ -4956,13 +5080,13 @@ function defaultLabelMap() {
|
|
|
4956
5080
|
}
|
|
4957
5081
|
|
|
4958
5082
|
// src/scripts/commitAndPush.ts
|
|
4959
|
-
import * as
|
|
4960
|
-
import * as
|
|
5083
|
+
import * as fs21 from "fs";
|
|
5084
|
+
import * as path20 from "path";
|
|
4961
5085
|
init_events();
|
|
4962
5086
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
4963
5087
|
function sentinelPathForStage(cwd, profileName) {
|
|
4964
5088
|
const runId = resolveRunId();
|
|
4965
|
-
return
|
|
5089
|
+
return path20.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
4966
5090
|
}
|
|
4967
5091
|
var commitAndPush2 = async (ctx, profile) => {
|
|
4968
5092
|
const branch = ctx.data.branch;
|
|
@@ -4972,9 +5096,9 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
4972
5096
|
}
|
|
4973
5097
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
4974
5098
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
4975
|
-
if (sentinel &&
|
|
5099
|
+
if (sentinel && fs21.existsSync(sentinel)) {
|
|
4976
5100
|
try {
|
|
4977
|
-
const replay = JSON.parse(
|
|
5101
|
+
const replay = JSON.parse(fs21.readFileSync(sentinel, "utf-8"));
|
|
4978
5102
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
4979
5103
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
4980
5104
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -5027,8 +5151,8 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
5027
5151
|
const result = ctx.data.commitResult;
|
|
5028
5152
|
if (sentinel && result?.committed) {
|
|
5029
5153
|
try {
|
|
5030
|
-
|
|
5031
|
-
|
|
5154
|
+
fs21.mkdirSync(path20.dirname(sentinel), { recursive: true });
|
|
5155
|
+
fs21.writeFileSync(
|
|
5032
5156
|
sentinel,
|
|
5033
5157
|
JSON.stringify(
|
|
5034
5158
|
{
|
|
@@ -5047,43 +5171,181 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
5047
5171
|
}
|
|
5048
5172
|
};
|
|
5049
5173
|
|
|
5050
|
-
// src/
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5174
|
+
// src/goal/stateStore.ts
|
|
5175
|
+
init_issue();
|
|
5176
|
+
|
|
5177
|
+
// src/stateBranch.ts
|
|
5178
|
+
init_issue();
|
|
5179
|
+
var STATE_BRANCH = "kody-state";
|
|
5180
|
+
function is404(err) {
|
|
5181
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5182
|
+
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5183
|
+
}
|
|
5184
|
+
function ensureStateBranch(owner, repo, cwd) {
|
|
5057
5185
|
try {
|
|
5058
|
-
|
|
5186
|
+
gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
|
|
5187
|
+
return;
|
|
5059
5188
|
} catch (err) {
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
`
|
|
5189
|
+
if (!is404(err)) throw err;
|
|
5190
|
+
}
|
|
5191
|
+
const repoInfo = JSON.parse(gh(["api", `/repos/${owner}/${repo}`], { cwd }));
|
|
5192
|
+
const defaultBranch2 = repoInfo.default_branch;
|
|
5193
|
+
if (!defaultBranch2) {
|
|
5194
|
+
throw new Error(`ensureStateBranch: could not resolve default branch for ${owner}/${repo}`);
|
|
5195
|
+
}
|
|
5196
|
+
const headRef = JSON.parse(
|
|
5197
|
+
gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${defaultBranch2}`], { cwd })
|
|
5198
|
+
);
|
|
5199
|
+
const sha = headRef.object?.sha;
|
|
5200
|
+
if (!sha) {
|
|
5201
|
+
throw new Error(`ensureStateBranch: could not resolve head sha for ${owner}/${repo}@${defaultBranch2}`);
|
|
5202
|
+
}
|
|
5203
|
+
try {
|
|
5204
|
+
gh(["api", "--method", "POST", `/repos/${owner}/${repo}/git/refs`, "--input", "-"], {
|
|
5205
|
+
cwd,
|
|
5206
|
+
input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
|
|
5207
|
+
});
|
|
5208
|
+
} catch (err) {
|
|
5209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5210
|
+
if (/already exists/i.test(msg) || /HTTP 422/i.test(msg)) return;
|
|
5211
|
+
throw err;
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
// src/goal/state.ts
|
|
5216
|
+
import * as fs22 from "fs";
|
|
5217
|
+
import * as path21 from "path";
|
|
5218
|
+
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
5219
|
+
var GoalStateError = class extends Error {
|
|
5220
|
+
constructor(path39, message) {
|
|
5221
|
+
super(`Invalid goal state at ${path39}:
|
|
5222
|
+
${message}`);
|
|
5223
|
+
this.path = path39;
|
|
5224
|
+
this.name = "GoalStateError";
|
|
5225
|
+
}
|
|
5226
|
+
path;
|
|
5227
|
+
};
|
|
5228
|
+
function parseGoalState(filePath, raw) {
|
|
5229
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5230
|
+
throw new GoalStateError(filePath, "must be a JSON object");
|
|
5231
|
+
}
|
|
5232
|
+
const r = raw;
|
|
5233
|
+
const stateValue = r.state;
|
|
5234
|
+
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5235
|
+
throw new GoalStateError(
|
|
5236
|
+
filePath,
|
|
5237
|
+
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5063
5238
|
);
|
|
5064
|
-
return;
|
|
5065
5239
|
}
|
|
5240
|
+
const parsed = {
|
|
5241
|
+
state: stateValue,
|
|
5242
|
+
extra: {}
|
|
5243
|
+
};
|
|
5244
|
+
if (typeof r.mergeApproved === "boolean") {
|
|
5245
|
+
parsed.mergeApproved = r.mergeApproved;
|
|
5246
|
+
}
|
|
5247
|
+
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
5248
|
+
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
5249
|
+
}
|
|
5250
|
+
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
5251
|
+
const v = r[ts];
|
|
5252
|
+
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5253
|
+
}
|
|
5254
|
+
const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
|
|
5255
|
+
for (const [k, v] of Object.entries(r)) {
|
|
5256
|
+
if (!known.has(k)) parsed.extra[k] = v;
|
|
5257
|
+
}
|
|
5258
|
+
return parsed;
|
|
5259
|
+
}
|
|
5260
|
+
function serializeGoalState(s) {
|
|
5261
|
+
const obj = { ...s.extra, state: s.state };
|
|
5262
|
+
if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
|
|
5263
|
+
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
5264
|
+
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5265
|
+
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5266
|
+
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5267
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
5268
|
+
`;
|
|
5269
|
+
}
|
|
5270
|
+
function nowIso() {
|
|
5271
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5272
|
+
}
|
|
5273
|
+
|
|
5274
|
+
// src/goal/stateStore.ts
|
|
5275
|
+
function statePath(goalId) {
|
|
5276
|
+
return `.kody/goals/${goalId}/state.json`;
|
|
5277
|
+
}
|
|
5278
|
+
function is4042(err) {
|
|
5279
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5280
|
+
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5281
|
+
}
|
|
5282
|
+
function fetchGoalState(owner, repo, goalId, cwd) {
|
|
5283
|
+
const filePath = statePath(goalId);
|
|
5284
|
+
let raw;
|
|
5066
5285
|
try {
|
|
5067
|
-
|
|
5286
|
+
raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5287
|
+
} catch (err) {
|
|
5288
|
+
if (is4042(err)) return null;
|
|
5289
|
+
throw err;
|
|
5290
|
+
}
|
|
5291
|
+
const o = JSON.parse(raw);
|
|
5292
|
+
if (!o.content) return null;
|
|
5293
|
+
const decoded = Buffer.from(o.content, "base64").toString("utf-8");
|
|
5294
|
+
return parseGoalState(filePath, JSON.parse(decoded));
|
|
5295
|
+
}
|
|
5296
|
+
function putGoalState(owner, repo, goalId, state, message, cwd) {
|
|
5297
|
+
ensureStateBranch(owner, repo, cwd);
|
|
5298
|
+
const filePath = statePath(goalId);
|
|
5299
|
+
const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
|
|
5300
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
5301
|
+
let sha;
|
|
5302
|
+
try {
|
|
5303
|
+
const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5304
|
+
const o = JSON.parse(cur);
|
|
5305
|
+
if (o.sha) sha = o.sha;
|
|
5306
|
+
} catch (err) {
|
|
5307
|
+
if (!is4042(err)) throw err;
|
|
5308
|
+
}
|
|
5309
|
+
const payload = { message, content, branch: STATE_BRANCH };
|
|
5310
|
+
if (sha) payload.sha = sha;
|
|
5311
|
+
try {
|
|
5312
|
+
gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
|
|
5313
|
+
cwd,
|
|
5314
|
+
input: JSON.stringify(payload)
|
|
5315
|
+
});
|
|
5316
|
+
return;
|
|
5317
|
+
} catch (err) {
|
|
5318
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5319
|
+
const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
5320
|
+
if (!conflict || attempt === 3) throw err;
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
}
|
|
5324
|
+
|
|
5325
|
+
// src/scripts/commitGoalState.ts
|
|
5326
|
+
var commitGoalState = async (ctx) => {
|
|
5327
|
+
const goal = ctx.data.goal;
|
|
5328
|
+
if (!goal) return;
|
|
5329
|
+
if (ctx.data.goalPersistChanged !== true) return;
|
|
5330
|
+
const updated = ctx.data.goalPersistState;
|
|
5331
|
+
if (!updated) return;
|
|
5332
|
+
const owner = ctx.config.github?.owner;
|
|
5333
|
+
const repo = ctx.config.github?.repo;
|
|
5334
|
+
if (!owner || !repo) {
|
|
5335
|
+
process.stderr.write(`[goal-tick] commitGoalState: missing github owner/repo; cannot persist ${goal.id}
|
|
5336
|
+
`);
|
|
5068
5337
|
return;
|
|
5069
|
-
} catch {
|
|
5070
5338
|
}
|
|
5071
|
-
const msg = describeCommitMessage(goal);
|
|
5072
5339
|
try {
|
|
5073
|
-
|
|
5340
|
+
putGoalState(owner, repo, goal.id, updated, describeCommitMessage(goal), ctx.cwd);
|
|
5074
5341
|
} catch (err) {
|
|
5075
5342
|
process.stderr.write(
|
|
5076
|
-
`[goal-tick] commitGoalState:
|
|
5077
|
-
`
|
|
5078
|
-
);
|
|
5079
|
-
return;
|
|
5080
|
-
}
|
|
5081
|
-
const result = pushWithRetry({ cwd: ctx.cwd });
|
|
5082
|
-
if (!result.ok) {
|
|
5083
|
-
process.stderr.write(`[goal-tick] commitGoalState: push failed (${result.reason}); will retry next tick
|
|
5084
|
-
`);
|
|
5343
|
+
`[goal-tick] commitGoalState: persist to ${STATE_BRANCH_LABEL} failed (${err instanceof Error ? err.message : String(err)}); will retry next tick
|
|
5344
|
+
`
|
|
5345
|
+
);
|
|
5085
5346
|
}
|
|
5086
5347
|
};
|
|
5348
|
+
var STATE_BRANCH_LABEL = "kody-state";
|
|
5087
5349
|
function describeCommitMessage(goal) {
|
|
5088
5350
|
if (goal.state === "closed") return `chore(goals): abandon ${goal.id} (cleanup complete)`;
|
|
5089
5351
|
if (goal.state === "awaiting-merge") return `chore(goals): park ${goal.id} awaiting merge`;
|
|
@@ -5098,20 +5360,20 @@ function describeCommitMessage(goal) {
|
|
|
5098
5360
|
}
|
|
5099
5361
|
|
|
5100
5362
|
// src/scripts/composePrompt.ts
|
|
5101
|
-
import * as
|
|
5102
|
-
import * as
|
|
5363
|
+
import * as fs23 from "fs";
|
|
5364
|
+
import * as path22 from "path";
|
|
5103
5365
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
5104
5366
|
var composePrompt = async (ctx, profile) => {
|
|
5105
5367
|
const explicit = ctx.data.promptTemplate;
|
|
5106
5368
|
const mode = ctx.args.mode;
|
|
5107
5369
|
const candidates = [
|
|
5108
|
-
explicit ?
|
|
5109
|
-
mode ?
|
|
5110
|
-
|
|
5370
|
+
explicit ? path22.join(profile.dir, explicit) : null,
|
|
5371
|
+
mode ? path22.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
5372
|
+
path22.join(profile.dir, "prompt.md")
|
|
5111
5373
|
].filter(Boolean);
|
|
5112
5374
|
let templatePath = "";
|
|
5113
5375
|
for (const c of candidates) {
|
|
5114
|
-
if (
|
|
5376
|
+
if (fs23.existsSync(c)) {
|
|
5115
5377
|
templatePath = c;
|
|
5116
5378
|
break;
|
|
5117
5379
|
}
|
|
@@ -5119,7 +5381,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
5119
5381
|
if (!templatePath) {
|
|
5120
5382
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
5121
5383
|
}
|
|
5122
|
-
const template =
|
|
5384
|
+
const template = fs23.readFileSync(templatePath, "utf-8");
|
|
5123
5385
|
const tokens = {
|
|
5124
5386
|
...stringifyAll(ctx.args, "args."),
|
|
5125
5387
|
...stringifyAll(ctx.data, ""),
|
|
@@ -5197,9 +5459,6 @@ function formatToolsUsage(profile) {
|
|
|
5197
5459
|
|
|
5198
5460
|
// src/scripts/createQaGoal.ts
|
|
5199
5461
|
init_issue();
|
|
5200
|
-
import { execFileSync as execFileSync11 } from "child_process";
|
|
5201
|
-
import * as fs22 from "fs";
|
|
5202
|
-
import * as path22 from "path";
|
|
5203
5462
|
|
|
5204
5463
|
// src/scripts/postReviewResult.ts
|
|
5205
5464
|
init_issue();
|
|
@@ -5451,104 +5710,6 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
|
5451
5710
|
if (!m) throw new Error(`gh issue create returned unexpected output: ${out}`);
|
|
5452
5711
|
return { number: Number(m[1]), created: true };
|
|
5453
5712
|
}
|
|
5454
|
-
function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
5455
|
-
const dir = path22.join(cwd, ".kody", "goals", goalId);
|
|
5456
|
-
fs22.mkdirSync(dir, { recursive: true });
|
|
5457
|
-
const state = {
|
|
5458
|
-
version: 1,
|
|
5459
|
-
state: "active",
|
|
5460
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5461
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5462
|
-
...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
|
|
5463
|
-
};
|
|
5464
|
-
const filePath = path22.join(dir, "state.json");
|
|
5465
|
-
fs22.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
|
|
5466
|
-
`);
|
|
5467
|
-
return filePath;
|
|
5468
|
-
}
|
|
5469
|
-
function gitTry(args, cwd) {
|
|
5470
|
-
const env = { ...process.env, SKIP_HOOKS: "1", HUSKY: "0" };
|
|
5471
|
-
try {
|
|
5472
|
-
execFileSync11("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], env });
|
|
5473
|
-
return { ok: true, stderr: "" };
|
|
5474
|
-
} catch (err) {
|
|
5475
|
-
const e = err;
|
|
5476
|
-
const stderr = typeof e?.stderr === "string" ? e.stderr : Buffer.isBuffer(e?.stderr) ? e.stderr.toString("utf8") : e?.message ?? "";
|
|
5477
|
-
return { ok: false, stderr: stderr.trim() };
|
|
5478
|
-
}
|
|
5479
|
-
}
|
|
5480
|
-
function commitAndPushState(filePath, goalId, cwd) {
|
|
5481
|
-
const add = gitTry(["add", filePath], cwd);
|
|
5482
|
-
if (!add.ok) {
|
|
5483
|
-
process.stderr.write(`[createQaGoal] git add failed: ${add.stderr.slice(-400) || "(no stderr)"}
|
|
5484
|
-
`);
|
|
5485
|
-
return;
|
|
5486
|
-
}
|
|
5487
|
-
const diff = gitTry(["diff", "--cached", "--quiet"], cwd);
|
|
5488
|
-
if (diff.ok) {
|
|
5489
|
-
process.stderr.write(`[createQaGoal] state.json unchanged \u2014 nothing to commit
|
|
5490
|
-
`);
|
|
5491
|
-
return;
|
|
5492
|
-
}
|
|
5493
|
-
const commit = gitTry(["commit", "-m", `chore(goals): activate ${goalId}`, "--quiet"], cwd);
|
|
5494
|
-
if (!commit.ok) {
|
|
5495
|
-
process.stderr.write(`[createQaGoal] git commit failed: ${commit.stderr.slice(-400) || "(no stderr)"}
|
|
5496
|
-
`);
|
|
5497
|
-
return;
|
|
5498
|
-
}
|
|
5499
|
-
const push = gitTry(["push", "--quiet"], cwd);
|
|
5500
|
-
if (push.ok) return;
|
|
5501
|
-
const stderr = push.stderr;
|
|
5502
|
-
const tail = stderr.slice(-400) || "(no stderr captured)";
|
|
5503
|
-
if (/non-fast-forward|rejected|fetch first|behind/i.test(stderr)) {
|
|
5504
|
-
process.stderr.write(`[createQaGoal] push rejected (non-fast-forward) \u2014 pulling --rebase and retrying
|
|
5505
|
-
`);
|
|
5506
|
-
const rebase = gitTry(["pull", "--rebase", "--autostash", "--quiet"], cwd);
|
|
5507
|
-
if (!rebase.ok) {
|
|
5508
|
-
process.stderr.write(
|
|
5509
|
-
`[createQaGoal] rebase failed (manual recovery required): ${rebase.stderr.slice(-400) || "(no stderr)"}
|
|
5510
|
-
`
|
|
5511
|
-
);
|
|
5512
|
-
return;
|
|
5513
|
-
}
|
|
5514
|
-
const retryPush = gitTry(["push", "--quiet"], cwd);
|
|
5515
|
-
if (retryPush.ok) {
|
|
5516
|
-
process.stderr.write(`[createQaGoal] push succeeded after rebase
|
|
5517
|
-
`);
|
|
5518
|
-
return;
|
|
5519
|
-
}
|
|
5520
|
-
process.stderr.write(
|
|
5521
|
-
`[createQaGoal] push still failed after rebase: ${retryPush.stderr.slice(-400) || "(no stderr)"}
|
|
5522
|
-
`
|
|
5523
|
-
);
|
|
5524
|
-
return;
|
|
5525
|
-
}
|
|
5526
|
-
if (/pre-push|hook|husky/i.test(stderr)) {
|
|
5527
|
-
process.stderr.write(`[createQaGoal] push rejected by pre-push hook \u2014 retrying with --no-verify
|
|
5528
|
-
`);
|
|
5529
|
-
process.stderr.write(`[createQaGoal] hook output:
|
|
5530
|
-
${tail}
|
|
5531
|
-
`);
|
|
5532
|
-
const noVerify = gitTry(["push", "--no-verify", "--quiet"], cwd);
|
|
5533
|
-
if (noVerify.ok) {
|
|
5534
|
-
process.stderr.write(`[createQaGoal] push succeeded with --no-verify (consider adding kody artifacts to ignore configs)
|
|
5535
|
-
`);
|
|
5536
|
-
return;
|
|
5537
|
-
}
|
|
5538
|
-
process.stderr.write(
|
|
5539
|
-
`[createQaGoal] --no-verify push also failed: ${noVerify.stderr.slice(-400) || "(no stderr)"}
|
|
5540
|
-
`
|
|
5541
|
-
);
|
|
5542
|
-
return;
|
|
5543
|
-
}
|
|
5544
|
-
process.stderr.write(
|
|
5545
|
-
`[createQaGoal] state.json commit landed but push failed.
|
|
5546
|
-
[createQaGoal] The goal will not be visible to goal-scheduler in CI until you run 'git push' manually.
|
|
5547
|
-
[createQaGoal] git stderr:
|
|
5548
|
-
${tail}
|
|
5549
|
-
`
|
|
5550
|
-
);
|
|
5551
|
-
}
|
|
5552
5713
|
function createTaskIssue(finding, goalId, manifestNumber, cwd) {
|
|
5553
5714
|
const labels = [`goal:${goalId}`, severityLabel(finding.severity), FINDING_LABEL];
|
|
5554
5715
|
ensureLabel(`goal:${goalId}`, "1d76db", `goal: ${goalId}`, cwd);
|
|
@@ -5718,8 +5879,17 @@ ${markdown}`,
|
|
|
5718
5879
|
`);
|
|
5719
5880
|
}
|
|
5720
5881
|
}
|
|
5721
|
-
const
|
|
5722
|
-
|
|
5882
|
+
const now = nowIso();
|
|
5883
|
+
const goalState = { state: "active", startedAt: now, updatedAt: now, extra: { version: 1 } };
|
|
5884
|
+
try {
|
|
5885
|
+
putGoalState(ctx.config.github.owner, ctx.config.github.repo, goalId, goalState, `chore(goals): activate ${goalId}`, ctx.cwd);
|
|
5886
|
+
} catch (err) {
|
|
5887
|
+
process.stderr.write(
|
|
5888
|
+
`[createQaGoal] failed to persist goal state to kody-state: ${err instanceof Error ? err.message : String(err)}
|
|
5889
|
+
[createQaGoal] goal-scheduler will not see ${goalId} until this succeeds.
|
|
5890
|
+
`
|
|
5891
|
+
);
|
|
5892
|
+
}
|
|
5723
5893
|
const repoUrl = `https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}`;
|
|
5724
5894
|
if (manifestIssueNumber !== null) {
|
|
5725
5895
|
const verb = manifestUpdated ? manifestCreated ? "OPENED" : "UPDATED" : "TARGETED";
|
|
@@ -5988,8 +6158,8 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
|
5988
6158
|
}
|
|
5989
6159
|
|
|
5990
6160
|
// src/scripts/diagMcp.ts
|
|
5991
|
-
import { execFileSync as
|
|
5992
|
-
import * as
|
|
6161
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
6162
|
+
import * as fs24 from "fs";
|
|
5993
6163
|
import * as os4 from "os";
|
|
5994
6164
|
import * as path23 from "path";
|
|
5995
6165
|
var diagMcp = async (_ctx) => {
|
|
@@ -5997,7 +6167,7 @@ var diagMcp = async (_ctx) => {
|
|
|
5997
6167
|
const cacheDir = path23.join(home, ".cache", "ms-playwright");
|
|
5998
6168
|
let entries = [];
|
|
5999
6169
|
try {
|
|
6000
|
-
entries =
|
|
6170
|
+
entries = fs24.readdirSync(cacheDir);
|
|
6001
6171
|
} catch {
|
|
6002
6172
|
}
|
|
6003
6173
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -6008,7 +6178,7 @@ var diagMcp = async (_ctx) => {
|
|
|
6008
6178
|
process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
|
|
6009
6179
|
`);
|
|
6010
6180
|
try {
|
|
6011
|
-
const v =
|
|
6181
|
+
const v = execFileSync10("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
|
|
6012
6182
|
stdio: "pipe",
|
|
6013
6183
|
timeout: 6e4,
|
|
6014
6184
|
encoding: "utf8"
|
|
@@ -6023,17 +6193,17 @@ var diagMcp = async (_ctx) => {
|
|
|
6023
6193
|
};
|
|
6024
6194
|
|
|
6025
6195
|
// src/scripts/discoverQaContext.ts
|
|
6026
|
-
import * as
|
|
6196
|
+
import * as fs26 from "fs";
|
|
6027
6197
|
import * as path25 from "path";
|
|
6028
6198
|
|
|
6029
6199
|
// src/scripts/frameworkDetectors.ts
|
|
6030
|
-
import * as
|
|
6200
|
+
import * as fs25 from "fs";
|
|
6031
6201
|
import * as path24 from "path";
|
|
6032
6202
|
function detectFrameworks(cwd) {
|
|
6033
6203
|
const out = [];
|
|
6034
6204
|
let deps = {};
|
|
6035
6205
|
try {
|
|
6036
|
-
const pkg = JSON.parse(
|
|
6206
|
+
const pkg = JSON.parse(fs25.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
|
|
6037
6207
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6038
6208
|
} catch {
|
|
6039
6209
|
return out;
|
|
@@ -6070,7 +6240,7 @@ function detectFrameworks(cwd) {
|
|
|
6070
6240
|
}
|
|
6071
6241
|
function findFile(cwd, candidates) {
|
|
6072
6242
|
for (const c of candidates) {
|
|
6073
|
-
if (
|
|
6243
|
+
if (fs25.existsSync(path24.join(cwd, c))) return c;
|
|
6074
6244
|
}
|
|
6075
6245
|
return null;
|
|
6076
6246
|
}
|
|
@@ -6084,17 +6254,17 @@ function discoverPayloadCollections(cwd) {
|
|
|
6084
6254
|
const out = [];
|
|
6085
6255
|
for (const dir of COLLECTION_DIRS) {
|
|
6086
6256
|
const full = path24.join(cwd, dir);
|
|
6087
|
-
if (!
|
|
6257
|
+
if (!fs25.existsSync(full)) continue;
|
|
6088
6258
|
let files;
|
|
6089
6259
|
try {
|
|
6090
|
-
files =
|
|
6260
|
+
files = fs25.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
6091
6261
|
} catch {
|
|
6092
6262
|
continue;
|
|
6093
6263
|
}
|
|
6094
6264
|
for (const file of files) {
|
|
6095
6265
|
try {
|
|
6096
6266
|
const filePath = path24.join(full, file);
|
|
6097
|
-
const content =
|
|
6267
|
+
const content = fs25.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
6098
6268
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
6099
6269
|
if (!slugMatch) continue;
|
|
6100
6270
|
const slug = slugMatch[1];
|
|
@@ -6123,10 +6293,10 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6123
6293
|
const out = [];
|
|
6124
6294
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
6125
6295
|
const full = path24.join(cwd, dir);
|
|
6126
|
-
if (!
|
|
6296
|
+
if (!fs25.existsSync(full)) continue;
|
|
6127
6297
|
let entries;
|
|
6128
6298
|
try {
|
|
6129
|
-
entries =
|
|
6299
|
+
entries = fs25.readdirSync(full, { withFileTypes: true });
|
|
6130
6300
|
} catch {
|
|
6131
6301
|
continue;
|
|
6132
6302
|
}
|
|
@@ -6136,7 +6306,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6136
6306
|
let filePath;
|
|
6137
6307
|
if (entry.isDirectory()) {
|
|
6138
6308
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
6139
|
-
(f) =>
|
|
6309
|
+
(f) => fs25.existsSync(path24.join(entryPath, f))
|
|
6140
6310
|
);
|
|
6141
6311
|
if (!indexFile) continue;
|
|
6142
6312
|
name = entry.name;
|
|
@@ -6151,7 +6321,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6151
6321
|
if (collections) {
|
|
6152
6322
|
for (const col of collections) {
|
|
6153
6323
|
try {
|
|
6154
|
-
const colContent =
|
|
6324
|
+
const colContent = fs25.readFileSync(path24.join(cwd, col.filePath), "utf-8");
|
|
6155
6325
|
if (colContent.includes(name)) {
|
|
6156
6326
|
usedInCollection = col.slug;
|
|
6157
6327
|
break;
|
|
@@ -6171,7 +6341,7 @@ function scanApiRoutes(cwd) {
|
|
|
6171
6341
|
const appDirs = ["src/app", "app"];
|
|
6172
6342
|
for (const appDir of appDirs) {
|
|
6173
6343
|
const apiDir = path24.join(cwd, appDir, "api");
|
|
6174
|
-
if (!
|
|
6344
|
+
if (!fs25.existsSync(apiDir)) continue;
|
|
6175
6345
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
6176
6346
|
break;
|
|
6177
6347
|
}
|
|
@@ -6180,14 +6350,14 @@ function scanApiRoutes(cwd) {
|
|
|
6180
6350
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
6181
6351
|
let entries;
|
|
6182
6352
|
try {
|
|
6183
|
-
entries =
|
|
6353
|
+
entries = fs25.readdirSync(dir, { withFileTypes: true });
|
|
6184
6354
|
} catch {
|
|
6185
6355
|
return;
|
|
6186
6356
|
}
|
|
6187
6357
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
6188
6358
|
if (routeFile) {
|
|
6189
6359
|
try {
|
|
6190
|
-
const content =
|
|
6360
|
+
const content = fs25.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
6191
6361
|
const methods = HTTP_METHODS.filter(
|
|
6192
6362
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
6193
6363
|
);
|
|
@@ -6235,9 +6405,9 @@ function scanEnvVars(cwd) {
|
|
|
6235
6405
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
6236
6406
|
for (const envFile of candidates) {
|
|
6237
6407
|
const envPath = path24.join(cwd, envFile);
|
|
6238
|
-
if (!
|
|
6408
|
+
if (!fs25.existsSync(envPath)) continue;
|
|
6239
6409
|
try {
|
|
6240
|
-
const content =
|
|
6410
|
+
const content = fs25.readFileSync(envPath, "utf-8");
|
|
6241
6411
|
const vars = [];
|
|
6242
6412
|
for (const line of content.split("\n")) {
|
|
6243
6413
|
const trimmed = line.trim();
|
|
@@ -6285,9 +6455,9 @@ function runQaDiscovery(cwd) {
|
|
|
6285
6455
|
}
|
|
6286
6456
|
function detectDevServer(cwd, out) {
|
|
6287
6457
|
try {
|
|
6288
|
-
const pkg = JSON.parse(
|
|
6458
|
+
const pkg = JSON.parse(fs26.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
|
|
6289
6459
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6290
|
-
const pm =
|
|
6460
|
+
const pm = fs26.existsSync(path25.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs26.existsSync(path25.join(cwd, "yarn.lock")) ? "yarn" : fs26.existsSync(path25.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
6291
6461
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
6292
6462
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
6293
6463
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -6298,7 +6468,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
6298
6468
|
const appDirs = ["src/app", "app"];
|
|
6299
6469
|
for (const appDir of appDirs) {
|
|
6300
6470
|
const full = path25.join(cwd, appDir);
|
|
6301
|
-
if (!
|
|
6471
|
+
if (!fs26.existsSync(full)) continue;
|
|
6302
6472
|
walkFrontendRoutes(full, "", out);
|
|
6303
6473
|
break;
|
|
6304
6474
|
}
|
|
@@ -6306,7 +6476,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
6306
6476
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
6307
6477
|
let entries;
|
|
6308
6478
|
try {
|
|
6309
|
-
entries =
|
|
6479
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
6310
6480
|
} catch {
|
|
6311
6481
|
return;
|
|
6312
6482
|
}
|
|
@@ -6348,23 +6518,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
6348
6518
|
"src/app/api/oauth"
|
|
6349
6519
|
];
|
|
6350
6520
|
for (const c of candidates) {
|
|
6351
|
-
if (
|
|
6521
|
+
if (fs26.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
|
|
6352
6522
|
}
|
|
6353
6523
|
}
|
|
6354
6524
|
function detectRoles(cwd, out) {
|
|
6355
6525
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
6356
6526
|
for (const rp of rolePaths) {
|
|
6357
6527
|
const dir = path25.join(cwd, rp);
|
|
6358
|
-
if (!
|
|
6528
|
+
if (!fs26.existsSync(dir)) continue;
|
|
6359
6529
|
let files;
|
|
6360
6530
|
try {
|
|
6361
|
-
files =
|
|
6531
|
+
files = fs26.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
6362
6532
|
} catch {
|
|
6363
6533
|
continue;
|
|
6364
6534
|
}
|
|
6365
6535
|
for (const f of files) {
|
|
6366
6536
|
try {
|
|
6367
|
-
const content =
|
|
6537
|
+
const content = fs26.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
|
|
6368
6538
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
6369
6539
|
if (roleMatches) {
|
|
6370
6540
|
for (const m of roleMatches) {
|
|
@@ -6448,7 +6618,7 @@ var discoverQaContext = async (ctx) => {
|
|
|
6448
6618
|
};
|
|
6449
6619
|
|
|
6450
6620
|
// src/scripts/dispatch.ts
|
|
6451
|
-
import { execFileSync as
|
|
6621
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
6452
6622
|
var API_TIMEOUT_MS4 = 3e4;
|
|
6453
6623
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
6454
6624
|
const next = args?.next;
|
|
@@ -6484,7 +6654,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
6484
6654
|
const sub = usePr ? "pr" : "issue";
|
|
6485
6655
|
const body = `@kody ${next}`;
|
|
6486
6656
|
try {
|
|
6487
|
-
|
|
6657
|
+
execFileSync11("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
6488
6658
|
timeout: API_TIMEOUT_MS4,
|
|
6489
6659
|
cwd: ctx.cwd,
|
|
6490
6660
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6504,7 +6674,7 @@ function parsePr(url) {
|
|
|
6504
6674
|
}
|
|
6505
6675
|
|
|
6506
6676
|
// src/scripts/dispatchClassified.ts
|
|
6507
|
-
import { execFileSync as
|
|
6677
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
6508
6678
|
var API_TIMEOUT_MS5 = 3e4;
|
|
6509
6679
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
6510
6680
|
var dispatchClassified = async (ctx) => {
|
|
@@ -6528,7 +6698,7 @@ ${auditLine}
|
|
|
6528
6698
|
|
|
6529
6699
|
${stateBody}`;
|
|
6530
6700
|
try {
|
|
6531
|
-
|
|
6701
|
+
execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
6532
6702
|
cwd: ctx.cwd,
|
|
6533
6703
|
timeout: API_TIMEOUT_MS5,
|
|
6534
6704
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6548,7 +6718,7 @@ function failedAction3(reason) {
|
|
|
6548
6718
|
}
|
|
6549
6719
|
|
|
6550
6720
|
// src/scripts/dispatchJobFileTicks.ts
|
|
6551
|
-
import * as
|
|
6721
|
+
import * as fs28 from "fs";
|
|
6552
6722
|
import * as path27 from "path";
|
|
6553
6723
|
|
|
6554
6724
|
// src/scripts/jobFrontmatter.ts
|
|
@@ -6642,44 +6812,6 @@ function stripQuotes(value) {
|
|
|
6642
6812
|
// src/scripts/jobState/contentsApiBackend.ts
|
|
6643
6813
|
init_issue();
|
|
6644
6814
|
|
|
6645
|
-
// src/stateBranch.ts
|
|
6646
|
-
init_issue();
|
|
6647
|
-
var STATE_BRANCH = "kody-state";
|
|
6648
|
-
function is404(err) {
|
|
6649
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6650
|
-
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
6651
|
-
}
|
|
6652
|
-
function ensureStateBranch(owner, repo, cwd) {
|
|
6653
|
-
try {
|
|
6654
|
-
gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
|
|
6655
|
-
return;
|
|
6656
|
-
} catch (err) {
|
|
6657
|
-
if (!is404(err)) throw err;
|
|
6658
|
-
}
|
|
6659
|
-
const repoInfo = JSON.parse(gh(["api", `/repos/${owner}/${repo}`], { cwd }));
|
|
6660
|
-
const defaultBranch2 = repoInfo.default_branch;
|
|
6661
|
-
if (!defaultBranch2) {
|
|
6662
|
-
throw new Error(`ensureStateBranch: could not resolve default branch for ${owner}/${repo}`);
|
|
6663
|
-
}
|
|
6664
|
-
const headRef = JSON.parse(
|
|
6665
|
-
gh(["api", `/repos/${owner}/${repo}/git/ref/heads/${defaultBranch2}`], { cwd })
|
|
6666
|
-
);
|
|
6667
|
-
const sha = headRef.object?.sha;
|
|
6668
|
-
if (!sha) {
|
|
6669
|
-
throw new Error(`ensureStateBranch: could not resolve head sha for ${owner}/${repo}@${defaultBranch2}`);
|
|
6670
|
-
}
|
|
6671
|
-
try {
|
|
6672
|
-
gh(["api", "--method", "POST", `/repos/${owner}/${repo}/git/refs`, "--input", "-"], {
|
|
6673
|
-
cwd,
|
|
6674
|
-
input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
|
|
6675
|
-
});
|
|
6676
|
-
} catch (err) {
|
|
6677
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6678
|
-
if (/already exists/i.test(msg) || /HTTP 422/i.test(msg)) return;
|
|
6679
|
-
throw err;
|
|
6680
|
-
}
|
|
6681
|
-
}
|
|
6682
|
-
|
|
6683
6815
|
// src/scripts/issueStateComment.ts
|
|
6684
6816
|
init_issue();
|
|
6685
6817
|
function isStateEnvelope(x) {
|
|
@@ -6856,7 +6988,7 @@ var ContentsApiBackend = class {
|
|
|
6856
6988
|
};
|
|
6857
6989
|
|
|
6858
6990
|
// src/scripts/jobState/localFileBackend.ts
|
|
6859
|
-
import * as
|
|
6991
|
+
import * as fs27 from "fs";
|
|
6860
6992
|
import * as path26 from "path";
|
|
6861
6993
|
var LocalFileBackend = class {
|
|
6862
6994
|
name = "local-file";
|
|
@@ -6887,7 +7019,7 @@ var LocalFileBackend = class {
|
|
|
6887
7019
|
`);
|
|
6888
7020
|
return;
|
|
6889
7021
|
}
|
|
6890
|
-
|
|
7022
|
+
fs27.mkdirSync(this.absDir, { recursive: true });
|
|
6891
7023
|
const prefix = this.cacheKeyPrefix();
|
|
6892
7024
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
6893
7025
|
try {
|
|
@@ -6916,7 +7048,7 @@ var LocalFileBackend = class {
|
|
|
6916
7048
|
`);
|
|
6917
7049
|
return;
|
|
6918
7050
|
}
|
|
6919
|
-
if (!
|
|
7051
|
+
if (!fs27.existsSync(this.absDir)) {
|
|
6920
7052
|
return;
|
|
6921
7053
|
}
|
|
6922
7054
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -6933,10 +7065,10 @@ var LocalFileBackend = class {
|
|
|
6933
7065
|
load(slug) {
|
|
6934
7066
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
6935
7067
|
const absPath = path26.join(this.cwd, relPath);
|
|
6936
|
-
if (!
|
|
7068
|
+
if (!fs27.existsSync(absPath)) {
|
|
6937
7069
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
6938
7070
|
}
|
|
6939
|
-
const raw =
|
|
7071
|
+
const raw = fs27.readFileSync(absPath, "utf-8");
|
|
6940
7072
|
let parsed;
|
|
6941
7073
|
try {
|
|
6942
7074
|
parsed = JSON.parse(raw);
|
|
@@ -6954,9 +7086,9 @@ var LocalFileBackend = class {
|
|
|
6954
7086
|
return false;
|
|
6955
7087
|
}
|
|
6956
7088
|
const absPath = path26.join(this.cwd, loaded.path);
|
|
6957
|
-
|
|
7089
|
+
fs27.mkdirSync(path26.dirname(absPath), { recursive: true });
|
|
6958
7090
|
const body = JSON.stringify(next, null, 2) + "\n";
|
|
6959
|
-
|
|
7091
|
+
fs27.writeFileSync(absPath, body, "utf-8");
|
|
6960
7092
|
return true;
|
|
6961
7093
|
}
|
|
6962
7094
|
cacheKeyPrefix() {
|
|
@@ -7147,17 +7279,17 @@ function formatAgo(ms) {
|
|
|
7147
7279
|
}
|
|
7148
7280
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
7149
7281
|
try {
|
|
7150
|
-
const raw =
|
|
7282
|
+
const raw = fs28.readFileSync(path27.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
7151
7283
|
return splitFrontmatter2(raw).frontmatter;
|
|
7152
7284
|
} catch {
|
|
7153
7285
|
return {};
|
|
7154
7286
|
}
|
|
7155
7287
|
}
|
|
7156
7288
|
function listJobSlugs(absDir) {
|
|
7157
|
-
if (!
|
|
7289
|
+
if (!fs28.existsSync(absDir)) return [];
|
|
7158
7290
|
let entries;
|
|
7159
7291
|
try {
|
|
7160
|
-
entries =
|
|
7292
|
+
entries = fs28.readdirSync(absDir, { withFileTypes: true });
|
|
7161
7293
|
} catch {
|
|
7162
7294
|
return [];
|
|
7163
7295
|
}
|
|
@@ -7684,7 +7816,7 @@ var finalizeTerminal = async (ctx) => {
|
|
|
7684
7816
|
|
|
7685
7817
|
// src/scripts/finishFlow.ts
|
|
7686
7818
|
init_issue();
|
|
7687
|
-
import { execFileSync as
|
|
7819
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
7688
7820
|
var TERMINAL_PHASE = {
|
|
7689
7821
|
"review-passed": { phase: "shipped", status: "succeeded" },
|
|
7690
7822
|
"fix-applied": { phase: "shipped", status: "succeeded" },
|
|
@@ -7724,7 +7856,7 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
|
|
|
7724
7856
|
**PR:** ${state.core.prUrl}` : "";
|
|
7725
7857
|
const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
7726
7858
|
try {
|
|
7727
|
-
|
|
7859
|
+
execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
7728
7860
|
timeout: API_TIMEOUT_MS6,
|
|
7729
7861
|
cwd: ctx.cwd,
|
|
7730
7862
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7754,9 +7886,9 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
|
|
|
7754
7886
|
};
|
|
7755
7887
|
|
|
7756
7888
|
// src/branch.ts
|
|
7757
|
-
import { execFileSync as
|
|
7889
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
7758
7890
|
function git2(args, cwd) {
|
|
7759
|
-
return
|
|
7891
|
+
return execFileSync14("git", args, {
|
|
7760
7892
|
encoding: "utf-8",
|
|
7761
7893
|
timeout: 3e4,
|
|
7762
7894
|
cwd,
|
|
@@ -7773,11 +7905,11 @@ function getCurrentBranch(cwd) {
|
|
|
7773
7905
|
}
|
|
7774
7906
|
function resetWorkingTree(cwd) {
|
|
7775
7907
|
try {
|
|
7776
|
-
|
|
7908
|
+
execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
7777
7909
|
} catch {
|
|
7778
7910
|
}
|
|
7779
7911
|
try {
|
|
7780
|
-
|
|
7912
|
+
execFileSync14("git", ["clean", "-fd"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
7781
7913
|
} catch {
|
|
7782
7914
|
}
|
|
7783
7915
|
}
|
|
@@ -7789,14 +7921,14 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
7789
7921
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
7790
7922
|
};
|
|
7791
7923
|
try {
|
|
7792
|
-
|
|
7924
|
+
execFileSync14("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
7793
7925
|
} catch {
|
|
7794
7926
|
}
|
|
7795
7927
|
try {
|
|
7796
|
-
|
|
7928
|
+
execFileSync14("git", ["clean", "-fd"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
7797
7929
|
} catch {
|
|
7798
7930
|
}
|
|
7799
|
-
|
|
7931
|
+
execFileSync14("gh", ["pr", "checkout", String(prNumber)], {
|
|
7800
7932
|
cwd,
|
|
7801
7933
|
env,
|
|
7802
7934
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -7922,8 +8054,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
|
|
|
7922
8054
|
}
|
|
7923
8055
|
|
|
7924
8056
|
// src/gha.ts
|
|
7925
|
-
import { execFileSync as
|
|
7926
|
-
import * as
|
|
8057
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
8058
|
+
import * as fs29 from "fs";
|
|
7927
8059
|
function getRunUrl() {
|
|
7928
8060
|
const server = process.env.GITHUB_SERVER_URL;
|
|
7929
8061
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -7934,10 +8066,10 @@ function getRunUrl() {
|
|
|
7934
8066
|
function reactToTriggerComment(cwd) {
|
|
7935
8067
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
7936
8068
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
7937
|
-
if (!eventPath || !
|
|
8069
|
+
if (!eventPath || !fs29.existsSync(eventPath)) return;
|
|
7938
8070
|
let event = null;
|
|
7939
8071
|
try {
|
|
7940
|
-
event = JSON.parse(
|
|
8072
|
+
event = JSON.parse(fs29.readFileSync(eventPath, "utf-8"));
|
|
7941
8073
|
} catch {
|
|
7942
8074
|
return;
|
|
7943
8075
|
}
|
|
@@ -7965,7 +8097,7 @@ function reactToTriggerComment(cwd) {
|
|
|
7965
8097
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
7966
8098
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
7967
8099
|
try {
|
|
7968
|
-
|
|
8100
|
+
execFileSync15("gh", args, opts);
|
|
7969
8101
|
return;
|
|
7970
8102
|
} catch (err) {
|
|
7971
8103
|
lastErr = err;
|
|
@@ -7978,7 +8110,7 @@ function reactToTriggerComment(cwd) {
|
|
|
7978
8110
|
}
|
|
7979
8111
|
function sleepMs(ms) {
|
|
7980
8112
|
try {
|
|
7981
|
-
|
|
8113
|
+
execFileSync15("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
7982
8114
|
} catch {
|
|
7983
8115
|
}
|
|
7984
8116
|
}
|
|
@@ -7987,7 +8119,7 @@ function sleepMs(ms) {
|
|
|
7987
8119
|
init_issue();
|
|
7988
8120
|
|
|
7989
8121
|
// src/workflow.ts
|
|
7990
|
-
import { execFileSync as
|
|
8122
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
7991
8123
|
var GH_TIMEOUT_MS = 3e4;
|
|
7992
8124
|
function ghToken3() {
|
|
7993
8125
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -7995,7 +8127,7 @@ function ghToken3() {
|
|
|
7995
8127
|
function gh3(args, cwd) {
|
|
7996
8128
|
const token = ghToken3();
|
|
7997
8129
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
7998
|
-
return
|
|
8130
|
+
return execFileSync16("gh", args, {
|
|
7999
8131
|
encoding: "utf-8",
|
|
8000
8132
|
timeout: GH_TIMEOUT_MS,
|
|
8001
8133
|
cwd,
|
|
@@ -8230,13 +8362,13 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
8230
8362
|
};
|
|
8231
8363
|
|
|
8232
8364
|
// src/scripts/initFlow.ts
|
|
8233
|
-
import { execFileSync as
|
|
8234
|
-
import * as
|
|
8365
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
8366
|
+
import * as fs30 from "fs";
|
|
8235
8367
|
import * as path28 from "path";
|
|
8236
8368
|
function detectPackageManager(cwd) {
|
|
8237
|
-
if (
|
|
8238
|
-
if (
|
|
8239
|
-
if (
|
|
8369
|
+
if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
8370
|
+
if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
|
|
8371
|
+
if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
|
|
8240
8372
|
return "npm";
|
|
8241
8373
|
}
|
|
8242
8374
|
function qualityCommandsFor(pm) {
|
|
@@ -8256,7 +8388,7 @@ function schemaUrlFromPkg() {
|
|
|
8256
8388
|
function detectOwnerRepo(cwd) {
|
|
8257
8389
|
let url;
|
|
8258
8390
|
try {
|
|
8259
|
-
url =
|
|
8391
|
+
url = execFileSync17("git", ["remote", "get-url", "origin"], {
|
|
8260
8392
|
cwd,
|
|
8261
8393
|
encoding: "utf-8",
|
|
8262
8394
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8341,7 +8473,7 @@ jobs:
|
|
|
8341
8473
|
`;
|
|
8342
8474
|
function defaultBranchFromGit(cwd) {
|
|
8343
8475
|
try {
|
|
8344
|
-
const ref =
|
|
8476
|
+
const ref = execFileSync17("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
8345
8477
|
cwd,
|
|
8346
8478
|
encoding: "utf-8",
|
|
8347
8479
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8349,7 +8481,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
8349
8481
|
return ref.replace("refs/remotes/origin/", "");
|
|
8350
8482
|
} catch {
|
|
8351
8483
|
try {
|
|
8352
|
-
return
|
|
8484
|
+
return execFileSync17("git", ["branch", "--show-current"], {
|
|
8353
8485
|
cwd,
|
|
8354
8486
|
encoding: "utf-8",
|
|
8355
8487
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8366,35 +8498,35 @@ function performInit(cwd, force) {
|
|
|
8366
8498
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
8367
8499
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
8368
8500
|
const configPath = path28.join(cwd, "kody.config.json");
|
|
8369
|
-
if (
|
|
8501
|
+
if (fs30.existsSync(configPath) && !force) {
|
|
8370
8502
|
skipped.push("kody.config.json");
|
|
8371
8503
|
} else {
|
|
8372
8504
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
|
|
8373
|
-
|
|
8505
|
+
fs30.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
8374
8506
|
`);
|
|
8375
8507
|
wrote.push("kody.config.json");
|
|
8376
8508
|
}
|
|
8377
8509
|
const workflowDir = path28.join(cwd, ".github", "workflows");
|
|
8378
8510
|
const workflowPath = path28.join(workflowDir, "kody.yml");
|
|
8379
|
-
if (
|
|
8511
|
+
if (fs30.existsSync(workflowPath) && !force) {
|
|
8380
8512
|
skipped.push(".github/workflows/kody.yml");
|
|
8381
8513
|
} else {
|
|
8382
|
-
|
|
8383
|
-
|
|
8514
|
+
fs30.mkdirSync(workflowDir, { recursive: true });
|
|
8515
|
+
fs30.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
8384
8516
|
wrote.push(".github/workflows/kody.yml");
|
|
8385
8517
|
}
|
|
8386
8518
|
const builtinJobs = listBuiltinJobs();
|
|
8387
8519
|
if (builtinJobs.length > 0) {
|
|
8388
8520
|
const jobsDir = path28.join(cwd, ".kody", "duties");
|
|
8389
|
-
|
|
8521
|
+
fs30.mkdirSync(jobsDir, { recursive: true });
|
|
8390
8522
|
for (const job of builtinJobs) {
|
|
8391
8523
|
const rel = path28.join(".kody", "duties", `${job.slug}.md`);
|
|
8392
8524
|
const target = path28.join(cwd, rel);
|
|
8393
|
-
if (
|
|
8525
|
+
if (fs30.existsSync(target) && !force) {
|
|
8394
8526
|
skipped.push(rel);
|
|
8395
8527
|
continue;
|
|
8396
8528
|
}
|
|
8397
|
-
|
|
8529
|
+
fs30.writeFileSync(target, fs30.readFileSync(job.filePath, "utf-8"));
|
|
8398
8530
|
wrote.push(rel);
|
|
8399
8531
|
}
|
|
8400
8532
|
}
|
|
@@ -8407,11 +8539,11 @@ function performInit(cwd, force) {
|
|
|
8407
8539
|
}
|
|
8408
8540
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
8409
8541
|
const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
|
|
8410
|
-
if (
|
|
8542
|
+
if (fs30.existsSync(target) && !force) {
|
|
8411
8543
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
8412
8544
|
continue;
|
|
8413
8545
|
}
|
|
8414
|
-
|
|
8546
|
+
fs30.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
8415
8547
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
8416
8548
|
}
|
|
8417
8549
|
let labels;
|
|
@@ -8488,86 +8620,6 @@ Nothing to do. All files already present. (Use --force to overwrite.)
|
|
|
8488
8620
|
init_loadConventions();
|
|
8489
8621
|
init_loadCoverageRules();
|
|
8490
8622
|
|
|
8491
|
-
// src/goal/state.ts
|
|
8492
|
-
import * as fs30 from "fs";
|
|
8493
|
-
import * as path29 from "path";
|
|
8494
|
-
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
8495
|
-
var GoalStateError = class extends Error {
|
|
8496
|
-
constructor(path40, message) {
|
|
8497
|
-
super(`Invalid goal state at ${path40}:
|
|
8498
|
-
${message}`);
|
|
8499
|
-
this.path = path40;
|
|
8500
|
-
this.name = "GoalStateError";
|
|
8501
|
-
}
|
|
8502
|
-
path;
|
|
8503
|
-
};
|
|
8504
|
-
function parseGoalState(filePath, raw) {
|
|
8505
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
8506
|
-
throw new GoalStateError(filePath, "must be a JSON object");
|
|
8507
|
-
}
|
|
8508
|
-
const r = raw;
|
|
8509
|
-
const stateValue = r.state;
|
|
8510
|
-
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
8511
|
-
throw new GoalStateError(
|
|
8512
|
-
filePath,
|
|
8513
|
-
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
8514
|
-
);
|
|
8515
|
-
}
|
|
8516
|
-
const parsed = {
|
|
8517
|
-
state: stateValue,
|
|
8518
|
-
extra: {}
|
|
8519
|
-
};
|
|
8520
|
-
if (typeof r.mergeApproved === "boolean") {
|
|
8521
|
-
parsed.mergeApproved = r.mergeApproved;
|
|
8522
|
-
}
|
|
8523
|
-
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
8524
|
-
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
8525
|
-
}
|
|
8526
|
-
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
8527
|
-
const v = r[ts];
|
|
8528
|
-
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
8529
|
-
}
|
|
8530
|
-
const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
|
|
8531
|
-
for (const [k, v] of Object.entries(r)) {
|
|
8532
|
-
if (!known.has(k)) parsed.extra[k] = v;
|
|
8533
|
-
}
|
|
8534
|
-
return parsed;
|
|
8535
|
-
}
|
|
8536
|
-
function serializeGoalState(s) {
|
|
8537
|
-
const obj = { ...s.extra, state: s.state };
|
|
8538
|
-
if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
|
|
8539
|
-
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
8540
|
-
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
8541
|
-
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
8542
|
-
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
8543
|
-
return `${JSON.stringify(obj, null, 2)}
|
|
8544
|
-
`;
|
|
8545
|
-
}
|
|
8546
|
-
function goalStatePath(cwd, goalId) {
|
|
8547
|
-
return path29.join(cwd, ".kody", "goals", goalId, "state.json");
|
|
8548
|
-
}
|
|
8549
|
-
function readGoalState(cwd, goalId) {
|
|
8550
|
-
const file = goalStatePath(cwd, goalId);
|
|
8551
|
-
if (!fs30.existsSync(file)) {
|
|
8552
|
-
throw new GoalStateError(file, "file not found");
|
|
8553
|
-
}
|
|
8554
|
-
let raw;
|
|
8555
|
-
try {
|
|
8556
|
-
raw = JSON.parse(fs30.readFileSync(file, "utf-8"));
|
|
8557
|
-
} catch (err) {
|
|
8558
|
-
throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
8559
|
-
}
|
|
8560
|
-
return parseGoalState(file, raw);
|
|
8561
|
-
}
|
|
8562
|
-
function writeGoalState(cwd, goalId, state) {
|
|
8563
|
-
const file = goalStatePath(cwd, goalId);
|
|
8564
|
-
fs30.mkdirSync(path29.dirname(file), { recursive: true });
|
|
8565
|
-
fs30.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
8566
|
-
}
|
|
8567
|
-
function nowIso() {
|
|
8568
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8569
|
-
}
|
|
8570
|
-
|
|
8571
8623
|
// src/scripts/loadGoalState.ts
|
|
8572
8624
|
var loadGoalState = async (ctx) => {
|
|
8573
8625
|
const goalId = ctx.args.goal;
|
|
@@ -8583,8 +8635,24 @@ var loadGoalState = async (ctx) => {
|
|
|
8583
8635
|
ctx.output.reason = "invalid goal id (no slashes or '..' allowed)";
|
|
8584
8636
|
return;
|
|
8585
8637
|
}
|
|
8638
|
+
const owner = ctx.config.github?.owner;
|
|
8639
|
+
const repo = ctx.config.github?.repo;
|
|
8640
|
+
if (!owner || !repo) {
|
|
8641
|
+
ctx.skipAgent = true;
|
|
8642
|
+
ctx.output.exitCode = 1;
|
|
8643
|
+
ctx.output.reason = "missing github owner/repo in config";
|
|
8644
|
+
return;
|
|
8645
|
+
}
|
|
8586
8646
|
try {
|
|
8587
|
-
const state =
|
|
8647
|
+
const state = fetchGoalState(owner, repo, goalId, ctx.cwd);
|
|
8648
|
+
if (!state) {
|
|
8649
|
+
process.stdout.write(`[goal-tick] no goal state for ${goalId} on ${owner}/${repo} \u2014 nothing to tick
|
|
8650
|
+
`);
|
|
8651
|
+
ctx.skipAgent = true;
|
|
8652
|
+
ctx.output.exitCode = 0;
|
|
8653
|
+
ctx.output.reason = "no goal state to tick";
|
|
8654
|
+
return;
|
|
8655
|
+
}
|
|
8588
8656
|
ctx.data.goal = {
|
|
8589
8657
|
id: goalId,
|
|
8590
8658
|
state: state.state,
|
|
@@ -8660,7 +8728,7 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
8660
8728
|
|
|
8661
8729
|
// src/scripts/loadJobFromFile.ts
|
|
8662
8730
|
import * as fs31 from "fs";
|
|
8663
|
-
import * as
|
|
8731
|
+
import * as path29 from "path";
|
|
8664
8732
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
8665
8733
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
8666
8734
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
@@ -8669,7 +8737,7 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8669
8737
|
if (!slug) {
|
|
8670
8738
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
8671
8739
|
}
|
|
8672
|
-
const absPath =
|
|
8740
|
+
const absPath = path29.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
8673
8741
|
if (!fs31.existsSync(absPath)) {
|
|
8674
8742
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
8675
8743
|
}
|
|
@@ -8681,7 +8749,7 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8681
8749
|
let workerTitle = "";
|
|
8682
8750
|
let workerPersona = "";
|
|
8683
8751
|
if (workerSlug) {
|
|
8684
|
-
const workerPath =
|
|
8752
|
+
const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8685
8753
|
if (!fs31.existsSync(workerPath)) {
|
|
8686
8754
|
throw new Error(
|
|
8687
8755
|
`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`
|
|
@@ -8731,14 +8799,14 @@ init_loadPriorArt();
|
|
|
8731
8799
|
|
|
8732
8800
|
// src/scripts/loadQaContext.ts
|
|
8733
8801
|
import * as fs34 from "fs";
|
|
8734
|
-
import * as
|
|
8802
|
+
import * as path32 from "path";
|
|
8735
8803
|
|
|
8736
8804
|
// src/scripts/kodyVariables.ts
|
|
8737
8805
|
import * as fs33 from "fs";
|
|
8738
|
-
import * as
|
|
8806
|
+
import * as path31 from "path";
|
|
8739
8807
|
var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
|
|
8740
8808
|
function readKodyVariables(cwd) {
|
|
8741
|
-
const full =
|
|
8809
|
+
const full = path31.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
8742
8810
|
let raw;
|
|
8743
8811
|
try {
|
|
8744
8812
|
raw = fs33.readFileSync(full, "utf-8");
|
|
@@ -8790,7 +8858,7 @@ function readProfileStaff(raw) {
|
|
|
8790
8858
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
8791
8859
|
}
|
|
8792
8860
|
function readProfile(cwd) {
|
|
8793
|
-
const dir =
|
|
8861
|
+
const dir = path32.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
8794
8862
|
if (!fs34.existsSync(dir)) return "";
|
|
8795
8863
|
let entries;
|
|
8796
8864
|
try {
|
|
@@ -8801,7 +8869,7 @@ function readProfile(cwd) {
|
|
|
8801
8869
|
const blocks = [];
|
|
8802
8870
|
for (const file of entries) {
|
|
8803
8871
|
try {
|
|
8804
|
-
const raw = fs34.readFileSync(
|
|
8872
|
+
const raw = fs34.readFileSync(path32.join(dir, file), "utf-8");
|
|
8805
8873
|
const { staff, body } = readProfileStaff(raw);
|
|
8806
8874
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
8807
8875
|
blocks.push(`## ${file}
|
|
@@ -8839,7 +8907,7 @@ init_events();
|
|
|
8839
8907
|
|
|
8840
8908
|
// src/taskContext.ts
|
|
8841
8909
|
import * as fs35 from "fs";
|
|
8842
|
-
import * as
|
|
8910
|
+
import * as path33 from "path";
|
|
8843
8911
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
8844
8912
|
function buildTaskContext(args) {
|
|
8845
8913
|
return {
|
|
@@ -8855,9 +8923,9 @@ function buildTaskContext(args) {
|
|
|
8855
8923
|
}
|
|
8856
8924
|
function persistTaskContext(cwd, ctx) {
|
|
8857
8925
|
try {
|
|
8858
|
-
const dir =
|
|
8926
|
+
const dir = path33.join(cwd, ".kody", "runs", ctx.runId);
|
|
8859
8927
|
fs35.mkdirSync(dir, { recursive: true });
|
|
8860
|
-
const file =
|
|
8928
|
+
const file = path33.join(dir, "task-context.json");
|
|
8861
8929
|
fs35.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
8862
8930
|
`);
|
|
8863
8931
|
return file;
|
|
@@ -8907,14 +8975,14 @@ var loadTaskState = async (ctx) => {
|
|
|
8907
8975
|
|
|
8908
8976
|
// src/scripts/loadWorkerAdhoc.ts
|
|
8909
8977
|
import * as fs36 from "fs";
|
|
8910
|
-
import * as
|
|
8978
|
+
import * as path34 from "path";
|
|
8911
8979
|
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
8912
8980
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
8913
8981
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
8914
8982
|
if (!workerSlug) {
|
|
8915
8983
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
8916
8984
|
}
|
|
8917
|
-
const workerPath =
|
|
8985
|
+
const workerPath = path34.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8918
8986
|
if (!fs36.existsSync(workerPath)) {
|
|
8919
8987
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
8920
8988
|
}
|
|
@@ -9103,7 +9171,7 @@ var mergeFlow = async (ctx) => {
|
|
|
9103
9171
|
};
|
|
9104
9172
|
|
|
9105
9173
|
// src/scripts/mergeReleasePr.ts
|
|
9106
|
-
import { execFileSync as
|
|
9174
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
9107
9175
|
var API_TIMEOUT_MS7 = 6e4;
|
|
9108
9176
|
var mergeReleasePr = async (ctx) => {
|
|
9109
9177
|
const state = ctx.data.taskState;
|
|
@@ -9122,7 +9190,7 @@ var mergeReleasePr = async (ctx) => {
|
|
|
9122
9190
|
process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
|
|
9123
9191
|
`);
|
|
9124
9192
|
try {
|
|
9125
|
-
const out =
|
|
9193
|
+
const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
9126
9194
|
timeout: API_TIMEOUT_MS7,
|
|
9127
9195
|
cwd: ctx.cwd,
|
|
9128
9196
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9610,8 +9678,8 @@ var FlyClient = class {
|
|
|
9610
9678
|
get fetch() {
|
|
9611
9679
|
return this.opts.fetchImpl ?? fetch;
|
|
9612
9680
|
}
|
|
9613
|
-
async call(
|
|
9614
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
9681
|
+
async call(path39, init = {}) {
|
|
9682
|
+
const res = await this.fetch(`${FLY_API_BASE}${path39}`, {
|
|
9615
9683
|
method: init.method ?? "GET",
|
|
9616
9684
|
headers: {
|
|
9617
9685
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -9622,7 +9690,7 @@ var FlyClient = class {
|
|
|
9622
9690
|
if (res.status === 404 && init.allow404) return null;
|
|
9623
9691
|
if (!res.ok) {
|
|
9624
9692
|
const text = await res.text().catch(() => "");
|
|
9625
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
9693
|
+
throw new Error(`Fly API ${res.status} on ${path39}: ${text.slice(0, 200) || res.statusText}`);
|
|
9626
9694
|
}
|
|
9627
9695
|
if (res.status === 204) return null;
|
|
9628
9696
|
const raw = await res.text();
|
|
@@ -10162,14 +10230,14 @@ function sendJson2(res, status, body) {
|
|
|
10162
10230
|
res.end(JSON.stringify(body));
|
|
10163
10231
|
}
|
|
10164
10232
|
function readJsonBody2(req) {
|
|
10165
|
-
return new Promise((
|
|
10233
|
+
return new Promise((resolve6, reject) => {
|
|
10166
10234
|
const chunks = [];
|
|
10167
10235
|
req.on("data", (c) => chunks.push(c));
|
|
10168
10236
|
req.on("end", () => {
|
|
10169
10237
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
10170
|
-
if (!raw.trim()) return
|
|
10238
|
+
if (!raw.trim()) return resolve6({});
|
|
10171
10239
|
try {
|
|
10172
|
-
|
|
10240
|
+
resolve6(JSON.parse(raw));
|
|
10173
10241
|
} catch (err) {
|
|
10174
10242
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
10175
10243
|
}
|
|
@@ -10316,10 +10384,10 @@ var poolServe = async (ctx) => {
|
|
|
10316
10384
|
}
|
|
10317
10385
|
});
|
|
10318
10386
|
const apiHost = process.env.POOL_API_HOST ?? "::";
|
|
10319
|
-
await new Promise((
|
|
10387
|
+
await new Promise((resolve6) => {
|
|
10320
10388
|
server.listen(apiPort, apiHost, () => {
|
|
10321
10389
|
log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
|
|
10322
|
-
|
|
10390
|
+
resolve6();
|
|
10323
10391
|
});
|
|
10324
10392
|
});
|
|
10325
10393
|
const shutdown = (signal) => {
|
|
@@ -10521,7 +10589,7 @@ ${body}`;
|
|
|
10521
10589
|
}
|
|
10522
10590
|
|
|
10523
10591
|
// src/scripts/recordClassification.ts
|
|
10524
|
-
import { execFileSync as
|
|
10592
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
10525
10593
|
var API_TIMEOUT_MS8 = 3e4;
|
|
10526
10594
|
var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
10527
10595
|
var recordClassification = async (ctx) => {
|
|
@@ -10569,7 +10637,7 @@ function parseClassification(prSummary) {
|
|
|
10569
10637
|
}
|
|
10570
10638
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
10571
10639
|
try {
|
|
10572
|
-
|
|
10640
|
+
execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
10573
10641
|
cwd,
|
|
10574
10642
|
timeout: API_TIMEOUT_MS8,
|
|
10575
10643
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -10683,7 +10751,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
10683
10751
|
};
|
|
10684
10752
|
|
|
10685
10753
|
// src/scripts/resolveFlow.ts
|
|
10686
|
-
import { execFileSync as
|
|
10754
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
10687
10755
|
init_issue();
|
|
10688
10756
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
10689
10757
|
var resolveFlow = async (ctx) => {
|
|
@@ -10777,7 +10845,7 @@ function buildPreferBlock(prefer, baseBranch) {
|
|
|
10777
10845
|
}
|
|
10778
10846
|
function getConflictedFiles(cwd) {
|
|
10779
10847
|
try {
|
|
10780
|
-
const out =
|
|
10848
|
+
const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
10781
10849
|
encoding: "utf-8",
|
|
10782
10850
|
cwd,
|
|
10783
10851
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -10792,7 +10860,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
10792
10860
|
let total = 0;
|
|
10793
10861
|
for (const f of files) {
|
|
10794
10862
|
try {
|
|
10795
|
-
const content =
|
|
10863
|
+
const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
10796
10864
|
const snippet = `### ${f}
|
|
10797
10865
|
|
|
10798
10866
|
\`\`\`
|
|
@@ -10816,12 +10884,12 @@ function tryPostPr3(prNumber, body, cwd) {
|
|
|
10816
10884
|
function pushEmptyCommit(branch, cwd) {
|
|
10817
10885
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
10818
10886
|
try {
|
|
10819
|
-
|
|
10887
|
+
execFileSync20(
|
|
10820
10888
|
"git",
|
|
10821
10889
|
["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
|
|
10822
10890
|
{ cwd, env, stdio: ["ignore", "pipe", "pipe"] }
|
|
10823
10891
|
);
|
|
10824
|
-
|
|
10892
|
+
execFileSync20("git", ["push", "-u", "origin", branch], {
|
|
10825
10893
|
cwd,
|
|
10826
10894
|
env,
|
|
10827
10895
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -10952,10 +11020,10 @@ var resolvePreviewUrl = async (ctx) => {
|
|
|
10952
11020
|
};
|
|
10953
11021
|
|
|
10954
11022
|
// src/scripts/resolveQaUrl.ts
|
|
10955
|
-
import { execFileSync as
|
|
11023
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
10956
11024
|
function ghQuery(args, cwd) {
|
|
10957
11025
|
try {
|
|
10958
|
-
const out =
|
|
11026
|
+
const out = execFileSync21("gh", args, {
|
|
10959
11027
|
cwd,
|
|
10960
11028
|
stdio: ["ignore", "pipe", "pipe"],
|
|
10961
11029
|
encoding: "utf-8",
|
|
@@ -11025,7 +11093,7 @@ var resolveQaUrl = async (ctx) => {
|
|
|
11025
11093
|
};
|
|
11026
11094
|
|
|
11027
11095
|
// src/scripts/revertFlow.ts
|
|
11028
|
-
import { execFileSync as
|
|
11096
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
11029
11097
|
init_issue();
|
|
11030
11098
|
var SHA_RE = /^[0-9a-f]{4,40}$/i;
|
|
11031
11099
|
var revertFlow = async (ctx) => {
|
|
@@ -11108,7 +11176,7 @@ function buildPrSummary(resolved) {
|
|
|
11108
11176
|
return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
|
|
11109
11177
|
}
|
|
11110
11178
|
function git3(args, cwd) {
|
|
11111
|
-
return
|
|
11179
|
+
return execFileSync22("git", args, {
|
|
11112
11180
|
encoding: "utf-8",
|
|
11113
11181
|
timeout: 3e4,
|
|
11114
11182
|
cwd,
|
|
@@ -11118,7 +11186,7 @@ function git3(args, cwd) {
|
|
|
11118
11186
|
}
|
|
11119
11187
|
function isAncestorOfHead(sha, cwd) {
|
|
11120
11188
|
try {
|
|
11121
|
-
|
|
11189
|
+
execFileSync22("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
|
|
11122
11190
|
cwd,
|
|
11123
11191
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
11124
11192
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -11237,17 +11305,17 @@ function authOk2(req, expected) {
|
|
|
11237
11305
|
return false;
|
|
11238
11306
|
}
|
|
11239
11307
|
function readJsonBody3(req) {
|
|
11240
|
-
return new Promise((
|
|
11308
|
+
return new Promise((resolve6, reject) => {
|
|
11241
11309
|
const chunks = [];
|
|
11242
11310
|
req.on("data", (c) => chunks.push(c));
|
|
11243
11311
|
req.on("end", () => {
|
|
11244
11312
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
11245
11313
|
if (!raw.trim()) {
|
|
11246
|
-
|
|
11314
|
+
resolve6({});
|
|
11247
11315
|
return;
|
|
11248
11316
|
}
|
|
11249
11317
|
try {
|
|
11250
|
-
|
|
11318
|
+
resolve6(JSON.parse(raw));
|
|
11251
11319
|
} catch (err) {
|
|
11252
11320
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
11253
11321
|
}
|
|
@@ -11322,13 +11390,13 @@ async function defaultRunJob(job) {
|
|
|
11322
11390
|
...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
|
|
11323
11391
|
...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
|
|
11324
11392
|
};
|
|
11325
|
-
const run = (cmd, args, cwd) => new Promise((
|
|
11393
|
+
const run = (cmd, args, cwd) => new Promise((resolve6) => {
|
|
11326
11394
|
const child = spawn5(cmd, args, { stdio: "inherit", env: childEnv, cwd });
|
|
11327
|
-
child.on("exit", (code) =>
|
|
11395
|
+
child.on("exit", (code) => resolve6(code ?? 0));
|
|
11328
11396
|
child.on("error", (err) => {
|
|
11329
11397
|
process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
|
|
11330
11398
|
`);
|
|
11331
|
-
|
|
11399
|
+
resolve6(1);
|
|
11332
11400
|
});
|
|
11333
11401
|
});
|
|
11334
11402
|
process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
|
|
@@ -11415,11 +11483,11 @@ var runnerServe = async (ctx) => {
|
|
|
11415
11483
|
const port = Number(process.env.PORT ?? DEFAULT_PORT2);
|
|
11416
11484
|
const server = buildServer2({ apiKey });
|
|
11417
11485
|
const host = process.env.RUNNER_HOST ?? "::";
|
|
11418
|
-
await new Promise((
|
|
11486
|
+
await new Promise((resolve6) => {
|
|
11419
11487
|
server.listen(port, host, () => {
|
|
11420
11488
|
process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
|
|
11421
11489
|
`);
|
|
11422
|
-
|
|
11490
|
+
resolve6();
|
|
11423
11491
|
});
|
|
11424
11492
|
});
|
|
11425
11493
|
const shutdown = (signal) => {
|
|
@@ -11436,7 +11504,7 @@ var runnerServe = async (ctx) => {
|
|
|
11436
11504
|
// src/scripts/runTickScript.ts
|
|
11437
11505
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
11438
11506
|
import * as fs38 from "fs";
|
|
11439
|
-
import * as
|
|
11507
|
+
import * as path35 from "path";
|
|
11440
11508
|
var runTickScript = async (ctx, _profile, args) => {
|
|
11441
11509
|
ctx.skipAgent = true;
|
|
11442
11510
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -11448,7 +11516,7 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
11448
11516
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
11449
11517
|
return;
|
|
11450
11518
|
}
|
|
11451
|
-
const jobPath =
|
|
11519
|
+
const jobPath = path35.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
11452
11520
|
if (!fs38.existsSync(jobPath)) {
|
|
11453
11521
|
ctx.output.exitCode = 99;
|
|
11454
11522
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
@@ -11462,7 +11530,7 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
11462
11530
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
11463
11531
|
return;
|
|
11464
11532
|
}
|
|
11465
|
-
const scriptPath =
|
|
11533
|
+
const scriptPath = path35.isAbsolute(tickScript) ? tickScript : path35.join(ctx.cwd, tickScript);
|
|
11466
11534
|
if (!fs38.existsSync(scriptPath)) {
|
|
11467
11535
|
ctx.output.exitCode = 99;
|
|
11468
11536
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
@@ -11579,7 +11647,8 @@ var saveGoalState = async (ctx) => {
|
|
|
11579
11647
|
lastDispatchedIssue: goal.lastDispatchedIssue,
|
|
11580
11648
|
updatedAt: changed ? nowIso() : prev?.updatedAt
|
|
11581
11649
|
};
|
|
11582
|
-
|
|
11650
|
+
ctx.data.goalPersistState = updated;
|
|
11651
|
+
ctx.data.goalPersistChanged = changed;
|
|
11583
11652
|
ctx.skipAgent = true;
|
|
11584
11653
|
};
|
|
11585
11654
|
|
|
@@ -11663,14 +11732,14 @@ var serveFlow = async (ctx) => {
|
|
|
11663
11732
|
`);
|
|
11664
11733
|
const args = ["--dangerously-skip-permissions", "--model", model.model];
|
|
11665
11734
|
const child = spawn6("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
|
|
11666
|
-
const exitCode = await new Promise((
|
|
11667
|
-
child.on("exit", (code) =>
|
|
11735
|
+
const exitCode = await new Promise((resolve6) => {
|
|
11736
|
+
child.on("exit", (code) => resolve6(code ?? 0));
|
|
11668
11737
|
child.on("error", (err) => {
|
|
11669
11738
|
process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
|
|
11670
11739
|
`);
|
|
11671
11740
|
process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
|
|
11672
11741
|
`);
|
|
11673
|
-
|
|
11742
|
+
resolve6(1);
|
|
11674
11743
|
});
|
|
11675
11744
|
});
|
|
11676
11745
|
killProxy();
|
|
@@ -11760,11 +11829,11 @@ var skipAgent = async (ctx) => {
|
|
|
11760
11829
|
};
|
|
11761
11830
|
|
|
11762
11831
|
// src/scripts/stageMergeConflicts.ts
|
|
11763
|
-
import { execFileSync as
|
|
11832
|
+
import { execFileSync as execFileSync23 } from "child_process";
|
|
11764
11833
|
var stageMergeConflicts = async (ctx) => {
|
|
11765
11834
|
if (ctx.data.agentDone === false) return;
|
|
11766
11835
|
try {
|
|
11767
|
-
|
|
11836
|
+
execFileSync23("git", ["add", "-A"], {
|
|
11768
11837
|
cwd: ctx.cwd,
|
|
11769
11838
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
11770
11839
|
stdio: "pipe"
|
|
@@ -11775,7 +11844,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
11775
11844
|
|
|
11776
11845
|
// src/scripts/startFlow.ts
|
|
11777
11846
|
init_issue();
|
|
11778
|
-
import { execFileSync as
|
|
11847
|
+
import { execFileSync as execFileSync24 } from "child_process";
|
|
11779
11848
|
var API_TIMEOUT_MS9 = 3e4;
|
|
11780
11849
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
11781
11850
|
const entry = args?.entry;
|
|
@@ -11809,7 +11878,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
11809
11878
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
11810
11879
|
const body = `@kody ${next}`;
|
|
11811
11880
|
try {
|
|
11812
|
-
|
|
11881
|
+
execFileSync24("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
11813
11882
|
timeout: API_TIMEOUT_MS9,
|
|
11814
11883
|
cwd,
|
|
11815
11884
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -11823,7 +11892,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
11823
11892
|
}
|
|
11824
11893
|
|
|
11825
11894
|
// src/scripts/syncFlow.ts
|
|
11826
|
-
import { execFileSync as
|
|
11895
|
+
import { execFileSync as execFileSync25 } from "child_process";
|
|
11827
11896
|
init_issue();
|
|
11828
11897
|
var syncFlow = async (ctx, _profile, args) => {
|
|
11829
11898
|
const announceOnSuccess = Boolean(args?.announceOnSuccess);
|
|
@@ -11888,7 +11957,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
11888
11957
|
}
|
|
11889
11958
|
function revParseHead(cwd) {
|
|
11890
11959
|
try {
|
|
11891
|
-
return
|
|
11960
|
+
return execFileSync25("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
11892
11961
|
} catch {
|
|
11893
11962
|
return "";
|
|
11894
11963
|
}
|
|
@@ -12006,7 +12075,7 @@ function stripAnsi2(s) {
|
|
|
12006
12075
|
return s.replace(ANSI_RE2, "");
|
|
12007
12076
|
}
|
|
12008
12077
|
function runCommand2(command, cwd) {
|
|
12009
|
-
return new Promise((
|
|
12078
|
+
return new Promise((resolve6) => {
|
|
12010
12079
|
const child = spawn7(command, {
|
|
12011
12080
|
cwd,
|
|
12012
12081
|
shell: true,
|
|
@@ -12033,11 +12102,11 @@ function runCommand2(command, cwd) {
|
|
|
12033
12102
|
}, TEST_TIMEOUT_MS);
|
|
12034
12103
|
child.on("exit", (code) => {
|
|
12035
12104
|
clearTimeout(timer);
|
|
12036
|
-
|
|
12105
|
+
resolve6({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
|
|
12037
12106
|
});
|
|
12038
12107
|
child.on("error", (err) => {
|
|
12039
12108
|
clearTimeout(timer);
|
|
12040
|
-
|
|
12109
|
+
resolve6({ exitCode: -1, output: err.message });
|
|
12041
12110
|
});
|
|
12042
12111
|
});
|
|
12043
12112
|
}
|
|
@@ -12145,7 +12214,7 @@ var verifyWithRetry = async (ctx) => {
|
|
|
12145
12214
|
|
|
12146
12215
|
// src/scripts/waitForCi.ts
|
|
12147
12216
|
init_issue();
|
|
12148
|
-
import { execFileSync as
|
|
12217
|
+
import { execFileSync as execFileSync26 } from "child_process";
|
|
12149
12218
|
var API_TIMEOUT_MS10 = 3e4;
|
|
12150
12219
|
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
12151
12220
|
const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
|
|
@@ -12223,7 +12292,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
12223
12292
|
};
|
|
12224
12293
|
function fetchChecks(prNumber, cwd) {
|
|
12225
12294
|
try {
|
|
12226
|
-
const raw =
|
|
12295
|
+
const raw = execFileSync26("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
|
|
12227
12296
|
encoding: "utf-8",
|
|
12228
12297
|
timeout: API_TIMEOUT_MS10,
|
|
12229
12298
|
cwd,
|
|
@@ -12459,20 +12528,20 @@ function lineStream(stream) {
|
|
|
12459
12528
|
tryDeliver();
|
|
12460
12529
|
});
|
|
12461
12530
|
return {
|
|
12462
|
-
next: (timeoutMs) => new Promise((
|
|
12531
|
+
next: (timeoutMs) => new Promise((resolve6) => {
|
|
12463
12532
|
if (queue.length > 0) {
|
|
12464
|
-
|
|
12533
|
+
resolve6(queue.shift());
|
|
12465
12534
|
return;
|
|
12466
12535
|
}
|
|
12467
12536
|
if (ended) {
|
|
12468
|
-
|
|
12537
|
+
resolve6(null);
|
|
12469
12538
|
return;
|
|
12470
12539
|
}
|
|
12471
|
-
waiter =
|
|
12540
|
+
waiter = resolve6;
|
|
12472
12541
|
const t = setTimeout(() => {
|
|
12473
|
-
if (waiter ===
|
|
12542
|
+
if (waiter === resolve6) {
|
|
12474
12543
|
waiter = null;
|
|
12475
|
-
|
|
12544
|
+
resolve6(null);
|
|
12476
12545
|
}
|
|
12477
12546
|
}, Math.max(0, timeoutMs));
|
|
12478
12547
|
t.unref?.();
|
|
@@ -12696,7 +12765,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
12696
12765
|
]);
|
|
12697
12766
|
|
|
12698
12767
|
// src/tools.ts
|
|
12699
|
-
import { execFileSync as
|
|
12768
|
+
import { execFileSync as execFileSync27 } from "child_process";
|
|
12700
12769
|
function verifyCliTools(tools, cwd) {
|
|
12701
12770
|
const out = [];
|
|
12702
12771
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -12710,31 +12779,49 @@ function firstRequiredFailure(results, tools) {
|
|
|
12710
12779
|
}
|
|
12711
12780
|
return null;
|
|
12712
12781
|
}
|
|
12713
|
-
function verifyOne(
|
|
12714
|
-
const result = { name:
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12782
|
+
function verifyOne(tool4, cwd) {
|
|
12783
|
+
const result = { name: tool4.name, present: false, verified: false };
|
|
12784
|
+
const checkRes = runShell(tool4.install.checkCommand, cwd);
|
|
12785
|
+
let present = checkRes.ok;
|
|
12786
|
+
if (!present && tool4.install.installCommand) {
|
|
12787
|
+
runShell(tool4.install.installCommand, cwd, 12e4);
|
|
12788
|
+
present = runShell(tool4.install.checkCommand, cwd).ok;
|
|
12719
12789
|
}
|
|
12720
12790
|
result.present = present;
|
|
12721
12791
|
if (!present) {
|
|
12722
|
-
result.error = `tool "${
|
|
12792
|
+
result.error = `tool "${tool4.name}" not on PATH (check: ${tool4.install.checkCommand})`;
|
|
12723
12793
|
return result;
|
|
12724
12794
|
}
|
|
12725
|
-
const
|
|
12726
|
-
result.verified =
|
|
12727
|
-
if (!
|
|
12795
|
+
const verifyRes = runShell(tool4.verify, cwd);
|
|
12796
|
+
result.verified = verifyRes.ok;
|
|
12797
|
+
if (!verifyRes.ok) {
|
|
12798
|
+
const tail = formatStderrTail(verifyRes.stderr, verifyRes.stdout);
|
|
12799
|
+
result.error = `tool "${tool4.name}" failed verify: ${tool4.verify}${tail ? ` \u2014 ${tail}` : ""}`;
|
|
12800
|
+
}
|
|
12728
12801
|
return result;
|
|
12729
12802
|
}
|
|
12730
12803
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
12731
12804
|
try {
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12805
|
+
const stdout = execFileSync27("sh", ["-c", cmd], {
|
|
12806
|
+
cwd,
|
|
12807
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12808
|
+
timeout: timeoutMs,
|
|
12809
|
+
encoding: "utf-8"
|
|
12810
|
+
});
|
|
12811
|
+
return { ok: true, stdout: stdout ?? "", stderr: "" };
|
|
12812
|
+
} catch (err) {
|
|
12813
|
+
const e = err;
|
|
12814
|
+
const stdout = e.stdout ? e.stdout.toString() : "";
|
|
12815
|
+
const stderr = e.stderr ? e.stderr.toString() : "";
|
|
12816
|
+
return { ok: false, stdout, stderr };
|
|
12736
12817
|
}
|
|
12737
12818
|
}
|
|
12819
|
+
function formatStderrTail(stderr, stdout) {
|
|
12820
|
+
const source = stderr.trim() || stdout.trim();
|
|
12821
|
+
if (!source) return "";
|
|
12822
|
+
const flat = source.replace(/\s+/g, " ").trim();
|
|
12823
|
+
return flat.length > 400 ? `\u2026${flat.slice(-400)}` : flat;
|
|
12824
|
+
}
|
|
12738
12825
|
|
|
12739
12826
|
// src/executor.ts
|
|
12740
12827
|
var CONTAINER_MAX_ITERATIONS = 50;
|
|
@@ -12780,11 +12867,12 @@ async function runExecutable(profileName, input) {
|
|
|
12780
12867
|
if (input.config) {
|
|
12781
12868
|
config = input.config;
|
|
12782
12869
|
} else if (input.skipConfig) {
|
|
12870
|
+
const envModel = process.env.MODEL?.trim();
|
|
12783
12871
|
config = {
|
|
12784
12872
|
quality: { typecheck: "", lint: "", testUnit: "", format: "" },
|
|
12785
12873
|
git: { defaultBranch: "main" },
|
|
12786
12874
|
github: { owner: "", repo: "" },
|
|
12787
|
-
agent: { model: "claude/claude-haiku-4-5-20251001" }
|
|
12875
|
+
agent: { model: envModel || "claude/claude-haiku-4-5-20251001" }
|
|
12788
12876
|
};
|
|
12789
12877
|
} else {
|
|
12790
12878
|
try {
|
|
@@ -12837,9 +12925,9 @@ async function runExecutable(profileName, input) {
|
|
|
12837
12925
|
})
|
|
12838
12926
|
};
|
|
12839
12927
|
})() : null;
|
|
12840
|
-
const ndjsonDir =
|
|
12928
|
+
const ndjsonDir = path36.join(input.cwd, ".kody");
|
|
12841
12929
|
const invokeAgent = async (prompt) => {
|
|
12842
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
12930
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path36.isAbsolute(p) ? p : path36.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
12843
12931
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
12844
12932
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
12845
12933
|
const agents = loadSubagents(profile);
|
|
@@ -13060,13 +13148,13 @@ function getProfileInputsForChild(profileName, _cwd) {
|
|
|
13060
13148
|
function resolveProfilePath(profileName) {
|
|
13061
13149
|
const found = resolveExecutable(profileName);
|
|
13062
13150
|
if (found) return found;
|
|
13063
|
-
const here =
|
|
13151
|
+
const here = path36.dirname(new URL(import.meta.url).pathname);
|
|
13064
13152
|
const candidates = [
|
|
13065
|
-
|
|
13153
|
+
path36.join(here, "executables", profileName, "profile.json"),
|
|
13066
13154
|
// same-dir sibling (dev)
|
|
13067
|
-
|
|
13155
|
+
path36.join(here, "..", "executables", profileName, "profile.json"),
|
|
13068
13156
|
// up one (prod: dist/bin → dist/executables)
|
|
13069
|
-
|
|
13157
|
+
path36.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
13070
13158
|
// fallback
|
|
13071
13159
|
];
|
|
13072
13160
|
for (const c of candidates) {
|
|
@@ -13170,7 +13258,7 @@ function resolveShellTimeoutMs(entry) {
|
|
|
13170
13258
|
var SIGKILL_GRACE_MS = 5e3;
|
|
13171
13259
|
async function runShellEntry(entry, ctx, profile) {
|
|
13172
13260
|
const shellName = entry.shell;
|
|
13173
|
-
const shellPath =
|
|
13261
|
+
const shellPath = path36.join(profile.dir, shellName);
|
|
13174
13262
|
if (!fs40.existsSync(shellPath)) {
|
|
13175
13263
|
ctx.skipAgent = true;
|
|
13176
13264
|
ctx.output.exitCode = 99;
|
|
@@ -13209,14 +13297,14 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
13209
13297
|
let killTimer;
|
|
13210
13298
|
let escalateTimer;
|
|
13211
13299
|
const result = await new Promise(
|
|
13212
|
-
(
|
|
13300
|
+
(resolve6) => {
|
|
13213
13301
|
let settled = false;
|
|
13214
13302
|
const settle = (code, signal, spawnErr) => {
|
|
13215
13303
|
if (settled) return;
|
|
13216
13304
|
settled = true;
|
|
13217
13305
|
if (killTimer) clearTimeout(killTimer);
|
|
13218
13306
|
if (escalateTimer) clearTimeout(escalateTimer);
|
|
13219
|
-
|
|
13307
|
+
resolve6({ code, signal, spawnErr });
|
|
13220
13308
|
};
|
|
13221
13309
|
child.on("error", (err) => settle(null, null, err));
|
|
13222
13310
|
child.on("close", (code, signal) => settle(code, signal));
|
|
@@ -13498,7 +13586,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
13498
13586
|
}
|
|
13499
13587
|
function resetWorkingTree2(cwd) {
|
|
13500
13588
|
try {
|
|
13501
|
-
|
|
13589
|
+
execFileSync28("git", ["reset", "--hard", "HEAD"], {
|
|
13502
13590
|
cwd,
|
|
13503
13591
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13504
13592
|
timeout: 3e4
|
|
@@ -13650,14 +13738,14 @@ function resolveAuthToken(env = process.env) {
|
|
|
13650
13738
|
return token;
|
|
13651
13739
|
}
|
|
13652
13740
|
function detectPackageManager2(cwd) {
|
|
13653
|
-
if (fs41.existsSync(
|
|
13654
|
-
if (fs41.existsSync(
|
|
13655
|
-
if (fs41.existsSync(
|
|
13741
|
+
if (fs41.existsSync(path37.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
13742
|
+
if (fs41.existsSync(path37.join(cwd, "yarn.lock"))) return "yarn";
|
|
13743
|
+
if (fs41.existsSync(path37.join(cwd, "bun.lockb"))) return "bun";
|
|
13656
13744
|
return "npm";
|
|
13657
13745
|
}
|
|
13658
13746
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
13659
13747
|
try {
|
|
13660
|
-
|
|
13748
|
+
execFileSync29(cmd, args, {
|
|
13661
13749
|
cwd,
|
|
13662
13750
|
stdio: stream ? "inherit" : "pipe",
|
|
13663
13751
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -13670,7 +13758,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
13670
13758
|
}
|
|
13671
13759
|
function isOnPath(bin) {
|
|
13672
13760
|
try {
|
|
13673
|
-
|
|
13761
|
+
execFileSync29("which", [bin], { stdio: "pipe" });
|
|
13674
13762
|
return true;
|
|
13675
13763
|
} catch {
|
|
13676
13764
|
return false;
|
|
@@ -13711,7 +13799,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
13711
13799
|
} catch {
|
|
13712
13800
|
}
|
|
13713
13801
|
try {
|
|
13714
|
-
|
|
13802
|
+
execFileSync29("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
13715
13803
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
13716
13804
|
return 0;
|
|
13717
13805
|
} catch {
|
|
@@ -13721,16 +13809,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
13721
13809
|
}
|
|
13722
13810
|
function configureGitIdentity(cwd) {
|
|
13723
13811
|
try {
|
|
13724
|
-
const name =
|
|
13812
|
+
const name = execFileSync29("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
13725
13813
|
if (name) return;
|
|
13726
13814
|
} catch {
|
|
13727
13815
|
}
|
|
13728
13816
|
try {
|
|
13729
|
-
|
|
13817
|
+
execFileSync29("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
13730
13818
|
} catch {
|
|
13731
13819
|
}
|
|
13732
13820
|
try {
|
|
13733
|
-
|
|
13821
|
+
execFileSync29("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
13734
13822
|
cwd,
|
|
13735
13823
|
stdio: "pipe"
|
|
13736
13824
|
});
|
|
@@ -13739,7 +13827,7 @@ function configureGitIdentity(cwd) {
|
|
|
13739
13827
|
}
|
|
13740
13828
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
13741
13829
|
if (!issueNumber) return;
|
|
13742
|
-
const logPath =
|
|
13830
|
+
const logPath = path37.join(cwd, ".kody", "last-run.jsonl");
|
|
13743
13831
|
let tail = "";
|
|
13744
13832
|
try {
|
|
13745
13833
|
if (fs41.existsSync(logPath)) {
|
|
@@ -13768,7 +13856,7 @@ async function runCi(argv) {
|
|
|
13768
13856
|
return 0;
|
|
13769
13857
|
}
|
|
13770
13858
|
const args = parseCiArgs(argv);
|
|
13771
|
-
const cwd = args.cwd ?
|
|
13859
|
+
const cwd = args.cwd ? path37.resolve(args.cwd) : process.cwd();
|
|
13772
13860
|
let earlyConfig;
|
|
13773
13861
|
try {
|
|
13774
13862
|
earlyConfig = loadConfig(cwd);
|
|
@@ -14039,17 +14127,17 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
14039
14127
|
return result;
|
|
14040
14128
|
}
|
|
14041
14129
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
14042
|
-
const sessionFile =
|
|
14043
|
-
const eventsFile =
|
|
14130
|
+
const sessionFile = path38.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
14131
|
+
const eventsFile = path38.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
14044
14132
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
14045
|
-
const tasksDir =
|
|
14133
|
+
const tasksDir = path38.join(".kody", "tasks", safeSession);
|
|
14046
14134
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
14047
|
-
const paths = candidatePaths.filter((p) => fs42.existsSync(
|
|
14135
|
+
const paths = candidatePaths.filter((p) => fs42.existsSync(path38.join(cwd, p)));
|
|
14048
14136
|
if (paths.length === 0) return;
|
|
14049
14137
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
14050
14138
|
try {
|
|
14051
|
-
|
|
14052
|
-
|
|
14139
|
+
execFileSync30("git", ["add", "-f", ...paths], opts);
|
|
14140
|
+
execFileSync30("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
14053
14141
|
} catch (err) {
|
|
14054
14142
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14055
14143
|
process.stderr.write(`[kody:chat] commit skipped: ${msg}
|
|
@@ -14088,7 +14176,7 @@ async function runChat(argv) {
|
|
|
14088
14176
|
${CHAT_HELP}`);
|
|
14089
14177
|
return 64;
|
|
14090
14178
|
}
|
|
14091
|
-
const cwd = args.cwd ?
|
|
14179
|
+
const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
|
|
14092
14180
|
const sessionId = args.sessionId;
|
|
14093
14181
|
const unpackedSecrets = unpackAllSecrets();
|
|
14094
14182
|
if (unpackedSecrets > 0) {
|
|
@@ -14496,7 +14584,7 @@ ${HELP_TEXT}`);
|
|
|
14496
14584
|
}
|
|
14497
14585
|
}
|
|
14498
14586
|
const cwd = args.cwd ?? process.cwd();
|
|
14499
|
-
const configlessCommands = /* @__PURE__ */ new Set(["init", "goal-scheduler"]);
|
|
14587
|
+
const configlessCommands = /* @__PURE__ */ new Set(["init", "goal-scheduler", "brain-serve"]);
|
|
14500
14588
|
const skipConfig = configlessCommands.has(args.executableName ?? "");
|
|
14501
14589
|
try {
|
|
14502
14590
|
const result = await runExecutable(args.executableName, {
|