@kody-ade/kody-engine 0.4.657 → 0.4.658

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 +90 -51
  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.657",
18
+ version: "0.4.658",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  repository: {
@@ -9525,12 +9525,12 @@ function isSafeConfigActivationChange(before, after) {
9525
9525
  const beforeCompanyRest = { ...beforeCompany };
9526
9526
  const afterCompanyRest = { ...afterCompany };
9527
9527
  for (const field of ACTIVATION_FIELDS) {
9528
- const previous = beforeCompany[field];
9528
+ const previous = beforeCompany[field] === void 0 ? [] : beforeCompany[field];
9529
9529
  const next = afterCompany[field];
9530
9530
  delete beforeCompanyRest[field];
9531
9531
  delete afterCompanyRest[field];
9532
- if (previous === void 0 && next === void 0) continue;
9533
- if (!Array.isArray(previous ?? []) || !Array.isArray(next)) return false;
9532
+ if (beforeCompany[field] === void 0 && next === void 0) continue;
9533
+ if (!Array.isArray(previous) || !Array.isArray(next)) return false;
9534
9534
  if (!next.every((value) => typeof value === "string")) return false;
9535
9535
  if (!previous.every((value) => typeof value === "string")) return false;
9536
9536
  const nextValues = new Set(next);
@@ -9569,6 +9569,9 @@ function isTrustedConfigActivationChange(filePath, deliveryPathAllowlist, delive
9569
9569
  }
9570
9570
  }
9571
9571
  function listChangedFiles(cwd) {
9572
+ return [...new Set(listChangedPathGroups(cwd).flat())];
9573
+ }
9574
+ function listChangedPathGroups(cwd) {
9572
9575
  const raw = execFileSync6("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
9573
9576
  encoding: "utf-8",
9574
9577
  maxBuffer: GIT_MAX_BUFFER_BYTES,
@@ -9577,8 +9580,19 @@ function listChangedFiles(cwd) {
9577
9580
  stdio: ["pipe", "pipe", "pipe"]
9578
9581
  });
9579
9582
  if (!raw) return [];
9580
- const entries = raw.split("\0").filter((e) => e.length > 0);
9581
- return entries.map((e) => e.slice(3)).filter(Boolean);
9583
+ const entries = raw.split("\0");
9584
+ const groups = [];
9585
+ for (let index = 0; index < entries.length - 1; index++) {
9586
+ const entry = entries[index];
9587
+ const paths = [entry.slice(3)];
9588
+ if (/[RC]/.test(entry.slice(0, 2))) {
9589
+ const source = entries[++index];
9590
+ if (!source) throw new Error("git status returned an incomplete rename/copy record");
9591
+ paths.push(source);
9592
+ }
9593
+ groups.push(paths);
9594
+ }
9595
+ return groups;
9582
9596
  }
9583
9597
  function listAllowlistedIgnoredFiles(deliveryPathAllowlist, cwd) {
9584
9598
  if (deliveryPathAllowlist.length === 0) return [];
@@ -9617,27 +9631,26 @@ function normalizeCommitMessage(raw) {
9617
9631
  function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = [], deliveryConfigAllowlist = {}) {
9618
9632
  const ignoredAllowedFiles = listAllowlistedIgnoredFiles(deliveryPathAllowlist, cwd);
9619
9633
  const ignoredAllowedSet = new Set(ignoredAllowedFiles);
9620
- const allChanged = [.../* @__PURE__ */ new Set([...listChangedFiles(cwd), ...ignoredAllowedFiles])];
9621
- const allowedFiles = allChanged.filter(
9622
- (f) => !isForbiddenPath(f, deliveryPathAllowlist) || isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
9623
- );
9624
- const forbiddenFiles = allChanged.filter(
9625
- (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
9634
+ const pathGroups = [...listChangedPathGroups(cwd), ...ignoredAllowedFiles.map((file) => [file])];
9635
+ const blockedGroups = pathGroups.filter(
9636
+ (group) => group.some(
9637
+ (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
9638
+ )
9626
9639
  );
9640
+ const forbiddenFiles = [...new Set(blockedGroups.flat())];
9641
+ const forbiddenSet = new Set(forbiddenFiles);
9642
+ const allowedFiles = [...new Set(pathGroups.flat())].filter((file) => !forbiddenSet.has(file));
9627
9643
  const omittedFiles = forbiddenFiles.filter(isReportableDeliveryOmission);
9628
9644
  const mergeHeadExists = fs30.existsSync(path28.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
9645
+ for (const f of forbiddenFiles) {
9646
+ git(["--literal-pathspecs", "reset", "-q", "--", f], cwd);
9647
+ }
9629
9648
  if (allowedFiles.length === 0 && !mergeHeadExists) {
9630
9649
  return { committed: false, pushed: false, sha: "", message: "", omittedFiles };
9631
9650
  }
9632
- for (const f of forbiddenFiles) {
9633
- try {
9634
- git(["reset", "-q", "--", f], cwd);
9635
- } catch {
9636
- }
9637
- }
9638
9651
  for (const f of allowedFiles) {
9639
9652
  try {
9640
- git(["add", ...ignoredAllowedSet.has(f) ? ["--force"] : [], "--", f], cwd);
9653
+ git(["--literal-pathspecs", "add", ...ignoredAllowedSet.has(f) ? ["--force"] : [], "--", f], cwd);
9641
9654
  } catch {
9642
9655
  }
9643
9656
  }
@@ -9652,8 +9665,8 @@ function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = [], de
9652
9665
  }
9653
9666
  throw err;
9654
9667
  }
9655
- const sha = git(["rev-parse", "HEAD"], cwd).slice(0, 7);
9656
9668
  const pushResult = pushWithRetry({ cwd, branch, setUpstream: true });
9669
+ const sha = git(["rev-parse", "HEAD"], cwd).slice(0, 7);
9657
9670
  if (pushResult.ok) {
9658
9671
  return { committed: true, pushed: true, sha, message, omittedFiles };
9659
9672
  }
@@ -25175,6 +25188,9 @@ function shouldRunCapabilityWorkflow(job, workflow, capabilityIdentity, selected
25175
25188
  return requestedImplementation === selectedImplementation || requestedImplementation === capabilityIdentity || requestedImplementation === job.action;
25176
25189
  }
25177
25190
  async function runCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
25191
+ if (parent.workflowState?.status === "done") {
25192
+ return { exitCode: 0, workflowState: structuredClone(parent.workflowState), usage: parent.workflowState.usage };
25193
+ }
25178
25194
  const invalid = workflowError(workflow, base);
25179
25195
  if (invalid) {
25180
25196
  if (isGraphWorkflow(workflow)) {
@@ -25407,6 +25423,20 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
25407
25423
  return { ...result, exitCode: 64, reason, workflowState: state };
25408
25424
  }
25409
25425
  const label = step.action ?? step.capability;
25426
+ if (!shouldRunWorkflowStep(step, chainData)) {
25427
+ process.stdout.write(
25428
+ `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label} (skipped)
25429
+
25430
+ `
25431
+ );
25432
+ result = { exitCode: 0 };
25433
+ delete chainData.workflowLastResult;
25434
+ delete chainData.workflowLastOutput;
25435
+ delete chainData.workflowLastOutcome;
25436
+ const terminal2 = await advanceGraphWorkflow(step, capability, state, result, chainData, checkpoint, true);
25437
+ if (terminal2) return terminal2;
25438
+ continue;
25439
+ }
25410
25440
  if (step.approval === "required") {
25411
25441
  const approvalState = requireWorkflowStepApproval(state, step.id);
25412
25442
  state.status = approvalState.status;
@@ -25500,42 +25530,47 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
25500
25530
  await checkpoint?.(state);
25501
25531
  return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
25502
25532
  }
25503
- if (!step.next || step.next.length === 0) {
25504
- return completeWorkflowAtTerminal(capability, state, result, checkpoint);
25505
- }
25506
- const resultConditionPaths = workflowResultConditionPaths(step.next);
25507
- if (resultConditionPaths.length > 0 && !result.capabilityResults?.at(-1)) {
25508
- const reason = `workflow step ${step.id} did not emit the structured result required by its conditions: ${resultConditionPaths.join(", ")}`;
25509
- state.status = "blocked";
25510
- state.blocker = reason;
25511
- await checkpoint?.(state);
25512
- return { ...result, exitCode: 64, reason, workflowState: state };
25513
- }
25514
- const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
25515
- if (!transition) {
25516
- const exhausted = exhaustedWorkflowTransitions(step, chainData, state.transitionCounts);
25517
- const reason = exhausted.length > 0 ? `workflow step ${step.id} reached iteration limit: ${exhausted.join(", ")}` : `workflow step ${step.id} has no available connection`;
25518
- state.status = "blocked";
25519
- state.blocker = reason;
25520
- await checkpoint?.(state);
25521
- return { ...result, exitCode: 64, reason, workflowState: state };
25522
- }
25523
- if (transition.maxIterations !== void 0) {
25524
- const key = `${step.id}->${transition.to}`;
25525
- state.transitionCounts[key] = (state.transitionCounts[key] ?? 0) + 1;
25526
- }
25527
- if (transition.to === "$end") {
25528
- return completeWorkflowAtTerminal(capability, state, result, checkpoint);
25529
- }
25530
- state.currentStepId = transition.to;
25531
- state.status = "running";
25532
- delete state.blocker;
25533
- await checkpoint?.(state);
25533
+ const terminal = await advanceGraphWorkflow(step, capability, state, result, chainData, checkpoint);
25534
+ if (terminal) return terminal;
25534
25535
  }
25535
25536
  state.status = "done";
25536
25537
  await checkpoint?.(state);
25537
25538
  return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
25538
25539
  }
25540
+ async function advanceGraphWorkflow(step, capability, state, result, chainData, checkpoint, skipped = false) {
25541
+ if (!step.next || step.next.length === 0) {
25542
+ return completeWorkflowAtTerminal(capability, state, result, checkpoint);
25543
+ }
25544
+ const resultConditionPaths = workflowResultConditionPaths(step.next);
25545
+ if (!skipped && resultConditionPaths.length > 0 && !result.capabilityResults?.at(-1)) {
25546
+ const reason = `workflow step ${step.id} did not emit the structured result required by its conditions: ${resultConditionPaths.join(", ")}`;
25547
+ state.status = "blocked";
25548
+ state.blocker = reason;
25549
+ await checkpoint?.(state);
25550
+ return { ...result, exitCode: 64, reason, workflowState: state };
25551
+ }
25552
+ const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
25553
+ if (!transition) {
25554
+ const exhausted = exhaustedWorkflowTransitions(step, chainData, state.transitionCounts);
25555
+ const reason = exhausted.length > 0 ? `workflow step ${step.id} reached iteration limit: ${exhausted.join(", ")}` : `workflow step ${step.id} has no available connection`;
25556
+ state.status = "blocked";
25557
+ state.blocker = reason;
25558
+ await checkpoint?.(state);
25559
+ return { ...result, exitCode: 64, reason, workflowState: state };
25560
+ }
25561
+ if (transition.maxIterations !== void 0) {
25562
+ const key = `${step.id}->${transition.to}`;
25563
+ state.transitionCounts[key] = (state.transitionCounts[key] ?? 0) + 1;
25564
+ }
25565
+ if (transition.to === "$end") {
25566
+ return completeWorkflowAtTerminal(capability, state, result, checkpoint);
25567
+ }
25568
+ state.currentStepId = transition.to;
25569
+ state.status = "running";
25570
+ delete state.blocker;
25571
+ await checkpoint?.(state);
25572
+ return null;
25573
+ }
25539
25574
  async function completeWorkflowAtTerminal(capability, state, output, checkpoint) {
25540
25575
  const result = output.capabilityResults?.at(-1);
25541
25576
  const customStatus = output.capabilityOutput && typeof output.capabilityOutput === "object" && !Array.isArray(output.capabilityOutput) && typeof output.capabilityOutput.status === "string" ? output.capabilityOutput.status : void 0;
@@ -31725,6 +31760,10 @@ function buildServer2(opts) {
31725
31760
  sendJson3(res, 400, { error: parsed.error });
31726
31761
  return;
31727
31762
  }
31763
+ if (busy) {
31764
+ sendJson3(res, 409, { error: "runner busy" });
31765
+ return;
31766
+ }
31728
31767
  busy = true;
31729
31768
  sendJson3(res, 202, { ok: true, jobId: parsed.job.jobId, started: true });
31730
31769
  void runJob2(parsed.job).catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.657",
3
+ "version": "0.4.658",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "repository": {