@kody-ade/kody-engine 0.4.58 → 0.4.60

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 CHANGED
@@ -1,9 +1,309 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/events.ts
13
+ import * as crypto from "crypto";
14
+ import * as fs3 from "fs";
15
+ import * as path3 from "path";
16
+ function resolveRunId() {
17
+ if (cachedRunId) return cachedRunId;
18
+ if (process.env.KODY_RUN_ID) {
19
+ cachedRunId = process.env.KODY_RUN_ID;
20
+ return cachedRunId;
21
+ }
22
+ if (process.env.GITHUB_RUN_ID) {
23
+ const attempt = process.env.GITHUB_RUN_ATTEMPT ?? "1";
24
+ cachedRunId = `gh-${process.env.GITHUB_RUN_ID}-${attempt}`;
25
+ } else {
26
+ cachedRunId = `${Date.now().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
27
+ }
28
+ process.env.KODY_RUN_ID = cachedRunId;
29
+ return cachedRunId;
30
+ }
31
+ function emitEvent(cwd, ev) {
32
+ if (process.env.KODY_EVENTS === "0") return;
33
+ try {
34
+ const runId = resolveRunId();
35
+ const fullEvent = {
36
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
37
+ runId,
38
+ ...ev
39
+ };
40
+ const eventsPath = path3.join(cwd, ".kody", "runs", runId, "events.jsonl");
41
+ fs3.mkdirSync(path3.dirname(eventsPath), { recursive: true });
42
+ fs3.appendFileSync(eventsPath, `${JSON.stringify(fullEvent)}
43
+ `);
44
+ } catch {
45
+ }
46
+ }
47
+ function readEvents(cwd, runId) {
48
+ const eventsPath = path3.join(cwd, ".kody", "runs", runId, "events.jsonl");
49
+ if (!fs3.existsSync(eventsPath)) return [];
50
+ const lines = fs3.readFileSync(eventsPath, "utf-8").split("\n");
51
+ const out = [];
52
+ for (const line of lines) {
53
+ const trimmed = line.trim();
54
+ if (!trimmed) continue;
55
+ try {
56
+ out.push(JSON.parse(trimmed));
57
+ } catch {
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+ function listRuns(cwd) {
63
+ const runsDir = path3.join(cwd, ".kody", "runs");
64
+ if (!fs3.existsSync(runsDir)) return [];
65
+ return fs3.readdirSync(runsDir).filter((name) => {
66
+ try {
67
+ return fs3.statSync(path3.join(runsDir, name)).isDirectory();
68
+ } catch {
69
+ return false;
70
+ }
71
+ }).sort();
72
+ }
73
+ var cachedRunId;
74
+ var init_events = __esm({
75
+ "src/events.ts"() {
76
+ "use strict";
77
+ cachedRunId = null;
78
+ }
79
+ });
80
+
81
+ // src/verify.ts
82
+ import { spawn } from "child_process";
83
+ function runCommand(command, cwd) {
84
+ return new Promise((resolve4) => {
85
+ const start = Date.now();
86
+ const child = spawn(command, {
87
+ cwd,
88
+ shell: true,
89
+ env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
90
+ stdio: ["ignore", "pipe", "pipe"]
91
+ });
92
+ const buffers = [];
93
+ let totalSize = 0;
94
+ const collect = (chunk) => {
95
+ buffers.push(chunk);
96
+ totalSize += chunk.length;
97
+ while (totalSize > TAIL_CHARS * 4 && buffers.length > 1) {
98
+ totalSize -= buffers[0].length;
99
+ buffers.shift();
100
+ }
101
+ };
102
+ child.stdout?.on("data", collect);
103
+ child.stderr?.on("data", collect);
104
+ const timer = setTimeout(() => {
105
+ child.kill("SIGTERM");
106
+ setTimeout(() => {
107
+ if (!child.killed) child.kill("SIGKILL");
108
+ }, 5e3);
109
+ }, COMMAND_TIMEOUT_MS);
110
+ child.on("exit", (code) => {
111
+ clearTimeout(timer);
112
+ const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
113
+ resolve4({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
114
+ });
115
+ child.on("error", (err) => {
116
+ clearTimeout(timer);
117
+ resolve4({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
118
+ });
119
+ });
120
+ }
121
+ async function verifyAll(config, cwd) {
122
+ const commands = [];
123
+ if (config.quality.typecheck) commands.push({ name: "typecheck", cmd: config.quality.typecheck });
124
+ if (config.quality.testUnit) commands.push({ name: "test", cmd: config.quality.testUnit });
125
+ if (config.quality.lint) commands.push({ name: "lint", cmd: config.quality.lint });
126
+ if (config.quality.format) commands.push({ name: "format", cmd: config.quality.format });
127
+ const failed = [];
128
+ const details = {};
129
+ for (const { name, cmd } of commands) {
130
+ const result = await runCommand(cmd, cwd);
131
+ details[name] = result;
132
+ if (result.exitCode !== 0) failed.push(name);
133
+ }
134
+ return { ok: failed.length === 0, failed, details };
135
+ }
136
+ async function applyTestRetries(initial, testCommand, cwd, runner, testRetries = DEFAULT_TEST_RETRIES) {
137
+ if (initial.ok) return { ...initial, recovered: [] };
138
+ const recovered = [];
139
+ const details = { ...initial.details };
140
+ let failed = [...initial.failed];
141
+ if (failed.includes("test") && testCommand && testRetries > 0) {
142
+ for (let attempt = 1; attempt <= testRetries; attempt++) {
143
+ const retry = await runner(testCommand, cwd);
144
+ details[`test (retry ${attempt})`] = retry;
145
+ if (retry.exitCode === 0) {
146
+ failed = failed.filter((f) => f !== "test");
147
+ recovered.push("test");
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ return { ok: failed.length === 0, failed, details, recovered };
153
+ }
154
+ async function verifyAllWithRetry(config, cwd, opts) {
155
+ const initial = await verifyAll(config, cwd);
156
+ return applyTestRetries(initial, config.quality.testUnit, cwd, runCommand, opts?.testRetries);
157
+ }
158
+ function stripAnsi(s) {
159
+ return s.replace(ANSI_RE, "");
160
+ }
161
+ function summarizeFailure(result) {
162
+ const lines = [`verify failed: ${result.failed.join(", ")}`];
163
+ for (const name of result.failed) {
164
+ const d = result.details[name];
165
+ if (!d) continue;
166
+ lines.push(`
167
+ --- ${name} (exit ${d.exitCode}, ${(d.durationMs / 1e3).toFixed(1)}s) ---`);
168
+ lines.push(stripAnsi(d.tail));
169
+ for (let attempt = 1; ; attempt++) {
170
+ const retry = result.details[`${name} (retry ${attempt})`];
171
+ if (!retry) break;
172
+ lines.push(`
173
+ --- ${name} (retry ${attempt}: exit ${retry.exitCode}, ${(retry.durationMs / 1e3).toFixed(1)}s) ---`);
174
+ lines.push(stripAnsi(retry.tail));
175
+ }
176
+ }
177
+ return lines.join("\n");
178
+ }
179
+ var TAIL_CHARS, COMMAND_TIMEOUT_MS, DEFAULT_TEST_RETRIES, ANSI_RE;
180
+ var init_verify = __esm({
181
+ "src/verify.ts"() {
182
+ "use strict";
183
+ TAIL_CHARS = 4e3;
184
+ COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
185
+ DEFAULT_TEST_RETRIES = 2;
186
+ ANSI_RE = /\x1B\[[0-?]*[ -/]*[@-~]/g;
187
+ }
188
+ });
189
+
190
+ // src/verifyMcp.ts
191
+ var verifyMcp_exports = {};
192
+ __export(verifyMcp_exports, {
193
+ DEFAULT_MAX_VERIFY_ATTEMPTS: () => DEFAULT_MAX_VERIFY_ATTEMPTS,
194
+ buildVerifyMcpServer: () => buildVerifyMcpServer,
195
+ truncateVerifyResult: () => truncateVerifyResult
196
+ });
197
+ import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
198
+ import { z } from "zod";
199
+ function buildVerifyMcpServer(opts) {
200
+ const state = {
201
+ attempts: 0,
202
+ maxAttempts: opts.maxAttempts ?? DEFAULT_MAX_VERIFY_ATTEMPTS
203
+ };
204
+ const runVerify = opts.__runVerify ?? verifyAllWithRetry;
205
+ const verifyTool = tool(
206
+ "verify",
207
+ "Run the project's quality gates (typecheck, lint, tests). Returns ok=true with empty failures when everything passes. Call this before declaring DONE. If ok=false, read the truncated failures, fix the code, commit, and call verify() again. You have a bounded number of attempts; after that the tool stops accepting calls and you must wrap up with whatever state is current.",
208
+ {},
209
+ async (_args, _extra) => {
210
+ state.attempts++;
211
+ const attempt = state.attempts;
212
+ if (attempt > state.maxAttempts) {
213
+ emitEvent(opts.cwd, {
214
+ executable: opts.executable,
215
+ kind: "error",
216
+ name: "verify_tool",
217
+ outcome: "failed",
218
+ meta: { reason: "budget exhausted", attempts: attempt, maxAttempts: state.maxAttempts }
219
+ });
220
+ return {
221
+ content: [
222
+ {
223
+ type: "text",
224
+ text: JSON.stringify({
225
+ ok: false,
226
+ locked: true,
227
+ reason: `verify budget exhausted (${state.maxAttempts} attempts used)`
228
+ })
229
+ }
230
+ ]
231
+ };
232
+ }
233
+ const startedAt = Date.now();
234
+ const result = await runVerify(opts.config, opts.cwd);
235
+ const durationMs = Date.now() - startedAt;
236
+ emitEvent(opts.cwd, {
237
+ executable: opts.executable,
238
+ kind: "postflight",
239
+ name: `verify_attempt_${attempt}`,
240
+ durationMs,
241
+ outcome: result.ok ? "ok" : "failed",
242
+ meta: {
243
+ attempt,
244
+ failureCount: result.failed.length,
245
+ recovered: result.recovered ?? []
246
+ }
247
+ });
248
+ const payload = truncateVerifyResult(result, state, attempt);
249
+ return {
250
+ content: [
251
+ {
252
+ type: "text",
253
+ text: JSON.stringify(payload)
254
+ }
255
+ ]
256
+ };
257
+ }
258
+ );
259
+ return createSdkMcpServer({
260
+ name: "kody-verify",
261
+ version: "0.1.0",
262
+ tools: [verifyTool]
263
+ });
264
+ }
265
+ function truncateVerifyResult(result, state, attempt) {
266
+ const failures = result.failed.slice(0, 5).map((name) => {
267
+ const detail = result.details[name];
268
+ const tail = detail?.tail ?? "";
269
+ return {
270
+ name,
271
+ exitCode: detail?.exitCode ?? -1,
272
+ tail: tail.length > PER_FAILURE_TAIL_CHARS ? `\u2026${tail.slice(-PER_FAILURE_TAIL_CHARS)}` : tail
273
+ };
274
+ });
275
+ const payload = {
276
+ ok: result.ok,
277
+ attempt,
278
+ attemptsRemaining: Math.max(0, state.maxAttempts - attempt),
279
+ failures,
280
+ ...result.recovered && result.recovered.length > 0 ? { recovered: result.recovered } : {}
281
+ };
282
+ const json = JSON.stringify(payload);
283
+ if (json.length <= MAX_RESULT_BYTES) return payload;
284
+ for (const f of payload.failures) {
285
+ f.tail = f.tail.slice(-Math.max(120, Math.floor(PER_FAILURE_TAIL_CHARS / 4)));
286
+ if (JSON.stringify(payload).length <= MAX_RESULT_BYTES) return payload;
287
+ }
288
+ for (const f of payload.failures) f.tail = "";
289
+ return payload;
290
+ }
291
+ var DEFAULT_MAX_VERIFY_ATTEMPTS, MAX_RESULT_BYTES, PER_FAILURE_TAIL_CHARS;
292
+ var init_verifyMcp = __esm({
293
+ "src/verifyMcp.ts"() {
294
+ "use strict";
295
+ init_events();
296
+ init_verify();
297
+ DEFAULT_MAX_VERIFY_ATTEMPTS = 4;
298
+ MAX_RESULT_BYTES = 2048;
299
+ PER_FAILURE_TAIL_CHARS = 600;
300
+ }
301
+ });
2
302
 
3
303
  // package.json
4
304
  var package_default = {
5
305
  name: "@kody-ade/kody-engine",
6
- version: "0.4.58",
306
+ version: "0.4.60",
7
307
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
308
  license: "MIT",
9
309
  type: "module",
@@ -29,7 +329,8 @@ var package_default = {
29
329
  },
30
330
  dependencies: {
31
331
  "@actions/cache": "^6.0.0",
32
- "@anthropic-ai/claude-agent-sdk": "0.2.119"
332
+ "@anthropic-ai/claude-agent-sdk": "0.2.119",
333
+ zod: "^4.0.0"
33
334
  },
34
335
  devDependencies: {
35
336
  "@biomejs/biome": "^2.4.12",
@@ -52,8 +353,8 @@ var package_default = {
52
353
 
53
354
  // src/chat-cli.ts
54
355
  import { execFileSync as execFileSync31 } from "child_process";
55
- import * as fs32 from "fs";
56
- import * as path30 from "path";
356
+ import * as fs33 from "fs";
357
+ import * as path31 from "path";
57
358
 
58
359
  // src/chat/events.ts
59
360
  import * as fs from "fs";
@@ -119,8 +420,8 @@ function makeRunId(sessionId, suffix) {
119
420
  }
120
421
 
121
422
  // src/agent.ts
122
- import * as fs3 from "fs";
123
- import * as path3 from "path";
423
+ import * as fs4 from "fs";
424
+ import * as path4 from "path";
124
425
  import { query } from "@anthropic-ai/claude-agent-sdk";
125
426
 
126
427
  // src/config.ts
@@ -412,10 +713,10 @@ function resolveTurnTimeoutMs(opts) {
412
713
  return DEFAULT_TURN_TIMEOUT_MS;
413
714
  }
414
715
  async function runAgent(opts) {
415
- const ndjsonDir = opts.ndjsonDir ?? path3.join(opts.cwd, ".kody");
416
- fs3.mkdirSync(ndjsonDir, { recursive: true });
417
- const ndjsonPath = path3.join(ndjsonDir, "last-run.jsonl");
418
- const fullLog = fs3.createWriteStream(ndjsonPath, { flags: "w" });
716
+ const ndjsonDir = opts.ndjsonDir ?? path4.join(opts.cwd, ".kody");
717
+ fs4.mkdirSync(ndjsonDir, { recursive: true });
718
+ const ndjsonPath = path4.join(ndjsonDir, "last-run.jsonl");
719
+ const fullLog = fs4.createWriteStream(ndjsonPath, { flags: "w" });
419
720
  const env = {
420
721
  ...process.env,
421
722
  SKIP_HOOKS: "1",
@@ -452,15 +753,27 @@ async function runAgent(opts) {
452
753
  permissionMode: opts.permissionModeOverride ?? "acceptEdits",
453
754
  env
454
755
  };
756
+ const mcpEntries = [];
455
757
  if (opts.mcpServers && opts.mcpServers.length > 0) {
456
- queryOptions.mcpServers = Object.fromEntries(
457
- opts.mcpServers.map((s) => {
458
- const cfg = { command: s.command };
459
- if (s.args) cfg.args = s.args;
460
- if (s.env) cfg.env = s.env;
461
- return [s.name, cfg];
462
- })
463
- );
758
+ for (const s of opts.mcpServers) {
759
+ const cfg = { command: s.command };
760
+ if (s.args) cfg.args = s.args;
761
+ if (s.env) cfg.env = s.env;
762
+ mcpEntries.push([s.name, cfg]);
763
+ }
764
+ }
765
+ if (opts.enableVerifyTool && opts.verifyConfig) {
766
+ const { buildVerifyMcpServer: buildVerifyMcpServer2 } = await Promise.resolve().then(() => (init_verifyMcp(), verifyMcp_exports));
767
+ const verifyServer = buildVerifyMcpServer2({
768
+ config: opts.verifyConfig,
769
+ cwd: opts.cwd,
770
+ executable: opts.executableName ?? "agent",
771
+ maxAttempts: typeof opts.verifyToolMaxAttempts === "number" && opts.verifyToolMaxAttempts > 0 ? opts.verifyToolMaxAttempts : void 0
772
+ });
773
+ mcpEntries.push(["kody-verify", verifyServer]);
774
+ }
775
+ if (mcpEntries.length > 0) {
776
+ queryOptions.mcpServers = Object.fromEntries(mcpEntries);
464
777
  }
465
778
  if (opts.pluginPaths && opts.pluginPaths.length > 0) {
466
779
  queryOptions.plugins = opts.pluginPaths.map((p) => ({ type: "local", path: p }));
@@ -588,14 +901,14 @@ async function runAgent(opts) {
588
901
  }
589
902
 
590
903
  // src/chat/session.ts
591
- import * as fs4 from "fs";
592
- import * as path4 from "path";
904
+ import * as fs5 from "fs";
905
+ import * as path5 from "path";
593
906
  function sessionFilePath(cwd, sessionId) {
594
- return path4.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
907
+ return path5.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
595
908
  }
596
909
  function readMeta(file) {
597
- if (!fs4.existsSync(file)) return null;
598
- const raw = fs4.readFileSync(file, "utf-8");
910
+ if (!fs5.existsSync(file)) return null;
911
+ const raw = fs5.readFileSync(file, "utf-8");
599
912
  const firstLine2 = raw.split("\n", 1)[0]?.trim();
600
913
  if (!firstLine2) return null;
601
914
  try {
@@ -608,8 +921,8 @@ function readMeta(file) {
608
921
  }
609
922
  }
610
923
  function readSession(file) {
611
- if (!fs4.existsSync(file)) return [];
612
- const raw = fs4.readFileSync(file, "utf-8").trim();
924
+ if (!fs5.existsSync(file)) return [];
925
+ const raw = fs5.readFileSync(file, "utf-8").trim();
613
926
  if (!raw) return [];
614
927
  const turns = [];
615
928
  for (const line of raw.split("\n")) {
@@ -625,14 +938,14 @@ function readSession(file) {
625
938
  return turns;
626
939
  }
627
940
  function appendTurn(file, turn) {
628
- fs4.mkdirSync(path4.dirname(file), { recursive: true });
941
+ fs5.mkdirSync(path5.dirname(file), { recursive: true });
629
942
  const line = JSON.stringify({
630
943
  role: turn.role,
631
944
  content: turn.content,
632
945
  timestamp: turn.timestamp,
633
946
  toolCalls: turn.toolCalls ?? []
634
947
  });
635
- fs4.appendFileSync(file, `${line}
948
+ fs5.appendFileSync(file, `${line}
636
949
  `);
637
950
  }
638
951
  function seedInitialMessage(file, message) {
@@ -777,8 +1090,8 @@ async function emit(sink, type, sessionId, suffix, payload) {
777
1090
 
778
1091
  // src/chat/modes/interactive.ts
779
1092
  import { execFileSync as execFileSync2 } from "child_process";
780
- import * as fs5 from "fs";
781
- import * as path5 from "path";
1093
+ import * as fs6 from "fs";
1094
+ import * as path6 from "path";
782
1095
 
783
1096
  // src/chat/inbox.ts
784
1097
  import { execFileSync } from "child_process";
@@ -935,9 +1248,9 @@ function findNextUserTurn(turns, fromIdx) {
935
1248
  return -1;
936
1249
  }
937
1250
  function commitTurn(cwd, sessionId, verbose) {
938
- const sessionRel = path5.relative(cwd, sessionFilePath(cwd, sessionId));
939
- const eventsRel = path5.relative(cwd, eventsFilePath(cwd, sessionId));
940
- const paths = [sessionRel, eventsRel].filter((p) => fs5.existsSync(path5.join(cwd, p)));
1251
+ const sessionRel = path6.relative(cwd, sessionFilePath(cwd, sessionId));
1252
+ const eventsRel = path6.relative(cwd, eventsFilePath(cwd, sessionId));
1253
+ const paths = [sessionRel, eventsRel].filter((p) => fs6.existsSync(path6.join(cwd, p)));
941
1254
  if (paths.length === 0) return;
942
1255
  const stdio = verbose ? "inherit" : "pipe";
943
1256
  const exec = (args) => execFileSync2("git", args, { cwd, stdio });
@@ -1013,11 +1326,11 @@ async function emit2(sink, type, sessionId, suffix, payload) {
1013
1326
 
1014
1327
  // src/kody-cli.ts
1015
1328
  import { execFileSync as execFileSync30 } from "child_process";
1016
- import * as fs31 from "fs";
1017
- import * as path29 from "path";
1329
+ import * as fs32 from "fs";
1330
+ import * as path30 from "path";
1018
1331
 
1019
1332
  // src/dispatch.ts
1020
- import * as fs7 from "fs";
1333
+ import * as fs8 from "fs";
1021
1334
 
1022
1335
  // src/cron-match.ts
1023
1336
  var FIELD_BOUNDS = [
@@ -1082,48 +1395,48 @@ function cronMatchesInWindow(spec, end, windowSec) {
1082
1395
  }
1083
1396
 
1084
1397
  // src/registry.ts
1085
- import * as fs6 from "fs";
1086
- import * as path6 from "path";
1398
+ import * as fs7 from "fs";
1399
+ import * as path7 from "path";
1087
1400
  function getExecutablesRoot() {
1088
- const here = path6.dirname(new URL(import.meta.url).pathname);
1401
+ const here = path7.dirname(new URL(import.meta.url).pathname);
1089
1402
  const candidates = [
1090
- path6.join(here, "executables"),
1403
+ path7.join(here, "executables"),
1091
1404
  // dev: src/
1092
- path6.join(here, "..", "executables"),
1405
+ path7.join(here, "..", "executables"),
1093
1406
  // built: dist/bin → dist/executables
1094
- path6.join(here, "..", "src", "executables")
1407
+ path7.join(here, "..", "src", "executables")
1095
1408
  // fallback
1096
1409
  ];
1097
1410
  for (const c of candidates) {
1098
- if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
1411
+ if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
1099
1412
  }
1100
1413
  return candidates[0];
1101
1414
  }
1102
1415
  function getProjectExecutablesRoot() {
1103
- return path6.join(process.cwd(), ".kody", "executables");
1416
+ return path7.join(process.cwd(), ".kody", "executables");
1104
1417
  }
1105
1418
  function getBuiltinJobsRoot() {
1106
- const here = path6.dirname(new URL(import.meta.url).pathname);
1419
+ const here = path7.dirname(new URL(import.meta.url).pathname);
1107
1420
  const candidates = [
1108
- path6.join(here, "jobs"),
1421
+ path7.join(here, "jobs"),
1109
1422
  // dev: src/
1110
- path6.join(here, "..", "jobs"),
1423
+ path7.join(here, "..", "jobs"),
1111
1424
  // built: dist/bin → dist/jobs
1112
- path6.join(here, "..", "src", "jobs")
1425
+ path7.join(here, "..", "src", "jobs")
1113
1426
  // fallback
1114
1427
  ];
1115
1428
  for (const c of candidates) {
1116
- if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
1429
+ if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
1117
1430
  }
1118
1431
  return candidates[0];
1119
1432
  }
1120
1433
  function listBuiltinJobs(root = getBuiltinJobsRoot()) {
1121
- if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
1434
+ if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
1122
1435
  const out = [];
1123
- for (const ent of fs6.readdirSync(root, { withFileTypes: true })) {
1436
+ for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
1124
1437
  if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
1125
1438
  const slug = ent.name.slice(0, -3);
1126
- out.push({ slug, filePath: path6.join(root, ent.name) });
1439
+ out.push({ slug, filePath: path7.join(root, ent.name) });
1127
1440
  }
1128
1441
  out.sort((a, b) => a.slug.localeCompare(b.slug));
1129
1442
  return out;
@@ -1136,13 +1449,13 @@ function listExecutables(roots = getExecutableRoots()) {
1136
1449
  const seen = /* @__PURE__ */ new Set();
1137
1450
  const out = [];
1138
1451
  for (const root of rootList) {
1139
- if (!fs6.existsSync(root)) continue;
1140
- const entries = fs6.readdirSync(root, { withFileTypes: true });
1452
+ if (!fs7.existsSync(root)) continue;
1453
+ const entries = fs7.readdirSync(root, { withFileTypes: true });
1141
1454
  for (const ent of entries) {
1142
1455
  if (!ent.isDirectory()) continue;
1143
1456
  if (seen.has(ent.name)) continue;
1144
- const profilePath = path6.join(root, ent.name, "profile.json");
1145
- if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
1457
+ const profilePath = path7.join(root, ent.name, "profile.json");
1458
+ if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
1146
1459
  out.push({ name: ent.name, profilePath });
1147
1460
  seen.add(ent.name);
1148
1461
  }
@@ -1154,8 +1467,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
1154
1467
  if (!isSafeName(name)) return null;
1155
1468
  const rootList = typeof roots === "string" ? [roots] : roots;
1156
1469
  for (const root of rootList) {
1157
- const profilePath = path6.join(root, name, "profile.json");
1158
- if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
1470
+ const profilePath = path7.join(root, name, "profile.json");
1471
+ if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
1159
1472
  return profilePath;
1160
1473
  }
1161
1474
  }
@@ -1171,7 +1484,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
1171
1484
  const profilePath = resolveExecutable(name, roots);
1172
1485
  if (!profilePath) return null;
1173
1486
  try {
1174
- const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
1487
+ const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
1175
1488
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
1176
1489
  return raw.inputs;
1177
1490
  } catch {
@@ -1220,10 +1533,10 @@ function autoDispatch(opts) {
1220
1533
  }
1221
1534
  const eventName = process.env.GITHUB_EVENT_NAME;
1222
1535
  const eventPath = process.env.GITHUB_EVENT_PATH;
1223
- if (!eventName || !eventPath || !fs7.existsSync(eventPath)) return null;
1536
+ if (!eventName || !eventPath || !fs8.existsSync(eventPath)) return null;
1224
1537
  let event = {};
1225
1538
  try {
1226
- event = JSON.parse(fs7.readFileSync(eventPath, "utf-8"));
1539
+ event = JSON.parse(fs8.readFileSync(eventPath, "utf-8"));
1227
1540
  } catch {
1228
1541
  return null;
1229
1542
  }
@@ -1296,7 +1609,7 @@ function autoDispatchTyped(opts) {
1296
1609
  if (legacy) return { kind: "route", ...legacy };
1297
1610
  const eventName = process.env.GITHUB_EVENT_NAME;
1298
1611
  const eventPath = process.env.GITHUB_EVENT_PATH;
1299
- if (!eventName || !eventPath || !fs7.existsSync(eventPath)) {
1612
+ if (!eventName || !eventPath || !fs8.existsSync(eventPath)) {
1300
1613
  return { kind: "silent", reason: "no GHA event context" };
1301
1614
  }
1302
1615
  if (eventName !== "issue_comment") {
@@ -1304,7 +1617,7 @@ function autoDispatchTyped(opts) {
1304
1617
  }
1305
1618
  let event = {};
1306
1619
  try {
1307
- event = JSON.parse(fs7.readFileSync(eventPath, "utf-8"));
1620
+ event = JSON.parse(fs8.readFileSync(eventPath, "utf-8"));
1308
1621
  } catch {
1309
1622
  return { kind: "silent", reason: "GHA event payload unreadable" };
1310
1623
  }
@@ -1338,7 +1651,7 @@ function dispatchScheduledWatches(opts) {
1338
1651
  for (const exe of listExecutables()) {
1339
1652
  let raw;
1340
1653
  try {
1341
- raw = fs7.readFileSync(exe.profilePath, "utf-8");
1654
+ raw = fs8.readFileSync(exe.profilePath, "utf-8");
1342
1655
  } catch {
1343
1656
  continue;
1344
1657
  }
@@ -1593,71 +1906,9 @@ function postPrReviewComment(prNumber, body, cwd) {
1593
1906
 
1594
1907
  // src/executor.ts
1595
1908
  import { execFileSync as execFileSync29, spawn as spawn5 } from "child_process";
1596
- import * as fs30 from "fs";
1597
- import * as path28 from "path";
1598
-
1599
- // src/events.ts
1600
- import * as crypto from "crypto";
1601
- import * as fs8 from "fs";
1602
- import * as path7 from "path";
1603
- var cachedRunId = null;
1604
- function resolveRunId() {
1605
- if (cachedRunId) return cachedRunId;
1606
- if (process.env.KODY_RUN_ID) {
1607
- cachedRunId = process.env.KODY_RUN_ID;
1608
- return cachedRunId;
1609
- }
1610
- if (process.env.GITHUB_RUN_ID) {
1611
- const attempt = process.env.GITHUB_RUN_ATTEMPT ?? "1";
1612
- cachedRunId = `gh-${process.env.GITHUB_RUN_ID}-${attempt}`;
1613
- } else {
1614
- cachedRunId = `${Date.now().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
1615
- }
1616
- process.env.KODY_RUN_ID = cachedRunId;
1617
- return cachedRunId;
1618
- }
1619
- function emitEvent(cwd, ev) {
1620
- if (process.env.KODY_EVENTS === "0") return;
1621
- try {
1622
- const runId = resolveRunId();
1623
- const fullEvent = {
1624
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1625
- runId,
1626
- ...ev
1627
- };
1628
- const eventsPath = path7.join(cwd, ".kody", "runs", runId, "events.jsonl");
1629
- fs8.mkdirSync(path7.dirname(eventsPath), { recursive: true });
1630
- fs8.appendFileSync(eventsPath, `${JSON.stringify(fullEvent)}
1631
- `);
1632
- } catch {
1633
- }
1634
- }
1635
- function readEvents(cwd, runId) {
1636
- const eventsPath = path7.join(cwd, ".kody", "runs", runId, "events.jsonl");
1637
- if (!fs8.existsSync(eventsPath)) return [];
1638
- const lines = fs8.readFileSync(eventsPath, "utf-8").split("\n");
1639
- const out = [];
1640
- for (const line of lines) {
1641
- const trimmed = line.trim();
1642
- if (!trimmed) continue;
1643
- try {
1644
- out.push(JSON.parse(trimmed));
1645
- } catch {
1646
- }
1647
- }
1648
- return out;
1649
- }
1650
- function listRuns(cwd) {
1651
- const runsDir = path7.join(cwd, ".kody", "runs");
1652
- if (!fs8.existsSync(runsDir)) return [];
1653
- return fs8.readdirSync(runsDir).filter((name) => {
1654
- try {
1655
- return fs8.statSync(path7.join(runsDir, name)).isDirectory();
1656
- } catch {
1657
- return false;
1658
- }
1659
- }).sort();
1660
- }
1909
+ import * as fs31 from "fs";
1910
+ import * as path29 from "path";
1911
+ init_events();
1661
1912
 
1662
1913
  // src/profile.ts
1663
1914
  import * as fs9 from "fs";
@@ -1721,6 +1972,10 @@ function loadProfile(profilePath) {
1721
1972
  inputArtifacts: parseInputArtifacts(profilePath, r.input),
1722
1973
  outputArtifacts: parseOutputArtifacts(profilePath, r.output),
1723
1974
  children,
1975
+ // Default true: preserves legacy bug-safe behaviour where each
1976
+ // container child sees a clean tracked tree (see executor.ts).
1977
+ // Containers opt out by setting `"resetBetweenChildren": false`.
1978
+ resetBetweenChildren: typeof r.resetBetweenChildren === "boolean" ? r.resetBetweenChildren : true,
1724
1979
  dir: path8.dirname(profilePath)
1725
1980
  };
1726
1981
  return profile;
@@ -1792,6 +2047,8 @@ function parseClaudeCode(p, raw) {
1792
2047
  maxTurnTimeoutSec: typeof r.maxTurnTimeoutSec === "number" ? r.maxTurnTimeoutSec : null,
1793
2048
  systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
1794
2049
  cacheable: r.cacheable === true,
2050
+ enableVerifyTool: r.enableVerifyTool === true,
2051
+ verifyAttempts: typeof r.verifyAttempts === "number" && r.verifyAttempts > 0 ? r.verifyAttempts : null,
1795
2052
  tools,
1796
2053
  hooks: Array.isArray(r.hooks) ? r.hooks : [],
1797
2054
  skills: Array.isArray(r.skills) ? r.skills : [],
@@ -2085,7 +2342,7 @@ function errMsg(err) {
2085
2342
  }
2086
2343
 
2087
2344
  // src/litellm.ts
2088
- import { execFileSync as execFileSync4, spawn } from "child_process";
2345
+ import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
2089
2346
  import * as fs10 from "fs";
2090
2347
  import * as os from "os";
2091
2348
  import * as path9 from "path";
@@ -2143,7 +2400,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
2143
2400
  const dotenvVars = readDotenvApiKeys(projectDir);
2144
2401
  const logPath = path9.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
2145
2402
  const outFd = fs10.openSync(logPath, "w");
2146
- const child = spawn(cmd, args, {
2403
+ const child = spawn2(cmd, args, {
2147
2404
  stdio: ["ignore", outFd, outFd],
2148
2405
  detached: true,
2149
2406
  env: stripBlockingEnv({ ...process.env, ...dotenvVars })
@@ -2977,13 +3234,36 @@ function defaultLabelMap() {
2977
3234
  }
2978
3235
 
2979
3236
  // src/scripts/commitAndPush.ts
3237
+ import * as fs14 from "fs";
3238
+ import * as path13 from "path";
3239
+ init_events();
2980
3240
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
2981
- var commitAndPush2 = async (ctx) => {
3241
+ function sentinelPathForStage(cwd, profileName) {
3242
+ const runId = resolveRunId();
3243
+ return path13.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
3244
+ }
3245
+ var commitAndPush2 = async (ctx, profile) => {
2982
3246
  const branch = ctx.data.branch;
2983
3247
  if (!branch) {
2984
3248
  ctx.data.commitResult = { committed: false, pushed: false };
2985
3249
  return;
2986
3250
  }
3251
+ const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
3252
+ const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
3253
+ if (sentinel && fs14.existsSync(sentinel)) {
3254
+ try {
3255
+ const replay = JSON.parse(fs14.readFileSync(sentinel, "utf-8"));
3256
+ ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
3257
+ if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
3258
+ if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
3259
+ if (replay.salvagedFromMissingMarker) ctx.data.salvagedFromMissingMarker = true;
3260
+ ctx.data.commitIdempotencyReplay = true;
3261
+ process.stderr.write(`[kody commitAndPush] idempotency replay (sentinel ${sentinel})
3262
+ `);
3263
+ return;
3264
+ } catch {
3265
+ }
3266
+ }
2987
3267
  const markerMissing = ctx.data.agentMarkerMissing === true;
2988
3268
  if (ctx.data.agentDone === false && !markerMissing) {
2989
3269
  ctx.data.commitResult = { committed: false, pushed: false, skippedReason: "agentDone=false" };
@@ -2995,12 +3275,12 @@ var commitAndPush2 = async (ctx) => {
2995
3275
  }
2996
3276
  const message = ctx.data.commitMessage || DEFAULT_COMMIT_MESSAGE;
2997
3277
  try {
2998
- const result = commitAndPush(branch, message, ctx.cwd);
2999
- ctx.data.commitResult = result;
3000
- const postCommitFiles = result.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
3278
+ const result2 = commitAndPush(branch, message, ctx.cwd);
3279
+ ctx.data.commitResult = result2;
3280
+ const postCommitFiles = result2.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
3001
3281
  ctx.data.changedFiles = postCommitFiles.filter((f) => !isForbiddenPath(f));
3002
- if (result.committed && !result.pushed) {
3003
- const reason = result.pushError ?? "push failed (no error detail)";
3282
+ if (result2.committed && !result2.pushed) {
3283
+ const reason = result2.pushError ?? "push failed (no error detail)";
3004
3284
  ctx.data.commitCrash = reason;
3005
3285
  if (ctx.output.exitCode === void 0 || ctx.output.exitCode === 0) {
3006
3286
  ctx.output.exitCode = 4;
@@ -3017,15 +3297,36 @@ var commitAndPush2 = async (ctx) => {
3017
3297
  `);
3018
3298
  }
3019
3299
  ctx.data.hasCommitsAhead = hasCommitsAhead(branch, ctx.config.git.defaultBranch, ctx.cwd);
3300
+ const result = ctx.data.commitResult;
3301
+ if (sentinel && result?.committed) {
3302
+ try {
3303
+ fs14.mkdirSync(path13.dirname(sentinel), { recursive: true });
3304
+ fs14.writeFileSync(
3305
+ sentinel,
3306
+ JSON.stringify(
3307
+ {
3308
+ commitResult: ctx.data.commitResult,
3309
+ changedFiles: ctx.data.changedFiles,
3310
+ hasCommitsAhead: ctx.data.hasCommitsAhead,
3311
+ salvagedFromMissingMarker: ctx.data.salvagedFromMissingMarker === true,
3312
+ writtenAt: (/* @__PURE__ */ new Date()).toISOString()
3313
+ },
3314
+ null,
3315
+ 2
3316
+ )
3317
+ );
3318
+ } catch {
3319
+ }
3320
+ }
3020
3321
  };
3021
3322
 
3022
3323
  // src/scripts/commitGoalState.ts
3023
3324
  import { execFileSync as execFileSync9 } from "child_process";
3024
- import * as path13 from "path";
3325
+ import * as path14 from "path";
3025
3326
  var commitGoalState = async (ctx) => {
3026
3327
  const goal = ctx.data.goal;
3027
3328
  if (!goal) return;
3028
- const stateRel = path13.posix.join(".kody", "goals", goal.id, "state.json");
3329
+ const stateRel = path14.posix.join(".kody", "goals", goal.id, "state.json");
3029
3330
  try {
3030
3331
  execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
3031
3332
  } catch (err) {
@@ -3069,20 +3370,20 @@ function describeCommitMessage(goal) {
3069
3370
  }
3070
3371
 
3071
3372
  // src/scripts/composePrompt.ts
3072
- import * as fs14 from "fs";
3073
- import * as path14 from "path";
3373
+ import * as fs15 from "fs";
3374
+ import * as path15 from "path";
3074
3375
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
3075
3376
  var composePrompt = async (ctx, profile) => {
3076
3377
  const explicit = ctx.data.promptTemplate;
3077
3378
  const mode = ctx.args.mode;
3078
3379
  const candidates = [
3079
- explicit ? path14.join(profile.dir, explicit) : null,
3080
- mode ? path14.join(profile.dir, "prompts", `${mode}.md`) : null,
3081
- path14.join(profile.dir, "prompt.md")
3380
+ explicit ? path15.join(profile.dir, explicit) : null,
3381
+ mode ? path15.join(profile.dir, "prompts", `${mode}.md`) : null,
3382
+ path15.join(profile.dir, "prompt.md")
3082
3383
  ].filter(Boolean);
3083
3384
  let templatePath = "";
3084
3385
  for (const c of candidates) {
3085
- if (fs14.existsSync(c)) {
3386
+ if (fs15.existsSync(c)) {
3086
3387
  templatePath = c;
3087
3388
  break;
3088
3389
  }
@@ -3090,7 +3391,7 @@ var composePrompt = async (ctx, profile) => {
3090
3391
  if (!templatePath) {
3091
3392
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
3092
3393
  }
3093
- const template = fs14.readFileSync(templatePath, "utf-8");
3394
+ const template = fs15.readFileSync(templatePath, "utf-8");
3094
3395
  const tokens = {
3095
3396
  ...stringifyAll(ctx.args, "args."),
3096
3397
  ...stringifyAll(ctx.data, ""),
@@ -3168,8 +3469,8 @@ function formatToolsUsage(profile) {
3168
3469
 
3169
3470
  // src/scripts/createQaGoal.ts
3170
3471
  import { execFileSync as execFileSync10 } from "child_process";
3171
- import * as fs15 from "fs";
3172
- import * as path15 from "path";
3472
+ import * as fs16 from "fs";
3473
+ import * as path16 from "path";
3173
3474
 
3174
3475
  // src/scripts/postReviewResult.ts
3175
3476
  function detectVerdict(body) {
@@ -3421,8 +3722,8 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
3421
3722
  return { number: Number(m[1]), created: true };
3422
3723
  }
3423
3724
  function writeStateFile(cwd, goalId, lastDispatchedIssue) {
3424
- const dir = path15.join(cwd, ".kody", "goals", goalId);
3425
- fs15.mkdirSync(dir, { recursive: true });
3725
+ const dir = path16.join(cwd, ".kody", "goals", goalId);
3726
+ fs16.mkdirSync(dir, { recursive: true });
3426
3727
  const state = {
3427
3728
  version: 1,
3428
3729
  state: "active",
@@ -3430,8 +3731,8 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
3430
3731
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3431
3732
  ...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
3432
3733
  };
3433
- const filePath = path15.join(dir, "state.json");
3434
- fs15.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
3734
+ const filePath = path16.join(dir, "state.json");
3735
+ fs16.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
3435
3736
  `);
3436
3737
  return filePath;
3437
3738
  }
@@ -3836,7 +4137,10 @@ function mergePrSquash(prNumber, cwd) {
3836
4137
  }
3837
4138
  function editPrBase(prNumber, baseBranch, cwd) {
3838
4139
  try {
3839
- gh(["pr", "edit", String(prNumber), "--base", baseBranch], { cwd });
4140
+ gh(
4141
+ ["api", "--method", "PATCH", `repos/{owner}/{repo}/pulls/${prNumber}`, "-f", `base=${baseBranch}`],
4142
+ { cwd }
4143
+ );
3840
4144
  return { ok: true };
3841
4145
  } catch (err) {
3842
4146
  return fail(err);
@@ -3921,15 +4225,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
3921
4225
 
3922
4226
  // src/scripts/diagMcp.ts
3923
4227
  import { execFileSync as execFileSync11 } from "child_process";
3924
- import * as fs16 from "fs";
4228
+ import * as fs17 from "fs";
3925
4229
  import * as os3 from "os";
3926
- import * as path16 from "path";
4230
+ import * as path17 from "path";
3927
4231
  var diagMcp = async (_ctx) => {
3928
4232
  const home = os3.homedir();
3929
- const cacheDir = path16.join(home, ".cache", "ms-playwright");
4233
+ const cacheDir = path17.join(home, ".cache", "ms-playwright");
3930
4234
  let entries = [];
3931
4235
  try {
3932
- entries = fs16.readdirSync(cacheDir);
4236
+ entries = fs17.readdirSync(cacheDir);
3933
4237
  } catch {
3934
4238
  }
3935
4239
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -3955,17 +4259,17 @@ var diagMcp = async (_ctx) => {
3955
4259
  };
3956
4260
 
3957
4261
  // src/scripts/discoverQaContext.ts
3958
- import * as fs18 from "fs";
3959
- import * as path18 from "path";
4262
+ import * as fs19 from "fs";
4263
+ import * as path19 from "path";
3960
4264
 
3961
4265
  // src/scripts/frameworkDetectors.ts
3962
- import * as fs17 from "fs";
3963
- import * as path17 from "path";
4266
+ import * as fs18 from "fs";
4267
+ import * as path18 from "path";
3964
4268
  function detectFrameworks(cwd) {
3965
4269
  const out = [];
3966
4270
  let deps = {};
3967
4271
  try {
3968
- const pkg = JSON.parse(fs17.readFileSync(path17.join(cwd, "package.json"), "utf-8"));
4272
+ const pkg = JSON.parse(fs18.readFileSync(path18.join(cwd, "package.json"), "utf-8"));
3969
4273
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
3970
4274
  } catch {
3971
4275
  return out;
@@ -4002,7 +4306,7 @@ function detectFrameworks(cwd) {
4002
4306
  }
4003
4307
  function findFile(cwd, candidates) {
4004
4308
  for (const c of candidates) {
4005
- if (fs17.existsSync(path17.join(cwd, c))) return c;
4309
+ if (fs18.existsSync(path18.join(cwd, c))) return c;
4006
4310
  }
4007
4311
  return null;
4008
4312
  }
@@ -4015,18 +4319,18 @@ var COLLECTION_DIRS = [
4015
4319
  function discoverPayloadCollections(cwd) {
4016
4320
  const out = [];
4017
4321
  for (const dir of COLLECTION_DIRS) {
4018
- const full = path17.join(cwd, dir);
4019
- if (!fs17.existsSync(full)) continue;
4322
+ const full = path18.join(cwd, dir);
4323
+ if (!fs18.existsSync(full)) continue;
4020
4324
  let files;
4021
4325
  try {
4022
- files = fs17.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4326
+ files = fs18.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4023
4327
  } catch {
4024
4328
  continue;
4025
4329
  }
4026
4330
  for (const file of files) {
4027
4331
  try {
4028
- const filePath = path17.join(full, file);
4029
- const content = fs17.readFileSync(filePath, "utf-8").slice(0, 1e4);
4332
+ const filePath = path18.join(full, file);
4333
+ const content = fs18.readFileSync(filePath, "utf-8").slice(0, 1e4);
4030
4334
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
4031
4335
  if (!slugMatch) continue;
4032
4336
  const slug = slugMatch[1];
@@ -4040,7 +4344,7 @@ function discoverPayloadCollections(cwd) {
4040
4344
  out.push({
4041
4345
  name,
4042
4346
  slug,
4043
- filePath: path17.relative(cwd, filePath),
4347
+ filePath: path18.relative(cwd, filePath),
4044
4348
  fields: fields.slice(0, 20),
4045
4349
  hasAdmin
4046
4350
  });
@@ -4054,28 +4358,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
4054
4358
  function discoverAdminComponents(cwd, collections) {
4055
4359
  const out = [];
4056
4360
  for (const dir of ADMIN_COMPONENT_DIRS) {
4057
- const full = path17.join(cwd, dir);
4058
- if (!fs17.existsSync(full)) continue;
4361
+ const full = path18.join(cwd, dir);
4362
+ if (!fs18.existsSync(full)) continue;
4059
4363
  let entries;
4060
4364
  try {
4061
- entries = fs17.readdirSync(full, { withFileTypes: true });
4365
+ entries = fs18.readdirSync(full, { withFileTypes: true });
4062
4366
  } catch {
4063
4367
  continue;
4064
4368
  }
4065
4369
  for (const entry of entries) {
4066
- const entryPath = path17.join(full, entry.name);
4370
+ const entryPath = path18.join(full, entry.name);
4067
4371
  let name;
4068
4372
  let filePath;
4069
4373
  if (entry.isDirectory()) {
4070
4374
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
4071
- (f) => fs17.existsSync(path17.join(entryPath, f))
4375
+ (f) => fs18.existsSync(path18.join(entryPath, f))
4072
4376
  );
4073
4377
  if (!indexFile) continue;
4074
4378
  name = entry.name;
4075
- filePath = path17.relative(cwd, path17.join(entryPath, indexFile));
4379
+ filePath = path18.relative(cwd, path18.join(entryPath, indexFile));
4076
4380
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
4077
4381
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
4078
- filePath = path17.relative(cwd, entryPath);
4382
+ filePath = path18.relative(cwd, entryPath);
4079
4383
  } else {
4080
4384
  continue;
4081
4385
  }
@@ -4083,7 +4387,7 @@ function discoverAdminComponents(cwd, collections) {
4083
4387
  if (collections) {
4084
4388
  for (const col of collections) {
4085
4389
  try {
4086
- const colContent = fs17.readFileSync(path17.join(cwd, col.filePath), "utf-8");
4390
+ const colContent = fs18.readFileSync(path18.join(cwd, col.filePath), "utf-8");
4087
4391
  if (colContent.includes(name)) {
4088
4392
  usedInCollection = col.slug;
4089
4393
  break;
@@ -4102,8 +4406,8 @@ function scanApiRoutes(cwd) {
4102
4406
  const out = [];
4103
4407
  const appDirs = ["src/app", "app"];
4104
4408
  for (const appDir of appDirs) {
4105
- const apiDir = path17.join(cwd, appDir, "api");
4106
- if (!fs17.existsSync(apiDir)) continue;
4409
+ const apiDir = path18.join(cwd, appDir, "api");
4410
+ if (!fs18.existsSync(apiDir)) continue;
4107
4411
  walkApiRoutes(apiDir, "/api", cwd, out);
4108
4412
  break;
4109
4413
  }
@@ -4112,14 +4416,14 @@ function scanApiRoutes(cwd) {
4112
4416
  function walkApiRoutes(dir, prefix, cwd, out) {
4113
4417
  let entries;
4114
4418
  try {
4115
- entries = fs17.readdirSync(dir, { withFileTypes: true });
4419
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
4116
4420
  } catch {
4117
4421
  return;
4118
4422
  }
4119
4423
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
4120
4424
  if (routeFile) {
4121
4425
  try {
4122
- const content = fs17.readFileSync(path17.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
4426
+ const content = fs18.readFileSync(path18.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
4123
4427
  const methods = HTTP_METHODS.filter(
4124
4428
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
4125
4429
  );
@@ -4127,7 +4431,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
4127
4431
  out.push({
4128
4432
  path: prefix,
4129
4433
  methods,
4130
- filePath: path17.relative(cwd, path17.join(dir, routeFile.name))
4434
+ filePath: path18.relative(cwd, path18.join(dir, routeFile.name))
4131
4435
  });
4132
4436
  }
4133
4437
  } catch {
@@ -4138,7 +4442,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
4138
4442
  if (entry.name === "node_modules" || entry.name === ".next") continue;
4139
4443
  let segment = entry.name;
4140
4444
  if (segment.startsWith("(") && segment.endsWith(")")) {
4141
- walkApiRoutes(path17.join(dir, entry.name), prefix, cwd, out);
4445
+ walkApiRoutes(path18.join(dir, entry.name), prefix, cwd, out);
4142
4446
  continue;
4143
4447
  }
4144
4448
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -4146,7 +4450,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
4146
4450
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
4147
4451
  segment = `:${segment.slice(1, -1)}`;
4148
4452
  }
4149
- walkApiRoutes(path17.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
4453
+ walkApiRoutes(path18.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
4150
4454
  }
4151
4455
  }
4152
4456
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -4166,10 +4470,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
4166
4470
  function scanEnvVars(cwd) {
4167
4471
  const candidates = [".env.example", ".env.local.example", ".env.template"];
4168
4472
  for (const envFile of candidates) {
4169
- const envPath = path17.join(cwd, envFile);
4170
- if (!fs17.existsSync(envPath)) continue;
4473
+ const envPath = path18.join(cwd, envFile);
4474
+ if (!fs18.existsSync(envPath)) continue;
4171
4475
  try {
4172
- const content = fs17.readFileSync(envPath, "utf-8");
4476
+ const content = fs18.readFileSync(envPath, "utf-8");
4173
4477
  const vars = [];
4174
4478
  for (const line of content.split("\n")) {
4175
4479
  const trimmed = line.trim();
@@ -4217,9 +4521,9 @@ function runQaDiscovery(cwd) {
4217
4521
  }
4218
4522
  function detectDevServer(cwd, out) {
4219
4523
  try {
4220
- const pkg = JSON.parse(fs18.readFileSync(path18.join(cwd, "package.json"), "utf-8"));
4524
+ const pkg = JSON.parse(fs19.readFileSync(path19.join(cwd, "package.json"), "utf-8"));
4221
4525
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
4222
- 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";
4526
+ const pm = fs19.existsSync(path19.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs19.existsSync(path19.join(cwd, "yarn.lock")) ? "yarn" : fs19.existsSync(path19.join(cwd, "bun.lockb")) ? "bun" : "npm";
4223
4527
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
4224
4528
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
4225
4529
  else if (allDeps.vite) out.devPort = 5173;
@@ -4229,8 +4533,8 @@ function detectDevServer(cwd, out) {
4229
4533
  function scanFrontendRoutes(cwd, out) {
4230
4534
  const appDirs = ["src/app", "app"];
4231
4535
  for (const appDir of appDirs) {
4232
- const full = path18.join(cwd, appDir);
4233
- if (!fs18.existsSync(full)) continue;
4536
+ const full = path19.join(cwd, appDir);
4537
+ if (!fs19.existsSync(full)) continue;
4234
4538
  walkFrontendRoutes(full, "", out);
4235
4539
  break;
4236
4540
  }
@@ -4238,7 +4542,7 @@ function scanFrontendRoutes(cwd, out) {
4238
4542
  function walkFrontendRoutes(dir, prefix, out) {
4239
4543
  let entries;
4240
4544
  try {
4241
- entries = fs18.readdirSync(dir, { withFileTypes: true });
4545
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
4242
4546
  } catch {
4243
4547
  return;
4244
4548
  }
@@ -4255,7 +4559,7 @@ function walkFrontendRoutes(dir, prefix, out) {
4255
4559
  if (entry.name === "node_modules" || entry.name === ".next") continue;
4256
4560
  let segment = entry.name;
4257
4561
  if (segment.startsWith("(") && segment.endsWith(")")) {
4258
- walkFrontendRoutes(path18.join(dir, entry.name), prefix, out);
4562
+ walkFrontendRoutes(path19.join(dir, entry.name), prefix, out);
4259
4563
  continue;
4260
4564
  }
4261
4565
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -4263,7 +4567,7 @@ function walkFrontendRoutes(dir, prefix, out) {
4263
4567
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
4264
4568
  segment = `:${segment.slice(1, -1)}`;
4265
4569
  }
4266
- walkFrontendRoutes(path18.join(dir, entry.name), `${prefix}/${segment}`, out);
4570
+ walkFrontendRoutes(path19.join(dir, entry.name), `${prefix}/${segment}`, out);
4267
4571
  }
4268
4572
  }
4269
4573
  function detectAuthFiles(cwd, out) {
@@ -4280,23 +4584,23 @@ function detectAuthFiles(cwd, out) {
4280
4584
  "src/app/api/oauth"
4281
4585
  ];
4282
4586
  for (const c of candidates) {
4283
- if (fs18.existsSync(path18.join(cwd, c))) out.authFiles.push(c);
4587
+ if (fs19.existsSync(path19.join(cwd, c))) out.authFiles.push(c);
4284
4588
  }
4285
4589
  }
4286
4590
  function detectRoles(cwd, out) {
4287
4591
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
4288
4592
  for (const rp of rolePaths) {
4289
- const dir = path18.join(cwd, rp);
4290
- if (!fs18.existsSync(dir)) continue;
4593
+ const dir = path19.join(cwd, rp);
4594
+ if (!fs19.existsSync(dir)) continue;
4291
4595
  let files;
4292
4596
  try {
4293
- files = fs18.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4597
+ files = fs19.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4294
4598
  } catch {
4295
4599
  continue;
4296
4600
  }
4297
4601
  for (const f of files) {
4298
4602
  try {
4299
- const content = fs18.readFileSync(path18.join(dir, f), "utf-8").slice(0, 5e3);
4603
+ const content = fs19.readFileSync(path19.join(dir, f), "utf-8").slice(0, 5e3);
4300
4604
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
4301
4605
  if (roleMatches) {
4302
4606
  for (const m of roleMatches) {
@@ -4529,8 +4833,8 @@ function failedAction3(reason) {
4529
4833
  }
4530
4834
 
4531
4835
  // src/scripts/dispatchJobFileTicks.ts
4532
- import * as fs20 from "fs";
4533
- import * as path20 from "path";
4836
+ import * as fs21 from "fs";
4837
+ import * as path21 from "path";
4534
4838
 
4535
4839
  // src/scripts/jobFrontmatter.ts
4536
4840
  var SCHEDULE_EVERY_VALUES = [
@@ -4781,8 +5085,8 @@ var ContentsApiBackend = class {
4781
5085
  };
4782
5086
 
4783
5087
  // src/scripts/jobState/localFileBackend.ts
4784
- import * as fs19 from "fs";
4785
- import * as path19 from "path";
5088
+ import * as fs20 from "fs";
5089
+ import * as path20 from "path";
4786
5090
  var LocalFileBackend = class {
4787
5091
  name = "local-file";
4788
5092
  cwd;
@@ -4797,7 +5101,7 @@ var LocalFileBackend = class {
4797
5101
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
4798
5102
  this.cwd = opts.cwd;
4799
5103
  this.jobsDir = opts.jobsDir;
4800
- this.absDir = path19.join(opts.cwd, opts.jobsDir);
5104
+ this.absDir = path20.join(opts.cwd, opts.jobsDir);
4801
5105
  this.owner = opts.owner;
4802
5106
  this.repo = opts.repo;
4803
5107
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -4812,7 +5116,7 @@ var LocalFileBackend = class {
4812
5116
  `);
4813
5117
  return;
4814
5118
  }
4815
- fs19.mkdirSync(this.absDir, { recursive: true });
5119
+ fs20.mkdirSync(this.absDir, { recursive: true });
4816
5120
  const prefix = this.cacheKeyPrefix();
4817
5121
  const probeKey = `${prefix}probe-${Date.now()}`;
4818
5122
  try {
@@ -4841,7 +5145,7 @@ var LocalFileBackend = class {
4841
5145
  `);
4842
5146
  return;
4843
5147
  }
4844
- if (!fs19.existsSync(this.absDir)) {
5148
+ if (!fs20.existsSync(this.absDir)) {
4845
5149
  return;
4846
5150
  }
4847
5151
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -4857,11 +5161,11 @@ var LocalFileBackend = class {
4857
5161
  }
4858
5162
  load(slug) {
4859
5163
  const relPath = stateFilePath(this.jobsDir, slug);
4860
- const absPath = path19.join(this.cwd, relPath);
4861
- if (!fs19.existsSync(absPath)) {
5164
+ const absPath = path20.join(this.cwd, relPath);
5165
+ if (!fs20.existsSync(absPath)) {
4862
5166
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
4863
5167
  }
4864
- const raw = fs19.readFileSync(absPath, "utf-8");
5168
+ const raw = fs20.readFileSync(absPath, "utf-8");
4865
5169
  let parsed;
4866
5170
  try {
4867
5171
  parsed = JSON.parse(raw);
@@ -4878,10 +5182,10 @@ var LocalFileBackend = class {
4878
5182
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
4879
5183
  return false;
4880
5184
  }
4881
- const absPath = path19.join(this.cwd, loaded.path);
4882
- fs19.mkdirSync(path19.dirname(absPath), { recursive: true });
5185
+ const absPath = path20.join(this.cwd, loaded.path);
5186
+ fs20.mkdirSync(path20.dirname(absPath), { recursive: true });
4883
5187
  const body = JSON.stringify(next, null, 2) + "\n";
4884
- fs19.writeFileSync(absPath, body, "utf-8");
5188
+ fs20.writeFileSync(absPath, body, "utf-8");
4885
5189
  return true;
4886
5190
  }
4887
5191
  cacheKeyPrefix() {
@@ -4958,7 +5262,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
4958
5262
  await backend.hydrate();
4959
5263
  }
4960
5264
  try {
4961
- const slugs = listJobSlugs(path20.join(ctx.cwd, jobsDir));
5265
+ const slugs = listJobSlugs(path21.join(ctx.cwd, jobsDir));
4962
5266
  ctx.data.jobSlugCount = slugs.length;
4963
5267
  if (slugs.length === 0) {
4964
5268
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -5057,17 +5361,17 @@ function formatAgo(ms) {
5057
5361
  }
5058
5362
  function readJobFrontmatter(cwd, jobsDir, slug) {
5059
5363
  try {
5060
- const raw = fs20.readFileSync(path20.join(cwd, jobsDir, `${slug}.md`), "utf-8");
5364
+ const raw = fs21.readFileSync(path21.join(cwd, jobsDir, `${slug}.md`), "utf-8");
5061
5365
  return splitFrontmatter(raw).frontmatter;
5062
5366
  } catch {
5063
5367
  return {};
5064
5368
  }
5065
5369
  }
5066
5370
  function listJobSlugs(absDir) {
5067
- if (!fs20.existsSync(absDir)) return [];
5371
+ if (!fs21.existsSync(absDir)) return [];
5068
5372
  let entries;
5069
5373
  try {
5070
- entries = fs20.readdirSync(absDir, { withFileTypes: true });
5374
+ entries = fs21.readdirSync(absDir, { withFileTypes: true });
5071
5375
  } catch {
5072
5376
  return [];
5073
5377
  }
@@ -5712,7 +6016,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
5712
6016
 
5713
6017
  // src/gha.ts
5714
6018
  import { execFileSync as execFileSync16 } from "child_process";
5715
- import * as fs21 from "fs";
6019
+ import * as fs22 from "fs";
5716
6020
  function getRunUrl() {
5717
6021
  const server = process.env.GITHUB_SERVER_URL;
5718
6022
  const repo = process.env.GITHUB_REPOSITORY;
@@ -5723,10 +6027,10 @@ function getRunUrl() {
5723
6027
  function reactToTriggerComment(cwd) {
5724
6028
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
5725
6029
  const eventPath = process.env.GITHUB_EVENT_PATH;
5726
- if (!eventPath || !fs21.existsSync(eventPath)) return;
6030
+ if (!eventPath || !fs22.existsSync(eventPath)) return;
5727
6031
  let event = null;
5728
6032
  try {
5729
- event = JSON.parse(fs21.readFileSync(eventPath, "utf-8"));
6033
+ event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
5730
6034
  } catch {
5731
6035
  return;
5732
6036
  }
@@ -6011,22 +6315,22 @@ var handleAbandonedGoal = async (ctx) => {
6011
6315
 
6012
6316
  // src/scripts/initFlow.ts
6013
6317
  import { execFileSync as execFileSync18 } from "child_process";
6014
- import * as fs23 from "fs";
6015
- import * as path22 from "path";
6318
+ import * as fs24 from "fs";
6319
+ import * as path23 from "path";
6016
6320
 
6017
6321
  // src/scripts/loadQaGuide.ts
6018
- import * as fs22 from "fs";
6019
- import * as path21 from "path";
6322
+ import * as fs23 from "fs";
6323
+ import * as path22 from "path";
6020
6324
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
6021
6325
  var loadQaGuide = async (ctx) => {
6022
- const full = path21.join(ctx.cwd, QA_GUIDE_REL_PATH);
6023
- if (!fs22.existsSync(full)) {
6326
+ const full = path22.join(ctx.cwd, QA_GUIDE_REL_PATH);
6327
+ if (!fs23.existsSync(full)) {
6024
6328
  ctx.data.qaGuide = "";
6025
6329
  ctx.data.qaGuidePath = "";
6026
6330
  return;
6027
6331
  }
6028
6332
  try {
6029
- ctx.data.qaGuide = fs22.readFileSync(full, "utf-8");
6333
+ ctx.data.qaGuide = fs23.readFileSync(full, "utf-8");
6030
6334
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
6031
6335
  } catch {
6032
6336
  ctx.data.qaGuide = "";
@@ -6036,9 +6340,9 @@ var loadQaGuide = async (ctx) => {
6036
6340
 
6037
6341
  // src/scripts/initFlow.ts
6038
6342
  function detectPackageManager(cwd) {
6039
- if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
6040
- if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) return "yarn";
6041
- if (fs23.existsSync(path22.join(cwd, "bun.lockb"))) return "bun";
6343
+ if (fs24.existsSync(path23.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
6344
+ if (fs24.existsSync(path23.join(cwd, "yarn.lock"))) return "yarn";
6345
+ if (fs24.existsSync(path23.join(cwd, "bun.lockb"))) return "bun";
6042
6346
  return "npm";
6043
6347
  }
6044
6348
  function qualityCommandsFor(pm) {
@@ -6160,48 +6464,48 @@ function performInit(cwd, force) {
6160
6464
  const pm = detectPackageManager(cwd);
6161
6465
  const ownerRepo = detectOwnerRepo(cwd);
6162
6466
  const defaultBranch = defaultBranchFromGit(cwd);
6163
- const configPath = path22.join(cwd, "kody.config.json");
6164
- if (fs23.existsSync(configPath) && !force) {
6467
+ const configPath = path23.join(cwd, "kody.config.json");
6468
+ if (fs24.existsSync(configPath) && !force) {
6165
6469
  skipped.push("kody.config.json");
6166
6470
  } else {
6167
6471
  const cfg = makeConfig(pm, ownerRepo, defaultBranch);
6168
- fs23.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
6472
+ fs24.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
6169
6473
  `);
6170
6474
  wrote.push("kody.config.json");
6171
6475
  }
6172
- const workflowDir = path22.join(cwd, ".github", "workflows");
6173
- const workflowPath = path22.join(workflowDir, "kody.yml");
6174
- if (fs23.existsSync(workflowPath) && !force) {
6476
+ const workflowDir = path23.join(cwd, ".github", "workflows");
6477
+ const workflowPath = path23.join(workflowDir, "kody.yml");
6478
+ if (fs24.existsSync(workflowPath) && !force) {
6175
6479
  skipped.push(".github/workflows/kody.yml");
6176
6480
  } else {
6177
- fs23.mkdirSync(workflowDir, { recursive: true });
6178
- fs23.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
6481
+ fs24.mkdirSync(workflowDir, { recursive: true });
6482
+ fs24.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
6179
6483
  wrote.push(".github/workflows/kody.yml");
6180
6484
  }
6181
- const hasUi = fs23.existsSync(path22.join(cwd, "src/app")) || fs23.existsSync(path22.join(cwd, "app")) || fs23.existsSync(path22.join(cwd, "pages"));
6485
+ const hasUi = fs24.existsSync(path23.join(cwd, "src/app")) || fs24.existsSync(path23.join(cwd, "app")) || fs24.existsSync(path23.join(cwd, "pages"));
6182
6486
  if (hasUi) {
6183
- const qaGuidePath = path22.join(cwd, QA_GUIDE_REL_PATH);
6184
- if (fs23.existsSync(qaGuidePath) && !force) {
6487
+ const qaGuidePath = path23.join(cwd, QA_GUIDE_REL_PATH);
6488
+ if (fs24.existsSync(qaGuidePath) && !force) {
6185
6489
  skipped.push(QA_GUIDE_REL_PATH);
6186
6490
  } else {
6187
- fs23.mkdirSync(path22.dirname(qaGuidePath), { recursive: true });
6491
+ fs24.mkdirSync(path23.dirname(qaGuidePath), { recursive: true });
6188
6492
  const discovery = runQaDiscovery(cwd);
6189
- fs23.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
6493
+ fs24.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
6190
6494
  wrote.push(QA_GUIDE_REL_PATH);
6191
6495
  }
6192
6496
  }
6193
6497
  const builtinJobs = listBuiltinJobs();
6194
6498
  if (builtinJobs.length > 0) {
6195
- const jobsDir = path22.join(cwd, ".kody", "jobs");
6196
- fs23.mkdirSync(jobsDir, { recursive: true });
6499
+ const jobsDir = path23.join(cwd, ".kody", "jobs");
6500
+ fs24.mkdirSync(jobsDir, { recursive: true });
6197
6501
  for (const job of builtinJobs) {
6198
- const rel = path22.join(".kody", "jobs", `${job.slug}.md`);
6199
- const target = path22.join(cwd, rel);
6200
- if (fs23.existsSync(target) && !force) {
6502
+ const rel = path23.join(".kody", "jobs", `${job.slug}.md`);
6503
+ const target = path23.join(cwd, rel);
6504
+ if (fs24.existsSync(target) && !force) {
6201
6505
  skipped.push(rel);
6202
6506
  continue;
6203
6507
  }
6204
- fs23.writeFileSync(target, fs23.readFileSync(job.filePath, "utf-8"));
6508
+ fs24.writeFileSync(target, fs24.readFileSync(job.filePath, "utf-8"));
6205
6509
  wrote.push(rel);
6206
6510
  }
6207
6511
  }
@@ -6213,12 +6517,12 @@ function performInit(cwd, force) {
6213
6517
  continue;
6214
6518
  }
6215
6519
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
6216
- const target = path22.join(workflowDir, `kody-${exe.name}.yml`);
6217
- if (fs23.existsSync(target) && !force) {
6520
+ const target = path23.join(workflowDir, `kody-${exe.name}.yml`);
6521
+ if (fs24.existsSync(target) && !force) {
6218
6522
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
6219
6523
  continue;
6220
6524
  }
6221
- fs23.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
6525
+ fs24.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
6222
6526
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
6223
6527
  }
6224
6528
  let labels;
@@ -6307,14 +6611,14 @@ var loadCoverageRules = async (ctx) => {
6307
6611
  };
6308
6612
 
6309
6613
  // src/goal/state.ts
6310
- import * as fs24 from "fs";
6311
- import * as path23 from "path";
6614
+ import * as fs25 from "fs";
6615
+ import * as path24 from "path";
6312
6616
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
6313
6617
  var GoalStateError = class extends Error {
6314
- constructor(path31, message) {
6315
- super(`Invalid goal state at ${path31}:
6618
+ constructor(path32, message) {
6619
+ super(`Invalid goal state at ${path32}:
6316
6620
  ${message}`);
6317
- this.path = path31;
6621
+ this.path = path32;
6318
6622
  this.name = "GoalStateError";
6319
6623
  }
6320
6624
  path;
@@ -6358,16 +6662,16 @@ function serializeGoalState(s) {
6358
6662
  `;
6359
6663
  }
6360
6664
  function goalStatePath(cwd, goalId) {
6361
- return path23.join(cwd, ".kody", "goals", goalId, "state.json");
6665
+ return path24.join(cwd, ".kody", "goals", goalId, "state.json");
6362
6666
  }
6363
6667
  function readGoalState(cwd, goalId) {
6364
6668
  const file = goalStatePath(cwd, goalId);
6365
- if (!fs24.existsSync(file)) {
6669
+ if (!fs25.existsSync(file)) {
6366
6670
  throw new GoalStateError(file, "file not found");
6367
6671
  }
6368
6672
  let raw;
6369
6673
  try {
6370
- raw = JSON.parse(fs24.readFileSync(file, "utf-8"));
6674
+ raw = JSON.parse(fs25.readFileSync(file, "utf-8"));
6371
6675
  } catch (err) {
6372
6676
  throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
6373
6677
  }
@@ -6375,8 +6679,8 @@ function readGoalState(cwd, goalId) {
6375
6679
  }
6376
6680
  function writeGoalState(cwd, goalId, state) {
6377
6681
  const file = goalStatePath(cwd, goalId);
6378
- fs24.mkdirSync(path23.dirname(file), { recursive: true });
6379
- fs24.writeFileSync(file, serializeGoalState(state), "utf-8");
6682
+ fs25.mkdirSync(path24.dirname(file), { recursive: true });
6683
+ fs25.writeFileSync(file, serializeGoalState(state), "utf-8");
6380
6684
  }
6381
6685
  function nowIso() {
6382
6686
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -6470,8 +6774,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
6470
6774
  };
6471
6775
 
6472
6776
  // src/scripts/loadJobFromFile.ts
6473
- import * as fs25 from "fs";
6474
- import * as path24 from "path";
6777
+ import * as fs26 from "fs";
6778
+ import * as path25 from "path";
6475
6779
  var loadJobFromFile = async (ctx, _profile, args) => {
6476
6780
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
6477
6781
  const slugArg = String(args?.slugArg ?? "job");
@@ -6479,11 +6783,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
6479
6783
  if (!slug) {
6480
6784
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
6481
6785
  }
6482
- const absPath = path24.join(ctx.cwd, jobsDir, `${slug}.md`);
6483
- if (!fs25.existsSync(absPath)) {
6786
+ const absPath = path25.join(ctx.cwd, jobsDir, `${slug}.md`);
6787
+ if (!fs26.existsSync(absPath)) {
6484
6788
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
6485
6789
  }
6486
- const raw = fs25.readFileSync(absPath, "utf-8");
6790
+ const raw = fs26.readFileSync(absPath, "utf-8");
6487
6791
  const { title, body } = parseJobFile(raw, slug);
6488
6792
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
6489
6793
  const loaded = await backend.load(slug);
@@ -6515,16 +6819,16 @@ function humanizeSlug(slug) {
6515
6819
  }
6516
6820
 
6517
6821
  // src/scripts/loadMemoryContext.ts
6518
- import * as fs26 from "fs";
6519
- import * as path25 from "path";
6822
+ import * as fs27 from "fs";
6823
+ import * as path26 from "path";
6520
6824
  var MEMORY_DIR_RELATIVE = ".kody/memory";
6521
6825
  var MAX_PAGES = 8;
6522
6826
  var PER_PAGE_MAX_BYTES = 4e3;
6523
6827
  var TOTAL_MAX_BYTES = 24e3;
6524
6828
  var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
6525
6829
  var loadMemoryContext = async (ctx) => {
6526
- const memoryAbs = path25.join(ctx.cwd, MEMORY_DIR_RELATIVE);
6527
- if (!fs26.existsSync(memoryAbs)) {
6830
+ const memoryAbs = path26.join(ctx.cwd, MEMORY_DIR_RELATIVE);
6831
+ if (!fs27.existsSync(memoryAbs)) {
6528
6832
  ctx.data.memoryContext = "";
6529
6833
  return;
6530
6834
  }
@@ -6549,21 +6853,21 @@ function collectPages(memoryAbs) {
6549
6853
  walkMd(memoryAbs, (file) => {
6550
6854
  let stat;
6551
6855
  try {
6552
- stat = fs26.statSync(file);
6856
+ stat = fs27.statSync(file);
6553
6857
  } catch {
6554
6858
  return;
6555
6859
  }
6556
6860
  let raw;
6557
6861
  try {
6558
- raw = fs26.readFileSync(file, "utf-8");
6862
+ raw = fs27.readFileSync(file, "utf-8");
6559
6863
  } catch {
6560
6864
  return;
6561
6865
  }
6562
6866
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
6563
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path25.basename(file, ".md");
6867
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path26.basename(file, ".md");
6564
6868
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
6565
6869
  out.push({
6566
- relPath: path25.relative(memoryAbs, file),
6870
+ relPath: path26.relative(memoryAbs, file),
6567
6871
  title,
6568
6872
  updated,
6569
6873
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
@@ -6631,16 +6935,16 @@ function walkMd(root, visit) {
6631
6935
  const dir = stack.pop();
6632
6936
  let names;
6633
6937
  try {
6634
- names = fs26.readdirSync(dir);
6938
+ names = fs27.readdirSync(dir);
6635
6939
  } catch {
6636
6940
  continue;
6637
6941
  }
6638
6942
  for (const name of names) {
6639
6943
  if (name.startsWith(".")) continue;
6640
- const full = path25.join(dir, name);
6944
+ const full = path26.join(dir, name);
6641
6945
  let stat;
6642
6946
  try {
6643
- stat = fs26.statSync(full);
6947
+ stat = fs27.statSync(full);
6644
6948
  } catch {
6645
6949
  continue;
6646
6950
  }
@@ -6744,9 +7048,12 @@ function formatReviewComments(raw) {
6744
7048
  }
6745
7049
  }
6746
7050
 
7051
+ // src/scripts/loadTaskContext.ts
7052
+ init_events();
7053
+
6747
7054
  // src/taskContext.ts
6748
- import * as fs27 from "fs";
6749
- import * as path26 from "path";
7055
+ import * as fs28 from "fs";
7056
+ import * as path27 from "path";
6750
7057
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
6751
7058
  function buildTaskContext(args) {
6752
7059
  return {
@@ -6762,10 +7069,10 @@ function buildTaskContext(args) {
6762
7069
  }
6763
7070
  function persistTaskContext(cwd, ctx) {
6764
7071
  try {
6765
- const dir = path26.join(cwd, ".kody", "runs", ctx.runId);
6766
- fs27.mkdirSync(dir, { recursive: true });
6767
- const file = path26.join(dir, "task-context.json");
6768
- fs27.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
7072
+ const dir = path27.join(cwd, ".kody", "runs", ctx.runId);
7073
+ fs28.mkdirSync(dir, { recursive: true });
7074
+ const file = path27.join(dir, "task-context.json");
7075
+ fs28.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
6769
7076
  `);
6770
7077
  return file;
6771
7078
  } catch (err) {
@@ -8120,8 +8427,8 @@ function resolveBaseOverride(value) {
8120
8427
 
8121
8428
  // src/scripts/runTickScript.ts
8122
8429
  import { spawnSync } from "child_process";
8123
- import * as fs28 from "fs";
8124
- import * as path27 from "path";
8430
+ import * as fs29 from "fs";
8431
+ import * as path28 from "path";
8125
8432
  var runTickScript = async (ctx, _profile, args) => {
8126
8433
  ctx.skipAgent = true;
8127
8434
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
@@ -8133,13 +8440,13 @@ var runTickScript = async (ctx, _profile, args) => {
8133
8440
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
8134
8441
  return;
8135
8442
  }
8136
- const jobPath = path27.join(ctx.cwd, jobsDir, `${slug}.md`);
8137
- if (!fs28.existsSync(jobPath)) {
8443
+ const jobPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
8444
+ if (!fs29.existsSync(jobPath)) {
8138
8445
  ctx.output.exitCode = 99;
8139
8446
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
8140
8447
  return;
8141
8448
  }
8142
- const raw = fs28.readFileSync(jobPath, "utf-8");
8449
+ const raw = fs29.readFileSync(jobPath, "utf-8");
8143
8450
  const { frontmatter } = splitFrontmatter(raw);
8144
8451
  const tickScript = frontmatter.tickScript;
8145
8452
  if (!tickScript) {
@@ -8147,8 +8454,8 @@ var runTickScript = async (ctx, _profile, args) => {
8147
8454
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
8148
8455
  return;
8149
8456
  }
8150
- const scriptPath = path27.isAbsolute(tickScript) ? tickScript : path27.join(ctx.cwd, tickScript);
8151
- if (!fs28.existsSync(scriptPath)) {
8457
+ const scriptPath = path28.isAbsolute(tickScript) ? tickScript : path28.join(ctx.cwd, tickScript);
8458
+ if (!fs29.existsSync(scriptPath)) {
8152
8459
  ctx.output.exitCode = 99;
8153
8460
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
8154
8461
  return;
@@ -8499,110 +8806,8 @@ function tryPostPr6(prNumber, body, cwd) {
8499
8806
  }
8500
8807
  }
8501
8808
 
8502
- // src/verify.ts
8503
- import { spawn as spawn2 } from "child_process";
8504
- var TAIL_CHARS = 4e3;
8505
- var COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
8506
- var DEFAULT_TEST_RETRIES = 2;
8507
- function runCommand(command, cwd) {
8508
- return new Promise((resolve4) => {
8509
- const start = Date.now();
8510
- const child = spawn2(command, {
8511
- cwd,
8512
- shell: true,
8513
- env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
8514
- stdio: ["ignore", "pipe", "pipe"]
8515
- });
8516
- const buffers = [];
8517
- let totalSize = 0;
8518
- const collect = (chunk) => {
8519
- buffers.push(chunk);
8520
- totalSize += chunk.length;
8521
- while (totalSize > TAIL_CHARS * 4 && buffers.length > 1) {
8522
- totalSize -= buffers[0].length;
8523
- buffers.shift();
8524
- }
8525
- };
8526
- child.stdout?.on("data", collect);
8527
- child.stderr?.on("data", collect);
8528
- const timer = setTimeout(() => {
8529
- child.kill("SIGTERM");
8530
- setTimeout(() => {
8531
- if (!child.killed) child.kill("SIGKILL");
8532
- }, 5e3);
8533
- }, COMMAND_TIMEOUT_MS);
8534
- child.on("exit", (code) => {
8535
- clearTimeout(timer);
8536
- const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
8537
- resolve4({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
8538
- });
8539
- child.on("error", (err) => {
8540
- clearTimeout(timer);
8541
- resolve4({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
8542
- });
8543
- });
8544
- }
8545
- async function verifyAll(config, cwd) {
8546
- const commands = [];
8547
- if (config.quality.typecheck) commands.push({ name: "typecheck", cmd: config.quality.typecheck });
8548
- if (config.quality.testUnit) commands.push({ name: "test", cmd: config.quality.testUnit });
8549
- if (config.quality.lint) commands.push({ name: "lint", cmd: config.quality.lint });
8550
- if (config.quality.format) commands.push({ name: "format", cmd: config.quality.format });
8551
- const failed = [];
8552
- const details = {};
8553
- for (const { name, cmd } of commands) {
8554
- const result = await runCommand(cmd, cwd);
8555
- details[name] = result;
8556
- if (result.exitCode !== 0) failed.push(name);
8557
- }
8558
- return { ok: failed.length === 0, failed, details };
8559
- }
8560
- async function applyTestRetries(initial, testCommand, cwd, runner, testRetries = DEFAULT_TEST_RETRIES) {
8561
- if (initial.ok) return { ...initial, recovered: [] };
8562
- const recovered = [];
8563
- const details = { ...initial.details };
8564
- let failed = [...initial.failed];
8565
- if (failed.includes("test") && testCommand && testRetries > 0) {
8566
- for (let attempt = 1; attempt <= testRetries; attempt++) {
8567
- const retry = await runner(testCommand, cwd);
8568
- details[`test (retry ${attempt})`] = retry;
8569
- if (retry.exitCode === 0) {
8570
- failed = failed.filter((f) => f !== "test");
8571
- recovered.push("test");
8572
- break;
8573
- }
8574
- }
8575
- }
8576
- return { ok: failed.length === 0, failed, details, recovered };
8577
- }
8578
- async function verifyAllWithRetry(config, cwd, opts) {
8579
- const initial = await verifyAll(config, cwd);
8580
- return applyTestRetries(initial, config.quality.testUnit, cwd, runCommand, opts?.testRetries);
8581
- }
8582
- var ANSI_RE = /\x1B\[[0-?]*[ -/]*[@-~]/g;
8583
- function stripAnsi(s) {
8584
- return s.replace(ANSI_RE, "");
8585
- }
8586
- function summarizeFailure(result) {
8587
- const lines = [`verify failed: ${result.failed.join(", ")}`];
8588
- for (const name of result.failed) {
8589
- const d = result.details[name];
8590
- if (!d) continue;
8591
- lines.push(`
8592
- --- ${name} (exit ${d.exitCode}, ${(d.durationMs / 1e3).toFixed(1)}s) ---`);
8593
- lines.push(stripAnsi(d.tail));
8594
- for (let attempt = 1; ; attempt++) {
8595
- const retry = result.details[`${name} (retry ${attempt})`];
8596
- if (!retry) break;
8597
- lines.push(`
8598
- --- ${name} (retry ${attempt}: exit ${retry.exitCode}, ${(retry.durationMs / 1e3).toFixed(1)}s) ---`);
8599
- lines.push(stripAnsi(retry.tail));
8600
- }
8601
- }
8602
- return lines.join("\n");
8603
- }
8604
-
8605
8809
  // src/scripts/verify.ts
8810
+ init_verify();
8606
8811
  var verify = async (ctx) => {
8607
8812
  try {
8608
8813
  const result = await verifyAllWithRetry(ctx.config, ctx.cwd);
@@ -9085,7 +9290,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
9085
9290
  };
9086
9291
 
9087
9292
  // src/scripts/writeRunSummary.ts
9088
- import * as fs29 from "fs";
9293
+ import * as fs30 from "fs";
9089
9294
  var writeRunSummary = async (ctx, profile) => {
9090
9295
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
9091
9296
  if (!summaryPath) return;
@@ -9107,7 +9312,7 @@ var writeRunSummary = async (ctx, profile) => {
9107
9312
  if (reason) lines.push(`- **Reason:** ${reason}`);
9108
9313
  lines.push("");
9109
9314
  try {
9110
- fs29.appendFileSync(summaryPath, `${lines.join("\n")}
9315
+ fs30.appendFileSync(summaryPath, `${lines.join("\n")}
9111
9316
  `);
9112
9317
  } catch {
9113
9318
  }
@@ -9215,21 +9420,21 @@ function firstRequiredFailure(results, tools) {
9215
9420
  }
9216
9421
  return null;
9217
9422
  }
9218
- function verifyOne(tool, cwd) {
9219
- const result = { name: tool.name, present: false, verified: false };
9220
- let present = runShell(tool.install.checkCommand, cwd);
9221
- if (!present && tool.install.installCommand) {
9222
- runShell(tool.install.installCommand, cwd, 12e4);
9223
- present = runShell(tool.install.checkCommand, cwd);
9423
+ function verifyOne(tool2, cwd) {
9424
+ const result = { name: tool2.name, present: false, verified: false };
9425
+ let present = runShell(tool2.install.checkCommand, cwd);
9426
+ if (!present && tool2.install.installCommand) {
9427
+ runShell(tool2.install.installCommand, cwd, 12e4);
9428
+ present = runShell(tool2.install.checkCommand, cwd);
9224
9429
  }
9225
9430
  result.present = present;
9226
9431
  if (!present) {
9227
- result.error = `tool "${tool.name}" not on PATH (check: ${tool.install.checkCommand})`;
9432
+ result.error = `tool "${tool2.name}" not on PATH (check: ${tool2.install.checkCommand})`;
9228
9433
  return result;
9229
9434
  }
9230
- const verified = runShell(tool.verify, cwd);
9435
+ const verified = runShell(tool2.verify, cwd);
9231
9436
  result.verified = verified;
9232
- if (!verified) result.error = `tool "${tool.name}" failed verify: ${tool.verify}`;
9437
+ if (!verified) result.error = `tool "${tool2.name}" failed verify: ${tool2.verify}`;
9233
9438
  return result;
9234
9439
  }
9235
9440
  function runShell(cmd, cwd, timeoutMs = 3e4) {
@@ -9324,9 +9529,9 @@ async function runExecutable(profileName, input) {
9324
9529
  data: {},
9325
9530
  output: { exitCode: 0 }
9326
9531
  };
9327
- const ndjsonDir = path28.join(input.cwd, ".kody");
9532
+ const ndjsonDir = path29.join(input.cwd, ".kody");
9328
9533
  const invokeAgent = async (prompt) => {
9329
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path28.isAbsolute(p) ? p : path28.resolve(profile.dir, p)).filter((p) => p.length > 0);
9534
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path29.isAbsolute(p) ? p : path29.resolve(profile.dir, p)).filter((p) => p.length > 0);
9330
9535
  const syntheticPath = ctx.data.syntheticPluginPath;
9331
9536
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
9332
9537
  return runAgent({
@@ -9346,6 +9551,10 @@ async function runExecutable(profileName, input) {
9346
9551
  maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
9347
9552
  systemPromptAppend: profile.claudeCode.systemPromptAppend,
9348
9553
  cacheable: profile.claudeCode.cacheable,
9554
+ enableVerifyTool: profile.claudeCode.enableVerifyTool,
9555
+ verifyToolMaxAttempts: profile.claudeCode.verifyAttempts ?? null,
9556
+ verifyConfig: profile.claudeCode.enableVerifyTool ? config : void 0,
9557
+ executableName: profileName,
9349
9558
  settingSources: profile.claudeCode.settingSources
9350
9559
  });
9351
9560
  };
@@ -9490,7 +9699,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
9490
9699
  function getProfileInputsForChild(profileName, _cwd) {
9491
9700
  try {
9492
9701
  const profilePath = resolveProfilePath(profileName);
9493
- if (!fs30.existsSync(profilePath)) return null;
9702
+ if (!fs31.existsSync(profilePath)) return null;
9494
9703
  return loadProfile(profilePath).inputs;
9495
9704
  } catch {
9496
9705
  return null;
@@ -9499,17 +9708,17 @@ function getProfileInputsForChild(profileName, _cwd) {
9499
9708
  function resolveProfilePath(profileName) {
9500
9709
  const found = resolveExecutable(profileName);
9501
9710
  if (found) return found;
9502
- const here = path28.dirname(new URL(import.meta.url).pathname);
9711
+ const here = path29.dirname(new URL(import.meta.url).pathname);
9503
9712
  const candidates = [
9504
- path28.join(here, "executables", profileName, "profile.json"),
9713
+ path29.join(here, "executables", profileName, "profile.json"),
9505
9714
  // same-dir sibling (dev)
9506
- path28.join(here, "..", "executables", profileName, "profile.json"),
9715
+ path29.join(here, "..", "executables", profileName, "profile.json"),
9507
9716
  // up one (prod: dist/bin → dist/executables)
9508
- path28.join(here, "..", "src", "executables", profileName, "profile.json")
9717
+ path29.join(here, "..", "src", "executables", profileName, "profile.json")
9509
9718
  // fallback
9510
9719
  ];
9511
9720
  for (const c of candidates) {
9512
- if (fs30.existsSync(c)) return c;
9721
+ if (fs31.existsSync(c)) return c;
9513
9722
  }
9514
9723
  return candidates[0];
9515
9724
  }
@@ -9606,8 +9815,8 @@ function resolveShellTimeoutMs(entry) {
9606
9815
  var SIGKILL_GRACE_MS = 5e3;
9607
9816
  async function runShellEntry(entry, ctx, profile) {
9608
9817
  const shellName = entry.shell;
9609
- const shellPath = path28.join(profile.dir, shellName);
9610
- if (!fs30.existsSync(shellPath)) {
9818
+ const shellPath = path29.join(profile.dir, shellName);
9819
+ if (!fs31.existsSync(shellPath)) {
9611
9820
  ctx.skipAgent = true;
9612
9821
  ctx.output.exitCode = 99;
9613
9822
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -9742,7 +9951,12 @@ async function runContainerLoop(profile, ctx, input) {
9742
9951
  const child = children[currentIdx];
9743
9952
  process.stderr.write(`[kody container] step ${iteration}: invoking ${child.exec}
9744
9953
  `);
9745
- resetWorkingTree(input.cwd);
9954
+ if (profile.resetBetweenChildren !== false) {
9955
+ resetWorkingTree(input.cwd);
9956
+ } else {
9957
+ process.stderr.write(`[kody container] resetBetweenChildren=false; preserving tracked tree
9958
+ `);
9959
+ }
9746
9960
  const priorState = readContainerState(ctx, child, reader);
9747
9961
  if (priorState.core?.prUrl) knownPrUrl = priorState.core.prUrl;
9748
9962
  const priorAction = priorState.executables?.[child.exec]?.lastAction;
@@ -10052,9 +10266,9 @@ function resolveAuthToken(env = process.env) {
10052
10266
  return token;
10053
10267
  }
10054
10268
  function detectPackageManager2(cwd) {
10055
- if (fs31.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
10056
- if (fs31.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
10057
- if (fs31.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
10269
+ if (fs32.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
10270
+ if (fs32.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
10271
+ if (fs32.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
10058
10272
  return "npm";
10059
10273
  }
10060
10274
  function shellOut(cmd, args, cwd, stream = true) {
@@ -10141,11 +10355,11 @@ function configureGitIdentity(cwd) {
10141
10355
  }
10142
10356
  function postFailureTail(issueNumber, cwd, reason) {
10143
10357
  if (!issueNumber) return;
10144
- const logPath = path29.join(cwd, ".kody", "last-run.jsonl");
10358
+ const logPath = path30.join(cwd, ".kody", "last-run.jsonl");
10145
10359
  let tail = "";
10146
10360
  try {
10147
- if (fs31.existsSync(logPath)) {
10148
- const content = fs31.readFileSync(logPath, "utf-8");
10361
+ if (fs32.existsSync(logPath)) {
10362
+ const content = fs32.readFileSync(logPath, "utf-8");
10149
10363
  tail = content.slice(-3e3);
10150
10364
  }
10151
10365
  } catch {
@@ -10170,7 +10384,7 @@ async function runCi(argv) {
10170
10384
  return 0;
10171
10385
  }
10172
10386
  const args = parseCiArgs(argv);
10173
- const cwd = args.cwd ? path29.resolve(args.cwd) : process.cwd();
10387
+ const cwd = args.cwd ? path30.resolve(args.cwd) : process.cwd();
10174
10388
  let earlyConfig;
10175
10389
  try {
10176
10390
  earlyConfig = loadConfig(cwd);
@@ -10180,9 +10394,9 @@ async function runCi(argv) {
10180
10394
  const eventName = process.env.GITHUB_EVENT_NAME;
10181
10395
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
10182
10396
  let manualWorkflowDispatch = false;
10183
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs31.existsSync(dispatchEventPath)) {
10397
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs32.existsSync(dispatchEventPath)) {
10184
10398
  try {
10185
- const evt = JSON.parse(fs31.readFileSync(dispatchEventPath, "utf-8"));
10399
+ const evt = JSON.parse(fs32.readFileSync(dispatchEventPath, "utf-8"));
10186
10400
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
10187
10401
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
10188
10402
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -10441,9 +10655,9 @@ function parseChatArgs(argv, env = process.env) {
10441
10655
  return result;
10442
10656
  }
10443
10657
  function commitChatFiles(cwd, sessionId, verbose) {
10444
- const sessionFile = path30.relative(cwd, sessionFilePath(cwd, sessionId));
10445
- const eventsFile = path30.relative(cwd, eventsFilePath(cwd, sessionId));
10446
- const paths = [sessionFile, eventsFile].filter((p) => fs32.existsSync(path30.join(cwd, p)));
10658
+ const sessionFile = path31.relative(cwd, sessionFilePath(cwd, sessionId));
10659
+ const eventsFile = path31.relative(cwd, eventsFilePath(cwd, sessionId));
10660
+ const paths = [sessionFile, eventsFile].filter((p) => fs33.existsSync(path31.join(cwd, p)));
10447
10661
  if (paths.length === 0) return;
10448
10662
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
10449
10663
  try {
@@ -10481,7 +10695,7 @@ async function runChat(argv) {
10481
10695
  ${CHAT_HELP}`);
10482
10696
  return 64;
10483
10697
  }
10484
- const cwd = args.cwd ? path30.resolve(args.cwd) : process.cwd();
10698
+ const cwd = args.cwd ? path31.resolve(args.cwd) : process.cwd();
10485
10699
  const sessionId = args.sessionId;
10486
10700
  const unpackedSecrets = unpackAllSecrets();
10487
10701
  if (unpackedSecrets > 0) {
@@ -10533,7 +10747,7 @@ ${CHAT_HELP}`);
10533
10747
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
10534
10748
  const meta = readMeta(sessionFile);
10535
10749
  process.stdout.write(
10536
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs32.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
10750
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs33.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
10537
10751
  `
10538
10752
  );
10539
10753
  try {
@@ -10571,6 +10785,7 @@ ${CHAT_HELP}`);
10571
10785
  }
10572
10786
 
10573
10787
  // src/stats.ts
10788
+ init_events();
10574
10789
  function parseStatsArgs(argv) {
10575
10790
  const out = { cwd: process.cwd() };
10576
10791
  for (let i = 0; i < argv.length; i++) {