@kody-ade/kody-engine 0.4.40 → 0.4.42
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 +668 -276
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.4.
|
|
6
|
+
version: "0.4.42",
|
|
7
7
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -52,8 +52,8 @@ var package_default = {
|
|
|
52
52
|
|
|
53
53
|
// src/chat-cli.ts
|
|
54
54
|
import { execFileSync as execFileSync31 } from "child_process";
|
|
55
|
-
import * as
|
|
56
|
-
import * as
|
|
55
|
+
import * as fs31 from "fs";
|
|
56
|
+
import * as path29 from "path";
|
|
57
57
|
|
|
58
58
|
// src/chat/events.ts
|
|
59
59
|
import * as fs from "fs";
|
|
@@ -417,6 +417,9 @@ async function runAgent(opts) {
|
|
|
417
417
|
const resultTexts = [];
|
|
418
418
|
let outcome = "failed";
|
|
419
419
|
let errorMessage;
|
|
420
|
+
const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
|
|
421
|
+
let messageCount = 0;
|
|
422
|
+
const startedAt = Date.now();
|
|
420
423
|
try {
|
|
421
424
|
const queryOptions = {
|
|
422
425
|
model: opts.model.model,
|
|
@@ -454,6 +457,7 @@ async function runAgent(opts) {
|
|
|
454
457
|
options: queryOptions
|
|
455
458
|
});
|
|
456
459
|
for await (const msg of result) {
|
|
460
|
+
messageCount++;
|
|
457
461
|
try {
|
|
458
462
|
fullLog.write(`${JSON.stringify(msg)}
|
|
459
463
|
`);
|
|
@@ -463,6 +467,17 @@ async function runAgent(opts) {
|
|
|
463
467
|
if (line) process.stdout.write(`${line}
|
|
464
468
|
`);
|
|
465
469
|
const m = msg;
|
|
470
|
+
const usage = m.usage;
|
|
471
|
+
if (usage && typeof usage === "object") {
|
|
472
|
+
const i = Number(usage.input_tokens ?? 0);
|
|
473
|
+
const o = Number(usage.output_tokens ?? 0);
|
|
474
|
+
const cr = Number(usage.cache_read_input_tokens ?? 0);
|
|
475
|
+
const cc = Number(usage.cache_creation_input_tokens ?? 0);
|
|
476
|
+
if (Number.isFinite(i)) tokens.input += i;
|
|
477
|
+
if (Number.isFinite(o)) tokens.output += o;
|
|
478
|
+
if (Number.isFinite(cr)) tokens.cacheRead += cr;
|
|
479
|
+
if (Number.isFinite(cc)) tokens.cacheCreate += cc;
|
|
480
|
+
}
|
|
466
481
|
if (m.type === "result") {
|
|
467
482
|
if (m.subtype === "success") {
|
|
468
483
|
outcome = "completed";
|
|
@@ -484,7 +499,15 @@ async function runAgent(opts) {
|
|
|
484
499
|
}
|
|
485
500
|
}
|
|
486
501
|
const finalText = resultTexts.join("\n\n---\n\n");
|
|
487
|
-
return {
|
|
502
|
+
return {
|
|
503
|
+
outcome,
|
|
504
|
+
finalText,
|
|
505
|
+
error: errorMessage,
|
|
506
|
+
ndjsonPath,
|
|
507
|
+
durationMs: Date.now() - startedAt,
|
|
508
|
+
tokens,
|
|
509
|
+
messageCount
|
|
510
|
+
};
|
|
488
511
|
}
|
|
489
512
|
|
|
490
513
|
// src/chat/session.ts
|
|
@@ -913,8 +936,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
913
936
|
|
|
914
937
|
// src/kody-cli.ts
|
|
915
938
|
import { execFileSync as execFileSync30 } from "child_process";
|
|
916
|
-
import * as
|
|
917
|
-
import * as
|
|
939
|
+
import * as fs30 from "fs";
|
|
940
|
+
import * as path28 from "path";
|
|
918
941
|
|
|
919
942
|
// src/dispatch.ts
|
|
920
943
|
import * as fs7 from "fs";
|
|
@@ -1493,12 +1516,75 @@ function postPrReviewComment(prNumber, body, cwd) {
|
|
|
1493
1516
|
|
|
1494
1517
|
// src/executor.ts
|
|
1495
1518
|
import { execFileSync as execFileSync29, spawn as spawn5 } from "child_process";
|
|
1496
|
-
import * as
|
|
1497
|
-
import * as
|
|
1519
|
+
import * as fs29 from "fs";
|
|
1520
|
+
import * as path27 from "path";
|
|
1498
1521
|
|
|
1499
|
-
// src/
|
|
1522
|
+
// src/events.ts
|
|
1523
|
+
import * as crypto from "crypto";
|
|
1500
1524
|
import * as fs8 from "fs";
|
|
1501
1525
|
import * as path7 from "path";
|
|
1526
|
+
var cachedRunId = null;
|
|
1527
|
+
function resolveRunId() {
|
|
1528
|
+
if (cachedRunId) return cachedRunId;
|
|
1529
|
+
if (process.env.KODY_RUN_ID) {
|
|
1530
|
+
cachedRunId = process.env.KODY_RUN_ID;
|
|
1531
|
+
return cachedRunId;
|
|
1532
|
+
}
|
|
1533
|
+
if (process.env.GITHUB_RUN_ID) {
|
|
1534
|
+
const attempt = process.env.GITHUB_RUN_ATTEMPT ?? "1";
|
|
1535
|
+
cachedRunId = `gh-${process.env.GITHUB_RUN_ID}-${attempt}`;
|
|
1536
|
+
} else {
|
|
1537
|
+
cachedRunId = `${Date.now().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
|
|
1538
|
+
}
|
|
1539
|
+
process.env.KODY_RUN_ID = cachedRunId;
|
|
1540
|
+
return cachedRunId;
|
|
1541
|
+
}
|
|
1542
|
+
function emitEvent(cwd, ev) {
|
|
1543
|
+
if (process.env.KODY_EVENTS === "0") return;
|
|
1544
|
+
try {
|
|
1545
|
+
const runId = resolveRunId();
|
|
1546
|
+
const fullEvent = {
|
|
1547
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1548
|
+
runId,
|
|
1549
|
+
...ev
|
|
1550
|
+
};
|
|
1551
|
+
const eventsPath = path7.join(cwd, ".kody", "runs", runId, "events.jsonl");
|
|
1552
|
+
fs8.mkdirSync(path7.dirname(eventsPath), { recursive: true });
|
|
1553
|
+
fs8.appendFileSync(eventsPath, `${JSON.stringify(fullEvent)}
|
|
1554
|
+
`);
|
|
1555
|
+
} catch {
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function readEvents(cwd, runId) {
|
|
1559
|
+
const eventsPath = path7.join(cwd, ".kody", "runs", runId, "events.jsonl");
|
|
1560
|
+
if (!fs8.existsSync(eventsPath)) return [];
|
|
1561
|
+
const lines = fs8.readFileSync(eventsPath, "utf-8").split("\n");
|
|
1562
|
+
const out = [];
|
|
1563
|
+
for (const line of lines) {
|
|
1564
|
+
const trimmed = line.trim();
|
|
1565
|
+
if (!trimmed) continue;
|
|
1566
|
+
try {
|
|
1567
|
+
out.push(JSON.parse(trimmed));
|
|
1568
|
+
} catch {
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return out;
|
|
1572
|
+
}
|
|
1573
|
+
function listRuns(cwd) {
|
|
1574
|
+
const runsDir = path7.join(cwd, ".kody", "runs");
|
|
1575
|
+
if (!fs8.existsSync(runsDir)) return [];
|
|
1576
|
+
return fs8.readdirSync(runsDir).filter((name) => {
|
|
1577
|
+
try {
|
|
1578
|
+
return fs8.statSync(path7.join(runsDir, name)).isDirectory();
|
|
1579
|
+
} catch {
|
|
1580
|
+
return false;
|
|
1581
|
+
}
|
|
1582
|
+
}).sort();
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/profile.ts
|
|
1586
|
+
import * as fs9 from "fs";
|
|
1587
|
+
import * as path8 from "path";
|
|
1502
1588
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
1503
1589
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
1504
1590
|
var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
|
|
@@ -1514,12 +1600,12 @@ var ProfileError = class extends Error {
|
|
|
1514
1600
|
profilePath;
|
|
1515
1601
|
};
|
|
1516
1602
|
function loadProfile(profilePath) {
|
|
1517
|
-
if (!
|
|
1603
|
+
if (!fs9.existsSync(profilePath)) {
|
|
1518
1604
|
throw new ProfileError(profilePath, "file not found");
|
|
1519
1605
|
}
|
|
1520
1606
|
let raw;
|
|
1521
1607
|
try {
|
|
1522
|
-
raw = JSON.parse(
|
|
1608
|
+
raw = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
|
|
1523
1609
|
} catch (err) {
|
|
1524
1610
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
1525
1611
|
}
|
|
@@ -1558,7 +1644,7 @@ function loadProfile(profilePath) {
|
|
|
1558
1644
|
inputArtifacts: parseInputArtifacts(profilePath, r.input),
|
|
1559
1645
|
outputArtifacts: parseOutputArtifacts(profilePath, r.output),
|
|
1560
1646
|
children,
|
|
1561
|
-
dir:
|
|
1647
|
+
dir: path8.dirname(profilePath)
|
|
1562
1648
|
};
|
|
1563
1649
|
return profile;
|
|
1564
1650
|
}
|
|
@@ -1921,9 +2007,9 @@ function errMsg(err) {
|
|
|
1921
2007
|
|
|
1922
2008
|
// src/litellm.ts
|
|
1923
2009
|
import { execFileSync as execFileSync4, spawn } from "child_process";
|
|
1924
|
-
import * as
|
|
2010
|
+
import * as fs10 from "fs";
|
|
1925
2011
|
import * as os from "os";
|
|
1926
|
-
import * as
|
|
2012
|
+
import * as path9 from "path";
|
|
1927
2013
|
async function checkLitellmHealth(url) {
|
|
1928
2014
|
try {
|
|
1929
2015
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -1963,20 +2049,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
1963
2049
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
1964
2050
|
}
|
|
1965
2051
|
}
|
|
1966
|
-
const configPath =
|
|
1967
|
-
|
|
2052
|
+
const configPath = path9.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
2053
|
+
fs10.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
1968
2054
|
const portMatch = url.match(/:(\d+)/);
|
|
1969
2055
|
const port = portMatch ? portMatch[1] : "4000";
|
|
1970
2056
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
1971
2057
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
1972
|
-
const logPath =
|
|
1973
|
-
const outFd =
|
|
2058
|
+
const logPath = path9.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
2059
|
+
const outFd = fs10.openSync(logPath, "w");
|
|
1974
2060
|
const child = spawn(cmd, args, {
|
|
1975
2061
|
stdio: ["ignore", outFd, outFd],
|
|
1976
2062
|
detached: true,
|
|
1977
2063
|
env: stripBlockingEnv({ ...process.env, ...dotenvVars })
|
|
1978
2064
|
});
|
|
1979
|
-
|
|
2065
|
+
fs10.closeSync(outFd);
|
|
1980
2066
|
for (let i = 0; i < 30; i++) {
|
|
1981
2067
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
1982
2068
|
if (await checkLitellmHealth(url)) {
|
|
@@ -1993,7 +2079,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
1993
2079
|
}
|
|
1994
2080
|
let logTail = "";
|
|
1995
2081
|
try {
|
|
1996
|
-
logTail =
|
|
2082
|
+
logTail = fs10.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
1997
2083
|
} catch {
|
|
1998
2084
|
}
|
|
1999
2085
|
try {
|
|
@@ -2004,10 +2090,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
2004
2090
|
${logTail}`);
|
|
2005
2091
|
}
|
|
2006
2092
|
function readDotenvApiKeys(projectDir) {
|
|
2007
|
-
const dotenvPath =
|
|
2008
|
-
if (!
|
|
2093
|
+
const dotenvPath = path9.join(projectDir, ".env");
|
|
2094
|
+
if (!fs10.existsSync(dotenvPath)) return {};
|
|
2009
2095
|
const result = {};
|
|
2010
|
-
for (const rawLine of
|
|
2096
|
+
for (const rawLine of fs10.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
2011
2097
|
const line = rawLine.trim();
|
|
2012
2098
|
if (!line || line.startsWith("#")) continue;
|
|
2013
2099
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -2031,8 +2117,8 @@ function stripBlockingEnv(env) {
|
|
|
2031
2117
|
|
|
2032
2118
|
// src/commit.ts
|
|
2033
2119
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
2034
|
-
import * as
|
|
2035
|
-
import * as
|
|
2120
|
+
import * as fs11 from "fs";
|
|
2121
|
+
import * as path10 from "path";
|
|
2036
2122
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
2037
2123
|
".kody/",
|
|
2038
2124
|
".kody-engine/",
|
|
@@ -2088,18 +2174,18 @@ function tryGit(args, cwd) {
|
|
|
2088
2174
|
}
|
|
2089
2175
|
function abortUnfinishedGitOps(cwd) {
|
|
2090
2176
|
const aborted = [];
|
|
2091
|
-
const gitDir =
|
|
2092
|
-
if (!
|
|
2093
|
-
if (
|
|
2177
|
+
const gitDir = path10.join(cwd ?? process.cwd(), ".git");
|
|
2178
|
+
if (!fs11.existsSync(gitDir)) return aborted;
|
|
2179
|
+
if (fs11.existsSync(path10.join(gitDir, "MERGE_HEAD"))) {
|
|
2094
2180
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
2095
2181
|
}
|
|
2096
|
-
if (
|
|
2182
|
+
if (fs11.existsSync(path10.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
2097
2183
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
2098
2184
|
}
|
|
2099
|
-
if (
|
|
2185
|
+
if (fs11.existsSync(path10.join(gitDir, "REVERT_HEAD"))) {
|
|
2100
2186
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
2101
2187
|
}
|
|
2102
|
-
if (
|
|
2188
|
+
if (fs11.existsSync(path10.join(gitDir, "rebase-merge")) || fs11.existsSync(path10.join(gitDir, "rebase-apply"))) {
|
|
2103
2189
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
2104
2190
|
}
|
|
2105
2191
|
try {
|
|
@@ -2155,7 +2241,7 @@ function normalizeCommitMessage(raw) {
|
|
|
2155
2241
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
2156
2242
|
const allChanged = listChangedFiles(cwd);
|
|
2157
2243
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
2158
|
-
const mergeHeadExists =
|
|
2244
|
+
const mergeHeadExists = fs11.existsSync(path10.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
2159
2245
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
2160
2246
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
2161
2247
|
}
|
|
@@ -2467,21 +2553,21 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
2467
2553
|
};
|
|
2468
2554
|
|
|
2469
2555
|
// src/scripts/buildSyntheticPlugin.ts
|
|
2470
|
-
import * as
|
|
2556
|
+
import * as fs12 from "fs";
|
|
2471
2557
|
import * as os2 from "os";
|
|
2472
|
-
import * as
|
|
2558
|
+
import * as path11 from "path";
|
|
2473
2559
|
function getPluginsCatalogRoot() {
|
|
2474
|
-
const here =
|
|
2560
|
+
const here = path11.dirname(new URL(import.meta.url).pathname);
|
|
2475
2561
|
const candidates = [
|
|
2476
|
-
|
|
2562
|
+
path11.join(here, "..", "plugins"),
|
|
2477
2563
|
// dev: src/scripts → src/plugins
|
|
2478
|
-
|
|
2564
|
+
path11.join(here, "..", "..", "plugins"),
|
|
2479
2565
|
// built: dist/scripts → dist/plugins
|
|
2480
|
-
|
|
2566
|
+
path11.join(here, "..", "..", "src", "plugins")
|
|
2481
2567
|
// fallback
|
|
2482
2568
|
];
|
|
2483
2569
|
for (const c of candidates) {
|
|
2484
|
-
if (
|
|
2570
|
+
if (fs12.existsSync(c) && fs12.statSync(c).isDirectory()) return c;
|
|
2485
2571
|
}
|
|
2486
2572
|
return candidates[0];
|
|
2487
2573
|
}
|
|
@@ -2491,52 +2577,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
2491
2577
|
if (!needsSynthetic) return;
|
|
2492
2578
|
const catalog = getPluginsCatalogRoot();
|
|
2493
2579
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2494
|
-
const root =
|
|
2495
|
-
|
|
2580
|
+
const root = path11.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
2581
|
+
fs12.mkdirSync(path11.join(root, ".claude-plugin"), { recursive: true });
|
|
2496
2582
|
const resolvePart = (bucket, entry) => {
|
|
2497
|
-
const local =
|
|
2498
|
-
if (
|
|
2499
|
-
const central =
|
|
2500
|
-
if (
|
|
2583
|
+
const local = path11.join(profile.dir, bucket, entry);
|
|
2584
|
+
if (fs12.existsSync(local)) return local;
|
|
2585
|
+
const central = path11.join(catalog, bucket, entry);
|
|
2586
|
+
if (fs12.existsSync(central)) return central;
|
|
2501
2587
|
throw new Error(
|
|
2502
2588
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
2503
2589
|
);
|
|
2504
2590
|
};
|
|
2505
2591
|
if (cc.skills.length > 0) {
|
|
2506
|
-
const dst =
|
|
2507
|
-
|
|
2592
|
+
const dst = path11.join(root, "skills");
|
|
2593
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2508
2594
|
for (const name of cc.skills) {
|
|
2509
|
-
copyDir(resolvePart("skills", name),
|
|
2595
|
+
copyDir(resolvePart("skills", name), path11.join(dst, name));
|
|
2510
2596
|
}
|
|
2511
2597
|
}
|
|
2512
2598
|
if (cc.commands.length > 0) {
|
|
2513
|
-
const dst =
|
|
2514
|
-
|
|
2599
|
+
const dst = path11.join(root, "commands");
|
|
2600
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2515
2601
|
for (const name of cc.commands) {
|
|
2516
|
-
|
|
2602
|
+
fs12.copyFileSync(resolvePart("commands", `${name}.md`), path11.join(dst, `${name}.md`));
|
|
2517
2603
|
}
|
|
2518
2604
|
}
|
|
2519
2605
|
if (cc.subagents.length > 0) {
|
|
2520
|
-
const dst =
|
|
2521
|
-
|
|
2606
|
+
const dst = path11.join(root, "agents");
|
|
2607
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2522
2608
|
for (const name of cc.subagents) {
|
|
2523
|
-
|
|
2609
|
+
fs12.copyFileSync(resolvePart("agents", `${name}.md`), path11.join(dst, `${name}.md`));
|
|
2524
2610
|
}
|
|
2525
2611
|
}
|
|
2526
2612
|
if (cc.hooks.length > 0) {
|
|
2527
|
-
const dst =
|
|
2528
|
-
|
|
2613
|
+
const dst = path11.join(root, "hooks");
|
|
2614
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2529
2615
|
const merged = { hooks: {} };
|
|
2530
2616
|
for (const name of cc.hooks) {
|
|
2531
2617
|
const src = resolvePart("hooks", `${name}.json`);
|
|
2532
|
-
const parsed = JSON.parse(
|
|
2618
|
+
const parsed = JSON.parse(fs12.readFileSync(src, "utf-8"));
|
|
2533
2619
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
2534
2620
|
if (!Array.isArray(entries)) continue;
|
|
2535
2621
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
2536
2622
|
merged.hooks[event].push(...entries);
|
|
2537
2623
|
}
|
|
2538
2624
|
}
|
|
2539
|
-
|
|
2625
|
+
fs12.writeFileSync(path11.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
2540
2626
|
`);
|
|
2541
2627
|
}
|
|
2542
2628
|
const manifest = {
|
|
@@ -2547,17 +2633,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
2547
2633
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
2548
2634
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
2549
2635
|
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
2550
|
-
|
|
2636
|
+
fs12.writeFileSync(path11.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
2551
2637
|
`);
|
|
2552
2638
|
ctx.data.syntheticPluginPath = root;
|
|
2553
2639
|
};
|
|
2554
2640
|
function copyDir(src, dst) {
|
|
2555
|
-
|
|
2556
|
-
for (const ent of
|
|
2557
|
-
const s =
|
|
2558
|
-
const d =
|
|
2641
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2642
|
+
for (const ent of fs12.readdirSync(src, { withFileTypes: true })) {
|
|
2643
|
+
const s = path11.join(src, ent.name);
|
|
2644
|
+
const d = path11.join(dst, ent.name);
|
|
2559
2645
|
if (ent.isDirectory()) copyDir(s, d);
|
|
2560
|
-
else if (ent.isFile())
|
|
2646
|
+
else if (ent.isFile()) fs12.copyFileSync(s, d);
|
|
2561
2647
|
}
|
|
2562
2648
|
}
|
|
2563
2649
|
|
|
@@ -2623,18 +2709,18 @@ function formatMissesForFeedback(misses) {
|
|
|
2623
2709
|
}
|
|
2624
2710
|
|
|
2625
2711
|
// src/prompt.ts
|
|
2626
|
-
import * as
|
|
2627
|
-
import * as
|
|
2712
|
+
import * as fs13 from "fs";
|
|
2713
|
+
import * as path12 from "path";
|
|
2628
2714
|
var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
2629
2715
|
var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
2630
2716
|
function loadProjectConventions(projectDir) {
|
|
2631
2717
|
const out = [];
|
|
2632
2718
|
for (const rel of CONVENTION_FILES) {
|
|
2633
|
-
const abs =
|
|
2634
|
-
if (!
|
|
2719
|
+
const abs = path12.join(projectDir, rel);
|
|
2720
|
+
if (!fs13.existsSync(abs)) continue;
|
|
2635
2721
|
let content;
|
|
2636
2722
|
try {
|
|
2637
|
-
content =
|
|
2723
|
+
content = fs13.readFileSync(abs, "utf-8");
|
|
2638
2724
|
} catch {
|
|
2639
2725
|
continue;
|
|
2640
2726
|
}
|
|
@@ -2846,11 +2932,11 @@ var commitAndPush2 = async (ctx) => {
|
|
|
2846
2932
|
|
|
2847
2933
|
// src/scripts/commitGoalState.ts
|
|
2848
2934
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
2849
|
-
import * as
|
|
2935
|
+
import * as path13 from "path";
|
|
2850
2936
|
var commitGoalState = async (ctx) => {
|
|
2851
2937
|
const goal = ctx.data.goal;
|
|
2852
2938
|
if (!goal) return;
|
|
2853
|
-
const stateRel =
|
|
2939
|
+
const stateRel = path13.posix.join(".kody", "goals", goal.id, "state.json");
|
|
2854
2940
|
try {
|
|
2855
2941
|
execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
|
|
2856
2942
|
} catch (err) {
|
|
@@ -2894,20 +2980,20 @@ function describeCommitMessage(goal) {
|
|
|
2894
2980
|
}
|
|
2895
2981
|
|
|
2896
2982
|
// src/scripts/composePrompt.ts
|
|
2897
|
-
import * as
|
|
2898
|
-
import * as
|
|
2983
|
+
import * as fs14 from "fs";
|
|
2984
|
+
import * as path14 from "path";
|
|
2899
2985
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
2900
2986
|
var composePrompt = async (ctx, profile) => {
|
|
2901
2987
|
const explicit = ctx.data.promptTemplate;
|
|
2902
2988
|
const mode = ctx.args.mode;
|
|
2903
2989
|
const candidates = [
|
|
2904
|
-
explicit ?
|
|
2905
|
-
mode ?
|
|
2906
|
-
|
|
2990
|
+
explicit ? path14.join(profile.dir, explicit) : null,
|
|
2991
|
+
mode ? path14.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
2992
|
+
path14.join(profile.dir, "prompt.md")
|
|
2907
2993
|
].filter(Boolean);
|
|
2908
2994
|
let templatePath = "";
|
|
2909
2995
|
for (const c of candidates) {
|
|
2910
|
-
if (
|
|
2996
|
+
if (fs14.existsSync(c)) {
|
|
2911
2997
|
templatePath = c;
|
|
2912
2998
|
break;
|
|
2913
2999
|
}
|
|
@@ -2915,7 +3001,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
2915
3001
|
if (!templatePath) {
|
|
2916
3002
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
2917
3003
|
}
|
|
2918
|
-
const template =
|
|
3004
|
+
const template = fs14.readFileSync(templatePath, "utf-8");
|
|
2919
3005
|
const tokens = {
|
|
2920
3006
|
...stringifyAll(ctx.args, "args."),
|
|
2921
3007
|
...stringifyAll(ctx.data, ""),
|
|
@@ -2993,8 +3079,8 @@ function formatToolsUsage(profile) {
|
|
|
2993
3079
|
|
|
2994
3080
|
// src/scripts/createQaGoal.ts
|
|
2995
3081
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
2996
|
-
import * as
|
|
2997
|
-
import * as
|
|
3082
|
+
import * as fs15 from "fs";
|
|
3083
|
+
import * as path15 from "path";
|
|
2998
3084
|
|
|
2999
3085
|
// src/scripts/postReviewResult.ts
|
|
3000
3086
|
function detectVerdict(body) {
|
|
@@ -3246,8 +3332,8 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
|
3246
3332
|
return { number: Number(m[1]), created: true };
|
|
3247
3333
|
}
|
|
3248
3334
|
function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
3249
|
-
const dir =
|
|
3250
|
-
|
|
3335
|
+
const dir = path15.join(cwd, ".kody", "goals", goalId);
|
|
3336
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
3251
3337
|
const state = {
|
|
3252
3338
|
version: 1,
|
|
3253
3339
|
state: "active",
|
|
@@ -3255,8 +3341,8 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
|
3255
3341
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3256
3342
|
...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
|
|
3257
3343
|
};
|
|
3258
|
-
const filePath =
|
|
3259
|
-
|
|
3344
|
+
const filePath = path15.join(dir, "state.json");
|
|
3345
|
+
fs15.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
|
|
3260
3346
|
`);
|
|
3261
3347
|
return filePath;
|
|
3262
3348
|
}
|
|
@@ -3667,6 +3753,14 @@ function markPrReady(prNumber, cwd) {
|
|
|
3667
3753
|
return fail(err);
|
|
3668
3754
|
}
|
|
3669
3755
|
}
|
|
3756
|
+
function fetchDefaultBranch(cwd) {
|
|
3757
|
+
try {
|
|
3758
|
+
const out = gh(["api", "repos/{owner}/{repo}", "--jq", ".default_branch"], { cwd });
|
|
3759
|
+
return { ok: true, value: out.trim() };
|
|
3760
|
+
} catch (err) {
|
|
3761
|
+
return fail(err);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3670
3764
|
|
|
3671
3765
|
// src/goal/phase.ts
|
|
3672
3766
|
function derivePhase(snap) {
|
|
@@ -3690,6 +3784,15 @@ function pickNextDispatchable(snap) {
|
|
|
3690
3784
|
var deriveGoalPhase = async (ctx) => {
|
|
3691
3785
|
const goal = ctx.data.goal;
|
|
3692
3786
|
if (!goal) return;
|
|
3787
|
+
const defaultBranchResult = fetchDefaultBranch(ctx.cwd);
|
|
3788
|
+
if (defaultBranchResult.ok && defaultBranchResult.value) {
|
|
3789
|
+
goal.defaultBranch = defaultBranchResult.value;
|
|
3790
|
+
} else if (defaultBranchResult.error) {
|
|
3791
|
+
process.stderr.write(
|
|
3792
|
+
`[goal-tick] deriveGoalPhase: fetchDefaultBranch failed (${defaultBranchResult.error}); falling back to ${goal.defaultBranch}
|
|
3793
|
+
`
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3693
3796
|
const issues = listGoalIssues(goal.id, ctx.cwd);
|
|
3694
3797
|
if (!issues.ok) {
|
|
3695
3798
|
process.stderr.write(`[goal-tick] deriveGoalPhase: list issues failed: ${issues.error}
|
|
@@ -3738,15 +3841,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
|
3738
3841
|
|
|
3739
3842
|
// src/scripts/diagMcp.ts
|
|
3740
3843
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
3741
|
-
import * as
|
|
3844
|
+
import * as fs16 from "fs";
|
|
3742
3845
|
import * as os3 from "os";
|
|
3743
|
-
import * as
|
|
3846
|
+
import * as path16 from "path";
|
|
3744
3847
|
var diagMcp = async (_ctx) => {
|
|
3745
3848
|
const home = os3.homedir();
|
|
3746
|
-
const cacheDir =
|
|
3849
|
+
const cacheDir = path16.join(home, ".cache", "ms-playwright");
|
|
3747
3850
|
let entries = [];
|
|
3748
3851
|
try {
|
|
3749
|
-
entries =
|
|
3852
|
+
entries = fs16.readdirSync(cacheDir);
|
|
3750
3853
|
} catch {
|
|
3751
3854
|
}
|
|
3752
3855
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -3772,17 +3875,17 @@ var diagMcp = async (_ctx) => {
|
|
|
3772
3875
|
};
|
|
3773
3876
|
|
|
3774
3877
|
// src/scripts/discoverQaContext.ts
|
|
3775
|
-
import * as
|
|
3776
|
-
import * as
|
|
3878
|
+
import * as fs18 from "fs";
|
|
3879
|
+
import * as path18 from "path";
|
|
3777
3880
|
|
|
3778
3881
|
// src/scripts/frameworkDetectors.ts
|
|
3779
|
-
import * as
|
|
3780
|
-
import * as
|
|
3882
|
+
import * as fs17 from "fs";
|
|
3883
|
+
import * as path17 from "path";
|
|
3781
3884
|
function detectFrameworks(cwd) {
|
|
3782
3885
|
const out = [];
|
|
3783
3886
|
let deps = {};
|
|
3784
3887
|
try {
|
|
3785
|
-
const pkg = JSON.parse(
|
|
3888
|
+
const pkg = JSON.parse(fs17.readFileSync(path17.join(cwd, "package.json"), "utf-8"));
|
|
3786
3889
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3787
3890
|
} catch {
|
|
3788
3891
|
return out;
|
|
@@ -3819,7 +3922,7 @@ function detectFrameworks(cwd) {
|
|
|
3819
3922
|
}
|
|
3820
3923
|
function findFile(cwd, candidates) {
|
|
3821
3924
|
for (const c of candidates) {
|
|
3822
|
-
if (
|
|
3925
|
+
if (fs17.existsSync(path17.join(cwd, c))) return c;
|
|
3823
3926
|
}
|
|
3824
3927
|
return null;
|
|
3825
3928
|
}
|
|
@@ -3832,18 +3935,18 @@ var COLLECTION_DIRS = [
|
|
|
3832
3935
|
function discoverPayloadCollections(cwd) {
|
|
3833
3936
|
const out = [];
|
|
3834
3937
|
for (const dir of COLLECTION_DIRS) {
|
|
3835
|
-
const full =
|
|
3836
|
-
if (!
|
|
3938
|
+
const full = path17.join(cwd, dir);
|
|
3939
|
+
if (!fs17.existsSync(full)) continue;
|
|
3837
3940
|
let files;
|
|
3838
3941
|
try {
|
|
3839
|
-
files =
|
|
3942
|
+
files = fs17.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
3840
3943
|
} catch {
|
|
3841
3944
|
continue;
|
|
3842
3945
|
}
|
|
3843
3946
|
for (const file of files) {
|
|
3844
3947
|
try {
|
|
3845
|
-
const filePath =
|
|
3846
|
-
const content =
|
|
3948
|
+
const filePath = path17.join(full, file);
|
|
3949
|
+
const content = fs17.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
3847
3950
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
3848
3951
|
if (!slugMatch) continue;
|
|
3849
3952
|
const slug = slugMatch[1];
|
|
@@ -3857,7 +3960,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
3857
3960
|
out.push({
|
|
3858
3961
|
name,
|
|
3859
3962
|
slug,
|
|
3860
|
-
filePath:
|
|
3963
|
+
filePath: path17.relative(cwd, filePath),
|
|
3861
3964
|
fields: fields.slice(0, 20),
|
|
3862
3965
|
hasAdmin
|
|
3863
3966
|
});
|
|
@@ -3871,28 +3974,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
3871
3974
|
function discoverAdminComponents(cwd, collections) {
|
|
3872
3975
|
const out = [];
|
|
3873
3976
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
3874
|
-
const full =
|
|
3875
|
-
if (!
|
|
3977
|
+
const full = path17.join(cwd, dir);
|
|
3978
|
+
if (!fs17.existsSync(full)) continue;
|
|
3876
3979
|
let entries;
|
|
3877
3980
|
try {
|
|
3878
|
-
entries =
|
|
3981
|
+
entries = fs17.readdirSync(full, { withFileTypes: true });
|
|
3879
3982
|
} catch {
|
|
3880
3983
|
continue;
|
|
3881
3984
|
}
|
|
3882
3985
|
for (const entry of entries) {
|
|
3883
|
-
const entryPath =
|
|
3986
|
+
const entryPath = path17.join(full, entry.name);
|
|
3884
3987
|
let name;
|
|
3885
3988
|
let filePath;
|
|
3886
3989
|
if (entry.isDirectory()) {
|
|
3887
3990
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
3888
|
-
(f) =>
|
|
3991
|
+
(f) => fs17.existsSync(path17.join(entryPath, f))
|
|
3889
3992
|
);
|
|
3890
3993
|
if (!indexFile) continue;
|
|
3891
3994
|
name = entry.name;
|
|
3892
|
-
filePath =
|
|
3995
|
+
filePath = path17.relative(cwd, path17.join(entryPath, indexFile));
|
|
3893
3996
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
3894
3997
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
3895
|
-
filePath =
|
|
3998
|
+
filePath = path17.relative(cwd, entryPath);
|
|
3896
3999
|
} else {
|
|
3897
4000
|
continue;
|
|
3898
4001
|
}
|
|
@@ -3900,7 +4003,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
3900
4003
|
if (collections) {
|
|
3901
4004
|
for (const col of collections) {
|
|
3902
4005
|
try {
|
|
3903
|
-
const colContent =
|
|
4006
|
+
const colContent = fs17.readFileSync(path17.join(cwd, col.filePath), "utf-8");
|
|
3904
4007
|
if (colContent.includes(name)) {
|
|
3905
4008
|
usedInCollection = col.slug;
|
|
3906
4009
|
break;
|
|
@@ -3919,8 +4022,8 @@ function scanApiRoutes(cwd) {
|
|
|
3919
4022
|
const out = [];
|
|
3920
4023
|
const appDirs = ["src/app", "app"];
|
|
3921
4024
|
for (const appDir of appDirs) {
|
|
3922
|
-
const apiDir =
|
|
3923
|
-
if (!
|
|
4025
|
+
const apiDir = path17.join(cwd, appDir, "api");
|
|
4026
|
+
if (!fs17.existsSync(apiDir)) continue;
|
|
3924
4027
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
3925
4028
|
break;
|
|
3926
4029
|
}
|
|
@@ -3929,14 +4032,14 @@ function scanApiRoutes(cwd) {
|
|
|
3929
4032
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
3930
4033
|
let entries;
|
|
3931
4034
|
try {
|
|
3932
|
-
entries =
|
|
4035
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
3933
4036
|
} catch {
|
|
3934
4037
|
return;
|
|
3935
4038
|
}
|
|
3936
4039
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
3937
4040
|
if (routeFile) {
|
|
3938
4041
|
try {
|
|
3939
|
-
const content =
|
|
4042
|
+
const content = fs17.readFileSync(path17.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
3940
4043
|
const methods = HTTP_METHODS.filter(
|
|
3941
4044
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
3942
4045
|
);
|
|
@@ -3944,7 +4047,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3944
4047
|
out.push({
|
|
3945
4048
|
path: prefix,
|
|
3946
4049
|
methods,
|
|
3947
|
-
filePath:
|
|
4050
|
+
filePath: path17.relative(cwd, path17.join(dir, routeFile.name))
|
|
3948
4051
|
});
|
|
3949
4052
|
}
|
|
3950
4053
|
} catch {
|
|
@@ -3955,7 +4058,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3955
4058
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
3956
4059
|
let segment = entry.name;
|
|
3957
4060
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
3958
|
-
walkApiRoutes(
|
|
4061
|
+
walkApiRoutes(path17.join(dir, entry.name), prefix, cwd, out);
|
|
3959
4062
|
continue;
|
|
3960
4063
|
}
|
|
3961
4064
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -3963,7 +4066,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3963
4066
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
3964
4067
|
segment = `:${segment.slice(1, -1)}`;
|
|
3965
4068
|
}
|
|
3966
|
-
walkApiRoutes(
|
|
4069
|
+
walkApiRoutes(path17.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
3967
4070
|
}
|
|
3968
4071
|
}
|
|
3969
4072
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -3983,10 +4086,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
3983
4086
|
function scanEnvVars(cwd) {
|
|
3984
4087
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
3985
4088
|
for (const envFile of candidates) {
|
|
3986
|
-
const envPath =
|
|
3987
|
-
if (!
|
|
4089
|
+
const envPath = path17.join(cwd, envFile);
|
|
4090
|
+
if (!fs17.existsSync(envPath)) continue;
|
|
3988
4091
|
try {
|
|
3989
|
-
const content =
|
|
4092
|
+
const content = fs17.readFileSync(envPath, "utf-8");
|
|
3990
4093
|
const vars = [];
|
|
3991
4094
|
for (const line of content.split("\n")) {
|
|
3992
4095
|
const trimmed = line.trim();
|
|
@@ -4034,9 +4137,9 @@ function runQaDiscovery(cwd) {
|
|
|
4034
4137
|
}
|
|
4035
4138
|
function detectDevServer(cwd, out) {
|
|
4036
4139
|
try {
|
|
4037
|
-
const pkg = JSON.parse(
|
|
4140
|
+
const pkg = JSON.parse(fs18.readFileSync(path18.join(cwd, "package.json"), "utf-8"));
|
|
4038
4141
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
4039
|
-
const pm =
|
|
4142
|
+
const pm = fs18.existsSync(path18.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs18.existsSync(path18.join(cwd, "yarn.lock")) ? "yarn" : fs18.existsSync(path18.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
4040
4143
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
4041
4144
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
4042
4145
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -4046,8 +4149,8 @@ function detectDevServer(cwd, out) {
|
|
|
4046
4149
|
function scanFrontendRoutes(cwd, out) {
|
|
4047
4150
|
const appDirs = ["src/app", "app"];
|
|
4048
4151
|
for (const appDir of appDirs) {
|
|
4049
|
-
const full =
|
|
4050
|
-
if (!
|
|
4152
|
+
const full = path18.join(cwd, appDir);
|
|
4153
|
+
if (!fs18.existsSync(full)) continue;
|
|
4051
4154
|
walkFrontendRoutes(full, "", out);
|
|
4052
4155
|
break;
|
|
4053
4156
|
}
|
|
@@ -4055,7 +4158,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
4055
4158
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
4056
4159
|
let entries;
|
|
4057
4160
|
try {
|
|
4058
|
-
entries =
|
|
4161
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
4059
4162
|
} catch {
|
|
4060
4163
|
return;
|
|
4061
4164
|
}
|
|
@@ -4072,7 +4175,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
4072
4175
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
4073
4176
|
let segment = entry.name;
|
|
4074
4177
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
4075
|
-
walkFrontendRoutes(
|
|
4178
|
+
walkFrontendRoutes(path18.join(dir, entry.name), prefix, out);
|
|
4076
4179
|
continue;
|
|
4077
4180
|
}
|
|
4078
4181
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -4080,7 +4183,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
4080
4183
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
4081
4184
|
segment = `:${segment.slice(1, -1)}`;
|
|
4082
4185
|
}
|
|
4083
|
-
walkFrontendRoutes(
|
|
4186
|
+
walkFrontendRoutes(path18.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
4084
4187
|
}
|
|
4085
4188
|
}
|
|
4086
4189
|
function detectAuthFiles(cwd, out) {
|
|
@@ -4097,23 +4200,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
4097
4200
|
"src/app/api/oauth"
|
|
4098
4201
|
];
|
|
4099
4202
|
for (const c of candidates) {
|
|
4100
|
-
if (
|
|
4203
|
+
if (fs18.existsSync(path18.join(cwd, c))) out.authFiles.push(c);
|
|
4101
4204
|
}
|
|
4102
4205
|
}
|
|
4103
4206
|
function detectRoles(cwd, out) {
|
|
4104
4207
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
4105
4208
|
for (const rp of rolePaths) {
|
|
4106
|
-
const dir =
|
|
4107
|
-
if (!
|
|
4209
|
+
const dir = path18.join(cwd, rp);
|
|
4210
|
+
if (!fs18.existsSync(dir)) continue;
|
|
4108
4211
|
let files;
|
|
4109
4212
|
try {
|
|
4110
|
-
files =
|
|
4213
|
+
files = fs18.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
4111
4214
|
} catch {
|
|
4112
4215
|
continue;
|
|
4113
4216
|
}
|
|
4114
4217
|
for (const f of files) {
|
|
4115
4218
|
try {
|
|
4116
|
-
const content =
|
|
4219
|
+
const content = fs18.readFileSync(path18.join(dir, f), "utf-8").slice(0, 5e3);
|
|
4117
4220
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
4118
4221
|
if (roleMatches) {
|
|
4119
4222
|
for (const m of roleMatches) {
|
|
@@ -4344,8 +4447,8 @@ function failedAction3(reason) {
|
|
|
4344
4447
|
}
|
|
4345
4448
|
|
|
4346
4449
|
// src/scripts/dispatchJobFileTicks.ts
|
|
4347
|
-
import * as
|
|
4348
|
-
import * as
|
|
4450
|
+
import * as fs20 from "fs";
|
|
4451
|
+
import * as path20 from "path";
|
|
4349
4452
|
|
|
4350
4453
|
// src/scripts/jobFrontmatter.ts
|
|
4351
4454
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -4596,8 +4699,8 @@ var ContentsApiBackend = class {
|
|
|
4596
4699
|
};
|
|
4597
4700
|
|
|
4598
4701
|
// src/scripts/jobState/localFileBackend.ts
|
|
4599
|
-
import * as
|
|
4600
|
-
import * as
|
|
4702
|
+
import * as fs19 from "fs";
|
|
4703
|
+
import * as path19 from "path";
|
|
4601
4704
|
var LocalFileBackend = class {
|
|
4602
4705
|
name = "local-file";
|
|
4603
4706
|
cwd;
|
|
@@ -4612,7 +4715,7 @@ var LocalFileBackend = class {
|
|
|
4612
4715
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
4613
4716
|
this.cwd = opts.cwd;
|
|
4614
4717
|
this.jobsDir = opts.jobsDir;
|
|
4615
|
-
this.absDir =
|
|
4718
|
+
this.absDir = path19.join(opts.cwd, opts.jobsDir);
|
|
4616
4719
|
this.owner = opts.owner;
|
|
4617
4720
|
this.repo = opts.repo;
|
|
4618
4721
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -4627,7 +4730,7 @@ var LocalFileBackend = class {
|
|
|
4627
4730
|
`);
|
|
4628
4731
|
return;
|
|
4629
4732
|
}
|
|
4630
|
-
|
|
4733
|
+
fs19.mkdirSync(this.absDir, { recursive: true });
|
|
4631
4734
|
const prefix = this.cacheKeyPrefix();
|
|
4632
4735
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
4633
4736
|
try {
|
|
@@ -4656,7 +4759,7 @@ var LocalFileBackend = class {
|
|
|
4656
4759
|
`);
|
|
4657
4760
|
return;
|
|
4658
4761
|
}
|
|
4659
|
-
if (!
|
|
4762
|
+
if (!fs19.existsSync(this.absDir)) {
|
|
4660
4763
|
return;
|
|
4661
4764
|
}
|
|
4662
4765
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -4672,11 +4775,11 @@ var LocalFileBackend = class {
|
|
|
4672
4775
|
}
|
|
4673
4776
|
load(slug) {
|
|
4674
4777
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
4675
|
-
const absPath =
|
|
4676
|
-
if (!
|
|
4778
|
+
const absPath = path19.join(this.cwd, relPath);
|
|
4779
|
+
if (!fs19.existsSync(absPath)) {
|
|
4677
4780
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
4678
4781
|
}
|
|
4679
|
-
const raw =
|
|
4782
|
+
const raw = fs19.readFileSync(absPath, "utf-8");
|
|
4680
4783
|
let parsed;
|
|
4681
4784
|
try {
|
|
4682
4785
|
parsed = JSON.parse(raw);
|
|
@@ -4693,10 +4796,10 @@ var LocalFileBackend = class {
|
|
|
4693
4796
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
4694
4797
|
return false;
|
|
4695
4798
|
}
|
|
4696
|
-
const absPath =
|
|
4697
|
-
|
|
4799
|
+
const absPath = path19.join(this.cwd, loaded.path);
|
|
4800
|
+
fs19.mkdirSync(path19.dirname(absPath), { recursive: true });
|
|
4698
4801
|
const body = JSON.stringify(next, null, 2) + "\n";
|
|
4699
|
-
|
|
4802
|
+
fs19.writeFileSync(absPath, body, "utf-8");
|
|
4700
4803
|
return true;
|
|
4701
4804
|
}
|
|
4702
4805
|
cacheKeyPrefix() {
|
|
@@ -4773,7 +4876,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
4773
4876
|
await backend.hydrate();
|
|
4774
4877
|
}
|
|
4775
4878
|
try {
|
|
4776
|
-
const slugs = listJobSlugs(
|
|
4879
|
+
const slugs = listJobSlugs(path20.join(ctx.cwd, jobsDir));
|
|
4777
4880
|
ctx.data.jobSlugCount = slugs.length;
|
|
4778
4881
|
if (slugs.length === 0) {
|
|
4779
4882
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -4872,17 +4975,17 @@ function formatAgo(ms) {
|
|
|
4872
4975
|
}
|
|
4873
4976
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
4874
4977
|
try {
|
|
4875
|
-
const raw =
|
|
4978
|
+
const raw = fs20.readFileSync(path20.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
4876
4979
|
return splitFrontmatter(raw).frontmatter;
|
|
4877
4980
|
} catch {
|
|
4878
4981
|
return {};
|
|
4879
4982
|
}
|
|
4880
4983
|
}
|
|
4881
4984
|
function listJobSlugs(absDir) {
|
|
4882
|
-
if (!
|
|
4985
|
+
if (!fs20.existsSync(absDir)) return [];
|
|
4883
4986
|
let entries;
|
|
4884
4987
|
try {
|
|
4885
|
-
entries =
|
|
4988
|
+
entries = fs20.readdirSync(absDir, { withFileTypes: true });
|
|
4886
4989
|
} catch {
|
|
4887
4990
|
return [];
|
|
4888
4991
|
}
|
|
@@ -5483,7 +5586,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
|
|
|
5483
5586
|
|
|
5484
5587
|
// src/gha.ts
|
|
5485
5588
|
import { execFileSync as execFileSync16 } from "child_process";
|
|
5486
|
-
import * as
|
|
5589
|
+
import * as fs21 from "fs";
|
|
5487
5590
|
function getRunUrl() {
|
|
5488
5591
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5489
5592
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5494,10 +5597,10 @@ function getRunUrl() {
|
|
|
5494
5597
|
function reactToTriggerComment(cwd) {
|
|
5495
5598
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5496
5599
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5497
|
-
if (!eventPath || !
|
|
5600
|
+
if (!eventPath || !fs21.existsSync(eventPath)) return;
|
|
5498
5601
|
let event = null;
|
|
5499
5602
|
try {
|
|
5500
|
-
event = JSON.parse(
|
|
5603
|
+
event = JSON.parse(fs21.readFileSync(eventPath, "utf-8"));
|
|
5501
5604
|
} catch {
|
|
5502
5605
|
return;
|
|
5503
5606
|
}
|
|
@@ -5782,22 +5885,22 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
5782
5885
|
|
|
5783
5886
|
// src/scripts/initFlow.ts
|
|
5784
5887
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
5785
|
-
import * as
|
|
5786
|
-
import * as
|
|
5888
|
+
import * as fs23 from "fs";
|
|
5889
|
+
import * as path22 from "path";
|
|
5787
5890
|
|
|
5788
5891
|
// src/scripts/loadQaGuide.ts
|
|
5789
|
-
import * as
|
|
5790
|
-
import * as
|
|
5892
|
+
import * as fs22 from "fs";
|
|
5893
|
+
import * as path21 from "path";
|
|
5791
5894
|
var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
|
|
5792
5895
|
var loadQaGuide = async (ctx) => {
|
|
5793
|
-
const full =
|
|
5794
|
-
if (!
|
|
5896
|
+
const full = path21.join(ctx.cwd, QA_GUIDE_REL_PATH);
|
|
5897
|
+
if (!fs22.existsSync(full)) {
|
|
5795
5898
|
ctx.data.qaGuide = "";
|
|
5796
5899
|
ctx.data.qaGuidePath = "";
|
|
5797
5900
|
return;
|
|
5798
5901
|
}
|
|
5799
5902
|
try {
|
|
5800
|
-
ctx.data.qaGuide =
|
|
5903
|
+
ctx.data.qaGuide = fs22.readFileSync(full, "utf-8");
|
|
5801
5904
|
ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
|
|
5802
5905
|
} catch {
|
|
5803
5906
|
ctx.data.qaGuide = "";
|
|
@@ -5807,9 +5910,9 @@ var loadQaGuide = async (ctx) => {
|
|
|
5807
5910
|
|
|
5808
5911
|
// src/scripts/initFlow.ts
|
|
5809
5912
|
function detectPackageManager(cwd) {
|
|
5810
|
-
if (
|
|
5811
|
-
if (
|
|
5812
|
-
if (
|
|
5913
|
+
if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5914
|
+
if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) return "yarn";
|
|
5915
|
+
if (fs23.existsSync(path22.join(cwd, "bun.lockb"))) return "bun";
|
|
5813
5916
|
return "npm";
|
|
5814
5917
|
}
|
|
5815
5918
|
function qualityCommandsFor(pm) {
|
|
@@ -5931,48 +6034,48 @@ function performInit(cwd, force) {
|
|
|
5931
6034
|
const pm = detectPackageManager(cwd);
|
|
5932
6035
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
5933
6036
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
5934
|
-
const configPath =
|
|
5935
|
-
if (
|
|
6037
|
+
const configPath = path22.join(cwd, "kody.config.json");
|
|
6038
|
+
if (fs23.existsSync(configPath) && !force) {
|
|
5936
6039
|
skipped.push("kody.config.json");
|
|
5937
6040
|
} else {
|
|
5938
6041
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
5939
|
-
|
|
6042
|
+
fs23.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
5940
6043
|
`);
|
|
5941
6044
|
wrote.push("kody.config.json");
|
|
5942
6045
|
}
|
|
5943
|
-
const workflowDir =
|
|
5944
|
-
const workflowPath =
|
|
5945
|
-
if (
|
|
6046
|
+
const workflowDir = path22.join(cwd, ".github", "workflows");
|
|
6047
|
+
const workflowPath = path22.join(workflowDir, "kody.yml");
|
|
6048
|
+
if (fs23.existsSync(workflowPath) && !force) {
|
|
5946
6049
|
skipped.push(".github/workflows/kody.yml");
|
|
5947
6050
|
} else {
|
|
5948
|
-
|
|
5949
|
-
|
|
6051
|
+
fs23.mkdirSync(workflowDir, { recursive: true });
|
|
6052
|
+
fs23.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
5950
6053
|
wrote.push(".github/workflows/kody.yml");
|
|
5951
6054
|
}
|
|
5952
|
-
const hasUi =
|
|
6055
|
+
const hasUi = fs23.existsSync(path22.join(cwd, "src/app")) || fs23.existsSync(path22.join(cwd, "app")) || fs23.existsSync(path22.join(cwd, "pages"));
|
|
5953
6056
|
if (hasUi) {
|
|
5954
|
-
const qaGuidePath =
|
|
5955
|
-
if (
|
|
6057
|
+
const qaGuidePath = path22.join(cwd, QA_GUIDE_REL_PATH);
|
|
6058
|
+
if (fs23.existsSync(qaGuidePath) && !force) {
|
|
5956
6059
|
skipped.push(QA_GUIDE_REL_PATH);
|
|
5957
6060
|
} else {
|
|
5958
|
-
|
|
6061
|
+
fs23.mkdirSync(path22.dirname(qaGuidePath), { recursive: true });
|
|
5959
6062
|
const discovery = runQaDiscovery(cwd);
|
|
5960
|
-
|
|
6063
|
+
fs23.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
|
|
5961
6064
|
wrote.push(QA_GUIDE_REL_PATH);
|
|
5962
6065
|
}
|
|
5963
6066
|
}
|
|
5964
6067
|
const builtinJobs = listBuiltinJobs();
|
|
5965
6068
|
if (builtinJobs.length > 0) {
|
|
5966
|
-
const jobsDir =
|
|
5967
|
-
|
|
6069
|
+
const jobsDir = path22.join(cwd, ".kody", "jobs");
|
|
6070
|
+
fs23.mkdirSync(jobsDir, { recursive: true });
|
|
5968
6071
|
for (const job of builtinJobs) {
|
|
5969
|
-
const rel =
|
|
5970
|
-
const target =
|
|
5971
|
-
if (
|
|
6072
|
+
const rel = path22.join(".kody", "jobs", `${job.slug}.md`);
|
|
6073
|
+
const target = path22.join(cwd, rel);
|
|
6074
|
+
if (fs23.existsSync(target) && !force) {
|
|
5972
6075
|
skipped.push(rel);
|
|
5973
6076
|
continue;
|
|
5974
6077
|
}
|
|
5975
|
-
|
|
6078
|
+
fs23.writeFileSync(target, fs23.readFileSync(job.filePath, "utf-8"));
|
|
5976
6079
|
wrote.push(rel);
|
|
5977
6080
|
}
|
|
5978
6081
|
}
|
|
@@ -5984,12 +6087,12 @@ function performInit(cwd, force) {
|
|
|
5984
6087
|
continue;
|
|
5985
6088
|
}
|
|
5986
6089
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
5987
|
-
const target =
|
|
5988
|
-
if (
|
|
6090
|
+
const target = path22.join(workflowDir, `kody-${exe.name}.yml`);
|
|
6091
|
+
if (fs23.existsSync(target) && !force) {
|
|
5989
6092
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
5990
6093
|
continue;
|
|
5991
6094
|
}
|
|
5992
|
-
|
|
6095
|
+
fs23.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
5993
6096
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
5994
6097
|
}
|
|
5995
6098
|
let labels;
|
|
@@ -6078,14 +6181,14 @@ var loadCoverageRules = async (ctx) => {
|
|
|
6078
6181
|
};
|
|
6079
6182
|
|
|
6080
6183
|
// src/goal/state.ts
|
|
6081
|
-
import * as
|
|
6082
|
-
import * as
|
|
6184
|
+
import * as fs24 from "fs";
|
|
6185
|
+
import * as path23 from "path";
|
|
6083
6186
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
6084
6187
|
var GoalStateError = class extends Error {
|
|
6085
|
-
constructor(
|
|
6086
|
-
super(`Invalid goal state at ${
|
|
6188
|
+
constructor(path30, message) {
|
|
6189
|
+
super(`Invalid goal state at ${path30}:
|
|
6087
6190
|
${message}`);
|
|
6088
|
-
this.path =
|
|
6191
|
+
this.path = path30;
|
|
6089
6192
|
this.name = "GoalStateError";
|
|
6090
6193
|
}
|
|
6091
6194
|
path;
|
|
@@ -6129,16 +6232,16 @@ function serializeGoalState(s) {
|
|
|
6129
6232
|
`;
|
|
6130
6233
|
}
|
|
6131
6234
|
function goalStatePath(cwd, goalId) {
|
|
6132
|
-
return
|
|
6235
|
+
return path23.join(cwd, ".kody", "goals", goalId, "state.json");
|
|
6133
6236
|
}
|
|
6134
6237
|
function readGoalState(cwd, goalId) {
|
|
6135
6238
|
const file = goalStatePath(cwd, goalId);
|
|
6136
|
-
if (!
|
|
6239
|
+
if (!fs24.existsSync(file)) {
|
|
6137
6240
|
throw new GoalStateError(file, "file not found");
|
|
6138
6241
|
}
|
|
6139
6242
|
let raw;
|
|
6140
6243
|
try {
|
|
6141
|
-
raw = JSON.parse(
|
|
6244
|
+
raw = JSON.parse(fs24.readFileSync(file, "utf-8"));
|
|
6142
6245
|
} catch (err) {
|
|
6143
6246
|
throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
6144
6247
|
}
|
|
@@ -6146,8 +6249,8 @@ function readGoalState(cwd, goalId) {
|
|
|
6146
6249
|
}
|
|
6147
6250
|
function writeGoalState(cwd, goalId, state) {
|
|
6148
6251
|
const file = goalStatePath(cwd, goalId);
|
|
6149
|
-
|
|
6150
|
-
|
|
6252
|
+
fs24.mkdirSync(path23.dirname(file), { recursive: true });
|
|
6253
|
+
fs24.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
6151
6254
|
}
|
|
6152
6255
|
function nowIso() {
|
|
6153
6256
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -6241,8 +6344,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
6241
6344
|
};
|
|
6242
6345
|
|
|
6243
6346
|
// src/scripts/loadJobFromFile.ts
|
|
6244
|
-
import * as
|
|
6245
|
-
import * as
|
|
6347
|
+
import * as fs25 from "fs";
|
|
6348
|
+
import * as path24 from "path";
|
|
6246
6349
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
6247
6350
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
6248
6351
|
const slugArg = String(args?.slugArg ?? "job");
|
|
@@ -6250,11 +6353,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
6250
6353
|
if (!slug) {
|
|
6251
6354
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
6252
6355
|
}
|
|
6253
|
-
const absPath =
|
|
6254
|
-
if (!
|
|
6356
|
+
const absPath = path24.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
6357
|
+
if (!fs25.existsSync(absPath)) {
|
|
6255
6358
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
6256
6359
|
}
|
|
6257
|
-
const raw =
|
|
6360
|
+
const raw = fs25.readFileSync(absPath, "utf-8");
|
|
6258
6361
|
const { title, body } = parseJobFile(raw, slug);
|
|
6259
6362
|
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
6260
6363
|
const loaded = await backend.load(slug);
|
|
@@ -6286,16 +6389,16 @@ function humanizeSlug(slug) {
|
|
|
6286
6389
|
}
|
|
6287
6390
|
|
|
6288
6391
|
// src/scripts/loadMemoryContext.ts
|
|
6289
|
-
import * as
|
|
6290
|
-
import * as
|
|
6392
|
+
import * as fs26 from "fs";
|
|
6393
|
+
import * as path25 from "path";
|
|
6291
6394
|
var MEMORY_DIR_RELATIVE = ".kody/memory";
|
|
6292
6395
|
var MAX_PAGES = 8;
|
|
6293
6396
|
var PER_PAGE_MAX_BYTES = 4e3;
|
|
6294
6397
|
var TOTAL_MAX_BYTES = 24e3;
|
|
6295
6398
|
var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
6296
6399
|
var loadMemoryContext = async (ctx) => {
|
|
6297
|
-
const memoryAbs =
|
|
6298
|
-
if (!
|
|
6400
|
+
const memoryAbs = path25.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
6401
|
+
if (!fs26.existsSync(memoryAbs)) {
|
|
6299
6402
|
ctx.data.memoryContext = "";
|
|
6300
6403
|
return;
|
|
6301
6404
|
}
|
|
@@ -6320,21 +6423,21 @@ function collectPages(memoryAbs) {
|
|
|
6320
6423
|
walkMd(memoryAbs, (file) => {
|
|
6321
6424
|
let stat;
|
|
6322
6425
|
try {
|
|
6323
|
-
stat =
|
|
6426
|
+
stat = fs26.statSync(file);
|
|
6324
6427
|
} catch {
|
|
6325
6428
|
return;
|
|
6326
6429
|
}
|
|
6327
6430
|
let raw;
|
|
6328
6431
|
try {
|
|
6329
|
-
raw =
|
|
6432
|
+
raw = fs26.readFileSync(file, "utf-8");
|
|
6330
6433
|
} catch {
|
|
6331
6434
|
return;
|
|
6332
6435
|
}
|
|
6333
6436
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
6334
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
6437
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path25.basename(file, ".md");
|
|
6335
6438
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
6336
6439
|
out.push({
|
|
6337
|
-
relPath:
|
|
6440
|
+
relPath: path25.relative(memoryAbs, file),
|
|
6338
6441
|
title,
|
|
6339
6442
|
updated,
|
|
6340
6443
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
@@ -6402,16 +6505,16 @@ function walkMd(root, visit) {
|
|
|
6402
6505
|
const dir = stack.pop();
|
|
6403
6506
|
let names;
|
|
6404
6507
|
try {
|
|
6405
|
-
names =
|
|
6508
|
+
names = fs26.readdirSync(dir);
|
|
6406
6509
|
} catch {
|
|
6407
6510
|
continue;
|
|
6408
6511
|
}
|
|
6409
6512
|
for (const name of names) {
|
|
6410
6513
|
if (name.startsWith(".")) continue;
|
|
6411
|
-
const full =
|
|
6514
|
+
const full = path25.join(dir, name);
|
|
6412
6515
|
let stat;
|
|
6413
6516
|
try {
|
|
6414
|
-
stat =
|
|
6517
|
+
stat = fs26.statSync(full);
|
|
6415
6518
|
} catch {
|
|
6416
6519
|
continue;
|
|
6417
6520
|
}
|
|
@@ -7249,30 +7352,21 @@ var recordOutcome = async (ctx, profile) => {
|
|
|
7249
7352
|
|
|
7250
7353
|
// src/scripts/requireFeedbackActions.ts
|
|
7251
7354
|
var MIN_ITEMS = 1;
|
|
7252
|
-
var requireFeedbackActions = async (ctx
|
|
7355
|
+
var requireFeedbackActions = async (ctx) => {
|
|
7253
7356
|
if (!ctx.data.agentDone) return;
|
|
7254
7357
|
const actions = String(ctx.data.feedbackActions ?? "").trim();
|
|
7255
7358
|
const items = countActionItems(actions);
|
|
7256
7359
|
ctx.data.feedbackAgentItemCount = items;
|
|
7257
7360
|
if (items < MIN_ITEMS) {
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7361
|
+
const reason = actions.length === 0 ? "FEEDBACK_ACTIONS block missing" : "FEEDBACK_ACTIONS block listed no items";
|
|
7362
|
+
process.stderr.write(
|
|
7363
|
+
`[kody requireFeedbackActions] warning: ${reason} \u2014 proceeding anyway (verifyFixAlignment + tests are the real gate)
|
|
7364
|
+
`
|
|
7262
7365
|
);
|
|
7366
|
+
ctx.data.feedbackActionsOmitted = actions.length === 0;
|
|
7367
|
+
ctx.data.feedbackActionsMalformed = actions.length > 0;
|
|
7263
7368
|
}
|
|
7264
7369
|
};
|
|
7265
|
-
function fail2(ctx, profile, reason) {
|
|
7266
|
-
ctx.data.agentDone = false;
|
|
7267
|
-
ctx.data.agentFailureReason = reason;
|
|
7268
|
-
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
7269
|
-
const failedAction6 = {
|
|
7270
|
-
type: `${modeSeg}_FAILED`,
|
|
7271
|
-
payload: { reason },
|
|
7272
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7273
|
-
};
|
|
7274
|
-
ctx.data.action = failedAction6;
|
|
7275
|
-
}
|
|
7276
7370
|
function countActionItems(block) {
|
|
7277
7371
|
if (!block.trim()) return 0;
|
|
7278
7372
|
let count = 0;
|
|
@@ -7829,8 +7923,8 @@ function resolveBaseOverride(value) {
|
|
|
7829
7923
|
|
|
7830
7924
|
// src/scripts/runTickScript.ts
|
|
7831
7925
|
import { spawnSync } from "child_process";
|
|
7832
|
-
import * as
|
|
7833
|
-
import * as
|
|
7926
|
+
import * as fs27 from "fs";
|
|
7927
|
+
import * as path26 from "path";
|
|
7834
7928
|
var runTickScript = async (ctx, _profile, args) => {
|
|
7835
7929
|
ctx.skipAgent = true;
|
|
7836
7930
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
@@ -7842,13 +7936,13 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
7842
7936
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
7843
7937
|
return;
|
|
7844
7938
|
}
|
|
7845
|
-
const jobPath =
|
|
7846
|
-
if (!
|
|
7939
|
+
const jobPath = path26.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
7940
|
+
if (!fs27.existsSync(jobPath)) {
|
|
7847
7941
|
ctx.output.exitCode = 99;
|
|
7848
7942
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
7849
7943
|
return;
|
|
7850
7944
|
}
|
|
7851
|
-
const raw =
|
|
7945
|
+
const raw = fs27.readFileSync(jobPath, "utf-8");
|
|
7852
7946
|
const { frontmatter } = splitFrontmatter(raw);
|
|
7853
7947
|
const tickScript = frontmatter.tickScript;
|
|
7854
7948
|
if (!tickScript) {
|
|
@@ -7856,8 +7950,8 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
7856
7950
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
7857
7951
|
return;
|
|
7858
7952
|
}
|
|
7859
|
-
const scriptPath =
|
|
7860
|
-
if (!
|
|
7953
|
+
const scriptPath = path26.isAbsolute(tickScript) ? tickScript : path26.join(ctx.cwd, tickScript);
|
|
7954
|
+
if (!fs27.existsSync(scriptPath)) {
|
|
7861
7955
|
ctx.output.exitCode = 99;
|
|
7862
7956
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
7863
7957
|
return;
|
|
@@ -8794,7 +8888,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
8794
8888
|
};
|
|
8795
8889
|
|
|
8796
8890
|
// src/scripts/writeRunSummary.ts
|
|
8797
|
-
import * as
|
|
8891
|
+
import * as fs28 from "fs";
|
|
8798
8892
|
var writeRunSummary = async (ctx, profile) => {
|
|
8799
8893
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
8800
8894
|
if (!summaryPath) return;
|
|
@@ -8816,7 +8910,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
8816
8910
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
8817
8911
|
lines.push("");
|
|
8818
8912
|
try {
|
|
8819
|
-
|
|
8913
|
+
fs28.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
8820
8914
|
`);
|
|
8821
8915
|
} catch {
|
|
8822
8916
|
}
|
|
@@ -8952,22 +9046,42 @@ function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
|
8952
9046
|
// src/executor.ts
|
|
8953
9047
|
var CONTAINER_MAX_ITERATIONS = 50;
|
|
8954
9048
|
async function runExecutable(profileName, input) {
|
|
9049
|
+
const stageStartedAt = Date.now();
|
|
9050
|
+
emitEvent(input.cwd, { executable: profileName, kind: "stage_start" });
|
|
9051
|
+
const finishAndEnd = (out) => {
|
|
9052
|
+
emitEvent(input.cwd, {
|
|
9053
|
+
executable: profileName,
|
|
9054
|
+
kind: "stage_end",
|
|
9055
|
+
durationMs: Date.now() - stageStartedAt,
|
|
9056
|
+
outcome: out.exitCode === 0 ? "ok" : "failed",
|
|
9057
|
+
meta: {
|
|
9058
|
+
exitCode: out.exitCode,
|
|
9059
|
+
...out.reason ? { reason: out.reason } : {},
|
|
9060
|
+
...out.prUrl ? { prUrl: out.prUrl } : {}
|
|
9061
|
+
}
|
|
9062
|
+
});
|
|
9063
|
+
if (out.prUrl) process.stdout.write(`PR_URL=${out.prUrl}
|
|
9064
|
+
`);
|
|
9065
|
+
else if (out.reason) process.stdout.write(`PR_URL=FAILED: ${out.reason}
|
|
9066
|
+
`);
|
|
9067
|
+
return out;
|
|
9068
|
+
};
|
|
8955
9069
|
const profilePath = resolveProfilePath(profileName);
|
|
8956
9070
|
const profile = loadProfile(profilePath);
|
|
8957
9071
|
const missing = validateScriptReferences(profile, allScriptNames);
|
|
8958
9072
|
if (missing.length > 0) {
|
|
8959
|
-
return
|
|
9073
|
+
return finishAndEnd({ exitCode: 99, reason: `profile references unknown scripts: ${missing.join(", ")}` });
|
|
8960
9074
|
}
|
|
8961
9075
|
let args;
|
|
8962
9076
|
try {
|
|
8963
9077
|
args = validateInputs(profile.inputs, input.cliArgs);
|
|
8964
9078
|
} catch (err) {
|
|
8965
|
-
return
|
|
9079
|
+
return finishAndEnd({ exitCode: 64, reason: err instanceof Error ? err.message : String(err) });
|
|
8966
9080
|
}
|
|
8967
9081
|
const toolResults = verifyCliTools(profile.cliTools, input.cwd);
|
|
8968
9082
|
const firstFail = firstRequiredFailure(toolResults, profile.cliTools);
|
|
8969
9083
|
if (firstFail) {
|
|
8970
|
-
return
|
|
9084
|
+
return finishAndEnd({ exitCode: 99, reason: `required CLI tool check failed: ${firstFail.error}` });
|
|
8971
9085
|
}
|
|
8972
9086
|
let config;
|
|
8973
9087
|
if (input.config) {
|
|
@@ -8983,7 +9097,7 @@ async function runExecutable(profileName, input) {
|
|
|
8983
9097
|
try {
|
|
8984
9098
|
config = loadConfig(input.cwd);
|
|
8985
9099
|
} catch (err) {
|
|
8986
|
-
return
|
|
9100
|
+
return finishAndEnd({ exitCode: 99, reason: `config error: ${err instanceof Error ? err.message : String(err)}` });
|
|
8987
9101
|
}
|
|
8988
9102
|
}
|
|
8989
9103
|
const modelSpec = profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
|
|
@@ -8991,13 +9105,13 @@ async function runExecutable(profileName, input) {
|
|
|
8991
9105
|
try {
|
|
8992
9106
|
model = parseProviderModel(modelSpec);
|
|
8993
9107
|
} catch (err) {
|
|
8994
|
-
return
|
|
9108
|
+
return finishAndEnd({ exitCode: 99, reason: `agent.model invalid: ${err instanceof Error ? err.message : String(err)}` });
|
|
8995
9109
|
}
|
|
8996
9110
|
let litellm = null;
|
|
8997
9111
|
try {
|
|
8998
9112
|
litellm = await startLitellmIfNeeded(model, input.cwd);
|
|
8999
9113
|
} catch (err) {
|
|
9000
|
-
return
|
|
9114
|
+
return finishAndEnd({
|
|
9001
9115
|
exitCode: 99,
|
|
9002
9116
|
reason: `litellm startup failed: ${err instanceof Error ? err.message : String(err)}`
|
|
9003
9117
|
});
|
|
@@ -9011,9 +9125,9 @@ async function runExecutable(profileName, input) {
|
|
|
9011
9125
|
data: {},
|
|
9012
9126
|
output: { exitCode: 0 }
|
|
9013
9127
|
};
|
|
9014
|
-
const ndjsonDir =
|
|
9128
|
+
const ndjsonDir = path27.join(input.cwd, ".kody");
|
|
9015
9129
|
const invokeAgent = async (prompt) => {
|
|
9016
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
9130
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path27.isAbsolute(p) ? p : path27.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
9017
9131
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
9018
9132
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
9019
9133
|
return runAgent({
|
|
@@ -9037,15 +9151,39 @@ async function runExecutable(profileName, input) {
|
|
|
9037
9151
|
ctx.data.__invokeAgent = invokeAgent;
|
|
9038
9152
|
try {
|
|
9039
9153
|
for (const entry of profile.scripts.preflight) {
|
|
9040
|
-
|
|
9154
|
+
const preLabel = entry.script ?? entry.shell ?? "<unknown>";
|
|
9155
|
+
if (!shouldRun(entry, ctx)) {
|
|
9156
|
+
emitEvent(input.cwd, {
|
|
9157
|
+
executable: profileName,
|
|
9158
|
+
kind: "preflight",
|
|
9159
|
+
name: preLabel,
|
|
9160
|
+
outcome: "skipped"
|
|
9161
|
+
});
|
|
9162
|
+
continue;
|
|
9163
|
+
}
|
|
9164
|
+
const t0 = Date.now();
|
|
9041
9165
|
if (entry.shell) {
|
|
9042
9166
|
await runShellEntry(entry, ctx, profile);
|
|
9167
|
+
emitEvent(input.cwd, {
|
|
9168
|
+
executable: profileName,
|
|
9169
|
+
kind: "preflight",
|
|
9170
|
+
name: preLabel,
|
|
9171
|
+
durationMs: Date.now() - t0,
|
|
9172
|
+
outcome: ctx.output.exitCode && ctx.output.exitCode !== 0 ? "failed" : "ok"
|
|
9173
|
+
});
|
|
9043
9174
|
} else {
|
|
9044
9175
|
const fn = preflightScripts[entry.script];
|
|
9045
|
-
if (!fn) return
|
|
9176
|
+
if (!fn) return finishAndEnd({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
|
|
9046
9177
|
await fn(ctx, profile, entry.with);
|
|
9178
|
+
emitEvent(input.cwd, {
|
|
9179
|
+
executable: profileName,
|
|
9180
|
+
kind: "preflight",
|
|
9181
|
+
name: preLabel,
|
|
9182
|
+
durationMs: Date.now() - t0,
|
|
9183
|
+
outcome: ctx.skipAgent && ctx.output.exitCode && ctx.output.exitCode !== 0 ? "failed" : "ok"
|
|
9184
|
+
});
|
|
9047
9185
|
if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
|
|
9048
|
-
return
|
|
9186
|
+
return finishAndEnd(ctx.output);
|
|
9049
9187
|
}
|
|
9050
9188
|
}
|
|
9051
9189
|
}
|
|
@@ -9056,9 +9194,21 @@ async function runExecutable(profileName, input) {
|
|
|
9056
9194
|
} else if (!ctx.skipAgent) {
|
|
9057
9195
|
const prompt = ctx.data.prompt;
|
|
9058
9196
|
if (!prompt) {
|
|
9059
|
-
return
|
|
9197
|
+
return finishAndEnd({ exitCode: 99, reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)" });
|
|
9060
9198
|
}
|
|
9199
|
+
emitEvent(input.cwd, { executable: profileName, kind: "agent_start" });
|
|
9061
9200
|
agentResult = await invokeAgent(prompt);
|
|
9201
|
+
emitEvent(input.cwd, {
|
|
9202
|
+
executable: profileName,
|
|
9203
|
+
kind: "agent_end",
|
|
9204
|
+
durationMs: agentResult.durationMs,
|
|
9205
|
+
outcome: agentResult.outcome === "completed" ? "ok" : "failed",
|
|
9206
|
+
meta: {
|
|
9207
|
+
...agentResult.tokens ? { tokens: agentResult.tokens } : {},
|
|
9208
|
+
...typeof agentResult.messageCount === "number" ? { messageCount: agentResult.messageCount } : {},
|
|
9209
|
+
...agentResult.error ? { error: agentResult.error } : {}
|
|
9210
|
+
}
|
|
9211
|
+
});
|
|
9062
9212
|
}
|
|
9063
9213
|
for (const entry of profile.scripts.postflight) {
|
|
9064
9214
|
const entryLabel = entry.script ?? entry.shell ?? "<unknown>";
|
|
@@ -9073,18 +9223,27 @@ async function runExecutable(profileName, input) {
|
|
|
9073
9223
|
process.stderr.write(`[kody postflight] skip ${entryLabel}: ${reasons.join("; ")}
|
|
9074
9224
|
`);
|
|
9075
9225
|
}
|
|
9226
|
+
emitEvent(input.cwd, {
|
|
9227
|
+
executable: profileName,
|
|
9228
|
+
kind: "postflight",
|
|
9229
|
+
name: entryLabel,
|
|
9230
|
+
outcome: "skipped"
|
|
9231
|
+
});
|
|
9076
9232
|
continue;
|
|
9077
9233
|
}
|
|
9078
9234
|
const label = entryLabel;
|
|
9235
|
+
const t0 = Date.now();
|
|
9236
|
+
let postOutcome = "ok";
|
|
9079
9237
|
try {
|
|
9080
9238
|
if (entry.shell) {
|
|
9081
9239
|
await runShellEntry(entry, ctx, profile);
|
|
9082
9240
|
} else {
|
|
9083
9241
|
const fn = postflightScripts[entry.script];
|
|
9084
|
-
if (!fn) return
|
|
9242
|
+
if (!fn) return finishAndEnd({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
|
|
9085
9243
|
await fn(ctx, profile, agentResult, entry.with);
|
|
9086
9244
|
}
|
|
9087
9245
|
} catch (err) {
|
|
9246
|
+
postOutcome = "failed";
|
|
9088
9247
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9089
9248
|
process.stderr.write(`[kody] postflight "${label}" crashed: ${msg}
|
|
9090
9249
|
`);
|
|
@@ -9092,8 +9251,15 @@ async function runExecutable(profileName, input) {
|
|
|
9092
9251
|
ctx.output.reason = ctx.output.reason ? `${ctx.output.reason}; ${summary}` : summary;
|
|
9093
9252
|
if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
|
|
9094
9253
|
}
|
|
9254
|
+
emitEvent(input.cwd, {
|
|
9255
|
+
executable: profileName,
|
|
9256
|
+
kind: "postflight",
|
|
9257
|
+
name: label,
|
|
9258
|
+
durationMs: Date.now() - t0,
|
|
9259
|
+
outcome: postOutcome
|
|
9260
|
+
});
|
|
9095
9261
|
}
|
|
9096
|
-
return
|
|
9262
|
+
return finishAndEnd({
|
|
9097
9263
|
exitCode: ctx.output.exitCode ?? 0,
|
|
9098
9264
|
prUrl: ctx.output.prUrl,
|
|
9099
9265
|
reason: ctx.output.reason
|
|
@@ -9122,17 +9288,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
9122
9288
|
function resolveProfilePath(profileName) {
|
|
9123
9289
|
const found = resolveExecutable(profileName);
|
|
9124
9290
|
if (found) return found;
|
|
9125
|
-
const here =
|
|
9291
|
+
const here = path27.dirname(new URL(import.meta.url).pathname);
|
|
9126
9292
|
const candidates = [
|
|
9127
|
-
|
|
9293
|
+
path27.join(here, "executables", profileName, "profile.json"),
|
|
9128
9294
|
// same-dir sibling (dev)
|
|
9129
|
-
|
|
9295
|
+
path27.join(here, "..", "executables", profileName, "profile.json"),
|
|
9130
9296
|
// up one (prod: dist/bin → dist/executables)
|
|
9131
|
-
|
|
9297
|
+
path27.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
9132
9298
|
// fallback
|
|
9133
9299
|
];
|
|
9134
9300
|
for (const c of candidates) {
|
|
9135
|
-
if (
|
|
9301
|
+
if (fs29.existsSync(c)) return c;
|
|
9136
9302
|
}
|
|
9137
9303
|
return candidates[0];
|
|
9138
9304
|
}
|
|
@@ -9215,13 +9381,6 @@ function resolveDottedPath(root, key) {
|
|
|
9215
9381
|
}
|
|
9216
9382
|
return cur;
|
|
9217
9383
|
}
|
|
9218
|
-
function finish(out) {
|
|
9219
|
-
if (out.prUrl) process.stdout.write(`PR_URL=${out.prUrl}
|
|
9220
|
-
`);
|
|
9221
|
-
else if (out.reason) process.stdout.write(`PR_URL=FAILED: ${out.reason}
|
|
9222
|
-
`);
|
|
9223
|
-
return out;
|
|
9224
|
-
}
|
|
9225
9384
|
var DEFAULT_SHELL_TIMEOUT_MS = 3e5;
|
|
9226
9385
|
function resolveShellTimeoutMs(entry) {
|
|
9227
9386
|
if (typeof entry.timeoutSec === "number" && entry.timeoutSec > 0) {
|
|
@@ -9236,8 +9395,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
9236
9395
|
var SIGKILL_GRACE_MS = 5e3;
|
|
9237
9396
|
async function runShellEntry(entry, ctx, profile) {
|
|
9238
9397
|
const shellName = entry.shell;
|
|
9239
|
-
const shellPath =
|
|
9240
|
-
if (!
|
|
9398
|
+
const shellPath = path27.join(profile.dir, shellName);
|
|
9399
|
+
if (!fs29.existsSync(shellPath)) {
|
|
9241
9400
|
ctx.skipAgent = true;
|
|
9242
9401
|
ctx.output.exitCode = 99;
|
|
9243
9402
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -9412,6 +9571,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
9412
9571
|
cliArgs = { issue: issueNumber };
|
|
9413
9572
|
}
|
|
9414
9573
|
let childOut;
|
|
9574
|
+
const childStartedAt = Date.now();
|
|
9415
9575
|
try {
|
|
9416
9576
|
childOut = await runChild(child.exec, {
|
|
9417
9577
|
cliArgs,
|
|
@@ -9421,7 +9581,23 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
9421
9581
|
verbose: input.verbose,
|
|
9422
9582
|
quiet: input.quiet
|
|
9423
9583
|
});
|
|
9584
|
+
emitEvent(input.cwd, {
|
|
9585
|
+
executable: profile.name,
|
|
9586
|
+
kind: "container_child",
|
|
9587
|
+
name: child.exec,
|
|
9588
|
+
durationMs: Date.now() - childStartedAt,
|
|
9589
|
+
outcome: childOut.exitCode === 0 ? "ok" : "failed",
|
|
9590
|
+
meta: { exitCode: childOut.exitCode, iteration }
|
|
9591
|
+
});
|
|
9424
9592
|
} catch (err) {
|
|
9593
|
+
emitEvent(input.cwd, {
|
|
9594
|
+
executable: profile.name,
|
|
9595
|
+
kind: "container_child",
|
|
9596
|
+
name: child.exec,
|
|
9597
|
+
durationMs: Date.now() - childStartedAt,
|
|
9598
|
+
outcome: "failed",
|
|
9599
|
+
meta: { iteration, error: err instanceof Error ? err.message : String(err) }
|
|
9600
|
+
});
|
|
9425
9601
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9426
9602
|
process.stderr.write(`[kody container] child "${child.exec}" crashed: ${msg}
|
|
9427
9603
|
`);
|
|
@@ -9650,9 +9826,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
9650
9826
|
return token;
|
|
9651
9827
|
}
|
|
9652
9828
|
function detectPackageManager2(cwd) {
|
|
9653
|
-
if (
|
|
9654
|
-
if (
|
|
9655
|
-
if (
|
|
9829
|
+
if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9830
|
+
if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
|
|
9831
|
+
if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
|
|
9656
9832
|
return "npm";
|
|
9657
9833
|
}
|
|
9658
9834
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -9739,11 +9915,11 @@ function configureGitIdentity(cwd) {
|
|
|
9739
9915
|
}
|
|
9740
9916
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
9741
9917
|
if (!issueNumber) return;
|
|
9742
|
-
const logPath =
|
|
9918
|
+
const logPath = path28.join(cwd, ".kody", "last-run.jsonl");
|
|
9743
9919
|
let tail = "";
|
|
9744
9920
|
try {
|
|
9745
|
-
if (
|
|
9746
|
-
const content =
|
|
9921
|
+
if (fs30.existsSync(logPath)) {
|
|
9922
|
+
const content = fs30.readFileSync(logPath, "utf-8");
|
|
9747
9923
|
tail = content.slice(-3e3);
|
|
9748
9924
|
}
|
|
9749
9925
|
} catch {
|
|
@@ -9768,7 +9944,7 @@ async function runCi(argv) {
|
|
|
9768
9944
|
return 0;
|
|
9769
9945
|
}
|
|
9770
9946
|
const args = parseCiArgs(argv);
|
|
9771
|
-
const cwd = args.cwd ?
|
|
9947
|
+
const cwd = args.cwd ? path28.resolve(args.cwd) : process.cwd();
|
|
9772
9948
|
let earlyConfig;
|
|
9773
9949
|
try {
|
|
9774
9950
|
earlyConfig = loadConfig(cwd);
|
|
@@ -9778,9 +9954,9 @@ async function runCi(argv) {
|
|
|
9778
9954
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
9779
9955
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
9780
9956
|
let manualWorkflowDispatch = false;
|
|
9781
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
9957
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs30.existsSync(dispatchEventPath)) {
|
|
9782
9958
|
try {
|
|
9783
|
-
const evt = JSON.parse(
|
|
9959
|
+
const evt = JSON.parse(fs30.readFileSync(dispatchEventPath, "utf-8"));
|
|
9784
9960
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
9785
9961
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
9786
9962
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -10025,9 +10201,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
10025
10201
|
return result;
|
|
10026
10202
|
}
|
|
10027
10203
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
10028
|
-
const sessionFile =
|
|
10029
|
-
const eventsFile =
|
|
10030
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
10204
|
+
const sessionFile = path29.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
10205
|
+
const eventsFile = path29.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
10206
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs31.existsSync(path29.join(cwd, p)));
|
|
10031
10207
|
if (paths.length === 0) return;
|
|
10032
10208
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
10033
10209
|
try {
|
|
@@ -10065,7 +10241,7 @@ async function runChat(argv) {
|
|
|
10065
10241
|
${CHAT_HELP}`);
|
|
10066
10242
|
return 64;
|
|
10067
10243
|
}
|
|
10068
|
-
const cwd = args.cwd ?
|
|
10244
|
+
const cwd = args.cwd ? path29.resolve(args.cwd) : process.cwd();
|
|
10069
10245
|
const sessionId = args.sessionId;
|
|
10070
10246
|
const unpackedSecrets = unpackAllSecrets();
|
|
10071
10247
|
if (unpackedSecrets > 0) {
|
|
@@ -10117,7 +10293,7 @@ ${CHAT_HELP}`);
|
|
|
10117
10293
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
10118
10294
|
const meta = readMeta(sessionFile);
|
|
10119
10295
|
process.stdout.write(
|
|
10120
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
10296
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs31.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
10121
10297
|
`
|
|
10122
10298
|
);
|
|
10123
10299
|
try {
|
|
@@ -10154,6 +10330,206 @@ ${CHAT_HELP}`);
|
|
|
10154
10330
|
}
|
|
10155
10331
|
}
|
|
10156
10332
|
|
|
10333
|
+
// src/stats.ts
|
|
10334
|
+
function parseStatsArgs(argv) {
|
|
10335
|
+
const out = { cwd: process.cwd() };
|
|
10336
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10337
|
+
const a = argv[i];
|
|
10338
|
+
if (a === "--json") out.asJson = true;
|
|
10339
|
+
else if (a === "--cwd" && argv[i + 1]) {
|
|
10340
|
+
out.cwd = argv[++i];
|
|
10341
|
+
} else if (a === "--since" && argv[i + 1]) {
|
|
10342
|
+
out.sinceMs = parseDuration(argv[++i]);
|
|
10343
|
+
} else if (a === "--run" && argv[i + 1]) {
|
|
10344
|
+
out.runId = argv[++i];
|
|
10345
|
+
}
|
|
10346
|
+
}
|
|
10347
|
+
return out;
|
|
10348
|
+
}
|
|
10349
|
+
function parseDuration(s) {
|
|
10350
|
+
const m = /^(\d+)\s*([smhd])$/i.exec(s.trim());
|
|
10351
|
+
if (!m) return void 0;
|
|
10352
|
+
const n = Number.parseInt(m[1], 10);
|
|
10353
|
+
const unit = m[2].toLowerCase();
|
|
10354
|
+
const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
10355
|
+
return n * mult;
|
|
10356
|
+
}
|
|
10357
|
+
function percentile(sorted, p) {
|
|
10358
|
+
if (sorted.length === 0) return 0;
|
|
10359
|
+
const idx = Math.min(sorted.length - 1, Math.max(0, Math.floor(p / 100 * sorted.length)));
|
|
10360
|
+
return sorted[idx];
|
|
10361
|
+
}
|
|
10362
|
+
function summarizeRun(events) {
|
|
10363
|
+
if (events.length === 0) return null;
|
|
10364
|
+
const sorted = [...events].sort((a, b) => a.ts.localeCompare(b.ts));
|
|
10365
|
+
const start = sorted.find((e) => e.kind === "stage_start");
|
|
10366
|
+
const ends = sorted.filter((e) => e.kind === "stage_end");
|
|
10367
|
+
const lastEnd = ends.length > 0 ? ends[ends.length - 1] : void 0;
|
|
10368
|
+
const startedAt = start?.ts ?? sorted[0].ts;
|
|
10369
|
+
const endedAt = lastEnd?.ts ?? sorted[sorted.length - 1].ts;
|
|
10370
|
+
const durationMs = new Date(endedAt).getTime() - new Date(startedAt).getTime();
|
|
10371
|
+
const exitCodeRaw = lastEnd?.meta?.exitCode;
|
|
10372
|
+
const exitCode = typeof exitCodeRaw === "number" ? exitCodeRaw : null;
|
|
10373
|
+
const executables = Array.from(new Set(sorted.map((e) => e.executable)));
|
|
10374
|
+
let tIn = 0;
|
|
10375
|
+
let tOut = 0;
|
|
10376
|
+
let tCacheR = 0;
|
|
10377
|
+
for (const ev of sorted) {
|
|
10378
|
+
if (ev.kind !== "agent_end") continue;
|
|
10379
|
+
const tokens = ev.meta?.tokens;
|
|
10380
|
+
if (tokens) {
|
|
10381
|
+
tIn += Number(tokens.input ?? 0);
|
|
10382
|
+
tOut += Number(tokens.output ?? 0);
|
|
10383
|
+
tCacheR += Number(tokens.cacheRead ?? 0);
|
|
10384
|
+
}
|
|
10385
|
+
}
|
|
10386
|
+
return {
|
|
10387
|
+
runId: sorted[0].runId,
|
|
10388
|
+
startedAt,
|
|
10389
|
+
endedAt,
|
|
10390
|
+
durationMs,
|
|
10391
|
+
executables,
|
|
10392
|
+
exitCode,
|
|
10393
|
+
ok: exitCode === 0,
|
|
10394
|
+
totalInputTokens: tIn,
|
|
10395
|
+
totalOutputTokens: tOut,
|
|
10396
|
+
totalCacheReadTokens: tCacheR
|
|
10397
|
+
};
|
|
10398
|
+
}
|
|
10399
|
+
function rollupByExecutable(events) {
|
|
10400
|
+
const byExec = /* @__PURE__ */ new Map();
|
|
10401
|
+
for (const ev of events) {
|
|
10402
|
+
if (ev.kind !== "stage_end") continue;
|
|
10403
|
+
if (!byExec.has(ev.executable)) byExec.set(ev.executable, []);
|
|
10404
|
+
byExec.get(ev.executable).push(ev);
|
|
10405
|
+
}
|
|
10406
|
+
const rollups = [];
|
|
10407
|
+
for (const [executable, stageEnds] of byExec) {
|
|
10408
|
+
const durations = stageEnds.map((e) => e.durationMs ?? 0).filter((d) => d > 0).sort((a, b) => a - b);
|
|
10409
|
+
const ok = stageEnds.filter((e) => e.outcome === "ok").length;
|
|
10410
|
+
const failed = stageEnds.filter((e) => e.outcome === "failed").length;
|
|
10411
|
+
let tIn = 0;
|
|
10412
|
+
let tOut = 0;
|
|
10413
|
+
let tCacheR = 0;
|
|
10414
|
+
let tCacheC = 0;
|
|
10415
|
+
for (const ev of events) {
|
|
10416
|
+
if (ev.kind !== "agent_end") continue;
|
|
10417
|
+
if (ev.executable !== executable) continue;
|
|
10418
|
+
const tokens = ev.meta?.tokens;
|
|
10419
|
+
if (tokens) {
|
|
10420
|
+
tIn += Number(tokens.input ?? 0);
|
|
10421
|
+
tOut += Number(tokens.output ?? 0);
|
|
10422
|
+
tCacheR += Number(tokens.cacheRead ?? 0);
|
|
10423
|
+
tCacheC += Number(tokens.cacheCreate ?? 0);
|
|
10424
|
+
}
|
|
10425
|
+
}
|
|
10426
|
+
const mean = durations.length > 0 ? durations.reduce((s, n) => s + n, 0) / durations.length : 0;
|
|
10427
|
+
rollups.push({
|
|
10428
|
+
executable,
|
|
10429
|
+
runs: stageEnds.length,
|
|
10430
|
+
ok,
|
|
10431
|
+
failed,
|
|
10432
|
+
p50Ms: percentile(durations, 50),
|
|
10433
|
+
p95Ms: percentile(durations, 95),
|
|
10434
|
+
meanMs: Math.round(mean),
|
|
10435
|
+
totalInputTokens: tIn,
|
|
10436
|
+
totalOutputTokens: tOut,
|
|
10437
|
+
totalCacheReadTokens: tCacheR,
|
|
10438
|
+
totalCacheCreateTokens: tCacheC
|
|
10439
|
+
});
|
|
10440
|
+
}
|
|
10441
|
+
rollups.sort((a, b) => b.runs - a.runs);
|
|
10442
|
+
return rollups;
|
|
10443
|
+
}
|
|
10444
|
+
async function runStats(argv) {
|
|
10445
|
+
const opts = parseStatsArgs(argv);
|
|
10446
|
+
const runIds = opts.runId ? [opts.runId] : listRuns(opts.cwd);
|
|
10447
|
+
if (runIds.length === 0) {
|
|
10448
|
+
process.stdout.write(`no runs found under ${opts.cwd}/.kody/runs/
|
|
10449
|
+
`);
|
|
10450
|
+
return 0;
|
|
10451
|
+
}
|
|
10452
|
+
const cutoff = opts.sinceMs ? Date.now() - opts.sinceMs : null;
|
|
10453
|
+
const allEvents = [];
|
|
10454
|
+
const runSummaries = [];
|
|
10455
|
+
for (const id of runIds) {
|
|
10456
|
+
const events = readEvents(opts.cwd, id);
|
|
10457
|
+
if (events.length === 0) continue;
|
|
10458
|
+
const summary = summarizeRun(events);
|
|
10459
|
+
if (!summary) continue;
|
|
10460
|
+
if (cutoff && new Date(summary.startedAt).getTime() < cutoff) continue;
|
|
10461
|
+
allEvents.push(...events);
|
|
10462
|
+
runSummaries.push(summary);
|
|
10463
|
+
}
|
|
10464
|
+
if (runSummaries.length === 0) {
|
|
10465
|
+
process.stdout.write("no runs in the requested window\n");
|
|
10466
|
+
return 0;
|
|
10467
|
+
}
|
|
10468
|
+
const byExec = rollupByExecutable(allEvents);
|
|
10469
|
+
if (opts.asJson) {
|
|
10470
|
+
process.stdout.write(`${JSON.stringify({ runs: runSummaries, byExecutable: byExec }, null, 2)}
|
|
10471
|
+
`);
|
|
10472
|
+
return 0;
|
|
10473
|
+
}
|
|
10474
|
+
printReport(runSummaries, byExec);
|
|
10475
|
+
return 0;
|
|
10476
|
+
}
|
|
10477
|
+
function printReport(runs, rollups) {
|
|
10478
|
+
const totalRuns = runs.length;
|
|
10479
|
+
const okRuns = runs.filter((r) => r.ok).length;
|
|
10480
|
+
const okPct = totalRuns > 0 ? (okRuns / totalRuns * 100).toFixed(1) : "\u2014";
|
|
10481
|
+
const durations = runs.map((r) => r.durationMs).sort((a, b) => a - b);
|
|
10482
|
+
const meanMs = durations.length > 0 ? durations.reduce((s, n) => s + n, 0) / durations.length : 0;
|
|
10483
|
+
process.stdout.write(`
|
|
10484
|
+
Kody run statistics \u2014 ${totalRuns} runs
|
|
10485
|
+
`);
|
|
10486
|
+
process.stdout.write(` success rate : ${okRuns}/${totalRuns} (${okPct}%)
|
|
10487
|
+
`);
|
|
10488
|
+
process.stdout.write(` mean wall-clock : ${formatMs(meanMs)}
|
|
10489
|
+
`);
|
|
10490
|
+
process.stdout.write(` p50 wall-clock : ${formatMs(percentile(durations, 50))}
|
|
10491
|
+
`);
|
|
10492
|
+
process.stdout.write(` p95 wall-clock : ${formatMs(percentile(durations, 95))}
|
|
10493
|
+
`);
|
|
10494
|
+
const totalIn = runs.reduce((s, r) => s + r.totalInputTokens, 0);
|
|
10495
|
+
const totalOut = runs.reduce((s, r) => s + r.totalOutputTokens, 0);
|
|
10496
|
+
const totalCacheR = runs.reduce((s, r) => s + r.totalCacheReadTokens, 0);
|
|
10497
|
+
process.stdout.write(` total tokens : ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out / ${totalCacheR.toLocaleString()} cache-read
|
|
10498
|
+
`);
|
|
10499
|
+
process.stdout.write(`
|
|
10500
|
+
Per-executable (stage_end events)
|
|
10501
|
+
`);
|
|
10502
|
+
const headers = ["executable", "runs", "ok", "failed", "p50", "p95", "mean", "tok-in", "tok-out", "cache-r"];
|
|
10503
|
+
const widths = [22, 6, 6, 7, 9, 9, 9, 10, 10, 10];
|
|
10504
|
+
process.stdout.write(headers.map((h, i) => h.padEnd(widths[i])).join("") + "\n");
|
|
10505
|
+
process.stdout.write(widths.map((w) => "-".repeat(w - 1) + " ").join("") + "\n");
|
|
10506
|
+
for (const r of rollups) {
|
|
10507
|
+
const row = [
|
|
10508
|
+
r.executable,
|
|
10509
|
+
String(r.runs),
|
|
10510
|
+
String(r.ok),
|
|
10511
|
+
String(r.failed),
|
|
10512
|
+
formatMs(r.p50Ms),
|
|
10513
|
+
formatMs(r.p95Ms),
|
|
10514
|
+
formatMs(r.meanMs),
|
|
10515
|
+
r.totalInputTokens.toLocaleString(),
|
|
10516
|
+
r.totalOutputTokens.toLocaleString(),
|
|
10517
|
+
r.totalCacheReadTokens.toLocaleString()
|
|
10518
|
+
];
|
|
10519
|
+
process.stdout.write(row.map((c, i) => c.padEnd(widths[i])).join("") + "\n");
|
|
10520
|
+
}
|
|
10521
|
+
process.stdout.write("\n");
|
|
10522
|
+
}
|
|
10523
|
+
function formatMs(ms) {
|
|
10524
|
+
if (!Number.isFinite(ms) || ms <= 0) return "\u2014";
|
|
10525
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
10526
|
+
const seconds = ms / 1e3;
|
|
10527
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
10528
|
+
const minutes = seconds / 60;
|
|
10529
|
+
if (minutes < 60) return `${minutes.toFixed(1)}m`;
|
|
10530
|
+
return `${(minutes / 60).toFixed(2)}h`;
|
|
10531
|
+
}
|
|
10532
|
+
|
|
10157
10533
|
// src/entry.ts
|
|
10158
10534
|
var HELP_TEXT = `kody \u2014 single-session autonomous engineer
|
|
10159
10535
|
|
|
@@ -10166,6 +10542,7 @@ Usage:
|
|
|
10166
10542
|
kody <other> [--cwd <path>] [--verbose|--quiet]
|
|
10167
10543
|
kody ci --issue <N> [preflight flags \u2014 see: kody ci --help]
|
|
10168
10544
|
kody chat [chat flags \u2014 see: kody chat --help]
|
|
10545
|
+
kody stats [--since 7d|--run <id>|--json|--cwd <path>]
|
|
10169
10546
|
kody help
|
|
10170
10547
|
kody version
|
|
10171
10548
|
|
|
@@ -10199,6 +10576,9 @@ function parseArgs(argv) {
|
|
|
10199
10576
|
if (cmd === "chat") {
|
|
10200
10577
|
return { ...result, command: "chat", chatArgv: argv.slice(1) };
|
|
10201
10578
|
}
|
|
10579
|
+
if (cmd === "stats") {
|
|
10580
|
+
return { ...result, command: "stats", statsArgv: argv.slice(1) };
|
|
10581
|
+
}
|
|
10202
10582
|
if (hasExecutable(cmd)) {
|
|
10203
10583
|
result.command = "__executable__";
|
|
10204
10584
|
result.executableName = cmd;
|
|
@@ -10209,7 +10589,7 @@ function parseArgs(argv) {
|
|
|
10209
10589
|
return result;
|
|
10210
10590
|
}
|
|
10211
10591
|
const discovered = listExecutables().map((e) => e.name);
|
|
10212
|
-
const available = ["ci", "help", "version", ...discovered];
|
|
10592
|
+
const available = ["ci", "chat", "stats", "help", "version", ...discovered];
|
|
10213
10593
|
result.errors.push(`unknown command: ${cmd} (available: ${available.join(", ")})`);
|
|
10214
10594
|
return result;
|
|
10215
10595
|
}
|
|
@@ -10251,6 +10631,18 @@ ${HELP_TEXT}`);
|
|
|
10251
10631
|
process.stderr.write(`[kody] fatal: ${msg}
|
|
10252
10632
|
`);
|
|
10253
10633
|
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
10634
|
+
`);
|
|
10635
|
+
return 99;
|
|
10636
|
+
}
|
|
10637
|
+
}
|
|
10638
|
+
if (args.command === "stats") {
|
|
10639
|
+
try {
|
|
10640
|
+
return await runStats(args.statsArgv ?? []);
|
|
10641
|
+
} catch (err) {
|
|
10642
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10643
|
+
process.stderr.write(`[kody] fatal: ${msg}
|
|
10644
|
+
`);
|
|
10645
|
+
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
10254
10646
|
`);
|
|
10255
10647
|
return 99;
|
|
10256
10648
|
}
|