@kody-ade/kody-engine 0.4.59 → 0.4.61

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
@@ -10,6 +10,14 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/events.ts
13
+ var events_exports = {};
14
+ __export(events_exports, {
15
+ __resetRunIdCache: () => __resetRunIdCache,
16
+ emitEvent: () => emitEvent,
17
+ listRuns: () => listRuns,
18
+ readEvents: () => readEvents,
19
+ resolveRunId: () => resolveRunId
20
+ });
13
21
  import * as crypto from "crypto";
14
22
  import * as fs3 from "fs";
15
23
  import * as path3 from "path";
@@ -28,6 +36,10 @@ function resolveRunId() {
28
36
  process.env.KODY_RUN_ID = cachedRunId;
29
37
  return cachedRunId;
30
38
  }
39
+ function __resetRunIdCache() {
40
+ cachedRunId = null;
41
+ delete process.env.KODY_RUN_ID;
42
+ }
31
43
  function emitEvent(cwd, ev) {
32
44
  if (process.env.KODY_EVENTS === "0") return;
33
45
  try {
@@ -303,7 +315,7 @@ var init_verifyMcp = __esm({
303
315
  // package.json
304
316
  var package_default = {
305
317
  name: "@kody-ade/kody-engine",
306
- version: "0.4.59",
318
+ version: "0.4.61",
307
319
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
308
320
  license: "MIT",
309
321
  type: "module",
@@ -353,8 +365,8 @@ var package_default = {
353
365
 
354
366
  // src/chat-cli.ts
355
367
  import { execFileSync as execFileSync31 } from "child_process";
356
- import * as fs32 from "fs";
357
- import * as path30 from "path";
368
+ import * as fs33 from "fs";
369
+ import * as path31 from "path";
358
370
 
359
371
  // src/chat/events.ts
360
372
  import * as fs from "fs";
@@ -1326,8 +1338,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
1326
1338
 
1327
1339
  // src/kody-cli.ts
1328
1340
  import { execFileSync as execFileSync30 } from "child_process";
1329
- import * as fs31 from "fs";
1330
- import * as path29 from "path";
1341
+ import * as fs32 from "fs";
1342
+ import * as path30 from "path";
1331
1343
 
1332
1344
  // src/dispatch.ts
1333
1345
  import * as fs8 from "fs";
@@ -1906,8 +1918,8 @@ function postPrReviewComment(prNumber, body, cwd) {
1906
1918
 
1907
1919
  // src/executor.ts
1908
1920
  import { execFileSync as execFileSync29, spawn as spawn5 } from "child_process";
1909
- import * as fs30 from "fs";
1910
- import * as path28 from "path";
1921
+ import * as fs31 from "fs";
1922
+ import * as path29 from "path";
1911
1923
  init_events();
1912
1924
 
1913
1925
  // src/profile.ts
@@ -1918,6 +1930,27 @@ var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits",
1918
1930
  var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
1919
1931
  var VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
1920
1932
  var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
1933
+ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
1934
+ "name",
1935
+ "describe",
1936
+ "role",
1937
+ "kind",
1938
+ "schedule",
1939
+ "phase",
1940
+ "inputs",
1941
+ "claudeCode",
1942
+ "cliTools",
1943
+ "scripts",
1944
+ "outputContract",
1945
+ "inputArtifacts",
1946
+ "outputArtifacts",
1947
+ "input",
1948
+ // legacy JSON name for inputArtifacts source
1949
+ "output",
1950
+ // legacy JSON name for outputArtifacts source
1951
+ "children",
1952
+ "resetBetweenChildren"
1953
+ ]);
1921
1954
  var ProfileError = class extends Error {
1922
1955
  constructor(profilePath, message) {
1923
1956
  super(`Invalid profile at ${profilePath}:
@@ -1941,6 +1974,13 @@ function loadProfile(profilePath) {
1941
1974
  throw new ProfileError(profilePath, "profile must be a JSON object");
1942
1975
  }
1943
1976
  const r = raw;
1977
+ const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
1978
+ if (unknownKeys.length > 0) {
1979
+ process.stderr.write(
1980
+ `[kody profile] ${path8.basename(path8.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
1981
+ `
1982
+ );
1983
+ }
1944
1984
  const kind = r.kind === "scheduled" ? "scheduled" : "oneshot";
1945
1985
  if (kind === "scheduled" && typeof r.schedule !== "string") {
1946
1986
  throw new ProfileError(profilePath, `kind: "scheduled" requires a "schedule" cron string`);
@@ -1972,6 +2012,10 @@ function loadProfile(profilePath) {
1972
2012
  inputArtifacts: parseInputArtifacts(profilePath, r.input),
1973
2013
  outputArtifacts: parseOutputArtifacts(profilePath, r.output),
1974
2014
  children,
2015
+ // Default true: preserves legacy bug-safe behaviour where each
2016
+ // container child sees a clean tracked tree (see executor.ts).
2017
+ // Containers opt out by setting `"resetBetweenChildren": false`.
2018
+ resetBetweenChildren: typeof r.resetBetweenChildren === "boolean" ? r.resetBetweenChildren : true,
1975
2019
  dir: path8.dirname(profilePath)
1976
2020
  };
1977
2021
  return profile;
@@ -3230,13 +3274,36 @@ function defaultLabelMap() {
3230
3274
  }
3231
3275
 
3232
3276
  // src/scripts/commitAndPush.ts
3277
+ import * as fs14 from "fs";
3278
+ import * as path13 from "path";
3279
+ init_events();
3233
3280
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
3234
- var commitAndPush2 = async (ctx) => {
3281
+ function sentinelPathForStage(cwd, profileName) {
3282
+ const runId = resolveRunId();
3283
+ return path13.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
3284
+ }
3285
+ var commitAndPush2 = async (ctx, profile) => {
3235
3286
  const branch = ctx.data.branch;
3236
3287
  if (!branch) {
3237
3288
  ctx.data.commitResult = { committed: false, pushed: false };
3238
3289
  return;
3239
3290
  }
3291
+ const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
3292
+ const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
3293
+ if (sentinel && fs14.existsSync(sentinel)) {
3294
+ try {
3295
+ const replay = JSON.parse(fs14.readFileSync(sentinel, "utf-8"));
3296
+ ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
3297
+ if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
3298
+ if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
3299
+ if (replay.salvagedFromMissingMarker) ctx.data.salvagedFromMissingMarker = true;
3300
+ ctx.data.commitIdempotencyReplay = true;
3301
+ process.stderr.write(`[kody commitAndPush] idempotency replay (sentinel ${sentinel})
3302
+ `);
3303
+ return;
3304
+ } catch {
3305
+ }
3306
+ }
3240
3307
  const markerMissing = ctx.data.agentMarkerMissing === true;
3241
3308
  if (ctx.data.agentDone === false && !markerMissing) {
3242
3309
  ctx.data.commitResult = { committed: false, pushed: false, skippedReason: "agentDone=false" };
@@ -3248,12 +3315,12 @@ var commitAndPush2 = async (ctx) => {
3248
3315
  }
3249
3316
  const message = ctx.data.commitMessage || DEFAULT_COMMIT_MESSAGE;
3250
3317
  try {
3251
- const result = commitAndPush(branch, message, ctx.cwd);
3252
- ctx.data.commitResult = result;
3253
- const postCommitFiles = result.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
3318
+ const result2 = commitAndPush(branch, message, ctx.cwd);
3319
+ ctx.data.commitResult = result2;
3320
+ const postCommitFiles = result2.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
3254
3321
  ctx.data.changedFiles = postCommitFiles.filter((f) => !isForbiddenPath(f));
3255
- if (result.committed && !result.pushed) {
3256
- const reason = result.pushError ?? "push failed (no error detail)";
3322
+ if (result2.committed && !result2.pushed) {
3323
+ const reason = result2.pushError ?? "push failed (no error detail)";
3257
3324
  ctx.data.commitCrash = reason;
3258
3325
  if (ctx.output.exitCode === void 0 || ctx.output.exitCode === 0) {
3259
3326
  ctx.output.exitCode = 4;
@@ -3270,15 +3337,36 @@ var commitAndPush2 = async (ctx) => {
3270
3337
  `);
3271
3338
  }
3272
3339
  ctx.data.hasCommitsAhead = hasCommitsAhead(branch, ctx.config.git.defaultBranch, ctx.cwd);
3340
+ const result = ctx.data.commitResult;
3341
+ if (sentinel && result?.committed) {
3342
+ try {
3343
+ fs14.mkdirSync(path13.dirname(sentinel), { recursive: true });
3344
+ fs14.writeFileSync(
3345
+ sentinel,
3346
+ JSON.stringify(
3347
+ {
3348
+ commitResult: ctx.data.commitResult,
3349
+ changedFiles: ctx.data.changedFiles,
3350
+ hasCommitsAhead: ctx.data.hasCommitsAhead,
3351
+ salvagedFromMissingMarker: ctx.data.salvagedFromMissingMarker === true,
3352
+ writtenAt: (/* @__PURE__ */ new Date()).toISOString()
3353
+ },
3354
+ null,
3355
+ 2
3356
+ )
3357
+ );
3358
+ } catch {
3359
+ }
3360
+ }
3273
3361
  };
3274
3362
 
3275
3363
  // src/scripts/commitGoalState.ts
3276
3364
  import { execFileSync as execFileSync9 } from "child_process";
3277
- import * as path13 from "path";
3365
+ import * as path14 from "path";
3278
3366
  var commitGoalState = async (ctx) => {
3279
3367
  const goal = ctx.data.goal;
3280
3368
  if (!goal) return;
3281
- const stateRel = path13.posix.join(".kody", "goals", goal.id, "state.json");
3369
+ const stateRel = path14.posix.join(".kody", "goals", goal.id, "state.json");
3282
3370
  try {
3283
3371
  execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
3284
3372
  } catch (err) {
@@ -3322,20 +3410,20 @@ function describeCommitMessage(goal) {
3322
3410
  }
3323
3411
 
3324
3412
  // src/scripts/composePrompt.ts
3325
- import * as fs14 from "fs";
3326
- import * as path14 from "path";
3413
+ import * as fs15 from "fs";
3414
+ import * as path15 from "path";
3327
3415
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
3328
3416
  var composePrompt = async (ctx, profile) => {
3329
3417
  const explicit = ctx.data.promptTemplate;
3330
3418
  const mode = ctx.args.mode;
3331
3419
  const candidates = [
3332
- explicit ? path14.join(profile.dir, explicit) : null,
3333
- mode ? path14.join(profile.dir, "prompts", `${mode}.md`) : null,
3334
- path14.join(profile.dir, "prompt.md")
3420
+ explicit ? path15.join(profile.dir, explicit) : null,
3421
+ mode ? path15.join(profile.dir, "prompts", `${mode}.md`) : null,
3422
+ path15.join(profile.dir, "prompt.md")
3335
3423
  ].filter(Boolean);
3336
3424
  let templatePath = "";
3337
3425
  for (const c of candidates) {
3338
- if (fs14.existsSync(c)) {
3426
+ if (fs15.existsSync(c)) {
3339
3427
  templatePath = c;
3340
3428
  break;
3341
3429
  }
@@ -3343,7 +3431,7 @@ var composePrompt = async (ctx, profile) => {
3343
3431
  if (!templatePath) {
3344
3432
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
3345
3433
  }
3346
- const template = fs14.readFileSync(templatePath, "utf-8");
3434
+ const template = fs15.readFileSync(templatePath, "utf-8");
3347
3435
  const tokens = {
3348
3436
  ...stringifyAll(ctx.args, "args."),
3349
3437
  ...stringifyAll(ctx.data, ""),
@@ -3421,8 +3509,8 @@ function formatToolsUsage(profile) {
3421
3509
 
3422
3510
  // src/scripts/createQaGoal.ts
3423
3511
  import { execFileSync as execFileSync10 } from "child_process";
3424
- import * as fs15 from "fs";
3425
- import * as path15 from "path";
3512
+ import * as fs16 from "fs";
3513
+ import * as path16 from "path";
3426
3514
 
3427
3515
  // src/scripts/postReviewResult.ts
3428
3516
  function detectVerdict(body) {
@@ -3674,8 +3762,8 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
3674
3762
  return { number: Number(m[1]), created: true };
3675
3763
  }
3676
3764
  function writeStateFile(cwd, goalId, lastDispatchedIssue) {
3677
- const dir = path15.join(cwd, ".kody", "goals", goalId);
3678
- fs15.mkdirSync(dir, { recursive: true });
3765
+ const dir = path16.join(cwd, ".kody", "goals", goalId);
3766
+ fs16.mkdirSync(dir, { recursive: true });
3679
3767
  const state = {
3680
3768
  version: 1,
3681
3769
  state: "active",
@@ -3683,8 +3771,8 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
3683
3771
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3684
3772
  ...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
3685
3773
  };
3686
- const filePath = path15.join(dir, "state.json");
3687
- fs15.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
3774
+ const filePath = path16.join(dir, "state.json");
3775
+ fs16.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
3688
3776
  `);
3689
3777
  return filePath;
3690
3778
  }
@@ -4089,7 +4177,10 @@ function mergePrSquash(prNumber, cwd) {
4089
4177
  }
4090
4178
  function editPrBase(prNumber, baseBranch, cwd) {
4091
4179
  try {
4092
- gh(["pr", "edit", String(prNumber), "--base", baseBranch], { cwd });
4180
+ gh(
4181
+ ["api", "--method", "PATCH", `repos/{owner}/{repo}/pulls/${prNumber}`, "-f", `base=${baseBranch}`],
4182
+ { cwd }
4183
+ );
4093
4184
  return { ok: true };
4094
4185
  } catch (err) {
4095
4186
  return fail(err);
@@ -4174,15 +4265,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
4174
4265
 
4175
4266
  // src/scripts/diagMcp.ts
4176
4267
  import { execFileSync as execFileSync11 } from "child_process";
4177
- import * as fs16 from "fs";
4268
+ import * as fs17 from "fs";
4178
4269
  import * as os3 from "os";
4179
- import * as path16 from "path";
4270
+ import * as path17 from "path";
4180
4271
  var diagMcp = async (_ctx) => {
4181
4272
  const home = os3.homedir();
4182
- const cacheDir = path16.join(home, ".cache", "ms-playwright");
4273
+ const cacheDir = path17.join(home, ".cache", "ms-playwright");
4183
4274
  let entries = [];
4184
4275
  try {
4185
- entries = fs16.readdirSync(cacheDir);
4276
+ entries = fs17.readdirSync(cacheDir);
4186
4277
  } catch {
4187
4278
  }
4188
4279
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -4208,17 +4299,17 @@ var diagMcp = async (_ctx) => {
4208
4299
  };
4209
4300
 
4210
4301
  // src/scripts/discoverQaContext.ts
4211
- import * as fs18 from "fs";
4212
- import * as path18 from "path";
4302
+ import * as fs19 from "fs";
4303
+ import * as path19 from "path";
4213
4304
 
4214
4305
  // src/scripts/frameworkDetectors.ts
4215
- import * as fs17 from "fs";
4216
- import * as path17 from "path";
4306
+ import * as fs18 from "fs";
4307
+ import * as path18 from "path";
4217
4308
  function detectFrameworks(cwd) {
4218
4309
  const out = [];
4219
4310
  let deps = {};
4220
4311
  try {
4221
- const pkg = JSON.parse(fs17.readFileSync(path17.join(cwd, "package.json"), "utf-8"));
4312
+ const pkg = JSON.parse(fs18.readFileSync(path18.join(cwd, "package.json"), "utf-8"));
4222
4313
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
4223
4314
  } catch {
4224
4315
  return out;
@@ -4255,7 +4346,7 @@ function detectFrameworks(cwd) {
4255
4346
  }
4256
4347
  function findFile(cwd, candidates) {
4257
4348
  for (const c of candidates) {
4258
- if (fs17.existsSync(path17.join(cwd, c))) return c;
4349
+ if (fs18.existsSync(path18.join(cwd, c))) return c;
4259
4350
  }
4260
4351
  return null;
4261
4352
  }
@@ -4268,18 +4359,18 @@ var COLLECTION_DIRS = [
4268
4359
  function discoverPayloadCollections(cwd) {
4269
4360
  const out = [];
4270
4361
  for (const dir of COLLECTION_DIRS) {
4271
- const full = path17.join(cwd, dir);
4272
- if (!fs17.existsSync(full)) continue;
4362
+ const full = path18.join(cwd, dir);
4363
+ if (!fs18.existsSync(full)) continue;
4273
4364
  let files;
4274
4365
  try {
4275
- files = fs17.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4366
+ files = fs18.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4276
4367
  } catch {
4277
4368
  continue;
4278
4369
  }
4279
4370
  for (const file of files) {
4280
4371
  try {
4281
- const filePath = path17.join(full, file);
4282
- const content = fs17.readFileSync(filePath, "utf-8").slice(0, 1e4);
4372
+ const filePath = path18.join(full, file);
4373
+ const content = fs18.readFileSync(filePath, "utf-8").slice(0, 1e4);
4283
4374
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
4284
4375
  if (!slugMatch) continue;
4285
4376
  const slug = slugMatch[1];
@@ -4293,7 +4384,7 @@ function discoverPayloadCollections(cwd) {
4293
4384
  out.push({
4294
4385
  name,
4295
4386
  slug,
4296
- filePath: path17.relative(cwd, filePath),
4387
+ filePath: path18.relative(cwd, filePath),
4297
4388
  fields: fields.slice(0, 20),
4298
4389
  hasAdmin
4299
4390
  });
@@ -4307,28 +4398,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
4307
4398
  function discoverAdminComponents(cwd, collections) {
4308
4399
  const out = [];
4309
4400
  for (const dir of ADMIN_COMPONENT_DIRS) {
4310
- const full = path17.join(cwd, dir);
4311
- if (!fs17.existsSync(full)) continue;
4401
+ const full = path18.join(cwd, dir);
4402
+ if (!fs18.existsSync(full)) continue;
4312
4403
  let entries;
4313
4404
  try {
4314
- entries = fs17.readdirSync(full, { withFileTypes: true });
4405
+ entries = fs18.readdirSync(full, { withFileTypes: true });
4315
4406
  } catch {
4316
4407
  continue;
4317
4408
  }
4318
4409
  for (const entry of entries) {
4319
- const entryPath = path17.join(full, entry.name);
4410
+ const entryPath = path18.join(full, entry.name);
4320
4411
  let name;
4321
4412
  let filePath;
4322
4413
  if (entry.isDirectory()) {
4323
4414
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
4324
- (f) => fs17.existsSync(path17.join(entryPath, f))
4415
+ (f) => fs18.existsSync(path18.join(entryPath, f))
4325
4416
  );
4326
4417
  if (!indexFile) continue;
4327
4418
  name = entry.name;
4328
- filePath = path17.relative(cwd, path17.join(entryPath, indexFile));
4419
+ filePath = path18.relative(cwd, path18.join(entryPath, indexFile));
4329
4420
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
4330
4421
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
4331
- filePath = path17.relative(cwd, entryPath);
4422
+ filePath = path18.relative(cwd, entryPath);
4332
4423
  } else {
4333
4424
  continue;
4334
4425
  }
@@ -4336,7 +4427,7 @@ function discoverAdminComponents(cwd, collections) {
4336
4427
  if (collections) {
4337
4428
  for (const col of collections) {
4338
4429
  try {
4339
- const colContent = fs17.readFileSync(path17.join(cwd, col.filePath), "utf-8");
4430
+ const colContent = fs18.readFileSync(path18.join(cwd, col.filePath), "utf-8");
4340
4431
  if (colContent.includes(name)) {
4341
4432
  usedInCollection = col.slug;
4342
4433
  break;
@@ -4355,8 +4446,8 @@ function scanApiRoutes(cwd) {
4355
4446
  const out = [];
4356
4447
  const appDirs = ["src/app", "app"];
4357
4448
  for (const appDir of appDirs) {
4358
- const apiDir = path17.join(cwd, appDir, "api");
4359
- if (!fs17.existsSync(apiDir)) continue;
4449
+ const apiDir = path18.join(cwd, appDir, "api");
4450
+ if (!fs18.existsSync(apiDir)) continue;
4360
4451
  walkApiRoutes(apiDir, "/api", cwd, out);
4361
4452
  break;
4362
4453
  }
@@ -4365,14 +4456,14 @@ function scanApiRoutes(cwd) {
4365
4456
  function walkApiRoutes(dir, prefix, cwd, out) {
4366
4457
  let entries;
4367
4458
  try {
4368
- entries = fs17.readdirSync(dir, { withFileTypes: true });
4459
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
4369
4460
  } catch {
4370
4461
  return;
4371
4462
  }
4372
4463
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
4373
4464
  if (routeFile) {
4374
4465
  try {
4375
- const content = fs17.readFileSync(path17.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
4466
+ const content = fs18.readFileSync(path18.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
4376
4467
  const methods = HTTP_METHODS.filter(
4377
4468
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
4378
4469
  );
@@ -4380,7 +4471,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
4380
4471
  out.push({
4381
4472
  path: prefix,
4382
4473
  methods,
4383
- filePath: path17.relative(cwd, path17.join(dir, routeFile.name))
4474
+ filePath: path18.relative(cwd, path18.join(dir, routeFile.name))
4384
4475
  });
4385
4476
  }
4386
4477
  } catch {
@@ -4391,7 +4482,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
4391
4482
  if (entry.name === "node_modules" || entry.name === ".next") continue;
4392
4483
  let segment = entry.name;
4393
4484
  if (segment.startsWith("(") && segment.endsWith(")")) {
4394
- walkApiRoutes(path17.join(dir, entry.name), prefix, cwd, out);
4485
+ walkApiRoutes(path18.join(dir, entry.name), prefix, cwd, out);
4395
4486
  continue;
4396
4487
  }
4397
4488
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -4399,7 +4490,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
4399
4490
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
4400
4491
  segment = `:${segment.slice(1, -1)}`;
4401
4492
  }
4402
- walkApiRoutes(path17.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
4493
+ walkApiRoutes(path18.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
4403
4494
  }
4404
4495
  }
4405
4496
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -4419,10 +4510,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
4419
4510
  function scanEnvVars(cwd) {
4420
4511
  const candidates = [".env.example", ".env.local.example", ".env.template"];
4421
4512
  for (const envFile of candidates) {
4422
- const envPath = path17.join(cwd, envFile);
4423
- if (!fs17.existsSync(envPath)) continue;
4513
+ const envPath = path18.join(cwd, envFile);
4514
+ if (!fs18.existsSync(envPath)) continue;
4424
4515
  try {
4425
- const content = fs17.readFileSync(envPath, "utf-8");
4516
+ const content = fs18.readFileSync(envPath, "utf-8");
4426
4517
  const vars = [];
4427
4518
  for (const line of content.split("\n")) {
4428
4519
  const trimmed = line.trim();
@@ -4470,9 +4561,9 @@ function runQaDiscovery(cwd) {
4470
4561
  }
4471
4562
  function detectDevServer(cwd, out) {
4472
4563
  try {
4473
- const pkg = JSON.parse(fs18.readFileSync(path18.join(cwd, "package.json"), "utf-8"));
4564
+ const pkg = JSON.parse(fs19.readFileSync(path19.join(cwd, "package.json"), "utf-8"));
4474
4565
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
4475
- 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";
4566
+ 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";
4476
4567
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
4477
4568
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
4478
4569
  else if (allDeps.vite) out.devPort = 5173;
@@ -4482,8 +4573,8 @@ function detectDevServer(cwd, out) {
4482
4573
  function scanFrontendRoutes(cwd, out) {
4483
4574
  const appDirs = ["src/app", "app"];
4484
4575
  for (const appDir of appDirs) {
4485
- const full = path18.join(cwd, appDir);
4486
- if (!fs18.existsSync(full)) continue;
4576
+ const full = path19.join(cwd, appDir);
4577
+ if (!fs19.existsSync(full)) continue;
4487
4578
  walkFrontendRoutes(full, "", out);
4488
4579
  break;
4489
4580
  }
@@ -4491,7 +4582,7 @@ function scanFrontendRoutes(cwd, out) {
4491
4582
  function walkFrontendRoutes(dir, prefix, out) {
4492
4583
  let entries;
4493
4584
  try {
4494
- entries = fs18.readdirSync(dir, { withFileTypes: true });
4585
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
4495
4586
  } catch {
4496
4587
  return;
4497
4588
  }
@@ -4508,7 +4599,7 @@ function walkFrontendRoutes(dir, prefix, out) {
4508
4599
  if (entry.name === "node_modules" || entry.name === ".next") continue;
4509
4600
  let segment = entry.name;
4510
4601
  if (segment.startsWith("(") && segment.endsWith(")")) {
4511
- walkFrontendRoutes(path18.join(dir, entry.name), prefix, out);
4602
+ walkFrontendRoutes(path19.join(dir, entry.name), prefix, out);
4512
4603
  continue;
4513
4604
  }
4514
4605
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -4516,7 +4607,7 @@ function walkFrontendRoutes(dir, prefix, out) {
4516
4607
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
4517
4608
  segment = `:${segment.slice(1, -1)}`;
4518
4609
  }
4519
- walkFrontendRoutes(path18.join(dir, entry.name), `${prefix}/${segment}`, out);
4610
+ walkFrontendRoutes(path19.join(dir, entry.name), `${prefix}/${segment}`, out);
4520
4611
  }
4521
4612
  }
4522
4613
  function detectAuthFiles(cwd, out) {
@@ -4533,23 +4624,23 @@ function detectAuthFiles(cwd, out) {
4533
4624
  "src/app/api/oauth"
4534
4625
  ];
4535
4626
  for (const c of candidates) {
4536
- if (fs18.existsSync(path18.join(cwd, c))) out.authFiles.push(c);
4627
+ if (fs19.existsSync(path19.join(cwd, c))) out.authFiles.push(c);
4537
4628
  }
4538
4629
  }
4539
4630
  function detectRoles(cwd, out) {
4540
4631
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
4541
4632
  for (const rp of rolePaths) {
4542
- const dir = path18.join(cwd, rp);
4543
- if (!fs18.existsSync(dir)) continue;
4633
+ const dir = path19.join(cwd, rp);
4634
+ if (!fs19.existsSync(dir)) continue;
4544
4635
  let files;
4545
4636
  try {
4546
- files = fs18.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4637
+ files = fs19.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4547
4638
  } catch {
4548
4639
  continue;
4549
4640
  }
4550
4641
  for (const f of files) {
4551
4642
  try {
4552
- const content = fs18.readFileSync(path18.join(dir, f), "utf-8").slice(0, 5e3);
4643
+ const content = fs19.readFileSync(path19.join(dir, f), "utf-8").slice(0, 5e3);
4553
4644
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
4554
4645
  if (roleMatches) {
4555
4646
  for (const m of roleMatches) {
@@ -4782,8 +4873,8 @@ function failedAction3(reason) {
4782
4873
  }
4783
4874
 
4784
4875
  // src/scripts/dispatchJobFileTicks.ts
4785
- import * as fs20 from "fs";
4786
- import * as path20 from "path";
4876
+ import * as fs21 from "fs";
4877
+ import * as path21 from "path";
4787
4878
 
4788
4879
  // src/scripts/jobFrontmatter.ts
4789
4880
  var SCHEDULE_EVERY_VALUES = [
@@ -5034,8 +5125,8 @@ var ContentsApiBackend = class {
5034
5125
  };
5035
5126
 
5036
5127
  // src/scripts/jobState/localFileBackend.ts
5037
- import * as fs19 from "fs";
5038
- import * as path19 from "path";
5128
+ import * as fs20 from "fs";
5129
+ import * as path20 from "path";
5039
5130
  var LocalFileBackend = class {
5040
5131
  name = "local-file";
5041
5132
  cwd;
@@ -5050,7 +5141,7 @@ var LocalFileBackend = class {
5050
5141
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
5051
5142
  this.cwd = opts.cwd;
5052
5143
  this.jobsDir = opts.jobsDir;
5053
- this.absDir = path19.join(opts.cwd, opts.jobsDir);
5144
+ this.absDir = path20.join(opts.cwd, opts.jobsDir);
5054
5145
  this.owner = opts.owner;
5055
5146
  this.repo = opts.repo;
5056
5147
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -5065,7 +5156,7 @@ var LocalFileBackend = class {
5065
5156
  `);
5066
5157
  return;
5067
5158
  }
5068
- fs19.mkdirSync(this.absDir, { recursive: true });
5159
+ fs20.mkdirSync(this.absDir, { recursive: true });
5069
5160
  const prefix = this.cacheKeyPrefix();
5070
5161
  const probeKey = `${prefix}probe-${Date.now()}`;
5071
5162
  try {
@@ -5094,7 +5185,7 @@ var LocalFileBackend = class {
5094
5185
  `);
5095
5186
  return;
5096
5187
  }
5097
- if (!fs19.existsSync(this.absDir)) {
5188
+ if (!fs20.existsSync(this.absDir)) {
5098
5189
  return;
5099
5190
  }
5100
5191
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -5110,11 +5201,11 @@ var LocalFileBackend = class {
5110
5201
  }
5111
5202
  load(slug) {
5112
5203
  const relPath = stateFilePath(this.jobsDir, slug);
5113
- const absPath = path19.join(this.cwd, relPath);
5114
- if (!fs19.existsSync(absPath)) {
5204
+ const absPath = path20.join(this.cwd, relPath);
5205
+ if (!fs20.existsSync(absPath)) {
5115
5206
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
5116
5207
  }
5117
- const raw = fs19.readFileSync(absPath, "utf-8");
5208
+ const raw = fs20.readFileSync(absPath, "utf-8");
5118
5209
  let parsed;
5119
5210
  try {
5120
5211
  parsed = JSON.parse(raw);
@@ -5131,10 +5222,10 @@ var LocalFileBackend = class {
5131
5222
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
5132
5223
  return false;
5133
5224
  }
5134
- const absPath = path19.join(this.cwd, loaded.path);
5135
- fs19.mkdirSync(path19.dirname(absPath), { recursive: true });
5225
+ const absPath = path20.join(this.cwd, loaded.path);
5226
+ fs20.mkdirSync(path20.dirname(absPath), { recursive: true });
5136
5227
  const body = JSON.stringify(next, null, 2) + "\n";
5137
- fs19.writeFileSync(absPath, body, "utf-8");
5228
+ fs20.writeFileSync(absPath, body, "utf-8");
5138
5229
  return true;
5139
5230
  }
5140
5231
  cacheKeyPrefix() {
@@ -5211,7 +5302,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
5211
5302
  await backend.hydrate();
5212
5303
  }
5213
5304
  try {
5214
- const slugs = listJobSlugs(path20.join(ctx.cwd, jobsDir));
5305
+ const slugs = listJobSlugs(path21.join(ctx.cwd, jobsDir));
5215
5306
  ctx.data.jobSlugCount = slugs.length;
5216
5307
  if (slugs.length === 0) {
5217
5308
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -5310,17 +5401,17 @@ function formatAgo(ms) {
5310
5401
  }
5311
5402
  function readJobFrontmatter(cwd, jobsDir, slug) {
5312
5403
  try {
5313
- const raw = fs20.readFileSync(path20.join(cwd, jobsDir, `${slug}.md`), "utf-8");
5404
+ const raw = fs21.readFileSync(path21.join(cwd, jobsDir, `${slug}.md`), "utf-8");
5314
5405
  return splitFrontmatter(raw).frontmatter;
5315
5406
  } catch {
5316
5407
  return {};
5317
5408
  }
5318
5409
  }
5319
5410
  function listJobSlugs(absDir) {
5320
- if (!fs20.existsSync(absDir)) return [];
5411
+ if (!fs21.existsSync(absDir)) return [];
5321
5412
  let entries;
5322
5413
  try {
5323
- entries = fs20.readdirSync(absDir, { withFileTypes: true });
5414
+ entries = fs21.readdirSync(absDir, { withFileTypes: true });
5324
5415
  } catch {
5325
5416
  return [];
5326
5417
  }
@@ -5687,54 +5778,61 @@ var finalizeGoal = async (ctx) => {
5687
5778
  if (!goal) return;
5688
5779
  process.stdout.write(`[goal-tick] all task(s) done \u2014 finalising goal ${goal.id}
5689
5780
  `);
5690
- const taskPrs = goal.openTaskPrs ?? [];
5691
- if (taskPrs.length === 0) {
5692
- process.stdout.write(`[goal-tick] no open task PRs \u2014 marking goal done without merge
5781
+ const leaf = goal.leafPr;
5782
+ if (!leaf) {
5783
+ process.stdout.write(`[goal-tick] no leaf PR \u2014 marking goal done without merge
5693
5784
  `);
5694
5785
  goal.state = "done";
5695
5786
  return;
5696
5787
  }
5697
- const ordered = [...taskPrs].sort((a, b) => extractIssueNumber(a) - extractIssueNumber(b));
5698
- for (const pr of ordered) {
5699
- if (pr.baseRefName === goal.defaultBranch) continue;
5788
+ if (leaf.baseRefName !== goal.defaultBranch) {
5700
5789
  process.stdout.write(
5701
- `[goal-tick] retargeting PR #${pr.number} base ${pr.baseRefName} \u2192 ${goal.defaultBranch}
5790
+ `[goal-tick] retargeting leaf PR #${leaf.number} base ${leaf.baseRefName} \u2192 ${goal.defaultBranch}
5702
5791
  `
5703
5792
  );
5704
- const retarget = editPrBase(pr.number, goal.defaultBranch, ctx.cwd);
5793
+ const retarget = editPrBase(leaf.number, goal.defaultBranch, ctx.cwd);
5705
5794
  if (!retarget.ok) {
5706
- process.stderr.write(`[goal-tick] finalizeGoal: editPrBase #${pr.number} failed: ${retarget.error}
5795
+ process.stderr.write(`[goal-tick] finalizeGoal: editPrBase #${leaf.number} failed: ${retarget.error}
5707
5796
  `);
5708
5797
  return;
5709
5798
  }
5710
5799
  }
5711
- for (const pr of ordered) {
5712
- if (pr.isDraft) {
5713
- process.stdout.write(`[goal-tick] promoting draft PR #${pr.number} \u2192 ready
5800
+ if (leaf.isDraft) {
5801
+ process.stdout.write(`[goal-tick] promoting draft leaf PR #${leaf.number} \u2192 ready
5714
5802
  `);
5715
- const ready = markPrReady(pr.number, ctx.cwd);
5716
- if (!ready.ok) {
5717
- process.stderr.write(`[goal-tick] finalizeGoal: markPrReady #${pr.number} failed: ${ready.error}
5803
+ const ready = markPrReady(leaf.number, ctx.cwd);
5804
+ if (!ready.ok) {
5805
+ process.stderr.write(`[goal-tick] finalizeGoal: markPrReady #${leaf.number} failed: ${ready.error}
5718
5806
  `);
5719
- return;
5720
- }
5807
+ return;
5721
5808
  }
5722
- process.stdout.write(`[goal-tick] squash-merging PR #${pr.number} \u2192 ${goal.defaultBranch} (head=${pr.headRefName})
5809
+ }
5810
+ process.stdout.write(
5811
+ `[goal-tick] squash-merging leaf PR #${leaf.number} \u2192 ${goal.defaultBranch} (cumulative goal diff)
5812
+ `
5813
+ );
5814
+ const merged = mergePrSquash(leaf.number, ctx.cwd);
5815
+ if (!merged.ok) {
5816
+ process.stderr.write(`[goal-tick] finalizeGoal: mergePrSquash #${leaf.number} failed: ${merged.error}
5723
5817
  `);
5724
- const merged = mergePrSquash(pr.number, ctx.cwd);
5725
- if (!merged.ok) {
5726
- process.stderr.write(`[goal-tick] finalizeGoal: mergePrSquash #${pr.number} failed: ${merged.error}
5818
+ return;
5819
+ }
5820
+ const others = (goal.openTaskPrs ?? []).filter((p) => p.number !== leaf.number);
5821
+ for (const pr of others) {
5822
+ process.stdout.write(`[goal-tick] closing intermediate stacked PR #${pr.number} (subsumed by leaf merge)
5823
+ `);
5824
+ const closed = closePr(
5825
+ pr.number,
5826
+ `_Stacked-PR finalize: this PR's content is included in the leaf squash to \`${goal.defaultBranch}\` (#${leaf.number})._`,
5827
+ ctx.cwd
5828
+ );
5829
+ if (!closed.ok) {
5830
+ process.stderr.write(`[goal-tick] finalizeGoal: closePr #${pr.number} failed: ${closed.error}
5727
5831
  `);
5728
- return;
5729
5832
  }
5730
5833
  }
5731
5834
  goal.state = "done";
5732
5835
  };
5733
- function extractIssueNumber(pr) {
5734
- const m = pr.headRefName.match(/^(\d+)-/);
5735
- if (m?.[1]) return Number.parseInt(m[1], 10);
5736
- return pr.number;
5737
- }
5738
5836
 
5739
5837
  // src/scripts/finishFlow.ts
5740
5838
  import { execFileSync as execFileSync14 } from "child_process";
@@ -5965,7 +6063,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
5965
6063
 
5966
6064
  // src/gha.ts
5967
6065
  import { execFileSync as execFileSync16 } from "child_process";
5968
- import * as fs21 from "fs";
6066
+ import * as fs22 from "fs";
5969
6067
  function getRunUrl() {
5970
6068
  const server = process.env.GITHUB_SERVER_URL;
5971
6069
  const repo = process.env.GITHUB_REPOSITORY;
@@ -5976,10 +6074,10 @@ function getRunUrl() {
5976
6074
  function reactToTriggerComment(cwd) {
5977
6075
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
5978
6076
  const eventPath = process.env.GITHUB_EVENT_PATH;
5979
- if (!eventPath || !fs21.existsSync(eventPath)) return;
6077
+ if (!eventPath || !fs22.existsSync(eventPath)) return;
5980
6078
  let event = null;
5981
6079
  try {
5982
- event = JSON.parse(fs21.readFileSync(eventPath, "utf-8"));
6080
+ event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
5983
6081
  } catch {
5984
6082
  return;
5985
6083
  }
@@ -6264,22 +6362,22 @@ var handleAbandonedGoal = async (ctx) => {
6264
6362
 
6265
6363
  // src/scripts/initFlow.ts
6266
6364
  import { execFileSync as execFileSync18 } from "child_process";
6267
- import * as fs23 from "fs";
6268
- import * as path22 from "path";
6365
+ import * as fs24 from "fs";
6366
+ import * as path23 from "path";
6269
6367
 
6270
6368
  // src/scripts/loadQaGuide.ts
6271
- import * as fs22 from "fs";
6272
- import * as path21 from "path";
6369
+ import * as fs23 from "fs";
6370
+ import * as path22 from "path";
6273
6371
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
6274
6372
  var loadQaGuide = async (ctx) => {
6275
- const full = path21.join(ctx.cwd, QA_GUIDE_REL_PATH);
6276
- if (!fs22.existsSync(full)) {
6373
+ const full = path22.join(ctx.cwd, QA_GUIDE_REL_PATH);
6374
+ if (!fs23.existsSync(full)) {
6277
6375
  ctx.data.qaGuide = "";
6278
6376
  ctx.data.qaGuidePath = "";
6279
6377
  return;
6280
6378
  }
6281
6379
  try {
6282
- ctx.data.qaGuide = fs22.readFileSync(full, "utf-8");
6380
+ ctx.data.qaGuide = fs23.readFileSync(full, "utf-8");
6283
6381
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
6284
6382
  } catch {
6285
6383
  ctx.data.qaGuide = "";
@@ -6289,9 +6387,9 @@ var loadQaGuide = async (ctx) => {
6289
6387
 
6290
6388
  // src/scripts/initFlow.ts
6291
6389
  function detectPackageManager(cwd) {
6292
- if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
6293
- if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) return "yarn";
6294
- if (fs23.existsSync(path22.join(cwd, "bun.lockb"))) return "bun";
6390
+ if (fs24.existsSync(path23.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
6391
+ if (fs24.existsSync(path23.join(cwd, "yarn.lock"))) return "yarn";
6392
+ if (fs24.existsSync(path23.join(cwd, "bun.lockb"))) return "bun";
6295
6393
  return "npm";
6296
6394
  }
6297
6395
  function qualityCommandsFor(pm) {
@@ -6413,48 +6511,48 @@ function performInit(cwd, force) {
6413
6511
  const pm = detectPackageManager(cwd);
6414
6512
  const ownerRepo = detectOwnerRepo(cwd);
6415
6513
  const defaultBranch = defaultBranchFromGit(cwd);
6416
- const configPath = path22.join(cwd, "kody.config.json");
6417
- if (fs23.existsSync(configPath) && !force) {
6514
+ const configPath = path23.join(cwd, "kody.config.json");
6515
+ if (fs24.existsSync(configPath) && !force) {
6418
6516
  skipped.push("kody.config.json");
6419
6517
  } else {
6420
6518
  const cfg = makeConfig(pm, ownerRepo, defaultBranch);
6421
- fs23.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
6519
+ fs24.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
6422
6520
  `);
6423
6521
  wrote.push("kody.config.json");
6424
6522
  }
6425
- const workflowDir = path22.join(cwd, ".github", "workflows");
6426
- const workflowPath = path22.join(workflowDir, "kody.yml");
6427
- if (fs23.existsSync(workflowPath) && !force) {
6523
+ const workflowDir = path23.join(cwd, ".github", "workflows");
6524
+ const workflowPath = path23.join(workflowDir, "kody.yml");
6525
+ if (fs24.existsSync(workflowPath) && !force) {
6428
6526
  skipped.push(".github/workflows/kody.yml");
6429
6527
  } else {
6430
- fs23.mkdirSync(workflowDir, { recursive: true });
6431
- fs23.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
6528
+ fs24.mkdirSync(workflowDir, { recursive: true });
6529
+ fs24.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
6432
6530
  wrote.push(".github/workflows/kody.yml");
6433
6531
  }
6434
- const hasUi = fs23.existsSync(path22.join(cwd, "src/app")) || fs23.existsSync(path22.join(cwd, "app")) || fs23.existsSync(path22.join(cwd, "pages"));
6532
+ const hasUi = fs24.existsSync(path23.join(cwd, "src/app")) || fs24.existsSync(path23.join(cwd, "app")) || fs24.existsSync(path23.join(cwd, "pages"));
6435
6533
  if (hasUi) {
6436
- const qaGuidePath = path22.join(cwd, QA_GUIDE_REL_PATH);
6437
- if (fs23.existsSync(qaGuidePath) && !force) {
6534
+ const qaGuidePath = path23.join(cwd, QA_GUIDE_REL_PATH);
6535
+ if (fs24.existsSync(qaGuidePath) && !force) {
6438
6536
  skipped.push(QA_GUIDE_REL_PATH);
6439
6537
  } else {
6440
- fs23.mkdirSync(path22.dirname(qaGuidePath), { recursive: true });
6538
+ fs24.mkdirSync(path23.dirname(qaGuidePath), { recursive: true });
6441
6539
  const discovery = runQaDiscovery(cwd);
6442
- fs23.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
6540
+ fs24.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
6443
6541
  wrote.push(QA_GUIDE_REL_PATH);
6444
6542
  }
6445
6543
  }
6446
6544
  const builtinJobs = listBuiltinJobs();
6447
6545
  if (builtinJobs.length > 0) {
6448
- const jobsDir = path22.join(cwd, ".kody", "jobs");
6449
- fs23.mkdirSync(jobsDir, { recursive: true });
6546
+ const jobsDir = path23.join(cwd, ".kody", "jobs");
6547
+ fs24.mkdirSync(jobsDir, { recursive: true });
6450
6548
  for (const job of builtinJobs) {
6451
- const rel = path22.join(".kody", "jobs", `${job.slug}.md`);
6452
- const target = path22.join(cwd, rel);
6453
- if (fs23.existsSync(target) && !force) {
6549
+ const rel = path23.join(".kody", "jobs", `${job.slug}.md`);
6550
+ const target = path23.join(cwd, rel);
6551
+ if (fs24.existsSync(target) && !force) {
6454
6552
  skipped.push(rel);
6455
6553
  continue;
6456
6554
  }
6457
- fs23.writeFileSync(target, fs23.readFileSync(job.filePath, "utf-8"));
6555
+ fs24.writeFileSync(target, fs24.readFileSync(job.filePath, "utf-8"));
6458
6556
  wrote.push(rel);
6459
6557
  }
6460
6558
  }
@@ -6466,12 +6564,12 @@ function performInit(cwd, force) {
6466
6564
  continue;
6467
6565
  }
6468
6566
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
6469
- const target = path22.join(workflowDir, `kody-${exe.name}.yml`);
6470
- if (fs23.existsSync(target) && !force) {
6567
+ const target = path23.join(workflowDir, `kody-${exe.name}.yml`);
6568
+ if (fs24.existsSync(target) && !force) {
6471
6569
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
6472
6570
  continue;
6473
6571
  }
6474
- fs23.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
6572
+ fs24.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
6475
6573
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
6476
6574
  }
6477
6575
  let labels;
@@ -6560,14 +6658,14 @@ var loadCoverageRules = async (ctx) => {
6560
6658
  };
6561
6659
 
6562
6660
  // src/goal/state.ts
6563
- import * as fs24 from "fs";
6564
- import * as path23 from "path";
6661
+ import * as fs25 from "fs";
6662
+ import * as path24 from "path";
6565
6663
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
6566
6664
  var GoalStateError = class extends Error {
6567
- constructor(path31, message) {
6568
- super(`Invalid goal state at ${path31}:
6665
+ constructor(path32, message) {
6666
+ super(`Invalid goal state at ${path32}:
6569
6667
  ${message}`);
6570
- this.path = path31;
6668
+ this.path = path32;
6571
6669
  this.name = "GoalStateError";
6572
6670
  }
6573
6671
  path;
@@ -6611,16 +6709,16 @@ function serializeGoalState(s) {
6611
6709
  `;
6612
6710
  }
6613
6711
  function goalStatePath(cwd, goalId) {
6614
- return path23.join(cwd, ".kody", "goals", goalId, "state.json");
6712
+ return path24.join(cwd, ".kody", "goals", goalId, "state.json");
6615
6713
  }
6616
6714
  function readGoalState(cwd, goalId) {
6617
6715
  const file = goalStatePath(cwd, goalId);
6618
- if (!fs24.existsSync(file)) {
6716
+ if (!fs25.existsSync(file)) {
6619
6717
  throw new GoalStateError(file, "file not found");
6620
6718
  }
6621
6719
  let raw;
6622
6720
  try {
6623
- raw = JSON.parse(fs24.readFileSync(file, "utf-8"));
6721
+ raw = JSON.parse(fs25.readFileSync(file, "utf-8"));
6624
6722
  } catch (err) {
6625
6723
  throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
6626
6724
  }
@@ -6628,8 +6726,8 @@ function readGoalState(cwd, goalId) {
6628
6726
  }
6629
6727
  function writeGoalState(cwd, goalId, state) {
6630
6728
  const file = goalStatePath(cwd, goalId);
6631
- fs24.mkdirSync(path23.dirname(file), { recursive: true });
6632
- fs24.writeFileSync(file, serializeGoalState(state), "utf-8");
6729
+ fs25.mkdirSync(path24.dirname(file), { recursive: true });
6730
+ fs25.writeFileSync(file, serializeGoalState(state), "utf-8");
6633
6731
  }
6634
6732
  function nowIso() {
6635
6733
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -6723,8 +6821,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
6723
6821
  };
6724
6822
 
6725
6823
  // src/scripts/loadJobFromFile.ts
6726
- import * as fs25 from "fs";
6727
- import * as path24 from "path";
6824
+ import * as fs26 from "fs";
6825
+ import * as path25 from "path";
6728
6826
  var loadJobFromFile = async (ctx, _profile, args) => {
6729
6827
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
6730
6828
  const slugArg = String(args?.slugArg ?? "job");
@@ -6732,11 +6830,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
6732
6830
  if (!slug) {
6733
6831
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
6734
6832
  }
6735
- const absPath = path24.join(ctx.cwd, jobsDir, `${slug}.md`);
6736
- if (!fs25.existsSync(absPath)) {
6833
+ const absPath = path25.join(ctx.cwd, jobsDir, `${slug}.md`);
6834
+ if (!fs26.existsSync(absPath)) {
6737
6835
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
6738
6836
  }
6739
- const raw = fs25.readFileSync(absPath, "utf-8");
6837
+ const raw = fs26.readFileSync(absPath, "utf-8");
6740
6838
  const { title, body } = parseJobFile(raw, slug);
6741
6839
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
6742
6840
  const loaded = await backend.load(slug);
@@ -6768,16 +6866,16 @@ function humanizeSlug(slug) {
6768
6866
  }
6769
6867
 
6770
6868
  // src/scripts/loadMemoryContext.ts
6771
- import * as fs26 from "fs";
6772
- import * as path25 from "path";
6869
+ import * as fs27 from "fs";
6870
+ import * as path26 from "path";
6773
6871
  var MEMORY_DIR_RELATIVE = ".kody/memory";
6774
6872
  var MAX_PAGES = 8;
6775
6873
  var PER_PAGE_MAX_BYTES = 4e3;
6776
6874
  var TOTAL_MAX_BYTES = 24e3;
6777
6875
  var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
6778
6876
  var loadMemoryContext = async (ctx) => {
6779
- const memoryAbs = path25.join(ctx.cwd, MEMORY_DIR_RELATIVE);
6780
- if (!fs26.existsSync(memoryAbs)) {
6877
+ const memoryAbs = path26.join(ctx.cwd, MEMORY_DIR_RELATIVE);
6878
+ if (!fs27.existsSync(memoryAbs)) {
6781
6879
  ctx.data.memoryContext = "";
6782
6880
  return;
6783
6881
  }
@@ -6802,21 +6900,21 @@ function collectPages(memoryAbs) {
6802
6900
  walkMd(memoryAbs, (file) => {
6803
6901
  let stat;
6804
6902
  try {
6805
- stat = fs26.statSync(file);
6903
+ stat = fs27.statSync(file);
6806
6904
  } catch {
6807
6905
  return;
6808
6906
  }
6809
6907
  let raw;
6810
6908
  try {
6811
- raw = fs26.readFileSync(file, "utf-8");
6909
+ raw = fs27.readFileSync(file, "utf-8");
6812
6910
  } catch {
6813
6911
  return;
6814
6912
  }
6815
6913
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
6816
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path25.basename(file, ".md");
6914
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path26.basename(file, ".md");
6817
6915
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
6818
6916
  out.push({
6819
- relPath: path25.relative(memoryAbs, file),
6917
+ relPath: path26.relative(memoryAbs, file),
6820
6918
  title,
6821
6919
  updated,
6822
6920
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
@@ -6884,16 +6982,16 @@ function walkMd(root, visit) {
6884
6982
  const dir = stack.pop();
6885
6983
  let names;
6886
6984
  try {
6887
- names = fs26.readdirSync(dir);
6985
+ names = fs27.readdirSync(dir);
6888
6986
  } catch {
6889
6987
  continue;
6890
6988
  }
6891
6989
  for (const name of names) {
6892
6990
  if (name.startsWith(".")) continue;
6893
- const full = path25.join(dir, name);
6991
+ const full = path26.join(dir, name);
6894
6992
  let stat;
6895
6993
  try {
6896
- stat = fs26.statSync(full);
6994
+ stat = fs27.statSync(full);
6897
6995
  } catch {
6898
6996
  continue;
6899
6997
  }
@@ -7001,8 +7099,8 @@ function formatReviewComments(raw) {
7001
7099
  init_events();
7002
7100
 
7003
7101
  // src/taskContext.ts
7004
- import * as fs27 from "fs";
7005
- import * as path26 from "path";
7102
+ import * as fs28 from "fs";
7103
+ import * as path27 from "path";
7006
7104
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
7007
7105
  function buildTaskContext(args) {
7008
7106
  return {
@@ -7018,10 +7116,10 @@ function buildTaskContext(args) {
7018
7116
  }
7019
7117
  function persistTaskContext(cwd, ctx) {
7020
7118
  try {
7021
- const dir = path26.join(cwd, ".kody", "runs", ctx.runId);
7022
- fs27.mkdirSync(dir, { recursive: true });
7023
- const file = path26.join(dir, "task-context.json");
7024
- fs27.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
7119
+ const dir = path27.join(cwd, ".kody", "runs", ctx.runId);
7120
+ fs28.mkdirSync(dir, { recursive: true });
7121
+ const file = path27.join(dir, "task-context.json");
7122
+ fs28.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
7025
7123
  `);
7026
7124
  return file;
7027
7125
  } catch (err) {
@@ -8376,8 +8474,8 @@ function resolveBaseOverride(value) {
8376
8474
 
8377
8475
  // src/scripts/runTickScript.ts
8378
8476
  import { spawnSync } from "child_process";
8379
- import * as fs28 from "fs";
8380
- import * as path27 from "path";
8477
+ import * as fs29 from "fs";
8478
+ import * as path28 from "path";
8381
8479
  var runTickScript = async (ctx, _profile, args) => {
8382
8480
  ctx.skipAgent = true;
8383
8481
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
@@ -8389,13 +8487,13 @@ var runTickScript = async (ctx, _profile, args) => {
8389
8487
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
8390
8488
  return;
8391
8489
  }
8392
- const jobPath = path27.join(ctx.cwd, jobsDir, `${slug}.md`);
8393
- if (!fs28.existsSync(jobPath)) {
8490
+ const jobPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
8491
+ if (!fs29.existsSync(jobPath)) {
8394
8492
  ctx.output.exitCode = 99;
8395
8493
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
8396
8494
  return;
8397
8495
  }
8398
- const raw = fs28.readFileSync(jobPath, "utf-8");
8496
+ const raw = fs29.readFileSync(jobPath, "utf-8");
8399
8497
  const { frontmatter } = splitFrontmatter(raw);
8400
8498
  const tickScript = frontmatter.tickScript;
8401
8499
  if (!tickScript) {
@@ -8403,8 +8501,8 @@ var runTickScript = async (ctx, _profile, args) => {
8403
8501
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
8404
8502
  return;
8405
8503
  }
8406
- const scriptPath = path27.isAbsolute(tickScript) ? tickScript : path27.join(ctx.cwd, tickScript);
8407
- if (!fs28.existsSync(scriptPath)) {
8504
+ const scriptPath = path28.isAbsolute(tickScript) ? tickScript : path28.join(ctx.cwd, tickScript);
8505
+ if (!fs29.existsSync(scriptPath)) {
8408
8506
  ctx.output.exitCode = 99;
8409
8507
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
8410
8508
  return;
@@ -9239,7 +9337,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
9239
9337
  };
9240
9338
 
9241
9339
  // src/scripts/writeRunSummary.ts
9242
- import * as fs29 from "fs";
9340
+ import * as fs30 from "fs";
9243
9341
  var writeRunSummary = async (ctx, profile) => {
9244
9342
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
9245
9343
  if (!summaryPath) return;
@@ -9261,7 +9359,7 @@ var writeRunSummary = async (ctx, profile) => {
9261
9359
  if (reason) lines.push(`- **Reason:** ${reason}`);
9262
9360
  lines.push("");
9263
9361
  try {
9264
- fs29.appendFileSync(summaryPath, `${lines.join("\n")}
9362
+ fs30.appendFileSync(summaryPath, `${lines.join("\n")}
9265
9363
  `);
9266
9364
  } catch {
9267
9365
  }
@@ -9478,9 +9576,9 @@ async function runExecutable(profileName, input) {
9478
9576
  data: {},
9479
9577
  output: { exitCode: 0 }
9480
9578
  };
9481
- const ndjsonDir = path28.join(input.cwd, ".kody");
9579
+ const ndjsonDir = path29.join(input.cwd, ".kody");
9482
9580
  const invokeAgent = async (prompt) => {
9483
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path28.isAbsolute(p) ? p : path28.resolve(profile.dir, p)).filter((p) => p.length > 0);
9581
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path29.isAbsolute(p) ? p : path29.resolve(profile.dir, p)).filter((p) => p.length > 0);
9484
9582
  const syntheticPath = ctx.data.syntheticPluginPath;
9485
9583
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
9486
9584
  return runAgent({
@@ -9607,6 +9705,33 @@ async function runExecutable(profileName, input) {
9607
9705
  const msg = err instanceof Error ? err.message : String(err);
9608
9706
  process.stderr.write(`[kody] postflight "${label}" crashed: ${msg}
9609
9707
  `);
9708
+ try {
9709
+ const fsMod = await import("fs");
9710
+ const pathMod = await import("path");
9711
+ const { resolveRunId: resolveRunId2 } = await Promise.resolve().then(() => (init_events(), events_exports));
9712
+ const runId = resolveRunId2();
9713
+ const dir = pathMod.join(input.cwd, ".kody", "runs", runId, "crashes");
9714
+ fsMod.mkdirSync(dir, { recursive: true });
9715
+ const file = pathMod.join(
9716
+ dir,
9717
+ `${label.replace(/[^a-zA-Z0-9_-]/g, "_")}-${Date.now()}.json`
9718
+ );
9719
+ fsMod.writeFileSync(
9720
+ file,
9721
+ JSON.stringify(
9722
+ {
9723
+ executable: profileName,
9724
+ postflight: label,
9725
+ message: msg,
9726
+ stack: err instanceof Error ? err.stack : void 0,
9727
+ ts: (/* @__PURE__ */ new Date()).toISOString()
9728
+ },
9729
+ null,
9730
+ 2
9731
+ )
9732
+ );
9733
+ } catch {
9734
+ }
9610
9735
  const summary = `postflight ${label} crashed: ${msg}`;
9611
9736
  ctx.output.reason = ctx.output.reason ? `${ctx.output.reason}; ${summary}` : summary;
9612
9737
  if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
@@ -9648,7 +9773,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
9648
9773
  function getProfileInputsForChild(profileName, _cwd) {
9649
9774
  try {
9650
9775
  const profilePath = resolveProfilePath(profileName);
9651
- if (!fs30.existsSync(profilePath)) return null;
9776
+ if (!fs31.existsSync(profilePath)) return null;
9652
9777
  return loadProfile(profilePath).inputs;
9653
9778
  } catch {
9654
9779
  return null;
@@ -9657,17 +9782,17 @@ function getProfileInputsForChild(profileName, _cwd) {
9657
9782
  function resolveProfilePath(profileName) {
9658
9783
  const found = resolveExecutable(profileName);
9659
9784
  if (found) return found;
9660
- const here = path28.dirname(new URL(import.meta.url).pathname);
9785
+ const here = path29.dirname(new URL(import.meta.url).pathname);
9661
9786
  const candidates = [
9662
- path28.join(here, "executables", profileName, "profile.json"),
9787
+ path29.join(here, "executables", profileName, "profile.json"),
9663
9788
  // same-dir sibling (dev)
9664
- path28.join(here, "..", "executables", profileName, "profile.json"),
9789
+ path29.join(here, "..", "executables", profileName, "profile.json"),
9665
9790
  // up one (prod: dist/bin → dist/executables)
9666
- path28.join(here, "..", "src", "executables", profileName, "profile.json")
9791
+ path29.join(here, "..", "src", "executables", profileName, "profile.json")
9667
9792
  // fallback
9668
9793
  ];
9669
9794
  for (const c of candidates) {
9670
- if (fs30.existsSync(c)) return c;
9795
+ if (fs31.existsSync(c)) return c;
9671
9796
  }
9672
9797
  return candidates[0];
9673
9798
  }
@@ -9764,8 +9889,8 @@ function resolveShellTimeoutMs(entry) {
9764
9889
  var SIGKILL_GRACE_MS = 5e3;
9765
9890
  async function runShellEntry(entry, ctx, profile) {
9766
9891
  const shellName = entry.shell;
9767
- const shellPath = path28.join(profile.dir, shellName);
9768
- if (!fs30.existsSync(shellPath)) {
9892
+ const shellPath = path29.join(profile.dir, shellName);
9893
+ if (!fs31.existsSync(shellPath)) {
9769
9894
  ctx.skipAgent = true;
9770
9895
  ctx.output.exitCode = 99;
9771
9896
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -9900,7 +10025,12 @@ async function runContainerLoop(profile, ctx, input) {
9900
10025
  const child = children[currentIdx];
9901
10026
  process.stderr.write(`[kody container] step ${iteration}: invoking ${child.exec}
9902
10027
  `);
9903
- resetWorkingTree(input.cwd);
10028
+ if (profile.resetBetweenChildren !== false) {
10029
+ resetWorkingTree(input.cwd);
10030
+ } else {
10031
+ process.stderr.write(`[kody container] resetBetweenChildren=false; preserving tracked tree
10032
+ `);
10033
+ }
9904
10034
  const priorState = readContainerState(ctx, child, reader);
9905
10035
  if (priorState.core?.prUrl) knownPrUrl = priorState.core.prUrl;
9906
10036
  const priorAction = priorState.executables?.[child.exec]?.lastAction;
@@ -10210,9 +10340,9 @@ function resolveAuthToken(env = process.env) {
10210
10340
  return token;
10211
10341
  }
10212
10342
  function detectPackageManager2(cwd) {
10213
- if (fs31.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
10214
- if (fs31.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
10215
- if (fs31.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
10343
+ if (fs32.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
10344
+ if (fs32.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
10345
+ if (fs32.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
10216
10346
  return "npm";
10217
10347
  }
10218
10348
  function shellOut(cmd, args, cwd, stream = true) {
@@ -10299,11 +10429,11 @@ function configureGitIdentity(cwd) {
10299
10429
  }
10300
10430
  function postFailureTail(issueNumber, cwd, reason) {
10301
10431
  if (!issueNumber) return;
10302
- const logPath = path29.join(cwd, ".kody", "last-run.jsonl");
10432
+ const logPath = path30.join(cwd, ".kody", "last-run.jsonl");
10303
10433
  let tail = "";
10304
10434
  try {
10305
- if (fs31.existsSync(logPath)) {
10306
- const content = fs31.readFileSync(logPath, "utf-8");
10435
+ if (fs32.existsSync(logPath)) {
10436
+ const content = fs32.readFileSync(logPath, "utf-8");
10307
10437
  tail = content.slice(-3e3);
10308
10438
  }
10309
10439
  } catch {
@@ -10328,7 +10458,7 @@ async function runCi(argv) {
10328
10458
  return 0;
10329
10459
  }
10330
10460
  const args = parseCiArgs(argv);
10331
- const cwd = args.cwd ? path29.resolve(args.cwd) : process.cwd();
10461
+ const cwd = args.cwd ? path30.resolve(args.cwd) : process.cwd();
10332
10462
  let earlyConfig;
10333
10463
  try {
10334
10464
  earlyConfig = loadConfig(cwd);
@@ -10338,9 +10468,9 @@ async function runCi(argv) {
10338
10468
  const eventName = process.env.GITHUB_EVENT_NAME;
10339
10469
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
10340
10470
  let manualWorkflowDispatch = false;
10341
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs31.existsSync(dispatchEventPath)) {
10471
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs32.existsSync(dispatchEventPath)) {
10342
10472
  try {
10343
- const evt = JSON.parse(fs31.readFileSync(dispatchEventPath, "utf-8"));
10473
+ const evt = JSON.parse(fs32.readFileSync(dispatchEventPath, "utf-8"));
10344
10474
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
10345
10475
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
10346
10476
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -10599,9 +10729,9 @@ function parseChatArgs(argv, env = process.env) {
10599
10729
  return result;
10600
10730
  }
10601
10731
  function commitChatFiles(cwd, sessionId, verbose) {
10602
- const sessionFile = path30.relative(cwd, sessionFilePath(cwd, sessionId));
10603
- const eventsFile = path30.relative(cwd, eventsFilePath(cwd, sessionId));
10604
- const paths = [sessionFile, eventsFile].filter((p) => fs32.existsSync(path30.join(cwd, p)));
10732
+ const sessionFile = path31.relative(cwd, sessionFilePath(cwd, sessionId));
10733
+ const eventsFile = path31.relative(cwd, eventsFilePath(cwd, sessionId));
10734
+ const paths = [sessionFile, eventsFile].filter((p) => fs33.existsSync(path31.join(cwd, p)));
10605
10735
  if (paths.length === 0) return;
10606
10736
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
10607
10737
  try {
@@ -10639,7 +10769,7 @@ async function runChat(argv) {
10639
10769
  ${CHAT_HELP}`);
10640
10770
  return 64;
10641
10771
  }
10642
- const cwd = args.cwd ? path30.resolve(args.cwd) : process.cwd();
10772
+ const cwd = args.cwd ? path31.resolve(args.cwd) : process.cwd();
10643
10773
  const sessionId = args.sessionId;
10644
10774
  const unpackedSecrets = unpackAllSecrets();
10645
10775
  if (unpackedSecrets > 0) {
@@ -10691,7 +10821,7 @@ ${CHAT_HELP}`);
10691
10821
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
10692
10822
  const meta = readMeta(sessionFile);
10693
10823
  process.stdout.write(
10694
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs32.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
10824
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs33.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
10695
10825
  `
10696
10826
  );
10697
10827
  try {
@@ -74,6 +74,16 @@ export interface Profile {
74
74
  * Defines the in-process step sequence and routing map. See ContainerChild.
75
75
  */
76
76
  children?: ContainerChild[]
77
+ /**
78
+ * Whether the container should `git reset --hard HEAD` between
79
+ * children to discard tracked-file modifications a prior child left
80
+ * behind. Default `true` (preserves the legacy bug-safe behaviour
81
+ * — see executor.ts:runContainerLoop notes). Set `false` for
82
+ * containers whose children are expected to share intermediate
83
+ * state (e.g. bug's `reproduce` writing a failing test that `run`
84
+ * then makes pass). Only honoured when `role === "container"`.
85
+ */
86
+ resetBetweenChildren?: boolean
77
87
  /** Absolute directory the profile was loaded from. Used to resolve prompt.md. */
78
88
  dir: string
79
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.59",
3
+ "version": "0.4.61",
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",