@kody-ade/kody-engine 0.4.156 → 0.4.158
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 +427 -317
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -650,16 +650,16 @@ var init_issue = __esm({
|
|
|
650
650
|
});
|
|
651
651
|
|
|
652
652
|
// src/prompt.ts
|
|
653
|
-
import * as
|
|
654
|
-
import * as
|
|
653
|
+
import * as fs21 from "fs";
|
|
654
|
+
import * as path20 from "path";
|
|
655
655
|
function loadProjectConventions(projectDir) {
|
|
656
656
|
const out = [];
|
|
657
657
|
for (const rel of CONVENTION_FILES) {
|
|
658
|
-
const abs =
|
|
659
|
-
if (!
|
|
658
|
+
const abs = path20.join(projectDir, rel);
|
|
659
|
+
if (!fs21.existsSync(abs)) continue;
|
|
660
660
|
let content;
|
|
661
661
|
try {
|
|
662
|
-
content =
|
|
662
|
+
content = fs21.readFileSync(abs, "utf-8");
|
|
663
663
|
} catch {
|
|
664
664
|
continue;
|
|
665
665
|
}
|
|
@@ -807,28 +807,28 @@ var loadMemoryContext_exports = {};
|
|
|
807
807
|
__export(loadMemoryContext_exports, {
|
|
808
808
|
loadMemoryContext: () => loadMemoryContext
|
|
809
809
|
});
|
|
810
|
-
import * as
|
|
811
|
-
import * as
|
|
810
|
+
import * as fs33 from "fs";
|
|
811
|
+
import * as path31 from "path";
|
|
812
812
|
function collectPages(memoryAbs) {
|
|
813
813
|
const out = [];
|
|
814
814
|
walkMd(memoryAbs, (file) => {
|
|
815
815
|
let stat;
|
|
816
816
|
try {
|
|
817
|
-
stat =
|
|
817
|
+
stat = fs33.statSync(file);
|
|
818
818
|
} catch {
|
|
819
819
|
return;
|
|
820
820
|
}
|
|
821
821
|
let raw;
|
|
822
822
|
try {
|
|
823
|
-
raw =
|
|
823
|
+
raw = fs33.readFileSync(file, "utf-8");
|
|
824
824
|
} catch {
|
|
825
825
|
return;
|
|
826
826
|
}
|
|
827
827
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
828
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
828
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path31.basename(file, ".md");
|
|
829
829
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
830
830
|
out.push({
|
|
831
|
-
relPath:
|
|
831
|
+
relPath: path31.relative(memoryAbs, file),
|
|
832
832
|
title,
|
|
833
833
|
updated,
|
|
834
834
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
@@ -896,16 +896,16 @@ function walkMd(root, visit) {
|
|
|
896
896
|
const dir = stack.pop();
|
|
897
897
|
let names;
|
|
898
898
|
try {
|
|
899
|
-
names =
|
|
899
|
+
names = fs33.readdirSync(dir);
|
|
900
900
|
} catch {
|
|
901
901
|
continue;
|
|
902
902
|
}
|
|
903
903
|
for (const name of names) {
|
|
904
904
|
if (name.startsWith(".")) continue;
|
|
905
|
-
const full =
|
|
905
|
+
const full = path31.join(dir, name);
|
|
906
906
|
let stat;
|
|
907
907
|
try {
|
|
908
|
-
stat =
|
|
908
|
+
stat = fs33.statSync(full);
|
|
909
909
|
} catch {
|
|
910
910
|
continue;
|
|
911
911
|
}
|
|
@@ -928,8 +928,8 @@ var init_loadMemoryContext = __esm({
|
|
|
928
928
|
TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
929
929
|
loadMemoryContext = async (ctx) => {
|
|
930
930
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
931
|
-
const memoryAbs =
|
|
932
|
-
if (!
|
|
931
|
+
const memoryAbs = path31.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
932
|
+
if (!fs33.existsSync(memoryAbs)) {
|
|
933
933
|
ctx.data.memoryContext = "";
|
|
934
934
|
return;
|
|
935
935
|
}
|
|
@@ -1061,7 +1061,7 @@ var init_loadPriorArt = __esm({
|
|
|
1061
1061
|
// package.json
|
|
1062
1062
|
var package_default = {
|
|
1063
1063
|
name: "@kody-ade/kody-engine",
|
|
1064
|
-
version: "0.4.
|
|
1064
|
+
version: "0.4.158",
|
|
1065
1065
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
1066
1066
|
license: "MIT",
|
|
1067
1067
|
type: "module",
|
|
@@ -1118,8 +1118,8 @@ var package_default = {
|
|
|
1118
1118
|
|
|
1119
1119
|
// src/chat-cli.ts
|
|
1120
1120
|
import { execFileSync as execFileSync31 } from "child_process";
|
|
1121
|
-
import * as
|
|
1122
|
-
import * as
|
|
1121
|
+
import * as fs43 from "fs";
|
|
1122
|
+
import * as path39 from "path";
|
|
1123
1123
|
|
|
1124
1124
|
// src/chat/events.ts
|
|
1125
1125
|
import * as fs from "fs";
|
|
@@ -1185,8 +1185,8 @@ function makeRunId(sessionId, suffix) {
|
|
|
1185
1185
|
}
|
|
1186
1186
|
|
|
1187
1187
|
// src/chat/loop.ts
|
|
1188
|
-
import * as
|
|
1189
|
-
import * as
|
|
1188
|
+
import * as fs11 from "fs";
|
|
1189
|
+
import * as path11 from "path";
|
|
1190
1190
|
|
|
1191
1191
|
// src/task-artifacts.ts
|
|
1192
1192
|
import fs2 from "fs";
|
|
@@ -2156,6 +2156,65 @@ function seedInitialMessage(file, message) {
|
|
|
2156
2156
|
return true;
|
|
2157
2157
|
}
|
|
2158
2158
|
|
|
2159
|
+
// src/chat/attachments.ts
|
|
2160
|
+
import * as fs10 from "fs";
|
|
2161
|
+
import * as path10 from "path";
|
|
2162
|
+
var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
|
|
2163
|
+
var EXT_BY_MIME = {
|
|
2164
|
+
"image/png": "png",
|
|
2165
|
+
"image/jpeg": "jpg",
|
|
2166
|
+
"image/jpg": "jpg",
|
|
2167
|
+
"image/gif": "gif",
|
|
2168
|
+
"image/webp": "webp",
|
|
2169
|
+
"image/bmp": "bmp",
|
|
2170
|
+
"image/svg+xml": "svg",
|
|
2171
|
+
"image/avif": "avif"
|
|
2172
|
+
};
|
|
2173
|
+
function extFor(mime) {
|
|
2174
|
+
return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
|
|
2175
|
+
}
|
|
2176
|
+
function attachmentsDir(cwd, sessionId) {
|
|
2177
|
+
return path10.join(cwd, ".kody", "tmp", "attachments", sessionId);
|
|
2178
|
+
}
|
|
2179
|
+
function prepareAttachments(turns, cwd, sessionId) {
|
|
2180
|
+
const lastUserIdx = (() => {
|
|
2181
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
2182
|
+
if (turns[i].role === "user") return i;
|
|
2183
|
+
}
|
|
2184
|
+
return -1;
|
|
2185
|
+
})();
|
|
2186
|
+
const imagePaths = [];
|
|
2187
|
+
let imageCounter = 0;
|
|
2188
|
+
const rewritten = turns.map((turn, idx) => {
|
|
2189
|
+
if (!turn.content.includes("base64,")) return turn;
|
|
2190
|
+
const isLastUser = idx === lastUserIdx;
|
|
2191
|
+
const newContent = turn.content.replace(
|
|
2192
|
+
INLINE_ATTACHMENT_RE,
|
|
2193
|
+
(_match, label, mime, data) => {
|
|
2194
|
+
const name = (label ?? "").trim() || "attachment";
|
|
2195
|
+
const isImage = mime.toLowerCase().startsWith("image/");
|
|
2196
|
+
if (!isLastUser || !isImage) {
|
|
2197
|
+
return `[${isImage ? "Image" : "File"}: ${name}${isLastUser ? "" : " \u2014 omitted from history"}]`;
|
|
2198
|
+
}
|
|
2199
|
+
try {
|
|
2200
|
+
const dir = attachmentsDir(cwd, sessionId);
|
|
2201
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
2202
|
+
const filePath = path10.join(dir, `${imageCounter}.${extFor(mime)}`);
|
|
2203
|
+
fs10.writeFileSync(filePath, Buffer.from(data, "base64"));
|
|
2204
|
+
imageCounter += 1;
|
|
2205
|
+
imagePaths.push(filePath);
|
|
2206
|
+
return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it before answering.]`;
|
|
2207
|
+
} catch {
|
|
2208
|
+
return `[Image: ${name} (could not be materialised)]`;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
);
|
|
2212
|
+
if (newContent === turn.content) return turn;
|
|
2213
|
+
return { ...turn, content: newContent };
|
|
2214
|
+
});
|
|
2215
|
+
return { turns: rewritten, imagePaths };
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2159
2218
|
// src/chat/loop.ts
|
|
2160
2219
|
var CHAT_SYSTEM_PROMPT = [
|
|
2161
2220
|
"You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the",
|
|
@@ -2259,7 +2318,7 @@ function buildExecutableCatalog() {
|
|
|
2259
2318
|
const entries = [];
|
|
2260
2319
|
for (const { name, profilePath } of discovered) {
|
|
2261
2320
|
try {
|
|
2262
|
-
const raw = JSON.parse(
|
|
2321
|
+
const raw = JSON.parse(fs11.readFileSync(profilePath, "utf-8"));
|
|
2263
2322
|
const describe = typeof raw.describe === "string" ? raw.describe : "";
|
|
2264
2323
|
const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
|
|
2265
2324
|
entries.push({ name, describe: firstSentence.trim() });
|
|
@@ -2294,6 +2353,7 @@ async function runChatTurn(opts) {
|
|
|
2294
2353
|
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
2295
2354
|
return { exitCode: 64, error };
|
|
2296
2355
|
}
|
|
2356
|
+
const { turns: promptTurns, imagePaths } = prepareAttachments(turns, opts.cwd, opts.sessionId);
|
|
2297
2357
|
const basePrompt = opts.systemPrompt ?? CHAT_SYSTEM_PROMPT;
|
|
2298
2358
|
const catalog = buildExecutableCatalog();
|
|
2299
2359
|
const taskArtifactsPaths = prepareTaskArtifactsDir(opts.cwd, opts.sessionId);
|
|
@@ -2306,16 +2366,25 @@ async function runChatTurn(opts) {
|
|
|
2306
2366
|
const memoryBlock = readMemoryIndexBlock(opts.cwd);
|
|
2307
2367
|
const instructionsBlock = readInstructionsBlock(opts.cwd);
|
|
2308
2368
|
const crossRepoBlock = opts.reposRoot ? CROSS_REPO_PROMPT : null;
|
|
2369
|
+
const imageBlock = imagePaths.length > 0 ? [
|
|
2370
|
+
"# Attached images",
|
|
2371
|
+
"The user attached one or more images on this turn. They are saved as",
|
|
2372
|
+
"files in this workspace and referenced inline in the conversation as",
|
|
2373
|
+
'`[Image "\u2026" is attached \u2014 saved to <path>]`. You CAN view them: call',
|
|
2374
|
+
"the Read tool on each of those exact paths BEFORE answering. Never tell",
|
|
2375
|
+
"the user you cannot see images \u2014 Read the file and describe what you see."
|
|
2376
|
+
].join("\n") : null;
|
|
2309
2377
|
const systemPrompt = [
|
|
2310
2378
|
basePrompt,
|
|
2311
2379
|
contextBlock,
|
|
2312
2380
|
memoryBlock,
|
|
2313
2381
|
instructionsBlock,
|
|
2314
2382
|
crossRepoBlock,
|
|
2383
|
+
imageBlock,
|
|
2315
2384
|
catalog,
|
|
2316
2385
|
artifactAddendum
|
|
2317
2386
|
].filter((s) => typeof s === "string" && s.length > 0).join("\n\n");
|
|
2318
|
-
const prompt = buildPrompt(
|
|
2387
|
+
const prompt = buildPrompt(promptTurns);
|
|
2319
2388
|
let progressSeq = 0;
|
|
2320
2389
|
const invoke = opts.invokeAgent ?? ((p) => runAgent({
|
|
2321
2390
|
prompt: p,
|
|
@@ -2417,10 +2486,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
2417
2486
|
var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
|
|
2418
2487
|
var MAX_INDEX_BYTES = 8e3;
|
|
2419
2488
|
function readMemoryIndexBlock(cwd) {
|
|
2420
|
-
const indexPath =
|
|
2489
|
+
const indexPath = path11.join(cwd, MEMORY_INDEX_REL);
|
|
2421
2490
|
let raw;
|
|
2422
2491
|
try {
|
|
2423
|
-
raw =
|
|
2492
|
+
raw = fs11.readFileSync(indexPath, "utf-8");
|
|
2424
2493
|
} catch {
|
|
2425
2494
|
return "";
|
|
2426
2495
|
}
|
|
@@ -2438,17 +2507,17 @@ function readMemoryIndexBlock(cwd) {
|
|
|
2438
2507
|
var CONTEXT_DIR_REL = ".kody/context";
|
|
2439
2508
|
var MAX_CONTEXT_BYTES = 12e3;
|
|
2440
2509
|
function readContextBlock(cwd) {
|
|
2441
|
-
const dir =
|
|
2510
|
+
const dir = path11.join(cwd, CONTEXT_DIR_REL);
|
|
2442
2511
|
let files;
|
|
2443
2512
|
try {
|
|
2444
|
-
files =
|
|
2513
|
+
files = fs11.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
2445
2514
|
} catch {
|
|
2446
2515
|
return "";
|
|
2447
2516
|
}
|
|
2448
2517
|
const sections = [];
|
|
2449
2518
|
for (const file of files) {
|
|
2450
2519
|
try {
|
|
2451
|
-
const content =
|
|
2520
|
+
const content = fs11.readFileSync(path11.join(dir, file), "utf-8").trim();
|
|
2452
2521
|
if (content) sections.push(`### ${file.replace(/\.md$/, "")}
|
|
2453
2522
|
|
|
2454
2523
|
${content}`);
|
|
@@ -2469,10 +2538,10 @@ ${content}`);
|
|
|
2469
2538
|
var INSTRUCTIONS_REL = ".kody/instructions.md";
|
|
2470
2539
|
var MAX_INSTRUCTIONS_BYTES = 8e3;
|
|
2471
2540
|
function readInstructionsBlock(cwd) {
|
|
2472
|
-
const instructionsPath =
|
|
2541
|
+
const instructionsPath = path11.join(cwd, INSTRUCTIONS_REL);
|
|
2473
2542
|
let raw;
|
|
2474
2543
|
try {
|
|
2475
|
-
raw =
|
|
2544
|
+
raw = fs11.readFileSync(instructionsPath, "utf-8");
|
|
2476
2545
|
} catch {
|
|
2477
2546
|
return "";
|
|
2478
2547
|
}
|
|
@@ -2491,8 +2560,8 @@ function readInstructionsBlock(cwd) {
|
|
|
2491
2560
|
// src/chat/modes/interactive.ts
|
|
2492
2561
|
init_issue();
|
|
2493
2562
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2494
|
-
import * as
|
|
2495
|
-
import * as
|
|
2563
|
+
import * as fs12 from "fs";
|
|
2564
|
+
import * as path12 from "path";
|
|
2496
2565
|
|
|
2497
2566
|
// src/chat/inbox.ts
|
|
2498
2567
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -2651,9 +2720,9 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
2651
2720
|
return -1;
|
|
2652
2721
|
}
|
|
2653
2722
|
function commitTurn(cwd, sessionId, _verbose) {
|
|
2654
|
-
const sessionRel =
|
|
2655
|
-
const eventsRel =
|
|
2656
|
-
const rels = [sessionRel, eventsRel].filter((p) =>
|
|
2723
|
+
const sessionRel = path12.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
2724
|
+
const eventsRel = path12.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
2725
|
+
const rels = [sessionRel, eventsRel].filter((p) => fs12.existsSync(path12.join(cwd, p)));
|
|
2657
2726
|
if (rels.length === 0) return;
|
|
2658
2727
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
2659
2728
|
if (!repository) {
|
|
@@ -2665,8 +2734,8 @@ function commitTurn(cwd, sessionId, _verbose) {
|
|
|
2665
2734
|
}
|
|
2666
2735
|
const branch = defaultBranch(cwd) ?? "main";
|
|
2667
2736
|
for (const rel of rels) {
|
|
2668
|
-
const repoPath = rel.split(
|
|
2669
|
-
const localText =
|
|
2737
|
+
const repoPath = rel.split(path12.sep).join("/");
|
|
2738
|
+
const localText = fs12.readFileSync(path12.join(cwd, rel), "utf-8");
|
|
2670
2739
|
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
2671
2740
|
}
|
|
2672
2741
|
}
|
|
@@ -2756,8 +2825,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
2756
2825
|
|
|
2757
2826
|
// src/kody-cli.ts
|
|
2758
2827
|
import { execFileSync as execFileSync30 } from "child_process";
|
|
2759
|
-
import * as
|
|
2760
|
-
import * as
|
|
2828
|
+
import * as fs42 from "fs";
|
|
2829
|
+
import * as path38 from "path";
|
|
2761
2830
|
|
|
2762
2831
|
// src/app-auth.ts
|
|
2763
2832
|
import { createSign } from "crypto";
|
|
@@ -2830,7 +2899,7 @@ async function mintAppInstallationToken(creds) {
|
|
|
2830
2899
|
}
|
|
2831
2900
|
|
|
2832
2901
|
// src/dispatch.ts
|
|
2833
|
-
import * as
|
|
2902
|
+
import * as fs13 from "fs";
|
|
2834
2903
|
|
|
2835
2904
|
// src/cron-match.ts
|
|
2836
2905
|
var FIELD_BOUNDS = [
|
|
@@ -2914,10 +2983,10 @@ function autoDispatch(opts) {
|
|
|
2914
2983
|
}
|
|
2915
2984
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
2916
2985
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2917
|
-
if (!eventName || !eventPath || !
|
|
2986
|
+
if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
|
|
2918
2987
|
let event = {};
|
|
2919
2988
|
try {
|
|
2920
|
-
event = JSON.parse(
|
|
2989
|
+
event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
|
|
2921
2990
|
} catch {
|
|
2922
2991
|
return null;
|
|
2923
2992
|
}
|
|
@@ -2935,7 +3004,7 @@ function autoDispatch(opts) {
|
|
|
2935
3004
|
const authorLogin = String(event.comment?.user?.login ?? "");
|
|
2936
3005
|
const authorType = String(event.comment?.user?.type ?? "");
|
|
2937
3006
|
if (!rawBody.toLowerCase().includes("@kody")) return null;
|
|
2938
|
-
|
|
3007
|
+
const isBotAuthor = authorLogin === "kody-bot" || authorType === "Bot";
|
|
2939
3008
|
if (!associationAllowed(event, opts?.config)) return null;
|
|
2940
3009
|
const body = rawBody.toLowerCase();
|
|
2941
3010
|
const targetNum = Number(event.issue?.number ?? 0);
|
|
@@ -2962,6 +3031,13 @@ function autoDispatch(opts) {
|
|
|
2962
3031
|
if (!executable && !firstToken) {
|
|
2963
3032
|
executable = isPr ? opts?.config?.defaultPrExecutable ?? "fix" : opts?.config?.defaultExecutable ?? null;
|
|
2964
3033
|
}
|
|
3034
|
+
if (isBotAuthor && !consumedFirstToken) {
|
|
3035
|
+
process.stderr.write(
|
|
3036
|
+
`[kody] dispatch: ignoring bot comment without an explicit command (author=${authorLogin || authorType}, firstToken=${firstToken ?? "<none>"})
|
|
3037
|
+
`
|
|
3038
|
+
);
|
|
3039
|
+
return null;
|
|
3040
|
+
}
|
|
2965
3041
|
if (!executable) {
|
|
2966
3042
|
const profileMissing = aliased ? getProfileInputs(aliased) === null : true;
|
|
2967
3043
|
process.stderr.write(
|
|
@@ -2991,7 +3067,7 @@ function autoDispatchTyped(opts) {
|
|
|
2991
3067
|
if (legacy) return { kind: "route", ...legacy };
|
|
2992
3068
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
2993
3069
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2994
|
-
if (!eventName || !eventPath || !
|
|
3070
|
+
if (!eventName || !eventPath || !fs13.existsSync(eventPath)) {
|
|
2995
3071
|
return { kind: "silent", reason: "no GHA event context" };
|
|
2996
3072
|
}
|
|
2997
3073
|
if (eventName !== "issue_comment") {
|
|
@@ -2999,7 +3075,7 @@ function autoDispatchTyped(opts) {
|
|
|
2999
3075
|
}
|
|
3000
3076
|
let event = {};
|
|
3001
3077
|
try {
|
|
3002
|
-
event = JSON.parse(
|
|
3078
|
+
event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
|
|
3003
3079
|
} catch {
|
|
3004
3080
|
return { kind: "silent", reason: "GHA event payload unreadable" };
|
|
3005
3081
|
}
|
|
@@ -3037,7 +3113,7 @@ function dispatchScheduledWatches(opts) {
|
|
|
3037
3113
|
for (const exe of listExecutables()) {
|
|
3038
3114
|
let raw;
|
|
3039
3115
|
try {
|
|
3040
|
-
raw =
|
|
3116
|
+
raw = fs13.readFileSync(exe.profilePath, "utf-8");
|
|
3041
3117
|
} catch {
|
|
3042
3118
|
continue;
|
|
3043
3119
|
}
|
|
@@ -3157,8 +3233,8 @@ function coerceBare(spec, value) {
|
|
|
3157
3233
|
|
|
3158
3234
|
// src/executor.ts
|
|
3159
3235
|
import { execFileSync as execFileSync29, spawn as spawn9 } from "child_process";
|
|
3160
|
-
import * as
|
|
3161
|
-
import * as
|
|
3236
|
+
import * as fs41 from "fs";
|
|
3237
|
+
import * as path37 from "path";
|
|
3162
3238
|
|
|
3163
3239
|
// src/discipline.ts
|
|
3164
3240
|
var DISCIPLINE = `# Working discipline (applies to this entire task)
|
|
@@ -3209,8 +3285,8 @@ init_events();
|
|
|
3209
3285
|
init_issue();
|
|
3210
3286
|
|
|
3211
3287
|
// src/profile.ts
|
|
3212
|
-
import * as
|
|
3213
|
-
import * as
|
|
3288
|
+
import * as fs14 from "fs";
|
|
3289
|
+
import * as path13 from "path";
|
|
3214
3290
|
|
|
3215
3291
|
// src/profile-error.ts
|
|
3216
3292
|
var ProfileError = class extends Error {
|
|
@@ -3378,12 +3454,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
|
3378
3454
|
"preloadContext"
|
|
3379
3455
|
]);
|
|
3380
3456
|
function loadProfile(profilePath) {
|
|
3381
|
-
if (!
|
|
3457
|
+
if (!fs14.existsSync(profilePath)) {
|
|
3382
3458
|
throw new ProfileError(profilePath, "file not found");
|
|
3383
3459
|
}
|
|
3384
3460
|
let raw;
|
|
3385
3461
|
try {
|
|
3386
|
-
raw = JSON.parse(
|
|
3462
|
+
raw = JSON.parse(fs14.readFileSync(profilePath, "utf-8"));
|
|
3387
3463
|
} catch (err) {
|
|
3388
3464
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
3389
3465
|
}
|
|
@@ -3394,7 +3470,7 @@ function loadProfile(profilePath) {
|
|
|
3394
3470
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
3395
3471
|
if (unknownKeys.length > 0) {
|
|
3396
3472
|
process.stderr.write(
|
|
3397
|
-
`[kody profile] ${
|
|
3473
|
+
`[kody profile] ${path13.basename(path13.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
3398
3474
|
`
|
|
3399
3475
|
);
|
|
3400
3476
|
}
|
|
@@ -3455,7 +3531,7 @@ function loadProfile(profilePath) {
|
|
|
3455
3531
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
3456
3532
|
// flip to true after end-to-end verification.
|
|
3457
3533
|
preloadContext: r.preloadContext === true,
|
|
3458
|
-
dir:
|
|
3534
|
+
dir: path13.dirname(profilePath)
|
|
3459
3535
|
};
|
|
3460
3536
|
if (lifecycle) {
|
|
3461
3537
|
applyLifecycle(profile, profilePath);
|
|
@@ -3826,9 +3902,9 @@ function errMsg(err) {
|
|
|
3826
3902
|
|
|
3827
3903
|
// src/litellm.ts
|
|
3828
3904
|
import { execFileSync as execFileSync4, spawn as spawn3 } from "child_process";
|
|
3829
|
-
import * as
|
|
3905
|
+
import * as fs15 from "fs";
|
|
3830
3906
|
import * as os2 from "os";
|
|
3831
|
-
import * as
|
|
3907
|
+
import * as path14 from "path";
|
|
3832
3908
|
async function checkLitellmHealth(url) {
|
|
3833
3909
|
try {
|
|
3834
3910
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -3880,13 +3956,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3880
3956
|
let child;
|
|
3881
3957
|
let logPath;
|
|
3882
3958
|
const spawnProxy = () => {
|
|
3883
|
-
const configPath =
|
|
3884
|
-
|
|
3959
|
+
const configPath = path14.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
3960
|
+
fs15.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
3885
3961
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
3886
|
-
const nextLogPath =
|
|
3887
|
-
const outFd =
|
|
3962
|
+
const nextLogPath = path14.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
3963
|
+
const outFd = fs15.openSync(nextLogPath, "w");
|
|
3888
3964
|
child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
3889
|
-
|
|
3965
|
+
fs15.closeSync(outFd);
|
|
3890
3966
|
logPath = nextLogPath;
|
|
3891
3967
|
};
|
|
3892
3968
|
const waitForHealth = async () => {
|
|
@@ -3900,7 +3976,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3900
3976
|
const readLogTail = () => {
|
|
3901
3977
|
if (!logPath) return "";
|
|
3902
3978
|
try {
|
|
3903
|
-
return
|
|
3979
|
+
return fs15.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
3904
3980
|
} catch {
|
|
3905
3981
|
return "";
|
|
3906
3982
|
}
|
|
@@ -3939,10 +4015,10 @@ ${tail}`
|
|
|
3939
4015
|
return { url, kill: killChild, ensureHealthy };
|
|
3940
4016
|
}
|
|
3941
4017
|
function readDotenvApiKeys(projectDir) {
|
|
3942
|
-
const dotenvPath =
|
|
3943
|
-
if (!
|
|
4018
|
+
const dotenvPath = path14.join(projectDir, ".env");
|
|
4019
|
+
if (!fs15.existsSync(dotenvPath)) return {};
|
|
3944
4020
|
const result = {};
|
|
3945
|
-
for (const rawLine of
|
|
4021
|
+
for (const rawLine of fs15.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
3946
4022
|
const line = rawLine.trim();
|
|
3947
4023
|
if (!line || line.startsWith("#")) continue;
|
|
3948
4024
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -3965,25 +4041,25 @@ function stripBlockingEnv(env) {
|
|
|
3965
4041
|
}
|
|
3966
4042
|
|
|
3967
4043
|
// src/subagents.ts
|
|
3968
|
-
import * as
|
|
3969
|
-
import * as
|
|
4044
|
+
import * as fs17 from "fs";
|
|
4045
|
+
import * as path16 from "path";
|
|
3970
4046
|
|
|
3971
4047
|
// src/scripts/buildSyntheticPlugin.ts
|
|
3972
|
-
import * as
|
|
4048
|
+
import * as fs16 from "fs";
|
|
3973
4049
|
import * as os3 from "os";
|
|
3974
|
-
import * as
|
|
4050
|
+
import * as path15 from "path";
|
|
3975
4051
|
function getPluginsCatalogRoot() {
|
|
3976
|
-
const here =
|
|
4052
|
+
const here = path15.dirname(new URL(import.meta.url).pathname);
|
|
3977
4053
|
const candidates = [
|
|
3978
|
-
|
|
4054
|
+
path15.join(here, "..", "plugins"),
|
|
3979
4055
|
// dev: src/scripts → src/plugins
|
|
3980
|
-
|
|
4056
|
+
path15.join(here, "..", "..", "plugins"),
|
|
3981
4057
|
// built: dist/scripts → dist/plugins
|
|
3982
|
-
|
|
4058
|
+
path15.join(here, "..", "..", "src", "plugins")
|
|
3983
4059
|
// fallback
|
|
3984
4060
|
];
|
|
3985
4061
|
for (const c of candidates) {
|
|
3986
|
-
if (
|
|
4062
|
+
if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
|
|
3987
4063
|
}
|
|
3988
4064
|
return candidates[0];
|
|
3989
4065
|
}
|
|
@@ -3993,45 +4069,45 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
3993
4069
|
if (!needsSynthetic) return;
|
|
3994
4070
|
const catalog = getPluginsCatalogRoot();
|
|
3995
4071
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3996
|
-
const root =
|
|
3997
|
-
|
|
4072
|
+
const root = path15.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
4073
|
+
fs16.mkdirSync(path15.join(root, ".claude-plugin"), { recursive: true });
|
|
3998
4074
|
const resolvePart = (bucket, entry) => {
|
|
3999
|
-
const local =
|
|
4000
|
-
if (
|
|
4001
|
-
const central =
|
|
4002
|
-
if (
|
|
4075
|
+
const local = path15.join(profile.dir, bucket, entry);
|
|
4076
|
+
if (fs16.existsSync(local)) return local;
|
|
4077
|
+
const central = path15.join(catalog, bucket, entry);
|
|
4078
|
+
if (fs16.existsSync(central)) return central;
|
|
4003
4079
|
throw new Error(
|
|
4004
4080
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
4005
4081
|
);
|
|
4006
4082
|
};
|
|
4007
4083
|
if (cc.skills.length > 0) {
|
|
4008
|
-
const dst =
|
|
4009
|
-
|
|
4084
|
+
const dst = path15.join(root, "skills");
|
|
4085
|
+
fs16.mkdirSync(dst, { recursive: true });
|
|
4010
4086
|
for (const name of cc.skills) {
|
|
4011
|
-
copyDir(resolvePart("skills", name),
|
|
4087
|
+
copyDir(resolvePart("skills", name), path15.join(dst, name));
|
|
4012
4088
|
}
|
|
4013
4089
|
}
|
|
4014
4090
|
if (cc.commands.length > 0) {
|
|
4015
|
-
const dst =
|
|
4016
|
-
|
|
4091
|
+
const dst = path15.join(root, "commands");
|
|
4092
|
+
fs16.mkdirSync(dst, { recursive: true });
|
|
4017
4093
|
for (const name of cc.commands) {
|
|
4018
|
-
|
|
4094
|
+
fs16.copyFileSync(resolvePart("commands", `${name}.md`), path15.join(dst, `${name}.md`));
|
|
4019
4095
|
}
|
|
4020
4096
|
}
|
|
4021
4097
|
if (cc.hooks.length > 0) {
|
|
4022
|
-
const dst =
|
|
4023
|
-
|
|
4098
|
+
const dst = path15.join(root, "hooks");
|
|
4099
|
+
fs16.mkdirSync(dst, { recursive: true });
|
|
4024
4100
|
const merged = { hooks: {} };
|
|
4025
4101
|
for (const name of cc.hooks) {
|
|
4026
4102
|
const src = resolvePart("hooks", `${name}.json`);
|
|
4027
|
-
const parsed = JSON.parse(
|
|
4103
|
+
const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
|
|
4028
4104
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
4029
4105
|
if (!Array.isArray(entries)) continue;
|
|
4030
4106
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
4031
4107
|
merged.hooks[event].push(...entries);
|
|
4032
4108
|
}
|
|
4033
4109
|
}
|
|
4034
|
-
|
|
4110
|
+
fs16.writeFileSync(path15.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
4035
4111
|
`);
|
|
4036
4112
|
}
|
|
4037
4113
|
const manifest = {
|
|
@@ -4041,17 +4117,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
4041
4117
|
};
|
|
4042
4118
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
4043
4119
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
4044
|
-
|
|
4120
|
+
fs16.writeFileSync(path15.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
4045
4121
|
`);
|
|
4046
4122
|
ctx.data.syntheticPluginPath = root;
|
|
4047
4123
|
};
|
|
4048
4124
|
function copyDir(src, dst) {
|
|
4049
|
-
|
|
4050
|
-
for (const ent of
|
|
4051
|
-
const s =
|
|
4052
|
-
const d =
|
|
4125
|
+
fs16.mkdirSync(dst, { recursive: true });
|
|
4126
|
+
for (const ent of fs16.readdirSync(src, { withFileTypes: true })) {
|
|
4127
|
+
const s = path15.join(src, ent.name);
|
|
4128
|
+
const d = path15.join(dst, ent.name);
|
|
4053
4129
|
if (ent.isDirectory()) copyDir(s, d);
|
|
4054
|
-
else if (ent.isFile())
|
|
4130
|
+
else if (ent.isFile()) fs16.copyFileSync(s, d);
|
|
4055
4131
|
}
|
|
4056
4132
|
}
|
|
4057
4133
|
|
|
@@ -4068,10 +4144,10 @@ function splitFrontmatter(raw) {
|
|
|
4068
4144
|
return { fm, body: (match[2] ?? "").trim() };
|
|
4069
4145
|
}
|
|
4070
4146
|
function resolveAgentFile(profileDir, name) {
|
|
4071
|
-
const local =
|
|
4072
|
-
if (
|
|
4073
|
-
const central =
|
|
4074
|
-
if (
|
|
4147
|
+
const local = path16.join(profileDir, "agents", `${name}.md`);
|
|
4148
|
+
if (fs17.existsSync(local)) return local;
|
|
4149
|
+
const central = path16.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
4150
|
+
if (fs17.existsSync(central)) return central;
|
|
4075
4151
|
throw new Error(
|
|
4076
4152
|
`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`
|
|
4077
4153
|
);
|
|
@@ -4081,7 +4157,7 @@ function loadSubagents(profile) {
|
|
|
4081
4157
|
if (!names || names.length === 0) return void 0;
|
|
4082
4158
|
const agents = {};
|
|
4083
4159
|
for (const name of names) {
|
|
4084
|
-
const { fm, body } = splitFrontmatter(
|
|
4160
|
+
const { fm, body } = splitFrontmatter(fs17.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
|
|
4085
4161
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
4086
4162
|
const def = {
|
|
4087
4163
|
description: fm.description ?? `Subagent ${name}`,
|
|
@@ -4177,8 +4253,8 @@ function pushWithRetry(opts = {}) {
|
|
|
4177
4253
|
}
|
|
4178
4254
|
|
|
4179
4255
|
// src/commit.ts
|
|
4180
|
-
import * as
|
|
4181
|
-
import * as
|
|
4256
|
+
import * as fs18 from "fs";
|
|
4257
|
+
import * as path17 from "path";
|
|
4182
4258
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
4183
4259
|
".kody/",
|
|
4184
4260
|
".kody-engine/",
|
|
@@ -4234,18 +4310,18 @@ function tryGit(args, cwd) {
|
|
|
4234
4310
|
}
|
|
4235
4311
|
function abortUnfinishedGitOps(cwd) {
|
|
4236
4312
|
const aborted = [];
|
|
4237
|
-
const gitDir =
|
|
4238
|
-
if (!
|
|
4239
|
-
if (
|
|
4313
|
+
const gitDir = path17.join(cwd ?? process.cwd(), ".git");
|
|
4314
|
+
if (!fs18.existsSync(gitDir)) return aborted;
|
|
4315
|
+
if (fs18.existsSync(path17.join(gitDir, "MERGE_HEAD"))) {
|
|
4240
4316
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
4241
4317
|
}
|
|
4242
|
-
if (
|
|
4318
|
+
if (fs18.existsSync(path17.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
4243
4319
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
4244
4320
|
}
|
|
4245
|
-
if (
|
|
4321
|
+
if (fs18.existsSync(path17.join(gitDir, "REVERT_HEAD"))) {
|
|
4246
4322
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
4247
4323
|
}
|
|
4248
|
-
if (
|
|
4324
|
+
if (fs18.existsSync(path17.join(gitDir, "rebase-merge")) || fs18.existsSync(path17.join(gitDir, "rebase-apply"))) {
|
|
4249
4325
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
4250
4326
|
}
|
|
4251
4327
|
try {
|
|
@@ -4301,7 +4377,7 @@ function normalizeCommitMessage(raw) {
|
|
|
4301
4377
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
4302
4378
|
const allChanged = listChangedFiles(cwd);
|
|
4303
4379
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
4304
|
-
const mergeHeadExists =
|
|
4380
|
+
const mergeHeadExists = fs18.existsSync(path17.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
4305
4381
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
4306
4382
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
4307
4383
|
}
|
|
@@ -4562,59 +4638,92 @@ function writeTaskState(target, number, state, cwd) {
|
|
|
4562
4638
|
|
|
4563
4639
|
// src/scripts/advanceFlow.ts
|
|
4564
4640
|
var API_TIMEOUT_MS3 = 3e4;
|
|
4641
|
+
var FLOW_HOP_CAP = 25;
|
|
4642
|
+
function ghComment(issueNumber, body, cwd, label) {
|
|
4643
|
+
try {
|
|
4644
|
+
execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
4645
|
+
timeout: API_TIMEOUT_MS3,
|
|
4646
|
+
cwd,
|
|
4647
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4648
|
+
});
|
|
4649
|
+
} catch (err) {
|
|
4650
|
+
process.stderr.write(
|
|
4651
|
+
`[kody advanceFlow] ${label} on issue #${issueNumber} failed: ${err instanceof Error ? err.message : String(err)}
|
|
4652
|
+
`
|
|
4653
|
+
);
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4565
4656
|
var advanceFlow = async (ctx, profile) => {
|
|
4566
4657
|
const state = ctx.data.taskState;
|
|
4567
4658
|
const flow = state?.flow;
|
|
4568
4659
|
if (!flow?.issueNumber) return;
|
|
4660
|
+
const curState = state;
|
|
4661
|
+
let issueState;
|
|
4662
|
+
try {
|
|
4663
|
+
issueState = readTaskState("issue", flow.issueNumber, ctx.cwd);
|
|
4664
|
+
} catch {
|
|
4665
|
+
issueState = curState;
|
|
4666
|
+
}
|
|
4569
4667
|
const targetType = ctx.data.commentTargetType;
|
|
4570
4668
|
const action = ctx.data.action;
|
|
4669
|
+
let nextIssueState = issueState;
|
|
4571
4670
|
if (targetType === "pr" && action) {
|
|
4671
|
+
nextIssueState = reduce(issueState, profile.name, action, profile.phase);
|
|
4672
|
+
if (state?.core.prUrl && !nextIssueState.core.prUrl) nextIssueState.core.prUrl = state.core.prUrl;
|
|
4673
|
+
}
|
|
4674
|
+
const prevHops = issueState.flow?.hops ?? flow.hops ?? 0;
|
|
4675
|
+
const hops = prevHops + 1;
|
|
4676
|
+
if (hops > FLOW_HOP_CAP) {
|
|
4677
|
+
nextIssueState.flow = void 0;
|
|
4572
4678
|
try {
|
|
4573
|
-
|
|
4574
|
-
issueState.flow = flow;
|
|
4575
|
-
const next = reduce(issueState, profile.name, action, profile.phase);
|
|
4576
|
-
if (state?.core.prUrl && !next.core.prUrl) next.core.prUrl = state.core.prUrl;
|
|
4577
|
-
next.flow = flow;
|
|
4578
|
-
writeTaskState("issue", flow.issueNumber, next, ctx.cwd);
|
|
4679
|
+
writeTaskState("issue", flow.issueNumber, nextIssueState, ctx.cwd);
|
|
4579
4680
|
} catch (err) {
|
|
4580
4681
|
process.stderr.write(
|
|
4581
|
-
`[kody advanceFlow] failed to
|
|
4682
|
+
`[kody advanceFlow] failed to clear looping flow on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
4582
4683
|
`
|
|
4583
4684
|
);
|
|
4584
4685
|
}
|
|
4686
|
+
ghComment(
|
|
4687
|
+
flow.issueNumber,
|
|
4688
|
+
`\u26A0\uFE0F kody: flow \`${flow.name}\` stopped after ${FLOW_HOP_CAP} steps without completing (loop guard). Re-trigger manually if this was intended.`,
|
|
4689
|
+
ctx.cwd,
|
|
4690
|
+
"loop-guard notice"
|
|
4691
|
+
);
|
|
4692
|
+
process.stderr.write(
|
|
4693
|
+
`[kody advanceFlow] flow '${flow.name}' on issue #${flow.issueNumber} hit hop cap ${FLOW_HOP_CAP}; stopping
|
|
4694
|
+
`
|
|
4695
|
+
);
|
|
4696
|
+
return;
|
|
4585
4697
|
}
|
|
4586
|
-
|
|
4698
|
+
nextIssueState.flow = { ...flow, hops };
|
|
4587
4699
|
try {
|
|
4588
|
-
|
|
4589
|
-
timeout: API_TIMEOUT_MS3,
|
|
4590
|
-
cwd: ctx.cwd,
|
|
4591
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4592
|
-
});
|
|
4700
|
+
writeTaskState("issue", flow.issueNumber, nextIssueState, ctx.cwd);
|
|
4593
4701
|
} catch (err) {
|
|
4594
4702
|
process.stderr.write(
|
|
4595
|
-
`[kody advanceFlow] failed to
|
|
4703
|
+
`[kody advanceFlow] failed to persist hop count on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
4596
4704
|
`
|
|
4597
4705
|
);
|
|
4598
4706
|
}
|
|
4707
|
+
ghComment(flow.issueNumber, `@kody ${flow.name}`, ctx.cwd, "re-trigger orchestrator");
|
|
4599
4708
|
};
|
|
4600
4709
|
|
|
4601
4710
|
// src/scripts/brainServe.ts
|
|
4602
4711
|
init_repoWorkspace();
|
|
4603
4712
|
import { createServer } from "http";
|
|
4604
|
-
import * as
|
|
4605
|
-
import * as
|
|
4713
|
+
import * as fs20 from "fs";
|
|
4714
|
+
import * as path19 from "path";
|
|
4606
4715
|
|
|
4607
4716
|
// src/scripts/brainTurnLog.ts
|
|
4608
|
-
import * as
|
|
4609
|
-
import * as
|
|
4717
|
+
import * as fs19 from "fs";
|
|
4718
|
+
import * as path18 from "path";
|
|
4610
4719
|
var live = /* @__PURE__ */ new Map();
|
|
4611
4720
|
function eventsPath(dir, chatId) {
|
|
4612
|
-
return
|
|
4721
|
+
return path18.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
4613
4722
|
}
|
|
4614
4723
|
function lastPersistedSeq(dir, chatId) {
|
|
4615
4724
|
const p = eventsPath(dir, chatId);
|
|
4616
|
-
if (!
|
|
4617
|
-
const lines =
|
|
4725
|
+
if (!fs19.existsSync(p)) return 0;
|
|
4726
|
+
const lines = fs19.readFileSync(p, "utf-8").split("\n").filter(Boolean);
|
|
4618
4727
|
if (lines.length === 0) return 0;
|
|
4619
4728
|
try {
|
|
4620
4729
|
return JSON.parse(lines[lines.length - 1]).seq || 0;
|
|
@@ -4624,9 +4733,9 @@ function lastPersistedSeq(dir, chatId) {
|
|
|
4624
4733
|
}
|
|
4625
4734
|
function readSince(dir, chatId, since) {
|
|
4626
4735
|
const p = eventsPath(dir, chatId);
|
|
4627
|
-
if (!
|
|
4736
|
+
if (!fs19.existsSync(p)) return [];
|
|
4628
4737
|
const out = [];
|
|
4629
|
-
for (const line of
|
|
4738
|
+
for (const line of fs19.readFileSync(p, "utf-8").split("\n")) {
|
|
4630
4739
|
if (!line) continue;
|
|
4631
4740
|
try {
|
|
4632
4741
|
const rec = JSON.parse(line);
|
|
@@ -4652,12 +4761,12 @@ function beginTurn(dir, chatId) {
|
|
|
4652
4761
|
};
|
|
4653
4762
|
live.set(chatId, state);
|
|
4654
4763
|
const p = eventsPath(dir, chatId);
|
|
4655
|
-
|
|
4764
|
+
fs19.mkdirSync(path18.dirname(p), { recursive: true });
|
|
4656
4765
|
return (event) => {
|
|
4657
4766
|
state.seq += 1;
|
|
4658
4767
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
4659
4768
|
try {
|
|
4660
|
-
|
|
4769
|
+
fs19.appendFileSync(p, JSON.stringify(rec) + "\n");
|
|
4661
4770
|
} catch (err) {
|
|
4662
4771
|
process.stderr.write(
|
|
4663
4772
|
`[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -4695,7 +4804,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
|
|
|
4695
4804
|
event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
|
|
4696
4805
|
};
|
|
4697
4806
|
try {
|
|
4698
|
-
|
|
4807
|
+
fs19.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
|
|
4699
4808
|
} catch {
|
|
4700
4809
|
}
|
|
4701
4810
|
state.status = "ended";
|
|
@@ -4921,7 +5030,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4921
5030
|
const repo = strField(body, "repo");
|
|
4922
5031
|
const repoToken = strField(body, "repoToken");
|
|
4923
5032
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
4924
|
-
|
|
5033
|
+
fs20.mkdirSync(path19.dirname(sessionFile), { recursive: true });
|
|
4925
5034
|
appendTurn(sessionFile, {
|
|
4926
5035
|
role: "user",
|
|
4927
5036
|
content: message,
|
|
@@ -4968,7 +5077,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4968
5077
|
function buildServer(opts) {
|
|
4969
5078
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
4970
5079
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
4971
|
-
const reposRoot = opts.reposRoot ??
|
|
5080
|
+
const reposRoot = opts.reposRoot ?? path19.join(path19.dirname(path19.resolve(opts.cwd)), "repos");
|
|
4972
5081
|
return createServer(async (req, res) => {
|
|
4973
5082
|
if (!req.method || !req.url) {
|
|
4974
5083
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -5223,13 +5332,13 @@ function defaultLabelMap() {
|
|
|
5223
5332
|
}
|
|
5224
5333
|
|
|
5225
5334
|
// src/scripts/commitAndPush.ts
|
|
5226
|
-
import * as
|
|
5227
|
-
import * as
|
|
5335
|
+
import * as fs22 from "fs";
|
|
5336
|
+
import * as path21 from "path";
|
|
5228
5337
|
init_events();
|
|
5229
5338
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
5230
5339
|
function sentinelPathForStage(cwd, profileName) {
|
|
5231
5340
|
const runId = resolveRunId();
|
|
5232
|
-
return
|
|
5341
|
+
return path21.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
5233
5342
|
}
|
|
5234
5343
|
var commitAndPush2 = async (ctx, profile) => {
|
|
5235
5344
|
const branch = ctx.data.branch;
|
|
@@ -5239,9 +5348,9 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
5239
5348
|
}
|
|
5240
5349
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
5241
5350
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
5242
|
-
if (sentinel &&
|
|
5351
|
+
if (sentinel && fs22.existsSync(sentinel)) {
|
|
5243
5352
|
try {
|
|
5244
|
-
const replay = JSON.parse(
|
|
5353
|
+
const replay = JSON.parse(fs22.readFileSync(sentinel, "utf-8"));
|
|
5245
5354
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
5246
5355
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
5247
5356
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -5294,8 +5403,8 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
5294
5403
|
const result = ctx.data.commitResult;
|
|
5295
5404
|
if (sentinel && result?.committed) {
|
|
5296
5405
|
try {
|
|
5297
|
-
|
|
5298
|
-
|
|
5406
|
+
fs22.mkdirSync(path21.dirname(sentinel), { recursive: true });
|
|
5407
|
+
fs22.writeFileSync(
|
|
5299
5408
|
sentinel,
|
|
5300
5409
|
JSON.stringify(
|
|
5301
5410
|
{
|
|
@@ -5356,14 +5465,14 @@ function ensureStateBranch(owner, repo, cwd) {
|
|
|
5356
5465
|
}
|
|
5357
5466
|
|
|
5358
5467
|
// src/goal/state.ts
|
|
5359
|
-
import * as
|
|
5360
|
-
import * as
|
|
5468
|
+
import * as fs23 from "fs";
|
|
5469
|
+
import * as path22 from "path";
|
|
5361
5470
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
5362
5471
|
var GoalStateError = class extends Error {
|
|
5363
|
-
constructor(
|
|
5364
|
-
super(`Invalid goal state at ${
|
|
5472
|
+
constructor(path40, message) {
|
|
5473
|
+
super(`Invalid goal state at ${path40}:
|
|
5365
5474
|
${message}`);
|
|
5366
|
-
this.path =
|
|
5475
|
+
this.path = path40;
|
|
5367
5476
|
this.name = "GoalStateError";
|
|
5368
5477
|
}
|
|
5369
5478
|
path;
|
|
@@ -5503,20 +5612,20 @@ function describeCommitMessage(goal) {
|
|
|
5503
5612
|
}
|
|
5504
5613
|
|
|
5505
5614
|
// src/scripts/composePrompt.ts
|
|
5506
|
-
import * as
|
|
5507
|
-
import * as
|
|
5615
|
+
import * as fs24 from "fs";
|
|
5616
|
+
import * as path23 from "path";
|
|
5508
5617
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
5509
5618
|
var composePrompt = async (ctx, profile) => {
|
|
5510
5619
|
const explicit = ctx.data.promptTemplate;
|
|
5511
5620
|
const mode = ctx.args.mode;
|
|
5512
5621
|
const candidates = [
|
|
5513
|
-
explicit ?
|
|
5514
|
-
mode ?
|
|
5515
|
-
|
|
5622
|
+
explicit ? path23.join(profile.dir, explicit) : null,
|
|
5623
|
+
mode ? path23.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
5624
|
+
path23.join(profile.dir, "prompt.md")
|
|
5516
5625
|
].filter(Boolean);
|
|
5517
5626
|
let templatePath = "";
|
|
5518
5627
|
for (const c of candidates) {
|
|
5519
|
-
if (
|
|
5628
|
+
if (fs24.existsSync(c)) {
|
|
5520
5629
|
templatePath = c;
|
|
5521
5630
|
break;
|
|
5522
5631
|
}
|
|
@@ -5524,7 +5633,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
5524
5633
|
if (!templatePath) {
|
|
5525
5634
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
5526
5635
|
}
|
|
5527
|
-
const template =
|
|
5636
|
+
const template = fs24.readFileSync(templatePath, "utf-8");
|
|
5528
5637
|
const tokens = {
|
|
5529
5638
|
...stringifyAll(ctx.args, "args."),
|
|
5530
5639
|
...stringifyAll(ctx.data, ""),
|
|
@@ -6302,15 +6411,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
|
6302
6411
|
|
|
6303
6412
|
// src/scripts/diagMcp.ts
|
|
6304
6413
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
6305
|
-
import * as
|
|
6414
|
+
import * as fs25 from "fs";
|
|
6306
6415
|
import * as os4 from "os";
|
|
6307
|
-
import * as
|
|
6416
|
+
import * as path24 from "path";
|
|
6308
6417
|
var diagMcp = async (_ctx) => {
|
|
6309
6418
|
const home = os4.homedir();
|
|
6310
|
-
const cacheDir =
|
|
6419
|
+
const cacheDir = path24.join(home, ".cache", "ms-playwright");
|
|
6311
6420
|
let entries = [];
|
|
6312
6421
|
try {
|
|
6313
|
-
entries =
|
|
6422
|
+
entries = fs25.readdirSync(cacheDir);
|
|
6314
6423
|
} catch {
|
|
6315
6424
|
}
|
|
6316
6425
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -6336,17 +6445,17 @@ var diagMcp = async (_ctx) => {
|
|
|
6336
6445
|
};
|
|
6337
6446
|
|
|
6338
6447
|
// src/scripts/discoverQaContext.ts
|
|
6339
|
-
import * as
|
|
6340
|
-
import * as
|
|
6448
|
+
import * as fs27 from "fs";
|
|
6449
|
+
import * as path26 from "path";
|
|
6341
6450
|
|
|
6342
6451
|
// src/scripts/frameworkDetectors.ts
|
|
6343
|
-
import * as
|
|
6344
|
-
import * as
|
|
6452
|
+
import * as fs26 from "fs";
|
|
6453
|
+
import * as path25 from "path";
|
|
6345
6454
|
function detectFrameworks(cwd) {
|
|
6346
6455
|
const out = [];
|
|
6347
6456
|
let deps = {};
|
|
6348
6457
|
try {
|
|
6349
|
-
const pkg = JSON.parse(
|
|
6458
|
+
const pkg = JSON.parse(fs26.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
|
|
6350
6459
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6351
6460
|
} catch {
|
|
6352
6461
|
return out;
|
|
@@ -6383,7 +6492,7 @@ function detectFrameworks(cwd) {
|
|
|
6383
6492
|
}
|
|
6384
6493
|
function findFile(cwd, candidates) {
|
|
6385
6494
|
for (const c of candidates) {
|
|
6386
|
-
if (
|
|
6495
|
+
if (fs26.existsSync(path25.join(cwd, c))) return c;
|
|
6387
6496
|
}
|
|
6388
6497
|
return null;
|
|
6389
6498
|
}
|
|
@@ -6396,18 +6505,18 @@ var COLLECTION_DIRS = [
|
|
|
6396
6505
|
function discoverPayloadCollections(cwd) {
|
|
6397
6506
|
const out = [];
|
|
6398
6507
|
for (const dir of COLLECTION_DIRS) {
|
|
6399
|
-
const full =
|
|
6400
|
-
if (!
|
|
6508
|
+
const full = path25.join(cwd, dir);
|
|
6509
|
+
if (!fs26.existsSync(full)) continue;
|
|
6401
6510
|
let files;
|
|
6402
6511
|
try {
|
|
6403
|
-
files =
|
|
6512
|
+
files = fs26.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
6404
6513
|
} catch {
|
|
6405
6514
|
continue;
|
|
6406
6515
|
}
|
|
6407
6516
|
for (const file of files) {
|
|
6408
6517
|
try {
|
|
6409
|
-
const filePath =
|
|
6410
|
-
const content =
|
|
6518
|
+
const filePath = path25.join(full, file);
|
|
6519
|
+
const content = fs26.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
6411
6520
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
6412
6521
|
if (!slugMatch) continue;
|
|
6413
6522
|
const slug = slugMatch[1];
|
|
@@ -6421,7 +6530,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
6421
6530
|
out.push({
|
|
6422
6531
|
name,
|
|
6423
6532
|
slug,
|
|
6424
|
-
filePath:
|
|
6533
|
+
filePath: path25.relative(cwd, filePath),
|
|
6425
6534
|
fields: fields.slice(0, 20),
|
|
6426
6535
|
hasAdmin
|
|
6427
6536
|
});
|
|
@@ -6435,28 +6544,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
6435
6544
|
function discoverAdminComponents(cwd, collections) {
|
|
6436
6545
|
const out = [];
|
|
6437
6546
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
6438
|
-
const full =
|
|
6439
|
-
if (!
|
|
6547
|
+
const full = path25.join(cwd, dir);
|
|
6548
|
+
if (!fs26.existsSync(full)) continue;
|
|
6440
6549
|
let entries;
|
|
6441
6550
|
try {
|
|
6442
|
-
entries =
|
|
6551
|
+
entries = fs26.readdirSync(full, { withFileTypes: true });
|
|
6443
6552
|
} catch {
|
|
6444
6553
|
continue;
|
|
6445
6554
|
}
|
|
6446
6555
|
for (const entry of entries) {
|
|
6447
|
-
const entryPath =
|
|
6556
|
+
const entryPath = path25.join(full, entry.name);
|
|
6448
6557
|
let name;
|
|
6449
6558
|
let filePath;
|
|
6450
6559
|
if (entry.isDirectory()) {
|
|
6451
6560
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
6452
|
-
(f) =>
|
|
6561
|
+
(f) => fs26.existsSync(path25.join(entryPath, f))
|
|
6453
6562
|
);
|
|
6454
6563
|
if (!indexFile) continue;
|
|
6455
6564
|
name = entry.name;
|
|
6456
|
-
filePath =
|
|
6565
|
+
filePath = path25.relative(cwd, path25.join(entryPath, indexFile));
|
|
6457
6566
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
6458
6567
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
6459
|
-
filePath =
|
|
6568
|
+
filePath = path25.relative(cwd, entryPath);
|
|
6460
6569
|
} else {
|
|
6461
6570
|
continue;
|
|
6462
6571
|
}
|
|
@@ -6464,7 +6573,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6464
6573
|
if (collections) {
|
|
6465
6574
|
for (const col of collections) {
|
|
6466
6575
|
try {
|
|
6467
|
-
const colContent =
|
|
6576
|
+
const colContent = fs26.readFileSync(path25.join(cwd, col.filePath), "utf-8");
|
|
6468
6577
|
if (colContent.includes(name)) {
|
|
6469
6578
|
usedInCollection = col.slug;
|
|
6470
6579
|
break;
|
|
@@ -6483,8 +6592,8 @@ function scanApiRoutes(cwd) {
|
|
|
6483
6592
|
const out = [];
|
|
6484
6593
|
const appDirs = ["src/app", "app"];
|
|
6485
6594
|
for (const appDir of appDirs) {
|
|
6486
|
-
const apiDir =
|
|
6487
|
-
if (!
|
|
6595
|
+
const apiDir = path25.join(cwd, appDir, "api");
|
|
6596
|
+
if (!fs26.existsSync(apiDir)) continue;
|
|
6488
6597
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
6489
6598
|
break;
|
|
6490
6599
|
}
|
|
@@ -6493,14 +6602,14 @@ function scanApiRoutes(cwd) {
|
|
|
6493
6602
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
6494
6603
|
let entries;
|
|
6495
6604
|
try {
|
|
6496
|
-
entries =
|
|
6605
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
6497
6606
|
} catch {
|
|
6498
6607
|
return;
|
|
6499
6608
|
}
|
|
6500
6609
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
6501
6610
|
if (routeFile) {
|
|
6502
6611
|
try {
|
|
6503
|
-
const content =
|
|
6612
|
+
const content = fs26.readFileSync(path25.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
6504
6613
|
const methods = HTTP_METHODS.filter(
|
|
6505
6614
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
6506
6615
|
);
|
|
@@ -6508,7 +6617,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6508
6617
|
out.push({
|
|
6509
6618
|
path: prefix,
|
|
6510
6619
|
methods,
|
|
6511
|
-
filePath:
|
|
6620
|
+
filePath: path25.relative(cwd, path25.join(dir, routeFile.name))
|
|
6512
6621
|
});
|
|
6513
6622
|
}
|
|
6514
6623
|
} catch {
|
|
@@ -6519,7 +6628,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6519
6628
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
6520
6629
|
let segment = entry.name;
|
|
6521
6630
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
6522
|
-
walkApiRoutes(
|
|
6631
|
+
walkApiRoutes(path25.join(dir, entry.name), prefix, cwd, out);
|
|
6523
6632
|
continue;
|
|
6524
6633
|
}
|
|
6525
6634
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -6527,7 +6636,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6527
6636
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
6528
6637
|
segment = `:${segment.slice(1, -1)}`;
|
|
6529
6638
|
}
|
|
6530
|
-
walkApiRoutes(
|
|
6639
|
+
walkApiRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
6531
6640
|
}
|
|
6532
6641
|
}
|
|
6533
6642
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -6547,10 +6656,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
6547
6656
|
function scanEnvVars(cwd) {
|
|
6548
6657
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
6549
6658
|
for (const envFile of candidates) {
|
|
6550
|
-
const envPath =
|
|
6551
|
-
if (!
|
|
6659
|
+
const envPath = path25.join(cwd, envFile);
|
|
6660
|
+
if (!fs26.existsSync(envPath)) continue;
|
|
6552
6661
|
try {
|
|
6553
|
-
const content =
|
|
6662
|
+
const content = fs26.readFileSync(envPath, "utf-8");
|
|
6554
6663
|
const vars = [];
|
|
6555
6664
|
for (const line of content.split("\n")) {
|
|
6556
6665
|
const trimmed = line.trim();
|
|
@@ -6598,9 +6707,9 @@ function runQaDiscovery(cwd) {
|
|
|
6598
6707
|
}
|
|
6599
6708
|
function detectDevServer(cwd, out) {
|
|
6600
6709
|
try {
|
|
6601
|
-
const pkg = JSON.parse(
|
|
6710
|
+
const pkg = JSON.parse(fs27.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
|
|
6602
6711
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6603
|
-
const pm =
|
|
6712
|
+
const pm = fs27.existsSync(path26.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs27.existsSync(path26.join(cwd, "yarn.lock")) ? "yarn" : fs27.existsSync(path26.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
6604
6713
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
6605
6714
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
6606
6715
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -6610,8 +6719,8 @@ function detectDevServer(cwd, out) {
|
|
|
6610
6719
|
function scanFrontendRoutes(cwd, out) {
|
|
6611
6720
|
const appDirs = ["src/app", "app"];
|
|
6612
6721
|
for (const appDir of appDirs) {
|
|
6613
|
-
const full =
|
|
6614
|
-
if (!
|
|
6722
|
+
const full = path26.join(cwd, appDir);
|
|
6723
|
+
if (!fs27.existsSync(full)) continue;
|
|
6615
6724
|
walkFrontendRoutes(full, "", out);
|
|
6616
6725
|
break;
|
|
6617
6726
|
}
|
|
@@ -6619,7 +6728,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
6619
6728
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
6620
6729
|
let entries;
|
|
6621
6730
|
try {
|
|
6622
|
-
entries =
|
|
6731
|
+
entries = fs27.readdirSync(dir, { withFileTypes: true });
|
|
6623
6732
|
} catch {
|
|
6624
6733
|
return;
|
|
6625
6734
|
}
|
|
@@ -6636,7 +6745,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
6636
6745
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
6637
6746
|
let segment = entry.name;
|
|
6638
6747
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
6639
|
-
walkFrontendRoutes(
|
|
6748
|
+
walkFrontendRoutes(path26.join(dir, entry.name), prefix, out);
|
|
6640
6749
|
continue;
|
|
6641
6750
|
}
|
|
6642
6751
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -6644,7 +6753,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
6644
6753
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
6645
6754
|
segment = `:${segment.slice(1, -1)}`;
|
|
6646
6755
|
}
|
|
6647
|
-
walkFrontendRoutes(
|
|
6756
|
+
walkFrontendRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
6648
6757
|
}
|
|
6649
6758
|
}
|
|
6650
6759
|
function detectAuthFiles(cwd, out) {
|
|
@@ -6661,23 +6770,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
6661
6770
|
"src/app/api/oauth"
|
|
6662
6771
|
];
|
|
6663
6772
|
for (const c of candidates) {
|
|
6664
|
-
if (
|
|
6773
|
+
if (fs27.existsSync(path26.join(cwd, c))) out.authFiles.push(c);
|
|
6665
6774
|
}
|
|
6666
6775
|
}
|
|
6667
6776
|
function detectRoles(cwd, out) {
|
|
6668
6777
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
6669
6778
|
for (const rp of rolePaths) {
|
|
6670
|
-
const dir =
|
|
6671
|
-
if (!
|
|
6779
|
+
const dir = path26.join(cwd, rp);
|
|
6780
|
+
if (!fs27.existsSync(dir)) continue;
|
|
6672
6781
|
let files;
|
|
6673
6782
|
try {
|
|
6674
|
-
files =
|
|
6783
|
+
files = fs27.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
6675
6784
|
} catch {
|
|
6676
6785
|
continue;
|
|
6677
6786
|
}
|
|
6678
6787
|
for (const f of files) {
|
|
6679
6788
|
try {
|
|
6680
|
-
const content =
|
|
6789
|
+
const content = fs27.readFileSync(path26.join(dir, f), "utf-8").slice(0, 5e3);
|
|
6681
6790
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
6682
6791
|
if (roleMatches) {
|
|
6683
6792
|
for (const m of roleMatches) {
|
|
@@ -6861,8 +6970,8 @@ function failedAction3(reason) {
|
|
|
6861
6970
|
}
|
|
6862
6971
|
|
|
6863
6972
|
// src/scripts/dispatchJobFileTicks.ts
|
|
6864
|
-
import * as
|
|
6865
|
-
import * as
|
|
6973
|
+
import * as fs29 from "fs";
|
|
6974
|
+
import * as path28 from "path";
|
|
6866
6975
|
|
|
6867
6976
|
// src/scripts/jobFrontmatter.ts
|
|
6868
6977
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -7131,8 +7240,8 @@ var ContentsApiBackend = class {
|
|
|
7131
7240
|
};
|
|
7132
7241
|
|
|
7133
7242
|
// src/scripts/jobState/localFileBackend.ts
|
|
7134
|
-
import * as
|
|
7135
|
-
import * as
|
|
7243
|
+
import * as fs28 from "fs";
|
|
7244
|
+
import * as path27 from "path";
|
|
7136
7245
|
var LocalFileBackend = class {
|
|
7137
7246
|
name = "local-file";
|
|
7138
7247
|
cwd;
|
|
@@ -7147,7 +7256,7 @@ var LocalFileBackend = class {
|
|
|
7147
7256
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
7148
7257
|
this.cwd = opts.cwd;
|
|
7149
7258
|
this.jobsDir = opts.jobsDir;
|
|
7150
|
-
this.absDir =
|
|
7259
|
+
this.absDir = path27.join(opts.cwd, opts.jobsDir);
|
|
7151
7260
|
this.owner = opts.owner;
|
|
7152
7261
|
this.repo = opts.repo;
|
|
7153
7262
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -7162,7 +7271,7 @@ var LocalFileBackend = class {
|
|
|
7162
7271
|
`);
|
|
7163
7272
|
return;
|
|
7164
7273
|
}
|
|
7165
|
-
|
|
7274
|
+
fs28.mkdirSync(this.absDir, { recursive: true });
|
|
7166
7275
|
const prefix = this.cacheKeyPrefix();
|
|
7167
7276
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
7168
7277
|
try {
|
|
@@ -7191,7 +7300,7 @@ var LocalFileBackend = class {
|
|
|
7191
7300
|
`);
|
|
7192
7301
|
return;
|
|
7193
7302
|
}
|
|
7194
|
-
if (!
|
|
7303
|
+
if (!fs28.existsSync(this.absDir)) {
|
|
7195
7304
|
return;
|
|
7196
7305
|
}
|
|
7197
7306
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -7207,11 +7316,11 @@ var LocalFileBackend = class {
|
|
|
7207
7316
|
}
|
|
7208
7317
|
load(slug) {
|
|
7209
7318
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
7210
|
-
const absPath =
|
|
7211
|
-
if (!
|
|
7319
|
+
const absPath = path27.join(this.cwd, relPath);
|
|
7320
|
+
if (!fs28.existsSync(absPath)) {
|
|
7212
7321
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
7213
7322
|
}
|
|
7214
|
-
const raw =
|
|
7323
|
+
const raw = fs28.readFileSync(absPath, "utf-8");
|
|
7215
7324
|
let parsed;
|
|
7216
7325
|
try {
|
|
7217
7326
|
parsed = JSON.parse(raw);
|
|
@@ -7228,10 +7337,10 @@ var LocalFileBackend = class {
|
|
|
7228
7337
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
7229
7338
|
return false;
|
|
7230
7339
|
}
|
|
7231
|
-
const absPath =
|
|
7232
|
-
|
|
7340
|
+
const absPath = path27.join(this.cwd, loaded.path);
|
|
7341
|
+
fs28.mkdirSync(path27.dirname(absPath), { recursive: true });
|
|
7233
7342
|
const body = JSON.stringify(next, null, 2) + "\n";
|
|
7234
|
-
|
|
7343
|
+
fs28.writeFileSync(absPath, body, "utf-8");
|
|
7235
7344
|
return true;
|
|
7236
7345
|
}
|
|
7237
7346
|
cacheKeyPrefix() {
|
|
@@ -7309,7 +7418,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
7309
7418
|
await backend.hydrate();
|
|
7310
7419
|
}
|
|
7311
7420
|
try {
|
|
7312
|
-
const slugs = listJobSlugs(
|
|
7421
|
+
const slugs = listJobSlugs(path28.join(ctx.cwd, jobsDir));
|
|
7313
7422
|
ctx.data.jobSlugCount = slugs.length;
|
|
7314
7423
|
if (slugs.length === 0) {
|
|
7315
7424
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -7422,17 +7531,17 @@ function formatAgo(ms) {
|
|
|
7422
7531
|
}
|
|
7423
7532
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
7424
7533
|
try {
|
|
7425
|
-
const raw =
|
|
7534
|
+
const raw = fs29.readFileSync(path28.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
7426
7535
|
return splitFrontmatter2(raw).frontmatter;
|
|
7427
7536
|
} catch {
|
|
7428
7537
|
return {};
|
|
7429
7538
|
}
|
|
7430
7539
|
}
|
|
7431
7540
|
function listJobSlugs(absDir) {
|
|
7432
|
-
if (!
|
|
7541
|
+
if (!fs29.existsSync(absDir)) return [];
|
|
7433
7542
|
let entries;
|
|
7434
7543
|
try {
|
|
7435
|
-
entries =
|
|
7544
|
+
entries = fs29.readdirSync(absDir, { withFileTypes: true });
|
|
7436
7545
|
} catch {
|
|
7437
7546
|
return [];
|
|
7438
7547
|
}
|
|
@@ -8225,7 +8334,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
|
|
|
8225
8334
|
|
|
8226
8335
|
// src/gha.ts
|
|
8227
8336
|
import { execFileSync as execFileSync16 } from "child_process";
|
|
8228
|
-
import * as
|
|
8337
|
+
import * as fs30 from "fs";
|
|
8229
8338
|
function getRunUrl() {
|
|
8230
8339
|
const server = process.env.GITHUB_SERVER_URL;
|
|
8231
8340
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -8236,10 +8345,10 @@ function getRunUrl() {
|
|
|
8236
8345
|
function reactToTriggerComment(cwd) {
|
|
8237
8346
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
8238
8347
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
8239
|
-
if (!eventPath || !
|
|
8348
|
+
if (!eventPath || !fs30.existsSync(eventPath)) return;
|
|
8240
8349
|
let event = null;
|
|
8241
8350
|
try {
|
|
8242
|
-
event = JSON.parse(
|
|
8351
|
+
event = JSON.parse(fs30.readFileSync(eventPath, "utf-8"));
|
|
8243
8352
|
} catch {
|
|
8244
8353
|
return;
|
|
8245
8354
|
}
|
|
@@ -8533,12 +8642,12 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
8533
8642
|
|
|
8534
8643
|
// src/scripts/initFlow.ts
|
|
8535
8644
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
8536
|
-
import * as
|
|
8537
|
-
import * as
|
|
8645
|
+
import * as fs31 from "fs";
|
|
8646
|
+
import * as path29 from "path";
|
|
8538
8647
|
function detectPackageManager(cwd) {
|
|
8539
|
-
if (
|
|
8540
|
-
if (
|
|
8541
|
-
if (
|
|
8648
|
+
if (fs31.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
8649
|
+
if (fs31.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
|
|
8650
|
+
if (fs31.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
|
|
8542
8651
|
return "npm";
|
|
8543
8652
|
}
|
|
8544
8653
|
function qualityCommandsFor(pm) {
|
|
@@ -8667,36 +8776,36 @@ function performInit(cwd, force) {
|
|
|
8667
8776
|
const pm = detectPackageManager(cwd);
|
|
8668
8777
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
8669
8778
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
8670
|
-
const configPath =
|
|
8671
|
-
if (
|
|
8779
|
+
const configPath = path29.join(cwd, "kody.config.json");
|
|
8780
|
+
if (fs31.existsSync(configPath) && !force) {
|
|
8672
8781
|
skipped.push("kody.config.json");
|
|
8673
8782
|
} else {
|
|
8674
8783
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
|
|
8675
|
-
|
|
8784
|
+
fs31.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
8676
8785
|
`);
|
|
8677
8786
|
wrote.push("kody.config.json");
|
|
8678
8787
|
}
|
|
8679
|
-
const workflowDir =
|
|
8680
|
-
const workflowPath =
|
|
8681
|
-
if (
|
|
8788
|
+
const workflowDir = path29.join(cwd, ".github", "workflows");
|
|
8789
|
+
const workflowPath = path29.join(workflowDir, "kody.yml");
|
|
8790
|
+
if (fs31.existsSync(workflowPath) && !force) {
|
|
8682
8791
|
skipped.push(".github/workflows/kody.yml");
|
|
8683
8792
|
} else {
|
|
8684
|
-
|
|
8685
|
-
|
|
8793
|
+
fs31.mkdirSync(workflowDir, { recursive: true });
|
|
8794
|
+
fs31.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
8686
8795
|
wrote.push(".github/workflows/kody.yml");
|
|
8687
8796
|
}
|
|
8688
8797
|
const builtinJobs = listBuiltinJobs();
|
|
8689
8798
|
if (builtinJobs.length > 0) {
|
|
8690
|
-
const jobsDir =
|
|
8691
|
-
|
|
8799
|
+
const jobsDir = path29.join(cwd, ".kody", "duties");
|
|
8800
|
+
fs31.mkdirSync(jobsDir, { recursive: true });
|
|
8692
8801
|
for (const job of builtinJobs) {
|
|
8693
|
-
const rel =
|
|
8694
|
-
const target =
|
|
8695
|
-
if (
|
|
8802
|
+
const rel = path29.join(".kody", "duties", `${job.slug}.md`);
|
|
8803
|
+
const target = path29.join(cwd, rel);
|
|
8804
|
+
if (fs31.existsSync(target) && !force) {
|
|
8696
8805
|
skipped.push(rel);
|
|
8697
8806
|
continue;
|
|
8698
8807
|
}
|
|
8699
|
-
|
|
8808
|
+
fs31.writeFileSync(target, fs31.readFileSync(job.filePath, "utf-8"));
|
|
8700
8809
|
wrote.push(rel);
|
|
8701
8810
|
}
|
|
8702
8811
|
}
|
|
@@ -8708,12 +8817,12 @@ function performInit(cwd, force) {
|
|
|
8708
8817
|
continue;
|
|
8709
8818
|
}
|
|
8710
8819
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
8711
|
-
const target =
|
|
8712
|
-
if (
|
|
8820
|
+
const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
|
|
8821
|
+
if (fs31.existsSync(target) && !force) {
|
|
8713
8822
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
8714
8823
|
continue;
|
|
8715
8824
|
}
|
|
8716
|
-
|
|
8825
|
+
fs31.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
8717
8826
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
8718
8827
|
}
|
|
8719
8828
|
let labels;
|
|
@@ -8897,8 +9006,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
8897
9006
|
};
|
|
8898
9007
|
|
|
8899
9008
|
// src/scripts/loadJobFromFile.ts
|
|
8900
|
-
import * as
|
|
8901
|
-
import * as
|
|
9009
|
+
import * as fs32 from "fs";
|
|
9010
|
+
import * as path30 from "path";
|
|
8902
9011
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
8903
9012
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
8904
9013
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
@@ -8907,11 +9016,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8907
9016
|
if (!slug) {
|
|
8908
9017
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
8909
9018
|
}
|
|
8910
|
-
const absPath =
|
|
8911
|
-
if (!
|
|
9019
|
+
const absPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
9020
|
+
if (!fs32.existsSync(absPath)) {
|
|
8912
9021
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
8913
9022
|
}
|
|
8914
|
-
const raw =
|
|
9023
|
+
const raw = fs32.readFileSync(absPath, "utf-8");
|
|
8915
9024
|
const { title, body } = parseJobFile(raw, slug);
|
|
8916
9025
|
const frontmatter = splitFrontmatter2(raw).frontmatter;
|
|
8917
9026
|
const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
@@ -8919,13 +9028,13 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8919
9028
|
let workerTitle = "";
|
|
8920
9029
|
let workerPersona = "";
|
|
8921
9030
|
if (workerSlug) {
|
|
8922
|
-
const workerPath =
|
|
8923
|
-
if (!
|
|
9031
|
+
const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
9032
|
+
if (!fs32.existsSync(workerPath)) {
|
|
8924
9033
|
throw new Error(
|
|
8925
9034
|
`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`
|
|
8926
9035
|
);
|
|
8927
9036
|
}
|
|
8928
|
-
const workerRaw =
|
|
9037
|
+
const workerRaw = fs32.readFileSync(workerPath, "utf-8");
|
|
8929
9038
|
const parsed = parseJobFile(workerRaw, workerSlug);
|
|
8930
9039
|
workerTitle = parsed.title;
|
|
8931
9040
|
workerPersona = parsed.body;
|
|
@@ -8968,18 +9077,18 @@ init_loadMemoryContext();
|
|
|
8968
9077
|
init_loadPriorArt();
|
|
8969
9078
|
|
|
8970
9079
|
// src/scripts/loadQaContext.ts
|
|
8971
|
-
import * as
|
|
8972
|
-
import * as
|
|
9080
|
+
import * as fs35 from "fs";
|
|
9081
|
+
import * as path33 from "path";
|
|
8973
9082
|
|
|
8974
9083
|
// src/scripts/kodyVariables.ts
|
|
8975
|
-
import * as
|
|
8976
|
-
import * as
|
|
9084
|
+
import * as fs34 from "fs";
|
|
9085
|
+
import * as path32 from "path";
|
|
8977
9086
|
var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
|
|
8978
9087
|
function readKodyVariables(cwd) {
|
|
8979
|
-
const full =
|
|
9088
|
+
const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
8980
9089
|
let raw;
|
|
8981
9090
|
try {
|
|
8982
|
-
raw =
|
|
9091
|
+
raw = fs34.readFileSync(full, "utf-8");
|
|
8983
9092
|
} catch {
|
|
8984
9093
|
return {};
|
|
8985
9094
|
}
|
|
@@ -9028,18 +9137,18 @@ function readProfileStaff(raw) {
|
|
|
9028
9137
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
9029
9138
|
}
|
|
9030
9139
|
function readProfile(cwd) {
|
|
9031
|
-
const dir =
|
|
9032
|
-
if (!
|
|
9140
|
+
const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
9141
|
+
if (!fs35.existsSync(dir)) return "";
|
|
9033
9142
|
let entries;
|
|
9034
9143
|
try {
|
|
9035
|
-
entries =
|
|
9144
|
+
entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
9036
9145
|
} catch {
|
|
9037
9146
|
return "";
|
|
9038
9147
|
}
|
|
9039
9148
|
const blocks = [];
|
|
9040
9149
|
for (const file of entries) {
|
|
9041
9150
|
try {
|
|
9042
|
-
const raw =
|
|
9151
|
+
const raw = fs35.readFileSync(path33.join(dir, file), "utf-8");
|
|
9043
9152
|
const { staff, body } = readProfileStaff(raw);
|
|
9044
9153
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
9045
9154
|
blocks.push(`## ${file}
|
|
@@ -9076,8 +9185,8 @@ var loadQaContext = async (ctx) => {
|
|
|
9076
9185
|
init_events();
|
|
9077
9186
|
|
|
9078
9187
|
// src/taskContext.ts
|
|
9079
|
-
import * as
|
|
9080
|
-
import * as
|
|
9188
|
+
import * as fs36 from "fs";
|
|
9189
|
+
import * as path34 from "path";
|
|
9081
9190
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
9082
9191
|
function buildTaskContext(args) {
|
|
9083
9192
|
return {
|
|
@@ -9093,10 +9202,10 @@ function buildTaskContext(args) {
|
|
|
9093
9202
|
}
|
|
9094
9203
|
function persistTaskContext(cwd, ctx) {
|
|
9095
9204
|
try {
|
|
9096
|
-
const dir =
|
|
9097
|
-
|
|
9098
|
-
const file =
|
|
9099
|
-
|
|
9205
|
+
const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
|
|
9206
|
+
fs36.mkdirSync(dir, { recursive: true });
|
|
9207
|
+
const file = path34.join(dir, "task-context.json");
|
|
9208
|
+
fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
9100
9209
|
`);
|
|
9101
9210
|
return file;
|
|
9102
9211
|
} catch (err) {
|
|
@@ -9144,19 +9253,19 @@ var loadTaskState = async (ctx) => {
|
|
|
9144
9253
|
};
|
|
9145
9254
|
|
|
9146
9255
|
// src/scripts/loadWorkerAdhoc.ts
|
|
9147
|
-
import * as
|
|
9148
|
-
import * as
|
|
9256
|
+
import * as fs37 from "fs";
|
|
9257
|
+
import * as path35 from "path";
|
|
9149
9258
|
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
9150
9259
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
9151
9260
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
9152
9261
|
if (!workerSlug) {
|
|
9153
9262
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
9154
9263
|
}
|
|
9155
|
-
const workerPath =
|
|
9156
|
-
if (!
|
|
9264
|
+
const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
9265
|
+
if (!fs37.existsSync(workerPath)) {
|
|
9157
9266
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
9158
9267
|
}
|
|
9159
|
-
const { title, body } = parsePersona(
|
|
9268
|
+
const { title, body } = parsePersona(fs37.readFileSync(workerPath, "utf-8"), workerSlug);
|
|
9160
9269
|
const message = resolveMessage(ctx.args.message);
|
|
9161
9270
|
if (!message) {
|
|
9162
9271
|
throw new Error(
|
|
@@ -9176,9 +9285,9 @@ function resolveMessage(messageArg) {
|
|
|
9176
9285
|
}
|
|
9177
9286
|
function readCommentBody() {
|
|
9178
9287
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
9179
|
-
if (!eventPath || !
|
|
9288
|
+
if (!eventPath || !fs37.existsSync(eventPath)) return "";
|
|
9180
9289
|
try {
|
|
9181
|
-
const event = JSON.parse(
|
|
9290
|
+
const event = JSON.parse(fs37.readFileSync(eventPath, "utf-8"));
|
|
9182
9291
|
return String(event.comment?.body ?? "");
|
|
9183
9292
|
} catch {
|
|
9184
9293
|
return "";
|
|
@@ -9848,8 +9957,8 @@ var FlyClient = class {
|
|
|
9848
9957
|
get fetch() {
|
|
9849
9958
|
return this.opts.fetchImpl ?? fetch;
|
|
9850
9959
|
}
|
|
9851
|
-
async call(
|
|
9852
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
9960
|
+
async call(path40, init = {}) {
|
|
9961
|
+
const res = await this.fetch(`${FLY_API_BASE}${path40}`, {
|
|
9853
9962
|
method: init.method ?? "GET",
|
|
9854
9963
|
headers: {
|
|
9855
9964
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -9860,7 +9969,7 @@ var FlyClient = class {
|
|
|
9860
9969
|
if (res.status === 404 && init.allow404) return null;
|
|
9861
9970
|
if (!res.ok) {
|
|
9862
9971
|
const text = await res.text().catch(() => "");
|
|
9863
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
9972
|
+
throw new Error(`Fly API ${res.status} on ${path40}: ${text.slice(0, 200) || res.statusText}`);
|
|
9864
9973
|
}
|
|
9865
9974
|
if (res.status === 204) return null;
|
|
9866
9975
|
const raw = await res.text();
|
|
@@ -11536,7 +11645,7 @@ function resolveBaseOverride(value) {
|
|
|
11536
11645
|
// src/scripts/runnerServe.ts
|
|
11537
11646
|
import { spawn as spawn5 } from "child_process";
|
|
11538
11647
|
import { createServer as createServer3 } from "http";
|
|
11539
|
-
import * as
|
|
11648
|
+
import * as fs38 from "fs";
|
|
11540
11649
|
var DEFAULT_PORT2 = 8080;
|
|
11541
11650
|
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
11542
11651
|
function getApiKey2() {
|
|
@@ -11617,8 +11726,8 @@ async function defaultRunJob(job) {
|
|
|
11617
11726
|
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
11618
11727
|
const branch = job.ref ?? "main";
|
|
11619
11728
|
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
11620
|
-
|
|
11621
|
-
|
|
11729
|
+
fs38.rmSync(workdir, { recursive: true, force: true });
|
|
11730
|
+
fs38.mkdirSync(workdir, { recursive: true });
|
|
11622
11731
|
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
11623
11732
|
const interactive = job.mode === "interactive";
|
|
11624
11733
|
const scheduled = job.mode === "scheduled";
|
|
@@ -11759,8 +11868,8 @@ var runnerServe = async (ctx) => {
|
|
|
11759
11868
|
|
|
11760
11869
|
// src/scripts/runTickScript.ts
|
|
11761
11870
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
11762
|
-
import * as
|
|
11763
|
-
import * as
|
|
11871
|
+
import * as fs39 from "fs";
|
|
11872
|
+
import * as path36 from "path";
|
|
11764
11873
|
var runTickScript = async (ctx, _profile, args) => {
|
|
11765
11874
|
ctx.skipAgent = true;
|
|
11766
11875
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -11772,13 +11881,13 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
11772
11881
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
11773
11882
|
return;
|
|
11774
11883
|
}
|
|
11775
|
-
const jobPath =
|
|
11776
|
-
if (!
|
|
11884
|
+
const jobPath = path36.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
11885
|
+
if (!fs39.existsSync(jobPath)) {
|
|
11777
11886
|
ctx.output.exitCode = 99;
|
|
11778
11887
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
11779
11888
|
return;
|
|
11780
11889
|
}
|
|
11781
|
-
const raw =
|
|
11890
|
+
const raw = fs39.readFileSync(jobPath, "utf-8");
|
|
11782
11891
|
const { frontmatter } = splitFrontmatter2(raw);
|
|
11783
11892
|
const tickScript = frontmatter.tickScript;
|
|
11784
11893
|
if (!tickScript) {
|
|
@@ -11786,8 +11895,8 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
11786
11895
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
11787
11896
|
return;
|
|
11788
11897
|
}
|
|
11789
|
-
const scriptPath =
|
|
11790
|
-
if (!
|
|
11898
|
+
const scriptPath = path36.isAbsolute(tickScript) ? tickScript : path36.join(ctx.cwd, tickScript);
|
|
11899
|
+
if (!fs39.existsSync(scriptPath)) {
|
|
11791
11900
|
ctx.output.exitCode = 99;
|
|
11792
11901
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
11793
11902
|
return;
|
|
@@ -12124,7 +12233,8 @@ var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
|
12124
12233
|
name: flowName,
|
|
12125
12234
|
step: entry,
|
|
12126
12235
|
issueNumber,
|
|
12127
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12236
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12237
|
+
hops: 0
|
|
12128
12238
|
};
|
|
12129
12239
|
}
|
|
12130
12240
|
postKodyComment(target, issueNumber, state, entry, ctx.cwd);
|
|
@@ -12910,7 +13020,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
|
|
|
12910
13020
|
};
|
|
12911
13021
|
|
|
12912
13022
|
// src/scripts/writeRunSummary.ts
|
|
12913
|
-
import * as
|
|
13023
|
+
import * as fs40 from "fs";
|
|
12914
13024
|
var writeRunSummary = async (ctx, profile) => {
|
|
12915
13025
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
12916
13026
|
if (!summaryPath) return;
|
|
@@ -12932,7 +13042,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
12932
13042
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
12933
13043
|
lines.push("");
|
|
12934
13044
|
try {
|
|
12935
|
-
|
|
13045
|
+
fs40.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
12936
13046
|
`);
|
|
12937
13047
|
} catch {
|
|
12938
13048
|
}
|
|
@@ -13200,9 +13310,9 @@ async function runExecutable(profileName, input) {
|
|
|
13200
13310
|
})
|
|
13201
13311
|
};
|
|
13202
13312
|
})() : null;
|
|
13203
|
-
const ndjsonDir =
|
|
13313
|
+
const ndjsonDir = path37.join(input.cwd, ".kody");
|
|
13204
13314
|
const invokeAgent = async (prompt) => {
|
|
13205
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
13315
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path37.isAbsolute(p) ? p : path37.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
13206
13316
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
13207
13317
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
13208
13318
|
const agents = loadSubagents(profile);
|
|
@@ -13416,7 +13526,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
13416
13526
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
13417
13527
|
try {
|
|
13418
13528
|
const profilePath = resolveProfilePath(profileName);
|
|
13419
|
-
if (!
|
|
13529
|
+
if (!fs41.existsSync(profilePath)) return null;
|
|
13420
13530
|
return loadProfile(profilePath).inputs;
|
|
13421
13531
|
} catch {
|
|
13422
13532
|
return null;
|
|
@@ -13425,17 +13535,17 @@ function getProfileInputsForChild(profileName, _cwd) {
|
|
|
13425
13535
|
function resolveProfilePath(profileName) {
|
|
13426
13536
|
const found = resolveExecutable(profileName);
|
|
13427
13537
|
if (found) return found;
|
|
13428
|
-
const here =
|
|
13538
|
+
const here = path37.dirname(new URL(import.meta.url).pathname);
|
|
13429
13539
|
const candidates = [
|
|
13430
|
-
|
|
13540
|
+
path37.join(here, "executables", profileName, "profile.json"),
|
|
13431
13541
|
// same-dir sibling (dev)
|
|
13432
|
-
|
|
13542
|
+
path37.join(here, "..", "executables", profileName, "profile.json"),
|
|
13433
13543
|
// up one (prod: dist/bin → dist/executables)
|
|
13434
|
-
|
|
13544
|
+
path37.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
13435
13545
|
// fallback
|
|
13436
13546
|
];
|
|
13437
13547
|
for (const c of candidates) {
|
|
13438
|
-
if (
|
|
13548
|
+
if (fs41.existsSync(c)) return c;
|
|
13439
13549
|
}
|
|
13440
13550
|
return candidates[0];
|
|
13441
13551
|
}
|
|
@@ -13535,8 +13645,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
13535
13645
|
var SIGKILL_GRACE_MS = 5e3;
|
|
13536
13646
|
async function runShellEntry(entry, ctx, profile) {
|
|
13537
13647
|
const shellName = entry.shell;
|
|
13538
|
-
const shellPath =
|
|
13539
|
-
if (!
|
|
13648
|
+
const shellPath = path37.join(profile.dir, shellName);
|
|
13649
|
+
if (!fs41.existsSync(shellPath)) {
|
|
13540
13650
|
ctx.skipAgent = true;
|
|
13541
13651
|
ctx.output.exitCode = 99;
|
|
13542
13652
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -14042,9 +14152,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
14042
14152
|
return void 0;
|
|
14043
14153
|
}
|
|
14044
14154
|
function detectPackageManager2(cwd) {
|
|
14045
|
-
if (
|
|
14046
|
-
if (
|
|
14047
|
-
if (
|
|
14155
|
+
if (fs42.existsSync(path38.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
14156
|
+
if (fs42.existsSync(path38.join(cwd, "yarn.lock"))) return "yarn";
|
|
14157
|
+
if (fs42.existsSync(path38.join(cwd, "bun.lockb"))) return "bun";
|
|
14048
14158
|
return "npm";
|
|
14049
14159
|
}
|
|
14050
14160
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -14131,11 +14241,11 @@ function configureGitIdentity(cwd) {
|
|
|
14131
14241
|
}
|
|
14132
14242
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
14133
14243
|
if (!issueNumber) return;
|
|
14134
|
-
const logPath =
|
|
14244
|
+
const logPath = path38.join(cwd, ".kody", "last-run.jsonl");
|
|
14135
14245
|
let tail = "";
|
|
14136
14246
|
try {
|
|
14137
|
-
if (
|
|
14138
|
-
const content =
|
|
14247
|
+
if (fs42.existsSync(logPath)) {
|
|
14248
|
+
const content = fs42.readFileSync(logPath, "utf-8");
|
|
14139
14249
|
tail = content.slice(-3e3);
|
|
14140
14250
|
}
|
|
14141
14251
|
} catch {
|
|
@@ -14160,7 +14270,7 @@ async function runCi(argv) {
|
|
|
14160
14270
|
return 0;
|
|
14161
14271
|
}
|
|
14162
14272
|
const args = parseCiArgs(argv);
|
|
14163
|
-
const cwd = args.cwd ?
|
|
14273
|
+
const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
|
|
14164
14274
|
let earlyConfig;
|
|
14165
14275
|
try {
|
|
14166
14276
|
earlyConfig = loadConfig(cwd);
|
|
@@ -14170,9 +14280,9 @@ async function runCi(argv) {
|
|
|
14170
14280
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
14171
14281
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
14172
14282
|
let manualWorkflowDispatch = false;
|
|
14173
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
14283
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs42.existsSync(dispatchEventPath)) {
|
|
14174
14284
|
try {
|
|
14175
|
-
const evt = JSON.parse(
|
|
14285
|
+
const evt = JSON.parse(fs42.readFileSync(dispatchEventPath, "utf-8"));
|
|
14176
14286
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
14177
14287
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
14178
14288
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -14433,12 +14543,12 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
14433
14543
|
return result;
|
|
14434
14544
|
}
|
|
14435
14545
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
14436
|
-
const sessionFile =
|
|
14437
|
-
const eventsFile =
|
|
14546
|
+
const sessionFile = path39.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
14547
|
+
const eventsFile = path39.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
14438
14548
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
14439
|
-
const tasksDir =
|
|
14549
|
+
const tasksDir = path39.join(".kody", "tasks", safeSession);
|
|
14440
14550
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
14441
|
-
const paths = candidatePaths.filter((p) =>
|
|
14551
|
+
const paths = candidatePaths.filter((p) => fs43.existsSync(path39.join(cwd, p)));
|
|
14442
14552
|
if (paths.length === 0) return;
|
|
14443
14553
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
14444
14554
|
try {
|
|
@@ -14482,7 +14592,7 @@ async function runChat(argv) {
|
|
|
14482
14592
|
${CHAT_HELP}`);
|
|
14483
14593
|
return 64;
|
|
14484
14594
|
}
|
|
14485
|
-
const cwd = args.cwd ?
|
|
14595
|
+
const cwd = args.cwd ? path39.resolve(args.cwd) : process.cwd();
|
|
14486
14596
|
const sessionId = args.sessionId;
|
|
14487
14597
|
const unpackedSecrets = unpackAllSecrets();
|
|
14488
14598
|
if (unpackedSecrets > 0) {
|
|
@@ -14534,7 +14644,7 @@ ${CHAT_HELP}`);
|
|
|
14534
14644
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
14535
14645
|
const meta = readMeta(sessionFile);
|
|
14536
14646
|
process.stdout.write(
|
|
14537
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
14647
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs43.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
14538
14648
|
`
|
|
14539
14649
|
);
|
|
14540
14650
|
try {
|