@kody-ade/kody-engine-lite 0.1.146 → 0.1.148

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/cli.js CHANGED
@@ -254,7 +254,12 @@ function getProjectConfig() {
254
254
  enabled: raw.mcp.enabled ?? !!raw.mcp.devServer
255
255
  } : void 0,
256
256
  // Top-level devServer takes precedence; fall back to mcp.devServer for backward compat
257
- devServer: raw.devServer ?? raw.mcp?.devServer ?? void 0
257
+ devServer: raw.devServer ?? raw.mcp?.devServer ?? void 0,
258
+ watch: raw.watch ? {
259
+ enabled: raw.watch.enabled ?? false,
260
+ digestIssue: raw.watch.digestIssue,
261
+ model: raw.watch.model
262
+ } : void 0
258
263
  };
259
264
  } catch {
260
265
  logger.warn("kody.config.json is invalid JSON \u2014 using defaults");
@@ -1193,13 +1198,11 @@ async function tryStartLitellm(url, projectDir, generatedConfig) {
1193
1198
  const child = spawn3(cmd, args2, {
1194
1199
  stdio: ["ignore", "pipe", "pipe"],
1195
1200
  detached: true,
1196
- env: {
1197
- ...process.env,
1198
- ...dotenvVars,
1199
- // Remove DATABASE_URL so LiteLLM doesn't try to set up Prisma DB
1200
- // (DATABASE_URL may be set for the project's dev server, not for LiteLLM)
1201
- DATABASE_URL: ""
1202
- }
1201
+ env: (() => {
1202
+ const env = { ...process.env, ...dotenvVars };
1203
+ delete env.DATABASE_URL;
1204
+ return env;
1205
+ })()
1203
1206
  });
1204
1207
  let proxyStderr = "";
1205
1208
  child.stderr?.on("data", (chunk) => {
@@ -3289,9 +3292,92 @@ var init_logger2 = __esm({
3289
3292
  }
3290
3293
  });
3291
3294
 
3295
+ // src/watch/agents/prompt-builder.ts
3296
+ function buildWatchAgentPrompt(agent, ctx) {
3297
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3298
+ const preamble = `You are a Kody Watch agent monitoring the GitHub repository **${ctx.repo}**.
3299
+ Your role is described below. Use the tools available to you to inspect the repository and take action.
3300
+
3301
+ ## Environment
3302
+ - Repository: ${ctx.repo}
3303
+ - Current date: ${now}
3304
+ - Watch cycle: #${ctx.cycleNumber}
3305
+ ${ctx.digestIssue ? `- Digest issue: #${ctx.digestIssue}` : ""}
3306
+
3307
+ ## Tools
3308
+ Use \`gh\` CLI (via Bash) for all GitHub operations. Examples:
3309
+ - \`gh pr list --repo ${ctx.repo} --state open --json number,title,author,updatedAt,labels,createdAt\`
3310
+ - \`gh pr view 123 --repo ${ctx.repo} --json number,title,body,author,commits,files,comments,reviews,updatedAt\`
3311
+ - \`gh issue list --repo ${ctx.repo} --state open --json number,title,labels,updatedAt\`
3312
+ - \`gh issue create --repo ${ctx.repo} --title "..." --body "..." --label "kody:watch"\`
3313
+ - \`gh api repos/${ctx.repo}/actions/runs --jq '.workflow_runs[:10]'\`
3314
+
3315
+ Use Read/Glob/Grep for inspecting repository files.
3316
+
3317
+ ## Guidelines
3318
+ - **Check before creating**: Always search for existing open issues with similar titles before creating new ones to avoid duplicates.
3319
+ - **Label issues**: Prefix all created issue labels with \`kody:watch:\` (e.g. \`kody:watch:stale-pr\`).
3320
+ - **Be concise**: Issue bodies should contain evidence and a suggested action, not lengthy explanations.
3321
+ - **Stay focused**: Only act on what your instructions below describe. Do not perform unrelated analysis.`;
3322
+ const agentInstructions = `## Your Instructions
3323
+
3324
+ ${agent.systemPrompt}`;
3325
+ return `${preamble}
3326
+
3327
+ ${agentInstructions}`;
3328
+ }
3329
+ var init_prompt_builder = __esm({
3330
+ "src/watch/agents/prompt-builder.ts"() {
3331
+ "use strict";
3332
+ }
3333
+ });
3334
+
3335
+ // src/watch/agents/run-agent.ts
3336
+ async function runWatchAgent(agent, ctx, options) {
3337
+ const { model, provider, projectDir, timeoutMs = AGENT_TIMEOUT_MS } = options;
3338
+ const prompt = buildWatchAgentPrompt(agent, {
3339
+ repo: ctx.repo,
3340
+ cycleNumber: ctx.cycleNumber,
3341
+ digestIssue: ctx.digestIssue
3342
+ });
3343
+ const runner = createClaudeCodeRunner();
3344
+ const extraEnv = {};
3345
+ const stageConfig = { provider: provider ?? "claude", model };
3346
+ if (stageNeedsProxy(stageConfig)) {
3347
+ extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
3348
+ }
3349
+ const result2 = await runner.run(
3350
+ `watch:${agent.config.name}`,
3351
+ prompt,
3352
+ model,
3353
+ timeoutMs,
3354
+ projectDir,
3355
+ {
3356
+ cwd: projectDir,
3357
+ env: extraEnv
3358
+ }
3359
+ );
3360
+ return {
3361
+ agentName: agent.config.name,
3362
+ outcome: result2.outcome,
3363
+ output: result2.output,
3364
+ error: result2.error
3365
+ };
3366
+ }
3367
+ var AGENT_TIMEOUT_MS;
3368
+ var init_run_agent = __esm({
3369
+ "src/watch/agents/run-agent.ts"() {
3370
+ "use strict";
3371
+ init_agent_runner();
3372
+ init_config();
3373
+ init_prompt_builder();
3374
+ AGENT_TIMEOUT_MS = 2 * 60 * 1e3;
3375
+ }
3376
+ });
3377
+
3292
3378
  // src/watch/core/watch.ts
3293
3379
  async function runWatch(config) {
3294
- const { repo, dryRun, stateFile, plugins } = config;
3380
+ const { repo, dryRun, stateFile, plugins, agents } = config;
3295
3381
  const token = process.env.GH_TOKEN || "";
3296
3382
  const github = createGitHubClient(repo, token);
3297
3383
  const state = createStateStore(stateFile, github, config.digestIssue);
@@ -3372,6 +3458,45 @@ async function runWatch(config) {
3372
3458
  } else {
3373
3459
  log2.info({ actionCount: dedupedActions.length }, "Dry run \u2014 skipping action execution");
3374
3460
  }
3461
+ const scheduledAgents = agents.filter((agent) => {
3462
+ return cycleNumber % agent.config.schedule.every === 0;
3463
+ });
3464
+ const agentResults = [];
3465
+ if (scheduledAgents.length > 0 && !dryRun) {
3466
+ log2.info(
3467
+ { agentsTotal: agents.length, agentsScheduled: scheduledAgents.length },
3468
+ "Running watch agents"
3469
+ );
3470
+ const provider = config.provider;
3471
+ for (const agent of scheduledAgents) {
3472
+ try {
3473
+ log2.info({ agent: agent.config.name }, "Running watch agent");
3474
+ const result3 = await runWatchAgent(agent, ctx, {
3475
+ model: config.model,
3476
+ provider,
3477
+ projectDir: config.projectDir
3478
+ });
3479
+ agentResults.push(result3);
3480
+ if (result3.outcome === "completed") {
3481
+ log2.info({ agent: agent.config.name }, "Watch agent completed");
3482
+ } else {
3483
+ const errMsg = `Agent ${agent.config.name}: ${result3.outcome}${result3.error ? ` \u2014 ${result3.error}` : ""}`;
3484
+ errors.push(errMsg);
3485
+ log2.warn({ agent: agent.config.name, outcome: result3.outcome }, "Watch agent did not complete");
3486
+ }
3487
+ } catch (error) {
3488
+ const message = error instanceof Error ? error.message : String(error);
3489
+ errors.push(`Agent ${agent.config.name}: ${message}`);
3490
+ log2.error({ agent: agent.config.name, error: message }, "Watch agent failed");
3491
+ agentResults.push({ agentName: agent.config.name, outcome: "failed", error: message });
3492
+ }
3493
+ }
3494
+ } else if (scheduledAgents.length > 0 && dryRun) {
3495
+ log2.info(
3496
+ { agentCount: scheduledAgents.length },
3497
+ "Dry run \u2014 skipping watch agent execution"
3498
+ );
3499
+ }
3375
3500
  state.save();
3376
3501
  const result2 = {
3377
3502
  cycleNumber,
@@ -3379,6 +3504,8 @@ async function runWatch(config) {
3379
3504
  actionsProduced: allActions.length,
3380
3505
  actionsExecuted,
3381
3506
  actionsDeduplicated,
3507
+ agentsRun: scheduledAgents.length,
3508
+ agentResults,
3382
3509
  errors
3383
3510
  };
3384
3511
  log2.info(
@@ -3388,6 +3515,7 @@ async function runWatch(config) {
3388
3515
  actionsProduced: result2.actionsProduced,
3389
3516
  actionsExecuted: result2.actionsExecuted,
3390
3517
  actionsDeduplicated: result2.actionsDeduplicated,
3518
+ agentsRun: result2.agentsRun,
3391
3519
  errors: result2.errors.length
3392
3520
  },
3393
3521
  "Watch cycle completed"
@@ -3401,6 +3529,7 @@ var init_watch = __esm({
3401
3529
  init_dedup();
3402
3530
  init_github();
3403
3531
  init_logger2();
3532
+ init_run_agent();
3404
3533
  }
3405
3534
  });
3406
3535
 
@@ -4111,27 +4240,134 @@ var init_config_health = __esm({
4111
4240
  }
4112
4241
  });
4113
4242
 
4243
+ // src/watch/agents/loader.ts
4244
+ import * as fs19 from "fs";
4245
+ import * as path17 from "path";
4246
+ function validateAgentConfig(raw, dirName) {
4247
+ if (!raw || typeof raw !== "object") {
4248
+ return `${dirName}: agent.json must be a JSON object`;
4249
+ }
4250
+ const obj = raw;
4251
+ if (typeof obj.name !== "string" || !obj.name.trim()) {
4252
+ return `${dirName}: agent.json missing required "name" (string)`;
4253
+ }
4254
+ if (typeof obj.description !== "string" || !obj.description.trim()) {
4255
+ return `${dirName}: agent.json missing required "description" (string)`;
4256
+ }
4257
+ let every = 1;
4258
+ if (obj.schedule && typeof obj.schedule === "object") {
4259
+ const sched = obj.schedule;
4260
+ if (sched.every !== void 0) {
4261
+ if (typeof sched.every !== "number" || sched.every < 1 || !Number.isInteger(sched.every)) {
4262
+ return `${dirName}: schedule.every must be an integer >= 1`;
4263
+ }
4264
+ every = sched.every;
4265
+ }
4266
+ }
4267
+ return {
4268
+ name: obj.name.trim(),
4269
+ description: obj.description.trim(),
4270
+ schedule: { every }
4271
+ };
4272
+ }
4273
+ function loadWatchAgents(projectDir) {
4274
+ const agentsDir = path17.join(projectDir, AGENTS_DIR);
4275
+ const warnings = [];
4276
+ const agents = [];
4277
+ if (!fs19.existsSync(agentsDir)) {
4278
+ return { agents, warnings };
4279
+ }
4280
+ let entries;
4281
+ try {
4282
+ entries = fs19.readdirSync(agentsDir, { withFileTypes: true });
4283
+ } catch {
4284
+ warnings.push(`Cannot read ${AGENTS_DIR}`);
4285
+ return { agents, warnings };
4286
+ }
4287
+ for (const entry of entries) {
4288
+ if (!entry.isDirectory()) continue;
4289
+ const dirPath = path17.join(agentsDir, entry.name);
4290
+ const jsonPath = path17.join(dirPath, "agent.json");
4291
+ const mdPath = path17.join(dirPath, "agent.md");
4292
+ if (!fs19.existsSync(jsonPath)) {
4293
+ warnings.push(`${entry.name}: missing agent.json \u2014 skipped`);
4294
+ continue;
4295
+ }
4296
+ if (!fs19.existsSync(mdPath)) {
4297
+ warnings.push(`${entry.name}: missing agent.md \u2014 skipped`);
4298
+ continue;
4299
+ }
4300
+ let rawJson;
4301
+ try {
4302
+ rawJson = JSON.parse(fs19.readFileSync(jsonPath, "utf-8"));
4303
+ } catch {
4304
+ warnings.push(`${entry.name}: agent.json is invalid JSON \u2014 skipped`);
4305
+ continue;
4306
+ }
4307
+ const configOrError = validateAgentConfig(rawJson, entry.name);
4308
+ if (typeof configOrError === "string") {
4309
+ warnings.push(`${configOrError} \u2014 skipped`);
4310
+ continue;
4311
+ }
4312
+ const systemPrompt = fs19.readFileSync(mdPath, "utf-8").trim();
4313
+ if (!systemPrompt) {
4314
+ warnings.push(`${entry.name}: agent.md is empty \u2014 skipped`);
4315
+ continue;
4316
+ }
4317
+ agents.push({
4318
+ config: configOrError,
4319
+ systemPrompt,
4320
+ dirPath
4321
+ });
4322
+ }
4323
+ return { agents, warnings };
4324
+ }
4325
+ var AGENTS_DIR;
4326
+ var init_loader = __esm({
4327
+ "src/watch/agents/loader.ts"() {
4328
+ "use strict";
4329
+ AGENTS_DIR = ".kody/watch/agents";
4330
+ }
4331
+ });
4332
+
4114
4333
  // src/watch/index.ts
4115
4334
  var watch_exports = {};
4116
4335
  __export(watch_exports, {
4117
4336
  runWatchCommand: () => runWatchCommand
4118
4337
  });
4119
- import * as fs19 from "fs";
4120
- import * as path17 from "path";
4338
+ import * as fs20 from "fs";
4339
+ import * as path18 from "path";
4121
4340
  async function runWatchCommand(opts) {
4122
4341
  const cwd = process.cwd();
4123
- const configPath = path17.join(cwd, "kody.config.json");
4342
+ let litellmProcess = null;
4343
+ const configPath = path18.join(cwd, "kody.config.json");
4124
4344
  let repo = process.env.REPO || "";
4125
4345
  let digestIssue;
4126
- if (!repo && fs19.existsSync(configPath)) {
4346
+ let watchModel;
4347
+ let agentProvider;
4348
+ let agentModelMap;
4349
+ let defaultCheapModel = "claude-haiku-4-5-20251001";
4350
+ if (!repo && fs20.existsSync(configPath)) {
4127
4351
  try {
4128
- const config2 = JSON.parse(fs19.readFileSync(configPath, "utf-8"));
4352
+ const config2 = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
4129
4353
  if (config2.github?.owner && config2.github?.repo) {
4130
4354
  repo = `${config2.github.owner}/${config2.github.repo}`;
4131
4355
  }
4132
4356
  if (config2.watch?.digestIssue) {
4133
4357
  digestIssue = config2.watch.digestIssue;
4134
4358
  }
4359
+ if (config2.watch?.model) {
4360
+ watchModel = config2.watch.model;
4361
+ }
4362
+ if (config2.agent?.provider) {
4363
+ agentProvider = config2.agent.provider;
4364
+ }
4365
+ if (config2.agent?.modelMap) {
4366
+ agentModelMap = config2.agent.modelMap;
4367
+ }
4368
+ if (config2.agent?.modelMap?.cheap) {
4369
+ defaultCheapModel = config2.agent.modelMap.cheap;
4370
+ }
4135
4371
  } catch {
4136
4372
  }
4137
4373
  }
@@ -4150,12 +4386,43 @@ async function runWatchCommand(opts) {
4150
4386
  registry.register(pipelineHealthPlugin);
4151
4387
  registry.register(securityScanPlugin);
4152
4388
  registry.register(configHealthPlugin);
4389
+ const { agents, warnings } = loadWatchAgents(cwd);
4390
+ for (const w of warnings) {
4391
+ console.warn(` Agent warning: ${w}`);
4392
+ }
4393
+ const model = watchModel ?? defaultCheapModel;
4394
+ const needsProxy = agentProvider && agentProvider !== "claude" && agentProvider !== "anthropic";
4395
+ if (agents.length > 0) {
4396
+ console.log(` Found ${agents.length} watch agent(s): ${agents.map((a) => a.config.name).join(", ")}`);
4397
+ console.log(` Model: ${model}${agentProvider ? ` (provider: ${agentProvider})` : ""}`);
4398
+ if (needsProxy && !opts.dryRun) {
4399
+ const litellmUrl = LITELLM_DEFAULT_URL;
4400
+ const isHealthy = await checkLitellmHealth(litellmUrl);
4401
+ if (!isHealthy) {
4402
+ const proxyModelMap = { ...agentModelMap, watch: model };
4403
+ const generatedConfig = generateLitellmConfig(agentProvider, proxyModelMap);
4404
+ console.log(` Starting LiteLLM proxy for ${agentProvider}...`);
4405
+ litellmProcess = await tryStartLitellm(litellmUrl, cwd, generatedConfig);
4406
+ if (litellmProcess) {
4407
+ console.log(` LiteLLM proxy started`);
4408
+ } else {
4409
+ console.warn(` LiteLLM proxy failed to start \u2014 agents using ${agentProvider} may fail`);
4410
+ }
4411
+ } else {
4412
+ console.log(` LiteLLM proxy already running`);
4413
+ }
4414
+ }
4415
+ }
4153
4416
  const config = {
4154
4417
  repo,
4155
4418
  dryRun: opts.dryRun,
4156
- stateFile: path17.join(cwd, ".kody", "watch-state.json"),
4419
+ stateFile: path18.join(cwd, ".kody", "watch-state.json"),
4157
4420
  plugins: registry.getAll(),
4158
- digestIssue
4421
+ digestIssue,
4422
+ agents,
4423
+ model,
4424
+ provider: agentProvider,
4425
+ projectDir: cwd
4159
4426
  };
4160
4427
  console.log(`
4161
4428
  Kody Watch \u2014 repo: ${repo}, dry-run: ${opts.dryRun}
@@ -4167,13 +4434,17 @@ Kody Watch \u2014 repo: ${repo}, dry-run: ${opts.dryRun}
4167
4434
  console.warn(` Warning: ${error}`);
4168
4435
  }
4169
4436
  }
4437
+ const agentSummary = result2.agentsRun > 0 ? `, ${result2.agentsRun} agents` : "";
4170
4438
  console.log(`
4171
- Cycle #${result2.cycleNumber} complete: ${result2.pluginsRun} plugins, ${result2.actionsExecuted} actions, ${result2.actionsDeduplicated} deduplicated`);
4172
- process.exit(0);
4439
+ Cycle #${result2.cycleNumber} complete: ${result2.pluginsRun} plugins${agentSummary}, ${result2.actionsExecuted} actions, ${result2.actionsDeduplicated} deduplicated`);
4173
4440
  } catch (error) {
4174
4441
  const message = error instanceof Error ? error.message : String(error);
4175
4442
  console.error(`Watch failed: ${message}`);
4176
- process.exit(1);
4443
+ } finally {
4444
+ if (litellmProcess) {
4445
+ litellmProcess.kill("SIGTERM");
4446
+ }
4447
+ process.exit(0);
4177
4448
  }
4178
4449
  }
4179
4450
  var init_watch2 = __esm({
@@ -4184,6 +4455,9 @@ var init_watch2 = __esm({
4184
4455
  init_pipeline_health();
4185
4456
  init_security_scan();
4186
4457
  init_config_health();
4458
+ init_loader();
4459
+ init_litellm();
4460
+ init_config();
4187
4461
  }
4188
4462
  });
4189
4463
 
@@ -4462,14 +4736,14 @@ var init_git_utils = __esm({
4462
4736
  });
4463
4737
 
4464
4738
  // src/pipeline/state.ts
4465
- import * as fs20 from "fs";
4466
- import * as path18 from "path";
4739
+ import * as fs21 from "fs";
4740
+ import * as path19 from "path";
4467
4741
  function loadState(taskId, taskDir) {
4468
- const p = path18.join(taskDir, "status.json");
4469
- if (!fs20.existsSync(p)) return null;
4742
+ const p = path19.join(taskDir, "status.json");
4743
+ if (!fs21.existsSync(p)) return null;
4470
4744
  try {
4471
4745
  const result2 = parseJsonSafe(
4472
- fs20.readFileSync(p, "utf-8"),
4746
+ fs21.readFileSync(p, "utf-8"),
4473
4747
  ["taskId", "state", "stages", "createdAt", "updatedAt"]
4474
4748
  );
4475
4749
  if (!result2.ok) {
@@ -4487,10 +4761,10 @@ function writeState(state, taskDir) {
4487
4761
  ...state,
4488
4762
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4489
4763
  };
4490
- const target = path18.join(taskDir, "status.json");
4764
+ const target = path19.join(taskDir, "status.json");
4491
4765
  const tmp = target + ".tmp";
4492
- fs20.writeFileSync(tmp, JSON.stringify(updated, null, 2));
4493
- fs20.renameSync(tmp, target);
4766
+ fs21.writeFileSync(tmp, JSON.stringify(updated, null, 2));
4767
+ fs21.renameSync(tmp, target);
4494
4768
  return updated;
4495
4769
  }
4496
4770
  function initState(taskId) {
@@ -4531,16 +4805,16 @@ var init_complexity = __esm({
4531
4805
  });
4532
4806
 
4533
4807
  // src/memory.ts
4534
- import * as fs21 from "fs";
4535
- import * as path19 from "path";
4808
+ import * as fs22 from "fs";
4809
+ import * as path20 from "path";
4536
4810
  function readProjectMemory(projectDir) {
4537
- const memoryDir = path19.join(projectDir, ".kody", "memory");
4538
- if (!fs21.existsSync(memoryDir)) return "";
4539
- const files = fs21.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
4811
+ const memoryDir = path20.join(projectDir, ".kody", "memory");
4812
+ if (!fs22.existsSync(memoryDir)) return "";
4813
+ const files = fs22.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
4540
4814
  if (files.length === 0) return "";
4541
4815
  const sections = [];
4542
4816
  for (const file of files) {
4543
- const content = fs21.readFileSync(path19.join(memoryDir, file), "utf-8").trim();
4817
+ const content = fs22.readFileSync(path20.join(memoryDir, file), "utf-8").trim();
4544
4818
  if (content) {
4545
4819
  sections.push(`## ${file.replace(".md", "")}
4546
4820
  ${content}`);
@@ -4559,8 +4833,8 @@ var init_memory = __esm({
4559
4833
  });
4560
4834
 
4561
4835
  // src/context-tiers.ts
4562
- import * as fs22 from "fs";
4563
- import * as path20 from "path";
4836
+ import * as fs23 from "fs";
4837
+ import * as path21 from "path";
4564
4838
  function estimateTokens(text) {
4565
4839
  return Math.ceil(text.length / 4);
4566
4840
  }
@@ -4651,7 +4925,7 @@ function generateL1Json(content) {
4651
4925
  }
4652
4926
  }
4653
4927
  function getTieredContent(filePath, content) {
4654
- const key = path20.basename(filePath);
4928
+ const key = path21.basename(filePath);
4655
4929
  return {
4656
4930
  source: filePath,
4657
4931
  L0: generateL0(content, key),
@@ -4663,15 +4937,15 @@ function selectTier(tiered, tier) {
4663
4937
  return tiered[tier];
4664
4938
  }
4665
4939
  function readProjectMemoryTiered(projectDir, tier) {
4666
- const memoryDir = path20.join(projectDir, ".kody", "memory");
4667
- if (!fs22.existsSync(memoryDir)) return "";
4668
- const files = fs22.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
4940
+ const memoryDir = path21.join(projectDir, ".kody", "memory");
4941
+ if (!fs23.existsSync(memoryDir)) return "";
4942
+ const files = fs23.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
4669
4943
  if (files.length === 0) return "";
4670
4944
  const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
4671
4945
  const sections = [];
4672
4946
  for (const file of files) {
4673
- const filePath = path20.join(memoryDir, file);
4674
- const content = fs22.readFileSync(filePath, "utf-8").trim();
4947
+ const filePath = path21.join(memoryDir, file);
4948
+ const content = fs23.readFileSync(filePath, "utf-8").trim();
4675
4949
  if (!content) continue;
4676
4950
  const tiered = getTieredContent(filePath, content);
4677
4951
  const selected = selectTier(tiered, tier);
@@ -4694,9 +4968,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
4694
4968
  `;
4695
4969
  context += `Task Directory: ${taskDir}
4696
4970
  `;
4697
- const taskMdPath = path20.join(taskDir, "task.md");
4698
- if (fs22.existsSync(taskMdPath)) {
4699
- const content = fs22.readFileSync(taskMdPath, "utf-8");
4971
+ const taskMdPath = path21.join(taskDir, "task.md");
4972
+ if (fs23.existsSync(taskMdPath)) {
4973
+ const content = fs23.readFileSync(taskMdPath, "utf-8");
4700
4974
  const selected = selectContent(taskMdPath, content, policy.taskDescription);
4701
4975
  const label = tierLabel("Task Description", policy.taskDescription);
4702
4976
  context += `
@@ -4704,9 +4978,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
4704
4978
  ${selected}
4705
4979
  `;
4706
4980
  }
4707
- const taskJsonPath = path20.join(taskDir, "task.json");
4708
- if (fs22.existsSync(taskJsonPath)) {
4709
- const content = fs22.readFileSync(taskJsonPath, "utf-8");
4981
+ const taskJsonPath = path21.join(taskDir, "task.json");
4982
+ if (fs23.existsSync(taskJsonPath)) {
4983
+ const content = fs23.readFileSync(taskJsonPath, "utf-8");
4710
4984
  if (policy.taskClassification === "L2") {
4711
4985
  try {
4712
4986
  const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
@@ -4732,9 +5006,9 @@ ${selected}
4732
5006
  }
4733
5007
  }
4734
5008
  }
4735
- const specPath = path20.join(taskDir, "spec.md");
4736
- if (fs22.existsSync(specPath)) {
4737
- const content = fs22.readFileSync(specPath, "utf-8");
5009
+ const specPath = path21.join(taskDir, "spec.md");
5010
+ if (fs23.existsSync(specPath)) {
5011
+ const content = fs23.readFileSync(specPath, "utf-8");
4738
5012
  const selected = selectContent(specPath, content, policy.spec);
4739
5013
  const label = tierLabel("Spec", policy.spec);
4740
5014
  context += `
@@ -4742,9 +5016,9 @@ ${selected}
4742
5016
  ${selected}
4743
5017
  `;
4744
5018
  }
4745
- const planPath = path20.join(taskDir, "plan.md");
4746
- if (fs22.existsSync(planPath)) {
4747
- const content = fs22.readFileSync(planPath, "utf-8");
5019
+ const planPath = path21.join(taskDir, "plan.md");
5020
+ if (fs23.existsSync(planPath)) {
5021
+ const content = fs23.readFileSync(planPath, "utf-8");
4748
5022
  const selected = selectContent(planPath, content, policy.plan);
4749
5023
  const label = tierLabel("Plan", policy.plan);
4750
5024
  context += `
@@ -4752,9 +5026,9 @@ ${selected}
4752
5026
  ${selected}
4753
5027
  `;
4754
5028
  }
4755
- const contextMdPath = path20.join(taskDir, "context.md");
4756
- if (fs22.existsSync(contextMdPath)) {
4757
- const content = fs22.readFileSync(contextMdPath, "utf-8");
5029
+ const contextMdPath = path21.join(taskDir, "context.md");
5030
+ if (fs23.existsSync(contextMdPath)) {
5031
+ const content = fs23.readFileSync(contextMdPath, "utf-8");
4758
5032
  const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
4759
5033
  const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
4760
5034
  context += `
@@ -4840,24 +5114,24 @@ var init_context_tiers = __esm({
4840
5114
  });
4841
5115
 
4842
5116
  // src/context.ts
4843
- import * as fs23 from "fs";
4844
- import * as path21 from "path";
5117
+ import * as fs24 from "fs";
5118
+ import * as path22 from "path";
4845
5119
  function readPromptFile(stageName, projectDir) {
4846
5120
  if (projectDir) {
4847
- const stepFile = path21.join(projectDir, ".kody", "steps", `${stageName}.md`);
4848
- if (fs23.existsSync(stepFile)) {
4849
- return fs23.readFileSync(stepFile, "utf-8");
5121
+ const stepFile = path22.join(projectDir, ".kody", "steps", `${stageName}.md`);
5122
+ if (fs24.existsSync(stepFile)) {
5123
+ return fs24.readFileSync(stepFile, "utf-8");
4850
5124
  }
4851
5125
  console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
4852
5126
  }
4853
5127
  const scriptDir = new URL(".", import.meta.url).pathname;
4854
5128
  const candidates = [
4855
- path21.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
4856
- path21.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
5129
+ path22.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
5130
+ path22.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
4857
5131
  ];
4858
5132
  for (const candidate of candidates) {
4859
- if (fs23.existsSync(candidate)) {
4860
- return fs23.readFileSync(candidate, "utf-8");
5133
+ if (fs24.existsSync(candidate)) {
5134
+ return fs24.readFileSync(candidate, "utf-8");
4861
5135
  }
4862
5136
  }
4863
5137
  throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
@@ -4869,18 +5143,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
4869
5143
  `;
4870
5144
  context += `Task Directory: ${taskDir}
4871
5145
  `;
4872
- const taskMdPath = path21.join(taskDir, "task.md");
4873
- if (fs23.existsSync(taskMdPath)) {
4874
- const taskMd = fs23.readFileSync(taskMdPath, "utf-8");
5146
+ const taskMdPath = path22.join(taskDir, "task.md");
5147
+ if (fs24.existsSync(taskMdPath)) {
5148
+ const taskMd = fs24.readFileSync(taskMdPath, "utf-8");
4875
5149
  context += `
4876
5150
  ## Task Description
4877
5151
  ${taskMd}
4878
5152
  `;
4879
5153
  }
4880
- const taskJsonPath = path21.join(taskDir, "task.json");
4881
- if (fs23.existsSync(taskJsonPath)) {
5154
+ const taskJsonPath = path22.join(taskDir, "task.json");
5155
+ if (fs24.existsSync(taskJsonPath)) {
4882
5156
  try {
4883
- const taskDef = JSON.parse(fs23.readFileSync(taskJsonPath, "utf-8"));
5157
+ const taskDef = JSON.parse(fs24.readFileSync(taskJsonPath, "utf-8"));
4884
5158
  context += `
4885
5159
  ## Task Classification
4886
5160
  `;
@@ -4893,27 +5167,27 @@ ${taskMd}
4893
5167
  } catch {
4894
5168
  }
4895
5169
  }
4896
- const specPath = path21.join(taskDir, "spec.md");
4897
- if (fs23.existsSync(specPath)) {
4898
- const spec = fs23.readFileSync(specPath, "utf-8");
5170
+ const specPath = path22.join(taskDir, "spec.md");
5171
+ if (fs24.existsSync(specPath)) {
5172
+ const spec = fs24.readFileSync(specPath, "utf-8");
4899
5173
  const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
4900
5174
  context += `
4901
5175
  ## Spec Summary
4902
5176
  ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
4903
5177
  `;
4904
5178
  }
4905
- const planPath = path21.join(taskDir, "plan.md");
4906
- if (fs23.existsSync(planPath)) {
4907
- const plan = fs23.readFileSync(planPath, "utf-8");
5179
+ const planPath = path22.join(taskDir, "plan.md");
5180
+ if (fs24.existsSync(planPath)) {
5181
+ const plan = fs24.readFileSync(planPath, "utf-8");
4908
5182
  const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
4909
5183
  context += `
4910
5184
  ## Plan Summary
4911
5185
  ${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
4912
5186
  `;
4913
5187
  }
4914
- const contextMdPath = path21.join(taskDir, "context.md");
4915
- if (fs23.existsSync(contextMdPath)) {
4916
- const accumulated = fs23.readFileSync(contextMdPath, "utf-8");
5188
+ const contextMdPath = path22.join(taskDir, "context.md");
5189
+ if (fs24.existsSync(contextMdPath)) {
5190
+ const accumulated = fs24.readFileSync(contextMdPath, "utf-8");
4917
5191
  const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
4918
5192
  const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
4919
5193
  context += `
@@ -4931,17 +5205,17 @@ ${feedback}
4931
5205
  }
4932
5206
  function inferHasUIFromScope(scope) {
4933
5207
  return scope.some((filePath) => {
4934
- const ext = path21.extname(filePath).toLowerCase();
5208
+ const ext = path22.extname(filePath).toLowerCase();
4935
5209
  if (UI_EXTENSIONS.has(ext)) return true;
4936
5210
  const normalized = filePath.replace(/\\/g, "/");
4937
5211
  return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
4938
5212
  });
4939
5213
  }
4940
5214
  function taskHasUI(taskDir) {
4941
- const taskJsonPath = path21.join(taskDir, "task.json");
4942
- if (!fs23.existsSync(taskJsonPath)) return true;
5215
+ const taskJsonPath = path22.join(taskDir, "task.json");
5216
+ if (!fs24.existsSync(taskJsonPath)) return true;
4943
5217
  try {
4944
- const taskDef = JSON.parse(fs23.readFileSync(taskJsonPath, "utf-8"));
5218
+ const taskDef = JSON.parse(fs24.readFileSync(taskJsonPath, "utf-8"));
4945
5219
  const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
4946
5220
  if (scope.length === 0) return true;
4947
5221
  return inferHasUIFromScope(scope);
@@ -5127,9 +5401,9 @@ ${prompt}` : prompt;
5127
5401
  const hasBrowserTools = isMcpEnabledForStage(stageName, config.mcp) || config.devServer && browserStages.includes(stageName);
5128
5402
  if (hasBrowserTools && taskHasUI(taskDir)) {
5129
5403
  assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
5130
- const qaGuidePath = path21.join(projectDir, ".kody", "qa-guide.md");
5131
- if (fs23.existsSync(qaGuidePath)) {
5132
- const qaGuide = fs23.readFileSync(qaGuidePath, "utf-8").trim();
5404
+ const qaGuidePath = path22.join(projectDir, ".kody", "qa-guide.md");
5405
+ if (fs24.existsSync(qaGuidePath)) {
5406
+ const qaGuide = fs24.readFileSync(qaGuidePath, "utf-8").trim();
5133
5407
  assembled = assembled + "\n\n" + qaGuide;
5134
5408
  }
5135
5409
  }
@@ -5239,7 +5513,7 @@ async function waitForReady(url, timeoutSec, stdoutMatch) {
5239
5513
  while (Date.now() < deadline) {
5240
5514
  if (stdoutMatch()) {
5241
5515
  logger.info(" Dev server stdout matched ready pattern");
5242
- const httpTimeout = Math.min(15, Math.max(1, Math.floor((deadline - Date.now()) / 1e3)));
5516
+ const httpTimeout = Math.max(1, Math.floor((deadline - Date.now()) / 1e3));
5243
5517
  return pollReady(url, httpTimeout);
5244
5518
  }
5245
5519
  await new Promise((r) => setTimeout(r, 500));
@@ -5317,8 +5591,8 @@ var init_dev_server = __esm({
5317
5591
  });
5318
5592
 
5319
5593
  // src/stages/agent.ts
5320
- import * as fs24 from "fs";
5321
- import * as path22 from "path";
5594
+ import * as fs25 from "fs";
5595
+ import * as path23 from "path";
5322
5596
  function getSessionInfo(stageName, sessions) {
5323
5597
  const group = SESSION_GROUP[stageName];
5324
5598
  if (!group) return void 0;
@@ -5440,27 +5714,27 @@ async function executeAgentStage(ctx, def) {
5440
5714
  }
5441
5715
  const result2 = lastResult;
5442
5716
  if (def.outputFile && result2.output) {
5443
- fs24.writeFileSync(path22.join(ctx.taskDir, def.outputFile), result2.output);
5717
+ fs25.writeFileSync(path23.join(ctx.taskDir, def.outputFile), result2.output);
5444
5718
  }
5445
5719
  if (def.outputFile) {
5446
- const outputPath = path22.join(ctx.taskDir, def.outputFile);
5447
- if (!fs24.existsSync(outputPath)) {
5448
- const ext = path22.extname(def.outputFile);
5449
- const base = path22.basename(def.outputFile, ext);
5450
- const files = fs24.readdirSync(ctx.taskDir);
5720
+ const outputPath = path23.join(ctx.taskDir, def.outputFile);
5721
+ if (!fs25.existsSync(outputPath)) {
5722
+ const ext = path23.extname(def.outputFile);
5723
+ const base = path23.basename(def.outputFile, ext);
5724
+ const files = fs25.readdirSync(ctx.taskDir);
5451
5725
  const variant = files.find(
5452
5726
  (f) => f.startsWith(base + "-") && f.endsWith(ext)
5453
5727
  );
5454
5728
  if (variant) {
5455
- fs24.renameSync(path22.join(ctx.taskDir, variant), outputPath);
5729
+ fs25.renameSync(path23.join(ctx.taskDir, variant), outputPath);
5456
5730
  logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
5457
5731
  }
5458
5732
  }
5459
5733
  }
5460
5734
  if (def.outputFile) {
5461
- const outputPath = path22.join(ctx.taskDir, def.outputFile);
5462
- if (fs24.existsSync(outputPath)) {
5463
- const content = fs24.readFileSync(outputPath, "utf-8");
5735
+ const outputPath = path23.join(ctx.taskDir, def.outputFile);
5736
+ if (fs25.existsSync(outputPath)) {
5737
+ const content = fs25.readFileSync(outputPath, "utf-8");
5464
5738
  const validation = validateStageOutput(def.name, content);
5465
5739
  if (!validation.valid) {
5466
5740
  if (def.name === "taskify") {
@@ -5474,7 +5748,7 @@ async function executeAgentStage(ctx, def) {
5474
5748
  const stripped = stripFences(retryResult.output);
5475
5749
  const retryValidation = validateTaskJson(stripped);
5476
5750
  if (retryValidation.valid) {
5477
- fs24.writeFileSync(outputPath, retryResult.output);
5751
+ fs25.writeFileSync(outputPath, retryResult.output);
5478
5752
  logger.info(` taskify retry produced valid JSON`);
5479
5753
  } else {
5480
5754
  logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
@@ -5487,7 +5761,7 @@ async function executeAgentStage(ctx, def) {
5487
5761
  risk_level: "low",
5488
5762
  questions: []
5489
5763
  }, null, 2);
5490
- fs24.writeFileSync(outputPath, fallback);
5764
+ fs25.writeFileSync(outputPath, fallback);
5491
5765
  logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
5492
5766
  }
5493
5767
  }
@@ -5501,7 +5775,7 @@ async function executeAgentStage(ctx, def) {
5501
5775
  return { outcome: "completed", outputFile: def.outputFile, retries };
5502
5776
  }
5503
5777
  function appendStageContext(taskDir, stageName, output) {
5504
- const contextPath = path22.join(taskDir, "context.md");
5778
+ const contextPath = path23.join(taskDir, "context.md");
5505
5779
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
5506
5780
  let summary;
5507
5781
  if (output && output.trim()) {
@@ -5514,7 +5788,7 @@ function appendStageContext(taskDir, stageName, output) {
5514
5788
  ### ${stageName} (${timestamp2})
5515
5789
  ${summary}
5516
5790
  `;
5517
- fs24.appendFileSync(contextPath, entry);
5791
+ fs25.appendFileSync(contextPath, entry);
5518
5792
  }
5519
5793
  var SESSION_GROUP;
5520
5794
  var init_agent = __esm({
@@ -5795,8 +6069,8 @@ Error context:
5795
6069
  });
5796
6070
 
5797
6071
  // src/stages/gate.ts
5798
- import * as fs25 from "fs";
5799
- import * as path23 from "path";
6072
+ import * as fs26 from "fs";
6073
+ import * as path24 from "path";
5800
6074
  function executeGateStage(ctx, def) {
5801
6075
  if (ctx.input.dryRun) {
5802
6076
  logger.info(` [dry-run] skipping ${def.name}`);
@@ -5839,7 +6113,7 @@ ${output}
5839
6113
  `);
5840
6114
  }
5841
6115
  }
5842
- fs25.writeFileSync(path23.join(ctx.taskDir, "verify.md"), lines.join(""));
6116
+ fs26.writeFileSync(path24.join(ctx.taskDir, "verify.md"), lines.join(""));
5843
6117
  return {
5844
6118
  outcome: verifyResult.pass ? "completed" : "failed",
5845
6119
  retries: 0
@@ -5854,8 +6128,8 @@ var init_gate = __esm({
5854
6128
  });
5855
6129
 
5856
6130
  // src/stages/verify.ts
5857
- import * as fs26 from "fs";
5858
- import * as path24 from "path";
6131
+ import * as fs27 from "fs";
6132
+ import * as path25 from "path";
5859
6133
  import { execFileSync as execFileSync16 } from "child_process";
5860
6134
  async function executeVerifyWithAutofix(ctx, def) {
5861
6135
  const maxAttempts = def.maxRetries ?? 2;
@@ -5866,8 +6140,8 @@ async function executeVerifyWithAutofix(ctx, def) {
5866
6140
  return { ...gateResult, retries: attempt };
5867
6141
  }
5868
6142
  if (attempt < maxAttempts) {
5869
- const verifyPath = path24.join(ctx.taskDir, "verify.md");
5870
- const errorOutput = fs26.existsSync(verifyPath) ? fs26.readFileSync(verifyPath, "utf-8") : "Unknown error";
6143
+ const verifyPath = path25.join(ctx.taskDir, "verify.md");
6144
+ const errorOutput = fs27.existsSync(verifyPath) ? fs27.readFileSync(verifyPath, "utf-8") : "Unknown error";
5871
6145
  const modifiedFiles = getModifiedFiles(ctx.projectDir);
5872
6146
  const defaultRunner = getRunnerForStage(ctx, "taskify");
5873
6147
  const diagConfig = getProjectConfig();
@@ -5963,8 +6237,8 @@ var init_verify = __esm({
5963
6237
  });
5964
6238
 
5965
6239
  // src/review-standalone.ts
5966
- import * as fs27 from "fs";
5967
- import * as path25 from "path";
6240
+ import * as fs28 from "fs";
6241
+ import * as path26 from "path";
5968
6242
  function resolveReviewTarget(input) {
5969
6243
  if (input.prs.length === 0) {
5970
6244
  return {
@@ -5988,8 +6262,8 @@ Or comment on the specific PR: \`@kody review\``
5988
6262
  }
5989
6263
  async function runStandaloneReview(input) {
5990
6264
  const taskId = input.taskId ?? `review-${generateTaskId()}`;
5991
- const taskDir = path25.join(input.projectDir, ".kody", "tasks", taskId);
5992
- fs27.mkdirSync(taskDir, { recursive: true });
6265
+ const taskDir = path26.join(input.projectDir, ".kody", "tasks", taskId);
6266
+ fs28.mkdirSync(taskDir, { recursive: true });
5993
6267
  let diffInstruction = "";
5994
6268
  let filesChangedSection = "";
5995
6269
  if (input.baseBranch) {
@@ -6016,7 +6290,7 @@ ${fileList}`;
6016
6290
  const taskContent = `# ${input.prTitle}
6017
6291
 
6018
6292
  ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
6019
- fs27.writeFileSync(path25.join(taskDir, "task.md"), taskContent);
6293
+ fs28.writeFileSync(path26.join(taskDir, "task.md"), taskContent);
6020
6294
  const reviewDef = STAGES.find((s) => s.name === "review");
6021
6295
  const ctx = {
6022
6296
  taskId,
@@ -6038,10 +6312,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
6038
6312
  error: result2.error ?? "Review stage failed"
6039
6313
  };
6040
6314
  }
6041
- const reviewPath = path25.join(taskDir, "review.md");
6315
+ const reviewPath = path26.join(taskDir, "review.md");
6042
6316
  let reviewContent;
6043
- if (fs27.existsSync(reviewPath)) {
6044
- reviewContent = fs27.readFileSync(reviewPath, "utf-8");
6317
+ if (fs28.existsSync(reviewPath)) {
6318
+ reviewContent = fs28.readFileSync(reviewPath, "utf-8");
6045
6319
  }
6046
6320
  return {
6047
6321
  outcome: "completed",
@@ -6081,8 +6355,8 @@ var init_review_standalone = __esm({
6081
6355
  });
6082
6356
 
6083
6357
  // src/stages/review.ts
6084
- import * as fs28 from "fs";
6085
- import * as path26 from "path";
6358
+ import * as fs29 from "fs";
6359
+ import * as path27 from "path";
6086
6360
  async function executeReviewWithFix(ctx, def) {
6087
6361
  if (ctx.input.dryRun) {
6088
6362
  return { outcome: "completed", retries: 0 };
@@ -6096,11 +6370,11 @@ async function executeReviewWithFix(ctx, def) {
6096
6370
  if (reviewResult.outcome !== "completed") {
6097
6371
  return reviewResult;
6098
6372
  }
6099
- const reviewFile = path26.join(ctx.taskDir, "review.md");
6100
- if (!fs28.existsSync(reviewFile)) {
6373
+ const reviewFile = path27.join(ctx.taskDir, "review.md");
6374
+ if (!fs29.existsSync(reviewFile)) {
6101
6375
  return { outcome: "failed", retries: iteration, error: "review.md not found" };
6102
6376
  }
6103
- const content = fs28.readFileSync(reviewFile, "utf-8");
6377
+ const content = fs29.readFileSync(reviewFile, "utf-8");
6104
6378
  if (detectReviewVerdict(content) !== "fail") {
6105
6379
  return { ...reviewResult, retries: iteration };
6106
6380
  }
@@ -6129,15 +6403,15 @@ var init_review = __esm({
6129
6403
  });
6130
6404
 
6131
6405
  // src/stages/ship.ts
6132
- import * as fs29 from "fs";
6133
- import * as path27 from "path";
6406
+ import * as fs30 from "fs";
6407
+ import * as path28 from "path";
6134
6408
  import { execFileSync as execFileSync17 } from "child_process";
6135
6409
  function buildPrBody(ctx) {
6136
6410
  const sections = [];
6137
- const taskJsonPath = path27.join(ctx.taskDir, "task.json");
6138
- if (fs29.existsSync(taskJsonPath)) {
6411
+ const taskJsonPath = path28.join(ctx.taskDir, "task.json");
6412
+ if (fs30.existsSync(taskJsonPath)) {
6139
6413
  try {
6140
- const raw = fs29.readFileSync(taskJsonPath, "utf-8");
6414
+ const raw = fs30.readFileSync(taskJsonPath, "utf-8");
6141
6415
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6142
6416
  const task = JSON.parse(cleaned);
6143
6417
  if (task.description) {
@@ -6156,9 +6430,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
6156
6430
  } catch {
6157
6431
  }
6158
6432
  }
6159
- const reviewPath = path27.join(ctx.taskDir, "review.md");
6160
- if (fs29.existsSync(reviewPath)) {
6161
- const review = fs29.readFileSync(reviewPath, "utf-8");
6433
+ const reviewPath = path28.join(ctx.taskDir, "review.md");
6434
+ if (fs30.existsSync(reviewPath)) {
6435
+ const review = fs30.readFileSync(reviewPath, "utf-8");
6162
6436
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
6163
6437
  if (summaryMatch) {
6164
6438
  const summary = summaryMatch[1].trim();
@@ -6175,14 +6449,14 @@ ${summary}`);
6175
6449
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
6176
6450
  }
6177
6451
  }
6178
- const verifyPath = path27.join(ctx.taskDir, "verify.md");
6179
- if (fs29.existsSync(verifyPath)) {
6180
- const verify = fs29.readFileSync(verifyPath, "utf-8");
6452
+ const verifyPath = path28.join(ctx.taskDir, "verify.md");
6453
+ if (fs30.existsSync(verifyPath)) {
6454
+ const verify = fs30.readFileSync(verifyPath, "utf-8");
6181
6455
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
6182
6456
  }
6183
- const planPath = path27.join(ctx.taskDir, "plan.md");
6184
- if (fs29.existsSync(planPath)) {
6185
- const plan = fs29.readFileSync(planPath, "utf-8").trim();
6457
+ const planPath = path28.join(ctx.taskDir, "plan.md");
6458
+ if (fs30.existsSync(planPath)) {
6459
+ const plan = fs30.readFileSync(planPath, "utf-8").trim();
6186
6460
  if (plan) {
6187
6461
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
6188
6462
  sections.push(`
@@ -6202,22 +6476,22 @@ Closes #${ctx.input.issueNumber}`);
6202
6476
  return sections.join("\n");
6203
6477
  }
6204
6478
  function executeShipStage(ctx, _def) {
6205
- const shipPath = path27.join(ctx.taskDir, "ship.md");
6479
+ const shipPath = path28.join(ctx.taskDir, "ship.md");
6206
6480
  if (ctx.input.dryRun) {
6207
- fs29.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
6481
+ fs30.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
6208
6482
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
6209
6483
  }
6210
6484
  if (ctx.input.local && !ctx.input.issueNumber) {
6211
- fs29.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
6485
+ fs30.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
6212
6486
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
6213
6487
  }
6214
6488
  try {
6215
6489
  const head = getCurrentBranch(ctx.projectDir);
6216
6490
  const base = getDefaultBranch(ctx.projectDir);
6217
6491
  try {
6218
- const memoryDir = path27.join(ctx.projectDir, ".kody", "memory");
6492
+ const memoryDir = path28.join(ctx.projectDir, ".kody", "memory");
6219
6493
  const addPaths = [ctx.taskDir];
6220
- if (fs29.existsSync(memoryDir)) addPaths.push(memoryDir);
6494
+ if (fs30.existsSync(memoryDir)) addPaths.push(memoryDir);
6221
6495
  execFileSync17("git", ["add", ...addPaths], {
6222
6496
  cwd: ctx.projectDir,
6223
6497
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -6258,28 +6532,28 @@ function executeShipStage(ctx, _def) {
6258
6532
  chore: "chore"
6259
6533
  };
6260
6534
  let prefix = "chore";
6261
- const taskJsonPath = path27.join(ctx.taskDir, "task.json");
6262
- if (fs29.existsSync(taskJsonPath)) {
6535
+ const taskJsonPath = path28.join(ctx.taskDir, "task.json");
6536
+ if (fs30.existsSync(taskJsonPath)) {
6263
6537
  try {
6264
- const raw = fs29.readFileSync(taskJsonPath, "utf-8");
6538
+ const raw = fs30.readFileSync(taskJsonPath, "utf-8");
6265
6539
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6266
6540
  const task = JSON.parse(cleaned);
6267
6541
  prefix = TYPE_PREFIX[task.task_type] ?? "chore";
6268
6542
  } catch {
6269
6543
  }
6270
6544
  }
6271
- const taskMdPath = path27.join(ctx.taskDir, "task.md");
6272
- if (fs29.existsSync(taskMdPath)) {
6273
- const content = fs29.readFileSync(taskMdPath, "utf-8");
6545
+ const taskMdPath = path28.join(ctx.taskDir, "task.md");
6546
+ if (fs30.existsSync(taskMdPath)) {
6547
+ const content = fs30.readFileSync(taskMdPath, "utf-8");
6274
6548
  const heading = content.split("\n").find((l) => l.startsWith("# "));
6275
6549
  if (heading) {
6276
6550
  title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
6277
6551
  }
6278
6552
  }
6279
6553
  if (title === "Update") {
6280
- if (fs29.existsSync(taskJsonPath)) {
6554
+ if (fs30.existsSync(taskJsonPath)) {
6281
6555
  try {
6282
- const raw = fs29.readFileSync(taskJsonPath, "utf-8");
6556
+ const raw = fs30.readFileSync(taskJsonPath, "utf-8");
6283
6557
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6284
6558
  const task = JSON.parse(cleaned);
6285
6559
  if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
@@ -6302,7 +6576,7 @@ function executeShipStage(ctx, _def) {
6302
6576
  } catch {
6303
6577
  }
6304
6578
  }
6305
- fs29.writeFileSync(shipPath, `# Ship
6579
+ fs30.writeFileSync(shipPath, `# Ship
6306
6580
 
6307
6581
  Updated existing PR: ${existingPr.url}
6308
6582
  PR #${existingPr.number}
@@ -6323,20 +6597,20 @@ PR #${existingPr.number}
6323
6597
  } catch {
6324
6598
  }
6325
6599
  }
6326
- fs29.writeFileSync(shipPath, `# Ship
6600
+ fs30.writeFileSync(shipPath, `# Ship
6327
6601
 
6328
6602
  PR created: ${pr.url}
6329
6603
  PR #${pr.number}
6330
6604
  `);
6331
6605
  } else {
6332
- fs29.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
6606
+ fs30.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
6333
6607
  }
6334
6608
  }
6335
6609
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
6336
6610
  } catch (err) {
6337
6611
  const msg = err instanceof Error ? err.message : String(err);
6338
6612
  try {
6339
- fs29.writeFileSync(shipPath, `# Ship
6613
+ fs30.writeFileSync(shipPath, `# Ship
6340
6614
 
6341
6615
  Failed: ${msg}
6342
6616
  `);
@@ -6385,15 +6659,15 @@ var init_executor_registry = __esm({
6385
6659
  });
6386
6660
 
6387
6661
  // src/pipeline/questions.ts
6388
- import * as fs30 from "fs";
6389
- import * as path28 from "path";
6662
+ import * as fs31 from "fs";
6663
+ import * as path29 from "path";
6390
6664
  function checkForQuestions(ctx, stageName) {
6391
6665
  if (ctx.input.local || !ctx.input.issueNumber) return false;
6392
6666
  try {
6393
6667
  if (stageName === "taskify") {
6394
- const taskJsonPath = path28.join(ctx.taskDir, "task.json");
6395
- if (!fs30.existsSync(taskJsonPath)) return false;
6396
- const raw = fs30.readFileSync(taskJsonPath, "utf-8");
6668
+ const taskJsonPath = path29.join(ctx.taskDir, "task.json");
6669
+ if (!fs31.existsSync(taskJsonPath)) return false;
6670
+ const raw = fs31.readFileSync(taskJsonPath, "utf-8");
6397
6671
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6398
6672
  const taskJson = JSON.parse(cleaned);
6399
6673
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -6408,9 +6682,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
6408
6682
  }
6409
6683
  }
6410
6684
  if (stageName === "plan") {
6411
- const planPath = path28.join(ctx.taskDir, "plan.md");
6412
- if (!fs30.existsSync(planPath)) return false;
6413
- const plan = fs30.readFileSync(planPath, "utf-8");
6685
+ const planPath = path29.join(ctx.taskDir, "plan.md");
6686
+ if (!fs31.existsSync(planPath)) return false;
6687
+ const plan = fs31.readFileSync(planPath, "utf-8");
6414
6688
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
6415
6689
  if (questionsMatch) {
6416
6690
  const questionsText = questionsMatch[1].trim();
@@ -6439,8 +6713,8 @@ var init_questions = __esm({
6439
6713
  });
6440
6714
 
6441
6715
  // src/pipeline/hooks.ts
6442
- import * as fs31 from "fs";
6443
- import * as path29 from "path";
6716
+ import * as fs32 from "fs";
6717
+ import * as path30 from "path";
6444
6718
  function applyPreStageLabel(ctx, def) {
6445
6719
  if (!ctx.input.issueNumber || ctx.input.local) return;
6446
6720
  if (def.name === "plan") setLifecycleLabel(ctx.input.issueNumber, "planning");
@@ -6481,9 +6755,9 @@ function autoDetectComplexity(ctx, def) {
6481
6755
  return { complexity, activeStages };
6482
6756
  }
6483
6757
  try {
6484
- const taskJsonPath = path29.join(ctx.taskDir, "task.json");
6485
- if (!fs31.existsSync(taskJsonPath)) return null;
6486
- const raw = fs31.readFileSync(taskJsonPath, "utf-8");
6758
+ const taskJsonPath = path30.join(ctx.taskDir, "task.json");
6759
+ if (!fs32.existsSync(taskJsonPath)) return null;
6760
+ const raw = fs32.readFileSync(taskJsonPath, "utf-8");
6487
6761
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6488
6762
  const taskJson = JSON.parse(cleaned);
6489
6763
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -6513,8 +6787,8 @@ function checkRiskGate(ctx, def, state, complexity) {
6513
6787
  if (ctx.input.dryRun || ctx.input.local) return null;
6514
6788
  if (ctx.input.mode === "rerun") return null;
6515
6789
  if (!ctx.input.issueNumber) return null;
6516
- const planPath = path29.join(ctx.taskDir, "plan.md");
6517
- const plan = fs31.existsSync(planPath) ? fs31.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
6790
+ const planPath = path30.join(ctx.taskDir, "plan.md");
6791
+ const plan = fs32.existsSync(planPath) ? fs32.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
6518
6792
  try {
6519
6793
  postComment(
6520
6794
  ctx.input.issueNumber,
@@ -6581,22 +6855,22 @@ var init_hooks = __esm({
6581
6855
  });
6582
6856
 
6583
6857
  // src/learning/auto-learn.ts
6584
- import * as fs32 from "fs";
6585
- import * as path30 from "path";
6858
+ import * as fs33 from "fs";
6859
+ import * as path31 from "path";
6586
6860
  function stripAnsi(str) {
6587
6861
  return str.replace(/\x1b\[[0-9;]*m/g, "");
6588
6862
  }
6589
6863
  function autoLearn(ctx) {
6590
6864
  try {
6591
- const memoryDir = path30.join(ctx.projectDir, ".kody", "memory");
6592
- if (!fs32.existsSync(memoryDir)) {
6593
- fs32.mkdirSync(memoryDir, { recursive: true });
6865
+ const memoryDir = path31.join(ctx.projectDir, ".kody", "memory");
6866
+ if (!fs33.existsSync(memoryDir)) {
6867
+ fs33.mkdirSync(memoryDir, { recursive: true });
6594
6868
  }
6595
6869
  const learnings = [];
6596
6870
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6597
- const verifyPath = path30.join(ctx.taskDir, "verify.md");
6598
- if (fs32.existsSync(verifyPath)) {
6599
- const verify = stripAnsi(fs32.readFileSync(verifyPath, "utf-8"));
6871
+ const verifyPath = path31.join(ctx.taskDir, "verify.md");
6872
+ if (fs33.existsSync(verifyPath)) {
6873
+ const verify = stripAnsi(fs33.readFileSync(verifyPath, "utf-8"));
6600
6874
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
6601
6875
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
6602
6876
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -6605,18 +6879,18 @@ function autoLearn(ctx) {
6605
6879
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
6606
6880
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
6607
6881
  }
6608
- const reviewPath = path30.join(ctx.taskDir, "review.md");
6609
- if (fs32.existsSync(reviewPath)) {
6610
- const review = fs32.readFileSync(reviewPath, "utf-8");
6882
+ const reviewPath = path31.join(ctx.taskDir, "review.md");
6883
+ if (fs33.existsSync(reviewPath)) {
6884
+ const review = fs33.readFileSync(reviewPath, "utf-8");
6611
6885
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
6612
6886
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
6613
6887
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
6614
6888
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
6615
6889
  }
6616
- const taskJsonPath = path30.join(ctx.taskDir, "task.json");
6617
- if (fs32.existsSync(taskJsonPath)) {
6890
+ const taskJsonPath = path31.join(ctx.taskDir, "task.json");
6891
+ if (fs33.existsSync(taskJsonPath)) {
6618
6892
  try {
6619
- const raw = stripAnsi(fs32.readFileSync(taskJsonPath, "utf-8"));
6893
+ const raw = stripAnsi(fs33.readFileSync(taskJsonPath, "utf-8"));
6620
6894
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6621
6895
  const task = JSON.parse(cleaned);
6622
6896
  if (task.scope && Array.isArray(task.scope)) {
@@ -6627,12 +6901,12 @@ function autoLearn(ctx) {
6627
6901
  }
6628
6902
  }
6629
6903
  if (learnings.length > 0) {
6630
- const conventionsPath = path30.join(memoryDir, "conventions.md");
6904
+ const conventionsPath = path31.join(memoryDir, "conventions.md");
6631
6905
  const entry = `
6632
6906
  ## Learned ${timestamp2} (task: ${ctx.taskId})
6633
6907
  ${learnings.join("\n")}
6634
6908
  `;
6635
- fs32.appendFileSync(conventionsPath, entry);
6909
+ fs33.appendFileSync(conventionsPath, entry);
6636
6910
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
6637
6911
  }
6638
6912
  autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
@@ -6640,8 +6914,8 @@ ${learnings.join("\n")}
6640
6914
  }
6641
6915
  }
6642
6916
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
6643
- const archPath = path30.join(memoryDir, "architecture.md");
6644
- if (fs32.existsSync(archPath)) return;
6917
+ const archPath = path31.join(memoryDir, "architecture.md");
6918
+ if (fs33.existsSync(archPath)) return;
6645
6919
  const detected = detectArchitectureBasic(projectDir);
6646
6920
  if (detected.length > 0) {
6647
6921
  const content = `# Architecture (auto-detected ${timestamp2})
@@ -6649,7 +6923,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
6649
6923
  ## Overview
6650
6924
  ${detected.join("\n")}
6651
6925
  `;
6652
- fs32.writeFileSync(archPath, content);
6926
+ fs33.writeFileSync(archPath, content);
6653
6927
  logger.info(`Auto-detected architecture (${detected.length} items)`);
6654
6928
  }
6655
6929
  }
@@ -6662,13 +6936,13 @@ var init_auto_learn = __esm({
6662
6936
  });
6663
6937
 
6664
6938
  // src/retrospective.ts
6665
- import * as fs33 from "fs";
6666
- import * as path31 from "path";
6939
+ import * as fs34 from "fs";
6940
+ import * as path32 from "path";
6667
6941
  function readArtifact(taskDir, filename, maxChars) {
6668
- const p = path31.join(taskDir, filename);
6669
- if (!fs33.existsSync(p)) return null;
6942
+ const p = path32.join(taskDir, filename);
6943
+ if (!fs34.existsSync(p)) return null;
6670
6944
  try {
6671
- const content = fs33.readFileSync(p, "utf-8");
6945
+ const content = fs34.readFileSync(p, "utf-8");
6672
6946
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
6673
6947
  } catch {
6674
6948
  return null;
@@ -6721,13 +6995,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
6721
6995
  return lines.join("\n");
6722
6996
  }
6723
6997
  function getLogPath(projectDir) {
6724
- return path31.join(projectDir, ".kody", "memory", "observer-log.jsonl");
6998
+ return path32.join(projectDir, ".kody", "memory", "observer-log.jsonl");
6725
6999
  }
6726
7000
  function readPreviousRetrospectives(projectDir, limit = 10) {
6727
7001
  const logPath = getLogPath(projectDir);
6728
- if (!fs33.existsSync(logPath)) return [];
7002
+ if (!fs34.existsSync(logPath)) return [];
6729
7003
  try {
6730
- const content = fs33.readFileSync(logPath, "utf-8");
7004
+ const content = fs34.readFileSync(logPath, "utf-8");
6731
7005
  const lines = content.split("\n").filter(Boolean);
6732
7006
  const entries = [];
6733
7007
  const start = Math.max(0, lines.length - limit);
@@ -6754,11 +7028,11 @@ function formatPreviousEntries(entries) {
6754
7028
  }
6755
7029
  function appendRetrospectiveEntry(projectDir, entry) {
6756
7030
  const logPath = getLogPath(projectDir);
6757
- const dir = path31.dirname(logPath);
6758
- if (!fs33.existsSync(dir)) {
6759
- fs33.mkdirSync(dir, { recursive: true });
7031
+ const dir = path32.dirname(logPath);
7032
+ if (!fs34.existsSync(dir)) {
7033
+ fs34.mkdirSync(dir, { recursive: true });
6760
7034
  }
6761
- fs33.appendFileSync(logPath, JSON.stringify(entry) + "\n");
7035
+ fs34.appendFileSync(logPath, JSON.stringify(entry) + "\n");
6762
7036
  }
6763
7037
  async function runRetrospective(ctx, state, pipelineStartTime) {
6764
7038
  if (ctx.input.dryRun) return;
@@ -6926,15 +7200,15 @@ var init_summary = __esm({
6926
7200
  });
6927
7201
 
6928
7202
  // src/tools.ts
6929
- import * as fs34 from "fs";
6930
- import * as path32 from "path";
7203
+ import * as fs35 from "fs";
7204
+ import * as path33 from "path";
6931
7205
  import { execSync as execSync3 } from "child_process";
6932
7206
  import { parse as parseYaml } from "yaml";
6933
7207
  function loadToolDeclarations(projectDir) {
6934
- const toolsPath = path32.join(projectDir, ".kody", "tools.yml");
6935
- if (!fs34.existsSync(toolsPath)) return [];
7208
+ const toolsPath = path33.join(projectDir, ".kody", "tools.yml");
7209
+ if (!fs35.existsSync(toolsPath)) return [];
6936
7210
  try {
6937
- const raw = fs34.readFileSync(toolsPath, "utf-8");
7211
+ const raw = fs35.readFileSync(toolsPath, "utf-8");
6938
7212
  const parsed = parseYaml(raw);
6939
7213
  if (!parsed || typeof parsed !== "object") return [];
6940
7214
  return Object.entries(parsed).map(([name, value]) => {
@@ -6955,7 +7229,7 @@ function loadToolDeclarations(projectDir) {
6955
7229
  function detectTools(declarations, projectDir) {
6956
7230
  const resolved = [];
6957
7231
  for (const decl of declarations) {
6958
- const detected = decl.detect.some((pattern) => fs34.existsSync(path32.join(projectDir, pattern)));
7232
+ const detected = decl.detect.some((pattern) => fs35.existsSync(path33.join(projectDir, pattern)));
6959
7233
  if (!detected) continue;
6960
7234
  resolved.push({
6961
7235
  name: decl.name,
@@ -6996,8 +7270,8 @@ var init_tools = __esm({
6996
7270
  });
6997
7271
 
6998
7272
  // src/pipeline.ts
6999
- import * as fs35 from "fs";
7000
- import * as path33 from "path";
7273
+ import * as fs36 from "fs";
7274
+ import * as path34 from "path";
7001
7275
  function ensureFeatureBranchIfNeeded(ctx) {
7002
7276
  if (ctx.input.dryRun) return;
7003
7277
  if (ctx.input.prNumber) {
@@ -7010,8 +7284,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
7010
7284
  }
7011
7285
  if (!ctx.input.issueNumber) return;
7012
7286
  try {
7013
- const taskMdPath = path33.join(ctx.taskDir, "task.md");
7014
- const title = fs35.existsSync(taskMdPath) ? fs35.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
7287
+ const taskMdPath = path34.join(ctx.taskDir, "task.md");
7288
+ const title = fs36.existsSync(taskMdPath) ? fs36.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
7015
7289
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
7016
7290
  syncWithDefault(ctx.projectDir);
7017
7291
  } catch (err) {
@@ -7025,10 +7299,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
7025
7299
  }
7026
7300
  }
7027
7301
  function acquireLock(taskDir) {
7028
- const lockPath = path33.join(taskDir, ".lock");
7029
- if (fs35.existsSync(lockPath)) {
7302
+ const lockPath = path34.join(taskDir, ".lock");
7303
+ if (fs36.existsSync(lockPath)) {
7030
7304
  try {
7031
- const pid = parseInt(fs35.readFileSync(lockPath, "utf-8").trim(), 10);
7305
+ const pid = parseInt(fs36.readFileSync(lockPath, "utf-8").trim(), 10);
7032
7306
  if (!isNaN(pid)) {
7033
7307
  try {
7034
7308
  process.kill(pid, 0);
@@ -7045,14 +7319,14 @@ function acquireLock(taskDir) {
7045
7319
  logger.warn(` Corrupt lock file \u2014 overwriting`);
7046
7320
  }
7047
7321
  try {
7048
- fs35.unlinkSync(lockPath);
7322
+ fs36.unlinkSync(lockPath);
7049
7323
  } catch {
7050
7324
  }
7051
7325
  }
7052
7326
  try {
7053
- const fd = fs35.openSync(lockPath, fs35.constants.O_WRONLY | fs35.constants.O_CREAT | fs35.constants.O_EXCL);
7054
- fs35.writeSync(fd, String(process.pid));
7055
- fs35.closeSync(fd);
7327
+ const fd = fs36.openSync(lockPath, fs36.constants.O_WRONLY | fs36.constants.O_CREAT | fs36.constants.O_EXCL);
7328
+ fs36.writeSync(fd, String(process.pid));
7329
+ fs36.closeSync(fd);
7056
7330
  } catch (err) {
7057
7331
  if (err.code === "EEXIST") {
7058
7332
  throw new Error("Pipeline already running (lock acquired by another process)");
@@ -7062,7 +7336,7 @@ function acquireLock(taskDir) {
7062
7336
  }
7063
7337
  function releaseLock(taskDir) {
7064
7338
  try {
7065
- fs35.unlinkSync(path33.join(taskDir, ".lock"));
7339
+ fs36.unlinkSync(path34.join(taskDir, ".lock"));
7066
7340
  } catch {
7067
7341
  }
7068
7342
  }
@@ -7279,7 +7553,7 @@ var init_pipeline = __esm({
7279
7553
 
7280
7554
  // src/preflight.ts
7281
7555
  import { execFileSync as execFileSync18 } from "child_process";
7282
- import * as fs36 from "fs";
7556
+ import * as fs37 from "fs";
7283
7557
  function check(name, fn) {
7284
7558
  try {
7285
7559
  const detail = fn() ?? void 0;
@@ -7332,7 +7606,7 @@ function runPreflight() {
7332
7606
  return v;
7333
7607
  }),
7334
7608
  check("package.json", () => {
7335
- if (!fs36.existsSync("package.json")) throw new Error("not found");
7609
+ if (!fs37.existsSync("package.json")) throw new Error("not found");
7336
7610
  })
7337
7611
  ];
7338
7612
  const failed = checks.filter((c) => !c.ok);
@@ -7409,8 +7683,8 @@ var init_args = __esm({
7409
7683
  });
7410
7684
 
7411
7685
  // src/cli/task-state.ts
7412
- import * as fs37 from "fs";
7413
- import * as path34 from "path";
7686
+ import * as fs38 from "fs";
7687
+ import * as path35 from "path";
7414
7688
  function resolveTaskAction(issueNumber, existingTaskId, existingState) {
7415
7689
  if (!existingTaskId || !existingState) {
7416
7690
  return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
@@ -7442,11 +7716,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
7442
7716
  function resolveForIssue(issueNumber, projectDir) {
7443
7717
  const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
7444
7718
  if (existingTaskId) {
7445
- const statusPath = path34.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
7719
+ const statusPath = path35.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
7446
7720
  let existingState = null;
7447
- if (fs37.existsSync(statusPath)) {
7721
+ if (fs38.existsSync(statusPath)) {
7448
7722
  try {
7449
- existingState = JSON.parse(fs37.readFileSync(statusPath, "utf-8"));
7723
+ existingState = JSON.parse(fs38.readFileSync(statusPath, "utf-8"));
7450
7724
  } catch {
7451
7725
  }
7452
7726
  }
@@ -7642,8 +7916,8 @@ var init_resolve = __esm({
7642
7916
 
7643
7917
  // src/entry.ts
7644
7918
  var entry_exports = {};
7645
- import * as fs38 from "fs";
7646
- import * as path35 from "path";
7919
+ import * as fs39 from "fs";
7920
+ import * as path36 from "path";
7647
7921
  async function ensureLitellmProxy(config, projectDir) {
7648
7922
  if (!anyStageNeedsProxy(config)) return null;
7649
7923
  const litellmUrl = getLitellmUrl();
@@ -7698,9 +7972,9 @@ async function runModelHealthCheck(config) {
7698
7972
  }
7699
7973
  async function main() {
7700
7974
  const input = parseArgs();
7701
- const projectDir = input.cwd ? path35.resolve(input.cwd) : process.cwd();
7975
+ const projectDir = input.cwd ? path36.resolve(input.cwd) : process.cwd();
7702
7976
  if (input.cwd) {
7703
- if (!fs38.existsSync(projectDir)) {
7977
+ if (!fs39.existsSync(projectDir)) {
7704
7978
  console.error(`--cwd path does not exist: ${projectDir}`);
7705
7979
  process.exit(1);
7706
7980
  }
@@ -7760,8 +8034,8 @@ async function main() {
7760
8034
  process.exit(1);
7761
8035
  }
7762
8036
  }
7763
- const taskDir = path35.join(projectDir, ".kody", "tasks", taskId);
7764
- fs38.mkdirSync(taskDir, { recursive: true });
8037
+ const taskDir = path36.join(projectDir, ".kody", "tasks", taskId);
8038
+ fs39.mkdirSync(taskDir, { recursive: true });
7765
8039
  if (input.command === "rerun" && isTaskifyRun(taskDir)) {
7766
8040
  const marker = readTaskifyMarker(taskDir);
7767
8041
  if (marker) {
@@ -7893,31 +8167,31 @@ async function main() {
7893
8167
  logger.info("Preflight checks:");
7894
8168
  runPreflight();
7895
8169
  if (input.task) {
7896
- fs38.writeFileSync(path35.join(taskDir, "task.md"), input.task);
8170
+ fs39.writeFileSync(path36.join(taskDir, "task.md"), input.task);
7897
8171
  }
7898
- const taskMdPath = path35.join(taskDir, "task.md");
7899
- if (!fs38.existsSync(taskMdPath) && isPRFix && input.prNumber) {
8172
+ const taskMdPath = path36.join(taskDir, "task.md");
8173
+ if (!fs39.existsSync(taskMdPath) && isPRFix && input.prNumber) {
7900
8174
  logger.info(`Fetching PR #${input.prNumber} details as task context...`);
7901
8175
  const prDetails = getPRDetails(input.prNumber);
7902
8176
  if (prDetails) {
7903
8177
  const taskContent = `# ${prDetails.title}
7904
8178
 
7905
8179
  ${prDetails.body ?? ""}`;
7906
- fs38.writeFileSync(taskMdPath, taskContent);
8180
+ fs39.writeFileSync(taskMdPath, taskContent);
7907
8181
  logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
7908
8182
  }
7909
- } else if (!fs38.existsSync(taskMdPath) && input.issueNumber) {
8183
+ } else if (!fs39.existsSync(taskMdPath) && input.issueNumber) {
7910
8184
  logger.info(`Fetching issue #${input.issueNumber} body as task...`);
7911
8185
  const issue = getIssue(input.issueNumber);
7912
8186
  if (issue) {
7913
8187
  const taskContent = `# ${issue.title}
7914
8188
 
7915
8189
  ${issue.body ?? ""}`;
7916
- fs38.writeFileSync(taskMdPath, taskContent);
8190
+ fs39.writeFileSync(taskMdPath, taskContent);
7917
8191
  logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
7918
8192
  }
7919
8193
  }
7920
- if (!fs38.existsSync(taskMdPath)) {
8194
+ if (!fs39.existsSync(taskMdPath)) {
7921
8195
  console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
7922
8196
  process.exit(1);
7923
8197
  }
@@ -8061,7 +8335,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
8061
8335
  }
8062
8336
  }
8063
8337
  const state = await runPipeline(ctx);
8064
- const files = fs38.readdirSync(taskDir);
8338
+ const files = fs39.readdirSync(taskDir);
8065
8339
  console.log(`
8066
8340
  Artifacts in ${taskDir}:`);
8067
8341
  for (const f of files) {
@@ -8143,8 +8417,8 @@ var init_entry = __esm({
8143
8417
  });
8144
8418
 
8145
8419
  // src/bin/cli.ts
8146
- import * as fs39 from "fs";
8147
- import * as path36 from "path";
8420
+ import * as fs40 from "fs";
8421
+ import * as path37 from "path";
8148
8422
  import { fileURLToPath as fileURLToPath2 } from "url";
8149
8423
 
8150
8424
  // src/bin/commands/init.ts
@@ -8475,6 +8749,24 @@ function initCommand(opts, pkgRoot) {
8475
8749
  console.log(" \u2139 Kody Watch will monitor pipeline health every 30 minutes");
8476
8750
  console.log(" \u2139 Digest issue will be created during bootstrap");
8477
8751
  }
8752
+ const agentTemplatesDir = path2.join(templatesDir, "watch-agents");
8753
+ const watchAgentsDir = path2.join(cwd, ".kody", "watch", "agents");
8754
+ if (fs3.existsSync(agentTemplatesDir)) {
8755
+ const agentDirs = fs3.readdirSync(agentTemplatesDir, { withFileTypes: true }).filter((d) => d.isDirectory());
8756
+ for (const agentDir of agentDirs) {
8757
+ const destDir = path2.join(watchAgentsDir, agentDir.name);
8758
+ if (fs3.existsSync(destDir) && !opts.force) {
8759
+ console.log(` \u25CB .kody/watch/agents/${agentDir.name} (exists)`);
8760
+ continue;
8761
+ }
8762
+ fs3.mkdirSync(destDir, { recursive: true });
8763
+ const srcDir = path2.join(agentTemplatesDir, agentDir.name);
8764
+ for (const file of fs3.readdirSync(srcDir)) {
8765
+ fs3.copyFileSync(path2.join(srcDir, file), path2.join(destDir, file));
8766
+ }
8767
+ console.log(` \u2713 .kody/watch/agents/${agentDir.name}`);
8768
+ }
8769
+ }
8478
8770
  } else {
8479
8771
  console.log(" \u25CB kody-watch.yml template not found \u2014 skipping");
8480
8772
  }
@@ -9984,11 +10276,11 @@ Create it manually.`, cwd);
9984
10276
 
9985
10277
  // src/bin/cli.ts
9986
10278
  init_architecture_detection();
9987
- var __dirname2 = path36.dirname(fileURLToPath2(import.meta.url));
9988
- var PKG_ROOT = path36.resolve(__dirname2, "..", "..");
10279
+ var __dirname2 = path37.dirname(fileURLToPath2(import.meta.url));
10280
+ var PKG_ROOT = path37.resolve(__dirname2, "..", "..");
9989
10281
  function getVersion() {
9990
- const pkgPath = path36.join(PKG_ROOT, "package.json");
9991
- const pkg = JSON.parse(fs39.readFileSync(pkgPath, "utf-8"));
10282
+ const pkgPath = path37.join(PKG_ROOT, "package.json");
10283
+ const pkg = JSON.parse(fs40.readFileSync(pkgPath, "utf-8"));
9992
10284
  return pkg.version;
9993
10285
  }
9994
10286
  var args = process.argv.slice(2);