@kody-ade/kody-engine 0.2.44 → 0.2.46

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/kody2.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.2.44",
6
+ version: "0.2.46",
7
7
  description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -50,7 +50,7 @@ var package_default = {
50
50
  };
51
51
 
52
52
  // src/chat-cli.ts
53
- import { execFileSync as execFileSync16 } from "child_process";
53
+ import { execFileSync as execFileSync20 } from "child_process";
54
54
  import * as fs19 from "fs";
55
55
  import * as path16 from "path";
56
56
 
@@ -528,7 +528,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
528
528
  }
529
529
 
530
530
  // src/kody2-cli.ts
531
- import { execFileSync as execFileSync15 } from "child_process";
531
+ import { execFileSync as execFileSync19 } from "child_process";
532
532
  import * as fs18 from "fs";
533
533
  import * as path15 from "path";
534
534
 
@@ -953,6 +953,248 @@ function parseScriptList(p, key, raw) {
953
953
  return out;
954
954
  }
955
955
 
956
+ // src/scripts/advanceFlow.ts
957
+ import { execFileSync as execFileSync3 } from "child_process";
958
+
959
+ // src/state.ts
960
+ import { execFileSync as execFileSync2 } from "child_process";
961
+ var STATE_BEGIN = "<!-- kody2:state:v1:begin -->";
962
+ var STATE_END = "<!-- kody2:state:v1:end -->";
963
+ var HISTORY_MAX_ENTRIES = 20;
964
+ var API_TIMEOUT_MS = 3e4;
965
+ function emptyState() {
966
+ return {
967
+ schemaVersion: 1,
968
+ core: {
969
+ phase: "idle",
970
+ status: "pending",
971
+ currentExecutable: null,
972
+ lastOutcome: null,
973
+ attempts: {}
974
+ },
975
+ executables: {},
976
+ artifacts: {},
977
+ history: []
978
+ };
979
+ }
980
+ function ghToken() {
981
+ return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
982
+ }
983
+ function gh(args, input, cwd) {
984
+ const token = ghToken();
985
+ const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
986
+ return execFileSync2("gh", args, {
987
+ encoding: "utf-8",
988
+ timeout: API_TIMEOUT_MS,
989
+ cwd,
990
+ env,
991
+ input,
992
+ stdio: input ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"]
993
+ }).trim();
994
+ }
995
+ function findStateComment(target, number, cwd) {
996
+ const apiPath = target === "issue" ? `repos/{owner}/{repo}/issues/${number}/comments` : `repos/{owner}/{repo}/issues/${number}/comments`;
997
+ try {
998
+ const raw = gh(["api", "--paginate", apiPath], void 0, cwd);
999
+ const list = JSON.parse(raw);
1000
+ for (const c of list) {
1001
+ if (c.body?.includes(STATE_BEGIN)) {
1002
+ return { id: String(c.id), body: c.body };
1003
+ }
1004
+ }
1005
+ } catch {
1006
+ }
1007
+ return null;
1008
+ }
1009
+ function parseStateComment(body) {
1010
+ const beginIdx = body.indexOf(STATE_BEGIN);
1011
+ const endIdx = body.indexOf(STATE_END, beginIdx + 1);
1012
+ if (beginIdx < 0 || endIdx < 0) return emptyState();
1013
+ const between = body.slice(beginIdx + STATE_BEGIN.length, endIdx).trim();
1014
+ const OPEN = "```json";
1015
+ const CLOSE = "```";
1016
+ if (!between.startsWith(OPEN) || !between.endsWith(CLOSE)) return emptyState();
1017
+ const jsonStr = between.slice(OPEN.length, between.length - CLOSE.length).trim();
1018
+ try {
1019
+ const parsed = JSON.parse(jsonStr);
1020
+ if (parsed?.schemaVersion !== 1) return emptyState();
1021
+ return {
1022
+ schemaVersion: 1,
1023
+ core: { ...emptyState().core, ...parsed.core },
1024
+ executables: parsed.executables ?? {},
1025
+ artifacts: parsed.artifacts && typeof parsed.artifacts === "object" ? parsed.artifacts : {},
1026
+ history: Array.isArray(parsed.history) ? parsed.history : [],
1027
+ flow: parsed.flow
1028
+ };
1029
+ } catch {
1030
+ return emptyState();
1031
+ }
1032
+ }
1033
+ function reduce(state, executable, action) {
1034
+ if (!action) return state;
1035
+ const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
1036
+ const newExecutables = {
1037
+ ...state.executables,
1038
+ [executable]: { ...state.executables[executable] ?? { lastAction: null }, lastAction: action }
1039
+ };
1040
+ const newHistory = [
1041
+ ...state.history,
1042
+ { timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action) }
1043
+ ].slice(-HISTORY_MAX_ENTRIES);
1044
+ return {
1045
+ schemaVersion: 1,
1046
+ core: {
1047
+ ...state.core,
1048
+ attempts: newAttempts,
1049
+ lastOutcome: action,
1050
+ currentExecutable: executable,
1051
+ status: statusFromAction(action),
1052
+ phase: phaseFromAction(executable, action)
1053
+ },
1054
+ executables: newExecutables,
1055
+ artifacts: { ...state.artifacts ?? {} },
1056
+ history: newHistory,
1057
+ flow: state.flow
1058
+ };
1059
+ }
1060
+ function statusFromAction(action) {
1061
+ if (/FAILED$|ERROR$|MISSING$|REJECTED$/i.test(action.type)) return "failed";
1062
+ if (/COMPLETED$|SHIPPED$|MERGED$|SUCCESS$/i.test(action.type)) return "succeeded";
1063
+ return "running";
1064
+ }
1065
+ function phaseFromAction(executable, action) {
1066
+ if (/FAILED$|ERROR$|REJECTED$/i.test(action.type)) return "failed";
1067
+ if (executable === "build") return statusFromAction(action) === "succeeded" ? "implementing" : "implementing";
1068
+ if (executable === "review") return "reviewing";
1069
+ if (executable === "release") return "shipped";
1070
+ return "idle";
1071
+ }
1072
+ function noteFromAction(action) {
1073
+ const p = action.payload;
1074
+ if (typeof p?.prUrl === "string") return p.prUrl;
1075
+ if (typeof p?.reason === "string") return p.reason.slice(0, 120);
1076
+ if (typeof p?.commitMessage === "string") return p.commitMessage.slice(0, 120);
1077
+ return void 0;
1078
+ }
1079
+ function renderStateComment(state) {
1080
+ const lines = [];
1081
+ lines.push(STATE_BEGIN);
1082
+ lines.push("");
1083
+ lines.push("```json");
1084
+ lines.push(
1085
+ JSON.stringify(
1086
+ {
1087
+ schemaVersion: state.schemaVersion,
1088
+ core: state.core,
1089
+ artifacts: state.artifacts ?? {},
1090
+ executables: state.executables,
1091
+ history: state.history,
1092
+ ...state.flow ? { flow: state.flow } : {}
1093
+ },
1094
+ null,
1095
+ 2
1096
+ )
1097
+ );
1098
+ lines.push("```");
1099
+ lines.push("");
1100
+ lines.push(STATE_END);
1101
+ lines.push("");
1102
+ lines.push("## kody2 task state");
1103
+ lines.push("");
1104
+ lines.push(`- **Phase:** \`${state.core.phase}\` **Status:** \`${state.core.status}\``);
1105
+ if (state.core.currentExecutable) {
1106
+ lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
1107
+ }
1108
+ if (state.core.lastOutcome) {
1109
+ lines.push(`- **Last action:** \`${state.core.lastOutcome.type}\``);
1110
+ }
1111
+ const attempts = Object.entries(state.core.attempts).map(([k, v]) => `${k}:${v}`).join(", ");
1112
+ if (attempts) lines.push(`- **Attempts:** ${attempts}`);
1113
+ if (state.core.prUrl) lines.push(`- **PR:** ${state.core.prUrl}`);
1114
+ if (state.core.runUrl) lines.push(`- **Run:** ${state.core.runUrl}`);
1115
+ const artifactNames = Object.keys(state.artifacts ?? {});
1116
+ if (artifactNames.length > 0) {
1117
+ lines.push(`- **Artifacts:** ${artifactNames.map((n) => `\`${n}\``).join(", ")}`);
1118
+ }
1119
+ lines.push("");
1120
+ if (state.history.length > 0) {
1121
+ lines.push("### Recent history");
1122
+ lines.push("");
1123
+ const recent = state.history.slice(-10).reverse();
1124
+ for (const h of recent) {
1125
+ const note = h.note ? ` \u2014 ${h.note}` : "";
1126
+ lines.push(`- \`${h.timestamp}\` **${h.executable}** \u2192 \`${h.action}\`${note}`);
1127
+ }
1128
+ lines.push("");
1129
+ }
1130
+ return lines.join("\n");
1131
+ }
1132
+ function readTaskState(target, number, cwd) {
1133
+ const existing = findStateComment(target, number, cwd);
1134
+ return existing ? parseStateComment(existing.body) : emptyState();
1135
+ }
1136
+ function setArtifact(state, name, artifact) {
1137
+ return {
1138
+ ...state,
1139
+ artifacts: { ...state.artifacts ?? {}, [name]: artifact }
1140
+ };
1141
+ }
1142
+ function writeTaskState(target, number, state, cwd) {
1143
+ const body = renderStateComment(state);
1144
+ const existing = findStateComment(target, number, cwd);
1145
+ try {
1146
+ if (existing) {
1147
+ gh(["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"], body, cwd);
1148
+ } else {
1149
+ const sub = target === "issue" ? "issue" : "pr";
1150
+ gh([sub, "comment", String(number), "--body-file", "-"], body, cwd);
1151
+ }
1152
+ } catch (err) {
1153
+ process.stderr.write(
1154
+ `[kody2 state] failed to write state on ${target} #${number}: ${err instanceof Error ? err.message : String(err)}
1155
+ `
1156
+ );
1157
+ }
1158
+ }
1159
+
1160
+ // src/scripts/advanceFlow.ts
1161
+ var API_TIMEOUT_MS2 = 3e4;
1162
+ var advanceFlow = async (ctx, profile) => {
1163
+ const state = ctx.data.taskState;
1164
+ const flow = state?.flow;
1165
+ if (!flow?.issueNumber) return;
1166
+ const targetType = ctx.data.commentTargetType;
1167
+ const action = ctx.data.action;
1168
+ if (targetType === "pr" && action) {
1169
+ try {
1170
+ const issueState = readTaskState("issue", flow.issueNumber, ctx.cwd);
1171
+ issueState.flow = flow;
1172
+ const next = reduce(issueState, profile.name, action);
1173
+ if (state?.core.prUrl && !next.core.prUrl) next.core.prUrl = state.core.prUrl;
1174
+ next.flow = flow;
1175
+ writeTaskState("issue", flow.issueNumber, next, ctx.cwd);
1176
+ } catch (err) {
1177
+ process.stderr.write(
1178
+ `[kody2 advanceFlow] failed to mirror action to issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
1179
+ `
1180
+ );
1181
+ }
1182
+ }
1183
+ const body = `@kody2 orchestrate --flow ${flow.name}`;
1184
+ try {
1185
+ execFileSync3("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
1186
+ timeout: API_TIMEOUT_MS2,
1187
+ cwd: ctx.cwd,
1188
+ stdio: ["ignore", "pipe", "pipe"]
1189
+ });
1190
+ } catch (err) {
1191
+ process.stderr.write(
1192
+ `[kody2 advanceFlow] failed to re-trigger orchestrator on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
1193
+ `
1194
+ );
1195
+ }
1196
+ };
1197
+
956
1198
  // src/scripts/buildSyntheticPlugin.ts
957
1199
  import * as fs8 from "fs";
958
1200
  import * as os2 from "os";
@@ -1047,7 +1289,7 @@ function copyDir(src, dst) {
1047
1289
  }
1048
1290
 
1049
1291
  // src/coverage.ts
1050
- import { execFileSync as execFileSync2 } from "child_process";
1292
+ import { execFileSync as execFileSync4 } from "child_process";
1051
1293
  function patternToRegex(pattern) {
1052
1294
  let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1053
1295
  s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
@@ -1065,7 +1307,7 @@ function renderSiblingPath(file, requireSibling) {
1065
1307
  }
1066
1308
  function safeGit(args, cwd) {
1067
1309
  try {
1068
- return execFileSync2("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
1310
+ return execFileSync4("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
1069
1311
  } catch {
1070
1312
  return "";
1071
1313
  }
@@ -1236,10 +1478,10 @@ ${formatMissesForFeedback(misses)}`;
1236
1478
  };
1237
1479
 
1238
1480
  // src/scripts/commitAndPush.ts
1239
- import { execFileSync as execFileSync4 } from "child_process";
1481
+ import { execFileSync as execFileSync6 } from "child_process";
1240
1482
 
1241
1483
  // src/commit.ts
1242
- import { execFileSync as execFileSync3 } from "child_process";
1484
+ import { execFileSync as execFileSync5 } from "child_process";
1243
1485
  import * as fs10 from "fs";
1244
1486
  import * as path9 from "path";
1245
1487
  var FORBIDDEN_PATH_PREFIXES = [
@@ -1269,7 +1511,7 @@ var CONVENTIONAL_PREFIXES = [
1269
1511
  ];
1270
1512
  function git(args, cwd) {
1271
1513
  try {
1272
- return execFileSync3("git", args, {
1514
+ return execFileSync5("git", args, {
1273
1515
  encoding: "utf-8",
1274
1516
  timeout: 12e4,
1275
1517
  cwd,
@@ -1327,7 +1569,7 @@ function isForbiddenPath(p) {
1327
1569
  return false;
1328
1570
  }
1329
1571
  function listChangedFiles(cwd) {
1330
- const raw = execFileSync3("git", ["status", "--porcelain=v1", "-z"], {
1572
+ const raw = execFileSync5("git", ["status", "--porcelain=v1", "-z"], {
1331
1573
  encoding: "utf-8",
1332
1574
  cwd,
1333
1575
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1339,7 +1581,7 @@ function listChangedFiles(cwd) {
1339
1581
  }
1340
1582
  function listFilesInCommit(ref = "HEAD", cwd) {
1341
1583
  try {
1342
- const raw = execFileSync3("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1584
+ const raw = execFileSync5("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1343
1585
  encoding: "utf-8",
1344
1586
  cwd,
1345
1587
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1419,7 +1661,7 @@ var commitAndPush2 = async (ctx, profile) => {
1419
1661
  const kind = profile.name;
1420
1662
  if (kind === "resolve") {
1421
1663
  try {
1422
- execFileSync4("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
1664
+ execFileSync6("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
1423
1665
  } catch {
1424
1666
  }
1425
1667
  } else {
@@ -1555,18 +1797,61 @@ function formatToolsUsage(profile) {
1555
1797
  return lines.join("\n");
1556
1798
  }
1557
1799
 
1800
+ // src/scripts/dispatch.ts
1801
+ import { execFileSync as execFileSync7 } from "child_process";
1802
+ var API_TIMEOUT_MS3 = 3e4;
1803
+ var dispatch = async (ctx, _profile, _agentResult, args) => {
1804
+ const next = args?.next;
1805
+ if (!next) {
1806
+ process.stderr.write("[kody2 dispatch] missing `with.next` \u2014 skipping\n");
1807
+ return;
1808
+ }
1809
+ const target = args?.target ?? "issue";
1810
+ const issueNumber = ctx.args.issue;
1811
+ if (!issueNumber) {
1812
+ process.stderr.write("[kody2 dispatch] no --issue arg \u2014 skipping\n");
1813
+ return;
1814
+ }
1815
+ const state = ctx.data.taskState;
1816
+ if (state?.flow) {
1817
+ state.flow.step = next;
1818
+ }
1819
+ const usePr = target === "pr" && state?.core.prUrl;
1820
+ const targetNumber = usePr ? parsePr(state.core.prUrl) ?? issueNumber : issueNumber;
1821
+ const sub = usePr ? "pr" : "issue";
1822
+ const body = `@kody2 ${next}`;
1823
+ try {
1824
+ execFileSync7("gh", [sub, "comment", String(targetNumber), "--body", body], {
1825
+ timeout: API_TIMEOUT_MS3,
1826
+ cwd: ctx.cwd,
1827
+ stdio: ["ignore", "pipe", "pipe"]
1828
+ });
1829
+ } catch (err) {
1830
+ process.stderr.write(
1831
+ `[kody2 dispatch] failed to post @kody2 ${next} on ${sub} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
1832
+ `
1833
+ );
1834
+ }
1835
+ };
1836
+ function parsePr(url) {
1837
+ const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
1838
+ if (!m) return null;
1839
+ const n = parseInt(m[1], 10);
1840
+ return Number.isFinite(n) ? n : null;
1841
+ }
1842
+
1558
1843
  // src/issue.ts
1559
- import { execFileSync as execFileSync5 } from "child_process";
1560
- var API_TIMEOUT_MS = 3e4;
1561
- function ghToken() {
1844
+ import { execFileSync as execFileSync8 } from "child_process";
1845
+ var API_TIMEOUT_MS4 = 3e4;
1846
+ function ghToken2() {
1562
1847
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
1563
1848
  }
1564
- function gh(args, options) {
1565
- const token = ghToken();
1849
+ function gh2(args, options) {
1850
+ const token = ghToken2();
1566
1851
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
1567
- return execFileSync5("gh", args, {
1852
+ return execFileSync8("gh", args, {
1568
1853
  encoding: "utf-8",
1569
- timeout: API_TIMEOUT_MS,
1854
+ timeout: API_TIMEOUT_MS4,
1570
1855
  cwd: options?.cwd,
1571
1856
  env,
1572
1857
  input: options?.input,
@@ -1574,7 +1859,7 @@ function gh(args, options) {
1574
1859
  }).trim();
1575
1860
  }
1576
1861
  function getIssue(issueNumber, cwd) {
1577
- const output = gh(["issue", "view", String(issueNumber), "--json", "number,title,body,comments"], { cwd });
1862
+ const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments"], { cwd });
1578
1863
  const parsed = JSON.parse(output);
1579
1864
  if (typeof parsed?.title !== "string") {
1580
1865
  throw new Error(`Issue #${issueNumber}: unexpected response shape`);
@@ -1592,7 +1877,7 @@ function getIssue(issueNumber, cwd) {
1592
1877
  }
1593
1878
  function postIssueComment(issueNumber, body, cwd) {
1594
1879
  try {
1595
- gh(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: body, cwd });
1880
+ gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: body, cwd });
1596
1881
  } catch (err) {
1597
1882
  process.stderr.write(
1598
1883
  `[kody2] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -1605,7 +1890,7 @@ function truncate2(s, maxBytes) {
1605
1890
  return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
1606
1891
  }
1607
1892
  function getPr(prNumber, cwd) {
1608
- const output = gh(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
1893
+ const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
1609
1894
  cwd
1610
1895
  });
1611
1896
  const parsed = JSON.parse(output);
@@ -1623,7 +1908,7 @@ function getPr(prNumber, cwd) {
1623
1908
  }
1624
1909
  function getPrDiff(prNumber, cwd) {
1625
1910
  try {
1626
- return gh(["pr", "diff", String(prNumber)], { cwd });
1911
+ return gh2(["pr", "diff", String(prNumber)], { cwd });
1627
1912
  } catch (err) {
1628
1913
  process.stderr.write(
1629
1914
  `[kody2] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -1634,7 +1919,7 @@ function getPrDiff(prNumber, cwd) {
1634
1919
  }
1635
1920
  function getPrReviews(prNumber, cwd) {
1636
1921
  try {
1637
- const output = gh(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
1922
+ const output = gh2(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
1638
1923
  const parsed = JSON.parse(output);
1639
1924
  if (!Array.isArray(parsed?.reviews)) return [];
1640
1925
  return parsed.reviews.map(
@@ -1651,7 +1936,7 @@ function getPrReviews(prNumber, cwd) {
1651
1936
  }
1652
1937
  function getPrComments(prNumber, cwd) {
1653
1938
  try {
1654
- const output = gh(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
1939
+ const output = gh2(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
1655
1940
  const parsed = JSON.parse(output);
1656
1941
  if (!Array.isArray(parsed?.comments)) return [];
1657
1942
  return parsed.comments.map((c) => ({
@@ -1677,7 +1962,7 @@ function getPrLatestReviewBody(prNumber, cwd) {
1677
1962
  }
1678
1963
  function postPrReviewComment(prNumber, body, cwd) {
1679
1964
  try {
1680
- gh(["pr", "comment", String(prNumber), "--body-file", "-"], { input: body, cwd });
1965
+ gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: body, cwd });
1681
1966
  } catch (err) {
1682
1967
  process.stderr.write(
1683
1968
  `[kody2] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -1754,7 +2039,7 @@ function firstLine(s) {
1754
2039
  }
1755
2040
  function findExistingPr(branch, cwd) {
1756
2041
  try {
1757
- const output = gh(["pr", "view", branch, "--json", "number,url"], { cwd });
2042
+ const output = gh2(["pr", "view", branch, "--json", "number,url"], { cwd });
1758
2043
  const parsed = JSON.parse(output);
1759
2044
  if (typeof parsed?.number === "number" && typeof parsed?.url === "string") {
1760
2045
  return { number: parsed.number, url: parsed.url };
@@ -1770,7 +2055,7 @@ function ensurePr(opts) {
1770
2055
  const existing = findExistingPr(opts.branch, opts.cwd);
1771
2056
  if (existing) {
1772
2057
  try {
1773
- gh(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: opts.cwd });
2058
+ gh2(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: opts.cwd });
1774
2059
  } catch (err) {
1775
2060
  process.stderr.write(
1776
2061
  `[kody2] failed to update PR #${existing.number}: ${err instanceof Error ? err.message : String(err)}
@@ -1792,7 +2077,7 @@ function ensurePr(opts) {
1792
2077
  "-"
1793
2078
  ];
1794
2079
  if (opts.draft) args.push("--draft");
1795
- const output = gh(args, { input: body, cwd: opts.cwd });
2080
+ const output = gh2(args, { input: body, cwd: opts.cwd });
1796
2081
  const url = output.trim();
1797
2082
  const match = url.match(/\/pull\/(\d+)$/);
1798
2083
  const number = match ? parseInt(match[1], 10) : 0;
@@ -1850,8 +2135,43 @@ function computeFailureReason(ctx) {
1850
2135
  return "";
1851
2136
  }
1852
2137
 
2138
+ // src/scripts/finishFlow.ts
2139
+ import { execFileSync as execFileSync9 } from "child_process";
2140
+ var API_TIMEOUT_MS5 = 3e4;
2141
+ var STATUS_ICON = {
2142
+ "review-passed": "\u2705",
2143
+ "fix-applied": "\u2705",
2144
+ "review-failed": "\u26A0\uFE0F",
2145
+ aborted: "\u26A0\uFE0F"
2146
+ };
2147
+ var finishFlow = async (ctx, _profile, _agentResult, args) => {
2148
+ const reason = args?.reason ?? "completed";
2149
+ const issueNumber = ctx.args.issue;
2150
+ const state = ctx.data.taskState;
2151
+ const flowName = state?.flow?.name ?? "(unknown flow)";
2152
+ if (state) state.flow = void 0;
2153
+ if (!issueNumber) return;
2154
+ const icon = STATUS_ICON[reason] ?? "\u2139\uFE0F";
2155
+ const prSuffix = state?.core.prUrl ? `
2156
+
2157
+ **PR:** ${state.core.prUrl}` : "";
2158
+ const body = `${icon} kody2 flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
2159
+ try {
2160
+ execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
2161
+ timeout: API_TIMEOUT_MS5,
2162
+ cwd: ctx.cwd,
2163
+ stdio: ["ignore", "pipe", "pipe"]
2164
+ });
2165
+ } catch (err) {
2166
+ process.stderr.write(
2167
+ `[kody2 finishFlow] failed to post final summary on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
2168
+ `
2169
+ );
2170
+ }
2171
+ };
2172
+
1853
2173
  // src/branch.ts
1854
- import { execFileSync as execFileSync6 } from "child_process";
2174
+ import { execFileSync as execFileSync10 } from "child_process";
1855
2175
  var UncommittedChangesError = class extends Error {
1856
2176
  constructor(branch) {
1857
2177
  super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
@@ -1861,7 +2181,7 @@ var UncommittedChangesError = class extends Error {
1861
2181
  branch;
1862
2182
  };
1863
2183
  function git2(args, cwd) {
1864
- return execFileSync6("git", args, {
2184
+ return execFileSync10("git", args, {
1865
2185
  encoding: "utf-8",
1866
2186
  timeout: 3e4,
1867
2187
  cwd,
@@ -1886,7 +2206,7 @@ function checkoutPrBranch(prNumber, cwd) {
1886
2206
  SKIP_HOOKS: "1",
1887
2207
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
1888
2208
  };
1889
- execFileSync6("gh", ["pr", "checkout", String(prNumber)], {
2209
+ execFileSync10("gh", ["pr", "checkout", String(prNumber)], {
1890
2210
  cwd,
1891
2211
  env,
1892
2212
  stdio: ["ignore", "pipe", "pipe"],
@@ -1953,7 +2273,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
1953
2273
  }
1954
2274
 
1955
2275
  // src/gha.ts
1956
- import { execFileSync as execFileSync7 } from "child_process";
2276
+ import { execFileSync as execFileSync11 } from "child_process";
1957
2277
  import * as fs12 from "fs";
1958
2278
  function getRunUrl() {
1959
2279
  const server = process.env.GITHUB_SERVER_URL;
@@ -1977,7 +2297,7 @@ function reactToTriggerComment(cwd) {
1977
2297
  if (!commentId || !repo) return;
1978
2298
  const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
1979
2299
  try {
1980
- execFileSync7(
2300
+ execFileSync11(
1981
2301
  "gh",
1982
2302
  [
1983
2303
  "api",
@@ -2001,15 +2321,15 @@ function reactToTriggerComment(cwd) {
2001
2321
  }
2002
2322
 
2003
2323
  // src/workflow.ts
2004
- import { execFileSync as execFileSync8 } from "child_process";
2324
+ import { execFileSync as execFileSync12 } from "child_process";
2005
2325
  var GH_TIMEOUT_MS = 3e4;
2006
- function ghToken2() {
2326
+ function ghToken3() {
2007
2327
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
2008
2328
  }
2009
- function gh2(args, cwd) {
2010
- const token = ghToken2();
2329
+ function gh3(args, cwd) {
2330
+ const token = ghToken3();
2011
2331
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
2012
- return execFileSync8("gh", args, {
2332
+ return execFileSync12("gh", args, {
2013
2333
  encoding: "utf-8",
2014
2334
  timeout: GH_TIMEOUT_MS,
2015
2335
  cwd,
@@ -2020,14 +2340,14 @@ function gh2(args, cwd) {
2020
2340
  function getRecentFailedRunsForPr(prNumber, limit, cwd) {
2021
2341
  let headBranch;
2022
2342
  try {
2023
- const out = gh2(["pr", "view", String(prNumber), "--json", "headRefName"], cwd);
2343
+ const out = gh3(["pr", "view", String(prNumber), "--json", "headRefName"], cwd);
2024
2344
  headBranch = JSON.parse(out).headRefName;
2025
2345
  } catch {
2026
2346
  return [];
2027
2347
  }
2028
2348
  if (!headBranch) return [];
2029
2349
  try {
2030
- const out = gh2(
2350
+ const out = gh3(
2031
2351
  [
2032
2352
  "run",
2033
2353
  "list",
@@ -2058,7 +2378,7 @@ function getRecentFailedRunsForPr(prNumber, limit, cwd) {
2058
2378
  }
2059
2379
  function getFailedRunLogTail(runId, maxBytes, cwd) {
2060
2380
  try {
2061
- const raw = gh2(["run", "view", String(runId), "--log-failed"], cwd);
2381
+ const raw = gh3(["run", "view", String(runId), "--log-failed"], cwd);
2062
2382
  if (raw.length <= maxBytes) return raw;
2063
2383
  return raw.slice(-maxBytes);
2064
2384
  } catch {
@@ -2193,7 +2513,7 @@ function tryPostPr2(prNumber, body, cwd) {
2193
2513
  }
2194
2514
 
2195
2515
  // src/scripts/initFlow.ts
2196
- import { execFileSync as execFileSync9 } from "child_process";
2516
+ import { execFileSync as execFileSync13 } from "child_process";
2197
2517
  import * as fs14 from "fs";
2198
2518
  import * as path12 from "path";
2199
2519
 
@@ -2275,7 +2595,7 @@ function qualityCommandsFor(pm) {
2275
2595
  function detectOwnerRepo(cwd) {
2276
2596
  let url;
2277
2597
  try {
2278
- url = execFileSync9("git", ["remote", "get-url", "origin"], {
2598
+ url = execFileSync13("git", ["remote", "get-url", "origin"], {
2279
2599
  cwd,
2280
2600
  encoding: "utf-8",
2281
2601
  stdio: ["ignore", "pipe", "pipe"]
@@ -2359,7 +2679,7 @@ jobs:
2359
2679
  `;
2360
2680
  function defaultBranchFromGit(cwd) {
2361
2681
  try {
2362
- const ref = execFileSync9("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
2682
+ const ref = execFileSync13("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
2363
2683
  cwd,
2364
2684
  encoding: "utf-8",
2365
2685
  stdio: ["ignore", "pipe", "pipe"]
@@ -2367,7 +2687,7 @@ function defaultBranchFromGit(cwd) {
2367
2687
  return ref.replace("refs/remotes/origin/", "");
2368
2688
  } catch {
2369
2689
  try {
2370
- return execFileSync9("git", ["branch", "--show-current"], {
2690
+ return execFileSync13("git", ["branch", "--show-current"], {
2371
2691
  cwd,
2372
2692
  encoding: "utf-8",
2373
2693
  stdio: ["ignore", "pipe", "pipe"]
@@ -2507,214 +2827,48 @@ var loadIssueContext = async (ctx) => {
2507
2827
  ctx.data.commentTargetNumber = issueNumber;
2508
2828
  };
2509
2829
 
2510
- // src/state.ts
2511
- import { execFileSync as execFileSync10 } from "child_process";
2512
- var STATE_BEGIN = "<!-- kody2:state:v1:begin -->";
2513
- var STATE_END = "<!-- kody2:state:v1:end -->";
2514
- var HISTORY_MAX_ENTRIES = 20;
2515
- var API_TIMEOUT_MS2 = 3e4;
2516
- function emptyState() {
2517
- return {
2518
- schemaVersion: 1,
2519
- core: {
2520
- phase: "idle",
2521
- status: "pending",
2522
- currentExecutable: null,
2523
- lastOutcome: null,
2524
- attempts: {}
2525
- },
2526
- executables: {},
2527
- artifacts: {},
2528
- history: []
2529
- };
2530
- }
2531
- function ghToken3() {
2532
- return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
2533
- }
2534
- function gh3(args, input, cwd) {
2535
- const token = ghToken3();
2536
- const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
2537
- return execFileSync10("gh", args, {
2538
- encoding: "utf-8",
2539
- timeout: API_TIMEOUT_MS2,
2540
- cwd,
2541
- env,
2542
- input,
2543
- stdio: input ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"]
2544
- }).trim();
2545
- }
2546
- function findStateComment(target, number, cwd) {
2547
- const apiPath = target === "issue" ? `repos/{owner}/{repo}/issues/${number}/comments` : `repos/{owner}/{repo}/issues/${number}/comments`;
2548
- try {
2549
- const raw = gh3(["api", "--paginate", apiPath], void 0, cwd);
2550
- const list = JSON.parse(raw);
2551
- for (const c of list) {
2552
- if (c.body?.includes(STATE_BEGIN)) {
2553
- return { id: String(c.id), body: c.body };
2554
- }
2555
- }
2556
- } catch {
2830
+ // src/scripts/loadTaskState.ts
2831
+ var loadTaskState = async (ctx) => {
2832
+ const target = ctx.data.commentTargetType;
2833
+ const number = ctx.data.commentTargetNumber;
2834
+ if (!target || !number) {
2835
+ ctx.data.taskState = emptyState();
2836
+ return;
2557
2837
  }
2558
- return null;
2559
- }
2560
- function parseStateComment(body) {
2561
- const beginIdx = body.indexOf(STATE_BEGIN);
2562
- const endIdx = body.indexOf(STATE_END, beginIdx + 1);
2563
- if (beginIdx < 0 || endIdx < 0) return emptyState();
2564
- const between = body.slice(beginIdx + STATE_BEGIN.length, endIdx).trim();
2565
- const OPEN = "```json";
2566
- const CLOSE = "```";
2567
- if (!between.startsWith(OPEN) || !between.endsWith(CLOSE)) return emptyState();
2568
- const jsonStr = between.slice(OPEN.length, between.length - CLOSE.length).trim();
2838
+ ctx.data.taskState = readTaskState(target, number, ctx.cwd);
2839
+ };
2840
+
2841
+ // src/scripts/mirrorStateToPr.ts
2842
+ var mirrorStateToPr = async (ctx) => {
2843
+ const issueNumber = ctx.data.commentTargetNumber;
2844
+ const issueTarget = ctx.data.commentTargetType;
2845
+ if (!issueNumber || issueTarget !== "issue") return;
2846
+ const prUrl = ctx.output.prUrl ?? ctx.data.prResult?.url;
2847
+ if (!prUrl) return;
2848
+ const prNumber = parsePrNumber(prUrl);
2849
+ if (!prNumber) return;
2850
+ let state;
2569
2851
  try {
2570
- const parsed = JSON.parse(jsonStr);
2571
- if (parsed?.schemaVersion !== 1) return emptyState();
2572
- return {
2573
- schemaVersion: 1,
2574
- core: { ...emptyState().core, ...parsed.core },
2575
- executables: parsed.executables ?? {},
2576
- artifacts: parsed.artifacts && typeof parsed.artifacts === "object" ? parsed.artifacts : {},
2577
- history: Array.isArray(parsed.history) ? parsed.history : []
2578
- };
2852
+ state = readTaskState("issue", issueNumber, ctx.cwd);
2579
2853
  } catch {
2580
- return emptyState();
2581
- }
2582
- }
2583
- function reduce(state, executable, action) {
2584
- if (!action) return state;
2585
- const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
2586
- const newExecutables = {
2587
- ...state.executables,
2588
- [executable]: { ...state.executables[executable] ?? { lastAction: null }, lastAction: action }
2589
- };
2590
- const newHistory = [
2591
- ...state.history,
2592
- { timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action) }
2593
- ].slice(-HISTORY_MAX_ENTRIES);
2594
- return {
2595
- schemaVersion: 1,
2596
- core: {
2597
- ...state.core,
2598
- attempts: newAttempts,
2599
- lastOutcome: action,
2600
- currentExecutable: executable,
2601
- status: statusFromAction(action),
2602
- phase: phaseFromAction(executable, action)
2603
- },
2604
- executables: newExecutables,
2605
- artifacts: { ...state.artifacts ?? {} },
2606
- history: newHistory
2607
- };
2608
- }
2609
- function statusFromAction(action) {
2610
- if (/FAILED$|ERROR$|MISSING$|REJECTED$/i.test(action.type)) return "failed";
2611
- if (/COMPLETED$|SHIPPED$|MERGED$|SUCCESS$/i.test(action.type)) return "succeeded";
2612
- return "running";
2613
- }
2614
- function phaseFromAction(executable, action) {
2615
- if (/FAILED$|ERROR$|REJECTED$/i.test(action.type)) return "failed";
2616
- if (executable === "build") return statusFromAction(action) === "succeeded" ? "implementing" : "implementing";
2617
- if (executable === "review") return "reviewing";
2618
- if (executable === "release") return "shipped";
2619
- return "idle";
2620
- }
2621
- function noteFromAction(action) {
2622
- const p = action.payload;
2623
- if (typeof p?.prUrl === "string") return p.prUrl;
2624
- if (typeof p?.reason === "string") return p.reason.slice(0, 120);
2625
- if (typeof p?.commitMessage === "string") return p.commitMessage.slice(0, 120);
2626
- return void 0;
2627
- }
2628
- function renderStateComment(state) {
2629
- const lines = [];
2630
- lines.push(STATE_BEGIN);
2631
- lines.push("");
2632
- lines.push("```json");
2633
- lines.push(
2634
- JSON.stringify(
2635
- {
2636
- schemaVersion: state.schemaVersion,
2637
- core: state.core,
2638
- artifacts: state.artifacts ?? {},
2639
- executables: state.executables,
2640
- history: state.history
2641
- },
2642
- null,
2643
- 2
2644
- )
2645
- );
2646
- lines.push("```");
2647
- lines.push("");
2648
- lines.push(STATE_END);
2649
- lines.push("");
2650
- lines.push("## kody2 task state");
2651
- lines.push("");
2652
- lines.push(`- **Phase:** \`${state.core.phase}\` **Status:** \`${state.core.status}\``);
2653
- if (state.core.currentExecutable) {
2654
- lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
2655
- }
2656
- if (state.core.lastOutcome) {
2657
- lines.push(`- **Last action:** \`${state.core.lastOutcome.type}\``);
2658
- }
2659
- const attempts = Object.entries(state.core.attempts).map(([k, v]) => `${k}:${v}`).join(", ");
2660
- if (attempts) lines.push(`- **Attempts:** ${attempts}`);
2661
- if (state.core.prUrl) lines.push(`- **PR:** ${state.core.prUrl}`);
2662
- if (state.core.runUrl) lines.push(`- **Run:** ${state.core.runUrl}`);
2663
- const artifactNames = Object.keys(state.artifacts ?? {});
2664
- if (artifactNames.length > 0) {
2665
- lines.push(`- **Artifacts:** ${artifactNames.map((n) => `\`${n}\``).join(", ")}`);
2666
- }
2667
- lines.push("");
2668
- if (state.history.length > 0) {
2669
- lines.push("### Recent history");
2670
- lines.push("");
2671
- const recent = state.history.slice(-10).reverse();
2672
- for (const h of recent) {
2673
- const note = h.note ? ` \u2014 ${h.note}` : "";
2674
- lines.push(`- \`${h.timestamp}\` **${h.executable}** \u2192 \`${h.action}\`${note}`);
2675
- }
2676
- lines.push("");
2854
+ return;
2677
2855
  }
2678
- return lines.join("\n");
2679
- }
2680
- function readTaskState(target, number, cwd) {
2681
- const existing = findStateComment(target, number, cwd);
2682
- return existing ? parseStateComment(existing.body) : emptyState();
2683
- }
2684
- function setArtifact(state, name, artifact) {
2685
- return {
2686
- ...state,
2687
- artifacts: { ...state.artifacts ?? {}, [name]: artifact }
2688
- };
2689
- }
2690
- function writeTaskState(target, number, state, cwd) {
2691
- const body = renderStateComment(state);
2692
- const existing = findStateComment(target, number, cwd);
2856
+ if (prUrl && !state.core.prUrl) state.core.prUrl = prUrl;
2693
2857
  try {
2694
- if (existing) {
2695
- gh3(["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"], body, cwd);
2696
- } else {
2697
- const sub = target === "issue" ? "issue" : "pr";
2698
- gh3([sub, "comment", String(number), "--body-file", "-"], body, cwd);
2699
- }
2858
+ writeTaskState("pr", prNumber, state, ctx.cwd);
2700
2859
  } catch (err) {
2701
2860
  process.stderr.write(
2702
- `[kody2 state] failed to write state on ${target} #${number}: ${err instanceof Error ? err.message : String(err)}
2861
+ `[kody2 mirrorStateToPr] failed to mirror state to PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
2703
2862
  `
2704
2863
  );
2705
2864
  }
2706
- }
2707
-
2708
- // src/scripts/loadTaskState.ts
2709
- var loadTaskState = async (ctx) => {
2710
- const target = ctx.data.commentTargetType;
2711
- const number = ctx.data.commentTargetNumber;
2712
- if (!target || !number) {
2713
- ctx.data.taskState = emptyState();
2714
- return;
2715
- }
2716
- ctx.data.taskState = readTaskState(target, number, ctx.cwd);
2717
2865
  };
2866
+ function parsePrNumber(prUrl) {
2867
+ const m = prUrl.match(/\/pull\/(\d+)(?:[/?#]|$)/);
2868
+ if (!m) return null;
2869
+ const n = parseInt(m[1], 10);
2870
+ return Number.isFinite(n) ? n : null;
2871
+ }
2718
2872
 
2719
2873
  // src/scripts/parseAgentResult.ts
2720
2874
  var parseAgentResult2 = async (ctx, profile, agentResult) => {
@@ -2938,7 +3092,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
2938
3092
  };
2939
3093
 
2940
3094
  // src/scripts/releaseFlow.ts
2941
- import { execFileSync as execFileSync11, spawnSync } from "child_process";
3095
+ import { execFileSync as execFileSync14, spawnSync } from "child_process";
2942
3096
  import * as fs15 from "fs";
2943
3097
  import * as path13 from "path";
2944
3098
  function bumpVersion(current, bump) {
@@ -2968,7 +3122,7 @@ function generateChangelog(cwd, newVersion, lastTag) {
2968
3122
  const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
2969
3123
  let log = "";
2970
3124
  try {
2971
- log = execFileSync11("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
3125
+ log = execFileSync14("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
2972
3126
  cwd,
2973
3127
  encoding: "utf-8",
2974
3128
  stdio: ["ignore", "pipe", "pipe"]
@@ -3025,7 +3179,7 @@ ${entry}${prior.slice(idx + 1)}`);
3025
3179
  }
3026
3180
  }
3027
3181
  function git3(args, cwd, timeout = 6e4) {
3028
- return execFileSync11("git", args, {
3182
+ return execFileSync14("git", args, {
3029
3183
  encoding: "utf-8",
3030
3184
  timeout,
3031
3185
  cwd,
@@ -3132,7 +3286,7 @@ ${entry}
3132
3286
  Merge this and then run \`kody2 release --mode finalize\`.`;
3133
3287
  let prUrl = "";
3134
3288
  try {
3135
- prUrl = gh(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
3289
+ prUrl = gh2(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
3136
3290
  input: body,
3137
3291
  cwd
3138
3292
  }).trim();
@@ -3209,7 +3363,7 @@ ${truncate2(r.stderr, 2e3)}
3209
3363
  try {
3210
3364
  const releaseArgs = ["release", "create", tag, "--title", tag, "--notes", `Release ${tag} \u2014 automated by kody2.`];
3211
3365
  if (releaseCfg.draftRelease) releaseArgs.push("--draft");
3212
- releaseUrl = gh(releaseArgs, { cwd }).trim();
3366
+ releaseUrl = gh2(releaseArgs, { cwd }).trim();
3213
3367
  } catch (err) {
3214
3368
  process.stderr.write(
3215
3369
  `[kody2 release] gh release create failed: ${err instanceof Error ? err.message : String(err)}
@@ -3328,7 +3482,7 @@ var resolveArtifacts = async (ctx, profile) => {
3328
3482
  };
3329
3483
 
3330
3484
  // src/scripts/resolveFlow.ts
3331
- import { execFileSync as execFileSync12 } from "child_process";
3485
+ import { execFileSync as execFileSync15 } from "child_process";
3332
3486
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
3333
3487
  var resolveFlow = async (ctx) => {
3334
3488
  const prNumber = ctx.args.pr;
@@ -3380,7 +3534,7 @@ var resolveFlow = async (ctx) => {
3380
3534
  };
3381
3535
  function getConflictedFiles(cwd) {
3382
3536
  try {
3383
- const out = execFileSync12("git", ["diff", "--name-only", "--diff-filter=U"], {
3537
+ const out = execFileSync15("git", ["diff", "--name-only", "--diff-filter=U"], {
3384
3538
  encoding: "utf-8",
3385
3539
  cwd,
3386
3540
  env: { ...process.env, HUSKY: "0" }
@@ -3395,7 +3549,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
3395
3549
  let total = 0;
3396
3550
  for (const f of files) {
3397
3551
  try {
3398
- const content = execFileSync12("cat", [f], { encoding: "utf-8", cwd }).toString();
3552
+ const content = execFileSync15("cat", [f], { encoding: "utf-8", cwd }).toString();
3399
3553
  const snippet = `### ${f}
3400
3554
 
3401
3555
  \`\`\`
@@ -3504,8 +3658,67 @@ function synthesizeAction(ctx) {
3504
3658
  };
3505
3659
  }
3506
3660
 
3661
+ // src/scripts/skipAgent.ts
3662
+ var skipAgent = async (ctx) => {
3663
+ ctx.skipAgent = true;
3664
+ };
3665
+
3666
+ // src/scripts/startFlow.ts
3667
+ import { execFileSync as execFileSync16 } from "child_process";
3668
+ var API_TIMEOUT_MS6 = 3e4;
3669
+ var startFlow = async (ctx, _profile, _agentResult, args) => {
3670
+ const entry = args?.entry;
3671
+ if (!entry) {
3672
+ process.stderr.write("[kody2 startFlow] missing `with.entry` \u2014 skipping\n");
3673
+ return;
3674
+ }
3675
+ const target = args?.target ?? "issue";
3676
+ const flowName = ctx.args.flow ?? "default";
3677
+ const issueNumber = ctx.args.issue;
3678
+ if (!issueNumber) {
3679
+ process.stderr.write("[kody2 startFlow] no --issue arg \u2014 skipping\n");
3680
+ return;
3681
+ }
3682
+ const state = ctx.data.taskState;
3683
+ if (state?.flow) {
3684
+ return;
3685
+ }
3686
+ if (state) {
3687
+ state.flow = {
3688
+ name: flowName,
3689
+ step: entry,
3690
+ issueNumber,
3691
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
3692
+ };
3693
+ }
3694
+ postKody2Comment(target, issueNumber, state, entry, ctx.cwd);
3695
+ };
3696
+ function postKody2Comment(target, issueNumber, state, next, cwd) {
3697
+ const targetNumber = target === "pr" && state?.core.prUrl ? parsePr2(state.core.prUrl) ?? issueNumber : issueNumber;
3698
+ const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
3699
+ const body = `@kody2 ${next}`;
3700
+ try {
3701
+ execFileSync16("gh", [sub, "comment", String(targetNumber), "--body", body], {
3702
+ timeout: API_TIMEOUT_MS6,
3703
+ cwd,
3704
+ stdio: ["ignore", "pipe", "pipe"]
3705
+ });
3706
+ } catch (err) {
3707
+ process.stderr.write(
3708
+ `[kody2 startFlow] failed to post @kody2 ${next} on ${sub} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
3709
+ `
3710
+ );
3711
+ }
3712
+ }
3713
+ function parsePr2(url) {
3714
+ const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
3715
+ if (!m) return null;
3716
+ const n = parseInt(m[1], 10);
3717
+ return Number.isFinite(n) ? n : null;
3718
+ }
3719
+
3507
3720
  // src/scripts/syncFlow.ts
3508
- import { execFileSync as execFileSync13 } from "child_process";
3721
+ import { execFileSync as execFileSync17 } from "child_process";
3509
3722
  var syncFlow = async (ctx) => {
3510
3723
  ctx.skipAgent = true;
3511
3724
  const prNumber = ctx.args.pr;
@@ -3564,7 +3777,7 @@ function bail2(ctx, prNumber, reason) {
3564
3777
  }
3565
3778
  function revParseHead(cwd) {
3566
3779
  try {
3567
- return execFileSync13("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
3780
+ return execFileSync17("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
3568
3781
  } catch {
3569
3782
  return "";
3570
3783
  }
@@ -3572,9 +3785,9 @@ function revParseHead(cwd) {
3572
3785
  function pushBranch(branch, cwd) {
3573
3786
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
3574
3787
  try {
3575
- execFileSync13("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
3788
+ execFileSync17("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
3576
3789
  } catch {
3577
- execFileSync13("git", ["push", "--force-with-lease", "-u", "origin", branch], {
3790
+ execFileSync17("git", ["push", "--force-with-lease", "-u", "origin", branch], {
3578
3791
  cwd,
3579
3792
  env,
3580
3793
  stdio: ["ignore", "pipe", "pipe"]
@@ -3685,7 +3898,7 @@ function readWatchConfig(ctx) {
3685
3898
  function findStalePrs(cwd, staleDays, now = /* @__PURE__ */ new Date()) {
3686
3899
  let raw = "";
3687
3900
  try {
3688
- raw = gh(["pr", "list", "--state", "open", "--limit", "100", "--json", "number,title,url,updatedAt"], { cwd });
3901
+ raw = gh2(["pr", "list", "--state", "open", "--limit", "100", "--json", "number,title,url,updatedAt"], { cwd });
3689
3902
  } catch {
3690
3903
  return [];
3691
3904
  }
@@ -3787,7 +4000,8 @@ var preflightScripts = {
3787
4000
  loadCoverageRules,
3788
4001
  buildSyntheticPlugin,
3789
4002
  resolveArtifacts,
3790
- composePrompt
4003
+ composePrompt,
4004
+ skipAgent
3791
4005
  };
3792
4006
  var postflightScripts = {
3793
4007
  parseAgentResult: parseAgentResult2,
@@ -3803,7 +4017,12 @@ var postflightScripts = {
3803
4017
  postReviewResult,
3804
4018
  persistArtifacts,
3805
4019
  writeRunSummary,
3806
- saveTaskState
4020
+ saveTaskState,
4021
+ mirrorStateToPr,
4022
+ startFlow,
4023
+ dispatch,
4024
+ finishFlow,
4025
+ advanceFlow
3807
4026
  };
3808
4027
  var allScriptNames = /* @__PURE__ */ new Set([
3809
4028
  ...Object.keys(preflightScripts),
@@ -3811,7 +4030,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
3811
4030
  ]);
3812
4031
 
3813
4032
  // src/tools.ts
3814
- import { execFileSync as execFileSync14 } from "child_process";
4033
+ import { execFileSync as execFileSync18 } from "child_process";
3815
4034
  function verifyCliTools(tools, cwd) {
3816
4035
  const out = [];
3817
4036
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -3844,7 +4063,7 @@ function verifyOne(tool, cwd) {
3844
4063
  }
3845
4064
  function runShell2(cmd, cwd, timeoutMs = 3e4) {
3846
4065
  try {
3847
- execFileSync14("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
4066
+ execFileSync18("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
3848
4067
  return true;
3849
4068
  } catch {
3850
4069
  return false;
@@ -3941,7 +4160,7 @@ async function runExecutable(profileName, input) {
3941
4160
  if (!shouldRun(entry, ctx)) continue;
3942
4161
  const fn = preflightScripts[entry.script];
3943
4162
  if (!fn) return finish({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
3944
- await fn(ctx, profile);
4163
+ await fn(ctx, profile, entry.with);
3945
4164
  if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
3946
4165
  return finish(ctx.output);
3947
4166
  }
@@ -3959,7 +4178,7 @@ async function runExecutable(profileName, input) {
3959
4178
  const fn = postflightScripts[entry.script];
3960
4179
  if (!fn) return finish({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
3961
4180
  try {
3962
- await fn(ctx, profile, agentResult);
4181
+ await fn(ctx, profile, agentResult, entry.with);
3963
4182
  } catch (err) {
3964
4183
  const msg = err instanceof Error ? err.message : String(err);
3965
4184
  process.stderr.write(`[kody2] postflight script "${entry.script}" crashed: ${msg}
@@ -4174,7 +4393,7 @@ function detectPackageManager2(cwd) {
4174
4393
  }
4175
4394
  function shellOut(cmd, args, cwd, stream = true) {
4176
4395
  try {
4177
- execFileSync15(cmd, args, {
4396
+ execFileSync19(cmd, args, {
4178
4397
  cwd,
4179
4398
  stdio: stream ? "inherit" : "pipe",
4180
4399
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -4187,7 +4406,7 @@ function shellOut(cmd, args, cwd, stream = true) {
4187
4406
  }
4188
4407
  function isOnPath(bin) {
4189
4408
  try {
4190
- execFileSync15("which", [bin], { stdio: "pipe" });
4409
+ execFileSync19("which", [bin], { stdio: "pipe" });
4191
4410
  return true;
4192
4411
  } catch {
4193
4412
  return false;
@@ -4221,7 +4440,7 @@ function installLitellmIfNeeded(cwd) {
4221
4440
  } catch {
4222
4441
  }
4223
4442
  try {
4224
- execFileSync15("python3", ["-c", "import litellm"], { stdio: "pipe" });
4443
+ execFileSync19("python3", ["-c", "import litellm"], { stdio: "pipe" });
4225
4444
  process.stdout.write("\u2192 kody2: litellm already installed\n");
4226
4445
  return 0;
4227
4446
  } catch {
@@ -4231,16 +4450,16 @@ function installLitellmIfNeeded(cwd) {
4231
4450
  }
4232
4451
  function configureGitIdentity(cwd) {
4233
4452
  try {
4234
- const name = execFileSync15("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
4453
+ const name = execFileSync19("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
4235
4454
  if (name) return;
4236
4455
  } catch {
4237
4456
  }
4238
4457
  try {
4239
- execFileSync15("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
4458
+ execFileSync19("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
4240
4459
  } catch {
4241
4460
  }
4242
4461
  try {
4243
- execFileSync15("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
4462
+ execFileSync19("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
4244
4463
  cwd,
4245
4464
  stdio: "pipe"
4246
4465
  });
@@ -4296,13 +4515,13 @@ async function runCi(argv) {
4296
4515
  ${CI_HELP}`);
4297
4516
  return 64;
4298
4517
  }
4299
- const dispatch = autoFallback ?? {
4518
+ const dispatch2 = autoFallback ?? {
4300
4519
  executable: "run",
4301
4520
  cliArgs: { issue: args.issueNumber },
4302
4521
  target: args.issueNumber
4303
4522
  };
4304
- const issueNumber = dispatch.target;
4305
- process.stdout.write(`\u2192 kody2 preflight (cwd=${cwd}, executable=${dispatch.executable}, target=${issueNumber})
4523
+ const issueNumber = dispatch2.target;
4524
+ process.stdout.write(`\u2192 kody2 preflight (cwd=${cwd}, executable=${dispatch2.executable}, target=${issueNumber})
4306
4525
  `);
4307
4526
  try {
4308
4527
  const n = unpackAllSecrets();
@@ -4339,13 +4558,13 @@ ${CI_HELP}`);
4339
4558
  postFailureTail(issueNumber, cwd, `preflight crashed: ${msg}`);
4340
4559
  return 99;
4341
4560
  }
4342
- process.stdout.write(`\u2192 kody2: preflight done, handing off to kody2 ${dispatch.executable}
4561
+ process.stdout.write(`\u2192 kody2: preflight done, handing off to kody2 ${dispatch2.executable}
4343
4562
 
4344
4563
  `);
4345
4564
  try {
4346
4565
  const config = earlyConfig ?? loadConfig(cwd);
4347
- const result = await runExecutable(dispatch.executable, {
4348
- cliArgs: dispatch.cliArgs,
4566
+ const result = await runExecutable(dispatch2.executable, {
4567
+ cliArgs: dispatch2.cliArgs,
4349
4568
  cwd,
4350
4569
  config,
4351
4570
  verbose: args.verbose,
@@ -4417,9 +4636,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
4417
4636
  if (paths.length === 0) return;
4418
4637
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
4419
4638
  try {
4420
- execFileSync16("git", ["add", ...paths], opts);
4421
- execFileSync16("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
4422
- execFileSync16("git", ["push", "--quiet", "origin", "HEAD"], opts);
4639
+ execFileSync20("git", ["add", ...paths], opts);
4640
+ execFileSync20("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
4641
+ execFileSync20("git", ["push", "--quiet", "origin", "HEAD"], opts);
4423
4642
  } catch (err) {
4424
4643
  const msg = err instanceof Error ? err.message : String(err);
4425
4644
  process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
@@ -83,6 +83,9 @@
83
83
  },
84
84
  {
85
85
  "script": "saveTaskState"
86
+ },
87
+ {
88
+ "script": "advanceFlow"
86
89
  }
87
90
  ]
88
91
  },
@@ -59,6 +59,9 @@
59
59
  },
60
60
  {
61
61
  "script": "saveTaskState"
62
+ },
63
+ {
64
+ "script": "advanceFlow"
62
65
  }
63
66
  ]
64
67
  },
@@ -50,6 +50,9 @@
50
50
  },
51
51
  {
52
52
  "script": "saveTaskState"
53
+ },
54
+ {
55
+ "script": "advanceFlow"
53
56
  }
54
57
  ]
55
58
  }
@@ -79,6 +79,12 @@
79
79
  },
80
80
  {
81
81
  "script": "saveTaskState"
82
+ },
83
+ {
84
+ "script": "mirrorStateToPr"
85
+ },
86
+ {
87
+ "script": "advanceFlow"
82
88
  }
83
89
  ]
84
90
  },
@@ -140,6 +140,12 @@ export interface ScriptEntry {
140
140
  * The script runs only when every key matches. Missing `runWhen` = always.
141
141
  */
142
142
  runWhen?: Record<string, string | number | boolean | Array<string | number | boolean>>
143
+ /**
144
+ * Optional per-call arguments passed to the script as the last positional
145
+ * parameter. Used by the orchestrator's transition table so the same
146
+ * dispatcher script can be reused with different `next` targets.
147
+ */
148
+ with?: Record<string, string | number | boolean>
143
149
  }
144
150
 
145
151
  export interface OutputContract {
@@ -183,9 +189,16 @@ export interface Context {
183
189
  // Script signatures. Two phases, two contracts.
184
190
  // ────────────────────────────────────────────────────────────────────────────
185
191
 
186
- export type PreflightScript = (ctx: Context, profile: Profile) => Promise<void>
192
+ export type ScriptArgs = Record<string, string | number | boolean>
193
+
194
+ export type PreflightScript = (ctx: Context, profile: Profile, args?: ScriptArgs) => Promise<void>
187
195
 
188
- export type PostflightScript = (ctx: Context, profile: Profile, agentResult: AgentResult | null) => Promise<void>
196
+ export type PostflightScript = (
197
+ ctx: Context,
198
+ profile: Profile,
199
+ agentResult: AgentResult | null,
200
+ args?: ScriptArgs,
201
+ ) => Promise<void>
189
202
 
190
203
  /** A registered script may be either phase; registry looks it up by name. */
191
204
  export type AnyScript = PreflightScript | PostflightScript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.2.44",
3
+ "version": "0.2.46",
4
4
  "description": "kody2 — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",