@kody-ade/kody-engine 0.3.38 → 0.3.40

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
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.3.38",
6
+ version: "0.3.40",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -4058,112 +4058,69 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
4058
4058
  import * as fs20 from "fs";
4059
4059
  import * as path18 from "path";
4060
4060
 
4061
- // src/scripts/missionGist.ts
4062
- function gistDescription(owner, repo, slug) {
4063
- return `kody-mission:${owner}/${repo}:${slug}`;
4061
+ // src/scripts/missionStateFile.ts
4062
+ function stateFilePath(missionsDir, slug) {
4063
+ return `${missionsDir.replace(/\/+$/, "")}/${slug}.state.json`;
4064
4064
  }
4065
- function listGists(cwd) {
4065
+ function loadMissionState(owner, repo, filePath, cwd) {
4066
4066
  let raw = "";
4067
4067
  try {
4068
- raw = gh2(["api", "--paginate", "/gists?per_page=100"], { cwd });
4069
- } catch {
4070
- return [];
4068
+ raw = gh2(["api", `/repos/${owner}/${repo}/contents/${filePath}`], { cwd });
4069
+ } catch (err) {
4070
+ const msg = err instanceof Error ? err.message : String(err);
4071
+ if (/HTTP 404/i.test(msg) || /Not Found/i.test(msg)) {
4072
+ return { path: filePath, sha: null, state: initialStateEnvelope("seed"), created: true };
4073
+ }
4074
+ throw err;
4071
4075
  }
4072
4076
  let parsed;
4073
4077
  try {
4074
4078
  parsed = JSON.parse(raw);
4075
4079
  } catch {
4076
- return [];
4080
+ throw new Error(`loadMissionState: contents API for ${filePath} did not return JSON`);
4077
4081
  }
4078
- if (!Array.isArray(parsed)) return [];
4079
- return parsed.filter((g) => typeof g.id === "string").map((g) => ({
4080
- id: g.id,
4081
- description: typeof g.description === "string" ? g.description : null,
4082
- files: g.files ?? {}
4083
- }));
4084
- }
4085
- function getGist(gistId, cwd) {
4086
- let raw = "";
4087
- try {
4088
- raw = gh2(["api", `/gists/${gistId}`], { cwd });
4089
- } catch {
4090
- return null;
4091
- }
4092
- let parsed;
4093
- try {
4094
- parsed = JSON.parse(raw);
4095
- } catch {
4096
- return null;
4082
+ if (!parsed || typeof parsed !== "object") {
4083
+ throw new Error(`loadMissionState: contents API for ${filePath} returned non-object`);
4097
4084
  }
4098
- if (!parsed || typeof parsed !== "object") return null;
4099
4085
  const o = parsed;
4100
- if (typeof o.id !== "string") return null;
4101
- return {
4102
- id: o.id,
4103
- description: typeof o.description === "string" ? o.description : null,
4104
- files: o.files ?? {}
4105
- };
4106
- }
4107
- function findGistByDescription(description, cwd) {
4108
- const all = listGists(cwd);
4109
- return all.find((g) => g.description === description) ?? null;
4110
- }
4111
- function readEnvelope(gist) {
4112
- const file = gist.files["state.json"];
4113
- if (!file?.content) return null;
4114
- let parsed;
4115
- try {
4116
- parsed = JSON.parse(file.content);
4117
- } catch {
4118
- const fallback = parseStateCommentBody("kody-mission-state", file.content);
4119
- return fallback ?? null;
4086
+ if (o.type !== "file" || o.encoding !== "base64" || typeof o.content !== "string") {
4087
+ throw new Error(`loadMissionState: ${filePath} is not a base64 file`);
4120
4088
  }
4121
- return isStateEnvelope(parsed) ? parsed : null;
4122
- }
4123
- function findMissionGist(owner, repo, slug, cwd) {
4124
- const desc = gistDescription(owner, repo, slug);
4125
- const gist = findGistByDescription(desc, cwd);
4126
- if (!gist) return null;
4127
- const full = getGist(gist.id, cwd) ?? gist;
4128
- const envelope = readEnvelope(full);
4129
- if (!envelope) return null;
4130
- return { gistId: full.id, state: envelope };
4131
- }
4132
- function createMissionGist(owner, repo, slug, cursor = "seed", cwd) {
4133
- const description = gistDescription(owner, repo, slug);
4134
- const initial = initialStateEnvelope(cursor);
4135
- const payload = {
4136
- description,
4137
- public: false,
4138
- files: {
4139
- "state.json": { content: JSON.stringify(initial, null, 2) + "\n" }
4140
- }
4141
- };
4142
- const raw = gh2(["api", "--method", "POST", "/gists", "--input", "-"], {
4143
- cwd,
4144
- input: JSON.stringify(payload)
4145
- });
4146
- let parsed;
4089
+ const decoded = Buffer.from(o.content, "base64").toString("utf-8");
4090
+ let envelope;
4147
4091
  try {
4148
- parsed = JSON.parse(raw);
4092
+ envelope = JSON.parse(decoded);
4149
4093
  } catch {
4150
- throw new Error(`createMissionGist: gh did not return JSON: ${raw.slice(0, 200)}`);
4094
+ throw new Error(`loadMissionState: ${filePath} is not valid JSON`);
4151
4095
  }
4152
- if (!parsed || typeof parsed !== "object" || typeof parsed.id !== "string") {
4153
- throw new Error("createMissionGist: gist creation response missing id");
4096
+ if (!isStateEnvelope(envelope)) {
4097
+ throw new Error(`loadMissionState: ${filePath} is not a StateEnvelope`);
4154
4098
  }
4155
- return { gistId: parsed.id, state: initial };
4099
+ return { path: filePath, sha: o.sha, state: envelope, created: false };
4156
4100
  }
4157
- function writeMissionGist(gistId, next, cwd) {
4101
+ function writeMissionState(owner, repo, loaded, next, cwd) {
4102
+ if (!loaded.created && deepEqualsState(loaded.state, next)) {
4103
+ return false;
4104
+ }
4105
+ const body = JSON.stringify(next, null, 2) + "\n";
4158
4106
  const payload = {
4159
- files: {
4160
- "state.json": { content: JSON.stringify(next, null, 2) + "\n" }
4161
- }
4107
+ message: `chore(missions): update state for ${stateFileSlug(loaded.path)} (rev ${next.rev})`,
4108
+ content: Buffer.from(body, "utf-8").toString("base64")
4162
4109
  };
4163
- gh2(["api", "--method", "PATCH", `/gists/${gistId}`, "--input", "-"], {
4110
+ if (loaded.sha) payload.sha = loaded.sha;
4111
+ gh2(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${loaded.path}`, "--input", "-"], {
4164
4112
  cwd,
4165
4113
  input: JSON.stringify(payload)
4166
4114
  });
4115
+ return true;
4116
+ }
4117
+ function deepEqualsState(a, b) {
4118
+ if (a.cursor !== b.cursor || a.done !== b.done) return false;
4119
+ return JSON.stringify(a.data) === JSON.stringify(b.data);
4120
+ }
4121
+ function stateFileSlug(filePath) {
4122
+ const last = filePath.split("/").pop() ?? filePath;
4123
+ return last.replace(/\.state\.json$/i, "");
4167
4124
  }
4168
4125
 
4169
4126
  // src/scripts/loadMissionFromFile.ts
@@ -4185,14 +4142,11 @@ var loadMissionFromFile = async (ctx, _profile, args) => {
4185
4142
  }
4186
4143
  const raw = fs20.readFileSync(absPath, "utf-8");
4187
4144
  const { title, body } = parseMissionFile(raw, slug);
4188
- let loaded = findMissionGist(owner, repo, slug, ctx.cwd);
4189
- if (!loaded) {
4190
- loaded = createMissionGist(owner, repo, slug, "seed", ctx.cwd);
4191
- }
4145
+ const loaded = loadMissionState(owner, repo, stateFilePath(missionsDir, slug), ctx.cwd);
4192
4146
  ctx.data.missionSlug = slug;
4193
4147
  ctx.data.missionTitle = title;
4194
4148
  ctx.data.missionIntent = body;
4195
- ctx.data.missionGist = loaded;
4149
+ ctx.data.missionState = loaded;
4196
4150
  ctx.data.missionStateJson = JSON.stringify(loaded.state, null, 2);
4197
4151
  };
4198
4152
  function parseMissionFile(raw, slug) {
@@ -4523,7 +4477,7 @@ var parseMissionStateFromAgentResult = async (ctx, _profile, agentResult, args)
4523
4477
  ctx.data.nextStateParseError = "agent did not run";
4524
4478
  return;
4525
4479
  }
4526
- const fenceRegex = new RegExp("```" + escapeRegex2(fenceLabel) + "\\s*\\n([\\s\\S]*?)\\n```", "m");
4480
+ const fenceRegex = new RegExp(`\`\`\`${escapeRegex2(fenceLabel)}\\s*\\n([\\s\\S]*?)\\n\`\`\``, "m");
4527
4481
  const match = fenceRegex.exec(agentResult.finalText);
4528
4482
  if (!match) {
4529
4483
  ctx.data.nextStateParseError = `agent did not emit a \`${fenceLabel}\` fenced block`;
@@ -4540,7 +4494,7 @@ var parseMissionStateFromAgentResult = async (ctx, _profile, agentResult, args)
4540
4494
  ctx.data.nextStateParseError = "state must be an object with string `cursor`, object `data`, and boolean `done`";
4541
4495
  return;
4542
4496
  }
4543
- const loaded = ctx.data.missionGist;
4497
+ const loaded = ctx.data.missionState;
4544
4498
  const prevRev = loaded?.state.rev ?? 0;
4545
4499
  const next = {
4546
4500
  version: 1,
@@ -5701,8 +5655,8 @@ var writeIssueStateComment = async (ctx, _profile, _agentResult, args) => {
5701
5655
  }
5702
5656
  };
5703
5657
 
5704
- // src/scripts/writeMissionGistState.ts
5705
- var writeMissionGistState = async (ctx, _profile, _agentResult) => {
5658
+ // src/scripts/writeMissionStateFile.ts
5659
+ var writeMissionStateFile = async (ctx, _profile, _agentResult) => {
5706
5660
  const parseError = ctx.data.nextStateParseError;
5707
5661
  if (parseError) {
5708
5662
  process.stderr.write(`[kody] mission state write skipped: ${parseError}
@@ -5715,11 +5669,16 @@ var writeMissionGistState = async (ctx, _profile, _agentResult) => {
5715
5669
  if (!next) {
5716
5670
  return;
5717
5671
  }
5718
- const loaded = ctx.data.missionGist;
5672
+ const loaded = ctx.data.missionState;
5719
5673
  if (!loaded) {
5720
- throw new Error("writeMissionGistState: ctx.data.missionGist missing \u2014 preflight must run first");
5674
+ throw new Error("writeMissionStateFile: ctx.data.missionState missing \u2014 preflight must run first");
5675
+ }
5676
+ const owner = ctx.config.github.owner;
5677
+ const repo = ctx.config.github.repo;
5678
+ if (!owner || !repo) {
5679
+ throw new Error("writeMissionStateFile: ctx.config.github.owner/repo must be set");
5721
5680
  }
5722
- writeMissionGist(loaded.gistId, next, ctx.cwd);
5681
+ writeMissionState(owner, repo, loaded, next, ctx.cwd);
5723
5682
  };
5724
5683
 
5725
5684
  // src/scripts/writeRunSummary.ts
@@ -5787,7 +5746,7 @@ var postflightScripts = {
5787
5746
  parseIssueStateFromAgentResult,
5788
5747
  parseMissionStateFromAgentResult,
5789
5748
  writeIssueStateComment,
5790
- writeMissionGistState,
5749
+ writeMissionStateFile,
5791
5750
  requireFeedbackActions,
5792
5751
  requirePlanDeviations,
5793
5752
  verify,
@@ -6000,6 +5959,8 @@ async function runExecutable(profileName, input) {
6000
5959
  }
6001
5960
  }
6002
5961
  function resolveProfilePath(profileName) {
5962
+ const found = resolveExecutable(profileName);
5963
+ if (found) return found;
6003
5964
  const here = path19.dirname(new URL(import.meta.url).pathname);
6004
5965
  const candidates = [
6005
5966
  path19.join(here, "executables", profileName, "profile.json"),
@@ -61,7 +61,7 @@
61
61
  }
62
62
  },
63
63
  {
64
- "script": "writeMissionGistState"
64
+ "script": "writeMissionStateFile"
65
65
  }
66
66
  ]
67
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.3.38",
3
+ "version": "0.3.40",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",