@kody-ade/kody-engine 0.4.584 → 0.4.586

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 +70 -9
  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.584",
18
+ version: "0.4.586",
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",
@@ -2189,6 +2189,7 @@ function readCapabilityFolder(root, slug) {
2189
2189
  ...contract.execution ? { execution: contract.execution } : {},
2190
2190
  ...contract.deliveryPolicy ? { deliveryPolicy: contract.deliveryPolicy } : {},
2191
2191
  ...contract.deliveryPathAllowlist ? { deliveryPathAllowlist: contract.deliveryPathAllowlist } : {},
2192
+ ...contract.deliveryConfigAllowlist ? { deliveryConfigAllowlist: contract.deliveryConfigAllowlist } : {},
2192
2193
  input: contract.input,
2193
2194
  output: contract.output
2194
2195
  } : {},
@@ -2229,11 +2230,18 @@ function parseCapabilityContract(raw) {
2229
2230
  throw new Error('contract.json requiredSubagents are supported only when execution is "agent"');
2230
2231
  }
2231
2232
  const deliveryPathAllowlist = parseDeliveryPathAllowlist(parsed.deliveryPathAllowlist);
2233
+ const deliveryConfigAllowlist = parseDeliveryConfigAllowlist(parsed.deliveryConfigAllowlist);
2232
2234
  if (deliveryPathAllowlist && parsed.execution !== "agent") {
2233
2235
  throw new Error('contract.json deliveryPathAllowlist is supported only when execution is "agent"');
2234
2236
  }
2237
+ if (deliveryConfigAllowlist && parsed.execution !== "agent") {
2238
+ throw new Error('contract.json deliveryConfigAllowlist is supported only when execution is "agent"');
2239
+ }
2240
+ if (deliveryConfigAllowlist && Object.keys(deliveryConfigAllowlist).some((filePath) => !deliveryPathAllowlist?.includes(filePath))) {
2241
+ throw new Error("contract.json deliveryConfigAllowlist files must also be deliveryPathAllowlist entries");
2242
+ }
2235
2243
  const unsupported = Object.keys(parsed).filter(
2236
- (key) => key !== "execution" && key !== "deliveryPolicy" && key !== "deliveryPathAllowlist" && key !== "requirements" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
2244
+ (key) => key !== "execution" && key !== "deliveryPolicy" && key !== "deliveryPathAllowlist" && key !== "deliveryConfigAllowlist" && key !== "requirements" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
2237
2245
  );
2238
2246
  if (unsupported.length > 0) {
2239
2247
  throw new Error(`contract.json contains unsupported fields: ${unsupported.join(", ")}`);
@@ -2248,6 +2256,7 @@ function parseCapabilityContract(raw) {
2248
2256
  ...parsed.execution ? { execution: parsed.execution } : {},
2249
2257
  ...parsed.deliveryPolicy === "checkpoint" ? { deliveryPolicy: "checkpoint" } : {},
2250
2258
  ...deliveryPathAllowlist ? { deliveryPathAllowlist } : {},
2259
+ ...deliveryConfigAllowlist ? { deliveryConfigAllowlist } : {},
2251
2260
  ...requirements ? { requirements } : {},
2252
2261
  ...secrets ? { secrets } : {},
2253
2262
  ...timeoutMs !== void 0 ? { timeoutMs } : {},
@@ -2256,6 +2265,23 @@ function parseCapabilityContract(raw) {
2256
2265
  output: parsed.output
2257
2266
  };
2258
2267
  }
2268
+ function parseDeliveryConfigAllowlist(raw) {
2269
+ if (raw === void 0) return void 0;
2270
+ if (!isPlainObject(raw) || Object.keys(raw).length === 0) {
2271
+ throw new Error("contract.json deliveryConfigAllowlist must be a non-empty object");
2272
+ }
2273
+ const parsed = {};
2274
+ for (const [filePath, paths] of Object.entries(raw)) {
2275
+ if (filePath !== "kody.config.json" || !Array.isArray(paths) || paths.length === 0 || paths.length > 32) {
2276
+ throw new Error("contract.json deliveryConfigAllowlist contains an unsupported config file or path list");
2277
+ }
2278
+ if (!paths.every((value) => typeof value === "string" && /^[A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)*$/.test(value))) {
2279
+ throw new Error("contract.json deliveryConfigAllowlist must contain safe dotted config paths");
2280
+ }
2281
+ parsed[filePath] = [...new Set(paths)];
2282
+ }
2283
+ return parsed;
2284
+ }
2259
2285
  function parseDeliveryPathAllowlist(raw) {
2260
2286
  if (raw === void 0) return void 0;
2261
2287
  if (!Array.isArray(raw) || raw.length === 0 || raw.length > 64) {
@@ -8632,6 +8658,7 @@ function git(args, cwd) {
8632
8658
  return execFileSync6("git", args, {
8633
8659
  encoding: "utf-8",
8634
8660
  timeout: 12e4,
8661
+ maxBuffer: GIT_MAX_BUFFER_BYTES,
8635
8662
  cwd,
8636
8663
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
8637
8664
  stdio: ["pipe", "pipe", "pipe"]
@@ -8733,12 +8760,32 @@ function isSafeConfigActivationChange(before, after) {
8733
8760
  }
8734
8761
  return isDeepStrictEqual(beforeCompanyRest, afterCompanyRest);
8735
8762
  }
8736
- function isTrustedConfigActivationChange(filePath, deliveryPathAllowlist, cwd) {
8763
+ function isSafeConfigChange(before, after, allowedPaths = []) {
8764
+ if (!isRecord(before) || !isRecord(after)) return false;
8765
+ const beforeCopy = structuredClone(before);
8766
+ const afterCopy = structuredClone(after);
8767
+ for (const dottedPath of allowedPaths) {
8768
+ deleteConfigPath(beforeCopy, dottedPath);
8769
+ deleteConfigPath(afterCopy, dottedPath);
8770
+ }
8771
+ return isSafeConfigActivationChange(beforeCopy, afterCopy);
8772
+ }
8773
+ function deleteConfigPath(value, dottedPath) {
8774
+ const segments = dottedPath.split(".");
8775
+ let cursor = value;
8776
+ for (const segment of segments.slice(0, -1)) {
8777
+ const next = cursor[segment];
8778
+ if (!isRecord(next)) return;
8779
+ cursor = next;
8780
+ }
8781
+ delete cursor[segments.at(-1)];
8782
+ }
8783
+ function isTrustedConfigActivationChange(filePath, deliveryPathAllowlist, deliveryConfigAllowlist, cwd) {
8737
8784
  if (filePath !== "kody.config.json" || !deliveryPathAllowlist.includes(filePath)) return false;
8738
8785
  try {
8739
8786
  const before = JSON.parse(git(["show", "HEAD:kody.config.json"], cwd));
8740
8787
  const after = JSON.parse(fs29.readFileSync(path28.join(cwd ?? process.cwd(), filePath), "utf-8"));
8741
- return isSafeConfigActivationChange(before, after);
8788
+ return isSafeConfigChange(before, after, deliveryConfigAllowlist[filePath] ?? []);
8742
8789
  } catch {
8743
8790
  return false;
8744
8791
  }
@@ -8746,6 +8793,7 @@ function isTrustedConfigActivationChange(filePath, deliveryPathAllowlist, cwd) {
8746
8793
  function listChangedFiles(cwd) {
8747
8794
  const raw = execFileSync6("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
8748
8795
  encoding: "utf-8",
8796
+ maxBuffer: GIT_MAX_BUFFER_BYTES,
8749
8797
  cwd,
8750
8798
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
8751
8799
  stdio: ["pipe", "pipe", "pipe"]
@@ -8758,6 +8806,7 @@ function listAllowlistedIgnoredFiles(deliveryPathAllowlist, cwd) {
8758
8806
  if (deliveryPathAllowlist.length === 0) return [];
8759
8807
  const raw = execFileSync6("git", ["ls-files", "--others", "--ignored", "--exclude-standard", "-z"], {
8760
8808
  encoding: "utf-8",
8809
+ maxBuffer: GIT_MAX_BUFFER_BYTES,
8761
8810
  cwd,
8762
8811
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
8763
8812
  stdio: ["pipe", "pipe", "pipe"]
@@ -8768,6 +8817,7 @@ function listFilesInCommit(ref = "HEAD", cwd) {
8768
8817
  try {
8769
8818
  const raw = execFileSync6("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
8770
8819
  encoding: "utf-8",
8820
+ maxBuffer: GIT_MAX_BUFFER_BYTES,
8771
8821
  cwd,
8772
8822
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
8773
8823
  stdio: ["pipe", "pipe", "pipe"]
@@ -8786,19 +8836,19 @@ function normalizeCommitMessage(raw) {
8786
8836
  }
8787
8837
  return `chore: ${trimmed}`;
8788
8838
  }
8789
- function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = []) {
8839
+ function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = [], deliveryConfigAllowlist = {}) {
8790
8840
  const ignoredAllowedFiles = listAllowlistedIgnoredFiles(deliveryPathAllowlist, cwd);
8791
8841
  const ignoredAllowedSet = new Set(ignoredAllowedFiles);
8792
8842
  const allChanged = [.../* @__PURE__ */ new Set([...listChangedFiles(cwd), ...ignoredAllowedFiles])];
8793
8843
  const allowedFiles = allChanged.filter(
8794
- (f) => !isForbiddenPath(f, deliveryPathAllowlist) || isTrustedConfigActivationChange(f, deliveryPathAllowlist, cwd)
8844
+ (f) => !isForbiddenPath(f, deliveryPathAllowlist) || isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
8795
8845
  );
8796
8846
  const mergeHeadExists = fs29.existsSync(path28.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
8797
8847
  if (allowedFiles.length === 0 && !mergeHeadExists) {
8798
8848
  return { committed: false, pushed: false, sha: "", message: "" };
8799
8849
  }
8800
8850
  const forbiddenFiles = allChanged.filter(
8801
- (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, cwd)
8851
+ (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
8802
8852
  );
8803
8853
  for (const f of forbiddenFiles) {
8804
8854
  try {
@@ -8843,11 +8893,12 @@ function hasCommitsAhead(branch, defaultBranch, cwd) {
8843
8893
  }
8844
8894
  }
8845
8895
  }
8846
- var FORBIDDEN_PATH_PREFIXES, ALLOWED_PATH_PREFIXES, FORBIDDEN_PATH_EXACT, FORBIDDEN_PATH_SUFFIXES, ACTIVATION_FIELDS, CONVENTIONAL_PREFIXES;
8896
+ var GIT_MAX_BUFFER_BYTES, FORBIDDEN_PATH_PREFIXES, ALLOWED_PATH_PREFIXES, FORBIDDEN_PATH_EXACT, FORBIDDEN_PATH_SUFFIXES, ACTIVATION_FIELDS, CONVENTIONAL_PREFIXES;
8847
8897
  var init_commit = __esm({
8848
8898
  "src/commit.ts"() {
8849
8899
  "use strict";
8850
8900
  init_pushWithRetry();
8901
+ GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
8851
8902
  FORBIDDEN_PATH_PREFIXES = [
8852
8903
  ".kody/",
8853
8904
  // legacy consumer state must never be reintroduced or committed
@@ -12467,8 +12518,15 @@ var init_commitAndPush = __esm({
12467
12518
  }
12468
12519
  const message = ctx.data.commitMessage || DEFAULT_COMMIT_MESSAGE;
12469
12520
  const deliveryPathAllowlist = Array.isArray(ctx.data.deliveryPathAllowlist) ? ctx.data.deliveryPathAllowlist : [];
12521
+ const deliveryConfigAllowlist = ctx.data.deliveryConfigAllowlist && typeof ctx.data.deliveryConfigAllowlist === "object" ? ctx.data.deliveryConfigAllowlist : {};
12470
12522
  try {
12471
- const result2 = commitAndPush(branch, message, ctx.cwd, deliveryPathAllowlist);
12523
+ const result2 = commitAndPush(
12524
+ branch,
12525
+ message,
12526
+ ctx.cwd,
12527
+ deliveryPathAllowlist,
12528
+ deliveryConfigAllowlist
12529
+ );
12472
12530
  ctx.data.commitResult = result2;
12473
12531
  const postCommitFiles = result2.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
12474
12532
  ctx.data.changedFiles = postCommitFiles.filter(
@@ -16451,6 +16509,9 @@ var init_loadSimpleCapability = __esm({
16451
16509
  if (capability.contract?.deliveryPathAllowlist) {
16452
16510
  ctx.data.deliveryPathAllowlist = capability.contract.deliveryPathAllowlist;
16453
16511
  }
16512
+ if (capability.contract?.deliveryConfigAllowlist) {
16513
+ ctx.data.deliveryConfigAllowlist = capability.contract.deliveryConfigAllowlist;
16514
+ }
16454
16515
  if (capability.contract?.requirements) {
16455
16516
  ctx.data.capabilityRequirements = capability.contract.requirements;
16456
16517
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.584",
3
+ "version": "0.4.586",
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",