@kody-ade/kody-engine-lite 0.1.147 → 0.1.149

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");
@@ -3287,9 +3292,92 @@ var init_logger2 = __esm({
3287
3292
  }
3288
3293
  });
3289
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
+
3290
3378
  // src/watch/core/watch.ts
3291
3379
  async function runWatch(config) {
3292
- const { repo, dryRun, stateFile, plugins } = config;
3380
+ const { repo, dryRun, stateFile, plugins, agents } = config;
3293
3381
  const token = process.env.GH_TOKEN || "";
3294
3382
  const github = createGitHubClient(repo, token);
3295
3383
  const state = createStateStore(stateFile, github, config.digestIssue);
@@ -3370,6 +3458,45 @@ async function runWatch(config) {
3370
3458
  } else {
3371
3459
  log2.info({ actionCount: dedupedActions.length }, "Dry run \u2014 skipping action execution");
3372
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
+ }
3373
3500
  state.save();
3374
3501
  const result2 = {
3375
3502
  cycleNumber,
@@ -3377,6 +3504,8 @@ async function runWatch(config) {
3377
3504
  actionsProduced: allActions.length,
3378
3505
  actionsExecuted,
3379
3506
  actionsDeduplicated,
3507
+ agentsRun: scheduledAgents.length,
3508
+ agentResults,
3380
3509
  errors
3381
3510
  };
3382
3511
  log2.info(
@@ -3386,6 +3515,7 @@ async function runWatch(config) {
3386
3515
  actionsProduced: result2.actionsProduced,
3387
3516
  actionsExecuted: result2.actionsExecuted,
3388
3517
  actionsDeduplicated: result2.actionsDeduplicated,
3518
+ agentsRun: result2.agentsRun,
3389
3519
  errors: result2.errors.length
3390
3520
  },
3391
3521
  "Watch cycle completed"
@@ -3399,6 +3529,7 @@ var init_watch = __esm({
3399
3529
  init_dedup();
3400
3530
  init_github();
3401
3531
  init_logger2();
3532
+ init_run_agent();
3402
3533
  }
3403
3534
  });
3404
3535
 
@@ -4109,27 +4240,134 @@ var init_config_health = __esm({
4109
4240
  }
4110
4241
  });
4111
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
+
4112
4333
  // src/watch/index.ts
4113
4334
  var watch_exports = {};
4114
4335
  __export(watch_exports, {
4115
4336
  runWatchCommand: () => runWatchCommand
4116
4337
  });
4117
- import * as fs19 from "fs";
4118
- import * as path17 from "path";
4338
+ import * as fs20 from "fs";
4339
+ import * as path18 from "path";
4119
4340
  async function runWatchCommand(opts) {
4120
4341
  const cwd = process.cwd();
4121
- const configPath = path17.join(cwd, "kody.config.json");
4342
+ let litellmProcess = null;
4343
+ const configPath = path18.join(cwd, "kody.config.json");
4122
4344
  let repo = process.env.REPO || "";
4123
4345
  let digestIssue;
4124
- 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)) {
4125
4351
  try {
4126
- const config2 = JSON.parse(fs19.readFileSync(configPath, "utf-8"));
4352
+ const config2 = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
4127
4353
  if (config2.github?.owner && config2.github?.repo) {
4128
4354
  repo = `${config2.github.owner}/${config2.github.repo}`;
4129
4355
  }
4130
4356
  if (config2.watch?.digestIssue) {
4131
4357
  digestIssue = config2.watch.digestIssue;
4132
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
+ }
4133
4371
  } catch {
4134
4372
  }
4135
4373
  }
@@ -4148,12 +4386,43 @@ async function runWatchCommand(opts) {
4148
4386
  registry.register(pipelineHealthPlugin);
4149
4387
  registry.register(securityScanPlugin);
4150
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
+ }
4151
4416
  const config = {
4152
4417
  repo,
4153
4418
  dryRun: opts.dryRun,
4154
- stateFile: path17.join(cwd, ".kody", "watch-state.json"),
4419
+ stateFile: path18.join(cwd, ".kody", "watch-state.json"),
4155
4420
  plugins: registry.getAll(),
4156
- digestIssue
4421
+ digestIssue,
4422
+ agents,
4423
+ model,
4424
+ provider: agentProvider,
4425
+ projectDir: cwd
4157
4426
  };
4158
4427
  console.log(`
4159
4428
  Kody Watch \u2014 repo: ${repo}, dry-run: ${opts.dryRun}
@@ -4165,13 +4434,17 @@ Kody Watch \u2014 repo: ${repo}, dry-run: ${opts.dryRun}
4165
4434
  console.warn(` Warning: ${error}`);
4166
4435
  }
4167
4436
  }
4437
+ const agentSummary = result2.agentsRun > 0 ? `, ${result2.agentsRun} agents` : "";
4168
4438
  console.log(`
4169
- Cycle #${result2.cycleNumber} complete: ${result2.pluginsRun} plugins, ${result2.actionsExecuted} actions, ${result2.actionsDeduplicated} deduplicated`);
4170
- process.exit(0);
4439
+ Cycle #${result2.cycleNumber} complete: ${result2.pluginsRun} plugins${agentSummary}, ${result2.actionsExecuted} actions, ${result2.actionsDeduplicated} deduplicated`);
4171
4440
  } catch (error) {
4172
4441
  const message = error instanceof Error ? error.message : String(error);
4173
4442
  console.error(`Watch failed: ${message}`);
4174
- process.exit(1);
4443
+ } finally {
4444
+ if (litellmProcess) {
4445
+ litellmProcess.kill("SIGTERM");
4446
+ }
4447
+ process.exit(0);
4175
4448
  }
4176
4449
  }
4177
4450
  var init_watch2 = __esm({
@@ -4182,6 +4455,9 @@ var init_watch2 = __esm({
4182
4455
  init_pipeline_health();
4183
4456
  init_security_scan();
4184
4457
  init_config_health();
4458
+ init_loader();
4459
+ init_litellm();
4460
+ init_config();
4185
4461
  }
4186
4462
  });
4187
4463
 
@@ -4460,14 +4736,14 @@ var init_git_utils = __esm({
4460
4736
  });
4461
4737
 
4462
4738
  // src/pipeline/state.ts
4463
- import * as fs20 from "fs";
4464
- import * as path18 from "path";
4739
+ import * as fs21 from "fs";
4740
+ import * as path19 from "path";
4465
4741
  function loadState(taskId, taskDir) {
4466
- const p = path18.join(taskDir, "status.json");
4467
- if (!fs20.existsSync(p)) return null;
4742
+ const p = path19.join(taskDir, "status.json");
4743
+ if (!fs21.existsSync(p)) return null;
4468
4744
  try {
4469
4745
  const result2 = parseJsonSafe(
4470
- fs20.readFileSync(p, "utf-8"),
4746
+ fs21.readFileSync(p, "utf-8"),
4471
4747
  ["taskId", "state", "stages", "createdAt", "updatedAt"]
4472
4748
  );
4473
4749
  if (!result2.ok) {
@@ -4485,10 +4761,10 @@ function writeState(state, taskDir) {
4485
4761
  ...state,
4486
4762
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4487
4763
  };
4488
- const target = path18.join(taskDir, "status.json");
4764
+ const target = path19.join(taskDir, "status.json");
4489
4765
  const tmp = target + ".tmp";
4490
- fs20.writeFileSync(tmp, JSON.stringify(updated, null, 2));
4491
- fs20.renameSync(tmp, target);
4766
+ fs21.writeFileSync(tmp, JSON.stringify(updated, null, 2));
4767
+ fs21.renameSync(tmp, target);
4492
4768
  return updated;
4493
4769
  }
4494
4770
  function initState(taskId) {
@@ -4529,16 +4805,16 @@ var init_complexity = __esm({
4529
4805
  });
4530
4806
 
4531
4807
  // src/memory.ts
4532
- import * as fs21 from "fs";
4533
- import * as path19 from "path";
4808
+ import * as fs22 from "fs";
4809
+ import * as path20 from "path";
4534
4810
  function readProjectMemory(projectDir) {
4535
- const memoryDir = path19.join(projectDir, ".kody", "memory");
4536
- if (!fs21.existsSync(memoryDir)) return "";
4537
- 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();
4538
4814
  if (files.length === 0) return "";
4539
4815
  const sections = [];
4540
4816
  for (const file of files) {
4541
- const content = fs21.readFileSync(path19.join(memoryDir, file), "utf-8").trim();
4817
+ const content = fs22.readFileSync(path20.join(memoryDir, file), "utf-8").trim();
4542
4818
  if (content) {
4543
4819
  sections.push(`## ${file.replace(".md", "")}
4544
4820
  ${content}`);
@@ -4557,8 +4833,8 @@ var init_memory = __esm({
4557
4833
  });
4558
4834
 
4559
4835
  // src/context-tiers.ts
4560
- import * as fs22 from "fs";
4561
- import * as path20 from "path";
4836
+ import * as fs23 from "fs";
4837
+ import * as path21 from "path";
4562
4838
  function estimateTokens(text) {
4563
4839
  return Math.ceil(text.length / 4);
4564
4840
  }
@@ -4649,7 +4925,7 @@ function generateL1Json(content) {
4649
4925
  }
4650
4926
  }
4651
4927
  function getTieredContent(filePath, content) {
4652
- const key = path20.basename(filePath);
4928
+ const key = path21.basename(filePath);
4653
4929
  return {
4654
4930
  source: filePath,
4655
4931
  L0: generateL0(content, key),
@@ -4661,15 +4937,15 @@ function selectTier(tiered, tier) {
4661
4937
  return tiered[tier];
4662
4938
  }
4663
4939
  function readProjectMemoryTiered(projectDir, tier) {
4664
- const memoryDir = path20.join(projectDir, ".kody", "memory");
4665
- if (!fs22.existsSync(memoryDir)) return "";
4666
- 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();
4667
4943
  if (files.length === 0) return "";
4668
4944
  const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
4669
4945
  const sections = [];
4670
4946
  for (const file of files) {
4671
- const filePath = path20.join(memoryDir, file);
4672
- const content = fs22.readFileSync(filePath, "utf-8").trim();
4947
+ const filePath = path21.join(memoryDir, file);
4948
+ const content = fs23.readFileSync(filePath, "utf-8").trim();
4673
4949
  if (!content) continue;
4674
4950
  const tiered = getTieredContent(filePath, content);
4675
4951
  const selected = selectTier(tiered, tier);
@@ -4692,9 +4968,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
4692
4968
  `;
4693
4969
  context += `Task Directory: ${taskDir}
4694
4970
  `;
4695
- const taskMdPath = path20.join(taskDir, "task.md");
4696
- if (fs22.existsSync(taskMdPath)) {
4697
- 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");
4698
4974
  const selected = selectContent(taskMdPath, content, policy.taskDescription);
4699
4975
  const label = tierLabel("Task Description", policy.taskDescription);
4700
4976
  context += `
@@ -4702,9 +4978,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
4702
4978
  ${selected}
4703
4979
  `;
4704
4980
  }
4705
- const taskJsonPath = path20.join(taskDir, "task.json");
4706
- if (fs22.existsSync(taskJsonPath)) {
4707
- 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");
4708
4984
  if (policy.taskClassification === "L2") {
4709
4985
  try {
4710
4986
  const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
@@ -4730,9 +5006,9 @@ ${selected}
4730
5006
  }
4731
5007
  }
4732
5008
  }
4733
- const specPath = path20.join(taskDir, "spec.md");
4734
- if (fs22.existsSync(specPath)) {
4735
- 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");
4736
5012
  const selected = selectContent(specPath, content, policy.spec);
4737
5013
  const label = tierLabel("Spec", policy.spec);
4738
5014
  context += `
@@ -4740,9 +5016,9 @@ ${selected}
4740
5016
  ${selected}
4741
5017
  `;
4742
5018
  }
4743
- const planPath = path20.join(taskDir, "plan.md");
4744
- if (fs22.existsSync(planPath)) {
4745
- 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");
4746
5022
  const selected = selectContent(planPath, content, policy.plan);
4747
5023
  const label = tierLabel("Plan", policy.plan);
4748
5024
  context += `
@@ -4750,9 +5026,9 @@ ${selected}
4750
5026
  ${selected}
4751
5027
  `;
4752
5028
  }
4753
- const contextMdPath = path20.join(taskDir, "context.md");
4754
- if (fs22.existsSync(contextMdPath)) {
4755
- 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");
4756
5032
  const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
4757
5033
  const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
4758
5034
  context += `
@@ -4838,24 +5114,24 @@ var init_context_tiers = __esm({
4838
5114
  });
4839
5115
 
4840
5116
  // src/context.ts
4841
- import * as fs23 from "fs";
4842
- import * as path21 from "path";
5117
+ import * as fs24 from "fs";
5118
+ import * as path22 from "path";
4843
5119
  function readPromptFile(stageName, projectDir) {
4844
5120
  if (projectDir) {
4845
- const stepFile = path21.join(projectDir, ".kody", "steps", `${stageName}.md`);
4846
- if (fs23.existsSync(stepFile)) {
4847
- 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");
4848
5124
  }
4849
5125
  console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
4850
5126
  }
4851
5127
  const scriptDir = new URL(".", import.meta.url).pathname;
4852
5128
  const candidates = [
4853
- path21.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
4854
- path21.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
5129
+ path22.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
5130
+ path22.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
4855
5131
  ];
4856
5132
  for (const candidate of candidates) {
4857
- if (fs23.existsSync(candidate)) {
4858
- return fs23.readFileSync(candidate, "utf-8");
5133
+ if (fs24.existsSync(candidate)) {
5134
+ return fs24.readFileSync(candidate, "utf-8");
4859
5135
  }
4860
5136
  }
4861
5137
  throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
@@ -4867,18 +5143,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
4867
5143
  `;
4868
5144
  context += `Task Directory: ${taskDir}
4869
5145
  `;
4870
- const taskMdPath = path21.join(taskDir, "task.md");
4871
- if (fs23.existsSync(taskMdPath)) {
4872
- 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");
4873
5149
  context += `
4874
5150
  ## Task Description
4875
5151
  ${taskMd}
4876
5152
  `;
4877
5153
  }
4878
- const taskJsonPath = path21.join(taskDir, "task.json");
4879
- if (fs23.existsSync(taskJsonPath)) {
5154
+ const taskJsonPath = path22.join(taskDir, "task.json");
5155
+ if (fs24.existsSync(taskJsonPath)) {
4880
5156
  try {
4881
- const taskDef = JSON.parse(fs23.readFileSync(taskJsonPath, "utf-8"));
5157
+ const taskDef = JSON.parse(fs24.readFileSync(taskJsonPath, "utf-8"));
4882
5158
  context += `
4883
5159
  ## Task Classification
4884
5160
  `;
@@ -4891,27 +5167,27 @@ ${taskMd}
4891
5167
  } catch {
4892
5168
  }
4893
5169
  }
4894
- const specPath = path21.join(taskDir, "spec.md");
4895
- if (fs23.existsSync(specPath)) {
4896
- 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");
4897
5173
  const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
4898
5174
  context += `
4899
5175
  ## Spec Summary
4900
5176
  ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
4901
5177
  `;
4902
5178
  }
4903
- const planPath = path21.join(taskDir, "plan.md");
4904
- if (fs23.existsSync(planPath)) {
4905
- 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");
4906
5182
  const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
4907
5183
  context += `
4908
5184
  ## Plan Summary
4909
5185
  ${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
4910
5186
  `;
4911
5187
  }
4912
- const contextMdPath = path21.join(taskDir, "context.md");
4913
- if (fs23.existsSync(contextMdPath)) {
4914
- 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");
4915
5191
  const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
4916
5192
  const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
4917
5193
  context += `
@@ -4929,17 +5205,17 @@ ${feedback}
4929
5205
  }
4930
5206
  function inferHasUIFromScope(scope) {
4931
5207
  return scope.some((filePath) => {
4932
- const ext = path21.extname(filePath).toLowerCase();
5208
+ const ext = path22.extname(filePath).toLowerCase();
4933
5209
  if (UI_EXTENSIONS.has(ext)) return true;
4934
5210
  const normalized = filePath.replace(/\\/g, "/");
4935
5211
  return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
4936
5212
  });
4937
5213
  }
4938
5214
  function taskHasUI(taskDir) {
4939
- const taskJsonPath = path21.join(taskDir, "task.json");
4940
- if (!fs23.existsSync(taskJsonPath)) return true;
5215
+ const taskJsonPath = path22.join(taskDir, "task.json");
5216
+ if (!fs24.existsSync(taskJsonPath)) return true;
4941
5217
  try {
4942
- const taskDef = JSON.parse(fs23.readFileSync(taskJsonPath, "utf-8"));
5218
+ const taskDef = JSON.parse(fs24.readFileSync(taskJsonPath, "utf-8"));
4943
5219
  const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
4944
5220
  if (scope.length === 0) return true;
4945
5221
  return inferHasUIFromScope(scope);
@@ -5125,9 +5401,9 @@ ${prompt}` : prompt;
5125
5401
  const hasBrowserTools = isMcpEnabledForStage(stageName, config.mcp) || config.devServer && browserStages.includes(stageName);
5126
5402
  if (hasBrowserTools && taskHasUI(taskDir)) {
5127
5403
  assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
5128
- const qaGuidePath = path21.join(projectDir, ".kody", "qa-guide.md");
5129
- if (fs23.existsSync(qaGuidePath)) {
5130
- 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();
5131
5407
  assembled = assembled + "\n\n" + qaGuide;
5132
5408
  }
5133
5409
  }
@@ -5237,7 +5513,7 @@ async function waitForReady(url, timeoutSec, stdoutMatch) {
5237
5513
  while (Date.now() < deadline) {
5238
5514
  if (stdoutMatch()) {
5239
5515
  logger.info(" Dev server stdout matched ready pattern");
5240
- 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));
5241
5517
  return pollReady(url, httpTimeout);
5242
5518
  }
5243
5519
  await new Promise((r) => setTimeout(r, 500));
@@ -5315,8 +5591,8 @@ var init_dev_server = __esm({
5315
5591
  });
5316
5592
 
5317
5593
  // src/stages/agent.ts
5318
- import * as fs24 from "fs";
5319
- import * as path22 from "path";
5594
+ import * as fs25 from "fs";
5595
+ import * as path23 from "path";
5320
5596
  function getSessionInfo(stageName, sessions) {
5321
5597
  const group = SESSION_GROUP[stageName];
5322
5598
  if (!group) return void 0;
@@ -5438,27 +5714,27 @@ async function executeAgentStage(ctx, def) {
5438
5714
  }
5439
5715
  const result2 = lastResult;
5440
5716
  if (def.outputFile && result2.output) {
5441
- fs24.writeFileSync(path22.join(ctx.taskDir, def.outputFile), result2.output);
5717
+ fs25.writeFileSync(path23.join(ctx.taskDir, def.outputFile), result2.output);
5442
5718
  }
5443
5719
  if (def.outputFile) {
5444
- const outputPath = path22.join(ctx.taskDir, def.outputFile);
5445
- if (!fs24.existsSync(outputPath)) {
5446
- const ext = path22.extname(def.outputFile);
5447
- const base = path22.basename(def.outputFile, ext);
5448
- 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);
5449
5725
  const variant = files.find(
5450
5726
  (f) => f.startsWith(base + "-") && f.endsWith(ext)
5451
5727
  );
5452
5728
  if (variant) {
5453
- fs24.renameSync(path22.join(ctx.taskDir, variant), outputPath);
5729
+ fs25.renameSync(path23.join(ctx.taskDir, variant), outputPath);
5454
5730
  logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
5455
5731
  }
5456
5732
  }
5457
5733
  }
5458
5734
  if (def.outputFile) {
5459
- const outputPath = path22.join(ctx.taskDir, def.outputFile);
5460
- if (fs24.existsSync(outputPath)) {
5461
- 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");
5462
5738
  const validation = validateStageOutput(def.name, content);
5463
5739
  if (!validation.valid) {
5464
5740
  if (def.name === "taskify") {
@@ -5472,7 +5748,7 @@ async function executeAgentStage(ctx, def) {
5472
5748
  const stripped = stripFences(retryResult.output);
5473
5749
  const retryValidation = validateTaskJson(stripped);
5474
5750
  if (retryValidation.valid) {
5475
- fs24.writeFileSync(outputPath, retryResult.output);
5751
+ fs25.writeFileSync(outputPath, retryResult.output);
5476
5752
  logger.info(` taskify retry produced valid JSON`);
5477
5753
  } else {
5478
5754
  logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
@@ -5485,7 +5761,7 @@ async function executeAgentStage(ctx, def) {
5485
5761
  risk_level: "low",
5486
5762
  questions: []
5487
5763
  }, null, 2);
5488
- fs24.writeFileSync(outputPath, fallback);
5764
+ fs25.writeFileSync(outputPath, fallback);
5489
5765
  logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
5490
5766
  }
5491
5767
  }
@@ -5499,7 +5775,7 @@ async function executeAgentStage(ctx, def) {
5499
5775
  return { outcome: "completed", outputFile: def.outputFile, retries };
5500
5776
  }
5501
5777
  function appendStageContext(taskDir, stageName, output) {
5502
- const contextPath = path22.join(taskDir, "context.md");
5778
+ const contextPath = path23.join(taskDir, "context.md");
5503
5779
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
5504
5780
  let summary;
5505
5781
  if (output && output.trim()) {
@@ -5512,7 +5788,7 @@ function appendStageContext(taskDir, stageName, output) {
5512
5788
  ### ${stageName} (${timestamp2})
5513
5789
  ${summary}
5514
5790
  `;
5515
- fs24.appendFileSync(contextPath, entry);
5791
+ fs25.appendFileSync(contextPath, entry);
5516
5792
  }
5517
5793
  var SESSION_GROUP;
5518
5794
  var init_agent = __esm({
@@ -5793,8 +6069,8 @@ Error context:
5793
6069
  });
5794
6070
 
5795
6071
  // src/stages/gate.ts
5796
- import * as fs25 from "fs";
5797
- import * as path23 from "path";
6072
+ import * as fs26 from "fs";
6073
+ import * as path24 from "path";
5798
6074
  function executeGateStage(ctx, def) {
5799
6075
  if (ctx.input.dryRun) {
5800
6076
  logger.info(` [dry-run] skipping ${def.name}`);
@@ -5837,7 +6113,7 @@ ${output}
5837
6113
  `);
5838
6114
  }
5839
6115
  }
5840
- fs25.writeFileSync(path23.join(ctx.taskDir, "verify.md"), lines.join(""));
6116
+ fs26.writeFileSync(path24.join(ctx.taskDir, "verify.md"), lines.join(""));
5841
6117
  return {
5842
6118
  outcome: verifyResult.pass ? "completed" : "failed",
5843
6119
  retries: 0
@@ -5852,8 +6128,8 @@ var init_gate = __esm({
5852
6128
  });
5853
6129
 
5854
6130
  // src/stages/verify.ts
5855
- import * as fs26 from "fs";
5856
- import * as path24 from "path";
6131
+ import * as fs27 from "fs";
6132
+ import * as path25 from "path";
5857
6133
  import { execFileSync as execFileSync16 } from "child_process";
5858
6134
  async function executeVerifyWithAutofix(ctx, def) {
5859
6135
  const maxAttempts = def.maxRetries ?? 2;
@@ -5864,8 +6140,8 @@ async function executeVerifyWithAutofix(ctx, def) {
5864
6140
  return { ...gateResult, retries: attempt };
5865
6141
  }
5866
6142
  if (attempt < maxAttempts) {
5867
- const verifyPath = path24.join(ctx.taskDir, "verify.md");
5868
- 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";
5869
6145
  const modifiedFiles = getModifiedFiles(ctx.projectDir);
5870
6146
  const defaultRunner = getRunnerForStage(ctx, "taskify");
5871
6147
  const diagConfig = getProjectConfig();
@@ -5961,8 +6237,8 @@ var init_verify = __esm({
5961
6237
  });
5962
6238
 
5963
6239
  // src/review-standalone.ts
5964
- import * as fs27 from "fs";
5965
- import * as path25 from "path";
6240
+ import * as fs28 from "fs";
6241
+ import * as path26 from "path";
5966
6242
  function resolveReviewTarget(input) {
5967
6243
  if (input.prs.length === 0) {
5968
6244
  return {
@@ -5986,8 +6262,8 @@ Or comment on the specific PR: \`@kody review\``
5986
6262
  }
5987
6263
  async function runStandaloneReview(input) {
5988
6264
  const taskId = input.taskId ?? `review-${generateTaskId()}`;
5989
- const taskDir = path25.join(input.projectDir, ".kody", "tasks", taskId);
5990
- fs27.mkdirSync(taskDir, { recursive: true });
6265
+ const taskDir = path26.join(input.projectDir, ".kody", "tasks", taskId);
6266
+ fs28.mkdirSync(taskDir, { recursive: true });
5991
6267
  let diffInstruction = "";
5992
6268
  let filesChangedSection = "";
5993
6269
  if (input.baseBranch) {
@@ -6014,7 +6290,7 @@ ${fileList}`;
6014
6290
  const taskContent = `# ${input.prTitle}
6015
6291
 
6016
6292
  ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
6017
- fs27.writeFileSync(path25.join(taskDir, "task.md"), taskContent);
6293
+ fs28.writeFileSync(path26.join(taskDir, "task.md"), taskContent);
6018
6294
  const reviewDef = STAGES.find((s) => s.name === "review");
6019
6295
  const ctx = {
6020
6296
  taskId,
@@ -6036,10 +6312,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
6036
6312
  error: result2.error ?? "Review stage failed"
6037
6313
  };
6038
6314
  }
6039
- const reviewPath = path25.join(taskDir, "review.md");
6315
+ const reviewPath = path26.join(taskDir, "review.md");
6040
6316
  let reviewContent;
6041
- if (fs27.existsSync(reviewPath)) {
6042
- reviewContent = fs27.readFileSync(reviewPath, "utf-8");
6317
+ if (fs28.existsSync(reviewPath)) {
6318
+ reviewContent = fs28.readFileSync(reviewPath, "utf-8");
6043
6319
  }
6044
6320
  return {
6045
6321
  outcome: "completed",
@@ -6079,8 +6355,8 @@ var init_review_standalone = __esm({
6079
6355
  });
6080
6356
 
6081
6357
  // src/stages/review.ts
6082
- import * as fs28 from "fs";
6083
- import * as path26 from "path";
6358
+ import * as fs29 from "fs";
6359
+ import * as path27 from "path";
6084
6360
  async function executeReviewWithFix(ctx, def) {
6085
6361
  if (ctx.input.dryRun) {
6086
6362
  return { outcome: "completed", retries: 0 };
@@ -6094,11 +6370,11 @@ async function executeReviewWithFix(ctx, def) {
6094
6370
  if (reviewResult.outcome !== "completed") {
6095
6371
  return reviewResult;
6096
6372
  }
6097
- const reviewFile = path26.join(ctx.taskDir, "review.md");
6098
- if (!fs28.existsSync(reviewFile)) {
6373
+ const reviewFile = path27.join(ctx.taskDir, "review.md");
6374
+ if (!fs29.existsSync(reviewFile)) {
6099
6375
  return { outcome: "failed", retries: iteration, error: "review.md not found" };
6100
6376
  }
6101
- const content = fs28.readFileSync(reviewFile, "utf-8");
6377
+ const content = fs29.readFileSync(reviewFile, "utf-8");
6102
6378
  if (detectReviewVerdict(content) !== "fail") {
6103
6379
  return { ...reviewResult, retries: iteration };
6104
6380
  }
@@ -6127,15 +6403,15 @@ var init_review = __esm({
6127
6403
  });
6128
6404
 
6129
6405
  // src/stages/ship.ts
6130
- import * as fs29 from "fs";
6131
- import * as path27 from "path";
6406
+ import * as fs30 from "fs";
6407
+ import * as path28 from "path";
6132
6408
  import { execFileSync as execFileSync17 } from "child_process";
6133
6409
  function buildPrBody(ctx) {
6134
6410
  const sections = [];
6135
- const taskJsonPath = path27.join(ctx.taskDir, "task.json");
6136
- if (fs29.existsSync(taskJsonPath)) {
6411
+ const taskJsonPath = path28.join(ctx.taskDir, "task.json");
6412
+ if (fs30.existsSync(taskJsonPath)) {
6137
6413
  try {
6138
- const raw = fs29.readFileSync(taskJsonPath, "utf-8");
6414
+ const raw = fs30.readFileSync(taskJsonPath, "utf-8");
6139
6415
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6140
6416
  const task = JSON.parse(cleaned);
6141
6417
  if (task.description) {
@@ -6154,9 +6430,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
6154
6430
  } catch {
6155
6431
  }
6156
6432
  }
6157
- const reviewPath = path27.join(ctx.taskDir, "review.md");
6158
- if (fs29.existsSync(reviewPath)) {
6159
- 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");
6160
6436
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
6161
6437
  if (summaryMatch) {
6162
6438
  const summary = summaryMatch[1].trim();
@@ -6173,14 +6449,14 @@ ${summary}`);
6173
6449
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
6174
6450
  }
6175
6451
  }
6176
- const verifyPath = path27.join(ctx.taskDir, "verify.md");
6177
- if (fs29.existsSync(verifyPath)) {
6178
- 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");
6179
6455
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
6180
6456
  }
6181
- const planPath = path27.join(ctx.taskDir, "plan.md");
6182
- if (fs29.existsSync(planPath)) {
6183
- 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();
6184
6460
  if (plan) {
6185
6461
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
6186
6462
  sections.push(`
@@ -6200,22 +6476,22 @@ Closes #${ctx.input.issueNumber}`);
6200
6476
  return sections.join("\n");
6201
6477
  }
6202
6478
  function executeShipStage(ctx, _def) {
6203
- const shipPath = path27.join(ctx.taskDir, "ship.md");
6479
+ const shipPath = path28.join(ctx.taskDir, "ship.md");
6204
6480
  if (ctx.input.dryRun) {
6205
- 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");
6206
6482
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
6207
6483
  }
6208
6484
  if (ctx.input.local && !ctx.input.issueNumber) {
6209
- 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");
6210
6486
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
6211
6487
  }
6212
6488
  try {
6213
6489
  const head = getCurrentBranch(ctx.projectDir);
6214
6490
  const base = getDefaultBranch(ctx.projectDir);
6215
6491
  try {
6216
- const memoryDir = path27.join(ctx.projectDir, ".kody", "memory");
6492
+ const memoryDir = path28.join(ctx.projectDir, ".kody", "memory");
6217
6493
  const addPaths = [ctx.taskDir];
6218
- if (fs29.existsSync(memoryDir)) addPaths.push(memoryDir);
6494
+ if (fs30.existsSync(memoryDir)) addPaths.push(memoryDir);
6219
6495
  execFileSync17("git", ["add", ...addPaths], {
6220
6496
  cwd: ctx.projectDir,
6221
6497
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -6256,28 +6532,28 @@ function executeShipStage(ctx, _def) {
6256
6532
  chore: "chore"
6257
6533
  };
6258
6534
  let prefix = "chore";
6259
- const taskJsonPath = path27.join(ctx.taskDir, "task.json");
6260
- if (fs29.existsSync(taskJsonPath)) {
6535
+ const taskJsonPath = path28.join(ctx.taskDir, "task.json");
6536
+ if (fs30.existsSync(taskJsonPath)) {
6261
6537
  try {
6262
- const raw = fs29.readFileSync(taskJsonPath, "utf-8");
6538
+ const raw = fs30.readFileSync(taskJsonPath, "utf-8");
6263
6539
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6264
6540
  const task = JSON.parse(cleaned);
6265
6541
  prefix = TYPE_PREFIX[task.task_type] ?? "chore";
6266
6542
  } catch {
6267
6543
  }
6268
6544
  }
6269
- const taskMdPath = path27.join(ctx.taskDir, "task.md");
6270
- if (fs29.existsSync(taskMdPath)) {
6271
- 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");
6272
6548
  const heading = content.split("\n").find((l) => l.startsWith("# "));
6273
6549
  if (heading) {
6274
6550
  title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
6275
6551
  }
6276
6552
  }
6277
6553
  if (title === "Update") {
6278
- if (fs29.existsSync(taskJsonPath)) {
6554
+ if (fs30.existsSync(taskJsonPath)) {
6279
6555
  try {
6280
- const raw = fs29.readFileSync(taskJsonPath, "utf-8");
6556
+ const raw = fs30.readFileSync(taskJsonPath, "utf-8");
6281
6557
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6282
6558
  const task = JSON.parse(cleaned);
6283
6559
  if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
@@ -6300,7 +6576,7 @@ function executeShipStage(ctx, _def) {
6300
6576
  } catch {
6301
6577
  }
6302
6578
  }
6303
- fs29.writeFileSync(shipPath, `# Ship
6579
+ fs30.writeFileSync(shipPath, `# Ship
6304
6580
 
6305
6581
  Updated existing PR: ${existingPr.url}
6306
6582
  PR #${existingPr.number}
@@ -6321,20 +6597,20 @@ PR #${existingPr.number}
6321
6597
  } catch {
6322
6598
  }
6323
6599
  }
6324
- fs29.writeFileSync(shipPath, `# Ship
6600
+ fs30.writeFileSync(shipPath, `# Ship
6325
6601
 
6326
6602
  PR created: ${pr.url}
6327
6603
  PR #${pr.number}
6328
6604
  `);
6329
6605
  } else {
6330
- 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");
6331
6607
  }
6332
6608
  }
6333
6609
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
6334
6610
  } catch (err) {
6335
6611
  const msg = err instanceof Error ? err.message : String(err);
6336
6612
  try {
6337
- fs29.writeFileSync(shipPath, `# Ship
6613
+ fs30.writeFileSync(shipPath, `# Ship
6338
6614
 
6339
6615
  Failed: ${msg}
6340
6616
  `);
@@ -6383,15 +6659,15 @@ var init_executor_registry = __esm({
6383
6659
  });
6384
6660
 
6385
6661
  // src/pipeline/questions.ts
6386
- import * as fs30 from "fs";
6387
- import * as path28 from "path";
6662
+ import * as fs31 from "fs";
6663
+ import * as path29 from "path";
6388
6664
  function checkForQuestions(ctx, stageName) {
6389
6665
  if (ctx.input.local || !ctx.input.issueNumber) return false;
6390
6666
  try {
6391
6667
  if (stageName === "taskify") {
6392
- const taskJsonPath = path28.join(ctx.taskDir, "task.json");
6393
- if (!fs30.existsSync(taskJsonPath)) return false;
6394
- 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");
6395
6671
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6396
6672
  const taskJson = JSON.parse(cleaned);
6397
6673
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -6406,9 +6682,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
6406
6682
  }
6407
6683
  }
6408
6684
  if (stageName === "plan") {
6409
- const planPath = path28.join(ctx.taskDir, "plan.md");
6410
- if (!fs30.existsSync(planPath)) return false;
6411
- 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");
6412
6688
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
6413
6689
  if (questionsMatch) {
6414
6690
  const questionsText = questionsMatch[1].trim();
@@ -6437,8 +6713,8 @@ var init_questions = __esm({
6437
6713
  });
6438
6714
 
6439
6715
  // src/pipeline/hooks.ts
6440
- import * as fs31 from "fs";
6441
- import * as path29 from "path";
6716
+ import * as fs32 from "fs";
6717
+ import * as path30 from "path";
6442
6718
  function applyPreStageLabel(ctx, def) {
6443
6719
  if (!ctx.input.issueNumber || ctx.input.local) return;
6444
6720
  if (def.name === "plan") setLifecycleLabel(ctx.input.issueNumber, "planning");
@@ -6479,9 +6755,9 @@ function autoDetectComplexity(ctx, def) {
6479
6755
  return { complexity, activeStages };
6480
6756
  }
6481
6757
  try {
6482
- const taskJsonPath = path29.join(ctx.taskDir, "task.json");
6483
- if (!fs31.existsSync(taskJsonPath)) return null;
6484
- 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");
6485
6761
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6486
6762
  const taskJson = JSON.parse(cleaned);
6487
6763
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -6511,8 +6787,8 @@ function checkRiskGate(ctx, def, state, complexity) {
6511
6787
  if (ctx.input.dryRun || ctx.input.local) return null;
6512
6788
  if (ctx.input.mode === "rerun") return null;
6513
6789
  if (!ctx.input.issueNumber) return null;
6514
- const planPath = path29.join(ctx.taskDir, "plan.md");
6515
- 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)";
6516
6792
  try {
6517
6793
  postComment(
6518
6794
  ctx.input.issueNumber,
@@ -6579,22 +6855,22 @@ var init_hooks = __esm({
6579
6855
  });
6580
6856
 
6581
6857
  // src/learning/auto-learn.ts
6582
- import * as fs32 from "fs";
6583
- import * as path30 from "path";
6858
+ import * as fs33 from "fs";
6859
+ import * as path31 from "path";
6584
6860
  function stripAnsi(str) {
6585
6861
  return str.replace(/\x1b\[[0-9;]*m/g, "");
6586
6862
  }
6587
6863
  function autoLearn(ctx) {
6588
6864
  try {
6589
- const memoryDir = path30.join(ctx.projectDir, ".kody", "memory");
6590
- if (!fs32.existsSync(memoryDir)) {
6591
- 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 });
6592
6868
  }
6593
6869
  const learnings = [];
6594
6870
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6595
- const verifyPath = path30.join(ctx.taskDir, "verify.md");
6596
- if (fs32.existsSync(verifyPath)) {
6597
- 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"));
6598
6874
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
6599
6875
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
6600
6876
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -6603,18 +6879,18 @@ function autoLearn(ctx) {
6603
6879
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
6604
6880
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
6605
6881
  }
6606
- const reviewPath = path30.join(ctx.taskDir, "review.md");
6607
- if (fs32.existsSync(reviewPath)) {
6608
- 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");
6609
6885
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
6610
6886
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
6611
6887
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
6612
6888
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
6613
6889
  }
6614
- const taskJsonPath = path30.join(ctx.taskDir, "task.json");
6615
- if (fs32.existsSync(taskJsonPath)) {
6890
+ const taskJsonPath = path31.join(ctx.taskDir, "task.json");
6891
+ if (fs33.existsSync(taskJsonPath)) {
6616
6892
  try {
6617
- const raw = stripAnsi(fs32.readFileSync(taskJsonPath, "utf-8"));
6893
+ const raw = stripAnsi(fs33.readFileSync(taskJsonPath, "utf-8"));
6618
6894
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
6619
6895
  const task = JSON.parse(cleaned);
6620
6896
  if (task.scope && Array.isArray(task.scope)) {
@@ -6625,12 +6901,12 @@ function autoLearn(ctx) {
6625
6901
  }
6626
6902
  }
6627
6903
  if (learnings.length > 0) {
6628
- const conventionsPath = path30.join(memoryDir, "conventions.md");
6904
+ const conventionsPath = path31.join(memoryDir, "conventions.md");
6629
6905
  const entry = `
6630
6906
  ## Learned ${timestamp2} (task: ${ctx.taskId})
6631
6907
  ${learnings.join("\n")}
6632
6908
  `;
6633
- fs32.appendFileSync(conventionsPath, entry);
6909
+ fs33.appendFileSync(conventionsPath, entry);
6634
6910
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
6635
6911
  }
6636
6912
  autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
@@ -6638,8 +6914,8 @@ ${learnings.join("\n")}
6638
6914
  }
6639
6915
  }
6640
6916
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
6641
- const archPath = path30.join(memoryDir, "architecture.md");
6642
- if (fs32.existsSync(archPath)) return;
6917
+ const archPath = path31.join(memoryDir, "architecture.md");
6918
+ if (fs33.existsSync(archPath)) return;
6643
6919
  const detected = detectArchitectureBasic(projectDir);
6644
6920
  if (detected.length > 0) {
6645
6921
  const content = `# Architecture (auto-detected ${timestamp2})
@@ -6647,7 +6923,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
6647
6923
  ## Overview
6648
6924
  ${detected.join("\n")}
6649
6925
  `;
6650
- fs32.writeFileSync(archPath, content);
6926
+ fs33.writeFileSync(archPath, content);
6651
6927
  logger.info(`Auto-detected architecture (${detected.length} items)`);
6652
6928
  }
6653
6929
  }
@@ -6660,13 +6936,13 @@ var init_auto_learn = __esm({
6660
6936
  });
6661
6937
 
6662
6938
  // src/retrospective.ts
6663
- import * as fs33 from "fs";
6664
- import * as path31 from "path";
6939
+ import * as fs34 from "fs";
6940
+ import * as path32 from "path";
6665
6941
  function readArtifact(taskDir, filename, maxChars) {
6666
- const p = path31.join(taskDir, filename);
6667
- if (!fs33.existsSync(p)) return null;
6942
+ const p = path32.join(taskDir, filename);
6943
+ if (!fs34.existsSync(p)) return null;
6668
6944
  try {
6669
- const content = fs33.readFileSync(p, "utf-8");
6945
+ const content = fs34.readFileSync(p, "utf-8");
6670
6946
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
6671
6947
  } catch {
6672
6948
  return null;
@@ -6719,13 +6995,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
6719
6995
  return lines.join("\n");
6720
6996
  }
6721
6997
  function getLogPath(projectDir) {
6722
- return path31.join(projectDir, ".kody", "memory", "observer-log.jsonl");
6998
+ return path32.join(projectDir, ".kody", "memory", "observer-log.jsonl");
6723
6999
  }
6724
7000
  function readPreviousRetrospectives(projectDir, limit = 10) {
6725
7001
  const logPath = getLogPath(projectDir);
6726
- if (!fs33.existsSync(logPath)) return [];
7002
+ if (!fs34.existsSync(logPath)) return [];
6727
7003
  try {
6728
- const content = fs33.readFileSync(logPath, "utf-8");
7004
+ const content = fs34.readFileSync(logPath, "utf-8");
6729
7005
  const lines = content.split("\n").filter(Boolean);
6730
7006
  const entries = [];
6731
7007
  const start = Math.max(0, lines.length - limit);
@@ -6752,11 +7028,11 @@ function formatPreviousEntries(entries) {
6752
7028
  }
6753
7029
  function appendRetrospectiveEntry(projectDir, entry) {
6754
7030
  const logPath = getLogPath(projectDir);
6755
- const dir = path31.dirname(logPath);
6756
- if (!fs33.existsSync(dir)) {
6757
- fs33.mkdirSync(dir, { recursive: true });
7031
+ const dir = path32.dirname(logPath);
7032
+ if (!fs34.existsSync(dir)) {
7033
+ fs34.mkdirSync(dir, { recursive: true });
6758
7034
  }
6759
- fs33.appendFileSync(logPath, JSON.stringify(entry) + "\n");
7035
+ fs34.appendFileSync(logPath, JSON.stringify(entry) + "\n");
6760
7036
  }
6761
7037
  async function runRetrospective(ctx, state, pipelineStartTime) {
6762
7038
  if (ctx.input.dryRun) return;
@@ -6924,15 +7200,15 @@ var init_summary = __esm({
6924
7200
  });
6925
7201
 
6926
7202
  // src/tools.ts
6927
- import * as fs34 from "fs";
6928
- import * as path32 from "path";
7203
+ import * as fs35 from "fs";
7204
+ import * as path33 from "path";
6929
7205
  import { execSync as execSync3 } from "child_process";
6930
7206
  import { parse as parseYaml } from "yaml";
6931
7207
  function loadToolDeclarations(projectDir) {
6932
- const toolsPath = path32.join(projectDir, ".kody", "tools.yml");
6933
- if (!fs34.existsSync(toolsPath)) return [];
7208
+ const toolsPath = path33.join(projectDir, ".kody", "tools.yml");
7209
+ if (!fs35.existsSync(toolsPath)) return [];
6934
7210
  try {
6935
- const raw = fs34.readFileSync(toolsPath, "utf-8");
7211
+ const raw = fs35.readFileSync(toolsPath, "utf-8");
6936
7212
  const parsed = parseYaml(raw);
6937
7213
  if (!parsed || typeof parsed !== "object") return [];
6938
7214
  return Object.entries(parsed).map(([name, value]) => {
@@ -6953,7 +7229,7 @@ function loadToolDeclarations(projectDir) {
6953
7229
  function detectTools(declarations, projectDir) {
6954
7230
  const resolved = [];
6955
7231
  for (const decl of declarations) {
6956
- 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)));
6957
7233
  if (!detected) continue;
6958
7234
  resolved.push({
6959
7235
  name: decl.name,
@@ -6994,8 +7270,8 @@ var init_tools = __esm({
6994
7270
  });
6995
7271
 
6996
7272
  // src/pipeline.ts
6997
- import * as fs35 from "fs";
6998
- import * as path33 from "path";
7273
+ import * as fs36 from "fs";
7274
+ import * as path34 from "path";
6999
7275
  function ensureFeatureBranchIfNeeded(ctx) {
7000
7276
  if (ctx.input.dryRun) return;
7001
7277
  if (ctx.input.prNumber) {
@@ -7008,8 +7284,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
7008
7284
  }
7009
7285
  if (!ctx.input.issueNumber) return;
7010
7286
  try {
7011
- const taskMdPath = path33.join(ctx.taskDir, "task.md");
7012
- 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;
7013
7289
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
7014
7290
  syncWithDefault(ctx.projectDir);
7015
7291
  } catch (err) {
@@ -7023,10 +7299,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
7023
7299
  }
7024
7300
  }
7025
7301
  function acquireLock(taskDir) {
7026
- const lockPath = path33.join(taskDir, ".lock");
7027
- if (fs35.existsSync(lockPath)) {
7302
+ const lockPath = path34.join(taskDir, ".lock");
7303
+ if (fs36.existsSync(lockPath)) {
7028
7304
  try {
7029
- const pid = parseInt(fs35.readFileSync(lockPath, "utf-8").trim(), 10);
7305
+ const pid = parseInt(fs36.readFileSync(lockPath, "utf-8").trim(), 10);
7030
7306
  if (!isNaN(pid)) {
7031
7307
  try {
7032
7308
  process.kill(pid, 0);
@@ -7043,14 +7319,14 @@ function acquireLock(taskDir) {
7043
7319
  logger.warn(` Corrupt lock file \u2014 overwriting`);
7044
7320
  }
7045
7321
  try {
7046
- fs35.unlinkSync(lockPath);
7322
+ fs36.unlinkSync(lockPath);
7047
7323
  } catch {
7048
7324
  }
7049
7325
  }
7050
7326
  try {
7051
- const fd = fs35.openSync(lockPath, fs35.constants.O_WRONLY | fs35.constants.O_CREAT | fs35.constants.O_EXCL);
7052
- fs35.writeSync(fd, String(process.pid));
7053
- 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);
7054
7330
  } catch (err) {
7055
7331
  if (err.code === "EEXIST") {
7056
7332
  throw new Error("Pipeline already running (lock acquired by another process)");
@@ -7060,7 +7336,7 @@ function acquireLock(taskDir) {
7060
7336
  }
7061
7337
  function releaseLock(taskDir) {
7062
7338
  try {
7063
- fs35.unlinkSync(path33.join(taskDir, ".lock"));
7339
+ fs36.unlinkSync(path34.join(taskDir, ".lock"));
7064
7340
  } catch {
7065
7341
  }
7066
7342
  }
@@ -7277,7 +7553,7 @@ var init_pipeline = __esm({
7277
7553
 
7278
7554
  // src/preflight.ts
7279
7555
  import { execFileSync as execFileSync18 } from "child_process";
7280
- import * as fs36 from "fs";
7556
+ import * as fs37 from "fs";
7281
7557
  function check(name, fn) {
7282
7558
  try {
7283
7559
  const detail = fn() ?? void 0;
@@ -7330,7 +7606,7 @@ function runPreflight() {
7330
7606
  return v;
7331
7607
  }),
7332
7608
  check("package.json", () => {
7333
- if (!fs36.existsSync("package.json")) throw new Error("not found");
7609
+ if (!fs37.existsSync("package.json")) throw new Error("not found");
7334
7610
  })
7335
7611
  ];
7336
7612
  const failed = checks.filter((c) => !c.ok);
@@ -7407,8 +7683,8 @@ var init_args = __esm({
7407
7683
  });
7408
7684
 
7409
7685
  // src/cli/task-state.ts
7410
- import * as fs37 from "fs";
7411
- import * as path34 from "path";
7686
+ import * as fs38 from "fs";
7687
+ import * as path35 from "path";
7412
7688
  function resolveTaskAction(issueNumber, existingTaskId, existingState) {
7413
7689
  if (!existingTaskId || !existingState) {
7414
7690
  return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
@@ -7440,11 +7716,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
7440
7716
  function resolveForIssue(issueNumber, projectDir) {
7441
7717
  const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
7442
7718
  if (existingTaskId) {
7443
- const statusPath = path34.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
7719
+ const statusPath = path35.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
7444
7720
  let existingState = null;
7445
- if (fs37.existsSync(statusPath)) {
7721
+ if (fs38.existsSync(statusPath)) {
7446
7722
  try {
7447
- existingState = JSON.parse(fs37.readFileSync(statusPath, "utf-8"));
7723
+ existingState = JSON.parse(fs38.readFileSync(statusPath, "utf-8"));
7448
7724
  } catch {
7449
7725
  }
7450
7726
  }
@@ -7640,8 +7916,8 @@ var init_resolve = __esm({
7640
7916
 
7641
7917
  // src/entry.ts
7642
7918
  var entry_exports = {};
7643
- import * as fs38 from "fs";
7644
- import * as path35 from "path";
7919
+ import * as fs39 from "fs";
7920
+ import * as path36 from "path";
7645
7921
  async function ensureLitellmProxy(config, projectDir) {
7646
7922
  if (!anyStageNeedsProxy(config)) return null;
7647
7923
  const litellmUrl = getLitellmUrl();
@@ -7696,9 +7972,9 @@ async function runModelHealthCheck(config) {
7696
7972
  }
7697
7973
  async function main() {
7698
7974
  const input = parseArgs();
7699
- const projectDir = input.cwd ? path35.resolve(input.cwd) : process.cwd();
7975
+ const projectDir = input.cwd ? path36.resolve(input.cwd) : process.cwd();
7700
7976
  if (input.cwd) {
7701
- if (!fs38.existsSync(projectDir)) {
7977
+ if (!fs39.existsSync(projectDir)) {
7702
7978
  console.error(`--cwd path does not exist: ${projectDir}`);
7703
7979
  process.exit(1);
7704
7980
  }
@@ -7758,8 +8034,8 @@ async function main() {
7758
8034
  process.exit(1);
7759
8035
  }
7760
8036
  }
7761
- const taskDir = path35.join(projectDir, ".kody", "tasks", taskId);
7762
- fs38.mkdirSync(taskDir, { recursive: true });
8037
+ const taskDir = path36.join(projectDir, ".kody", "tasks", taskId);
8038
+ fs39.mkdirSync(taskDir, { recursive: true });
7763
8039
  if (input.command === "rerun" && isTaskifyRun(taskDir)) {
7764
8040
  const marker = readTaskifyMarker(taskDir);
7765
8041
  if (marker) {
@@ -7891,31 +8167,31 @@ async function main() {
7891
8167
  logger.info("Preflight checks:");
7892
8168
  runPreflight();
7893
8169
  if (input.task) {
7894
- fs38.writeFileSync(path35.join(taskDir, "task.md"), input.task);
8170
+ fs39.writeFileSync(path36.join(taskDir, "task.md"), input.task);
7895
8171
  }
7896
- const taskMdPath = path35.join(taskDir, "task.md");
7897
- if (!fs38.existsSync(taskMdPath) && isPRFix && input.prNumber) {
8172
+ const taskMdPath = path36.join(taskDir, "task.md");
8173
+ if (!fs39.existsSync(taskMdPath) && isPRFix && input.prNumber) {
7898
8174
  logger.info(`Fetching PR #${input.prNumber} details as task context...`);
7899
8175
  const prDetails = getPRDetails(input.prNumber);
7900
8176
  if (prDetails) {
7901
8177
  const taskContent = `# ${prDetails.title}
7902
8178
 
7903
8179
  ${prDetails.body ?? ""}`;
7904
- fs38.writeFileSync(taskMdPath, taskContent);
8180
+ fs39.writeFileSync(taskMdPath, taskContent);
7905
8181
  logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
7906
8182
  }
7907
- } else if (!fs38.existsSync(taskMdPath) && input.issueNumber) {
8183
+ } else if (!fs39.existsSync(taskMdPath) && input.issueNumber) {
7908
8184
  logger.info(`Fetching issue #${input.issueNumber} body as task...`);
7909
8185
  const issue = getIssue(input.issueNumber);
7910
8186
  if (issue) {
7911
8187
  const taskContent = `# ${issue.title}
7912
8188
 
7913
8189
  ${issue.body ?? ""}`;
7914
- fs38.writeFileSync(taskMdPath, taskContent);
8190
+ fs39.writeFileSync(taskMdPath, taskContent);
7915
8191
  logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
7916
8192
  }
7917
8193
  }
7918
- if (!fs38.existsSync(taskMdPath)) {
8194
+ if (!fs39.existsSync(taskMdPath)) {
7919
8195
  console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
7920
8196
  process.exit(1);
7921
8197
  }
@@ -8059,7 +8335,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
8059
8335
  }
8060
8336
  }
8061
8337
  const state = await runPipeline(ctx);
8062
- const files = fs38.readdirSync(taskDir);
8338
+ const files = fs39.readdirSync(taskDir);
8063
8339
  console.log(`
8064
8340
  Artifacts in ${taskDir}:`);
8065
8341
  for (const f of files) {
@@ -8141,8 +8417,8 @@ var init_entry = __esm({
8141
8417
  });
8142
8418
 
8143
8419
  // src/bin/cli.ts
8144
- import * as fs39 from "fs";
8145
- import * as path36 from "path";
8420
+ import * as fs40 from "fs";
8421
+ import * as path37 from "path";
8146
8422
  import { fileURLToPath as fileURLToPath2 } from "url";
8147
8423
 
8148
8424
  // src/bin/commands/init.ts
@@ -8473,6 +8749,24 @@ function initCommand(opts, pkgRoot) {
8473
8749
  console.log(" \u2139 Kody Watch will monitor pipeline health every 30 minutes");
8474
8750
  console.log(" \u2139 Digest issue will be created during bootstrap");
8475
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
+ }
8476
8770
  } else {
8477
8771
  console.log(" \u25CB kody-watch.yml template not found \u2014 skipping");
8478
8772
  }
@@ -9982,11 +10276,11 @@ Create it manually.`, cwd);
9982
10276
 
9983
10277
  // src/bin/cli.ts
9984
10278
  init_architecture_detection();
9985
- var __dirname2 = path36.dirname(fileURLToPath2(import.meta.url));
9986
- var PKG_ROOT = path36.resolve(__dirname2, "..", "..");
10279
+ var __dirname2 = path37.dirname(fileURLToPath2(import.meta.url));
10280
+ var PKG_ROOT = path37.resolve(__dirname2, "..", "..");
9987
10281
  function getVersion() {
9988
- const pkgPath = path36.join(PKG_ROOT, "package.json");
9989
- 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"));
9990
10284
  return pkg.version;
9991
10285
  }
9992
10286
  var args = process.argv.slice(2);