@kody-ade/kody-engine 0.4.381 → 0.4.383

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 +262 -86
  2. package/package.json +24 -25
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.381",
18
+ version: "0.4.383",
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",
@@ -1271,13 +1271,32 @@ var init_events = __esm({
1271
1271
 
1272
1272
  // src/verify.ts
1273
1273
  import { spawn } from "child_process";
1274
+ function buildVerifyEnv(source = process.env) {
1275
+ const env = { ...source };
1276
+ const rawSecrets = env.ALL_SECRETS;
1277
+ delete env.ALL_SECRETS;
1278
+ if (rawSecrets) {
1279
+ try {
1280
+ const parsed = JSON.parse(rawSecrets);
1281
+ for (const key of Object.keys(parsed)) delete env[key];
1282
+ } catch {
1283
+ }
1284
+ }
1285
+ for (const key of Object.keys(env)) {
1286
+ if (SENSITIVE_ENV_NAME.test(key)) delete env[key];
1287
+ }
1288
+ env.HUSKY = "0";
1289
+ env.SKIP_HOOKS = "1";
1290
+ env.CI = source.CI ?? "1";
1291
+ return env;
1292
+ }
1274
1293
  function runCommand(command, cwd) {
1275
1294
  return new Promise((resolve10) => {
1276
1295
  const start = Date.now();
1277
1296
  const child = spawn(command, {
1278
1297
  cwd,
1279
1298
  shell: true,
1280
- env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
1299
+ env: buildVerifyEnv(),
1281
1300
  stdio: ["ignore", "pipe", "pipe"]
1282
1301
  });
1283
1302
  const buffers = [];
@@ -1369,13 +1388,14 @@ function summarizeFailure(result) {
1369
1388
  }
1370
1389
  return lines.join("\n");
1371
1390
  }
1372
- var TAIL_CHARS, COMMAND_TIMEOUT_MS, DEFAULT_TEST_RETRIES, ANSI_RE;
1391
+ var TAIL_CHARS, COMMAND_TIMEOUT_MS, DEFAULT_TEST_RETRIES, SENSITIVE_ENV_NAME, ANSI_RE;
1373
1392
  var init_verify = __esm({
1374
1393
  "src/verify.ts"() {
1375
1394
  "use strict";
1376
1395
  TAIL_CHARS = 4e3;
1377
1396
  COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
1378
1397
  DEFAULT_TEST_RETRIES = 2;
1398
+ SENSITIVE_ENV_NAME = /(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY|PRIVATE_KEY|SERVICE_KEY|MASTER_KEY|CREDENTIALS?)(?:_|$)/i;
1379
1399
  ANSI_RE = /\x1B\[[0-?]*[ -/]*[@-~]/g;
1380
1400
  }
1381
1401
  });
@@ -3757,6 +3777,108 @@ var init_agents = __esm({
3757
3777
  }
3758
3778
  });
3759
3779
 
3780
+ // src/chat/convex-client.ts
3781
+ import { ConvexHttpClient } from "convex/browser";
3782
+ function isPlainObject2(value) {
3783
+ if (value === null || typeof value !== "object") return false;
3784
+ const proto = Object.getPrototypeOf(value);
3785
+ return proto === Object.prototype || proto === null;
3786
+ }
3787
+ function deepMapKeys(value, mapKey) {
3788
+ if (Array.isArray(value)) return value.map((item) => deepMapKeys(item, mapKey));
3789
+ if (isPlainObject2(value)) {
3790
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [mapKey(key), deepMapKeys(item, mapKey)]));
3791
+ }
3792
+ return value;
3793
+ }
3794
+ function deepEscapeKeys(value) {
3795
+ return deepMapKeys(value, (k) => NEEDS_ESCAPE.test(k) ? `${ESCAPE_CHAR}${k}` : k);
3796
+ }
3797
+ function deepUnescapeKeys(value) {
3798
+ return deepMapKeys(value, (k) => k.startsWith(ESCAPE_CHAR) ? k.slice(1) : k);
3799
+ }
3800
+ function injectServiceKey(args, serviceKey = process.env.KODY_SERVICE_KEY) {
3801
+ if (!serviceKey) return args;
3802
+ if (args === void 0) return { serviceKey };
3803
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
3804
+ return { ...args, serviceKey };
3805
+ }
3806
+ function withEscapedKeys(client, serviceKey = process.env.KODY_SERVICE_KEY) {
3807
+ return new Proxy(client, {
3808
+ get(target, prop, receiver) {
3809
+ if (CALL_METHODS.includes(prop)) {
3810
+ const method = Reflect.get(target, prop, target);
3811
+ return async (fn, args) => {
3812
+ const authed = injectServiceKey(args, serviceKey);
3813
+ const result = await method.call(target, fn, authed === void 0 ? void 0 : deepEscapeKeys(authed));
3814
+ return deepUnescapeKeys(result);
3815
+ };
3816
+ }
3817
+ const value = Reflect.get(target, prop, receiver);
3818
+ return typeof value === "function" ? value.bind(target) : value;
3819
+ }
3820
+ });
3821
+ }
3822
+ function createConvexClientFromEnv(env = process.env) {
3823
+ const url = env.CONVEX_URL?.trim();
3824
+ if (!url) return null;
3825
+ return withEscapedKeys(new ConvexHttpClient(url), env.KODY_SERVICE_KEY);
3826
+ }
3827
+ var ESCAPE_CHAR, NEEDS_ESCAPE, CALL_METHODS;
3828
+ var init_convex_client = __esm({
3829
+ "src/chat/convex-client.ts"() {
3830
+ "use strict";
3831
+ ESCAPE_CHAR = "~";
3832
+ NEEDS_ESCAPE = /^[$_~]/;
3833
+ CALL_METHODS = ["query", "mutation", "action"];
3834
+ }
3835
+ });
3836
+
3837
+ // src/state-backend.ts
3838
+ import { anyApi } from "convex/server";
3839
+ function requireTenant(tenantId) {
3840
+ const value = tenantId.trim();
3841
+ if (!/^[^/\s]+\/[^/\s]+$/.test(value)) throw new Error("tenantId must be an owner/repository pair");
3842
+ return value;
3843
+ }
3844
+ function requireNonEmpty(value, name) {
3845
+ const normalized = value.trim();
3846
+ if (!normalized) throw new Error(`${name} must not be empty`);
3847
+ return normalized;
3848
+ }
3849
+ function createStateBackendFromEnv(env = process.env, client) {
3850
+ const url = env.CONVEX_URL?.trim();
3851
+ const serviceKey = env.KODY_SERVICE_KEY?.trim();
3852
+ if (!url || !serviceKey) throw new Error("CONVEX_URL and KODY_SERVICE_KEY are required");
3853
+ const transport = client ?? createConvexClientFromEnv(env);
3854
+ return {
3855
+ async get(tenantId, taskKey, kind) {
3856
+ const result = await transport.query(anyApi.taskState.get, {
3857
+ tenantId: requireTenant(tenantId),
3858
+ taskKey: requireNonEmpty(taskKey, "taskKey"),
3859
+ kind: requireNonEmpty(kind, "kind")
3860
+ });
3861
+ return result ?? null;
3862
+ },
3863
+ async save(tenantId, taskKey, kind, doc, expectedUpdatedAt) {
3864
+ await transport.mutation(anyApi.taskState.save, {
3865
+ tenantId: requireTenant(tenantId),
3866
+ taskKey: requireNonEmpty(taskKey, "taskKey"),
3867
+ kind: requireNonEmpty(kind, "kind"),
3868
+ doc,
3869
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3870
+ ...expectedUpdatedAt ? { expectedUpdatedAt } : {}
3871
+ });
3872
+ }
3873
+ };
3874
+ }
3875
+ var init_state_backend = __esm({
3876
+ "src/state-backend.ts"() {
3877
+ "use strict";
3878
+ init_convex_client();
3879
+ }
3880
+ });
3881
+
3760
3882
  // src/task-artifacts.ts
3761
3883
  import fs10 from "fs";
3762
3884
  import path12 from "path";
@@ -3784,7 +3906,29 @@ function verifyTaskArtifacts(absDir) {
3784
3906
  function taskArtifactStatePath(taskId, file) {
3785
3907
  return posixPath.join("tasks", taskId, file);
3786
3908
  }
3787
- function persistTaskArtifactsToState(config, cwd, artifacts) {
3909
+ async function persistTaskArtifactsToState(config, cwd, artifacts) {
3910
+ const tenantId = config.github?.owner && config.github.repo ? `${config.github.owner}/${config.github.repo}` : process.env.GITHUB_REPOSITORY?.trim();
3911
+ if (process.env.CONVEX_URL && process.env.KODY_SERVICE_KEY && tenantId) {
3912
+ const backend = createStateBackendFromEnv();
3913
+ for (const file of TASK_ARTIFACT_FILES) {
3914
+ const full = path12.join(artifacts.absDir, file);
3915
+ if (!fs10.existsSync(full)) continue;
3916
+ const stat = fs10.statSync(full);
3917
+ if (!stat.isFile() || stat.size === 0) continue;
3918
+ const content = fs10.readFileSync(full, "utf-8");
3919
+ const kind = file.replace(/\.(json|md)$/, "");
3920
+ let doc = content;
3921
+ if (file.endsWith(".json")) {
3922
+ try {
3923
+ doc = JSON.parse(content);
3924
+ } catch (err) {
3925
+ throw new Error(`task artifact ${file} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
3926
+ }
3927
+ }
3928
+ await backend.save(tenantId, artifacts.taskId, kind, doc);
3929
+ }
3930
+ return;
3931
+ }
3788
3932
  for (const file of TASK_ARTIFACT_FILES) {
3789
3933
  const full = path12.join(artifacts.absDir, file);
3790
3934
  if (!fs10.existsSync(full)) continue;
@@ -3863,6 +4007,7 @@ var init_task_artifacts = __esm({
3863
4007
  "src/task-artifacts.ts"() {
3864
4008
  "use strict";
3865
4009
  init_runtimePaths();
4010
+ init_state_backend();
3866
4011
  init_stateRepo();
3867
4012
  TASK_ARTIFACT_FILES = ["context.json", "memory-recs.json", "followups.json", "handoff-notes.md"];
3868
4013
  }
@@ -4290,6 +4435,7 @@ function prBranchLifecycle(profile, profilePath) {
4290
4435
  const tail = [
4291
4436
  ...verifyChain,
4292
4437
  { script: "commitAndPush" },
4438
+ { script: "requireDeliveryArtifacts" },
4293
4439
  { script: "ensurePr" },
4294
4440
  { script: "postIssueComment" },
4295
4441
  { script: "writeAgentRunSummary" },
@@ -5477,7 +5623,7 @@ function renderStateComment(state) {
5477
5623
  lines.push("</details>");
5478
5624
  return lines.join("\n");
5479
5625
  }
5480
- function readTaskState(target, number, cwd, config) {
5626
+ function readTaskStateLegacy(target, number, cwd, config) {
5481
5627
  const stateConfig = taskStateConfig(cwd, config);
5482
5628
  const loaded = readStateText(stateConfig, cwd, taskStatePath(target, number));
5483
5629
  if (!loaded) return emptyState();
@@ -5491,13 +5637,34 @@ function readTaskState(target, number, cwd, config) {
5491
5637
  }
5492
5638
  return normalizeTaskState(parsed);
5493
5639
  }
5640
+ function backendScope(config) {
5641
+ const tenantId = config?.github?.owner && config.github.repo ? `${config.github.owner}/${config.github.repo}` : process.env.GITHUB_REPOSITORY?.trim();
5642
+ if (!process.env.CONVEX_URL || !process.env.KODY_SERVICE_KEY || !tenantId) return null;
5643
+ return { tenantId };
5644
+ }
5645
+ async function readTaskState(target, number, cwd, config) {
5646
+ const scope = backendScope(config);
5647
+ if (!scope) return readTaskStateLegacy(target, number, cwd, config);
5648
+ const backend = createStateBackendFromEnv();
5649
+ const kind = "state";
5650
+ const taskKey = `${target === "issue" ? "issues" : "prs"}/${number}`;
5651
+ const record = await backend.get(scope.tenantId, taskKey, kind);
5652
+ if (!record) return emptyState();
5653
+ try {
5654
+ return normalizeTaskState(record.doc);
5655
+ } catch (err) {
5656
+ throw new CorruptStateError(
5657
+ `backend task state unparseable for ${taskKey}: ${err instanceof Error ? err.message : String(err)}`
5658
+ );
5659
+ }
5660
+ }
5494
5661
  function setArtifact(state, name, artifact) {
5495
5662
  return {
5496
5663
  ...state,
5497
5664
  artifacts: { ...state.artifacts ?? {}, [name]: artifact }
5498
5665
  };
5499
5666
  }
5500
- function writeTaskState(target, number, state, cwd, config) {
5667
+ function writeTaskStateLegacy(target, number, state, cwd, config) {
5501
5668
  const stateConfig = taskStateConfig(cwd, config);
5502
5669
  upsertStateText(
5503
5670
  stateConfig,
@@ -5508,11 +5675,21 @@ function writeTaskState(target, number, state, cwd, config) {
5508
5675
  `chore(tasks): update ${target} ${number} state`
5509
5676
  );
5510
5677
  }
5511
- var STATE_BEGIN, STATE_END, HISTORY_MAX_ENTRIES, JOB_RUNS_MAX_ENTRIES, CorruptStateError;
5678
+ async function writeTaskState(target, number, state, cwd, config) {
5679
+ const scope = backendScope(config);
5680
+ if (!scope) {
5681
+ writeTaskStateLegacy(target, number, state, cwd, config);
5682
+ return;
5683
+ }
5684
+ const backend = createStateBackendFromEnv();
5685
+ await backend.save(scope.tenantId, `${target === "issue" ? "issues" : "prs"}/${number}`, "state", state);
5686
+ }
5687
+ var STATE_BEGIN, STATE_END, HISTORY_MAX_ENTRIES, JOB_RUNS_MAX_ENTRIES, CorruptStateError, readTaskStateAsync;
5512
5688
  var init_state = __esm({
5513
5689
  "src/state.ts"() {
5514
5690
  "use strict";
5515
5691
  init_config();
5692
+ init_state_backend();
5516
5693
  init_stateRepo();
5517
5694
  STATE_BEGIN = "<!-- kody:state:v1:begin -->";
5518
5695
  STATE_END = "<!-- kody:state:v1:end -->";
@@ -5524,6 +5701,7 @@ var init_state = __esm({
5524
5701
  this.name = "CorruptStateError";
5525
5702
  }
5526
5703
  };
5704
+ readTaskStateAsync = readTaskState;
5527
5705
  }
5528
5706
  });
5529
5707
 
@@ -5958,7 +6136,7 @@ async function runContainerLoop(profile, ctx, input) {
5958
6136
  return;
5959
6137
  }
5960
6138
  const runChild = input.__runChild ?? ((name, opts) => runImplementation(name, opts));
5961
- const reader = input.__readTaskState ?? readTaskState;
6139
+ const reader = input.__readTaskState ?? readTaskStateAsync;
5962
6140
  const issueNumber = ctx.args.issue;
5963
6141
  let preloadedSnapshot;
5964
6142
  if (profile.preloadContext) {
@@ -6008,7 +6186,7 @@ async function runContainerLoop(profile, ctx, input) {
6008
6186
  process.stderr.write(`[kody container] resetBetweenChildren=false; preserving tracked tree
6009
6187
  `);
6010
6188
  }
6011
- const priorState = readContainerState(ctx, child, reader);
6189
+ const priorState = await readContainerState(ctx, child, reader);
6012
6190
  if (priorState.core?.prUrl) knownPrUrl = priorState.core.prUrl;
6013
6191
  const priorAction = priorState.implementations?.[child.implementation]?.lastAction;
6014
6192
  let actionType2;
@@ -6102,7 +6280,7 @@ async function runContainerLoop(profile, ctx, input) {
6102
6280
  else process.env.KODY_CONTAINER_PARENT = priorParent;
6103
6281
  }
6104
6282
  const priorAttempts = priorState.core?.attempts?.[child.implementation] ?? 0;
6105
- const next = readContainerState(ctx, child, reader);
6283
+ const next = await readContainerState(ctx, child, reader);
6106
6284
  if (next.core?.prUrl) knownPrUrl = next.core.prUrl;
6107
6285
  const nextAttempts = next.core?.attempts?.[child.implementation] ?? 0;
6108
6286
  const nextChildAction = next.implementations?.[child.implementation]?.lastAction;
@@ -6189,20 +6367,20 @@ function resetWorkingTree(cwd) {
6189
6367
  `);
6190
6368
  }
6191
6369
  }
6192
- function readContainerState(ctx, child, reader) {
6370
+ async function readContainerState(ctx, child, reader) {
6193
6371
  const issueNumber = ctx.args.issue;
6194
6372
  const cached2 = ctx.data.taskState;
6195
6373
  const prUrl = cached2?.core?.prUrl;
6196
6374
  const prNumber = prUrl ? parsePrNumber2(prUrl) : null;
6197
6375
  if (child.target === "pr" && prNumber) {
6198
6376
  try {
6199
- return reader("pr", prNumber, ctx.cwd);
6377
+ return await reader("pr", prNumber, ctx.cwd);
6200
6378
  } catch {
6201
6379
  }
6202
6380
  }
6203
6381
  if (issueNumber !== void 0) {
6204
6382
  try {
6205
- return reader("issue", issueNumber, ctx.cwd);
6383
+ return await reader("issue", issueNumber, ctx.cwd);
6206
6384
  } catch {
6207
6385
  }
6208
6386
  }
@@ -7360,7 +7538,7 @@ var init_saveTaskState = __esm({
7360
7538
  if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
7361
7539
  if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
7362
7540
  applyStandaloneFinalState(next, ctx, profile);
7363
- writeTaskState(target, number, next, ctx.cwd, ctx.config);
7541
+ await writeTaskState(target, number, next, ctx.cwd, ctx.config);
7364
7542
  ctx.data.taskState = next;
7365
7543
  ctx.data.taskStateRendered = renderStateComment(next);
7366
7544
  };
@@ -7398,7 +7576,7 @@ var init_advanceFlow = __esm({
7398
7576
  const curState = state;
7399
7577
  let issueState;
7400
7578
  try {
7401
- issueState = readTaskState("issue", flow.issueNumber, ctx.cwd, ctx.config);
7579
+ issueState = await readTaskState("issue", flow.issueNumber, ctx.cwd, ctx.config);
7402
7580
  } catch {
7403
7581
  issueState = curState;
7404
7582
  }
@@ -7414,7 +7592,7 @@ var init_advanceFlow = __esm({
7414
7592
  if (hops > FLOW_HOP_CAP) {
7415
7593
  nextIssueState.flow = void 0;
7416
7594
  try {
7417
- writeTaskState("issue", flow.issueNumber, nextIssueState, ctx.cwd, ctx.config);
7595
+ await writeTaskState("issue", flow.issueNumber, nextIssueState, ctx.cwd, ctx.config);
7418
7596
  } catch (err) {
7419
7597
  process.stderr.write(
7420
7598
  `[kody advanceFlow] failed to clear looping flow on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -7435,7 +7613,7 @@ var init_advanceFlow = __esm({
7435
7613
  }
7436
7614
  nextIssueState.flow = { ...flow, hops };
7437
7615
  try {
7438
- writeTaskState("issue", flow.issueNumber, nextIssueState, ctx.cwd, ctx.config);
7616
+ await writeTaskState("issue", flow.issueNumber, nextIssueState, ctx.cwd, ctx.config);
7439
7617
  } catch (err) {
7440
7618
  process.stderr.write(
7441
7619
  `[kody advanceFlow] failed to persist hop count on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -13147,7 +13325,7 @@ var init_dispatchClassified = __esm({
13147
13325
  const nextState = reduce(state, "classify", action, void 0, profile.agent, jobMetaFromData(ctx.data));
13148
13326
  ctx.data.taskState = nextState;
13149
13327
  ctx.data.taskStateRendered = renderStateComment(nextState);
13150
- writeTaskState("issue", issueNumber, nextState, ctx.cwd, ctx.config);
13328
+ await writeTaskState("issue", issueNumber, nextState, ctx.cwd, ctx.config);
13151
13329
  const cliArgs = { issue: issueNumber };
13152
13330
  if (base && getProfileInputs(classification)?.some((i) => i.name === "base")) {
13153
13331
  cliArgs.base = base;
@@ -13553,7 +13731,7 @@ var init_ensurePr = __esm({
13553
13731
  draft: isFailure,
13554
13732
  failureReason: isFailure ? failureReason : void 0,
13555
13733
  changedFiles,
13556
- agentSummary: ctx.data.prSummary,
13734
+ agentSummary: ctx.data.prSummary || ctx.data.agentFallbackSummary,
13557
13735
  baseBranch,
13558
13736
  // No fresh commit this run → don't rebuild the body of an existing PR;
13559
13737
  // it would replace the original agent summary with the empty fallback.
@@ -13660,7 +13838,7 @@ var init_finalizeTerminal = __esm({
13660
13838
  if (prNumber && prNumber !== issueNumber) setKodyLabel(prNumber, spec, ctx.cwd);
13661
13839
  if (!state) {
13662
13840
  try {
13663
- state = readTaskState(target, targetNumber, ctx.cwd, ctx.config);
13841
+ state = await readTaskState(target, targetNumber, ctx.cwd, ctx.config);
13664
13842
  } catch {
13665
13843
  state = void 0;
13666
13844
  }
@@ -13679,7 +13857,7 @@ var init_finalizeTerminal = __esm({
13679
13857
  };
13680
13858
  ctx.data.taskState = next;
13681
13859
  try {
13682
- writeTaskState(target, targetNumber, next, ctx.cwd, ctx.config);
13860
+ await writeTaskState(target, targetNumber, next, ctx.cwd, ctx.config);
13683
13861
  } catch (err) {
13684
13862
  process.stderr.write(
13685
13863
  `[kody finalizeTerminal] failed to write terminal state on ${target} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -13757,7 +13935,7 @@ var init_finishFlow = __esm({
13757
13935
  const target = ctx.data.commentTargetType ?? "issue";
13758
13936
  const targetNumber = ctx.data.commentTargetNumber ?? issueNumber;
13759
13937
  try {
13760
- writeTaskState(target, targetNumber, state, ctx.cwd, ctx.config);
13938
+ await writeTaskState(target, targetNumber, state, ctx.cwd, ctx.config);
13761
13939
  } catch (err) {
13762
13940
  process.stderr.write(
13763
13941
  `[kody finishFlow] failed to update state mirror: ${err instanceof Error ? err.message : String(err)}
@@ -15561,7 +15739,7 @@ var init_loadTaskState = __esm({
15561
15739
  return;
15562
15740
  }
15563
15741
  try {
15564
- ctx.data.taskState = readTaskState(target, number, ctx.cwd, ctx.config);
15742
+ ctx.data.taskState = await readTaskState(target, number, ctx.cwd, ctx.config);
15565
15743
  } catch (err) {
15566
15744
  if (err instanceof CorruptStateError) {
15567
15745
  process.stderr.write(
@@ -15569,7 +15747,7 @@ var init_loadTaskState = __esm({
15569
15747
  `
15570
15748
  );
15571
15749
  try {
15572
- writeTaskState(target, number, emptyState(), ctx.cwd, ctx.config);
15750
+ await writeTaskState(target, number, emptyState(), ctx.cwd, ctx.config);
15573
15751
  } catch {
15574
15752
  }
15575
15753
  ctx.skipAgent = true;
@@ -15826,7 +16004,7 @@ var init_mirrorStateToPr = __esm({
15826
16004
  if (!prNumber) return;
15827
16005
  if (!state) {
15828
16006
  try {
15829
- state = readTaskState("issue", issueNumber, ctx.cwd, ctx.config);
16007
+ state = await readTaskState("issue", issueNumber, ctx.cwd, ctx.config);
15830
16008
  } catch {
15831
16009
  return;
15832
16010
  }
@@ -15836,7 +16014,7 @@ var init_mirrorStateToPr = __esm({
15836
16014
  ctx.data.taskState = state;
15837
16015
  }
15838
16016
  try {
15839
- writeTaskState("pr", prNumber, state, ctx.cwd, ctx.config);
16017
+ await writeTaskState("pr", prNumber, state, ctx.cwd, ctx.config);
15840
16018
  } catch (err) {
15841
16019
  process.stderr.write(
15842
16020
  `[kody mirrorStateToPr] failed to mirror state to PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -16285,6 +16463,7 @@ var init_parseAgentResult = __esm({
16285
16463
  return;
16286
16464
  }
16287
16465
  const parsed = parseAgentResult(agentResult.finalText);
16466
+ ctx.data.agentFinalText = agentResult.finalText;
16288
16467
  ctx.data.agentDone = parsed.done;
16289
16468
  ctx.data.commitMessage = parsed.commitMessage;
16290
16469
  ctx.data.prSummary = parsed.prSummary;
@@ -16598,7 +16777,7 @@ var init_persistFlowState = __esm({
16598
16777
  const issueNumber = ctx.args.issue ?? state.flow?.issueNumber;
16599
16778
  if (!issueNumber) return;
16600
16779
  try {
16601
- writeTaskState("issue", issueNumber, state, ctx.cwd, ctx.config);
16780
+ await writeTaskState("issue", issueNumber, state, ctx.cwd, ctx.config);
16602
16781
  } catch (err) {
16603
16782
  process.stderr.write(
16604
16783
  `[kody persistFlowState] failed to write state on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -16718,7 +16897,7 @@ var init_planTaskJobs = __esm({
16718
16897
  ctx.data.plannedTaskJobIds = planned.map((job) => job.id);
16719
16898
  const target = ctx.data.commentTargetType;
16720
16899
  const number = ctx.data.commentTargetNumber;
16721
- if (target && number) writeTaskState(target, number, next, ctx.cwd, ctx.config);
16900
+ if (target && number) await writeTaskState(target, number, next, ctx.cwd, ctx.config);
16722
16901
  };
16723
16902
  }
16724
16903
  });
@@ -17430,6 +17609,52 @@ var init_recordOutcome = __esm({
17430
17609
  }
17431
17610
  });
17432
17611
 
17612
+ // src/scripts/requireDeliveryArtifacts.ts
17613
+ function fallbackSummary(finalText) {
17614
+ const prose = finalText.split("\n").filter(
17615
+ (line) => !/^[\s>*_#`~-]*(?:DONE\b|COMMIT_MSG\s*:|PR_SUMMARY\s*:|PLAN_DEVIATIONS\s*:|FEEDBACK_ACTIONS\s*:)/i.test(line)
17616
+ ).join("\n").trim();
17617
+ if (!prose) return "";
17618
+ if (prose.length <= FALLBACK_SUMMARY_MAX) return prose;
17619
+ return `${prose.slice(0, FALLBACK_SUMMARY_MAX - 1)}\u2026`;
17620
+ }
17621
+ var FALLBACK_SUMMARY_MAX, requireDeliveryArtifacts;
17622
+ var init_requireDeliveryArtifacts = __esm({
17623
+ "src/scripts/requireDeliveryArtifacts.ts"() {
17624
+ "use strict";
17625
+ FALLBACK_SUMMARY_MAX = 6e3;
17626
+ requireDeliveryArtifacts = async (ctx) => {
17627
+ if (ctx.data.agentDone !== true) return;
17628
+ const commitResult = ctx.data.commitResult;
17629
+ const hasCommits = ctx.data.hasCommitsAhead === true;
17630
+ if (!commitResult?.committed && !hasCommits) return;
17631
+ const commitMessage = String(ctx.data.commitMessage ?? "").trim();
17632
+ const prSummary = String(ctx.data.prSummary ?? "").trim();
17633
+ const missing = [];
17634
+ if (!commitMessage) missing.push("COMMIT_MSG");
17635
+ if (!prSummary) missing.push("PR_SUMMARY");
17636
+ if (missing.length === 0) return;
17637
+ const reason = `agent omitted required delivery artifacts: ${missing.join(", ")}`;
17638
+ ctx.data.agentDone = false;
17639
+ ctx.data.agentResultIncomplete = true;
17640
+ ctx.data.agentMissingArtifacts = missing;
17641
+ ctx.data.agentFailureReason = reason;
17642
+ if (!prSummary) {
17643
+ const fallback = fallbackSummary(String(ctx.data.agentFinalText ?? ""));
17644
+ if (fallback) ctx.data.agentFallbackSummary = fallback;
17645
+ }
17646
+ const action = ctx.data.action;
17647
+ if (action?.type.endsWith("_COMPLETED")) {
17648
+ ctx.data.action = {
17649
+ type: action.type.replace(/_COMPLETED$/, "_FAILED"),
17650
+ payload: { reason, downgradedFrom: action.type },
17651
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17652
+ };
17653
+ }
17654
+ };
17655
+ }
17656
+ });
17657
+
17433
17658
  // src/scripts/requireFeedbackActions.ts
17434
17659
  function countActionItems(block) {
17435
17660
  if (!block.trim()) return 0;
@@ -20174,6 +20399,7 @@ var init_scripts = __esm({
20174
20399
  init_publishReport();
20175
20400
  init_recordClassification();
20176
20401
  init_recordOutcome();
20402
+ init_requireDeliveryArtifacts();
20177
20403
  init_requireFeedbackActions();
20178
20404
  init_requirePlanDeviations();
20179
20405
  init_resolveArtifacts();
@@ -20265,6 +20491,7 @@ var init_scripts = __esm({
20265
20491
  writeJobStateFile,
20266
20492
  appendCompanyActivity,
20267
20493
  requireFeedbackActions,
20494
+ requireDeliveryArtifacts,
20268
20495
  requirePlanDeviations,
20269
20496
  verify,
20270
20497
  verifyWithRetry,
@@ -20993,7 +21220,7 @@ async function runImplementation(profileName, input) {
20993
21220
  `);
20994
21221
  }
20995
21222
  if (!input.skipConfig && (config.state || config.github.owner && config.github.repo)) {
20996
- persistTaskArtifactsToState(config, input.cwd, taskArtifacts);
21223
+ await persistTaskArtifactsToState(config, input.cwd, taskArtifacts);
20997
21224
  }
20998
21225
  } catch (err) {
20999
21226
  process.stderr.write(
@@ -22465,59 +22692,8 @@ function makeRunId(sessionId, suffix) {
22465
22692
  }
22466
22693
 
22467
22694
  // src/chat/session-store.ts
22468
- import { anyApi } from "convex/server";
22469
-
22470
- // src/chat/convex-client.ts
22471
- import { ConvexHttpClient } from "convex/browser";
22472
- var ESCAPE_CHAR = "~";
22473
- var NEEDS_ESCAPE = /^[$_~]/;
22474
- function isPlainObject2(value) {
22475
- if (value === null || typeof value !== "object") return false;
22476
- const proto = Object.getPrototypeOf(value);
22477
- return proto === Object.prototype || proto === null;
22478
- }
22479
- function deepMapKeys(value, mapKey) {
22480
- if (Array.isArray(value)) return value.map((item) => deepMapKeys(item, mapKey));
22481
- if (isPlainObject2(value)) {
22482
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [mapKey(key), deepMapKeys(item, mapKey)]));
22483
- }
22484
- return value;
22485
- }
22486
- function deepEscapeKeys(value) {
22487
- return deepMapKeys(value, (k) => NEEDS_ESCAPE.test(k) ? `${ESCAPE_CHAR}${k}` : k);
22488
- }
22489
- function deepUnescapeKeys(value) {
22490
- return deepMapKeys(value, (k) => k.startsWith(ESCAPE_CHAR) ? k.slice(1) : k);
22491
- }
22492
- var CALL_METHODS = ["query", "mutation", "action"];
22493
- function injectServiceKey(args) {
22494
- const serviceKey = process.env.KODY_SERVICE_KEY;
22495
- if (!serviceKey) return args;
22496
- if (args === void 0) return { serviceKey };
22497
- if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
22498
- return { ...args, serviceKey };
22499
- }
22500
- function withEscapedKeys(client) {
22501
- return new Proxy(client, {
22502
- get(target, prop, receiver) {
22503
- if (CALL_METHODS.includes(prop)) {
22504
- const method = Reflect.get(target, prop, target);
22505
- return async (fn, args) => {
22506
- const authed = injectServiceKey(args);
22507
- const result = await method.call(target, fn, authed === void 0 ? void 0 : deepEscapeKeys(authed));
22508
- return deepUnescapeKeys(result);
22509
- };
22510
- }
22511
- const value = Reflect.get(target, prop, receiver);
22512
- return typeof value === "function" ? value.bind(target) : value;
22513
- }
22514
- });
22515
- }
22516
- function createConvexClientFromEnv(env = process.env) {
22517
- const url = env.CONVEX_URL?.trim();
22518
- if (!url) return null;
22519
- return withEscapedKeys(new ConvexHttpClient(url));
22520
- }
22695
+ init_convex_client();
22696
+ import { anyApi as anyApi2 } from "convex/server";
22521
22697
 
22522
22698
  // src/chat/session.ts
22523
22699
  import * as fs13 from "fs";
@@ -22640,7 +22816,7 @@ function createConvexStore(args) {
22640
22816
  if (!sessionUpserted) {
22641
22817
  try {
22642
22818
  const meta = readMeta(sessionFile) ?? { type: "meta", mode: "one-shot" };
22643
- await client.mutation(anyApi.chatSessions.upsert, {
22819
+ await client.mutation(anyApi2.chatSessions.upsert, {
22644
22820
  tenantId,
22645
22821
  sessionId,
22646
22822
  meta,
@@ -22651,12 +22827,12 @@ function createConvexStore(args) {
22651
22827
  logger.warn(`session ${sessionId}: chatSessions.upsert failed: ${err instanceof Error ? err.message : String(err)}`);
22652
22828
  }
22653
22829
  }
22654
- await client.mutation(anyApi.chatTurns.append, { tenantId, sessionId, turn });
22830
+ await client.mutation(anyApi2.chatTurns.append, { tenantId, sessionId, turn });
22655
22831
  };
22656
22832
  return {
22657
22833
  backend: "convex",
22658
22834
  readTurns: async () => {
22659
- const docs = await client.query(anyApi.chatTurns.list, { tenantId, sessionId });
22835
+ const docs = await client.query(anyApi2.chatTurns.list, { tenantId, sessionId });
22660
22836
  const convexTurns = [...docs].sort((a, b) => a.seq - b.seq).map((doc) => doc.turn).filter(isChatTurn);
22661
22837
  const localTurns = readSession(sessionFile);
22662
22838
  if (localTurns.length <= convexTurns.length) return convexTurns;
@@ -22975,7 +23151,7 @@ async function runChatTurn(opts) {
22975
23151
  `
22976
23152
  );
22977
23153
  }
22978
- if (opts.stateConfig) persistTaskArtifactsToState(opts.stateConfig, opts.cwd, taskArtifactsPaths);
23154
+ if (opts.stateConfig) await persistTaskArtifactsToState(opts.stateConfig, opts.cwd, taskArtifactsPaths);
22979
23155
  } catch (err) {
22980
23156
  process.stderr.write(
22981
23157
  `[task-artifacts] chat session ${taskArtifactsPaths.taskId} persist failed: ${err instanceof Error ? err.message : String(err)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.381",
3
+ "version": "0.4.383",
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",
@@ -12,28 +12,6 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
- "scripts": {
16
- "kody:run": "tsx bin/kody.ts",
17
- "serve": "tsx bin/kody.ts serve",
18
- "serve:vscode": "tsx bin/kody.ts serve vscode",
19
- "serve:claude": "tsx bin/kody.ts serve claude",
20
- "clean:dist": "node scripts/clean-dist.cjs",
21
- "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
22
- "check:modularity": "tsx scripts/check-script-modularity.ts",
23
- "pretest": "pnpm check:modularity",
24
- "test": "vitest run tests/unit tests/int --coverage",
25
- "posttest": "tsx scripts/check-coverage-floor.ts",
26
- "test:smoke": "vitest run tests/smoke --no-coverage",
27
- "test:e2e": "vitest run tests/e2e --no-coverage",
28
- "test:all": "vitest run tests --no-coverage",
29
- "typecheck": "tsc --noEmit",
30
- "lint": "biome check",
31
- "lint:fix": "biome check --write",
32
- "format": "biome format --write",
33
- "verify:package": "node scripts/verify-package-tarball.cjs",
34
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
35
- "prepublishOnly": "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm build && pnpm verify:package"
36
- },
37
15
  "dependencies": {
38
16
  "@actions/cache": "^6.0.0",
39
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -58,5 +36,26 @@
58
36
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
59
37
  },
60
38
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
61
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
62
- }
39
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
40
+ "scripts": {
41
+ "kody:run": "tsx bin/kody.ts",
42
+ "serve": "tsx bin/kody.ts serve",
43
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
44
+ "serve:claude": "tsx bin/kody.ts serve claude",
45
+ "clean:dist": "node scripts/clean-dist.cjs",
46
+ "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
47
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
48
+ "pretest": "pnpm check:modularity",
49
+ "test": "vitest run tests/unit tests/int --coverage",
50
+ "posttest": "tsx scripts/check-coverage-floor.ts",
51
+ "test:smoke": "vitest run tests/smoke --no-coverage",
52
+ "test:e2e": "vitest run tests/e2e --no-coverage",
53
+ "test:all": "vitest run tests --no-coverage",
54
+ "typecheck": "tsc --noEmit",
55
+ "lint": "biome check",
56
+ "lint:fix": "biome check --write",
57
+ "format": "biome format --write",
58
+ "verify:package": "node scripts/verify-package-tarball.cjs",
59
+ "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
60
+ }
61
+ }