@kody-ade/kody-engine 0.2.45 → 0.2.47

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.45",
6
+ version: "0.2.47",
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
 
@@ -591,7 +591,19 @@ function autoDispatch(opts) {
591
591
  return asDispatch(defaultExec, targetNum);
592
592
  }
593
593
  if (sub === "orchestrate" || sub === "orchestrator") {
594
- return { executable: "orchestrator", cliArgs: { issue: targetNum }, target: targetNum };
594
+ const flow = extractFlowName(afterTag);
595
+ if (flow) {
596
+ return {
597
+ executable: `orchestrator-${flow}`,
598
+ cliArgs: { issue: targetNum, flow },
599
+ target: targetNum
600
+ };
601
+ }
602
+ return {
603
+ executable: "orchestrator-plan-build-review",
604
+ cliArgs: { issue: targetNum, flow: "plan-build-review" },
605
+ target: targetNum
606
+ };
595
607
  }
596
608
  if (sub === "build") {
597
609
  return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
@@ -611,6 +623,10 @@ function extractSubcommand(afterTag) {
611
623
  if (!match) return null;
612
624
  return match[1];
613
625
  }
626
+ function extractFlowName(afterTag) {
627
+ const match = afterTag.match(/--flow[=\s]+([a-z][a-z0-9-]{0,60})/);
628
+ return match ? match[1] : null;
629
+ }
614
630
  function extractFeedback(afterTag) {
615
631
  const cleaned = afterTag.replace(/^(fix|please|kindly)(?:[\s:,.-]+|$)/i, "").trim();
616
632
  return cleaned.length > 0 ? cleaned : void 0;
@@ -948,11 +964,256 @@ function parseScriptList(p, key, raw) {
948
964
  if (r.runWhen && typeof r.runWhen === "object") {
949
965
  entry.runWhen = r.runWhen;
950
966
  }
967
+ if (r.with && typeof r.with === "object") {
968
+ entry.with = r.with;
969
+ }
951
970
  out.push(entry);
952
971
  }
953
972
  return out;
954
973
  }
955
974
 
975
+ // src/scripts/advanceFlow.ts
976
+ import { execFileSync as execFileSync3 } from "child_process";
977
+
978
+ // src/state.ts
979
+ import { execFileSync as execFileSync2 } from "child_process";
980
+ var STATE_BEGIN = "<!-- kody2:state:v1:begin -->";
981
+ var STATE_END = "<!-- kody2:state:v1:end -->";
982
+ var HISTORY_MAX_ENTRIES = 20;
983
+ var API_TIMEOUT_MS = 3e4;
984
+ function emptyState() {
985
+ return {
986
+ schemaVersion: 1,
987
+ core: {
988
+ phase: "idle",
989
+ status: "pending",
990
+ currentExecutable: null,
991
+ lastOutcome: null,
992
+ attempts: {}
993
+ },
994
+ executables: {},
995
+ artifacts: {},
996
+ history: []
997
+ };
998
+ }
999
+ function ghToken() {
1000
+ return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
1001
+ }
1002
+ function gh(args, input, cwd) {
1003
+ const token = ghToken();
1004
+ const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
1005
+ return execFileSync2("gh", args, {
1006
+ encoding: "utf-8",
1007
+ timeout: API_TIMEOUT_MS,
1008
+ cwd,
1009
+ env,
1010
+ input,
1011
+ stdio: input ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"]
1012
+ }).trim();
1013
+ }
1014
+ function findStateComment(target, number, cwd) {
1015
+ const apiPath = target === "issue" ? `repos/{owner}/{repo}/issues/${number}/comments` : `repos/{owner}/{repo}/issues/${number}/comments`;
1016
+ try {
1017
+ const raw = gh(["api", "--paginate", apiPath], void 0, cwd);
1018
+ const list = JSON.parse(raw);
1019
+ for (const c of list) {
1020
+ if (c.body?.includes(STATE_BEGIN)) {
1021
+ return { id: String(c.id), body: c.body };
1022
+ }
1023
+ }
1024
+ } catch {
1025
+ }
1026
+ return null;
1027
+ }
1028
+ function parseStateComment(body) {
1029
+ const beginIdx = body.indexOf(STATE_BEGIN);
1030
+ const endIdx = body.indexOf(STATE_END, beginIdx + 1);
1031
+ if (beginIdx < 0 || endIdx < 0) return emptyState();
1032
+ const between = body.slice(beginIdx + STATE_BEGIN.length, endIdx).trim();
1033
+ const OPEN = "```json";
1034
+ const CLOSE = "```";
1035
+ if (!between.startsWith(OPEN) || !between.endsWith(CLOSE)) return emptyState();
1036
+ const jsonStr = between.slice(OPEN.length, between.length - CLOSE.length).trim();
1037
+ try {
1038
+ const parsed = JSON.parse(jsonStr);
1039
+ if (parsed?.schemaVersion !== 1) return emptyState();
1040
+ return {
1041
+ schemaVersion: 1,
1042
+ core: { ...emptyState().core, ...parsed.core },
1043
+ executables: parsed.executables ?? {},
1044
+ artifacts: parsed.artifacts && typeof parsed.artifacts === "object" ? parsed.artifacts : {},
1045
+ history: Array.isArray(parsed.history) ? parsed.history : [],
1046
+ flow: parsed.flow
1047
+ };
1048
+ } catch {
1049
+ return emptyState();
1050
+ }
1051
+ }
1052
+ function reduce(state, executable, action) {
1053
+ if (!action) return state;
1054
+ const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
1055
+ const newExecutables = {
1056
+ ...state.executables,
1057
+ [executable]: { ...state.executables[executable] ?? { lastAction: null }, lastAction: action }
1058
+ };
1059
+ const newHistory = [
1060
+ ...state.history,
1061
+ { timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action) }
1062
+ ].slice(-HISTORY_MAX_ENTRIES);
1063
+ return {
1064
+ schemaVersion: 1,
1065
+ core: {
1066
+ ...state.core,
1067
+ attempts: newAttempts,
1068
+ lastOutcome: action,
1069
+ currentExecutable: executable,
1070
+ status: statusFromAction(action),
1071
+ phase: phaseFromAction(executable, action)
1072
+ },
1073
+ executables: newExecutables,
1074
+ artifacts: { ...state.artifacts ?? {} },
1075
+ history: newHistory,
1076
+ flow: state.flow
1077
+ };
1078
+ }
1079
+ function statusFromAction(action) {
1080
+ if (/FAILED$|ERROR$|MISSING$|REJECTED$/i.test(action.type)) return "failed";
1081
+ if (/COMPLETED$|SHIPPED$|MERGED$|SUCCESS$/i.test(action.type)) return "succeeded";
1082
+ return "running";
1083
+ }
1084
+ function phaseFromAction(executable, action) {
1085
+ if (/FAILED$|ERROR$|REJECTED$/i.test(action.type)) return "failed";
1086
+ if (executable === "build") return statusFromAction(action) === "succeeded" ? "implementing" : "implementing";
1087
+ if (executable === "review") return "reviewing";
1088
+ if (executable === "release") return "shipped";
1089
+ return "idle";
1090
+ }
1091
+ function noteFromAction(action) {
1092
+ const p = action.payload;
1093
+ if (typeof p?.prUrl === "string") return p.prUrl;
1094
+ if (typeof p?.reason === "string") return p.reason.slice(0, 120);
1095
+ if (typeof p?.commitMessage === "string") return p.commitMessage.slice(0, 120);
1096
+ return void 0;
1097
+ }
1098
+ function renderStateComment(state) {
1099
+ const lines = [];
1100
+ lines.push(STATE_BEGIN);
1101
+ lines.push("");
1102
+ lines.push("```json");
1103
+ lines.push(
1104
+ JSON.stringify(
1105
+ {
1106
+ schemaVersion: state.schemaVersion,
1107
+ core: state.core,
1108
+ artifacts: state.artifacts ?? {},
1109
+ executables: state.executables,
1110
+ history: state.history,
1111
+ ...state.flow ? { flow: state.flow } : {}
1112
+ },
1113
+ null,
1114
+ 2
1115
+ )
1116
+ );
1117
+ lines.push("```");
1118
+ lines.push("");
1119
+ lines.push(STATE_END);
1120
+ lines.push("");
1121
+ lines.push("## kody2 task state");
1122
+ lines.push("");
1123
+ lines.push(`- **Phase:** \`${state.core.phase}\` **Status:** \`${state.core.status}\``);
1124
+ if (state.core.currentExecutable) {
1125
+ lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
1126
+ }
1127
+ if (state.core.lastOutcome) {
1128
+ lines.push(`- **Last action:** \`${state.core.lastOutcome.type}\``);
1129
+ }
1130
+ const attempts = Object.entries(state.core.attempts).map(([k, v]) => `${k}:${v}`).join(", ");
1131
+ if (attempts) lines.push(`- **Attempts:** ${attempts}`);
1132
+ if (state.core.prUrl) lines.push(`- **PR:** ${state.core.prUrl}`);
1133
+ if (state.core.runUrl) lines.push(`- **Run:** ${state.core.runUrl}`);
1134
+ const artifactNames = Object.keys(state.artifacts ?? {});
1135
+ if (artifactNames.length > 0) {
1136
+ lines.push(`- **Artifacts:** ${artifactNames.map((n) => `\`${n}\``).join(", ")}`);
1137
+ }
1138
+ lines.push("");
1139
+ if (state.history.length > 0) {
1140
+ lines.push("### Recent history");
1141
+ lines.push("");
1142
+ const recent = state.history.slice(-10).reverse();
1143
+ for (const h of recent) {
1144
+ const note = h.note ? ` \u2014 ${h.note}` : "";
1145
+ lines.push(`- \`${h.timestamp}\` **${h.executable}** \u2192 \`${h.action}\`${note}`);
1146
+ }
1147
+ lines.push("");
1148
+ }
1149
+ return lines.join("\n");
1150
+ }
1151
+ function readTaskState(target, number, cwd) {
1152
+ const existing = findStateComment(target, number, cwd);
1153
+ return existing ? parseStateComment(existing.body) : emptyState();
1154
+ }
1155
+ function setArtifact(state, name, artifact) {
1156
+ return {
1157
+ ...state,
1158
+ artifacts: { ...state.artifacts ?? {}, [name]: artifact }
1159
+ };
1160
+ }
1161
+ function writeTaskState(target, number, state, cwd) {
1162
+ const body = renderStateComment(state);
1163
+ const existing = findStateComment(target, number, cwd);
1164
+ try {
1165
+ if (existing) {
1166
+ gh(["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"], body, cwd);
1167
+ } else {
1168
+ const sub = target === "issue" ? "issue" : "pr";
1169
+ gh([sub, "comment", String(number), "--body-file", "-"], body, cwd);
1170
+ }
1171
+ } catch (err) {
1172
+ process.stderr.write(
1173
+ `[kody2 state] failed to write state on ${target} #${number}: ${err instanceof Error ? err.message : String(err)}
1174
+ `
1175
+ );
1176
+ }
1177
+ }
1178
+
1179
+ // src/scripts/advanceFlow.ts
1180
+ var API_TIMEOUT_MS2 = 3e4;
1181
+ var advanceFlow = async (ctx, profile) => {
1182
+ const state = ctx.data.taskState;
1183
+ const flow = state?.flow;
1184
+ if (!flow?.issueNumber) return;
1185
+ const targetType = ctx.data.commentTargetType;
1186
+ const action = ctx.data.action;
1187
+ if (targetType === "pr" && action) {
1188
+ try {
1189
+ const issueState = readTaskState("issue", flow.issueNumber, ctx.cwd);
1190
+ issueState.flow = flow;
1191
+ const next = reduce(issueState, profile.name, action);
1192
+ if (state?.core.prUrl && !next.core.prUrl) next.core.prUrl = state.core.prUrl;
1193
+ next.flow = flow;
1194
+ writeTaskState("issue", flow.issueNumber, next, ctx.cwd);
1195
+ } catch (err) {
1196
+ process.stderr.write(
1197
+ `[kody2 advanceFlow] failed to mirror action to issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
1198
+ `
1199
+ );
1200
+ }
1201
+ }
1202
+ const body = `@kody2 orchestrate --flow ${flow.name}`;
1203
+ try {
1204
+ execFileSync3("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
1205
+ timeout: API_TIMEOUT_MS2,
1206
+ cwd: ctx.cwd,
1207
+ stdio: ["ignore", "pipe", "pipe"]
1208
+ });
1209
+ } catch (err) {
1210
+ process.stderr.write(
1211
+ `[kody2 advanceFlow] failed to re-trigger orchestrator on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
1212
+ `
1213
+ );
1214
+ }
1215
+ };
1216
+
956
1217
  // src/scripts/buildSyntheticPlugin.ts
957
1218
  import * as fs8 from "fs";
958
1219
  import * as os2 from "os";
@@ -1047,7 +1308,7 @@ function copyDir(src, dst) {
1047
1308
  }
1048
1309
 
1049
1310
  // src/coverage.ts
1050
- import { execFileSync as execFileSync2 } from "child_process";
1311
+ import { execFileSync as execFileSync4 } from "child_process";
1051
1312
  function patternToRegex(pattern) {
1052
1313
  let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1053
1314
  s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
@@ -1065,7 +1326,7 @@ function renderSiblingPath(file, requireSibling) {
1065
1326
  }
1066
1327
  function safeGit(args, cwd) {
1067
1328
  try {
1068
- return execFileSync2("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
1329
+ return execFileSync4("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
1069
1330
  } catch {
1070
1331
  return "";
1071
1332
  }
@@ -1236,10 +1497,10 @@ ${formatMissesForFeedback(misses)}`;
1236
1497
  };
1237
1498
 
1238
1499
  // src/scripts/commitAndPush.ts
1239
- import { execFileSync as execFileSync4 } from "child_process";
1500
+ import { execFileSync as execFileSync6 } from "child_process";
1240
1501
 
1241
1502
  // src/commit.ts
1242
- import { execFileSync as execFileSync3 } from "child_process";
1503
+ import { execFileSync as execFileSync5 } from "child_process";
1243
1504
  import * as fs10 from "fs";
1244
1505
  import * as path9 from "path";
1245
1506
  var FORBIDDEN_PATH_PREFIXES = [
@@ -1269,7 +1530,7 @@ var CONVENTIONAL_PREFIXES = [
1269
1530
  ];
1270
1531
  function git(args, cwd) {
1271
1532
  try {
1272
- return execFileSync3("git", args, {
1533
+ return execFileSync5("git", args, {
1273
1534
  encoding: "utf-8",
1274
1535
  timeout: 12e4,
1275
1536
  cwd,
@@ -1327,7 +1588,7 @@ function isForbiddenPath(p) {
1327
1588
  return false;
1328
1589
  }
1329
1590
  function listChangedFiles(cwd) {
1330
- const raw = execFileSync3("git", ["status", "--porcelain=v1", "-z"], {
1591
+ const raw = execFileSync5("git", ["status", "--porcelain=v1", "-z"], {
1331
1592
  encoding: "utf-8",
1332
1593
  cwd,
1333
1594
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1339,7 +1600,7 @@ function listChangedFiles(cwd) {
1339
1600
  }
1340
1601
  function listFilesInCommit(ref = "HEAD", cwd) {
1341
1602
  try {
1342
- const raw = execFileSync3("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1603
+ const raw = execFileSync5("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1343
1604
  encoding: "utf-8",
1344
1605
  cwd,
1345
1606
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1419,7 +1680,7 @@ var commitAndPush2 = async (ctx, profile) => {
1419
1680
  const kind = profile.name;
1420
1681
  if (kind === "resolve") {
1421
1682
  try {
1422
- execFileSync4("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
1683
+ execFileSync6("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
1423
1684
  } catch {
1424
1685
  }
1425
1686
  } else {
@@ -1555,18 +1816,61 @@ function formatToolsUsage(profile) {
1555
1816
  return lines.join("\n");
1556
1817
  }
1557
1818
 
1819
+ // src/scripts/dispatch.ts
1820
+ import { execFileSync as execFileSync7 } from "child_process";
1821
+ var API_TIMEOUT_MS3 = 3e4;
1822
+ var dispatch = async (ctx, _profile, _agentResult, args) => {
1823
+ const next = args?.next;
1824
+ if (!next) {
1825
+ process.stderr.write("[kody2 dispatch] missing `with.next` \u2014 skipping\n");
1826
+ return;
1827
+ }
1828
+ const target = args?.target ?? "issue";
1829
+ const issueNumber = ctx.args.issue;
1830
+ if (!issueNumber) {
1831
+ process.stderr.write("[kody2 dispatch] no --issue arg \u2014 skipping\n");
1832
+ return;
1833
+ }
1834
+ const state = ctx.data.taskState;
1835
+ if (state?.flow) {
1836
+ state.flow.step = next;
1837
+ }
1838
+ const usePr = target === "pr" && state?.core.prUrl;
1839
+ const targetNumber = usePr ? parsePr(state.core.prUrl) ?? issueNumber : issueNumber;
1840
+ const sub = usePr ? "pr" : "issue";
1841
+ const body = `@kody2 ${next}`;
1842
+ try {
1843
+ execFileSync7("gh", [sub, "comment", String(targetNumber), "--body", body], {
1844
+ timeout: API_TIMEOUT_MS3,
1845
+ cwd: ctx.cwd,
1846
+ stdio: ["ignore", "pipe", "pipe"]
1847
+ });
1848
+ } catch (err) {
1849
+ process.stderr.write(
1850
+ `[kody2 dispatch] failed to post @kody2 ${next} on ${sub} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
1851
+ `
1852
+ );
1853
+ }
1854
+ };
1855
+ function parsePr(url) {
1856
+ const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
1857
+ if (!m) return null;
1858
+ const n = parseInt(m[1], 10);
1859
+ return Number.isFinite(n) ? n : null;
1860
+ }
1861
+
1558
1862
  // src/issue.ts
1559
- import { execFileSync as execFileSync5 } from "child_process";
1560
- var API_TIMEOUT_MS = 3e4;
1561
- function ghToken() {
1863
+ import { execFileSync as execFileSync8 } from "child_process";
1864
+ var API_TIMEOUT_MS4 = 3e4;
1865
+ function ghToken2() {
1562
1866
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
1563
1867
  }
1564
- function gh(args, options) {
1565
- const token = ghToken();
1868
+ function gh2(args, options) {
1869
+ const token = ghToken2();
1566
1870
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
1567
- return execFileSync5("gh", args, {
1871
+ return execFileSync8("gh", args, {
1568
1872
  encoding: "utf-8",
1569
- timeout: API_TIMEOUT_MS,
1873
+ timeout: API_TIMEOUT_MS4,
1570
1874
  cwd: options?.cwd,
1571
1875
  env,
1572
1876
  input: options?.input,
@@ -1574,7 +1878,7 @@ function gh(args, options) {
1574
1878
  }).trim();
1575
1879
  }
1576
1880
  function getIssue(issueNumber, cwd) {
1577
- const output = gh(["issue", "view", String(issueNumber), "--json", "number,title,body,comments"], { cwd });
1881
+ const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments"], { cwd });
1578
1882
  const parsed = JSON.parse(output);
1579
1883
  if (typeof parsed?.title !== "string") {
1580
1884
  throw new Error(`Issue #${issueNumber}: unexpected response shape`);
@@ -1592,7 +1896,7 @@ function getIssue(issueNumber, cwd) {
1592
1896
  }
1593
1897
  function postIssueComment(issueNumber, body, cwd) {
1594
1898
  try {
1595
- gh(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: body, cwd });
1899
+ gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: body, cwd });
1596
1900
  } catch (err) {
1597
1901
  process.stderr.write(
1598
1902
  `[kody2] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -1605,7 +1909,7 @@ function truncate2(s, maxBytes) {
1605
1909
  return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
1606
1910
  }
1607
1911
  function getPr(prNumber, cwd) {
1608
- const output = gh(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
1912
+ const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
1609
1913
  cwd
1610
1914
  });
1611
1915
  const parsed = JSON.parse(output);
@@ -1623,7 +1927,7 @@ function getPr(prNumber, cwd) {
1623
1927
  }
1624
1928
  function getPrDiff(prNumber, cwd) {
1625
1929
  try {
1626
- return gh(["pr", "diff", String(prNumber)], { cwd });
1930
+ return gh2(["pr", "diff", String(prNumber)], { cwd });
1627
1931
  } catch (err) {
1628
1932
  process.stderr.write(
1629
1933
  `[kody2] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -1634,7 +1938,7 @@ function getPrDiff(prNumber, cwd) {
1634
1938
  }
1635
1939
  function getPrReviews(prNumber, cwd) {
1636
1940
  try {
1637
- const output = gh(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
1941
+ const output = gh2(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
1638
1942
  const parsed = JSON.parse(output);
1639
1943
  if (!Array.isArray(parsed?.reviews)) return [];
1640
1944
  return parsed.reviews.map(
@@ -1651,7 +1955,7 @@ function getPrReviews(prNumber, cwd) {
1651
1955
  }
1652
1956
  function getPrComments(prNumber, cwd) {
1653
1957
  try {
1654
- const output = gh(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
1958
+ const output = gh2(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
1655
1959
  const parsed = JSON.parse(output);
1656
1960
  if (!Array.isArray(parsed?.comments)) return [];
1657
1961
  return parsed.comments.map((c) => ({
@@ -1677,7 +1981,7 @@ function getPrLatestReviewBody(prNumber, cwd) {
1677
1981
  }
1678
1982
  function postPrReviewComment(prNumber, body, cwd) {
1679
1983
  try {
1680
- gh(["pr", "comment", String(prNumber), "--body-file", "-"], { input: body, cwd });
1984
+ gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: body, cwd });
1681
1985
  } catch (err) {
1682
1986
  process.stderr.write(
1683
1987
  `[kody2] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -1754,7 +2058,7 @@ function firstLine(s) {
1754
2058
  }
1755
2059
  function findExistingPr(branch, cwd) {
1756
2060
  try {
1757
- const output = gh(["pr", "view", branch, "--json", "number,url"], { cwd });
2061
+ const output = gh2(["pr", "view", branch, "--json", "number,url"], { cwd });
1758
2062
  const parsed = JSON.parse(output);
1759
2063
  if (typeof parsed?.number === "number" && typeof parsed?.url === "string") {
1760
2064
  return { number: parsed.number, url: parsed.url };
@@ -1770,7 +2074,7 @@ function ensurePr(opts) {
1770
2074
  const existing = findExistingPr(opts.branch, opts.cwd);
1771
2075
  if (existing) {
1772
2076
  try {
1773
- gh(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: opts.cwd });
2077
+ gh2(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: opts.cwd });
1774
2078
  } catch (err) {
1775
2079
  process.stderr.write(
1776
2080
  `[kody2] failed to update PR #${existing.number}: ${err instanceof Error ? err.message : String(err)}
@@ -1792,7 +2096,7 @@ function ensurePr(opts) {
1792
2096
  "-"
1793
2097
  ];
1794
2098
  if (opts.draft) args.push("--draft");
1795
- const output = gh(args, { input: body, cwd: opts.cwd });
2099
+ const output = gh2(args, { input: body, cwd: opts.cwd });
1796
2100
  const url = output.trim();
1797
2101
  const match = url.match(/\/pull\/(\d+)$/);
1798
2102
  const number = match ? parseInt(match[1], 10) : 0;
@@ -1850,8 +2154,43 @@ function computeFailureReason(ctx) {
1850
2154
  return "";
1851
2155
  }
1852
2156
 
2157
+ // src/scripts/finishFlow.ts
2158
+ import { execFileSync as execFileSync9 } from "child_process";
2159
+ var API_TIMEOUT_MS5 = 3e4;
2160
+ var STATUS_ICON = {
2161
+ "review-passed": "\u2705",
2162
+ "fix-applied": "\u2705",
2163
+ "review-failed": "\u26A0\uFE0F",
2164
+ aborted: "\u26A0\uFE0F"
2165
+ };
2166
+ var finishFlow = async (ctx, _profile, _agentResult, args) => {
2167
+ const reason = args?.reason ?? "completed";
2168
+ const issueNumber = ctx.args.issue;
2169
+ const state = ctx.data.taskState;
2170
+ const flowName = state?.flow?.name ?? "(unknown flow)";
2171
+ if (state) state.flow = void 0;
2172
+ if (!issueNumber) return;
2173
+ const icon = STATUS_ICON[reason] ?? "\u2139\uFE0F";
2174
+ const prSuffix = state?.core.prUrl ? `
2175
+
2176
+ **PR:** ${state.core.prUrl}` : "";
2177
+ const body = `${icon} kody2 flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
2178
+ try {
2179
+ execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
2180
+ timeout: API_TIMEOUT_MS5,
2181
+ cwd: ctx.cwd,
2182
+ stdio: ["ignore", "pipe", "pipe"]
2183
+ });
2184
+ } catch (err) {
2185
+ process.stderr.write(
2186
+ `[kody2 finishFlow] failed to post final summary on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
2187
+ `
2188
+ );
2189
+ }
2190
+ };
2191
+
1853
2192
  // src/branch.ts
1854
- import { execFileSync as execFileSync6 } from "child_process";
2193
+ import { execFileSync as execFileSync10 } from "child_process";
1855
2194
  var UncommittedChangesError = class extends Error {
1856
2195
  constructor(branch) {
1857
2196
  super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
@@ -1861,7 +2200,7 @@ var UncommittedChangesError = class extends Error {
1861
2200
  branch;
1862
2201
  };
1863
2202
  function git2(args, cwd) {
1864
- return execFileSync6("git", args, {
2203
+ return execFileSync10("git", args, {
1865
2204
  encoding: "utf-8",
1866
2205
  timeout: 3e4,
1867
2206
  cwd,
@@ -1886,7 +2225,7 @@ function checkoutPrBranch(prNumber, cwd) {
1886
2225
  SKIP_HOOKS: "1",
1887
2226
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
1888
2227
  };
1889
- execFileSync6("gh", ["pr", "checkout", String(prNumber)], {
2228
+ execFileSync10("gh", ["pr", "checkout", String(prNumber)], {
1890
2229
  cwd,
1891
2230
  env,
1892
2231
  stdio: ["ignore", "pipe", "pipe"],
@@ -1953,7 +2292,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
1953
2292
  }
1954
2293
 
1955
2294
  // src/gha.ts
1956
- import { execFileSync as execFileSync7 } from "child_process";
2295
+ import { execFileSync as execFileSync11 } from "child_process";
1957
2296
  import * as fs12 from "fs";
1958
2297
  function getRunUrl() {
1959
2298
  const server = process.env.GITHUB_SERVER_URL;
@@ -1977,7 +2316,7 @@ function reactToTriggerComment(cwd) {
1977
2316
  if (!commentId || !repo) return;
1978
2317
  const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
1979
2318
  try {
1980
- execFileSync7(
2319
+ execFileSync11(
1981
2320
  "gh",
1982
2321
  [
1983
2322
  "api",
@@ -2001,15 +2340,15 @@ function reactToTriggerComment(cwd) {
2001
2340
  }
2002
2341
 
2003
2342
  // src/workflow.ts
2004
- import { execFileSync as execFileSync8 } from "child_process";
2343
+ import { execFileSync as execFileSync12 } from "child_process";
2005
2344
  var GH_TIMEOUT_MS = 3e4;
2006
- function ghToken2() {
2345
+ function ghToken3() {
2007
2346
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
2008
2347
  }
2009
- function gh2(args, cwd) {
2010
- const token = ghToken2();
2348
+ function gh3(args, cwd) {
2349
+ const token = ghToken3();
2011
2350
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
2012
- return execFileSync8("gh", args, {
2351
+ return execFileSync12("gh", args, {
2013
2352
  encoding: "utf-8",
2014
2353
  timeout: GH_TIMEOUT_MS,
2015
2354
  cwd,
@@ -2020,14 +2359,14 @@ function gh2(args, cwd) {
2020
2359
  function getRecentFailedRunsForPr(prNumber, limit, cwd) {
2021
2360
  let headBranch;
2022
2361
  try {
2023
- const out = gh2(["pr", "view", String(prNumber), "--json", "headRefName"], cwd);
2362
+ const out = gh3(["pr", "view", String(prNumber), "--json", "headRefName"], cwd);
2024
2363
  headBranch = JSON.parse(out).headRefName;
2025
2364
  } catch {
2026
2365
  return [];
2027
2366
  }
2028
2367
  if (!headBranch) return [];
2029
2368
  try {
2030
- const out = gh2(
2369
+ const out = gh3(
2031
2370
  [
2032
2371
  "run",
2033
2372
  "list",
@@ -2058,7 +2397,7 @@ function getRecentFailedRunsForPr(prNumber, limit, cwd) {
2058
2397
  }
2059
2398
  function getFailedRunLogTail(runId, maxBytes, cwd) {
2060
2399
  try {
2061
- const raw = gh2(["run", "view", String(runId), "--log-failed"], cwd);
2400
+ const raw = gh3(["run", "view", String(runId), "--log-failed"], cwd);
2062
2401
  if (raw.length <= maxBytes) return raw;
2063
2402
  return raw.slice(-maxBytes);
2064
2403
  } catch {
@@ -2193,7 +2532,7 @@ function tryPostPr2(prNumber, body, cwd) {
2193
2532
  }
2194
2533
 
2195
2534
  // src/scripts/initFlow.ts
2196
- import { execFileSync as execFileSync9 } from "child_process";
2535
+ import { execFileSync as execFileSync13 } from "child_process";
2197
2536
  import * as fs14 from "fs";
2198
2537
  import * as path12 from "path";
2199
2538
 
@@ -2275,7 +2614,7 @@ function qualityCommandsFor(pm) {
2275
2614
  function detectOwnerRepo(cwd) {
2276
2615
  let url;
2277
2616
  try {
2278
- url = execFileSync9("git", ["remote", "get-url", "origin"], {
2617
+ url = execFileSync13("git", ["remote", "get-url", "origin"], {
2279
2618
  cwd,
2280
2619
  encoding: "utf-8",
2281
2620
  stdio: ["ignore", "pipe", "pipe"]
@@ -2359,7 +2698,7 @@ jobs:
2359
2698
  `;
2360
2699
  function defaultBranchFromGit(cwd) {
2361
2700
  try {
2362
- const ref = execFileSync9("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
2701
+ const ref = execFileSync13("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
2363
2702
  cwd,
2364
2703
  encoding: "utf-8",
2365
2704
  stdio: ["ignore", "pipe", "pipe"]
@@ -2367,7 +2706,7 @@ function defaultBranchFromGit(cwd) {
2367
2706
  return ref.replace("refs/remotes/origin/", "");
2368
2707
  } catch {
2369
2708
  try {
2370
- return execFileSync9("git", ["branch", "--show-current"], {
2709
+ return execFileSync13("git", ["branch", "--show-current"], {
2371
2710
  cwd,
2372
2711
  encoding: "utf-8",
2373
2712
  stdio: ["ignore", "pipe", "pipe"]
@@ -2507,204 +2846,6 @@ var loadIssueContext = async (ctx) => {
2507
2846
  ctx.data.commentTargetNumber = issueNumber;
2508
2847
  };
2509
2848
 
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 {
2557
- }
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();
2569
- 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
- };
2579
- } 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("");
2677
- }
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);
2693
- 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
- }
2700
- } catch (err) {
2701
- process.stderr.write(
2702
- `[kody2 state] failed to write state on ${target} #${number}: ${err instanceof Error ? err.message : String(err)}
2703
- `
2704
- );
2705
- }
2706
- }
2707
-
2708
2849
  // src/scripts/loadTaskState.ts
2709
2850
  var loadTaskState = async (ctx) => {
2710
2851
  const target = ctx.data.commentTargetType;
@@ -2809,6 +2950,22 @@ function readDottedString(source, dotted) {
2809
2950
  return String(cur);
2810
2951
  }
2811
2952
 
2953
+ // src/scripts/persistFlowState.ts
2954
+ var persistFlowState = async (ctx) => {
2955
+ const state = ctx.data.taskState;
2956
+ if (!state) return;
2957
+ const issueNumber = ctx.args.issue ?? state.flow?.issueNumber;
2958
+ if (!issueNumber) return;
2959
+ try {
2960
+ writeTaskState("issue", issueNumber, state, ctx.cwd);
2961
+ } catch (err) {
2962
+ process.stderr.write(
2963
+ `[kody2 persistFlowState] failed to write state on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
2964
+ `
2965
+ );
2966
+ }
2967
+ };
2968
+
2812
2969
  // src/scripts/postIssueComment.ts
2813
2970
  var postIssueComment2 = async (ctx) => {
2814
2971
  if (ctx.skipAgent && ctx.output.exitCode !== void 0) return;
@@ -2970,7 +3127,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
2970
3127
  };
2971
3128
 
2972
3129
  // src/scripts/releaseFlow.ts
2973
- import { execFileSync as execFileSync11, spawnSync } from "child_process";
3130
+ import { execFileSync as execFileSync14, spawnSync } from "child_process";
2974
3131
  import * as fs15 from "fs";
2975
3132
  import * as path13 from "path";
2976
3133
  function bumpVersion(current, bump) {
@@ -3000,7 +3157,7 @@ function generateChangelog(cwd, newVersion, lastTag) {
3000
3157
  const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
3001
3158
  let log = "";
3002
3159
  try {
3003
- log = execFileSync11("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
3160
+ log = execFileSync14("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
3004
3161
  cwd,
3005
3162
  encoding: "utf-8",
3006
3163
  stdio: ["ignore", "pipe", "pipe"]
@@ -3057,7 +3214,7 @@ ${entry}${prior.slice(idx + 1)}`);
3057
3214
  }
3058
3215
  }
3059
3216
  function git3(args, cwd, timeout = 6e4) {
3060
- return execFileSync11("git", args, {
3217
+ return execFileSync14("git", args, {
3061
3218
  encoding: "utf-8",
3062
3219
  timeout,
3063
3220
  cwd,
@@ -3164,7 +3321,7 @@ ${entry}
3164
3321
  Merge this and then run \`kody2 release --mode finalize\`.`;
3165
3322
  let prUrl = "";
3166
3323
  try {
3167
- prUrl = gh(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
3324
+ prUrl = gh2(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
3168
3325
  input: body,
3169
3326
  cwd
3170
3327
  }).trim();
@@ -3241,7 +3398,7 @@ ${truncate2(r.stderr, 2e3)}
3241
3398
  try {
3242
3399
  const releaseArgs = ["release", "create", tag, "--title", tag, "--notes", `Release ${tag} \u2014 automated by kody2.`];
3243
3400
  if (releaseCfg.draftRelease) releaseArgs.push("--draft");
3244
- releaseUrl = gh(releaseArgs, { cwd }).trim();
3401
+ releaseUrl = gh2(releaseArgs, { cwd }).trim();
3245
3402
  } catch (err) {
3246
3403
  process.stderr.write(
3247
3404
  `[kody2 release] gh release create failed: ${err instanceof Error ? err.message : String(err)}
@@ -3360,7 +3517,7 @@ var resolveArtifacts = async (ctx, profile) => {
3360
3517
  };
3361
3518
 
3362
3519
  // src/scripts/resolveFlow.ts
3363
- import { execFileSync as execFileSync12 } from "child_process";
3520
+ import { execFileSync as execFileSync15 } from "child_process";
3364
3521
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
3365
3522
  var resolveFlow = async (ctx) => {
3366
3523
  const prNumber = ctx.args.pr;
@@ -3412,7 +3569,7 @@ var resolveFlow = async (ctx) => {
3412
3569
  };
3413
3570
  function getConflictedFiles(cwd) {
3414
3571
  try {
3415
- const out = execFileSync12("git", ["diff", "--name-only", "--diff-filter=U"], {
3572
+ const out = execFileSync15("git", ["diff", "--name-only", "--diff-filter=U"], {
3416
3573
  encoding: "utf-8",
3417
3574
  cwd,
3418
3575
  env: { ...process.env, HUSKY: "0" }
@@ -3427,7 +3584,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
3427
3584
  let total = 0;
3428
3585
  for (const f of files) {
3429
3586
  try {
3430
- const content = execFileSync12("cat", [f], { encoding: "utf-8", cwd }).toString();
3587
+ const content = execFileSync15("cat", [f], { encoding: "utf-8", cwd }).toString();
3431
3588
  const snippet = `### ${f}
3432
3589
 
3433
3590
  \`\`\`
@@ -3541,8 +3698,62 @@ var skipAgent = async (ctx) => {
3541
3698
  ctx.skipAgent = true;
3542
3699
  };
3543
3700
 
3701
+ // src/scripts/startFlow.ts
3702
+ import { execFileSync as execFileSync16 } from "child_process";
3703
+ var API_TIMEOUT_MS6 = 3e4;
3704
+ var startFlow = async (ctx, _profile, _agentResult, args) => {
3705
+ const entry = args?.entry;
3706
+ if (!entry) {
3707
+ process.stderr.write("[kody2 startFlow] missing `with.entry` \u2014 skipping\n");
3708
+ return;
3709
+ }
3710
+ const target = args?.target ?? "issue";
3711
+ const flowName = ctx.args.flow ?? "default";
3712
+ const issueNumber = ctx.args.issue;
3713
+ if (!issueNumber) {
3714
+ process.stderr.write("[kody2 startFlow] no --issue arg \u2014 skipping\n");
3715
+ return;
3716
+ }
3717
+ const state = ctx.data.taskState;
3718
+ if (state?.flow) {
3719
+ return;
3720
+ }
3721
+ if (state) {
3722
+ state.flow = {
3723
+ name: flowName,
3724
+ step: entry,
3725
+ issueNumber,
3726
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
3727
+ };
3728
+ }
3729
+ postKody2Comment(target, issueNumber, state, entry, ctx.cwd);
3730
+ };
3731
+ function postKody2Comment(target, issueNumber, state, next, cwd) {
3732
+ const targetNumber = target === "pr" && state?.core.prUrl ? parsePr2(state.core.prUrl) ?? issueNumber : issueNumber;
3733
+ const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
3734
+ const body = `@kody2 ${next}`;
3735
+ try {
3736
+ execFileSync16("gh", [sub, "comment", String(targetNumber), "--body", body], {
3737
+ timeout: API_TIMEOUT_MS6,
3738
+ cwd,
3739
+ stdio: ["ignore", "pipe", "pipe"]
3740
+ });
3741
+ } catch (err) {
3742
+ process.stderr.write(
3743
+ `[kody2 startFlow] failed to post @kody2 ${next} on ${sub} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
3744
+ `
3745
+ );
3746
+ }
3747
+ }
3748
+ function parsePr2(url) {
3749
+ const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
3750
+ if (!m) return null;
3751
+ const n = parseInt(m[1], 10);
3752
+ return Number.isFinite(n) ? n : null;
3753
+ }
3754
+
3544
3755
  // src/scripts/syncFlow.ts
3545
- import { execFileSync as execFileSync13 } from "child_process";
3756
+ import { execFileSync as execFileSync17 } from "child_process";
3546
3757
  var syncFlow = async (ctx) => {
3547
3758
  ctx.skipAgent = true;
3548
3759
  const prNumber = ctx.args.pr;
@@ -3601,7 +3812,7 @@ function bail2(ctx, prNumber, reason) {
3601
3812
  }
3602
3813
  function revParseHead(cwd) {
3603
3814
  try {
3604
- return execFileSync13("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
3815
+ return execFileSync17("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
3605
3816
  } catch {
3606
3817
  return "";
3607
3818
  }
@@ -3609,9 +3820,9 @@ function revParseHead(cwd) {
3609
3820
  function pushBranch(branch, cwd) {
3610
3821
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
3611
3822
  try {
3612
- execFileSync13("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
3823
+ execFileSync17("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
3613
3824
  } catch {
3614
- execFileSync13("git", ["push", "--force-with-lease", "-u", "origin", branch], {
3825
+ execFileSync17("git", ["push", "--force-with-lease", "-u", "origin", branch], {
3615
3826
  cwd,
3616
3827
  env,
3617
3828
  stdio: ["ignore", "pipe", "pipe"]
@@ -3722,7 +3933,7 @@ function readWatchConfig(ctx) {
3722
3933
  function findStalePrs(cwd, staleDays, now = /* @__PURE__ */ new Date()) {
3723
3934
  let raw = "";
3724
3935
  try {
3725
- raw = gh(["pr", "list", "--state", "open", "--limit", "100", "--json", "number,title,url,updatedAt"], { cwd });
3936
+ raw = gh2(["pr", "list", "--state", "open", "--limit", "100", "--json", "number,title,url,updatedAt"], { cwd });
3726
3937
  } catch {
3727
3938
  return [];
3728
3939
  }
@@ -3842,7 +4053,12 @@ var postflightScripts = {
3842
4053
  persistArtifacts,
3843
4054
  writeRunSummary,
3844
4055
  saveTaskState,
3845
- mirrorStateToPr
4056
+ mirrorStateToPr,
4057
+ startFlow,
4058
+ dispatch,
4059
+ finishFlow,
4060
+ advanceFlow,
4061
+ persistFlowState
3846
4062
  };
3847
4063
  var allScriptNames = /* @__PURE__ */ new Set([
3848
4064
  ...Object.keys(preflightScripts),
@@ -3850,7 +4066,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
3850
4066
  ]);
3851
4067
 
3852
4068
  // src/tools.ts
3853
- import { execFileSync as execFileSync14 } from "child_process";
4069
+ import { execFileSync as execFileSync18 } from "child_process";
3854
4070
  function verifyCliTools(tools, cwd) {
3855
4071
  const out = [];
3856
4072
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -3883,7 +4099,7 @@ function verifyOne(tool, cwd) {
3883
4099
  }
3884
4100
  function runShell2(cmd, cwd, timeoutMs = 3e4) {
3885
4101
  try {
3886
- execFileSync14("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
4102
+ execFileSync18("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
3887
4103
  return true;
3888
4104
  } catch {
3889
4105
  return false;
@@ -3980,7 +4196,7 @@ async function runExecutable(profileName, input) {
3980
4196
  if (!shouldRun(entry, ctx)) continue;
3981
4197
  const fn = preflightScripts[entry.script];
3982
4198
  if (!fn) return finish({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
3983
- await fn(ctx, profile);
4199
+ await fn(ctx, profile, entry.with);
3984
4200
  if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
3985
4201
  return finish(ctx.output);
3986
4202
  }
@@ -3998,7 +4214,7 @@ async function runExecutable(profileName, input) {
3998
4214
  const fn = postflightScripts[entry.script];
3999
4215
  if (!fn) return finish({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
4000
4216
  try {
4001
- await fn(ctx, profile, agentResult);
4217
+ await fn(ctx, profile, agentResult, entry.with);
4002
4218
  } catch (err) {
4003
4219
  const msg = err instanceof Error ? err.message : String(err);
4004
4220
  process.stderr.write(`[kody2] postflight script "${entry.script}" crashed: ${msg}
@@ -4213,7 +4429,7 @@ function detectPackageManager2(cwd) {
4213
4429
  }
4214
4430
  function shellOut(cmd, args, cwd, stream = true) {
4215
4431
  try {
4216
- execFileSync15(cmd, args, {
4432
+ execFileSync19(cmd, args, {
4217
4433
  cwd,
4218
4434
  stdio: stream ? "inherit" : "pipe",
4219
4435
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -4226,7 +4442,7 @@ function shellOut(cmd, args, cwd, stream = true) {
4226
4442
  }
4227
4443
  function isOnPath(bin) {
4228
4444
  try {
4229
- execFileSync15("which", [bin], { stdio: "pipe" });
4445
+ execFileSync19("which", [bin], { stdio: "pipe" });
4230
4446
  return true;
4231
4447
  } catch {
4232
4448
  return false;
@@ -4260,7 +4476,7 @@ function installLitellmIfNeeded(cwd) {
4260
4476
  } catch {
4261
4477
  }
4262
4478
  try {
4263
- execFileSync15("python3", ["-c", "import litellm"], { stdio: "pipe" });
4479
+ execFileSync19("python3", ["-c", "import litellm"], { stdio: "pipe" });
4264
4480
  process.stdout.write("\u2192 kody2: litellm already installed\n");
4265
4481
  return 0;
4266
4482
  } catch {
@@ -4270,16 +4486,16 @@ function installLitellmIfNeeded(cwd) {
4270
4486
  }
4271
4487
  function configureGitIdentity(cwd) {
4272
4488
  try {
4273
- const name = execFileSync15("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
4489
+ const name = execFileSync19("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
4274
4490
  if (name) return;
4275
4491
  } catch {
4276
4492
  }
4277
4493
  try {
4278
- execFileSync15("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
4494
+ execFileSync19("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
4279
4495
  } catch {
4280
4496
  }
4281
4497
  try {
4282
- execFileSync15("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
4498
+ execFileSync19("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
4283
4499
  cwd,
4284
4500
  stdio: "pipe"
4285
4501
  });
@@ -4335,13 +4551,13 @@ async function runCi(argv) {
4335
4551
  ${CI_HELP}`);
4336
4552
  return 64;
4337
4553
  }
4338
- const dispatch = autoFallback ?? {
4554
+ const dispatch2 = autoFallback ?? {
4339
4555
  executable: "run",
4340
4556
  cliArgs: { issue: args.issueNumber },
4341
4557
  target: args.issueNumber
4342
4558
  };
4343
- const issueNumber = dispatch.target;
4344
- process.stdout.write(`\u2192 kody2 preflight (cwd=${cwd}, executable=${dispatch.executable}, target=${issueNumber})
4559
+ const issueNumber = dispatch2.target;
4560
+ process.stdout.write(`\u2192 kody2 preflight (cwd=${cwd}, executable=${dispatch2.executable}, target=${issueNumber})
4345
4561
  `);
4346
4562
  try {
4347
4563
  const n = unpackAllSecrets();
@@ -4378,13 +4594,13 @@ ${CI_HELP}`);
4378
4594
  postFailureTail(issueNumber, cwd, `preflight crashed: ${msg}`);
4379
4595
  return 99;
4380
4596
  }
4381
- process.stdout.write(`\u2192 kody2: preflight done, handing off to kody2 ${dispatch.executable}
4597
+ process.stdout.write(`\u2192 kody2: preflight done, handing off to kody2 ${dispatch2.executable}
4382
4598
 
4383
4599
  `);
4384
4600
  try {
4385
4601
  const config = earlyConfig ?? loadConfig(cwd);
4386
- const result = await runExecutable(dispatch.executable, {
4387
- cliArgs: dispatch.cliArgs,
4602
+ const result = await runExecutable(dispatch2.executable, {
4603
+ cliArgs: dispatch2.cliArgs,
4388
4604
  cwd,
4389
4605
  config,
4390
4606
  verbose: args.verbose,
@@ -4456,9 +4672,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
4456
4672
  if (paths.length === 0) return;
4457
4673
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
4458
4674
  try {
4459
- execFileSync16("git", ["add", ...paths], opts);
4460
- execFileSync16("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
4461
- execFileSync16("git", ["push", "--quiet", "origin", "HEAD"], opts);
4675
+ execFileSync20("git", ["add", ...paths], opts);
4676
+ execFileSync20("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
4677
+ execFileSync20("git", ["push", "--quiet", "origin", "HEAD"], opts);
4462
4678
  } catch (err) {
4463
4679
  const msg = err instanceof Error ? err.message : String(err);
4464
4680
  process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}