@kody-ade/kody-engine 0.4.204-next.3 → 0.4.204-next.5

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
@@ -1483,7 +1483,7 @@ var init_loadCoverageRules = __esm({
1483
1483
  // package.json
1484
1484
  var package_default = {
1485
1485
  name: "@kody-ade/kody-engine",
1486
- version: "0.4.204-next.3",
1486
+ version: "0.4.204-next.5",
1487
1487
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
1488
1488
  license: "MIT",
1489
1489
  type: "module",
@@ -4023,6 +4023,7 @@ var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementin
4023
4023
  var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
4024
4024
  "name",
4025
4025
  "staff",
4026
+ "every",
4026
4027
  "describe",
4027
4028
  "role",
4028
4029
  "kind",
@@ -4104,6 +4105,8 @@ function loadProfile(profilePath) {
4104
4105
  describe: typeof r.describe === "string" ? r.describe : "",
4105
4106
  // Optional persona to run as. Empty/blank string → undefined (no persona).
4106
4107
  staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
4108
+ // Optional recurrence cadence (scheduled duty). Blank → undefined (on-demand).
4109
+ every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
4107
4110
  role,
4108
4111
  kind,
4109
4112
  schedule: typeof r.schedule === "string" ? r.schedule : void 0,
@@ -8497,6 +8500,55 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
8497
8500
  `);
8498
8501
  const results = [];
8499
8502
  const now = Date.now();
8503
+ const scheduledDuties = listFolderDutySlugs(path29.join(ctx.cwd, jobsDir)).map((slug) => {
8504
+ try {
8505
+ const p = loadProfile(path29.join(ctx.cwd, jobsDir, slug, "profile.json"));
8506
+ return { slug, every: p.every, staff: p.staff };
8507
+ } catch (err) {
8508
+ process.stderr.write(`[jobs] \u23ED skip folder-duty ${slug}: profile load failed: ${String(err)}
8509
+ `);
8510
+ return null;
8511
+ }
8512
+ }).filter((d) => d !== null && Boolean(d.every));
8513
+ process.stdout.write(`[jobs] ${scheduledDuties.length} scheduled folder-dut(y/ies) to consider
8514
+ `);
8515
+ for (const { slug, every, staff } of scheduledDuties) {
8516
+ if (!staff || staff.trim().length === 0) {
8517
+ process.stderr.write(`[jobs] \u23ED skip ${slug}: scheduled duty has no staff
8518
+ `);
8519
+ results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
8520
+ continue;
8521
+ }
8522
+ const decision = await decideShouldFire(every, slug, backend, now);
8523
+ if (decision.skip) {
8524
+ process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
8525
+ `);
8526
+ results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
8527
+ continue;
8528
+ }
8529
+ await stampFired(backend, slug, now);
8530
+ process.stdout.write(`[jobs] \u2192 run scheduled duty ${slug} (one-shot, as ${staff})
8531
+ `);
8532
+ try {
8533
+ const out = await runExecutable(slug, {
8534
+ cliArgs: {},
8535
+ cwd: ctx.cwd,
8536
+ config: ctx.config,
8537
+ verbose: ctx.verbose,
8538
+ quiet: ctx.quiet
8539
+ });
8540
+ results.push({ slug, exitCode: out.exitCode, reason: out.reason });
8541
+ if (out.exitCode !== 0) {
8542
+ process.stderr.write(`[jobs] scheduled duty ${slug} failed (exit ${out.exitCode}): ${out.reason ?? ""}
8543
+ `);
8544
+ }
8545
+ } catch (err) {
8546
+ const msg = err instanceof Error ? err.message : String(err);
8547
+ process.stderr.write(`[jobs] scheduled duty ${slug} crashed: ${msg}
8548
+ `);
8549
+ results.push({ slug, exitCode: 99, reason: msg });
8550
+ }
8551
+ }
8500
8552
  for (const slug of slugs) {
8501
8553
  const frontmatter = readJobFrontmatter(ctx.cwd, jobsDir, slug);
8502
8554
  if (frontmatter.disabled === true) {
@@ -8613,6 +8665,26 @@ function listJobSlugs(absDir) {
8613
8665
  }
8614
8666
  return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, "")).filter((slug) => slug.length > 0 && !slug.startsWith("_") && !slug.startsWith(".")).sort();
8615
8667
  }
8668
+ function listFolderDutySlugs(absDir) {
8669
+ if (!fs32.existsSync(absDir)) return [];
8670
+ let entries;
8671
+ try {
8672
+ entries = fs32.readdirSync(absDir, { withFileTypes: true });
8673
+ } catch {
8674
+ return [];
8675
+ }
8676
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => fs32.existsSync(path29.join(absDir, e.name, "profile.json"))).map((e) => e.name).sort();
8677
+ }
8678
+ async function stampFired(backend, slug, now) {
8679
+ try {
8680
+ const loaded = await backend.load(slug);
8681
+ const nextData = { ...loaded.state.data ?? {}, lastFiredAt: new Date(now).toISOString() };
8682
+ await backend.save(loaded, { ...loaded.state, data: nextData });
8683
+ } catch (err) {
8684
+ process.stderr.write(`[jobs] failed to stamp lastFiredAt for ${slug}: ${String(err)}
8685
+ `);
8686
+ }
8687
+ }
8616
8688
 
8617
8689
  // src/scripts/dispatchJobTicks.ts
8618
8690
  init_issue();
@@ -49,6 +49,14 @@ export interface Profile {
49
49
  * `schedule:`). Scheduled profiles must declare a `schedule` cron string.
50
50
  */
51
51
  kind: "oneshot" | "scheduled"
52
+ /**
53
+ * Recurrence cadence for a duty that runs on a timer (unified successor to a
54
+ * markdown duty's `every:` frontmatter). One of the ScheduleEvery values
55
+ * ("15m".."7d" | "manual"). Present → the duty-scheduler fires a one-shot run
56
+ * when due (no target). Absent → on-demand only (runs against an issue/PR).
57
+ * This is what makes "scheduled" just a field on the one duty shape.
58
+ */
59
+ every?: string
52
60
  /** Cron expression for scheduled profiles (e.g. "0 8 * * MON"). */
53
61
  schedule?: string
54
62
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.204-next.3",
3
+ "version": "0.4.204-next.5",
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",