@kody-ade/kody-engine 0.4.220 → 0.4.222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.220",
18
+ version: "0.4.222",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -1089,8 +1089,8 @@ function listRepairCandidates(repoSlug) {
1089
1089
  };
1090
1090
  });
1091
1091
  }
1092
- function dispatchVerb(workflowFile, executable, prNumber) {
1093
- return dispatchWorkflow(workflowFile, executable, prNumber);
1092
+ function dispatchVerb(workflowFile, duty, prNumber) {
1093
+ return dispatchWorkflow(workflowFile, duty, prNumber);
1094
1094
  }
1095
1095
  function postRecommendation(prNumber, mention, message, dutySlug) {
1096
1096
  const mentioned = mention ? `${mention} ${message}` : message;
@@ -1219,17 +1219,17 @@ ${marker}` });
1219
1219
  return { error: err instanceof Error ? err.message : String(err) };
1220
1220
  }
1221
1221
  }
1222
- function dispatchWorkflow(workflowFile, executable, issueNumber) {
1222
+ function dispatchWorkflow(workflowFile, duty, issueNumber) {
1223
1223
  try {
1224
- gh(["workflow", "run", workflowFile, "-f", `executable=${executable}`, "-f", `issue_number=${issueNumber}`]);
1224
+ gh(["workflow", "run", workflowFile, "-f", `duty=${duty}`, "-f", `issue_number=${issueNumber}`]);
1225
1225
  return { ok: true };
1226
1226
  } catch (err) {
1227
1227
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
1228
1228
  }
1229
1229
  }
1230
- function isDispatchGated(executable, mode) {
1230
+ function isDispatchGated(duty, mode) {
1231
1231
  if (mode === "auto") return false;
1232
- if (executable && GATE_EXEMPT_EXECUTABLES.has(executable)) return false;
1232
+ if (duty && GATE_EXEMPT_DUTIES.has(duty)) return false;
1233
1233
  return true;
1234
1234
  }
1235
1235
  function trustRefusal(dutySlug) {
@@ -1283,7 +1283,7 @@ function dutyToolDefinitions(opts) {
1283
1283
  );
1284
1284
  const recommendTool = {
1285
1285
  name: "recommend_to_operator",
1286
- description: "Post ONE comment on a PR with the operator @-mention prepended. Use this when a verb is NOT graduated in the trust ledger and you want the operator to confirm via the dashboard inbox. The mention handle is substituted from kody.config.json `github.operators` \u2014 do not type it yourself.",
1286
+ description: "Post ONE comment on a PR with the operator @-mention prepended. Use this when a duty is in ASK mode and you want the operator to confirm via the dashboard inbox. The mention handle is substituted from kody.config.json `github.operators` \u2014 do not type it yourself.",
1287
1287
  inputSchema: {
1288
1288
  pr: z2.number().int().positive().describe("PR number to comment on."),
1289
1289
  body: z2.string().min(1).describe("Comment body (markdown). Do not include the operator mention \u2014 the engine prepends it.")
@@ -1298,9 +1298,9 @@ function dutyToolDefinitions(opts) {
1298
1298
  };
1299
1299
  const ledgerTool = {
1300
1300
  name: "read_ledger",
1301
- description: "Read the trust ledger (or any sentinel-fenced JSON manifest stored on a labeled issue). Returns `{found, issueNumber, payload}` where payload is the parsed JSON between `<!-- <label>:start -->` and `<!-- <label>:end -->` sentinels. Use `read_ledger({label: 'kody:cto-decisions'})` to look up per-verb graduation modes for the trust gate.",
1301
+ description: "Read any sentinel-fenced JSON manifest stored on a labeled issue. Returns `{found, issueNumber, payload}` where payload is the parsed JSON between `<!-- <label>:start -->` and `<!-- <label>:end -->` sentinels.",
1302
1302
  inputSchema: {
1303
- label: z2.string().min(1).describe("GitHub issue label that identifies the manifest issue (e.g. 'kody:cto-decisions').")
1303
+ label: z2.string().min(1).describe("GitHub issue label that identifies the manifest issue.")
1304
1304
  },
1305
1305
  handler: async (args) => {
1306
1306
  const label = String(args.label ?? "");
@@ -1374,19 +1374,20 @@ function dutyToolDefinitions(opts) {
1374
1374
  };
1375
1375
  const dispatchTool = {
1376
1376
  name: "dispatch_workflow",
1377
- description: "Dispatch a kody.yml workflow_dispatch run for an executable against an issue (the cross-run bot\u2192engine path; a bot `@kody` comment would be dropped). E.g. dispatch_workflow({executable:'run', issueNumber:<n>}) opens a fix PR from a tracking issue. Returns {ok} or {ok:false,error}.",
1377
+ description: "Dispatch a kody.yml workflow_dispatch run for a duty action against an issue (the cross-run bot\u2192engine path; a bot `@kody` comment would be dropped). E.g. dispatch_workflow({duty:'run', issueNumber:<n>}) opens a fix PR from a tracking issue. Returns {ok} or {ok:false,error}.",
1378
1378
  inputSchema: {
1379
- executable: z2.string().min(1).describe("Executable/stage to run (e.g. 'run')."),
1379
+ duty: z2.string().min(1).optional().describe("Duty action to run (e.g. 'run')."),
1380
+ executable: z2.string().min(1).optional().describe("Deprecated alias for duty."),
1380
1381
  issueNumber: z2.number().int().positive().describe("Issue (or PR) number forwarded as issue_number.")
1381
1382
  },
1382
1383
  handler: async (args) => {
1383
- const executable = String(args.executable ?? "");
1384
+ const duty = String(args.duty ?? args.executable ?? "");
1384
1385
  const issueNumber = Number(args.issueNumber);
1385
- if (isDispatchGated(executable, readDutyTrustMode(opts.repoSlug, opts.dutySlug))) {
1386
+ if (isDispatchGated(duty, readDutyTrustMode(opts.repoSlug, opts.dutySlug))) {
1386
1387
  return { content: [{ type: "text", text: trustRefusal(opts.dutySlug) }] };
1387
1388
  }
1388
- const result = dispatchWorkflow(workflowFile, executable, issueNumber);
1389
- const text = result.ok ? `Dispatched \`${executable}\` on #${issueNumber} via workflow_dispatch.` : `Dispatch failed for \`${executable}\` on #${issueNumber}: ${result.error}`;
1389
+ const result = dispatchWorkflow(workflowFile, duty, issueNumber);
1390
+ const text = result.ok ? `Dispatched duty \`${duty}\` on #${issueNumber} via workflow_dispatch.` : `Dispatch failed for duty \`${duty}\` on #${issueNumber}: ${result.error}`;
1390
1391
  return { content: [{ type: "text", text }] };
1391
1392
  }
1392
1393
  };
@@ -1421,7 +1422,7 @@ function buildDutyMcpServer(opts) {
1421
1422
  });
1422
1423
  return { server };
1423
1424
  }
1424
- var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, TRUST_STATE_BRANCH, THREAD_BODY_MAX, CHECK_FAIL_CONCLUSIONS, DEFAULT_IGNORE_CHECKS, trackMarker, commentMarker, GATE_EXEMPT_EXECUTABLES, DUTY_MCP_TOOL_NAMES;
1425
+ var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, TRUST_STATE_BRANCH, THREAD_BODY_MAX, CHECK_FAIL_CONCLUSIONS, DEFAULT_IGNORE_CHECKS, trackMarker, commentMarker, GATE_EXEMPT_DUTIES, DUTY_MCP_TOOL_NAMES;
1425
1426
  var init_dutyMcp = __esm({
1426
1427
  "src/dutyMcp.ts"() {
1427
1428
  "use strict";
@@ -1435,7 +1436,7 @@ var init_dutyMcp = __esm({
1435
1436
  DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "goal-tick", "worker-ask", "chat"];
1436
1437
  trackMarker = (key) => `<!-- kody-track:${key} -->`;
1437
1438
  commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
1438
- GATE_EXEMPT_EXECUTABLES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
1439
+ GATE_EXEMPT_DUTIES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
1439
1440
  DUTY_MCP_TOOL_NAMES = [
1440
1441
  "list_prs_to_repair",
1441
1442
  "sync_pr",
@@ -2205,27 +2206,10 @@ function listBuiltinJobs(root = getBuiltinJobsRoot()) {
2205
2206
  return out;
2206
2207
  }
2207
2208
  function getExecutableRoots() {
2208
- return [getProjectDutiesRoot(), getProjectExecutablesRoot(), getExecutablesRoot()];
2209
- }
2210
- function builtinExecutableNames() {
2211
- if (_builtinNames) return _builtinNames;
2212
- const out = /* @__PURE__ */ new Set();
2213
- const root = getExecutablesRoot();
2214
- try {
2215
- for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
2216
- if (ent.isDirectory() && fs7.existsSync(path7.join(root, ent.name, "profile.json"))) out.add(ent.name);
2217
- }
2218
- } catch {
2219
- }
2220
- _builtinNames = out;
2221
- return out;
2222
- }
2223
- function isBuiltinExecutable(name) {
2224
- return builtinExecutableNames().has(name);
2209
+ return [getProjectExecutablesRoot(), getExecutablesRoot()];
2225
2210
  }
2226
2211
  function listExecutables(roots = getExecutableRoots()) {
2227
2212
  const rootList = typeof roots === "string" ? [roots] : roots;
2228
- const dutiesRoot = getProjectDutiesRoot();
2229
2213
  const seen = /* @__PURE__ */ new Set();
2230
2214
  const out = [];
2231
2215
  for (const root of rootList) {
@@ -2234,9 +2218,7 @@ function listExecutables(roots = getExecutableRoots()) {
2234
2218
  for (const ent of entries) {
2235
2219
  if (!ent.isDirectory()) continue;
2236
2220
  if (seen.has(ent.name)) continue;
2237
- if (root === dutiesRoot && isBuiltinExecutable(ent.name)) continue;
2238
2221
  const profilePath = path7.join(root, ent.name, DUTY_PROFILE_FILE);
2239
- if (root === dutiesRoot && !fs7.existsSync(path7.join(root, ent.name, DUTY_BODY_FILE))) continue;
2240
2222
  if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
2241
2223
  out.push({ name: ent.name, profilePath });
2242
2224
  seen.add(ent.name);
@@ -2248,10 +2230,7 @@ function listExecutables(roots = getExecutableRoots()) {
2248
2230
  function resolveExecutable(name, roots = getExecutableRoots()) {
2249
2231
  if (!isSafeName(name)) return null;
2250
2232
  const rootList = typeof roots === "string" ? [roots] : roots;
2251
- const dutiesRoot = getProjectDutiesRoot();
2252
2233
  for (const root of rootList) {
2253
- if (root === dutiesRoot && isBuiltinExecutable(name)) continue;
2254
- if (root === dutiesRoot && !fs7.existsSync(path7.join(root, name, DUTY_BODY_FILE))) continue;
2255
2234
  const profilePath = path7.join(root, name, "profile.json");
2256
2235
  if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
2257
2236
  return profilePath;
@@ -2259,9 +2238,6 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
2259
2238
  }
2260
2239
  return null;
2261
2240
  }
2262
- function hasExecutable(name, roots = getExecutableRoots()) {
2263
- return resolveExecutable(name, roots) !== null;
2264
- }
2265
2241
  function listDutyActions(projectDutiesRoot = getProjectDutiesRoot()) {
2266
2242
  const seen = /* @__PURE__ */ new Set();
2267
2243
  const out = [];
@@ -2365,12 +2341,10 @@ function parseGenericFlags(argv) {
2365
2341
  if (positional.length > 0) args._ = positional;
2366
2342
  return args;
2367
2343
  }
2368
- var _builtinNames;
2369
2344
  var init_registry = __esm({
2370
2345
  "src/registry.ts"() {
2371
2346
  "use strict";
2372
2347
  init_dutyFolders();
2373
- _builtinNames = null;
2374
2348
  }
2375
2349
  });
2376
2350
 
@@ -2463,6 +2437,73 @@ var init_task_artifacts = __esm({
2463
2437
  }
2464
2438
  });
2465
2439
 
2440
+ // src/gha.ts
2441
+ import { execFileSync as execFileSync2 } from "child_process";
2442
+ import * as fs14 from "fs";
2443
+ function getRunUrl() {
2444
+ const server = process.env.GITHUB_SERVER_URL;
2445
+ const repo = process.env.GITHUB_REPOSITORY;
2446
+ const runId = process.env.GITHUB_RUN_ID;
2447
+ if (!server || !repo || !runId) return "";
2448
+ return `${server}/${repo}/actions/runs/${runId}`;
2449
+ }
2450
+ function reactToTriggerComment(cwd) {
2451
+ if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
2452
+ const eventPath = process.env.GITHUB_EVENT_PATH;
2453
+ if (!eventPath || !fs14.existsSync(eventPath)) return;
2454
+ let event = null;
2455
+ try {
2456
+ event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
2457
+ } catch {
2458
+ return;
2459
+ }
2460
+ const commentId = event?.comment?.id;
2461
+ const repo = process.env.GITHUB_REPOSITORY;
2462
+ if (!commentId || !repo) return;
2463
+ const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
2464
+ const args = [
2465
+ "api",
2466
+ "-X",
2467
+ "POST",
2468
+ "-H",
2469
+ "Accept: application/vnd.github+json",
2470
+ `/repos/${repo}/issues/comments/${commentId}/reactions`,
2471
+ "-f",
2472
+ "content=eyes"
2473
+ ];
2474
+ const opts = {
2475
+ cwd,
2476
+ env: { ...process.env, GH_TOKEN: token ?? process.env.GH_TOKEN ?? "" },
2477
+ stdio: "pipe",
2478
+ timeout: 15e3
2479
+ };
2480
+ let lastErr = null;
2481
+ for (let attempt = 0; attempt < 3; attempt++) {
2482
+ if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
2483
+ try {
2484
+ execFileSync2("gh", args, opts);
2485
+ return;
2486
+ } catch (err) {
2487
+ lastErr = err;
2488
+ }
2489
+ }
2490
+ process.stderr.write(
2491
+ `[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
2492
+ `
2493
+ );
2494
+ }
2495
+ function sleepMs(ms) {
2496
+ try {
2497
+ execFileSync2("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
2498
+ } catch {
2499
+ }
2500
+ }
2501
+ var init_gha = __esm({
2502
+ "src/gha.ts"() {
2503
+ "use strict";
2504
+ }
2505
+ });
2506
+
2466
2507
  // src/profile-error.ts
2467
2508
  var ProfileError;
2468
2509
  var init_profile_error = __esm({
@@ -2623,7 +2664,7 @@ var init_lifecycles = __esm({
2623
2664
  });
2624
2665
 
2625
2666
  // src/scripts/buildSyntheticPlugin.ts
2626
- import * as fs14 from "fs";
2667
+ import * as fs15 from "fs";
2627
2668
  import * as os2 from "os";
2628
2669
  import * as path13 from "path";
2629
2670
  function getPluginsCatalogRoot() {
@@ -2637,17 +2678,17 @@ function getPluginsCatalogRoot() {
2637
2678
  // fallback
2638
2679
  ];
2639
2680
  for (const c of candidates) {
2640
- if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
2681
+ if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
2641
2682
  }
2642
2683
  return candidates[0];
2643
2684
  }
2644
2685
  function copyDir(src, dst) {
2645
- fs14.mkdirSync(dst, { recursive: true });
2646
- for (const ent of fs14.readdirSync(src, { withFileTypes: true })) {
2686
+ fs15.mkdirSync(dst, { recursive: true });
2687
+ for (const ent of fs15.readdirSync(src, { withFileTypes: true })) {
2647
2688
  const s = path13.join(src, ent.name);
2648
2689
  const d = path13.join(dst, ent.name);
2649
2690
  if (ent.isDirectory()) copyDir(s, d);
2650
- else if (ent.isFile()) fs14.copyFileSync(s, d);
2691
+ else if (ent.isFile()) fs15.copyFileSync(s, d);
2651
2692
  }
2652
2693
  }
2653
2694
  var buildSyntheticPlugin;
@@ -2661,44 +2702,44 @@ var init_buildSyntheticPlugin = __esm({
2661
2702
  const catalog = getPluginsCatalogRoot();
2662
2703
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2663
2704
  const root = path13.join(os2.tmpdir(), `kody-synth-${runId}`);
2664
- fs14.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
2705
+ fs15.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
2665
2706
  const resolvePart = (bucket, entry) => {
2666
2707
  const local = path13.join(profile.dir, bucket, entry);
2667
- if (fs14.existsSync(local)) return local;
2708
+ if (fs15.existsSync(local)) return local;
2668
2709
  const central = path13.join(catalog, bucket, entry);
2669
- if (fs14.existsSync(central)) return central;
2710
+ if (fs15.existsSync(central)) return central;
2670
2711
  throw new Error(
2671
2712
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
2672
2713
  );
2673
2714
  };
2674
2715
  if (cc.skills.length > 0) {
2675
2716
  const dst = path13.join(root, "skills");
2676
- fs14.mkdirSync(dst, { recursive: true });
2717
+ fs15.mkdirSync(dst, { recursive: true });
2677
2718
  for (const name of cc.skills) {
2678
2719
  copyDir(resolvePart("skills", name), path13.join(dst, name));
2679
2720
  }
2680
2721
  }
2681
2722
  if (cc.commands.length > 0) {
2682
2723
  const dst = path13.join(root, "commands");
2683
- fs14.mkdirSync(dst, { recursive: true });
2724
+ fs15.mkdirSync(dst, { recursive: true });
2684
2725
  for (const name of cc.commands) {
2685
- fs14.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
2726
+ fs15.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
2686
2727
  }
2687
2728
  }
2688
2729
  if (cc.hooks.length > 0) {
2689
2730
  const dst = path13.join(root, "hooks");
2690
- fs14.mkdirSync(dst, { recursive: true });
2731
+ fs15.mkdirSync(dst, { recursive: true });
2691
2732
  const merged = { hooks: {} };
2692
2733
  for (const name of cc.hooks) {
2693
2734
  const src = resolvePart("hooks", `${name}.json`);
2694
- const parsed = JSON.parse(fs14.readFileSync(src, "utf-8"));
2735
+ const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
2695
2736
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
2696
2737
  if (!Array.isArray(entries)) continue;
2697
2738
  if (!merged.hooks[event]) merged.hooks[event] = [];
2698
2739
  merged.hooks[event].push(...entries);
2699
2740
  }
2700
2741
  }
2701
- fs14.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2742
+ fs15.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2702
2743
  `);
2703
2744
  }
2704
2745
  const manifest = {
@@ -2708,7 +2749,7 @@ var init_buildSyntheticPlugin = __esm({
2708
2749
  };
2709
2750
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
2710
2751
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
2711
- fs14.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2752
+ fs15.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2712
2753
  `);
2713
2754
  ctx.data.syntheticPluginPath = root;
2714
2755
  };
@@ -2716,7 +2757,7 @@ var init_buildSyntheticPlugin = __esm({
2716
2757
  });
2717
2758
 
2718
2759
  // src/subagents.ts
2719
- import * as fs15 from "fs";
2760
+ import * as fs16 from "fs";
2720
2761
  import * as path14 from "path";
2721
2762
  function splitFrontmatter(raw) {
2722
2763
  const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
@@ -2731,9 +2772,9 @@ function splitFrontmatter(raw) {
2731
2772
  }
2732
2773
  function resolveAgentFile(profileDir, name) {
2733
2774
  const local = path14.join(profileDir, "agents", `${name}.md`);
2734
- if (fs15.existsSync(local)) return local;
2775
+ if (fs16.existsSync(local)) return local;
2735
2776
  const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
2736
- if (fs15.existsSync(central)) return central;
2777
+ if (fs16.existsSync(central)) return central;
2737
2778
  throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
2738
2779
  }
2739
2780
  function captureSubagentTemplates(profile) {
@@ -2742,7 +2783,7 @@ function captureSubagentTemplates(profile) {
2742
2783
  const out = {};
2743
2784
  for (const name of names) {
2744
2785
  try {
2745
- out[name] = fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2786
+ out[name] = fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2746
2787
  } catch {
2747
2788
  }
2748
2789
  }
@@ -2753,7 +2794,7 @@ function loadSubagents(profile) {
2753
2794
  if (!names || names.length === 0) return void 0;
2754
2795
  const agents = {};
2755
2796
  for (const name of names) {
2756
- const raw = profile.subagentTemplates?.[name] ?? fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2797
+ const raw = profile.subagentTemplates?.[name] ?? fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
2757
2798
  const { fm, body } = splitFrontmatter(raw);
2758
2799
  if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
2759
2800
  const def = {
@@ -2777,15 +2818,15 @@ var init_subagents = __esm({
2777
2818
  });
2778
2819
 
2779
2820
  // src/profile.ts
2780
- import * as fs16 from "fs";
2821
+ import * as fs17 from "fs";
2781
2822
  import * as path15 from "path";
2782
2823
  function loadProfile(profilePath) {
2783
- if (!fs16.existsSync(profilePath)) {
2824
+ if (!fs17.existsSync(profilePath)) {
2784
2825
  throw new ProfileError(profilePath, "file not found");
2785
2826
  }
2786
2827
  let raw;
2787
2828
  try {
2788
- raw = JSON.parse(fs16.readFileSync(profilePath, "utf-8"));
2829
+ raw = JSON.parse(fs17.readFileSync(profilePath, "utf-8"));
2789
2830
  } catch (err) {
2790
2831
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
2791
2832
  }
@@ -2918,7 +2959,7 @@ function readPromptTemplates(dir) {
2918
2959
  const out = {};
2919
2960
  const read = (p) => {
2920
2961
  try {
2921
- out[p] = fs16.readFileSync(p, "utf-8");
2962
+ out[p] = fs17.readFileSync(p, "utf-8");
2922
2963
  } catch {
2923
2964
  }
2924
2965
  };
@@ -2926,7 +2967,7 @@ function readPromptTemplates(dir) {
2926
2967
  read(path15.join(dir, "duty.md"));
2927
2968
  try {
2928
2969
  const promptsDir = path15.join(dir, "prompts");
2929
- for (const ent of fs16.readdirSync(promptsDir)) {
2970
+ for (const ent of fs17.readdirSync(promptsDir)) {
2930
2971
  if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
2931
2972
  }
2932
2973
  } catch {
@@ -3234,7 +3275,7 @@ var init_profile = __esm({
3234
3275
  });
3235
3276
 
3236
3277
  // src/state.ts
3237
- import { execFileSync as execFileSync2 } from "child_process";
3278
+ import { execFileSync as execFileSync3 } from "child_process";
3238
3279
  function emptyState() {
3239
3280
  return {
3240
3281
  schemaVersion: 1,
@@ -3257,7 +3298,7 @@ function ghToken2() {
3257
3298
  function gh2(args, input, cwd) {
3258
3299
  const token = ghToken2();
3259
3300
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
3260
- return execFileSync2("gh", args, {
3301
+ return execFileSync3("gh", args, {
3261
3302
  encoding: "utf-8",
3262
3303
  timeout: API_TIMEOUT_MS2,
3263
3304
  cwd,
@@ -3604,16 +3645,16 @@ var init_state = __esm({
3604
3645
  });
3605
3646
 
3606
3647
  // src/prompt.ts
3607
- import * as fs17 from "fs";
3648
+ import * as fs18 from "fs";
3608
3649
  import * as path16 from "path";
3609
3650
  function loadProjectConventions(projectDir) {
3610
3651
  const out = [];
3611
3652
  for (const rel of CONVENTION_FILES) {
3612
3653
  const abs = path16.join(projectDir, rel);
3613
- if (!fs17.existsSync(abs)) continue;
3654
+ if (!fs18.existsSync(abs)) continue;
3614
3655
  let content;
3615
3656
  try {
3616
- content = fs17.readFileSync(abs, "utf-8");
3657
+ content = fs18.readFileSync(abs, "utf-8");
3617
3658
  } catch {
3618
3659
  continue;
3619
3660
  }
@@ -3848,20 +3889,20 @@ var loadMemoryContext_exports = {};
3848
3889
  __export(loadMemoryContext_exports, {
3849
3890
  loadMemoryContext: () => loadMemoryContext
3850
3891
  });
3851
- import * as fs18 from "fs";
3892
+ import * as fs19 from "fs";
3852
3893
  import * as path17 from "path";
3853
3894
  function collectPages(memoryAbs) {
3854
3895
  const out = [];
3855
3896
  walkMd(memoryAbs, (file) => {
3856
3897
  let stat;
3857
3898
  try {
3858
- stat = fs18.statSync(file);
3899
+ stat = fs19.statSync(file);
3859
3900
  } catch {
3860
3901
  return;
3861
3902
  }
3862
3903
  let raw;
3863
3904
  try {
3864
- raw = fs18.readFileSync(file, "utf-8");
3905
+ raw = fs19.readFileSync(file, "utf-8");
3865
3906
  } catch {
3866
3907
  return;
3867
3908
  }
@@ -3937,7 +3978,7 @@ function walkMd(root, visit) {
3937
3978
  const dir = stack.pop();
3938
3979
  let names;
3939
3980
  try {
3940
- names = fs18.readdirSync(dir);
3981
+ names = fs19.readdirSync(dir);
3941
3982
  } catch {
3942
3983
  continue;
3943
3984
  }
@@ -3946,7 +3987,7 @@ function walkMd(root, visit) {
3946
3987
  const full = path17.join(dir, name);
3947
3988
  let stat;
3948
3989
  try {
3949
- stat = fs18.statSync(full);
3990
+ stat = fs19.statSync(full);
3950
3991
  } catch {
3951
3992
  continue;
3952
3993
  }
@@ -3970,7 +4011,7 @@ var init_loadMemoryContext = __esm({
3970
4011
  loadMemoryContext = async (ctx) => {
3971
4012
  if (typeof ctx.data.memoryContext === "string") return;
3972
4013
  const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
3973
- if (!fs18.existsSync(memoryAbs)) {
4014
+ if (!fs19.existsSync(memoryAbs)) {
3974
4015
  ctx.data.memoryContext = "";
3975
4016
  return;
3976
4017
  }
@@ -4013,12 +4054,12 @@ var init_loadCoverageRules = __esm({
4013
4054
  });
4014
4055
 
4015
4056
  // src/container.ts
4016
- import { execFileSync as execFileSync3 } from "child_process";
4017
- import * as fs19 from "fs";
4057
+ import { execFileSync as execFileSync4 } from "child_process";
4058
+ import * as fs20 from "fs";
4018
4059
  function getProfileInputsForChild(profileName, _cwd) {
4019
4060
  try {
4020
4061
  const profilePath = resolveProfilePath(profileName);
4021
- if (!fs19.existsSync(profilePath)) return null;
4062
+ if (!fs20.existsSync(profilePath)) return null;
4022
4063
  return loadProfile(profilePath).inputs;
4023
4064
  } catch {
4024
4065
  return null;
@@ -4252,7 +4293,7 @@ async function runContainerLoop(profile, ctx, input) {
4252
4293
  }
4253
4294
  function resetWorkingTree(cwd) {
4254
4295
  try {
4255
- execFileSync3("git", ["reset", "--hard", "HEAD"], {
4296
+ execFileSync4("git", ["reset", "--hard", "HEAD"], {
4256
4297
  cwd,
4257
4298
  stdio: ["ignore", "pipe", "pipe"],
4258
4299
  timeout: 3e4
@@ -4479,8 +4520,8 @@ var init_lifecycleLabels = __esm({
4479
4520
  });
4480
4521
 
4481
4522
  // src/litellm.ts
4482
- import { execFileSync as execFileSync4, spawn as spawn3 } from "child_process";
4483
- import * as fs20 from "fs";
4523
+ import { execFileSync as execFileSync5, spawn as spawn3 } from "child_process";
4524
+ import * as fs21 from "fs";
4484
4525
  import * as os3 from "os";
4485
4526
  import * as path18 from "path";
4486
4527
  async function checkLitellmHealth(url) {
@@ -4512,7 +4553,7 @@ function generateLitellmConfigYaml(model) {
4512
4553
  }
4513
4554
  function litellmImportable() {
4514
4555
  try {
4515
- execFileSync4("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
4556
+ execFileSync5("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
4516
4557
  return true;
4517
4558
  } catch {
4518
4559
  return false;
@@ -4520,7 +4561,7 @@ function litellmImportable() {
4520
4561
  }
4521
4562
  function locateLitellmScript() {
4522
4563
  try {
4523
- const out = execFileSync4(
4564
+ const out = execFileSync5(
4524
4565
  "python3",
4525
4566
  [
4526
4567
  "-c",
@@ -4535,7 +4576,7 @@ function locateLitellmScript() {
4535
4576
  }
4536
4577
  function resolveLitellmCommand() {
4537
4578
  try {
4538
- execFileSync4("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
4579
+ execFileSync5("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
4539
4580
  return "litellm";
4540
4581
  } catch {
4541
4582
  if (!litellmImportable()) {
@@ -4543,7 +4584,7 @@ function resolveLitellmCommand() {
4543
4584
  let installed = false;
4544
4585
  for (const pip of ["pip", "pip3"]) {
4545
4586
  try {
4546
- execFileSync4(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
4587
+ execFileSync5(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
4547
4588
  installed = true;
4548
4589
  break;
4549
4590
  } catch {
@@ -4568,12 +4609,12 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4568
4609
  let logPath;
4569
4610
  const spawnProxy = () => {
4570
4611
  const configPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
4571
- fs20.writeFileSync(configPath, generateLitellmConfigYaml(model));
4612
+ fs21.writeFileSync(configPath, generateLitellmConfigYaml(model));
4572
4613
  const args = ["--config", configPath, "--port", port];
4573
4614
  const nextLogPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
4574
- const outFd = fs20.openSync(nextLogPath, "w");
4615
+ const outFd = fs21.openSync(nextLogPath, "w");
4575
4616
  child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
4576
- fs20.closeSync(outFd);
4617
+ fs21.closeSync(outFd);
4577
4618
  logPath = nextLogPath;
4578
4619
  };
4579
4620
  const waitForHealth = async () => {
@@ -4587,7 +4628,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4587
4628
  const readLogTail = () => {
4588
4629
  if (!logPath) return "";
4589
4630
  try {
4590
- return fs20.readFileSync(logPath, "utf-8").slice(-2e3);
4631
+ return fs21.readFileSync(logPath, "utf-8").slice(-2e3);
4591
4632
  } catch {
4592
4633
  return "";
4593
4634
  }
@@ -4640,9 +4681,9 @@ ${tail}`
4640
4681
  }
4641
4682
  function readDotenvApiKeys(projectDir) {
4642
4683
  const dotenvPath = path18.join(projectDir, ".env");
4643
- if (!fs20.existsSync(dotenvPath)) return {};
4684
+ if (!fs21.existsSync(dotenvPath)) return {};
4644
4685
  const result = {};
4645
- for (const rawLine of fs20.readFileSync(dotenvPath, "utf-8").split("\n")) {
4686
+ for (const rawLine of fs21.readFileSync(dotenvPath, "utf-8").split("\n")) {
4646
4687
  const line = rawLine.trim();
4647
4688
  if (!line || line.startsWith("#")) continue;
4648
4689
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -4674,14 +4715,14 @@ var init_litellm = __esm({
4674
4715
  });
4675
4716
 
4676
4717
  // src/pushWithRetry.ts
4677
- import { execFileSync as execFileSync5 } from "child_process";
4718
+ import { execFileSync as execFileSync6 } from "child_process";
4678
4719
  function sleepSync(ms) {
4679
4720
  if (ms <= 0) return;
4680
4721
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
4681
4722
  }
4682
4723
  function runGit(args, cwd) {
4683
4724
  try {
4684
- const stdout = execFileSync5("git", args, {
4725
+ const stdout = execFileSync6("git", args, {
4685
4726
  cwd,
4686
4727
  encoding: "utf-8",
4687
4728
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4756,12 +4797,12 @@ var init_pushWithRetry = __esm({
4756
4797
  });
4757
4798
 
4758
4799
  // src/commit.ts
4759
- import { execFileSync as execFileSync6 } from "child_process";
4760
- import * as fs21 from "fs";
4800
+ import { execFileSync as execFileSync7 } from "child_process";
4801
+ import * as fs22 from "fs";
4761
4802
  import * as path19 from "path";
4762
4803
  function git(args, cwd) {
4763
4804
  try {
4764
- return execFileSync6("git", args, {
4805
+ return execFileSync7("git", args, {
4765
4806
  encoding: "utf-8",
4766
4807
  timeout: 12e4,
4767
4808
  cwd,
@@ -4797,17 +4838,17 @@ function ensureGitIdentity(cwd) {
4797
4838
  function abortUnfinishedGitOps(cwd) {
4798
4839
  const aborted = [];
4799
4840
  const gitDir = path19.join(cwd ?? process.cwd(), ".git");
4800
- if (!fs21.existsSync(gitDir)) return aborted;
4801
- if (fs21.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
4841
+ if (!fs22.existsSync(gitDir)) return aborted;
4842
+ if (fs22.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
4802
4843
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
4803
4844
  }
4804
- if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
4845
+ if (fs22.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
4805
4846
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
4806
4847
  }
4807
- if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
4848
+ if (fs22.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
4808
4849
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
4809
4850
  }
4810
- if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
4851
+ if (fs22.existsSync(path19.join(gitDir, "rebase-merge")) || fs22.existsSync(path19.join(gitDir, "rebase-apply"))) {
4811
4852
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
4812
4853
  }
4813
4854
  try {
@@ -4828,7 +4869,7 @@ function isForbiddenPath(p) {
4828
4869
  return false;
4829
4870
  }
4830
4871
  function listChangedFiles(cwd) {
4831
- const raw = execFileSync6("git", ["status", "--porcelain=v1", "-z"], {
4872
+ const raw = execFileSync7("git", ["status", "--porcelain=v1", "-z"], {
4832
4873
  encoding: "utf-8",
4833
4874
  cwd,
4834
4875
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4840,7 +4881,7 @@ function listChangedFiles(cwd) {
4840
4881
  }
4841
4882
  function listFilesInCommit(ref = "HEAD", cwd) {
4842
4883
  try {
4843
- const raw = execFileSync6("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
4884
+ const raw = execFileSync7("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
4844
4885
  encoding: "utf-8",
4845
4886
  cwd,
4846
4887
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -4863,7 +4904,7 @@ function normalizeCommitMessage(raw) {
4863
4904
  function commitAndPush(branch, agentMessage, cwd) {
4864
4905
  const allChanged = listChangedFiles(cwd);
4865
4906
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
4866
- const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
4907
+ const mergeHeadExists = fs22.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
4867
4908
  if (allowedFiles.length === 0 && !mergeHeadExists) {
4868
4909
  return { committed: false, pushed: false, sha: "", message: "" };
4869
4910
  }
@@ -5019,10 +5060,10 @@ var init_saveTaskState = __esm({
5019
5060
  });
5020
5061
 
5021
5062
  // src/scripts/advanceFlow.ts
5022
- import { execFileSync as execFileSync7 } from "child_process";
5063
+ import { execFileSync as execFileSync8 } from "child_process";
5023
5064
  function ghComment(issueNumber, body, cwd, label) {
5024
5065
  try {
5025
- execFileSync7("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5066
+ execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5026
5067
  timeout: API_TIMEOUT_MS3,
5027
5068
  cwd,
5028
5069
  stdio: ["ignore", "pipe", "pipe"]
@@ -5093,78 +5134,11 @@ var init_advanceFlow = __esm({
5093
5134
  `
5094
5135
  );
5095
5136
  }
5096
- ctx.output.nextDispatch = { executable: flow.name, cliArgs: { issue: flow.issueNumber } };
5137
+ ctx.output.nextDispatch = { action: flow.name, cliArgs: { issue: flow.issueNumber } };
5097
5138
  };
5098
5139
  }
5099
5140
  });
5100
5141
 
5101
- // src/gha.ts
5102
- import { execFileSync as execFileSync8 } from "child_process";
5103
- import * as fs22 from "fs";
5104
- function getRunUrl() {
5105
- const server = process.env.GITHUB_SERVER_URL;
5106
- const repo = process.env.GITHUB_REPOSITORY;
5107
- const runId = process.env.GITHUB_RUN_ID;
5108
- if (!server || !repo || !runId) return "";
5109
- return `${server}/${repo}/actions/runs/${runId}`;
5110
- }
5111
- function reactToTriggerComment(cwd) {
5112
- if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
5113
- const eventPath = process.env.GITHUB_EVENT_PATH;
5114
- if (!eventPath || !fs22.existsSync(eventPath)) return;
5115
- let event = null;
5116
- try {
5117
- event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
5118
- } catch {
5119
- return;
5120
- }
5121
- const commentId = event?.comment?.id;
5122
- const repo = process.env.GITHUB_REPOSITORY;
5123
- if (!commentId || !repo) return;
5124
- const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
5125
- const args = [
5126
- "api",
5127
- "-X",
5128
- "POST",
5129
- "-H",
5130
- "Accept: application/vnd.github+json",
5131
- `/repos/${repo}/issues/comments/${commentId}/reactions`,
5132
- "-f",
5133
- "content=eyes"
5134
- ];
5135
- const opts = {
5136
- cwd,
5137
- env: { ...process.env, GH_TOKEN: token ?? process.env.GH_TOKEN ?? "" },
5138
- stdio: "pipe",
5139
- timeout: 15e3
5140
- };
5141
- let lastErr = null;
5142
- for (let attempt = 0; attempt < 3; attempt++) {
5143
- if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
5144
- try {
5145
- execFileSync8("gh", args, opts);
5146
- return;
5147
- } catch (err) {
5148
- lastErr = err;
5149
- }
5150
- }
5151
- process.stderr.write(
5152
- `[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
5153
- `
5154
- );
5155
- }
5156
- function sleepMs(ms) {
5157
- try {
5158
- execFileSync8("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
5159
- } catch {
5160
- }
5161
- }
5162
- var init_gha = __esm({
5163
- "src/gha.ts"() {
5164
- "use strict";
5165
- }
5166
- });
5167
-
5168
5142
  // src/stateBranch.ts
5169
5143
  function is404(err) {
5170
5144
  const msg = err instanceof Error ? err.message : String(err);
@@ -5600,10 +5574,10 @@ var init_state2 = __esm({
5600
5574
  "use strict";
5601
5575
  VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
5602
5576
  GoalStateError = class extends Error {
5603
- constructor(path43, message) {
5604
- super(`Invalid goal state at ${path43}:
5577
+ constructor(path44, message) {
5578
+ super(`Invalid goal state at ${path44}:
5605
5579
  ${message}`);
5606
- this.path = path43;
5580
+ this.path = path44;
5607
5581
  this.name = "GoalStateError";
5608
5582
  }
5609
5583
  path;
@@ -6484,7 +6458,7 @@ function dispatchTaskRun(issueNumber, base, cwd) {
6484
6458
  "-f",
6485
6459
  `issue_number=${issueNumber}`,
6486
6460
  "-f",
6487
- "executable=classify",
6461
+ "duty=classify",
6488
6462
  "-f",
6489
6463
  `base=${base}`
6490
6464
  ],
@@ -7210,7 +7184,7 @@ var init_dispatch = __esm({
7210
7184
  const usePr = target === "pr" && state?.core.prUrl;
7211
7185
  const targetNumber = usePr ? parsePr(state.core.prUrl) ?? issueNumber : issueNumber;
7212
7186
  ctx.output.nextDispatch = {
7213
- executable: next,
7187
+ action: next,
7214
7188
  cliArgs: usePr ? { pr: targetNumber } : { issue: targetNumber }
7215
7189
  };
7216
7190
  };
@@ -7266,182 +7240,16 @@ ${stateBody}`;
7266
7240
  });
7267
7241
  }
7268
7242
  } catch (err) {
7269
- process.stderr.write(
7270
- `[kody dispatchClassified] failed to post state comment for #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
7271
- `
7272
- );
7273
- }
7274
- const cliArgs = { issue: issueNumber };
7275
- if (base && getProfileInputs(classification)?.some((i) => i.name === "base")) {
7276
- cliArgs.base = base;
7277
- }
7278
- ctx.output.nextDispatch = { executable: classification, cliArgs };
7279
- };
7280
- }
7281
- });
7282
-
7283
- // src/jobIdentity.ts
7284
- function stableJobKey(job) {
7285
- const duty = job.duty ?? job.action;
7286
- const executable = job.executable ?? duty ?? "unknown";
7287
- if (job.flavor === "scheduled" && job.duty) return `scheduled:${job.duty}:${executable}`;
7288
- const target = typeof job.target === "number" ? job.target : targetFromCliArgs(job.cliArgs);
7289
- const work = duty ?? executable;
7290
- return target === void 0 ? `${job.flavor}:${work}` : `${job.flavor}:${work}:${target}`;
7291
- }
7292
- function targetFromCliArgs(cliArgs) {
7293
- if (!cliArgs) return void 0;
7294
- for (const key of ["issue", "pr", "target", "issue_number"]) {
7295
- const value = cliArgs[key];
7296
- if (typeof value === "number" && Number.isFinite(value)) return value;
7297
- }
7298
- return void 0;
7299
- }
7300
- var init_jobIdentity = __esm({
7301
- "src/jobIdentity.ts"() {
7302
- "use strict";
7303
- }
7304
- });
7305
-
7306
- // src/job.ts
7307
- var job_exports = {};
7308
- __export(job_exports, {
7309
- DEFAULT_INSTANT_PERSONA: () => DEFAULT_INSTANT_PERSONA,
7310
- InvalidJobError: () => InvalidJobError,
7311
- mintInstantJob: () => mintInstantJob,
7312
- mintScheduledJob: () => mintScheduledJob,
7313
- newJobId: () => newJobId,
7314
- runJob: () => runJob,
7315
- stableJobKey: () => stableJobKey,
7316
- validateJob: () => validateJob
7317
- });
7318
- function newJobId(flavor) {
7319
- localJobSeq += 1;
7320
- const runId = process.env.GITHUB_RUN_ID;
7321
- if (runId) return `gh-${runId}-${process.env.GITHUB_RUN_ATTEMPT ?? "1"}-${localJobSeq}`;
7322
- return `${flavor}-${Date.now()}-${localJobSeq}`;
7323
- }
7324
- function validateJob(input) {
7325
- if (!input || typeof input !== "object") {
7326
- throw new InvalidJobError("job must be an object");
7327
- }
7328
- const j = input;
7329
- if (typeof j.executable !== "string" && typeof j.duty !== "string" && typeof j.action !== "string") {
7330
- throw new InvalidJobError("job must reference a duty action, duty, or executable");
7331
- }
7332
- if (j.flavor !== "instant" && j.flavor !== "scheduled") {
7333
- throw new InvalidJobError(`job.flavor must be "instant" or "scheduled" (got ${String(j.flavor)})`);
7334
- }
7335
- if (j.cliArgs !== void 0 && (typeof j.cliArgs !== "object" || j.cliArgs === null)) {
7336
- throw new InvalidJobError("job.cliArgs must be an object when present");
7337
- }
7338
- return {
7339
- action: typeof j.action === "string" ? j.action : void 0,
7340
- executable: typeof j.executable === "string" ? j.executable : void 0,
7341
- duty: typeof j.duty === "string" ? j.duty : void 0,
7342
- why: typeof j.why === "string" ? j.why : void 0,
7343
- persona: typeof j.persona === "string" ? j.persona : void 0,
7344
- schedule: typeof j.schedule === "string" ? j.schedule : void 0,
7345
- target: typeof j.target === "number" ? j.target : void 0,
7346
- cliArgs: j.cliArgs ?? {},
7347
- flavor: j.flavor,
7348
- force: j.force === true
7349
- };
7350
- }
7351
- async function runJob(job, base) {
7352
- const valid = validateJob(job);
7353
- const action = valid.action ?? valid.duty;
7354
- const resolvedDuty = action ? resolveDutyAction(action) : null;
7355
- const profileName = valid.executable ?? resolvedDuty?.executable ?? valid.duty;
7356
- if (!profileName) {
7357
- throw new InvalidJobError("job resolves to no executable or duty");
7358
- }
7359
- const preloadedData = { ...base.preloadedData ?? {} };
7360
- preloadedData.jobId = newJobId(valid.flavor);
7361
- preloadedData.jobKey = stableJobKey(valid);
7362
- preloadedData.jobFlavor = valid.flavor;
7363
- if (valid.target !== void 0) preloadedData.jobTarget = valid.target;
7364
- if (valid.action !== void 0 && valid.action.length > 0) preloadedData.jobAction = valid.action;
7365
- const dutyIdentity = valid.duty ?? resolvedDuty?.duty;
7366
- if (dutyIdentity !== void 0 && dutyIdentity.length > 0) preloadedData.jobDuty = dutyIdentity;
7367
- const executableIdentity = valid.executable ?? resolvedDuty?.executable;
7368
- if (executableIdentity !== void 0 && executableIdentity.length > 0)
7369
- preloadedData.jobExecutable = executableIdentity;
7370
- if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
7371
- const dutyContext = loadDutyContext(dutyIdentity ?? valid.duty);
7372
- if (dutyContext) {
7373
- preloadedData.dutySlug = dutyContext.slug;
7374
- preloadedData.dutyTitle = dutyContext.title;
7375
- preloadedData.dutyIntent = dutyContext.body;
7376
- preloadedData.jobIntent = dutyContext.body;
7377
- if (preloadedData.jobDuty === void 0) preloadedData.jobDuty = dutyContext.slug;
7378
- if (dutyContext.config.staff && preloadedData.jobPersona === void 0) {
7379
- preloadedData.jobPersona = dutyContext.config.staff;
7380
- }
7381
- if (dutyContext.config.every && preloadedData.jobSchedule === void 0) {
7382
- preloadedData.jobSchedule = dutyContext.config.every;
7383
- }
7384
- if (dutyContext.config.mentions && dutyContext.config.mentions.length > 0) {
7385
- preloadedData.mentions = dutyContext.config.mentions.map((login) => `@${login}`).join(" ");
7386
- }
7387
- }
7388
- if (valid.why !== void 0 && valid.why.length > 0) preloadedData.jobWhy = valid.why;
7389
- if (valid.persona !== void 0) preloadedData.jobPersona = valid.persona;
7390
- const input = {
7391
- cliArgs: { ...valid.cliArgs },
7392
- cwd: base.cwd,
7393
- config: base.config,
7394
- skipConfig: base.skipConfig,
7395
- verbose: base.verbose,
7396
- quiet: base.quiet,
7397
- preloadedData: Object.keys(preloadedData).length > 0 ? preloadedData : void 0
7398
- };
7399
- input.cliArgs = resolvedDuty ? { ...resolvedDuty.cliArgs, ...input.cliArgs } : input.cliArgs;
7400
- const run = base.chain === false ? runExecutable : runExecutableChain;
7401
- return run(profileName, input);
7402
- }
7403
- function loadDutyContext(slug) {
7404
- if (!slug) return null;
7405
- return readDutyFolder(getProjectDutiesRoot(), slug) ?? readDutyFolder(getBuiltinDutiesRoot(), slug);
7406
- }
7407
- function mintInstantJob(dispatch2, opts) {
7408
- return {
7409
- action: dispatch2.action,
7410
- executable: dispatch2.executable,
7411
- duty: dispatch2.duty,
7412
- why: opts?.why ?? dispatch2.why,
7413
- persona: opts?.persona ?? DEFAULT_INSTANT_PERSONA,
7414
- target: dispatch2.target,
7415
- cliArgs: dispatch2.cliArgs,
7416
- flavor: "instant"
7417
- };
7418
- }
7419
- function mintScheduledJob(input) {
7420
- return {
7421
- duty: input.duty,
7422
- executable: input.executable,
7423
- schedule: input.schedule,
7424
- persona: input.persona,
7425
- cliArgs: input.cliArgs ?? {},
7426
- flavor: "scheduled"
7427
- };
7428
- }
7429
- var DEFAULT_INSTANT_PERSONA, localJobSeq, InvalidJobError;
7430
- var init_job = __esm({
7431
- "src/job.ts"() {
7432
- "use strict";
7433
- init_executor();
7434
- init_dutyFolders();
7435
- init_registry();
7436
- init_jobIdentity();
7437
- init_jobIdentity();
7438
- DEFAULT_INSTANT_PERSONA = "kody";
7439
- localJobSeq = 0;
7440
- InvalidJobError = class extends Error {
7441
- constructor(message) {
7442
- super(message);
7443
- this.name = "InvalidJobError";
7243
+ process.stderr.write(
7244
+ `[kody dispatchClassified] failed to post state comment for #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
7245
+ `
7246
+ );
7247
+ }
7248
+ const cliArgs = { issue: issueNumber };
7249
+ if (base && getProfileInputs(classification)?.some((i) => i.name === "base")) {
7250
+ cliArgs.base = base;
7444
7251
  }
7252
+ ctx.output.nextDispatch = { action: classification, cliArgs };
7445
7253
  };
7446
7254
  }
7447
7255
  });
@@ -7542,9 +7350,13 @@ function isStateUnchanged(prev, next) {
7542
7350
  return JSON.stringify(prev.data) === JSON.stringify(next.data);
7543
7351
  }
7544
7352
  function stateFilePath(jobsDir, slug) {
7545
- return `${jobsDir.replace(/\/+$/, "")}/${slug}.state.json`;
7353
+ return `${jobsDir.replace(/\/+$/, "")}/${slug}/state.json`;
7546
7354
  }
7547
7355
  function slugFromStateFilePath(filePath) {
7356
+ if (/\/state\.json$/i.test(filePath)) {
7357
+ const parts = filePath.split("/");
7358
+ return parts.at(-2) ?? filePath;
7359
+ }
7548
7360
  const last = filePath.split("/").pop() ?? filePath;
7549
7361
  return last.replace(/\.state\.json$/i, "");
7550
7362
  }
@@ -7845,6 +7657,29 @@ var init_jobState = __esm({
7845
7657
  }
7846
7658
  });
7847
7659
 
7660
+ // src/jobIdentity.ts
7661
+ function stableJobKey(job) {
7662
+ const duty = job.duty ?? job.action;
7663
+ const executable = job.executable ?? duty ?? "unknown";
7664
+ if (job.flavor === "scheduled" && job.duty) return `scheduled:${job.duty}:${executable}`;
7665
+ const target = typeof job.target === "number" ? job.target : targetFromCliArgs(job.cliArgs);
7666
+ const work = duty && executable && executable !== duty ? `${duty}:${executable}` : duty ?? executable;
7667
+ return target === void 0 ? `${job.flavor}:${work}` : `${job.flavor}:${work}:${target}`;
7668
+ }
7669
+ function targetFromCliArgs(cliArgs) {
7670
+ if (!cliArgs) return void 0;
7671
+ for (const key of ["issue", "pr", "target", "issue_number"]) {
7672
+ const value = cliArgs[key];
7673
+ if (typeof value === "number" && Number.isFinite(value)) return value;
7674
+ }
7675
+ return void 0;
7676
+ }
7677
+ var init_jobIdentity = __esm({
7678
+ "src/jobIdentity.ts"() {
7679
+ "use strict";
7680
+ }
7681
+ });
7682
+
7848
7683
  // src/scripts/planTaskJobs.ts
7849
7684
  function parseTaskJobSpecs(body) {
7850
7685
  const match = body.match(new RegExp(`<!--\\s*${TASK_JOBS_MARKER}\\s*([\\s\\S]*?)-->`));
@@ -7862,7 +7697,7 @@ function taskJobSpecToJob(spec, issueNumber) {
7862
7697
  const cliArgs = spec.cliArgs ?? { issue: issueNumber };
7863
7698
  const target = typeof spec.target === "number" ? spec.target : targetFromCliArgs(cliArgs) ?? issueNumber;
7864
7699
  return {
7865
- duty: spec.duty,
7700
+ duty: spec.duty ?? spec.executable,
7866
7701
  executable: spec.executable,
7867
7702
  why: spec.reason,
7868
7703
  persona: spec.persona ?? spec.staff,
@@ -7905,7 +7740,7 @@ function jobToPlannedTaskJob(job) {
7905
7740
  return {
7906
7741
  id: stableJobKey(job),
7907
7742
  executable: job.executable ?? job.duty ?? "unknown",
7908
- ...job.duty ? { duty: job.duty } : {},
7743
+ duty: job.duty ?? job.action ?? job.executable ?? "unknown",
7909
7744
  ...job.persona ? { staff: job.persona } : {},
7910
7745
  ...job.flavor ? { flavor: job.flavor } : {},
7911
7746
  ...job.schedule ? { schedule: job.schedule } : {},
@@ -8217,8 +8052,8 @@ var dispatchDutyTicks;
8217
8052
  var init_dispatchDutyTicks = __esm({
8218
8053
  "src/scripts/dispatchDutyTicks.ts"() {
8219
8054
  "use strict";
8220
- init_executor();
8221
8055
  init_issue();
8056
+ init_job();
8222
8057
  dispatchDutyTicks = async (ctx, _profile, args) => {
8223
8058
  ctx.skipAgent = true;
8224
8059
  const label = String(args?.label ?? "");
@@ -8240,13 +8075,14 @@ var init_dispatchDutyTicks = __esm({
8240
8075
  process.stdout.write(`[jobs] \u2192 tick #${issue.number}: ${issue.title}
8241
8076
  `);
8242
8077
  try {
8243
- const out = await runExecutable(targetExecutable, {
8244
- cliArgs: { [issueArg]: issue.number },
8245
- cwd: ctx.cwd,
8246
- config: ctx.config,
8247
- verbose: ctx.verbose,
8248
- quiet: ctx.quiet
8249
- });
8078
+ const out = await runJob(
8079
+ mintScheduledJob({
8080
+ duty: targetExecutable,
8081
+ executable: targetExecutable,
8082
+ cliArgs: { [issueArg]: issue.number }
8083
+ }),
8084
+ { cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false }
8085
+ );
8250
8086
  results.push({ issue: issue.number, exitCode: out.exitCode, reason: out.reason });
8251
8087
  if (out.exitCode !== 0) {
8252
8088
  process.stderr.write(`[jobs] tick #${issue.number} failed (exit ${out.exitCode}): ${out.reason ?? ""}
@@ -8301,8 +8137,8 @@ var init_dispatchNextTask = __esm({
8301
8137
  function taskJobToJob(job, issueArg) {
8302
8138
  const target = typeof job.target === "number" ? job.target : typeof issueArg === "number" ? issueArg : void 0;
8303
8139
  return {
8140
+ duty: job.duty ?? job.executable,
8304
8141
  executable: job.executable,
8305
- ...job.duty ? { duty: job.duty } : {},
8306
8142
  ...job.reason ? { why: job.reason } : {},
8307
8143
  ...job.staff ? { persona: job.staff } : {},
8308
8144
  ...job.schedule ? { schedule: job.schedule } : {},
@@ -8313,7 +8149,7 @@ function taskJobToJob(job, issueArg) {
8313
8149
  function isJob(input) {
8314
8150
  if (!input || typeof input !== "object" || Array.isArray(input)) return false;
8315
8151
  const job = input;
8316
- return typeof job.executable === "string" && (job.flavor === "instant" || job.flavor === "scheduled") && (!job.cliArgs || typeof job.cliArgs === "object" && !Array.isArray(job.cliArgs));
8152
+ return (typeof job.duty === "string" || typeof job.action === "string") && (job.flavor === "instant" || job.flavor === "scheduled") && (!job.cliArgs || typeof job.cliArgs === "object" && !Array.isArray(job.cliArgs));
8317
8153
  }
8318
8154
  var dispatchNextTaskJob;
8319
8155
  var init_dispatchNextTaskJob = __esm({
@@ -8334,7 +8170,7 @@ var init_dispatchNextTaskJob = __esm({
8334
8170
  const plannedJobs = Array.isArray(ctx.data.plannedTaskJobs) ? ctx.data.plannedTaskJobs.filter(isJob) : [];
8335
8171
  ctx.output.nextJob = plannedJobs.find((job) => stableJobKey(job) === next.id) ?? taskJobToJob(next, ctx.args.issue);
8336
8172
  if (typeof ctx.args.issue === "number") {
8337
- ctx.output.afterNextJob = { executable: profile.name, cliArgs: { issue: ctx.args.issue } };
8173
+ ctx.output.afterNextJob = { action: profile.action ?? profile.name, cliArgs: { issue: ctx.args.issue } };
8338
8174
  }
8339
8175
  };
8340
8176
  }
@@ -8654,6 +8490,7 @@ var init_failOnceTaskJob = __esm({
8654
8490
  ctx.skipAgent = true;
8655
8491
  const issue = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
8656
8492
  const fallbackJob = {
8493
+ duty: profile.action ?? profile.name,
8657
8494
  executable: profile.name,
8658
8495
  flavor: "instant",
8659
8496
  ...typeof issue === "number" ? { target: issue, cliArgs: { issue } } : { cliArgs: {} }
@@ -9552,9 +9389,9 @@ function performInit(cwd, force) {
9552
9389
  return { wrote, skipped, labels };
9553
9390
  }
9554
9391
  function renderScheduledWorkflow(name, cron) {
9555
- return `# Scheduled kody executable: ${name}
9392
+ return `# Scheduled kody duty: ${name}
9556
9393
  # Generated by \`kody init\`. Regenerate with \`kody init --force\`.
9557
- # Edit the cron below or the executable's profile.json#schedule.
9394
+ # Edit the cron below or the duty's implementation profile.json#schedule.
9558
9395
 
9559
9396
  name: kody ${name}
9560
9397
 
@@ -12799,7 +12636,7 @@ var init_startFlow = __esm({
12799
12636
  const usePr = target === "pr" && !!state?.core.prUrl;
12800
12637
  const targetNumber = usePr ? parsePrNumber(state.core.prUrl) ?? issueNumber : issueNumber;
12801
12638
  ctx.output.nextDispatch = {
12802
- executable: entry,
12639
+ action: entry,
12803
12640
  cliArgs: usePr ? { pr: targetNumber } : { issue: targetNumber }
12804
12641
  };
12805
12642
  };
@@ -14331,8 +14168,8 @@ async function runExecutableChain(profileName, input) {
14331
14168
  process.stdout.write(`\u2192 kody: in-process job hand-off \u2192 ${label} (hop ${hops}/${MAX_CHAIN_HOPS})
14332
14169
 
14333
14170
  `);
14334
- const { runJob: runJob2 } = await Promise.resolve().then(() => (init_job(), job_exports));
14335
- const childResult = await runJob2(next2, {
14171
+ const { runJob: runJob3 } = await Promise.resolve().then(() => (init_job(), job_exports));
14172
+ const childResult = await runJob3(next2, {
14336
14173
  cwd: input.cwd,
14337
14174
  config: input.config,
14338
14175
  verbose: input.verbose,
@@ -14344,12 +14181,24 @@ async function runExecutableChain(profileName, input) {
14344
14181
  ...chainData,
14345
14182
  ...childResult.taskState ? { taskState: childResult.taskState } : {}
14346
14183
  };
14347
- process.stdout.write(`\u2192 kody: in-process return \u2192 ${after.executable} (hop ${hops}/${MAX_CHAIN_HOPS})
14184
+ const afterJob = handoffToJob(after);
14185
+ if (!afterJob) {
14186
+ return {
14187
+ exitCode: 99,
14188
+ reason: `in-process return missing duty/action for ${after.executable ?? "unknown"}`
14189
+ };
14190
+ }
14191
+ process.stdout.write(
14192
+ `\u2192 kody: in-process return \u2192 ${afterJob.action ?? afterJob.duty} (hop ${hops}/${MAX_CHAIN_HOPS})
14348
14193
 
14349
- `);
14350
- result = await runExecutable(after.executable, {
14351
- ...input,
14352
- cliArgs: after.cliArgs,
14194
+ `
14195
+ );
14196
+ const { runJob: runJob4 } = await Promise.resolve().then(() => (init_job(), job_exports));
14197
+ result = await runJob4(afterJob, {
14198
+ cwd: input.cwd,
14199
+ config: input.config,
14200
+ verbose: input.verbose,
14201
+ quiet: input.quiet,
14353
14202
  preloadedData: chainData
14354
14203
  });
14355
14204
  chainData = {
@@ -14366,10 +14215,26 @@ async function runExecutableChain(profileName, input) {
14366
14215
  continue;
14367
14216
  }
14368
14217
  const next = result.nextDispatch;
14369
- process.stdout.write(`\u2192 kody: in-process hand-off \u2192 ${next.executable} (hop ${hops}/${MAX_CHAIN_HOPS})
14218
+ const nextJob = handoffToJob(next);
14219
+ if (!nextJob) {
14220
+ return {
14221
+ exitCode: 99,
14222
+ reason: `in-process hand-off missing duty/action for ${next.executable ?? "unknown"}`
14223
+ };
14224
+ }
14225
+ process.stdout.write(
14226
+ `\u2192 kody: in-process hand-off \u2192 ${nextJob.action ?? nextJob.duty} (hop ${hops}/${MAX_CHAIN_HOPS})
14370
14227
 
14371
- `);
14372
- result = await runExecutable(next.executable, { ...input, cliArgs: next.cliArgs, preloadedData: chainData });
14228
+ `
14229
+ );
14230
+ const { runJob: runJob2 } = await Promise.resolve().then(() => (init_job(), job_exports));
14231
+ result = await runJob2(nextJob, {
14232
+ cwd: input.cwd,
14233
+ config: input.config,
14234
+ verbose: input.verbose,
14235
+ quiet: input.quiet,
14236
+ preloadedData: chainData
14237
+ });
14373
14238
  chainData = {
14374
14239
  ...chainData,
14375
14240
  ...result.taskState ? { taskState: result.taskState } : {}
@@ -14382,6 +14247,17 @@ async function runExecutableChain(profileName, input) {
14382
14247
  }
14383
14248
  return result;
14384
14249
  }
14250
+ function handoffToJob(handoff) {
14251
+ const dutyOrAction = handoff.action ?? handoff.duty;
14252
+ if (!dutyOrAction) return null;
14253
+ return {
14254
+ action: handoff.action ?? handoff.duty,
14255
+ duty: handoff.duty,
14256
+ executable: handoff.executable,
14257
+ cliArgs: handoff.cliArgs,
14258
+ flavor: "instant"
14259
+ };
14260
+ }
14385
14261
  function clearStampedLifecycleLabels(profile, ctx) {
14386
14262
  const target = ctx.args.issue ?? ctx.args.pr;
14387
14263
  if (typeof target !== "number" || !Number.isFinite(target)) return;
@@ -14654,6 +14530,156 @@ var init_executor = __esm({
14654
14530
  }
14655
14531
  });
14656
14532
 
14533
+ // src/job.ts
14534
+ var job_exports = {};
14535
+ __export(job_exports, {
14536
+ DEFAULT_INSTANT_PERSONA: () => DEFAULT_INSTANT_PERSONA,
14537
+ InvalidJobError: () => InvalidJobError,
14538
+ mintInstantJob: () => mintInstantJob,
14539
+ mintScheduledJob: () => mintScheduledJob,
14540
+ newJobId: () => newJobId,
14541
+ runJob: () => runJob,
14542
+ stableJobKey: () => stableJobKey,
14543
+ validateJob: () => validateJob
14544
+ });
14545
+ import * as path38 from "path";
14546
+ function newJobId(flavor) {
14547
+ localJobSeq += 1;
14548
+ const runId = process.env.GITHUB_RUN_ID;
14549
+ if (runId) return `gh-${runId}-${process.env.GITHUB_RUN_ATTEMPT ?? "1"}-${localJobSeq}`;
14550
+ return `${flavor}-${Date.now()}-${localJobSeq}`;
14551
+ }
14552
+ function validateJob(input) {
14553
+ if (!input || typeof input !== "object") {
14554
+ throw new InvalidJobError("job must be an object");
14555
+ }
14556
+ const j = input;
14557
+ if (typeof j.duty !== "string" && typeof j.action !== "string") {
14558
+ throw new InvalidJobError("job must reference a duty action or duty");
14559
+ }
14560
+ if (j.flavor !== "instant" && j.flavor !== "scheduled") {
14561
+ throw new InvalidJobError(`job.flavor must be "instant" or "scheduled" (got ${String(j.flavor)})`);
14562
+ }
14563
+ if (j.cliArgs !== void 0 && (typeof j.cliArgs !== "object" || j.cliArgs === null)) {
14564
+ throw new InvalidJobError("job.cliArgs must be an object when present");
14565
+ }
14566
+ return {
14567
+ action: typeof j.action === "string" ? j.action : void 0,
14568
+ executable: typeof j.executable === "string" ? j.executable : void 0,
14569
+ duty: typeof j.duty === "string" ? j.duty : void 0,
14570
+ why: typeof j.why === "string" ? j.why : void 0,
14571
+ persona: typeof j.persona === "string" ? j.persona : void 0,
14572
+ schedule: typeof j.schedule === "string" ? j.schedule : void 0,
14573
+ target: typeof j.target === "number" ? j.target : void 0,
14574
+ cliArgs: j.cliArgs ?? {},
14575
+ flavor: j.flavor,
14576
+ force: j.force === true
14577
+ };
14578
+ }
14579
+ async function runJob(job, base) {
14580
+ const valid = validateJob(job);
14581
+ const action = valid.action ?? valid.duty;
14582
+ const projectDutiesRoot = path38.join(base.cwd, ".kody", "duties");
14583
+ const resolvedDuty = action ? resolveDutyAction(action, projectDutiesRoot) : null;
14584
+ const dutyIdentity = valid.duty ?? resolvedDuty?.duty;
14585
+ const dutyContext = loadDutyContext(dutyIdentity, base.cwd);
14586
+ if (!resolvedDuty && !dutyContext) {
14587
+ throw new InvalidJobError(`job duty not found: ${action ?? valid.duty ?? "<none>"}`);
14588
+ }
14589
+ const dutySelectedExecutable = resolvedDuty?.executable ?? dutyContext?.config.executable ?? dutyContext?.config.executables?.[0] ?? (dutyContext?.config.tickScript ? "duty-tick-scripted" : void 0);
14590
+ const profileName = valid.executable ?? dutySelectedExecutable;
14591
+ if (!profileName) {
14592
+ throw new InvalidJobError(`job duty resolves to no executable: ${dutyIdentity ?? action}`);
14593
+ }
14594
+ const preloadedData = { ...base.preloadedData ?? {} };
14595
+ preloadedData.jobId = newJobId(valid.flavor);
14596
+ preloadedData.jobKey = stableJobKey(valid);
14597
+ preloadedData.jobFlavor = valid.flavor;
14598
+ if (valid.target !== void 0) preloadedData.jobTarget = valid.target;
14599
+ if (valid.action !== void 0 && valid.action.length > 0) preloadedData.jobAction = valid.action;
14600
+ if (dutyIdentity !== void 0 && dutyIdentity.length > 0) preloadedData.jobDuty = dutyIdentity;
14601
+ const executableIdentity = profileName;
14602
+ if (executableIdentity !== void 0 && executableIdentity.length > 0)
14603
+ preloadedData.jobExecutable = executableIdentity;
14604
+ if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
14605
+ if (dutyContext) {
14606
+ preloadedData.dutySlug = dutyContext.slug;
14607
+ preloadedData.dutyTitle = dutyContext.title;
14608
+ preloadedData.dutyIntent = dutyContext.body;
14609
+ preloadedData.jobIntent = dutyContext.body;
14610
+ if (preloadedData.jobDuty === void 0) preloadedData.jobDuty = dutyContext.slug;
14611
+ if (dutyContext.config.staff && preloadedData.jobPersona === void 0) {
14612
+ preloadedData.jobPersona = dutyContext.config.staff;
14613
+ }
14614
+ if (dutyContext.config.every && preloadedData.jobSchedule === void 0) {
14615
+ preloadedData.jobSchedule = dutyContext.config.every;
14616
+ }
14617
+ if (dutyContext.config.mentions && dutyContext.config.mentions.length > 0) {
14618
+ preloadedData.mentions = dutyContext.config.mentions.map((login) => `@${login}`).join(" ");
14619
+ }
14620
+ }
14621
+ if (valid.why !== void 0 && valid.why.length > 0) preloadedData.jobWhy = valid.why;
14622
+ if (valid.persona !== void 0) preloadedData.jobPersona = valid.persona;
14623
+ const input = {
14624
+ cliArgs: { ...valid.cliArgs },
14625
+ cwd: base.cwd,
14626
+ config: base.config,
14627
+ skipConfig: base.skipConfig,
14628
+ verbose: base.verbose,
14629
+ quiet: base.quiet,
14630
+ preloadedData: Object.keys(preloadedData).length > 0 ? preloadedData : void 0
14631
+ };
14632
+ input.cliArgs = resolvedDuty && profileName === resolvedDuty.executable ? { ...resolvedDuty.cliArgs, ...input.cliArgs } : input.cliArgs;
14633
+ const run = base.chain === false ? runExecutable : runExecutableChain;
14634
+ return run(profileName, input);
14635
+ }
14636
+ function loadDutyContext(slug, cwd) {
14637
+ if (!slug) return null;
14638
+ return readDutyFolder(path38.join(cwd, ".kody", "duties"), slug) ?? readDutyFolder(getProjectDutiesRoot(), slug) ?? readDutyFolder(getBuiltinDutiesRoot(), slug);
14639
+ }
14640
+ function mintInstantJob(dispatch2, opts) {
14641
+ return {
14642
+ action: dispatch2.action,
14643
+ executable: dispatch2.executable,
14644
+ duty: dispatch2.duty,
14645
+ why: opts?.why ?? dispatch2.why,
14646
+ persona: opts?.persona ?? DEFAULT_INSTANT_PERSONA,
14647
+ target: dispatch2.target,
14648
+ cliArgs: dispatch2.cliArgs,
14649
+ flavor: "instant"
14650
+ };
14651
+ }
14652
+ function mintScheduledJob(input) {
14653
+ return {
14654
+ action: input.action,
14655
+ duty: input.duty,
14656
+ executable: input.executable,
14657
+ schedule: input.schedule,
14658
+ persona: input.persona,
14659
+ cliArgs: input.cliArgs ?? {},
14660
+ flavor: "scheduled"
14661
+ };
14662
+ }
14663
+ var DEFAULT_INSTANT_PERSONA, localJobSeq, InvalidJobError;
14664
+ var init_job = __esm({
14665
+ "src/job.ts"() {
14666
+ "use strict";
14667
+ init_dutyFolders();
14668
+ init_executor();
14669
+ init_registry();
14670
+ init_jobIdentity();
14671
+ init_jobIdentity();
14672
+ DEFAULT_INSTANT_PERSONA = "kody";
14673
+ localJobSeq = 0;
14674
+ InvalidJobError = class extends Error {
14675
+ constructor(message) {
14676
+ super(message);
14677
+ this.name = "InvalidJobError";
14678
+ }
14679
+ };
14680
+ }
14681
+ });
14682
+
14657
14683
  // src/entry.ts
14658
14684
  init_package();
14659
14685
 
@@ -14763,7 +14789,7 @@ function translateOpenAISseToBrain(opts) {
14763
14789
  // src/servers/brain-serve.ts
14764
14790
  import * as fs42 from "fs";
14765
14791
  import { createServer } from "http";
14766
- import * as path40 from "path";
14792
+ import * as path41 from "path";
14767
14793
 
14768
14794
  // src/chat/loop.ts
14769
14795
  init_agent();
@@ -15305,7 +15331,7 @@ init_config();
15305
15331
  // src/kody-cli.ts
15306
15332
  import { execFileSync as execFileSync26 } from "child_process";
15307
15333
  import * as fs40 from "fs";
15308
- import * as path38 from "path";
15334
+ import * as path39 from "path";
15309
15335
 
15310
15336
  // src/app-auth.ts
15311
15337
  import { createSign } from "crypto";
@@ -15459,28 +15485,12 @@ function resolveOperatorAction(action) {
15459
15485
  return resolveDutyAction(action);
15460
15486
  }
15461
15487
  function resolveConfiguredAction(action) {
15462
- const resolved = resolveDutyAction(action);
15463
- if (resolved) return resolved;
15464
- return compatibilityDutyAction(action);
15488
+ return resolveDutyAction(action);
15465
15489
  }
15466
15490
  function requiredRoute(action) {
15467
- return resolveConfiguredAction(action) ?? {
15468
- action,
15469
- duty: action,
15470
- executable: action,
15471
- cliArgs: {},
15472
- source: "builtin"
15473
- };
15474
- }
15475
- function compatibilityDutyAction(action) {
15476
- if (!/^[a-z][a-z0-9-]*$/.test(action)) return null;
15477
- return {
15478
- action,
15479
- duty: action,
15480
- executable: action,
15481
- cliArgs: {},
15482
- source: "builtin"
15483
- };
15491
+ const route = resolveConfiguredAction(action);
15492
+ if (!route) throw new Error(`required duty action not found: ${action}`);
15493
+ return route;
15484
15494
  }
15485
15495
  function routeResult(route, cliArgs, target, why) {
15486
15496
  const result = {
@@ -15511,7 +15521,7 @@ function autoDispatch(opts) {
15511
15521
  const inputs2 = objectValue(event.inputs);
15512
15522
  const n = parseInt(String(inputs2?.issue_number ?? ""), 10);
15513
15523
  if (!Number.isNaN(n) && n > 0) {
15514
- const actionName = String(inputs2?.executable ?? "").trim() || "run";
15524
+ const actionName = String(inputs2?.duty ?? inputs2?.executable ?? "").trim() || "run";
15515
15525
  const route2 = resolveConfiguredAction(actionName);
15516
15526
  if (!route2) return null;
15517
15527
  const base = String(inputs2?.base ?? "").trim();
@@ -15652,7 +15662,7 @@ function autoDispatchTyped(opts) {
15652
15662
  if (!tokenRaw || POLITE_WORDS.has(tokenRaw)) {
15653
15663
  return {
15654
15664
  kind: "silent",
15655
- reason: tokenRaw ? `polite-word lead-in '${tokenRaw}', no default executable configured` : "no subcommand token, no default executable configured"
15665
+ reason: tokenRaw ? `polite-word lead-in '${tokenRaw}', no default duty action configured` : "no subcommand token, no default duty action configured"
15656
15666
  };
15657
15667
  }
15658
15668
  const available = listDutyActions().map((e) => e.action).filter((n) => !n.startsWith("goal-") && !n.startsWith("job-")).sort();
@@ -15692,7 +15702,15 @@ function dispatchScheduledWatches(opts) {
15692
15702
  continue;
15693
15703
  }
15694
15704
  }
15695
- out.push({ action: exe.name, duty: exe.name, executable: exe.name, cliArgs: {}, target: 0 });
15705
+ const route = resolveConfiguredAction(exe.name);
15706
+ if (!route) {
15707
+ process.stderr.write(
15708
+ `[kody] dispatchScheduledWatches: '${exe.name}' is scheduled but has no duty action; skipping
15709
+ `
15710
+ );
15711
+ continue;
15712
+ }
15713
+ out.push({ ...route, cliArgs: route.cliArgs, target: 0 });
15696
15714
  }
15697
15715
  return out;
15698
15716
  }
@@ -15794,7 +15812,6 @@ function coerceBare(spec, value) {
15794
15812
  }
15795
15813
 
15796
15814
  // src/kody-cli.ts
15797
- init_executor();
15798
15815
  init_gha();
15799
15816
  init_issue();
15800
15817
  init_job();
@@ -15909,9 +15926,9 @@ async function resolveAuthToken(env = process.env) {
15909
15926
  return void 0;
15910
15927
  }
15911
15928
  function detectPackageManager2(cwd) {
15912
- if (fs40.existsSync(path38.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
15913
- if (fs40.existsSync(path38.join(cwd, "yarn.lock"))) return "yarn";
15914
- if (fs40.existsSync(path38.join(cwd, "bun.lockb"))) return "bun";
15929
+ if (fs40.existsSync(path39.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
15930
+ if (fs40.existsSync(path39.join(cwd, "yarn.lock"))) return "yarn";
15931
+ if (fs40.existsSync(path39.join(cwd, "bun.lockb"))) return "bun";
15915
15932
  return "npm";
15916
15933
  }
15917
15934
  function shellOut(cmd, args, cwd, stream = true) {
@@ -15998,7 +16015,7 @@ function configureGitIdentity(cwd) {
15998
16015
  }
15999
16016
  function postFailureTail(issueNumber, cwd, reason) {
16000
16017
  if (!issueNumber) return;
16001
- const logPath = path38.join(cwd, ".kody", "last-run.jsonl");
16018
+ const logPath = path39.join(cwd, ".kody", "last-run.jsonl");
16002
16019
  let tail = "";
16003
16020
  try {
16004
16021
  if (fs40.existsSync(logPath)) {
@@ -16027,7 +16044,7 @@ async function runCi(argv) {
16027
16044
  return 0;
16028
16045
  }
16029
16046
  const args = parseCiArgs(argv);
16030
- const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
16047
+ const cwd = args.cwd ? path39.resolve(args.cwd) : process.cwd();
16031
16048
  let earlyConfig;
16032
16049
  let earlyConfigError;
16033
16050
  try {
@@ -16045,9 +16062,9 @@ async function runCi(argv) {
16045
16062
  const evt = JSON.parse(fs40.readFileSync(dispatchEventPath, "utf-8"));
16046
16063
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
16047
16064
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
16048
- const exeInput = String(evt?.inputs?.executable ?? "").trim();
16065
+ const dutyInput = String(evt?.inputs?.duty ?? evt?.inputs?.executable ?? "").trim();
16049
16066
  const noTarget = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
16050
- if (noTarget && exeInput) forceRunAction = exeInput;
16067
+ if (noTarget && dutyInput) forceRunAction = dutyInput;
16051
16068
  else manualWorkflowDispatch = noTarget;
16052
16069
  } catch {
16053
16070
  manualWorkflowDispatch = false;
@@ -16147,7 +16164,7 @@ async function runCi(argv) {
16147
16164
  );
16148
16165
  return 0;
16149
16166
  }
16150
- if (outcome.kind === "silent" && earlyConfigError && outcome.reason.includes("no default executable configured")) {
16167
+ if (outcome.kind === "silent" && earlyConfigError && outcome.reason.includes("no default duty action configured")) {
16151
16168
  process.stderr.write(`[kody] config error: ${earlyConfigError.message}
16152
16169
  `);
16153
16170
  return 64;
@@ -16167,11 +16184,14 @@ async function runCi(argv) {
16167
16184
  ${CI_HELP}`);
16168
16185
  return 64;
16169
16186
  }
16187
+ const runRoute = args.issueNumber ? resolveDutyAction("run") : null;
16188
+ if (!autoFallback && args.issueNumber && !runRoute) {
16189
+ process.stderr.write("[kody] required duty action 'run' not found\n");
16190
+ return 64;
16191
+ }
16170
16192
  const dispatch2 = autoFallback ?? {
16171
- action: "run",
16172
- duty: "run",
16173
- executable: "run",
16174
- cliArgs: { issue: args.issueNumber },
16193
+ ...runRoute,
16194
+ cliArgs: { ...runRoute.cliArgs, issue: args.issueNumber },
16175
16195
  target: args.issueNumber
16176
16196
  };
16177
16197
  const issueNumber = dispatch2.target;
@@ -16253,8 +16273,8 @@ async function runScheduledFanOut(cwd, args, opts) {
16253
16273
  );
16254
16274
  return 0;
16255
16275
  }
16256
- const names = matches.map((m) => m.executable).join(", ");
16257
- process.stdout.write(`\u2192 kody: scheduled wake \u2014 firing ${matches.length} watch(es): ${names}
16276
+ const names = matches.map((m) => `${m.duty}\u2192${m.executable}`).join(", ");
16277
+ process.stdout.write(`\u2192 kody: scheduled wake \u2014 firing ${matches.length} watch dut(y/ies): ${names}
16258
16278
  `);
16259
16279
  try {
16260
16280
  const n = unpackAllSecrets();
@@ -16291,19 +16311,21 @@ async function runScheduledFanOut(cwd, args, opts) {
16291
16311
  const serial = process.env.KODY_SERIAL_WATCHES === "1";
16292
16312
  const runWatch = async (match) => {
16293
16313
  process.stdout.write(`
16294
- \u2192 kody: running watch \`${match.executable}\`
16314
+ \u2192 kody: running watch duty \`${match.duty}\` (${match.executable})
16295
16315
  `);
16296
16316
  try {
16297
- const result = await runExecutable(match.executable, {
16298
- cliArgs: match.cliArgs,
16299
- cwd,
16300
- config,
16301
- verbose: args.verbose,
16302
- quiet: args.quiet
16303
- });
16317
+ const result = await runJob(
16318
+ mintScheduledJob({
16319
+ action: match.action,
16320
+ duty: match.duty,
16321
+ executable: match.executable,
16322
+ cliArgs: match.cliArgs
16323
+ }),
16324
+ { cwd, config, verbose: args.verbose, quiet: args.quiet, chain: false }
16325
+ );
16304
16326
  if (result.exitCode !== 0) {
16305
16327
  process.stderr.write(
16306
- `[kody] watch \`${match.executable}\` exited ${result.exitCode}: ${result.reason ?? "(no reason)"}
16328
+ `[kody] watch duty \`${match.duty}\` exited ${result.exitCode}: ${result.reason ?? "(no reason)"}
16307
16329
  `
16308
16330
  );
16309
16331
  return result.exitCode;
@@ -16311,7 +16333,7 @@ async function runScheduledFanOut(cwd, args, opts) {
16311
16333
  return 0;
16312
16334
  } catch (err) {
16313
16335
  const msg = err instanceof Error ? err.message : String(err);
16314
- process.stderr.write(`[kody] watch \`${match.executable}\` crashed: ${msg}
16336
+ process.stderr.write(`[kody] watch duty \`${match.duty}\` crashed: ${msg}
16315
16337
  `);
16316
16338
  return 99;
16317
16339
  }
@@ -16338,10 +16360,10 @@ init_repoWorkspace();
16338
16360
 
16339
16361
  // src/scripts/brainTurnLog.ts
16340
16362
  import * as fs41 from "fs";
16341
- import * as path39 from "path";
16363
+ import * as path40 from "path";
16342
16364
  var live = /* @__PURE__ */ new Map();
16343
16365
  function eventsPath(dir, chatId) {
16344
- return path39.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
16366
+ return path40.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
16345
16367
  }
16346
16368
  function lastPersistedSeq(dir, chatId) {
16347
16369
  const p = eventsPath(dir, chatId);
@@ -16384,7 +16406,7 @@ function beginTurn(dir, chatId) {
16384
16406
  };
16385
16407
  live.set(chatId, state);
16386
16408
  const p = eventsPath(dir, chatId);
16387
- fs41.mkdirSync(path39.dirname(p), { recursive: true });
16409
+ fs41.mkdirSync(path40.dirname(p), { recursive: true });
16388
16410
  return (event) => {
16389
16411
  state.seq += 1;
16390
16412
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
@@ -16659,7 +16681,7 @@ async function handleChatTurn(req, res, chatId, opts) {
16659
16681
  const repo = strField(body, "repo");
16660
16682
  const repoToken = strField(body, "repoToken");
16661
16683
  const sessionFile = sessionFilePath(opts.cwd, chatId);
16662
- fs42.mkdirSync(path40.dirname(sessionFile), { recursive: true });
16684
+ fs42.mkdirSync(path41.dirname(sessionFile), { recursive: true });
16663
16685
  appendTurn(sessionFile, {
16664
16686
  role: "user",
16665
16687
  content: message,
@@ -16706,7 +16728,7 @@ async function handleChatTurn(req, res, chatId, opts) {
16706
16728
  function buildServer(opts) {
16707
16729
  const runTurn = opts.runTurn ?? runChatTurn;
16708
16730
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
16709
- const reposRoot = opts.reposRoot ?? path40.join(path40.dirname(path40.resolve(opts.cwd)), "repos");
16731
+ const reposRoot = opts.reposRoot ?? path41.join(path41.dirname(path41.resolve(opts.cwd)), "repos");
16710
16732
  return createServer(async (req, res) => {
16711
16733
  if (!req.method || !req.url) {
16712
16734
  sendJson(res, 400, { error: "bad request" });
@@ -17300,13 +17322,13 @@ async function loadConfigSafe() {
17300
17322
  // src/chat-cli.ts
17301
17323
  import { execFileSync as execFileSync29 } from "child_process";
17302
17324
  import * as fs44 from "fs";
17303
- import * as path42 from "path";
17325
+ import * as path43 from "path";
17304
17326
 
17305
17327
  // src/chat/modes/interactive.ts
17306
17328
  init_issue();
17307
17329
  import { execFileSync as execFileSync28 } from "child_process";
17308
17330
  import * as fs43 from "fs";
17309
- import * as path41 from "path";
17331
+ import * as path42 from "path";
17310
17332
 
17311
17333
  // src/chat/inbox.ts
17312
17334
  import { execFileSync as execFileSync27 } from "child_process";
@@ -17465,9 +17487,9 @@ function findNextUserTurn(turns, fromIdx) {
17465
17487
  return -1;
17466
17488
  }
17467
17489
  function commitTurn(cwd, sessionId, _verbose) {
17468
- const sessionRel = path41.relative(cwd, sessionFilePath(cwd, sessionId));
17469
- const eventsRel = path41.relative(cwd, eventsFilePath(cwd, sessionId));
17470
- const rels = [sessionRel, eventsRel].filter((p) => fs43.existsSync(path41.join(cwd, p)));
17490
+ const sessionRel = path42.relative(cwd, sessionFilePath(cwd, sessionId));
17491
+ const eventsRel = path42.relative(cwd, eventsFilePath(cwd, sessionId));
17492
+ const rels = [sessionRel, eventsRel].filter((p) => fs43.existsSync(path42.join(cwd, p)));
17471
17493
  if (rels.length === 0) return;
17472
17494
  const repository = process.env.GITHUB_REPOSITORY;
17473
17495
  if (!repository) {
@@ -17479,8 +17501,8 @@ function commitTurn(cwd, sessionId, _verbose) {
17479
17501
  }
17480
17502
  const branch = defaultBranch(cwd) ?? "main";
17481
17503
  for (const rel of rels) {
17482
- const repoPath = rel.split(path41.sep).join("/");
17483
- const localText = fs43.readFileSync(path41.join(cwd, rel), "utf-8");
17504
+ const repoPath = rel.split(path42.sep).join("/");
17505
+ const localText = fs43.readFileSync(path42.join(cwd, rel), "utf-8");
17484
17506
  putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
17485
17507
  }
17486
17508
  }
@@ -17617,12 +17639,12 @@ function parseChatArgs(argv, env = process.env) {
17617
17639
  return result;
17618
17640
  }
17619
17641
  function commitChatFiles(cwd, sessionId, verbose) {
17620
- const sessionFile = path42.relative(cwd, sessionFilePath(cwd, sessionId));
17621
- const eventsFile = path42.relative(cwd, eventsFilePath(cwd, sessionId));
17642
+ const sessionFile = path43.relative(cwd, sessionFilePath(cwd, sessionId));
17643
+ const eventsFile = path43.relative(cwd, eventsFilePath(cwd, sessionId));
17622
17644
  const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
17623
- const tasksDir = path42.join(".kody", "tasks", safeSession);
17645
+ const tasksDir = path43.join(".kody", "tasks", safeSession);
17624
17646
  const candidatePaths = [sessionFile, eventsFile, tasksDir];
17625
- const paths = candidatePaths.filter((p) => fs44.existsSync(path42.join(cwd, p)));
17647
+ const paths = candidatePaths.filter((p) => fs44.existsSync(path43.join(cwd, p)));
17626
17648
  if (paths.length === 0) return;
17627
17649
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
17628
17650
  try {
@@ -17666,7 +17688,7 @@ async function runChat(argv) {
17666
17688
  ${CHAT_HELP}`);
17667
17689
  return 64;
17668
17690
  }
17669
- const cwd = args.cwd ? path42.resolve(args.cwd) : process.cwd();
17691
+ const cwd = args.cwd ? path43.resolve(args.cwd) : process.cwd();
17670
17692
  const sessionId = args.sessionId;
17671
17693
  const unpackedSecrets = unpackAllSecrets();
17672
17694
  if (unpackedSecrets > 0) {
@@ -17757,7 +17779,6 @@ ${CHAT_HELP}`);
17757
17779
 
17758
17780
  // src/entry.ts
17759
17781
  init_config();
17760
- init_executor();
17761
17782
  init_job();
17762
17783
  init_registry();
17763
17784
 
@@ -17875,8 +17896,8 @@ var FlyClient = class {
17875
17896
  get fetch() {
17876
17897
  return this.opts.fetchImpl ?? fetch;
17877
17898
  }
17878
- async call(path43, init = {}) {
17879
- const res = await this.fetch(`${FLY_API_BASE}${path43}`, {
17899
+ async call(path44, init = {}) {
17900
+ const res = await this.fetch(`${FLY_API_BASE}${path44}`, {
17880
17901
  method: init.method ?? "GET",
17881
17902
  headers: {
17882
17903
  Authorization: `Bearer ${this.opts.token}`,
@@ -17887,7 +17908,7 @@ var FlyClient = class {
17887
17908
  if (res.status === 404 && init.allow404) return null;
17888
17909
  if (!res.ok) {
17889
17910
  const text = await res.text().catch(() => "");
17890
- throw new Error(`Fly API ${res.status} on ${path43}: ${text.slice(0, 200) || res.statusText}`);
17911
+ throw new Error(`Fly API ${res.status} on ${path44}: ${text.slice(0, 200) || res.statusText}`);
17891
17912
  }
17892
17913
  if (res.status === 204) return null;
17893
17914
  const raw = await res.text();
@@ -19148,7 +19169,6 @@ Usage:
19148
19169
  kody-engine release --issue <N> [--cwd <path>] [--verbose|--quiet]
19149
19170
  kody-engine init [--cwd <path>] [--verbose|--quiet]
19150
19171
  kody-engine <action> [--cwd <path>] [--verbose|--quiet]
19151
- kody-engine exec <executable> [--cwd <path>] [--verbose|--quiet]
19152
19172
  kody-engine ci [preflight flags \u2014 see: kody-engine ci --help]
19153
19173
  kody-engine chat [chat flags \u2014 see: kody-engine chat --help]
19154
19174
  kody-engine stats [--since 7d|--run <id>|--json|--cwd <path>]
@@ -19156,8 +19176,7 @@ Usage:
19156
19176
  kody-engine version
19157
19177
 
19158
19178
  Top-level work commands are duty actions. A duty owns the public action name
19159
- and selects an implementation executable. \`exec <executable>\` is the low-level
19160
- debug path for engine internals and migration compatibility.
19179
+ and selects an implementation executable.
19161
19180
 
19162
19181
  Exit codes:
19163
19182
  0 success (PR opened, verify passed \u2014 or resolve produced a merge commit)
@@ -19198,24 +19217,6 @@ function parseArgs(argv) {
19198
19217
  result.serverArgs = argv.slice(1).filter((a) => !a.startsWith("-"));
19199
19218
  return result;
19200
19219
  }
19201
- if (cmd === "exec") {
19202
- const executableName = argv[1];
19203
- if (!executableName) {
19204
- result.errors.push("exec requires an executable name");
19205
- return result;
19206
- }
19207
- if (!hasExecutable(executableName)) {
19208
- result.errors.push(`unknown executable: ${executableName}`);
19209
- return result;
19210
- }
19211
- result.command = "__executable__";
19212
- result.executableName = executableName;
19213
- result.cliArgs = parseGenericFlags(argv.slice(2));
19214
- if (typeof result.cliArgs.cwd === "string") result.cwd = result.cliArgs.cwd;
19215
- if (result.cliArgs.verbose === true) result.verbose = true;
19216
- if (result.cliArgs.quiet === true) result.quiet = true;
19217
- return result;
19218
- }
19219
19220
  if (hasDutyAction(cmd)) {
19220
19221
  result.command = "__duty__";
19221
19222
  result.actionName = cmd;
@@ -19225,18 +19226,8 @@ function parseArgs(argv) {
19225
19226
  if (result.cliArgs.quiet === true) result.quiet = true;
19226
19227
  return result;
19227
19228
  }
19228
- if (hasExecutable(cmd)) {
19229
- result.command = "__executable__";
19230
- result.executableName = cmd;
19231
- result.cliArgs = parseGenericFlags(argv.slice(1));
19232
- if (typeof result.cliArgs.cwd === "string") result.cwd = result.cliArgs.cwd;
19233
- if (result.cliArgs.verbose === true) result.verbose = true;
19234
- if (result.cliArgs.quiet === true) result.quiet = true;
19235
- return result;
19236
- }
19237
19229
  const discoveredActions = listDutyActions().map((e) => e.action);
19238
- const discoveredExecutables = listExecutables().map((e) => `exec ${e.name}`);
19239
- const available = ["ci", "chat", "stats", "help", "version", ...discoveredActions, ...discoveredExecutables];
19230
+ const available = ["ci", "chat", "stats", "help", "version", ...discoveredActions];
19240
19231
  result.errors.push(`unknown command: ${cmd} (available: ${available.join(", ")})`);
19241
19232
  return result;
19242
19233
  }
@@ -19334,7 +19325,7 @@ ${HELP_TEXT}`);
19334
19325
  return 64;
19335
19326
  }
19336
19327
  const cliArgs = { ...route.cliArgs, ...args.cliArgs ?? {} };
19337
- const skipConfig2 = configlessCommands.has(route.executable);
19328
+ const skipConfig = configlessCommands.has(route.executable);
19338
19329
  try {
19339
19330
  const result = await runJob(
19340
19331
  {
@@ -19347,7 +19338,7 @@ ${HELP_TEXT}`);
19347
19338
  },
19348
19339
  {
19349
19340
  cwd,
19350
- skipConfig: skipConfig2,
19341
+ skipConfig,
19351
19342
  verbose: args.verbose,
19352
19343
  quiet: args.quiet
19353
19344
  }
@@ -19368,30 +19359,8 @@ ${HELP_TEXT}`);
19368
19359
  return 99;
19369
19360
  }
19370
19361
  }
19371
- const skipConfig = configlessCommands.has(args.executableName ?? "");
19372
- try {
19373
- const result = await runExecutableChain(args.executableName, {
19374
- cliArgs: args.cliArgs ?? {},
19375
- cwd,
19376
- skipConfig,
19377
- verbose: args.verbose,
19378
- quiet: args.quiet
19379
- });
19380
- if (result.exitCode !== 0 && result.reason) {
19381
- process.stderr.write(`error: ${result.reason}
19382
- `);
19383
- }
19384
- return result.exitCode;
19385
- } catch (err) {
19386
- const msg = err instanceof Error ? err.message : String(err);
19387
- process.stderr.write(`[kody] ${args.executableName} crashed: ${msg}
19388
- `);
19389
- if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
19390
- `);
19391
- process.stdout.write(`PR_URL=FAILED: ${args.executableName} crashed: ${msg}
19392
- `);
19393
- return 99;
19394
- }
19362
+ process.stderr.write("error: command did not resolve to a duty\n");
19363
+ return 64;
19395
19364
  }
19396
19365
  function numericTarget(cliArgs) {
19397
19366
  for (const key of ["issue", "pr"]) {