@kody-ade/kody-engine 0.4.585 → 0.4.587

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
@@ -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.585",
18
+ version: "0.4.587",
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) {
@@ -8734,12 +8760,32 @@ function isSafeConfigActivationChange(before, after) {
8734
8760
  }
8735
8761
  return isDeepStrictEqual(beforeCompanyRest, afterCompanyRest);
8736
8762
  }
8737
- 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) {
8738
8784
  if (filePath !== "kody.config.json" || !deliveryPathAllowlist.includes(filePath)) return false;
8739
8785
  try {
8740
8786
  const before = JSON.parse(git(["show", "HEAD:kody.config.json"], cwd));
8741
8787
  const after = JSON.parse(fs29.readFileSync(path28.join(cwd ?? process.cwd(), filePath), "utf-8"));
8742
- return isSafeConfigActivationChange(before, after);
8788
+ return isSafeConfigChange(before, after, deliveryConfigAllowlist[filePath] ?? []);
8743
8789
  } catch {
8744
8790
  return false;
8745
8791
  }
@@ -8790,19 +8836,19 @@ function normalizeCommitMessage(raw) {
8790
8836
  }
8791
8837
  return `chore: ${trimmed}`;
8792
8838
  }
8793
- function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = []) {
8839
+ function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = [], deliveryConfigAllowlist = {}) {
8794
8840
  const ignoredAllowedFiles = listAllowlistedIgnoredFiles(deliveryPathAllowlist, cwd);
8795
8841
  const ignoredAllowedSet = new Set(ignoredAllowedFiles);
8796
8842
  const allChanged = [.../* @__PURE__ */ new Set([...listChangedFiles(cwd), ...ignoredAllowedFiles])];
8797
8843
  const allowedFiles = allChanged.filter(
8798
- (f) => !isForbiddenPath(f, deliveryPathAllowlist) || isTrustedConfigActivationChange(f, deliveryPathAllowlist, cwd)
8844
+ (f) => !isForbiddenPath(f, deliveryPathAllowlist) || isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
8799
8845
  );
8800
8846
  const mergeHeadExists = fs29.existsSync(path28.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
8801
8847
  if (allowedFiles.length === 0 && !mergeHeadExists) {
8802
8848
  return { committed: false, pushed: false, sha: "", message: "" };
8803
8849
  }
8804
8850
  const forbiddenFiles = allChanged.filter(
8805
- (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, cwd)
8851
+ (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
8806
8852
  );
8807
8853
  for (const f of forbiddenFiles) {
8808
8854
  try {
@@ -12472,8 +12518,15 @@ var init_commitAndPush = __esm({
12472
12518
  }
12473
12519
  const message = ctx.data.commitMessage || DEFAULT_COMMIT_MESSAGE;
12474
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 : {};
12475
12522
  try {
12476
- const result2 = commitAndPush(branch, message, ctx.cwd, deliveryPathAllowlist);
12523
+ const result2 = commitAndPush(
12524
+ branch,
12525
+ message,
12526
+ ctx.cwd,
12527
+ deliveryPathAllowlist,
12528
+ deliveryConfigAllowlist
12529
+ );
12477
12530
  ctx.data.commitResult = result2;
12478
12531
  const postCommitFiles = result2.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
12479
12532
  ctx.data.changedFiles = postCommitFiles.filter(
@@ -16456,6 +16509,9 @@ var init_loadSimpleCapability = __esm({
16456
16509
  if (capability.contract?.deliveryPathAllowlist) {
16457
16510
  ctx.data.deliveryPathAllowlist = capability.contract.deliveryPathAllowlist;
16458
16511
  }
16512
+ if (capability.contract?.deliveryConfigAllowlist) {
16513
+ ctx.data.deliveryConfigAllowlist = capability.contract.deliveryConfigAllowlist;
16514
+ }
16459
16515
  if (capability.contract?.requirements) {
16460
16516
  ctx.data.capabilityRequirements = capability.contract.requirements;
16461
16517
  }
@@ -17481,7 +17537,7 @@ var init_parseReproOutput = __esm({
17481
17537
 
17482
17538
  // src/scripts/parseSimpleCapabilityOutput.ts
17483
17539
  import * as fs47 from "fs";
17484
- function acceptAuthoritativeFileOutput(ctx, profile, output) {
17540
+ function acceptAuthoritativeCapabilityOutput(ctx, profile, output) {
17485
17541
  ctx.data.agentDone = true;
17486
17542
  delete ctx.data.agentFailureReason;
17487
17543
  const summary = isObject2(output) && typeof output.summary === "string" ? output.summary.trim() : "";
@@ -17571,7 +17627,8 @@ var init_parseSimpleCapabilityOutput = __esm({
17571
17627
  }
17572
17628
  const outputPath = typeof ctx.data.capabilityOutputPath === "string" ? ctx.data.capabilityOutputPath : void 0;
17573
17629
  const fileOutput = readOutputFile(outputPath);
17574
- const output = fileOutput.found ? fileOutput.value : Object.hasOwn(ctx.data, "capabilityScriptOutput") ? ctx.data.capabilityScriptOutput : parseOutput(agentResult?.finalText);
17630
+ const hasScriptOutput = Object.hasOwn(ctx.data, "capabilityScriptOutput");
17631
+ const output = fileOutput.found ? fileOutput.value : hasScriptOutput ? ctx.data.capabilityScriptOutput : parseOutput(agentResult?.finalText);
17575
17632
  if (output === void 0) {
17576
17633
  const reason2 = agentResult?.outcomeKind === "out_of_turns" ? "Capability execution limit reached" : "Capability execution ended before returning a result";
17577
17634
  const blocked = {
@@ -17618,7 +17675,7 @@ var init_parseSimpleCapabilityOutput = __esm({
17618
17675
  return;
17619
17676
  }
17620
17677
  }
17621
- if (fileOutput.found) acceptAuthoritativeFileOutput(ctx, profile, output);
17678
+ if (fileOutput.found || hasScriptOutput) acceptAuthoritativeCapabilityOutput(ctx, profile, output);
17622
17679
  ctx.data.capabilityOutput = output;
17623
17680
  const structuredResult = parseCapabilityResult(output);
17624
17681
  if (structuredResult) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.585",
3
+ "version": "0.4.587",
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",
@@ -14,6 +14,10 @@ on:
14
14
  description: 'GitHub issue number (agent mode)'
15
15
  type: string
16
16
  default: ''
17
+ requestId:
18
+ description: 'Unique Kody execution request ID'
19
+ type: string
20
+ default: ''
17
21
  sessionId:
18
22
  description: 'Chat session ID (chat mode, from Kody Dashboard)'
19
23
  type: string
@@ -55,7 +59,7 @@ jobs:
55
59
  runs-on: ubuntu-latest
56
60
  timeout-minutes: 360
57
61
  concurrency:
58
- group: kody-${{ inputs.sessionId || inputs.issue_number || github.event.issue.number || github.sha }}
62
+ group: kody-${{ inputs.requestId || inputs.sessionId || inputs.issue_number || github.event.issue.number || github.sha }}
59
63
  cancel-in-progress: false
60
64
  permissions:
61
65
  issues: write