@kody-ade/kody-engine 0.4.41 → 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 +244 -2
- 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",
|
|
@@ -1555,6 +1555,32 @@ function emitEvent(cwd, ev) {
|
|
|
1555
1555
|
} catch {
|
|
1556
1556
|
}
|
|
1557
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
|
+
}
|
|
1558
1584
|
|
|
1559
1585
|
// src/profile.ts
|
|
1560
1586
|
import * as fs9 from "fs";
|
|
@@ -10304,6 +10330,206 @@ ${CHAT_HELP}`);
|
|
|
10304
10330
|
}
|
|
10305
10331
|
}
|
|
10306
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
|
+
|
|
10307
10533
|
// src/entry.ts
|
|
10308
10534
|
var HELP_TEXT = `kody \u2014 single-session autonomous engineer
|
|
10309
10535
|
|
|
@@ -10316,6 +10542,7 @@ Usage:
|
|
|
10316
10542
|
kody <other> [--cwd <path>] [--verbose|--quiet]
|
|
10317
10543
|
kody ci --issue <N> [preflight flags \u2014 see: kody ci --help]
|
|
10318
10544
|
kody chat [chat flags \u2014 see: kody chat --help]
|
|
10545
|
+
kody stats [--since 7d|--run <id>|--json|--cwd <path>]
|
|
10319
10546
|
kody help
|
|
10320
10547
|
kody version
|
|
10321
10548
|
|
|
@@ -10349,6 +10576,9 @@ function parseArgs(argv) {
|
|
|
10349
10576
|
if (cmd === "chat") {
|
|
10350
10577
|
return { ...result, command: "chat", chatArgv: argv.slice(1) };
|
|
10351
10578
|
}
|
|
10579
|
+
if (cmd === "stats") {
|
|
10580
|
+
return { ...result, command: "stats", statsArgv: argv.slice(1) };
|
|
10581
|
+
}
|
|
10352
10582
|
if (hasExecutable(cmd)) {
|
|
10353
10583
|
result.command = "__executable__";
|
|
10354
10584
|
result.executableName = cmd;
|
|
@@ -10359,7 +10589,7 @@ function parseArgs(argv) {
|
|
|
10359
10589
|
return result;
|
|
10360
10590
|
}
|
|
10361
10591
|
const discovered = listExecutables().map((e) => e.name);
|
|
10362
|
-
const available = ["ci", "help", "version", ...discovered];
|
|
10592
|
+
const available = ["ci", "chat", "stats", "help", "version", ...discovered];
|
|
10363
10593
|
result.errors.push(`unknown command: ${cmd} (available: ${available.join(", ")})`);
|
|
10364
10594
|
return result;
|
|
10365
10595
|
}
|
|
@@ -10401,6 +10631,18 @@ ${HELP_TEXT}`);
|
|
|
10401
10631
|
process.stderr.write(`[kody] fatal: ${msg}
|
|
10402
10632
|
`);
|
|
10403
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}
|
|
10404
10646
|
`);
|
|
10405
10647
|
return 99;
|
|
10406
10648
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.42",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|