@kody-ade/kody-engine 0.4.121 → 0.4.122

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
@@ -880,7 +880,7 @@ var init_loadPriorArt = __esm({
880
880
  // package.json
881
881
  var package_default = {
882
882
  name: "@kody-ade/kody-engine",
883
- version: "0.4.121",
883
+ version: "0.4.122",
884
884
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
885
885
  license: "MIT",
886
886
  type: "module",
@@ -901,6 +901,7 @@ var package_default = {
901
901
  "check:modularity": "tsx scripts/check-script-modularity.ts",
902
902
  pretest: "pnpm check:modularity",
903
903
  test: "vitest run tests/unit tests/int --coverage",
904
+ "test:smoke": "vitest run tests/smoke --no-coverage",
904
905
  "test:e2e": "vitest run tests/e2e --no-coverage",
905
906
  "test:all": "vitest run tests --no-coverage",
906
907
  typecheck: "tsc --noEmit",
@@ -7451,8 +7452,8 @@ function parseFlatYaml(text) {
7451
7452
  const lower = value.toLowerCase();
7452
7453
  if (lower === "true") out.disabled = true;
7453
7454
  else if (lower === "false") out.disabled = false;
7454
- } else if (key === "worker" && value.length > 0) {
7455
- out.worker = value;
7455
+ } else if (key === "staff" && value.length > 0) {
7456
+ out.staff = value;
7456
7457
  }
7457
7458
  }
7458
7459
  return out;
@@ -7812,7 +7813,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
7812
7813
  if (!targetExecutable) {
7813
7814
  throw new Error("dispatchJobFileTicks: `with.targetExecutable` is required");
7814
7815
  }
7815
- const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
7816
+ const jobsDir = String(args?.jobsDir ?? ".kody/duties");
7816
7817
  const scriptedExecutable = String(args?.scriptedExecutable ?? "job-tick-scripted");
7817
7818
  const slugArg = String(args?.slugArg ?? "job");
7818
7819
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
@@ -7839,12 +7840,12 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
7839
7840
  results.push({ slug, exitCode: 0, skipped: true, reason: "disabled" });
7840
7841
  continue;
7841
7842
  }
7842
- if (!frontmatter.worker || frontmatter.worker.trim().length === 0) {
7843
+ if (!frontmatter.staff || frontmatter.staff.trim().length === 0) {
7843
7844
  process.stderr.write(
7844
- `[jobs] \u23ED skip ${slug}: no worker assigned (add 'worker: <slug>' frontmatter)
7845
+ `[jobs] \u23ED skip ${slug}: no staff assigned (add 'staff: <slug>' frontmatter)
7845
7846
  `
7846
7847
  );
7847
- results.push({ slug, exitCode: 0, skipped: true, reason: "no worker assigned" });
7848
+ results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
7848
7849
  continue;
7849
7850
  }
7850
7851
  const decision = await decideShouldFire(frontmatter.every, slug, backend, now);
@@ -8384,6 +8385,7 @@ var finalizeGoal = async (ctx) => {
8384
8385
  `);
8385
8386
  }
8386
8387
  }
8388
+ const leafIssues = new Set(prIssueNumbers(leaf));
8387
8389
  const openIssues = (goal.childTasks ?? []).filter((t) => t.state === "OPEN");
8388
8390
  for (const t of openIssues) {
8389
8391
  if (uncarriedIssues.has(t.number)) {
@@ -8393,6 +8395,18 @@ var finalizeGoal = async (ctx) => {
8393
8395
  );
8394
8396
  continue;
8395
8397
  }
8398
+ if (leafIssues.has(t.number)) {
8399
+ process.stdout.write(
8400
+ `[goal-tick] leaving leaf task issue #${t.number} OPEN as the review anchor for deliverable PR #${leaf.number}
8401
+ `
8402
+ );
8403
+ commentOnIssue(
8404
+ t.number,
8405
+ `_Goal \`${goal.id}\` finalized \u2014 this task's PR #${leaf.number} (open against \`${goal.defaultBranch}\`) is the goal's single deliverable and carries every task's changes. This issue stays open as the review anchor; merge PR #${leaf.number} to ship._`,
8406
+ ctx.cwd
8407
+ );
8408
+ continue;
8409
+ }
8396
8410
  process.stdout.write(`[goal-tick] closing task issue #${t.number} (goal finalized \u2014 carried by PR #${leaf.number})
8397
8411
  `);
8398
8412
  const closed = closeIssue(
@@ -9040,6 +9054,13 @@ function qualityCommandsFor(pm) {
9040
9054
  testUnit: `${pm} test`
9041
9055
  };
9042
9056
  }
9057
+ function schemaUrlFromPkg() {
9058
+ const fallback = "https://raw.githubusercontent.com/aharonyaircohen/kody-engine/main/kody.config.schema.json";
9059
+ const repoUrl = package_default.repository?.url;
9060
+ const m = repoUrl?.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) ?? null;
9061
+ if (!m) return fallback;
9062
+ return `https://raw.githubusercontent.com/${m[1]}/${m[2]}/main/kody.config.schema.json`;
9063
+ }
9043
9064
  function detectOwnerRepo(cwd) {
9044
9065
  let url;
9045
9066
  try {
@@ -9057,7 +9078,7 @@ function detectOwnerRepo(cwd) {
9057
9078
  }
9058
9079
  function makeConfig(pm, ownerRepo, defaultBranch2) {
9059
9080
  return {
9060
- $schema: "https://raw.githubusercontent.com/aharonyaircohen/kody-engine/main/kody.config.schema.json",
9081
+ $schema: schemaUrlFromPkg(),
9061
9082
  quality: qualityCommandsFor(pm),
9062
9083
  git: { defaultBranch: defaultBranch2 },
9063
9084
  github: {
@@ -9184,10 +9205,10 @@ function performInit(cwd, force) {
9184
9205
  }
9185
9206
  const builtinJobs = listBuiltinJobs();
9186
9207
  if (builtinJobs.length > 0) {
9187
- const jobsDir = path29.join(cwd, ".kody", "jobs");
9208
+ const jobsDir = path29.join(cwd, ".kody", "duties");
9188
9209
  fs31.mkdirSync(jobsDir, { recursive: true });
9189
9210
  for (const job of builtinJobs) {
9190
- const rel = path29.join(".kody", "jobs", `${job.slug}.md`);
9211
+ const rel = path29.join(".kody", "duties", `${job.slug}.md`);
9191
9212
  const target = path29.join(cwd, rel);
9192
9213
  if (fs31.existsSync(target) && !force) {
9193
9214
  skipped.push(rel);
@@ -9461,8 +9482,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
9461
9482
  import * as fs33 from "fs";
9462
9483
  import * as path31 from "path";
9463
9484
  var loadJobFromFile = async (ctx, _profile, args) => {
9464
- const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
9465
- const workersDir = String(args?.workersDir ?? ".kody/workers");
9485
+ const jobsDir = String(args?.jobsDir ?? ".kody/duties");
9486
+ const workersDir = String(args?.workersDir ?? ".kody/staff");
9466
9487
  const slugArg = String(args?.slugArg ?? "job");
9467
9488
  const slug = String(ctx.args[slugArg] ?? "").trim();
9468
9489
  if (!slug) {
@@ -9474,14 +9495,14 @@ var loadJobFromFile = async (ctx, _profile, args) => {
9474
9495
  }
9475
9496
  const raw = fs33.readFileSync(absPath, "utf-8");
9476
9497
  const { title, body } = parseJobFile(raw, slug);
9477
- const workerSlug = (splitFrontmatter2(raw).frontmatter.worker ?? "").trim();
9498
+ const workerSlug = (splitFrontmatter2(raw).frontmatter.staff ?? "").trim();
9478
9499
  let workerTitle = "";
9479
9500
  let workerPersona = "";
9480
9501
  if (workerSlug) {
9481
9502
  const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9482
9503
  if (!fs33.existsSync(workerPath)) {
9483
9504
  throw new Error(
9484
- `loadJobFromFile: job '${slug}' declares worker '${workerSlug}' but ${workerPath} does not exist`
9505
+ `loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`
9485
9506
  );
9486
9507
  }
9487
9508
  const workerRaw = fs33.readFileSync(workerPath, "utf-8");
@@ -9525,7 +9546,7 @@ function humanizeSlug(slug) {
9525
9546
  import * as fs34 from "fs";
9526
9547
  import * as path32 from "path";
9527
9548
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
9528
- const workersDir = String(args?.workersDir ?? ".kody/workers");
9549
+ const workersDir = String(args?.workersDir ?? ".kody/staff");
9529
9550
  const workerSlug = String(ctx.args.worker ?? "").trim();
9530
9551
  if (!workerSlug) {
9531
9552
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
@@ -10989,7 +11010,7 @@ import * as fs37 from "fs";
10989
11010
  import * as path35 from "path";
10990
11011
  var runTickScript = async (ctx, _profile, args) => {
10991
11012
  ctx.skipAgent = true;
10992
- const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
11013
+ const jobsDir = String(args?.jobsDir ?? ".kody/duties");
10993
11014
  const slugArg = String(args?.slugArg ?? "job");
10994
11015
  const fenceLabel = String(args?.fenceLabel ?? "kody-job-next-state");
10995
11016
  const slug = String(ctx.args[slugArg] ?? "").trim();
@@ -12023,7 +12044,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
12023
12044
  lastFiredAt: (/* @__PURE__ */ new Date()).toISOString()
12024
12045
  }
12025
12046
  };
12026
- const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
12047
+ const jobsDir = String(args?.jobsDir ?? ".kody/duties");
12027
12048
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
12028
12049
  await backend.save(loaded, stamped);
12029
12050
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "job-scheduler",
3
3
  "role": "watch",
4
- "describe": "Scheduled: for every job file under .kody/jobs/, invoke job-tick once. No agent on the scheduler itself.",
4
+ "describe": "Scheduled: for every duty file under .kody/duties/, invoke job-tick once. No agent on the scheduler itself.",
5
5
  "kind": "scheduled",
6
6
  "schedule": "*/5 * * * *",
7
7
  "inputs": [],
@@ -38,7 +38,7 @@
38
38
  {
39
39
  "script": "dispatchJobFileTicks",
40
40
  "with": {
41
- "jobsDir": ".kody/jobs",
41
+ "jobsDir": ".kody/duties",
42
42
  "targetExecutable": "job-tick",
43
43
  "slugArg": "job"
44
44
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "job-tick",
3
3
  "role": "primitive",
4
- "describe": "One classifier tick for one job file: read intent + state, decide and execute via gh, emit next state.",
4
+ "describe": "One classifier tick for one duty file: read intent + state, decide and execute via gh, emit next state.",
5
5
  "kind": "oneshot",
6
6
  "inputs": [
7
7
  {
@@ -9,7 +9,7 @@
9
9
  "flag": "--job",
10
10
  "type": "string",
11
11
  "required": true,
12
- "describe": "Job slug — basename (without .md) of the file under .kody/jobs/."
12
+ "describe": "Duty slug — basename (without .md) of the file under .kody/duties/."
13
13
  },
14
14
  {
15
15
  "name": "force",
@@ -51,7 +51,7 @@
51
51
  {
52
52
  "script": "loadJobFromFile",
53
53
  "with": {
54
- "jobsDir": ".kody/jobs",
54
+ "jobsDir": ".kody/duties",
55
55
  "slugArg": "job"
56
56
  }
57
57
  },
@@ -9,7 +9,7 @@
9
9
  "flag": "--job",
10
10
  "type": "string",
11
11
  "required": true,
12
- "describe": "Job slug — basename (without .md) of the file under .kody/jobs/."
12
+ "describe": "Duty slug — basename (without .md) of the file under .kody/duties/."
13
13
  },
14
14
  {
15
15
  "name": "force",
@@ -51,7 +51,7 @@
51
51
  {
52
52
  "script": "runTickScript",
53
53
  "with": {
54
- "jobsDir": ".kody/jobs",
54
+ "jobsDir": ".kody/duties",
55
55
  "slugArg": "job",
56
56
  "fenceLabel": "kody-job-next-state"
57
57
  }
@@ -61,7 +61,7 @@
61
61
  {
62
62
  "script": "writeJobStateFile",
63
63
  "with": {
64
- "jobsDir": ".kody/jobs"
64
+ "jobsDir": ".kody/duties"
65
65
  }
66
66
  }
67
67
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "worker-ask",
3
3
  "role": "primitive",
4
- "describe": "Ad-hoc one-shot: run a worker persona against an inline message + context (from a dashboard @worker mention). Stateless — no job file, no state, no commit. Replies into the originating thread.",
4
+ "describe": "Ad-hoc one-shot: run a staff persona against an inline message + context (from a dashboard @staff mention). Stateless — no duty file, no state, no commit. Replies into the originating thread.",
5
5
  "kind": "oneshot",
6
6
  "inputs": [
7
7
  {
@@ -9,7 +9,7 @@
9
9
  "flag": "--worker",
10
10
  "type": "string",
11
11
  "required": true,
12
- "describe": "Worker slug — basename (without .md) of the persona file under .kody/workers/."
12
+ "describe": "Staff slug — basename (without .md) of the persona file under .kody/staff/."
13
13
  },
14
14
  {
15
15
  "name": "thread",
@@ -58,7 +58,7 @@
58
58
  {
59
59
  "script": "loadWorkerAdhoc",
60
60
  "with": {
61
- "workersDir": ".kody/workers"
61
+ "workersDir": ".kody/staff"
62
62
  }
63
63
  },
64
64
  {
@@ -393,7 +393,7 @@
393
393
  },
394
394
  "jobs": {
395
395
  "type": "object",
396
- "description": "File-based job configuration. Jobs are long-running scheduled executables defined under .kody/jobs/<slug>.md.",
396
+ "description": "File-based job configuration. Jobs are long-running scheduled executables defined under .kody/duties/<slug>.md.",
397
397
  "properties": {
398
398
  "stateBackend": {
399
399
  "type": "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.121",
3
+ "version": "0.4.122",
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",
@@ -21,6 +21,7 @@
21
21
  "check:modularity": "tsx scripts/check-script-modularity.ts",
22
22
  "pretest": "pnpm check:modularity",
23
23
  "test": "vitest run tests/unit tests/int --coverage",
24
+ "test:smoke": "vitest run tests/smoke --no-coverage",
24
25
  "test:e2e": "vitest run tests/e2e --no-coverage",
25
26
  "test:all": "vitest run tests --no-coverage",
26
27
  "typecheck": "tsc --noEmit",