@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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +244 -2
  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.41",
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.41",
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",