@kody-ade/kody-engine 0.4.573 → 0.4.575

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 +61 -13
  2. package/package.json +2 -2
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.573",
18
+ version: "0.4.575",
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
  type: "module",
@@ -54,7 +54,7 @@ var init_package = __esm({
54
54
  dependencies: {
55
55
  "@actions/cache": "^6.0.0",
56
56
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
57
- "@kody-ade/engine-contracts": "0.1.0",
57
+ "@kody-ade/engine-contracts": "^0.2.0",
58
58
  "@modelcontextprotocol/sdk": "^1.29.0",
59
59
  ajv: "^8.18.0",
60
60
  convex: "^1.17.0",
@@ -2188,6 +2188,7 @@ function readCapabilityFolder(root, slug) {
2188
2188
  rawProfile: contract ? {
2189
2189
  ...contract.execution ? { execution: contract.execution } : {},
2190
2190
  ...contract.deliveryPolicy ? { deliveryPolicy: contract.deliveryPolicy } : {},
2191
+ ...contract.deliveryPathAllowlist ? { deliveryPathAllowlist: contract.deliveryPathAllowlist } : {},
2191
2192
  input: contract.input,
2192
2193
  output: contract.output
2193
2194
  } : {},
@@ -2227,8 +2228,12 @@ function parseCapabilityContract(raw) {
2227
2228
  if (requiredSubagents && parsed.execution !== "agent") {
2228
2229
  throw new Error('contract.json requiredSubagents are supported only when execution is "agent"');
2229
2230
  }
2231
+ const deliveryPathAllowlist = parseDeliveryPathAllowlist(parsed.deliveryPathAllowlist);
2232
+ if (deliveryPathAllowlist && parsed.execution !== "agent") {
2233
+ throw new Error('contract.json deliveryPathAllowlist is supported only when execution is "agent"');
2234
+ }
2230
2235
  const unsupported = Object.keys(parsed).filter(
2231
- (key) => key !== "execution" && key !== "deliveryPolicy" && key !== "requirements" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
2236
+ (key) => key !== "execution" && key !== "deliveryPolicy" && key !== "deliveryPathAllowlist" && key !== "requirements" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
2232
2237
  );
2233
2238
  if (unsupported.length > 0) {
2234
2239
  throw new Error(`contract.json contains unsupported fields: ${unsupported.join(", ")}`);
@@ -2242,6 +2247,7 @@ function parseCapabilityContract(raw) {
2242
2247
  return {
2243
2248
  ...parsed.execution ? { execution: parsed.execution } : {},
2244
2249
  ...parsed.deliveryPolicy === "checkpoint" ? { deliveryPolicy: "checkpoint" } : {},
2250
+ ...deliveryPathAllowlist ? { deliveryPathAllowlist } : {},
2245
2251
  ...requirements ? { requirements } : {},
2246
2252
  ...secrets ? { secrets } : {},
2247
2253
  ...timeoutMs !== void 0 ? { timeoutMs } : {},
@@ -2250,6 +2256,25 @@ function parseCapabilityContract(raw) {
2250
2256
  output: parsed.output
2251
2257
  };
2252
2258
  }
2259
+ function parseDeliveryPathAllowlist(raw) {
2260
+ if (raw === void 0) return void 0;
2261
+ if (!Array.isArray(raw) || raw.length === 0 || raw.length > 64) {
2262
+ throw new Error("contract.json deliveryPathAllowlist must contain 1 to 64 paths");
2263
+ }
2264
+ if (!raw.every((value) => typeof value === "string")) {
2265
+ throw new Error("contract.json deliveryPathAllowlist must contain paths");
2266
+ }
2267
+ const paths = [...new Set(raw)];
2268
+ for (const value of paths) {
2269
+ const subtree = value.endsWith("/**");
2270
+ const base = subtree ? value.slice(0, -3) : value;
2271
+ const segments = base.split("/");
2272
+ if (!base || base.startsWith("/") || base.startsWith(".") && !base.startsWith(".github/") || base.includes("\\") || base.includes("..") || base.includes("*") || segments.some((segment) => !segment) || subtree && segments.length < 2 || value === ".github/**") {
2273
+ throw new Error(`contract.json deliveryPathAllowlist contains an unsafe path: ${value}`);
2274
+ }
2275
+ }
2276
+ return paths;
2277
+ }
2253
2278
  function parseCapabilityRequirements(raw) {
2254
2279
  if (raw === void 0) return void 0;
2255
2280
  if (!isPlainObject(raw)) throw new Error("contract.json requirements must be an object");
@@ -4744,6 +4769,7 @@ var init_task_artifacts = __esm({
4744
4769
  });
4745
4770
 
4746
4771
  // src/workflowValidation.ts
4772
+ import { validateExecutableWorkflow } from "@kody-ade/engine-contracts";
4747
4773
  function validateWorkflow(value, options = {}) {
4748
4774
  const issues = [];
4749
4775
  const workflow = asRecord(value);
@@ -4762,6 +4788,7 @@ function validateWorkflow(value, options = {}) {
4762
4788
  const step = asRecord(entry);
4763
4789
  return Boolean(step && (step.id !== void 0 || step.next !== void 0));
4764
4790
  });
4791
+ const sharedIssues = graphMode ? validateExecutableWorkflow(value, options) : [];
4765
4792
  const steps = rawSteps.map(
4766
4793
  (entry) => typeof entry === "string" ? { capability: entry } : asRecord(entry)
4767
4794
  );
@@ -4822,7 +4849,7 @@ function validateWorkflow(value, options = {}) {
4822
4849
  capability ? options.capabilityInputs?.get(capability) : void 0
4823
4850
  );
4824
4851
  });
4825
- if (!graphMode) return issues;
4852
+ if (!graphMode) return uniqueIssues([...issues, ...sharedIssues]);
4826
4853
  const seen = /* @__PURE__ */ new Set();
4827
4854
  ids.forEach((id, index) => {
4828
4855
  if (seen.has(id)) issue(issues, "duplicate_step_id", `steps[${index}].id`, `workflow step id ${id} is duplicated`);
@@ -4965,7 +4992,16 @@ function validateWorkflow(value, options = {}) {
4965
4992
  issue(issues, "missing_terminal_step", "steps", "workflow has no reachable final step");
4966
4993
  }
4967
4994
  }
4968
- return issues;
4995
+ return uniqueIssues([...issues, ...sharedIssues]);
4996
+ }
4997
+ function uniqueIssues(issues) {
4998
+ const seen = /* @__PURE__ */ new Set();
4999
+ return issues.filter((entry) => {
5000
+ const key = `${entry.code}\0${entry.path}`;
5001
+ if (seen.has(key)) return false;
5002
+ seen.add(key);
5003
+ return true;
5004
+ });
4969
5005
  }
4970
5006
  function validateInputBindings(value, path59, issues, declaredInputs) {
4971
5007
  if (value === void 0) return;
@@ -8644,12 +8680,18 @@ function abortUnfinishedGitOps(cwd) {
8644
8680
  }
8645
8681
  return aborted;
8646
8682
  }
8647
- function isForbiddenPath(p) {
8683
+ function isExplicitlyAllowed(p, allowlist) {
8684
+ return allowlist.some(
8685
+ (entry) => entry.endsWith("/**") ? p.startsWith(entry.slice(0, -2)) : p === entry
8686
+ );
8687
+ }
8688
+ function isForbiddenPath(p, deliveryPathAllowlist = []) {
8648
8689
  if (FORBIDDEN_PATH_EXACT.has(p)) return true;
8649
- if (isGitHubYamlPath(p)) return true;
8650
- for (const pre of ALLOWED_PATH_PREFIXES) if (p.startsWith(pre)) return false;
8651
8690
  for (const pre of FORBIDDEN_PATH_PREFIXES) if (p.startsWith(pre)) return true;
8652
8691
  for (const suf of FORBIDDEN_PATH_SUFFIXES) if (p.endsWith(suf)) return true;
8692
+ if (isExplicitlyAllowed(p, deliveryPathAllowlist)) return false;
8693
+ if (isGitHubYamlPath(p)) return true;
8694
+ for (const pre of ALLOWED_PATH_PREFIXES) if (p.startsWith(pre)) return false;
8653
8695
  return false;
8654
8696
  }
8655
8697
  function listChangedFiles(cwd) {
@@ -8685,14 +8727,14 @@ function normalizeCommitMessage(raw) {
8685
8727
  }
8686
8728
  return `chore: ${trimmed}`;
8687
8729
  }
8688
- function commitAndPush(branch, agentMessage, cwd) {
8730
+ function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = []) {
8689
8731
  const allChanged = listChangedFiles(cwd);
8690
- const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
8732
+ const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f, deliveryPathAllowlist));
8691
8733
  const mergeHeadExists = fs29.existsSync(path28.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
8692
8734
  if (allowedFiles.length === 0 && !mergeHeadExists) {
8693
8735
  return { committed: false, pushed: false, sha: "", message: "" };
8694
8736
  }
8695
- const forbiddenFiles = allChanged.filter((f) => isForbiddenPath(f));
8737
+ const forbiddenFiles = allChanged.filter((f) => isForbiddenPath(f, deliveryPathAllowlist));
8696
8738
  for (const f of forbiddenFiles) {
8697
8739
  try {
8698
8740
  git(["reset", "-q", "--", f], cwd);
@@ -12351,11 +12393,14 @@ var init_commitAndPush = __esm({
12351
12393
  ctx.data.salvagedFromMissingMarker = true;
12352
12394
  }
12353
12395
  const message = ctx.data.commitMessage || DEFAULT_COMMIT_MESSAGE;
12396
+ const deliveryPathAllowlist = Array.isArray(ctx.data.deliveryPathAllowlist) ? ctx.data.deliveryPathAllowlist : [];
12354
12397
  try {
12355
- const result2 = commitAndPush(branch, message, ctx.cwd);
12398
+ const result2 = commitAndPush(branch, message, ctx.cwd, deliveryPathAllowlist);
12356
12399
  ctx.data.commitResult = result2;
12357
12400
  const postCommitFiles = result2.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
12358
- ctx.data.changedFiles = postCommitFiles.filter((f) => !isForbiddenPath(f));
12401
+ ctx.data.changedFiles = postCommitFiles.filter(
12402
+ (f) => !isForbiddenPath(f, deliveryPathAllowlist)
12403
+ );
12359
12404
  if (result2.committed && !result2.pushed) {
12360
12405
  const reason = result2.pushError ?? "push failed (no error detail)";
12361
12406
  ctx.data.commitCrash = reason;
@@ -16315,6 +16360,9 @@ var init_loadSimpleCapability = __esm({
16315
16360
  if (capability.contract?.deliveryPolicy) {
16316
16361
  ctx.data.capabilityDeliveryPolicy = capability.contract.deliveryPolicy;
16317
16362
  }
16363
+ if (capability.contract?.deliveryPathAllowlist) {
16364
+ ctx.data.deliveryPathAllowlist = capability.contract.deliveryPathAllowlist;
16365
+ }
16318
16366
  if (capability.contract?.requirements) {
16319
16367
  ctx.data.capabilityRequirements = capability.contract.requirements;
16320
16368
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.573",
3
+ "version": "0.4.575",
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
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "dependencies": {
16
16
  "@actions/cache": "^6.0.0",
17
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
18
- "@kody-ade/engine-contracts": "0.1.0",
18
+ "@kody-ade/engine-contracts": "^0.2.0",
19
19
  "@modelcontextprotocol/sdk": "^1.29.0",
20
20
  "ajv": "^8.18.0",
21
21
  "convex": "^1.17.0",