@kody-ade/kody-engine 0.4.213 → 0.4.214-live.0

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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +193 -118
  2. package/package.json +1 -1
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.213",
18
+ version: "0.4.214-live.0",
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",
@@ -7232,6 +7232,9 @@ function parseFlatYaml(text) {
7232
7232
  } else if (key === "tools") {
7233
7233
  const names = value.split(",").map((s) => s.trim()).filter(Boolean);
7234
7234
  if (names.length > 0) out.tools = names;
7235
+ } else if (key === "executables") {
7236
+ const names = value.split(",").map((s) => s.trim()).filter(Boolean);
7237
+ if (names.length > 0) out.executables = names;
7235
7238
  }
7236
7239
  }
7237
7240
  return out;
@@ -7665,6 +7668,119 @@ var init_jobState = __esm({
7665
7668
  }
7666
7669
  });
7667
7670
 
7671
+ // src/scripts/planTaskJobs.ts
7672
+ function parseTaskJobSpecs(body) {
7673
+ const match = body.match(new RegExp(`<!--\\s*${TASK_JOBS_MARKER}\\s*([\\s\\S]*?)-->`));
7674
+ if (!match) return [];
7675
+ let parsed;
7676
+ try {
7677
+ parsed = JSON.parse(match[1].trim());
7678
+ } catch (err) {
7679
+ throw new Error(`task job plan is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
7680
+ }
7681
+ if (!Array.isArray(parsed)) throw new Error("task job plan must be a JSON array");
7682
+ return parsed.map((entry, index) => normalizeSpec(entry, index));
7683
+ }
7684
+ function taskJobSpecToJob(spec, issueNumber) {
7685
+ const cliArgs = spec.cliArgs ?? { issue: issueNumber };
7686
+ const target = typeof spec.target === "number" ? spec.target : targetFromCliArgs(cliArgs) ?? issueNumber;
7687
+ return {
7688
+ duty: spec.duty,
7689
+ executable: spec.executable,
7690
+ why: spec.reason,
7691
+ persona: spec.persona ?? spec.staff,
7692
+ schedule: spec.schedule,
7693
+ target,
7694
+ cliArgs,
7695
+ flavor: spec.flavor ?? "instant"
7696
+ };
7697
+ }
7698
+ function normalizeSpec(input, index) {
7699
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
7700
+ throw new Error(`task job plan entry ${index} must be an object`);
7701
+ }
7702
+ const raw = input;
7703
+ const executable = typeof raw.executable === "string" ? raw.executable.trim() : "";
7704
+ if (!/^[a-z][a-z0-9-]*$/.test(executable)) {
7705
+ throw new Error(`task job plan entry ${index} must have a valid executable`);
7706
+ }
7707
+ const cliArgs = raw.cliArgs;
7708
+ if (cliArgs !== void 0 && (!cliArgs || typeof cliArgs !== "object" || Array.isArray(cliArgs))) {
7709
+ throw new Error(`task job plan entry ${index} cliArgs must be an object`);
7710
+ }
7711
+ const flavor = raw.flavor;
7712
+ if (flavor !== void 0 && flavor !== "instant" && flavor !== "scheduled") {
7713
+ throw new Error(`task job plan entry ${index} flavor must be "instant" or "scheduled"`);
7714
+ }
7715
+ return {
7716
+ executable,
7717
+ ...typeof raw.duty === "string" && raw.duty.trim() ? { duty: raw.duty.trim() } : {},
7718
+ ...typeof raw.reason === "string" && raw.reason.trim() ? { reason: raw.reason.trim() } : {},
7719
+ ...typeof raw.staff === "string" && raw.staff.trim() ? { staff: raw.staff.trim() } : {},
7720
+ ...typeof raw.persona === "string" && raw.persona.trim() ? { persona: raw.persona.trim() } : {},
7721
+ ...cliArgs ? { cliArgs } : {},
7722
+ ...typeof raw.target === "number" && Number.isFinite(raw.target) ? { target: raw.target } : {},
7723
+ ...flavor === "instant" || flavor === "scheduled" ? { flavor } : {},
7724
+ ...typeof raw.schedule === "string" && raw.schedule.trim() ? { schedule: raw.schedule.trim() } : {}
7725
+ };
7726
+ }
7727
+ function jobToPlannedTaskJob(job) {
7728
+ return {
7729
+ id: stableJobKey(job),
7730
+ executable: job.executable ?? job.duty ?? "unknown",
7731
+ ...job.duty ? { duty: job.duty } : {},
7732
+ ...job.persona ? { staff: job.persona } : {},
7733
+ ...job.flavor ? { flavor: job.flavor } : {},
7734
+ ...job.schedule ? { schedule: job.schedule } : {},
7735
+ ...typeof job.target === "number" ? { target: job.target } : {},
7736
+ ...job.why ? { reason: job.why } : {}
7737
+ };
7738
+ }
7739
+ function assertUniqueJobIds(planned) {
7740
+ const seen = /* @__PURE__ */ new Set();
7741
+ for (const job of planned) {
7742
+ if (seen.has(job.id)) throw new Error(`duplicate planned task job id: ${job.id}`);
7743
+ seen.add(job.id);
7744
+ }
7745
+ }
7746
+ var TASK_JOBS_MARKER, planTaskJobs;
7747
+ var init_planTaskJobs = __esm({
7748
+ "src/scripts/planTaskJobs.ts"() {
7749
+ "use strict";
7750
+ init_jobIdentity();
7751
+ init_state();
7752
+ TASK_JOBS_MARKER = "kody:task-jobs:v1";
7753
+ planTaskJobs = async (ctx) => {
7754
+ const issueNumber = ctx.args.issue;
7755
+ if (!issueNumber) {
7756
+ ctx.skipAgent = true;
7757
+ ctx.output.exitCode = 64;
7758
+ ctx.output.reason = "planTaskJobs requires --issue";
7759
+ return;
7760
+ }
7761
+ const issue = ctx.data.issue;
7762
+ const specs = parseTaskJobSpecs(issue?.body ?? "");
7763
+ if (specs.length === 0) {
7764
+ ctx.skipAgent = true;
7765
+ ctx.output.exitCode = 64;
7766
+ ctx.output.reason = `no ${TASK_JOBS_MARKER} block found on issue #${issueNumber}`;
7767
+ return;
7768
+ }
7769
+ const jobs = specs.map((spec) => taskJobSpecToJob(spec, issueNumber));
7770
+ const planned = jobs.map(jobToPlannedTaskJob);
7771
+ assertUniqueJobIds(planned);
7772
+ const prior = ctx.data.taskState ?? emptyState();
7773
+ const next = upsertTaskJobs(prior, planned, (/* @__PURE__ */ new Date()).toISOString());
7774
+ ctx.data.taskState = next;
7775
+ ctx.data.plannedTaskJobs = jobs;
7776
+ ctx.data.plannedTaskJobIds = planned.map((job) => job.id);
7777
+ const target = ctx.data.commentTargetType;
7778
+ const number = ctx.data.commentTargetNumber;
7779
+ if (target && number) writeTaskState(target, number, next, ctx.cwd);
7780
+ };
7781
+ }
7782
+ });
7783
+
7668
7784
  // src/scripts/dispatchDutyFileTicks.ts
7669
7785
  import * as fs29 from "fs";
7670
7786
  import * as path26 from "path";
@@ -7708,14 +7824,43 @@ function formatAgo(ms) {
7708
7824
  const day = Math.round(hr / 24);
7709
7825
  return `${day}d`;
7710
7826
  }
7711
- function readJobFrontmatter(cwd, jobsDir, slug) {
7827
+ function readJobFile(cwd, jobsDir, slug) {
7712
7828
  try {
7713
7829
  const raw = fs29.readFileSync(path26.join(cwd, jobsDir, `${slug}.md`), "utf-8");
7714
- return splitFrontmatter2(raw).frontmatter;
7830
+ return splitFrontmatter2(raw);
7715
7831
  } catch {
7716
- return {};
7832
+ return { frontmatter: {}, body: "" };
7717
7833
  }
7718
7834
  }
7835
+ function createDutyTaskIssue(opts) {
7836
+ const title = `Duty ${opts.slug} - multi-executable task`;
7837
+ const body = buildDutyTaskIssueBody(opts.slug, opts.body, opts.frontmatter);
7838
+ const out = gh(["issue", "create", "--title", title, "--body-file", "-"], { input: body, cwd: opts.cwd });
7839
+ const url = out.split("\n").map((line) => line.trim()).filter(Boolean).pop() ?? "";
7840
+ const match = url.match(/\/issues\/(\d+)\b/);
7841
+ if (!match) throw new Error(`gh issue create returned unexpected output: ${out}`);
7842
+ return { number: Number(match[1]), url };
7843
+ }
7844
+ function buildDutyTaskIssueBody(slug, dutyBody, frontmatter) {
7845
+ const specs = (frontmatter.executables ?? []).map((executable) => ({
7846
+ executable,
7847
+ duty: slug,
7848
+ ...frontmatter.staff ? { staff: frontmatter.staff } : {},
7849
+ reason: `Duty \`${slug}\` slice for \`${executable}\`.`,
7850
+ flavor: "scheduled",
7851
+ ...frontmatter.every ? { schedule: frontmatter.every } : {}
7852
+ }));
7853
+ return [
7854
+ `# Duty task: ${slug}`,
7855
+ "",
7856
+ dutyBody.trim() || "(no duty body)",
7857
+ "",
7858
+ `<!-- ${TASK_JOBS_MARKER}`,
7859
+ JSON.stringify(specs, null, 2),
7860
+ "-->",
7861
+ ""
7862
+ ].join("\n");
7863
+ }
7719
7864
  function listJobSlugs(absDir) {
7720
7865
  if (!fs29.existsSync(absDir)) return [];
7721
7866
  let entries;
@@ -7736,10 +7881,14 @@ function listFolderDutySlugs(absDir) {
7736
7881
  }
7737
7882
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => fs29.existsSync(path26.join(absDir, e.name, "profile.json"))).map((e) => e.name).sort();
7738
7883
  }
7739
- async function stampFired(backend, slug, now) {
7884
+ async function stampFired(backend, slug, now, task) {
7740
7885
  try {
7741
7886
  const loaded = await backend.load(slug);
7742
- const nextData = { ...loaded.state.data ?? {}, lastFiredAt: new Date(now).toISOString() };
7887
+ const nextData = {
7888
+ ...loaded.state.data ?? {},
7889
+ lastFiredAt: new Date(now).toISOString(),
7890
+ ...task ? { lastTaskIssue: task.number, lastTaskUrl: task.url } : {}
7891
+ };
7743
7892
  await backend.save(loaded, { ...loaded.state, data: nextData });
7744
7893
  } catch (err) {
7745
7894
  process.stderr.write(`[jobs] failed to stamp lastFiredAt for ${slug}: ${String(err)}
@@ -7750,10 +7899,12 @@ var dispatchDutyFileTicks;
7750
7899
  var init_dispatchDutyFileTicks = __esm({
7751
7900
  "src/scripts/dispatchDutyFileTicks.ts"() {
7752
7901
  "use strict";
7902
+ init_issue();
7753
7903
  init_job();
7754
7904
  init_profile();
7755
7905
  init_jobFrontmatter();
7756
7906
  init_jobState();
7907
+ init_planTaskJobs();
7757
7908
  dispatchDutyFileTicks = async (ctx, _profile, args) => {
7758
7909
  ctx.skipAgent = true;
7759
7910
  const targetExecutable = String(args?.targetExecutable ?? "");
@@ -7837,7 +7988,8 @@ var init_dispatchDutyFileTicks = __esm({
7837
7988
  results.push({ slug, exitCode: 0, skipped: true, reason: "handled as folder-duty" });
7838
7989
  continue;
7839
7990
  }
7840
- const frontmatter = readJobFrontmatter(ctx.cwd, jobsDir, slug);
7991
+ const dutyFile = readJobFile(ctx.cwd, jobsDir, slug);
7992
+ const frontmatter = dutyFile.frontmatter;
7841
7993
  if (frontmatter.disabled === true) {
7842
7994
  process.stdout.write(`[jobs] \u23ED skip ${slug}: disabled in frontmatter
7843
7995
  `);
@@ -7857,6 +8009,40 @@ var init_dispatchDutyFileTicks = __esm({
7857
8009
  results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
7858
8010
  continue;
7859
8011
  }
8012
+ if (frontmatter.executables && frontmatter.executables.length > 0) {
8013
+ try {
8014
+ const task = createDutyTaskIssue({
8015
+ slug,
8016
+ body: dutyFile.body,
8017
+ frontmatter,
8018
+ cwd: ctx.cwd
8019
+ });
8020
+ await stampFired(backend, slug, now, task);
8021
+ process.stdout.write(`[jobs] \u2192 run ${slug} multi-executable task #${task.number} (task-jobs)
8022
+ `);
8023
+ const out = await runJob(
8024
+ mintScheduledJob({
8025
+ duty: slug,
8026
+ executable: "task-jobs",
8027
+ schedule: frontmatter.every,
8028
+ persona: frontmatter.staff,
8029
+ cliArgs: { issue: task.number }
8030
+ }),
8031
+ { cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet }
8032
+ );
8033
+ results.push({ slug, exitCode: out.exitCode, reason: out.reason });
8034
+ if (out.exitCode !== 0) {
8035
+ process.stderr.write(`[jobs] task ${slug} failed (exit ${out.exitCode}): ${out.reason ?? ""}
8036
+ `);
8037
+ }
8038
+ } catch (err) {
8039
+ const msg = err instanceof Error ? err.message : String(err);
8040
+ process.stderr.write(`[jobs] task ${slug} crashed: ${msg}
8041
+ `);
8042
+ results.push({ slug, exitCode: 99, reason: msg });
8043
+ }
8044
+ continue;
8045
+ }
7860
8046
  const slugTarget = frontmatter.tickScript ? scriptedExecutable : targetExecutable;
7861
8047
  process.stdout.write(`[jobs] \u2192 tick ${slug} (${slugTarget})
7862
8048
  `);
@@ -10684,117 +10870,6 @@ var init_persistFlowState = __esm({
10684
10870
  }
10685
10871
  });
10686
10872
 
10687
- // src/scripts/planTaskJobs.ts
10688
- function parseTaskJobSpecs(body) {
10689
- const match = body.match(new RegExp(`<!--\\s*${TASK_JOBS_MARKER}\\s*([\\s\\S]*?)-->`));
10690
- if (!match) return [];
10691
- let parsed;
10692
- try {
10693
- parsed = JSON.parse(match[1].trim());
10694
- } catch (err) {
10695
- throw new Error(`task job plan is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
10696
- }
10697
- if (!Array.isArray(parsed)) throw new Error("task job plan must be a JSON array");
10698
- return parsed.map((entry, index) => normalizeSpec(entry, index));
10699
- }
10700
- function taskJobSpecToJob(spec, issueNumber) {
10701
- const cliArgs = spec.cliArgs ?? { issue: issueNumber };
10702
- const target = typeof spec.target === "number" ? spec.target : targetFromCliArgs(cliArgs) ?? issueNumber;
10703
- return {
10704
- executable: spec.executable,
10705
- why: spec.reason,
10706
- persona: spec.persona ?? spec.staff,
10707
- schedule: spec.schedule,
10708
- target,
10709
- cliArgs,
10710
- flavor: spec.flavor ?? "instant"
10711
- };
10712
- }
10713
- function normalizeSpec(input, index) {
10714
- if (!input || typeof input !== "object" || Array.isArray(input)) {
10715
- throw new Error(`task job plan entry ${index} must be an object`);
10716
- }
10717
- const raw = input;
10718
- const executable = typeof raw.executable === "string" ? raw.executable.trim() : "";
10719
- if (!/^[a-z][a-z0-9-]*$/.test(executable)) {
10720
- throw new Error(`task job plan entry ${index} must have a valid executable`);
10721
- }
10722
- const cliArgs = raw.cliArgs;
10723
- if (cliArgs !== void 0 && (!cliArgs || typeof cliArgs !== "object" || Array.isArray(cliArgs))) {
10724
- throw new Error(`task job plan entry ${index} cliArgs must be an object`);
10725
- }
10726
- const flavor = raw.flavor;
10727
- if (flavor !== void 0 && flavor !== "instant" && flavor !== "scheduled") {
10728
- throw new Error(`task job plan entry ${index} flavor must be "instant" or "scheduled"`);
10729
- }
10730
- return {
10731
- executable,
10732
- ...typeof raw.reason === "string" && raw.reason.trim() ? { reason: raw.reason.trim() } : {},
10733
- ...typeof raw.staff === "string" && raw.staff.trim() ? { staff: raw.staff.trim() } : {},
10734
- ...typeof raw.persona === "string" && raw.persona.trim() ? { persona: raw.persona.trim() } : {},
10735
- ...cliArgs ? { cliArgs } : {},
10736
- ...typeof raw.target === "number" && Number.isFinite(raw.target) ? { target: raw.target } : {},
10737
- ...flavor === "instant" || flavor === "scheduled" ? { flavor } : {},
10738
- ...typeof raw.schedule === "string" && raw.schedule.trim() ? { schedule: raw.schedule.trim() } : {}
10739
- };
10740
- }
10741
- function jobToPlannedTaskJob(job) {
10742
- return {
10743
- id: stableJobKey(job),
10744
- executable: job.executable ?? job.duty ?? "unknown",
10745
- ...job.duty ? { duty: job.duty } : {},
10746
- ...job.persona ? { staff: job.persona } : {},
10747
- ...job.flavor ? { flavor: job.flavor } : {},
10748
- ...job.schedule ? { schedule: job.schedule } : {},
10749
- ...typeof job.target === "number" ? { target: job.target } : {},
10750
- ...job.why ? { reason: job.why } : {}
10751
- };
10752
- }
10753
- function assertUniqueJobIds(planned) {
10754
- const seen = /* @__PURE__ */ new Set();
10755
- for (const job of planned) {
10756
- if (seen.has(job.id)) throw new Error(`duplicate planned task job id: ${job.id}`);
10757
- seen.add(job.id);
10758
- }
10759
- }
10760
- var TASK_JOBS_MARKER, planTaskJobs;
10761
- var init_planTaskJobs = __esm({
10762
- "src/scripts/planTaskJobs.ts"() {
10763
- "use strict";
10764
- init_jobIdentity();
10765
- init_state();
10766
- TASK_JOBS_MARKER = "kody:task-jobs:v1";
10767
- planTaskJobs = async (ctx) => {
10768
- const issueNumber = ctx.args.issue;
10769
- if (!issueNumber) {
10770
- ctx.skipAgent = true;
10771
- ctx.output.exitCode = 64;
10772
- ctx.output.reason = "planTaskJobs requires --issue";
10773
- return;
10774
- }
10775
- const issue = ctx.data.issue;
10776
- const specs = parseTaskJobSpecs(issue?.body ?? "");
10777
- if (specs.length === 0) {
10778
- ctx.skipAgent = true;
10779
- ctx.output.exitCode = 64;
10780
- ctx.output.reason = `no ${TASK_JOBS_MARKER} block found on issue #${issueNumber}`;
10781
- return;
10782
- }
10783
- const jobs = specs.map((spec) => taskJobSpecToJob(spec, issueNumber));
10784
- const planned = jobs.map(jobToPlannedTaskJob);
10785
- assertUniqueJobIds(planned);
10786
- const prior = ctx.data.taskState ?? emptyState();
10787
- const next = upsertTaskJobs(prior, planned, (/* @__PURE__ */ new Date()).toISOString());
10788
- ctx.data.taskState = next;
10789
- ctx.data.plannedTaskJobs = jobs;
10790
- ctx.data.plannedTaskJobIds = planned.map((job) => job.id);
10791
- const target = ctx.data.commentTargetType;
10792
- const number = ctx.data.commentTargetNumber;
10793
- if (target && number) writeTaskState(target, number, next, ctx.cwd);
10794
- };
10795
- }
10796
- });
10797
-
10798
10873
  // src/scripts/postAgentSummaryComment.ts
10799
10874
  function postAgentSummaryComment(ctx, opts = {}) {
10800
10875
  if (!ctx.data.agentDone) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.213",
3
+ "version": "0.4.214-live.0",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",