@kody-ade/kody-engine 0.2.61 → 0.2.63

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.61",
6
+ version: "0.2.63",
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 execFileSync23 } from "child_process";
53
+ import { execFileSync as execFileSync21 } from "child_process";
54
54
  import * as fs22 from "fs";
55
55
  import * as path19 from "path";
56
56
 
@@ -181,7 +181,7 @@ function loadConfig(projectDir = process.cwd()) {
181
181
  },
182
182
  issueContext: parseIssueContext(raw.issueContext),
183
183
  testRequirements: parseTestRequirements(raw.testRequirements),
184
- defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable : void 0,
184
+ defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable : "classify",
185
185
  classify: parseClassifyConfig(raw.classify),
186
186
  release: parseReleaseConfig(raw.release)
187
187
  };
@@ -295,7 +295,7 @@ ${truncate(text, 4e3)}`);
295
295
  }
296
296
  function formatResult(msg) {
297
297
  const ok = msg.subtype === "success";
298
- const tag = ok ? "DONE" : `FAILED (${msg.subtype ?? "unknown"})`;
298
+ const tag = ok ? "SESSION ok" : `SESSION failed (${msg.subtype ?? "unknown"})`;
299
299
  const dur = msg.duration_ms ? ` ${(msg.duration_ms / 1e3).toFixed(1)}s` : "";
300
300
  const turns = msg.num_turns ? ` ${msg.num_turns} turns` : "";
301
301
  const cost = typeof msg.total_cost_usd === "number" ? ` $${msg.total_cost_usd.toFixed(4)}` : "";
@@ -354,7 +354,7 @@ async function runAgent(opts) {
354
354
  env.ANTHROPIC_BASE_URL = opts.litellmUrl;
355
355
  env.ANTHROPIC_API_KEY = getAnthropicApiKeyOrDummy();
356
356
  }
357
- let finalText = "";
357
+ const resultTexts = [];
358
358
  let outcome = "failed";
359
359
  let errorMessage;
360
360
  try {
@@ -399,7 +399,8 @@ async function runAgent(opts) {
399
399
  if (m.type === "result") {
400
400
  if (m.subtype === "success") {
401
401
  outcome = "completed";
402
- finalText = (typeof m.result === "string" ? m.result : "").trim();
402
+ const text = (typeof m.result === "string" ? m.result : "").trim();
403
+ if (text) resultTexts.push(text);
403
404
  } else {
404
405
  outcome = "failed";
405
406
  errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
@@ -415,6 +416,7 @@ async function runAgent(opts) {
415
416
  } catch {
416
417
  }
417
418
  }
419
+ const finalText = resultTexts.join("\n\n---\n\n");
418
420
  return { outcome, finalText, error: errorMessage, ndjsonPath };
419
421
  }
420
422
 
@@ -543,7 +545,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
543
545
  }
544
546
 
545
547
  // src/kody2-cli.ts
546
- import { execFileSync as execFileSync22 } from "child_process";
548
+ import { execFileSync as execFileSync20 } from "child_process";
547
549
  import * as fs21 from "fs";
548
550
  import * as path18 from "path";
549
551
 
@@ -581,9 +583,6 @@ function autoDispatch(opts) {
581
583
  if (!targetNum) return null;
582
584
  const afterTag = extractAfterTag(body);
583
585
  if (isPr) {
584
- if (/\bapprove\b/.test(afterTag)) {
585
- return { executable: "approve", cliArgs: { pr: targetNum }, target: targetNum };
586
- }
587
586
  if (/\bfix-ci\b/.test(afterTag)) {
588
587
  return { executable: "fix-ci", cliArgs: { pr: targetNum }, target: targetNum };
589
588
  }
@@ -607,8 +606,9 @@ function autoDispatch(opts) {
607
606
  };
608
607
  }
609
608
  const sub = extractSubcommand(afterTag);
610
- const defaultExec = opts?.config?.defaultExecutable ?? "run";
611
609
  if (!sub) {
610
+ const defaultExec = opts?.config?.defaultExecutable;
611
+ if (!defaultExec) return null;
612
612
  return asDispatch(defaultExec, targetNum);
613
613
  }
614
614
  if (sub === "build") {
@@ -1241,206 +1241,6 @@ var advanceFlow = async (ctx, profile) => {
1241
1241
  }
1242
1242
  };
1243
1243
 
1244
- // src/scripts/applyApprovals.ts
1245
- import { execFileSync as execFileSync5 } from "child_process";
1246
-
1247
- // src/issue.ts
1248
- import { execFileSync as execFileSync4 } from "child_process";
1249
- var API_TIMEOUT_MS3 = 3e4;
1250
- function ghToken2() {
1251
- return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
1252
- }
1253
- function gh2(args, options) {
1254
- const token = ghToken2();
1255
- const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
1256
- return execFileSync4("gh", args, {
1257
- encoding: "utf-8",
1258
- timeout: API_TIMEOUT_MS3,
1259
- cwd: options?.cwd,
1260
- env,
1261
- input: options?.input,
1262
- stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
1263
- }).trim();
1264
- }
1265
- function getIssue(issueNumber, cwd) {
1266
- const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
1267
- const parsed = JSON.parse(output);
1268
- if (typeof parsed?.title !== "string") {
1269
- throw new Error(`Issue #${issueNumber}: unexpected response shape`);
1270
- }
1271
- return {
1272
- number: parsed.number ?? issueNumber,
1273
- title: parsed.title,
1274
- body: parsed.body ?? "",
1275
- comments: (parsed.comments ?? []).map((c) => ({
1276
- body: c.body ?? "",
1277
- author: c.author?.login ?? "unknown",
1278
- createdAt: c.createdAt ?? ""
1279
- })),
1280
- labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
1281
- };
1282
- }
1283
- function stripKody2Mentions(body) {
1284
- return body.replace(/(@)(kody2)/gi, "$1\u200B$2");
1285
- }
1286
- function postIssueComment(issueNumber, body, cwd) {
1287
- try {
1288
- gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
1289
- } catch (err) {
1290
- process.stderr.write(
1291
- `[kody2] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
1292
- `
1293
- );
1294
- }
1295
- }
1296
- function truncate2(s, maxBytes) {
1297
- if (s.length <= maxBytes) return s;
1298
- return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
1299
- }
1300
- function getPr(prNumber, cwd) {
1301
- const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
1302
- cwd
1303
- });
1304
- const parsed = JSON.parse(output);
1305
- if (typeof parsed?.title !== "string") {
1306
- throw new Error(`PR #${prNumber}: unexpected response shape`);
1307
- }
1308
- return {
1309
- number: parsed.number ?? prNumber,
1310
- title: parsed.title,
1311
- body: parsed.body ?? "",
1312
- headRefName: String(parsed.headRefName ?? ""),
1313
- baseRefName: String(parsed.baseRefName ?? ""),
1314
- state: String(parsed.state ?? "")
1315
- };
1316
- }
1317
- function getPrDiff(prNumber, cwd) {
1318
- try {
1319
- return gh2(["pr", "diff", String(prNumber)], { cwd });
1320
- } catch (err) {
1321
- process.stderr.write(
1322
- `[kody2] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
1323
- `
1324
- );
1325
- return "";
1326
- }
1327
- }
1328
- function getPrReviews(prNumber, cwd) {
1329
- try {
1330
- const output = gh2(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
1331
- const parsed = JSON.parse(output);
1332
- if (!Array.isArray(parsed?.reviews)) return [];
1333
- return parsed.reviews.map(
1334
- (r) => ({
1335
- body: r.body ?? "",
1336
- state: r.state ?? "",
1337
- author: r.author?.login ?? "unknown",
1338
- submittedAt: r.submittedAt ?? ""
1339
- })
1340
- );
1341
- } catch {
1342
- return [];
1343
- }
1344
- }
1345
- function getPrComments(prNumber, cwd) {
1346
- try {
1347
- const output = gh2(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
1348
- const parsed = JSON.parse(output);
1349
- if (!Array.isArray(parsed?.comments)) return [];
1350
- return parsed.comments.map((c) => ({
1351
- body: c.body ?? "",
1352
- author: c.author?.login ?? "unknown",
1353
- createdAt: c.createdAt ?? ""
1354
- })).filter((c) => c.body.trim().length > 0);
1355
- } catch {
1356
- return [];
1357
- }
1358
- }
1359
- var VERDICT_HEADING = /(^|\n)\s*#{1,6}\s*Verdict\s*:/i;
1360
- function isReviewShaped(body) {
1361
- return VERDICT_HEADING.test(body);
1362
- }
1363
- function getPrLatestReviewBody(prNumber, cwd) {
1364
- const reviews = getPrReviews(prNumber, cwd).filter((r) => r.body.trim().length > 0).map((r) => ({ body: r.body, at: r.submittedAt }));
1365
- const comments = getPrComments(prNumber, cwd).filter((c) => isReviewShaped(c.body)).map((c) => ({ body: c.body, at: c.createdAt }));
1366
- const all = [...reviews, ...comments].sort((a, b) => (b.at || "").localeCompare(a.at || ""));
1367
- if (all.length > 0) return all[0].body;
1368
- const pr = getPr(prNumber, cwd);
1369
- return pr.body;
1370
- }
1371
- function postPrReviewComment(prNumber, body, cwd) {
1372
- try {
1373
- gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
1374
- } catch (err) {
1375
- process.stderr.write(
1376
- `[kody2] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
1377
- `
1378
- );
1379
- }
1380
- }
1381
-
1382
- // src/scripts/applyApprovals.ts
1383
- var API_TIMEOUT_MS4 = 3e4;
1384
- var applyApprovals = async (ctx) => {
1385
- const issueArg = typeof ctx.args.issue === "number" ? ctx.args.issue : null;
1386
- const prArg = typeof ctx.args.pr === "number" ? ctx.args.pr : null;
1387
- const currentTarget = issueArg ? { type: "issue", number: issueArg } : prArg ? { type: "pr", number: prArg } : null;
1388
- if (!currentTarget) {
1389
- ctx.output.exitCode = 64;
1390
- ctx.output.reason = "approve: must be invoked with --issue or --pr";
1391
- return;
1392
- }
1393
- let state = null;
1394
- try {
1395
- state = readTaskState(currentTarget.type, currentTarget.number, ctx.cwd);
1396
- } catch {
1397
- state = null;
1398
- }
1399
- const issueNumber = currentTarget.type === "issue" ? currentTarget.number : state?.flow?.issueNumber ?? null;
1400
- const flowName = state?.flow?.name ?? null;
1401
- const confirmation = formatConfirmation(currentTarget, flowName, issueNumber);
1402
- try {
1403
- if (currentTarget.type === "issue") postIssueComment(currentTarget.number, confirmation, ctx.cwd);
1404
- else postPrReviewComment(currentTarget.number, confirmation, ctx.cwd);
1405
- } catch {
1406
- }
1407
- if (issueNumber && typeof flowName === "string" && flowName.length > 0) {
1408
- try {
1409
- execFileSync5("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${flowName}`], {
1410
- timeout: API_TIMEOUT_MS4,
1411
- cwd: ctx.cwd,
1412
- stdio: ["ignore", "pipe", "pipe"]
1413
- });
1414
- } catch (err) {
1415
- process.stderr.write(
1416
- `[kody2 approve] failed to re-trigger flow on issue #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
1417
- `
1418
- );
1419
- }
1420
- }
1421
- ctx.output.exitCode = 0;
1422
- };
1423
- function formatConfirmation(current, flowName, issueNumber) {
1424
- const lines = [];
1425
- lines.push("\u2705 **kody2 risk gates approved.**");
1426
- lines.push("");
1427
- lines.push("The approval is recorded as this comment \u2014 kody2 reads it directly on the next run.");
1428
- if (flowName && issueNumber) {
1429
- lines.push("");
1430
- if (current.type === "pr") {
1431
- lines.push(
1432
- `Re-triggering the \`${flowName}\` flow on the originating issue (#${issueNumber}) \u2014 it will resume from the existing branch/PR checkpoint.`
1433
- );
1434
- } else {
1435
- lines.push(`Re-triggering the \`${flowName}\` flow now \u2014 it will resume from the existing branch checkpoint.`);
1436
- }
1437
- } else {
1438
- lines.push("");
1439
- lines.push("No active flow found in task state. Post `@kody2 <command>` to resume manually.");
1440
- }
1441
- return lines.join("\n");
1442
- }
1443
-
1444
1244
  // src/scripts/buildSyntheticPlugin.ts
1445
1245
  import * as fs8 from "fs";
1446
1246
  import * as os2 from "os";
@@ -1535,7 +1335,7 @@ function copyDir(src, dst) {
1535
1335
  }
1536
1336
 
1537
1337
  // src/coverage.ts
1538
- import { execFileSync as execFileSync6 } from "child_process";
1338
+ import { execFileSync as execFileSync4 } from "child_process";
1539
1339
  function patternToRegex(pattern) {
1540
1340
  let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1541
1341
  s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
@@ -1553,7 +1353,7 @@ function renderSiblingPath(file, requireSibling) {
1553
1353
  }
1554
1354
  function safeGit(args, cwd) {
1555
1355
  try {
1556
- return execFileSync6("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
1356
+ return execFileSync4("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
1557
1357
  } catch {
1558
1358
  return "";
1559
1359
  }
@@ -1641,7 +1441,9 @@ function parseAgentResult(finalText) {
1641
1441
  failureReason: failedMatch[1].trim()
1642
1442
  };
1643
1443
  }
1644
- if (!/(^|\n)\s*DONE\b/i.test(text)) {
1444
+ const hasDoneMarker = /(^|\n)\s*DONE\b/i.test(text);
1445
+ const hasCommitMsg = /^[ \t]*COMMIT_MSG\s*:/im.test(text);
1446
+ if (!hasDoneMarker && !hasCommitMsg) {
1645
1447
  return {
1646
1448
  done: false,
1647
1449
  commitMessage: "",
@@ -1759,10 +1561,10 @@ function defaultLabelMap() {
1759
1561
  }
1760
1562
 
1761
1563
  // src/scripts/commitAndPush.ts
1762
- import { execFileSync as execFileSync8 } from "child_process";
1564
+ import { execFileSync as execFileSync6 } from "child_process";
1763
1565
 
1764
1566
  // src/commit.ts
1765
- import { execFileSync as execFileSync7 } from "child_process";
1567
+ import { execFileSync as execFileSync5 } from "child_process";
1766
1568
  import * as fs10 from "fs";
1767
1569
  import * as path9 from "path";
1768
1570
  var FORBIDDEN_PATH_PREFIXES = [
@@ -1792,7 +1594,7 @@ var CONVENTIONAL_PREFIXES = [
1792
1594
  ];
1793
1595
  function git(args, cwd) {
1794
1596
  try {
1795
- return execFileSync7("git", args, {
1597
+ return execFileSync5("git", args, {
1796
1598
  encoding: "utf-8",
1797
1599
  timeout: 12e4,
1798
1600
  cwd,
@@ -1850,7 +1652,7 @@ function isForbiddenPath(p) {
1850
1652
  return false;
1851
1653
  }
1852
1654
  function listChangedFiles(cwd) {
1853
- const raw = execFileSync7("git", ["status", "--porcelain=v1", "-z"], {
1655
+ const raw = execFileSync5("git", ["status", "--porcelain=v1", "-z"], {
1854
1656
  encoding: "utf-8",
1855
1657
  cwd,
1856
1658
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1862,7 +1664,7 @@ function listChangedFiles(cwd) {
1862
1664
  }
1863
1665
  function listFilesInCommit(ref = "HEAD", cwd) {
1864
1666
  try {
1865
- const raw = execFileSync7("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1667
+ const raw = execFileSync5("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1866
1668
  encoding: "utf-8",
1867
1669
  cwd,
1868
1670
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1942,7 +1744,7 @@ var commitAndPush2 = async (ctx, profile) => {
1942
1744
  const kind = profile.name;
1943
1745
  if (kind === "resolve") {
1944
1746
  try {
1945
- execFileSync8("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
1747
+ execFileSync6("git", ["add", "-A"], { cwd: ctx.cwd, env: { ...process.env, HUSKY: "0" }, stdio: "pipe" });
1946
1748
  } catch {
1947
1749
  }
1948
1750
  } else {
@@ -2564,8 +2366,8 @@ var discoverQaContext = async (ctx) => {
2564
2366
  };
2565
2367
 
2566
2368
  // src/scripts/dispatch.ts
2567
- import { execFileSync as execFileSync9 } from "child_process";
2568
- var API_TIMEOUT_MS5 = 3e4;
2369
+ import { execFileSync as execFileSync7 } from "child_process";
2370
+ var API_TIMEOUT_MS3 = 3e4;
2569
2371
  var dispatch = async (ctx, _profile, _agentResult, args) => {
2570
2372
  const next = args?.next;
2571
2373
  if (!next) {
@@ -2587,8 +2389,8 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
2587
2389
  const sub = usePr ? "pr" : "issue";
2588
2390
  const body = `@kody2 ${next}`;
2589
2391
  try {
2590
- execFileSync9("gh", [sub, "comment", String(targetNumber), "--body", body], {
2591
- timeout: API_TIMEOUT_MS5,
2392
+ execFileSync7("gh", [sub, "comment", String(targetNumber), "--body", body], {
2393
+ timeout: API_TIMEOUT_MS3,
2592
2394
  cwd: ctx.cwd,
2593
2395
  stdio: ["ignore", "pipe", "pipe"]
2594
2396
  });
@@ -2606,6 +2408,141 @@ function parsePr(url) {
2606
2408
  return Number.isFinite(n) ? n : null;
2607
2409
  }
2608
2410
 
2411
+ // src/issue.ts
2412
+ import { execFileSync as execFileSync8 } from "child_process";
2413
+ var API_TIMEOUT_MS4 = 3e4;
2414
+ function ghToken2() {
2415
+ return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
2416
+ }
2417
+ function gh2(args, options) {
2418
+ const token = ghToken2();
2419
+ const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
2420
+ return execFileSync8("gh", args, {
2421
+ encoding: "utf-8",
2422
+ timeout: API_TIMEOUT_MS4,
2423
+ cwd: options?.cwd,
2424
+ env,
2425
+ input: options?.input,
2426
+ stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
2427
+ }).trim();
2428
+ }
2429
+ function getIssue(issueNumber, cwd) {
2430
+ const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
2431
+ const parsed = JSON.parse(output);
2432
+ if (typeof parsed?.title !== "string") {
2433
+ throw new Error(`Issue #${issueNumber}: unexpected response shape`);
2434
+ }
2435
+ return {
2436
+ number: parsed.number ?? issueNumber,
2437
+ title: parsed.title,
2438
+ body: parsed.body ?? "",
2439
+ comments: (parsed.comments ?? []).map((c) => ({
2440
+ body: c.body ?? "",
2441
+ author: c.author?.login ?? "unknown",
2442
+ createdAt: c.createdAt ?? ""
2443
+ })),
2444
+ labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
2445
+ };
2446
+ }
2447
+ function stripKody2Mentions(body) {
2448
+ return body.replace(/(@)(kody2)/gi, "$1\u200B$2");
2449
+ }
2450
+ function postIssueComment(issueNumber, body, cwd) {
2451
+ try {
2452
+ gh2(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
2453
+ } catch (err) {
2454
+ process.stderr.write(
2455
+ `[kody2] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
2456
+ `
2457
+ );
2458
+ }
2459
+ }
2460
+ function truncate2(s, maxBytes) {
2461
+ if (s.length <= maxBytes) return s;
2462
+ return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
2463
+ }
2464
+ function getPr(prNumber, cwd) {
2465
+ const output = gh2(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
2466
+ cwd
2467
+ });
2468
+ const parsed = JSON.parse(output);
2469
+ if (typeof parsed?.title !== "string") {
2470
+ throw new Error(`PR #${prNumber}: unexpected response shape`);
2471
+ }
2472
+ return {
2473
+ number: parsed.number ?? prNumber,
2474
+ title: parsed.title,
2475
+ body: parsed.body ?? "",
2476
+ headRefName: String(parsed.headRefName ?? ""),
2477
+ baseRefName: String(parsed.baseRefName ?? ""),
2478
+ state: String(parsed.state ?? "")
2479
+ };
2480
+ }
2481
+ function getPrDiff(prNumber, cwd) {
2482
+ try {
2483
+ return gh2(["pr", "diff", String(prNumber)], { cwd });
2484
+ } catch (err) {
2485
+ process.stderr.write(
2486
+ `[kody2] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
2487
+ `
2488
+ );
2489
+ return "";
2490
+ }
2491
+ }
2492
+ function getPrReviews(prNumber, cwd) {
2493
+ try {
2494
+ const output = gh2(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
2495
+ const parsed = JSON.parse(output);
2496
+ if (!Array.isArray(parsed?.reviews)) return [];
2497
+ return parsed.reviews.map(
2498
+ (r) => ({
2499
+ body: r.body ?? "",
2500
+ state: r.state ?? "",
2501
+ author: r.author?.login ?? "unknown",
2502
+ submittedAt: r.submittedAt ?? ""
2503
+ })
2504
+ );
2505
+ } catch {
2506
+ return [];
2507
+ }
2508
+ }
2509
+ function getPrComments(prNumber, cwd) {
2510
+ try {
2511
+ const output = gh2(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
2512
+ const parsed = JSON.parse(output);
2513
+ if (!Array.isArray(parsed?.comments)) return [];
2514
+ return parsed.comments.map((c) => ({
2515
+ body: c.body ?? "",
2516
+ author: c.author?.login ?? "unknown",
2517
+ createdAt: c.createdAt ?? ""
2518
+ })).filter((c) => c.body.trim().length > 0);
2519
+ } catch {
2520
+ return [];
2521
+ }
2522
+ }
2523
+ var VERDICT_HEADING = /(^|\n)\s*#{1,6}\s*Verdict\s*:/i;
2524
+ function isReviewShaped(body) {
2525
+ return VERDICT_HEADING.test(body);
2526
+ }
2527
+ function getPrLatestReviewBody(prNumber, cwd) {
2528
+ const reviews = getPrReviews(prNumber, cwd).filter((r) => r.body.trim().length > 0).map((r) => ({ body: r.body, at: r.submittedAt }));
2529
+ const comments = getPrComments(prNumber, cwd).filter((c) => isReviewShaped(c.body)).map((c) => ({ body: c.body, at: c.createdAt }));
2530
+ const all = [...reviews, ...comments].sort((a, b) => (b.at || "").localeCompare(a.at || ""));
2531
+ if (all.length > 0) return all[0].body;
2532
+ const pr = getPr(prNumber, cwd);
2533
+ return pr.body;
2534
+ }
2535
+ function postPrReviewComment(prNumber, body, cwd) {
2536
+ try {
2537
+ gh2(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKody2Mentions(body), cwd });
2538
+ } catch (err) {
2539
+ process.stderr.write(
2540
+ `[kody2] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
2541
+ `
2542
+ );
2543
+ }
2544
+ }
2545
+
2609
2546
  // src/pr.ts
2610
2547
  var TITLE_MAX = 72;
2611
2548
  function stripTitlePrefixes(raw) {
@@ -2692,10 +2629,7 @@ function ensurePr(opts) {
2692
2629
  try {
2693
2630
  gh2(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: opts.cwd });
2694
2631
  } catch (err) {
2695
- process.stderr.write(
2696
- `[kody2] failed to update PR #${existing.number}: ${err instanceof Error ? err.message : String(err)}
2697
- `
2698
- );
2632
+ throw new Error(`gh pr edit #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`);
2699
2633
  }
2700
2634
  return { url: existing.url, number: existing.number, draft: opts.draft, action: "updated" };
2701
2635
  }
@@ -2771,7 +2705,7 @@ function computeFailureReason(ctx) {
2771
2705
  }
2772
2706
 
2773
2707
  // src/scripts/finishFlow.ts
2774
- import { execFileSync as execFileSync10 } from "child_process";
2708
+ import { execFileSync as execFileSync9 } from "child_process";
2775
2709
 
2776
2710
  // src/registry.ts
2777
2711
  import * as fs14 from "fs";
@@ -2959,7 +2893,7 @@ function errMsg(err) {
2959
2893
  }
2960
2894
 
2961
2895
  // src/scripts/finishFlow.ts
2962
- var API_TIMEOUT_MS6 = 3e4;
2896
+ var API_TIMEOUT_MS5 = 3e4;
2963
2897
  var STATUS_ICON = {
2964
2898
  "review-passed": "\u2705",
2965
2899
  "fix-applied": "\u2705",
@@ -2991,8 +2925,8 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
2991
2925
  **PR:** ${state.core.prUrl}` : "";
2992
2926
  const body = `${icon} kody2 flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
2993
2927
  try {
2994
- execFileSync10("gh", ["issue", "comment", String(issueNumber), "--body", body], {
2995
- timeout: API_TIMEOUT_MS6,
2928
+ execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
2929
+ timeout: API_TIMEOUT_MS5,
2996
2930
  cwd: ctx.cwd,
2997
2931
  stdio: ["ignore", "pipe", "pipe"]
2998
2932
  });
@@ -3005,7 +2939,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
3005
2939
  };
3006
2940
 
3007
2941
  // src/branch.ts
3008
- import { execFileSync as execFileSync11 } from "child_process";
2942
+ import { execFileSync as execFileSync10 } from "child_process";
3009
2943
  var UncommittedChangesError = class extends Error {
3010
2944
  constructor(branch) {
3011
2945
  super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
@@ -3015,7 +2949,7 @@ var UncommittedChangesError = class extends Error {
3015
2949
  branch;
3016
2950
  };
3017
2951
  function git2(args, cwd) {
3018
- return execFileSync11("git", args, {
2952
+ return execFileSync10("git", args, {
3019
2953
  encoding: "utf-8",
3020
2954
  timeout: 3e4,
3021
2955
  cwd,
@@ -3040,7 +2974,7 @@ function checkoutPrBranch(prNumber, cwd) {
3040
2974
  SKIP_HOOKS: "1",
3041
2975
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
3042
2976
  };
3043
- execFileSync11("gh", ["pr", "checkout", String(prNumber)], {
2977
+ execFileSync10("gh", ["pr", "checkout", String(prNumber)], {
3044
2978
  cwd,
3045
2979
  env,
3046
2980
  stdio: ["ignore", "pipe", "pipe"],
@@ -3107,7 +3041,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
3107
3041
  }
3108
3042
 
3109
3043
  // src/gha.ts
3110
- import { execFileSync as execFileSync12 } from "child_process";
3044
+ import { execFileSync as execFileSync11 } from "child_process";
3111
3045
  import * as fs15 from "fs";
3112
3046
  function getRunUrl() {
3113
3047
  const server = process.env.GITHUB_SERVER_URL;
@@ -3150,7 +3084,7 @@ function reactToTriggerComment(cwd) {
3150
3084
  for (let attempt = 0; attempt < 3; attempt++) {
3151
3085
  if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
3152
3086
  try {
3153
- execFileSync12("gh", args, opts);
3087
+ execFileSync11("gh", args, opts);
3154
3088
  return;
3155
3089
  } catch (err) {
3156
3090
  lastErr = err;
@@ -3163,13 +3097,13 @@ function reactToTriggerComment(cwd) {
3163
3097
  }
3164
3098
  function sleepMs(ms) {
3165
3099
  try {
3166
- execFileSync12("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
3100
+ execFileSync11("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
3167
3101
  } catch {
3168
3102
  }
3169
3103
  }
3170
3104
 
3171
3105
  // src/workflow.ts
3172
- import { execFileSync as execFileSync13 } from "child_process";
3106
+ import { execFileSync as execFileSync12 } from "child_process";
3173
3107
  var GH_TIMEOUT_MS = 3e4;
3174
3108
  function ghToken3() {
3175
3109
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
@@ -3177,7 +3111,7 @@ function ghToken3() {
3177
3111
  function gh3(args, cwd) {
3178
3112
  const token = ghToken3();
3179
3113
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
3180
- return execFileSync13("gh", args, {
3114
+ return execFileSync12("gh", args, {
3181
3115
  encoding: "utf-8",
3182
3116
  timeout: GH_TIMEOUT_MS,
3183
3117
  cwd,
@@ -3361,7 +3295,7 @@ function tryPostPr2(prNumber, body, cwd) {
3361
3295
  }
3362
3296
 
3363
3297
  // src/scripts/initFlow.ts
3364
- import { execFileSync as execFileSync14 } from "child_process";
3298
+ import { execFileSync as execFileSync13 } from "child_process";
3365
3299
  import * as fs17 from "fs";
3366
3300
  import * as path15 from "path";
3367
3301
 
@@ -3402,7 +3336,7 @@ function qualityCommandsFor(pm) {
3402
3336
  function detectOwnerRepo(cwd) {
3403
3337
  let url;
3404
3338
  try {
3405
- url = execFileSync14("git", ["remote", "get-url", "origin"], {
3339
+ url = execFileSync13("git", ["remote", "get-url", "origin"], {
3406
3340
  cwd,
3407
3341
  encoding: "utf-8",
3408
3342
  stdio: ["ignore", "pipe", "pipe"]
@@ -3486,7 +3420,7 @@ jobs:
3486
3420
  `;
3487
3421
  function defaultBranchFromGit(cwd) {
3488
3422
  try {
3489
- const ref = execFileSync14("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
3423
+ const ref = execFileSync13("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
3490
3424
  cwd,
3491
3425
  encoding: "utf-8",
3492
3426
  stdio: ["ignore", "pipe", "pipe"]
@@ -3494,7 +3428,7 @@ function defaultBranchFromGit(cwd) {
3494
3428
  return ref.replace("refs/remotes/origin/", "");
3495
3429
  } catch {
3496
3430
  try {
3497
- return execFileSync14("git", ["branch", "--show-current"], {
3431
+ return execFileSync13("git", ["branch", "--show-current"], {
3498
3432
  cwd,
3499
3433
  encoding: "utf-8",
3500
3434
  stdio: ["ignore", "pipe", "pipe"]
@@ -3787,8 +3721,8 @@ var persistFlowState = async (ctx) => {
3787
3721
  };
3788
3722
 
3789
3723
  // src/scripts/postClassification.ts
3790
- import { execFileSync as execFileSync15 } from "child_process";
3791
- var API_TIMEOUT_MS7 = 3e4;
3724
+ import { execFileSync as execFileSync14 } from "child_process";
3725
+ var API_TIMEOUT_MS6 = 3e4;
3792
3726
  var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
3793
3727
  var postClassification = async (ctx) => {
3794
3728
  const issueNumber = ctx.args.issue;
@@ -3817,9 +3751,9 @@ var postClassification = async (ctx) => {
3817
3751
  ctx.cwd
3818
3752
  );
3819
3753
  try {
3820
- execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${classification}`], {
3754
+ execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${classification}`], {
3821
3755
  cwd: ctx.cwd,
3822
- timeout: API_TIMEOUT_MS7,
3756
+ timeout: API_TIMEOUT_MS6,
3823
3757
  stdio: ["ignore", "pipe", "pipe"]
3824
3758
  });
3825
3759
  } catch (err) {
@@ -3851,9 +3785,9 @@ function parseClassification(prSummary) {
3851
3785
  }
3852
3786
  function tryAuditComment(issueNumber, body, cwd) {
3853
3787
  try {
3854
- execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
3788
+ execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
3855
3789
  cwd,
3856
- timeout: API_TIMEOUT_MS7,
3790
+ timeout: API_TIMEOUT_MS6,
3857
3791
  stdio: ["ignore", "pipe", "pipe"]
3858
3792
  });
3859
3793
  } catch {
@@ -4035,7 +3969,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
4035
3969
  };
4036
3970
 
4037
3971
  // src/scripts/releaseFlow.ts
4038
- import { execFileSync as execFileSync16, spawnSync } from "child_process";
3972
+ import { execFileSync as execFileSync15, spawnSync } from "child_process";
4039
3973
  import * as fs18 from "fs";
4040
3974
  import * as path16 from "path";
4041
3975
  function bumpVersion(current, bump) {
@@ -4065,7 +3999,7 @@ function generateChangelog(cwd, newVersion, lastTag) {
4065
3999
  const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
4066
4000
  let log = "";
4067
4001
  try {
4068
- log = execFileSync16("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
4002
+ log = execFileSync15("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
4069
4003
  cwd,
4070
4004
  encoding: "utf-8",
4071
4005
  stdio: ["ignore", "pipe", "pipe"]
@@ -4122,7 +4056,7 @@ ${entry}${prior.slice(idx + 1)}`);
4122
4056
  }
4123
4057
  }
4124
4058
  function git3(args, cwd, timeout = 6e4) {
4125
- return execFileSync16("git", args, {
4059
+ return execFileSync15("git", args, {
4126
4060
  encoding: "utf-8",
4127
4061
  timeout,
4128
4062
  cwd,
@@ -4425,7 +4359,7 @@ var resolveArtifacts = async (ctx, profile) => {
4425
4359
  };
4426
4360
 
4427
4361
  // src/scripts/resolveFlow.ts
4428
- import { execFileSync as execFileSync17 } from "child_process";
4362
+ import { execFileSync as execFileSync16 } from "child_process";
4429
4363
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
4430
4364
  var resolveFlow = async (ctx) => {
4431
4365
  const prNumber = ctx.args.pr;
@@ -4477,7 +4411,7 @@ var resolveFlow = async (ctx) => {
4477
4411
  };
4478
4412
  function getConflictedFiles(cwd) {
4479
4413
  try {
4480
- const out = execFileSync17("git", ["diff", "--name-only", "--diff-filter=U"], {
4414
+ const out = execFileSync16("git", ["diff", "--name-only", "--diff-filter=U"], {
4481
4415
  encoding: "utf-8",
4482
4416
  cwd,
4483
4417
  env: { ...process.env, HUSKY: "0" }
@@ -4492,7 +4426,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
4492
4426
  let total = 0;
4493
4427
  for (const f of files) {
4494
4428
  try {
4495
- const content = execFileSync17("cat", [f], { encoding: "utf-8", cwd }).toString();
4429
+ const content = execFileSync16("cat", [f], { encoding: "utf-8", cwd }).toString();
4496
4430
  const snippet = `### ${f}
4497
4431
 
4498
4432
  \`\`\`
@@ -4560,295 +4494,6 @@ function tryPostPr4(prNumber, body, cwd) {
4560
4494
  }
4561
4495
  }
4562
4496
 
4563
- // src/scripts/riskGate.ts
4564
- import { execFileSync as execFileSync18 } from "child_process";
4565
- var ALL_GATES = ["secrets", "workflow-edit", "large-diff", "dep-change", "test-deletion"];
4566
- var WAITING_LABEL = "kody:waiting";
4567
- var ADVISORY_MARKER = "kody2 risk gate halted the flow";
4568
- var APPROVE_COMMAND = /(^|\s)@kody2\s+approve\b/i;
4569
- var DEFAULT_MAX_FILES = 20;
4570
- var DEFAULT_MAX_DELETIONS = 500;
4571
- var riskGate = async (ctx, profile, _agent, args) => {
4572
- const changedFiles = collectBranchChangedFiles(ctx);
4573
- const gatesToRun = parseGates(args?.gates);
4574
- const violations = evaluateGates(ctx, profile.name, changedFiles, gatesToRun, args);
4575
- if (violations.length === 0) {
4576
- ctx.data.riskGate = { violations: [], pending: [], decision: "allow" };
4577
- return;
4578
- }
4579
- const targetType = ctx.data.commentTargetType;
4580
- const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
4581
- const approved = hasApproval(ctx, targetType, targetNumber);
4582
- ctx.data.riskGate = {
4583
- violations,
4584
- pending: approved ? [] : violations,
4585
- decision: approved ? "allow" : "halt"
4586
- };
4587
- if (approved || !targetType || targetNumber <= 0) return;
4588
- try {
4589
- setKodyLabel(
4590
- targetNumber,
4591
- {
4592
- label: WAITING_LABEL,
4593
- color: "fbca04",
4594
- description: "kody2: awaiting human approval of risk gate(s)"
4595
- },
4596
- ctx.cwd
4597
- );
4598
- } catch {
4599
- }
4600
- const compareUrl = computeCompareUrl(ctx);
4601
- const body = formatAdvisory(violations, compareUrl);
4602
- try {
4603
- if (targetType === "issue") postIssueComment(targetNumber, body, ctx.cwd);
4604
- else postPrReviewComment(targetNumber, body, ctx.cwd);
4605
- } catch {
4606
- }
4607
- if (!ctx.output.reason) {
4608
- ctx.output.reason = `risk gate halt: ${violations.map((p) => p.name).join(", ")}`;
4609
- }
4610
- };
4611
- function hasApproval(ctx, targetType, targetNumber) {
4612
- const surfaces = [];
4613
- if (targetNumber > 0) surfaces.push(targetNumber);
4614
- if (targetType === "pr") {
4615
- const state = ctx.data.taskState;
4616
- const issueNum = state?.flow?.issueNumber;
4617
- if (typeof issueNum === "number" && issueNum > 0 && !surfaces.includes(issueNum)) {
4618
- surfaces.push(issueNum);
4619
- }
4620
- }
4621
- for (const n of surfaces) {
4622
- if (surfaceIsApproved(n, ctx.cwd)) return true;
4623
- }
4624
- return false;
4625
- }
4626
- function surfaceIsApproved(n, cwd) {
4627
- let comments = [];
4628
- try {
4629
- comments = getIssue(n, cwd).comments;
4630
- } catch {
4631
- return false;
4632
- }
4633
- const advisoryAt = latestAdvisoryTimestamp(comments);
4634
- for (const c of comments) {
4635
- if (!APPROVE_COMMAND.test(c.body)) continue;
4636
- if (advisoryAt !== null && c.createdAt <= advisoryAt) continue;
4637
- return true;
4638
- }
4639
- return false;
4640
- }
4641
- function latestAdvisoryTimestamp(comments) {
4642
- let latest = null;
4643
- for (const c of comments) {
4644
- if (!c.body.includes(ADVISORY_MARKER)) continue;
4645
- if (latest === null || c.createdAt > latest) latest = c.createdAt;
4646
- }
4647
- return latest;
4648
- }
4649
- function evaluateGates(ctx, profileName, changedFiles, gatesToRun, args) {
4650
- const violations = [];
4651
- if (gatesToRun.includes("secrets")) {
4652
- const hits = changedFiles.filter(isSecretPath);
4653
- if (hits.length > 0) {
4654
- violations.push({
4655
- name: "secrets",
4656
- severity: "hard",
4657
- reason: `secret/credential files touched: ${preview(hits)}`
4658
- });
4659
- }
4660
- }
4661
- if (gatesToRun.includes("workflow-edit")) {
4662
- const hits = changedFiles.filter((f) => f.startsWith(".github/workflows/"));
4663
- if (hits.length > 0) {
4664
- violations.push({
4665
- name: "workflow-edit",
4666
- severity: "soft",
4667
- reason: `CI workflow files modified: ${preview(hits)}`
4668
- });
4669
- }
4670
- }
4671
- if (gatesToRun.includes("large-diff")) {
4672
- const maxFiles = toPositiveInt(args?.maxFiles, DEFAULT_MAX_FILES);
4673
- const maxDeletions = toPositiveInt(args?.maxDeletions, DEFAULT_MAX_DELETIONS);
4674
- if (changedFiles.length > maxFiles) {
4675
- violations.push({
4676
- name: "large-diff",
4677
- severity: "soft",
4678
- reason: `${changedFiles.length} files changed (threshold: ${maxFiles})`
4679
- });
4680
- } else {
4681
- const stats = computeDiffStats(ctx);
4682
- if (stats && stats.deletions > maxDeletions) {
4683
- violations.push({
4684
- name: "large-diff",
4685
- severity: "soft",
4686
- reason: `${stats.deletions} lines deleted (threshold: ${maxDeletions})`
4687
- });
4688
- }
4689
- }
4690
- }
4691
- if (gatesToRun.includes("dep-change") && profileName !== "chore") {
4692
- const hits = changedFiles.filter(isDepFile);
4693
- if (hits.length > 0) {
4694
- violations.push({
4695
- name: "dep-change",
4696
- severity: "soft",
4697
- reason: `dependency/lockfile changes outside a chore flow: ${preview(hits)}`
4698
- });
4699
- }
4700
- }
4701
- if (gatesToRun.includes("test-deletion")) {
4702
- const deleted = listDeletedFilesInHeadCommit(ctx.cwd).filter(isTestFile);
4703
- if (deleted.length > 0) {
4704
- violations.push({
4705
- name: "test-deletion",
4706
- severity: "soft",
4707
- reason: `test files deleted: ${preview(deleted)}`
4708
- });
4709
- }
4710
- }
4711
- return violations;
4712
- }
4713
- function parseGates(spec) {
4714
- if (spec === void 0 || spec === null || spec === "") return ALL_GATES;
4715
- const list = String(spec).split(",").map((s) => s.trim()).filter(Boolean);
4716
- const valid = ALL_GATES;
4717
- const matched = list.filter((n) => valid.includes(n));
4718
- return matched.length > 0 ? matched : ALL_GATES;
4719
- }
4720
- function toPositiveInt(v, fallback) {
4721
- const n = typeof v === "number" ? v : parseInt(String(v ?? ""), 10);
4722
- return Number.isFinite(n) && n > 0 ? n : fallback;
4723
- }
4724
- function preview(list, max = 5) {
4725
- if (list.length <= max) return list.join(", ");
4726
- return `${list.slice(0, max).join(", ")} (+${list.length - max} more)`;
4727
- }
4728
- var SECRET_PATTERNS = [
4729
- /(^|\/)\.env(\.|$)/i,
4730
- /\.pem$/i,
4731
- /\.key$/i,
4732
- /(^|\/)(id_rsa|id_ed25519|id_ecdsa)(\.|$)/i,
4733
- // Match the keyword anywhere inside the filename, as a whole word (so e.g.
4734
- // `api-secrets.json`, `config/app.credentials.yaml`, `user-passwords.txt`
4735
- // all trip — while false friends like `secretary.md` do not).
4736
- /(^|\/)[^/]*\bsecrets?\b[^/]*$/i,
4737
- /(^|\/)[^/]*\bcredentials?\b[^/]*$/i,
4738
- /(^|\/)[^/]*\bpasswords?\b[^/]*$/i,
4739
- /(^|\/)[^/]*\bapi[-_]keys?\b[^/]*$/i,
4740
- /(^|\/)\.netrc$/i,
4741
- /(^|\/)\.npmrc$/i
4742
- ];
4743
- function isSecretPath(p) {
4744
- return SECRET_PATTERNS.some((r) => r.test(p));
4745
- }
4746
- var DEP_FILES = /* @__PURE__ */ new Set([
4747
- "package.json",
4748
- "pnpm-lock.yaml",
4749
- "package-lock.json",
4750
- "yarn.lock",
4751
- "requirements.txt",
4752
- "Pipfile",
4753
- "Pipfile.lock",
4754
- "poetry.lock",
4755
- "go.mod",
4756
- "go.sum",
4757
- "Cargo.toml",
4758
- "Cargo.lock",
4759
- "Gemfile",
4760
- "Gemfile.lock"
4761
- ]);
4762
- function isDepFile(p) {
4763
- return DEP_FILES.has(p.split("/").pop() ?? "");
4764
- }
4765
- function isTestFile(p) {
4766
- return /(^|\/)(tests?|__tests__|spec)\//i.test(p) || /\.(test|spec)\.[a-z0-9]+$/i.test(p);
4767
- }
4768
- function collectBranchChangedFiles(ctx) {
4769
- const base = ctx.config.git.defaultBranch;
4770
- for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
4771
- try {
4772
- const out = execFileSync18("git", ["diff", "--name-only", ref], {
4773
- cwd: ctx.cwd,
4774
- encoding: "utf-8",
4775
- stdio: ["pipe", "pipe", "pipe"]
4776
- });
4777
- const files = out.split("\n").map((s) => s.trim()).filter(Boolean);
4778
- if (files.length > 0) return files;
4779
- } catch {
4780
- }
4781
- }
4782
- return ctx.data.changedFiles ?? [];
4783
- }
4784
- function computeDiffStats(ctx) {
4785
- const base = ctx.config.git.defaultBranch;
4786
- for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
4787
- try {
4788
- const out = execFileSync18("git", ["diff", "--shortstat", ref], {
4789
- cwd: ctx.cwd,
4790
- encoding: "utf-8",
4791
- stdio: ["pipe", "pipe", "pipe"]
4792
- }).trim();
4793
- if (out) return parseShortstat(out);
4794
- } catch {
4795
- }
4796
- }
4797
- return null;
4798
- }
4799
- function parseShortstat(s) {
4800
- const ins = /(\d+)\s+insertions?/.exec(s);
4801
- const del = /(\d+)\s+deletions?/.exec(s);
4802
- return {
4803
- insertions: ins ? parseInt(ins[1], 10) : 0,
4804
- deletions: del ? parseInt(del[1], 10) : 0
4805
- };
4806
- }
4807
- function listDeletedFilesInHeadCommit(cwd) {
4808
- try {
4809
- const out = execFileSync18("git", ["show", "--name-status", "--pretty=format:", "HEAD"], {
4810
- cwd,
4811
- encoding: "utf-8",
4812
- stdio: ["pipe", "pipe", "pipe"]
4813
- });
4814
- return out.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("D ")).map((l) => l.slice(2).trim()).filter(Boolean);
4815
- } catch {
4816
- return [];
4817
- }
4818
- }
4819
- function formatAdvisory(violations, compareUrl) {
4820
- const lines = [];
4821
- lines.push(`\u23F8\uFE0F **${ADVISORY_MARKER}.**`);
4822
- lines.push("");
4823
- lines.push("The branch was pushed but **no PR was opened** \u2014 waiting for human approval:");
4824
- lines.push("");
4825
- for (const v of violations) {
4826
- lines.push(`- **\`${v.name}\`** _(${v.severity})_ \u2014 ${v.reason}`);
4827
- }
4828
- lines.push("");
4829
- if (compareUrl) {
4830
- lines.push(`\u{1F4CE} Review the branch diff: ${compareUrl}`);
4831
- lines.push("");
4832
- }
4833
- lines.push("**To approve and resume**, post a comment on this issue or PR:");
4834
- lines.push("");
4835
- lines.push("> `@kody2 approve`");
4836
- lines.push("");
4837
- lines.push(
4838
- "kody2 will open the PR and continue the flow from this checkpoint. No re-running the agent."
4839
- );
4840
- return lines.join("\n");
4841
- }
4842
- function computeCompareUrl(ctx) {
4843
- const branch = ctx.data.branch;
4844
- if (!branch) return null;
4845
- const owner = ctx.config.github?.owner;
4846
- const repo = ctx.config.github?.repo;
4847
- if (!owner || !repo) return null;
4848
- const base = ctx.config.git.defaultBranch;
4849
- return `https://github.com/${owner}/${repo}/compare/${base}...${branch}`;
4850
- }
4851
-
4852
4497
  // src/scripts/runFlow.ts
4853
4498
  var runFlow = async (ctx) => {
4854
4499
  const issueNumber = ctx.args.issue;
@@ -4945,8 +4590,8 @@ var skipAgent = async (ctx) => {
4945
4590
  };
4946
4591
 
4947
4592
  // src/scripts/startFlow.ts
4948
- import { execFileSync as execFileSync19 } from "child_process";
4949
- var API_TIMEOUT_MS8 = 3e4;
4593
+ import { execFileSync as execFileSync17 } from "child_process";
4594
+ var API_TIMEOUT_MS7 = 3e4;
4950
4595
  var startFlow = async (ctx, profile, _agentResult, args) => {
4951
4596
  const entry = args?.entry;
4952
4597
  if (!entry) {
@@ -4979,8 +4624,8 @@ function postKody2Comment(target, issueNumber, state, next, cwd) {
4979
4624
  const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
4980
4625
  const body = `@kody2 ${next}`;
4981
4626
  try {
4982
- execFileSync19("gh", [sub, "comment", String(targetNumber), "--body", body], {
4983
- timeout: API_TIMEOUT_MS8,
4627
+ execFileSync17("gh", [sub, "comment", String(targetNumber), "--body", body], {
4628
+ timeout: API_TIMEOUT_MS7,
4984
4629
  cwd,
4985
4630
  stdio: ["ignore", "pipe", "pipe"]
4986
4631
  });
@@ -4999,7 +4644,7 @@ function parsePr2(url) {
4999
4644
  }
5000
4645
 
5001
4646
  // src/scripts/syncFlow.ts
5002
- import { execFileSync as execFileSync20 } from "child_process";
4647
+ import { execFileSync as execFileSync18 } from "child_process";
5003
4648
  var syncFlow = async (ctx) => {
5004
4649
  ctx.skipAgent = true;
5005
4650
  const prNumber = ctx.args.pr;
@@ -5058,7 +4703,7 @@ function bail2(ctx, prNumber, reason) {
5058
4703
  }
5059
4704
  function revParseHead(cwd) {
5060
4705
  try {
5061
- return execFileSync20("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
4706
+ return execFileSync18("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
5062
4707
  } catch {
5063
4708
  return "";
5064
4709
  }
@@ -5066,9 +4711,9 @@ function revParseHead(cwd) {
5066
4711
  function pushBranch(branch, cwd) {
5067
4712
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
5068
4713
  try {
5069
- execFileSync20("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
4714
+ execFileSync18("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
5070
4715
  } catch {
5071
- execFileSync20("git", ["push", "--force-with-lease", "-u", "origin", branch], {
4716
+ execFileSync18("git", ["push", "--force-with-lease", "-u", "origin", branch], {
5072
4717
  cwd,
5073
4718
  env,
5074
4719
  stdio: ["ignore", "pipe", "pipe"]
@@ -5310,9 +4955,7 @@ var postflightScripts = {
5310
4955
  finishFlow,
5311
4956
  advanceFlow,
5312
4957
  persistFlowState,
5313
- postClassification,
5314
- riskGate,
5315
- applyApprovals
4958
+ postClassification
5316
4959
  };
5317
4960
  var allScriptNames = /* @__PURE__ */ new Set([
5318
4961
  ...Object.keys(preflightScripts),
@@ -5320,7 +4963,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
5320
4963
  ]);
5321
4964
 
5322
4965
  // src/tools.ts
5323
- import { execFileSync as execFileSync21 } from "child_process";
4966
+ import { execFileSync as execFileSync19 } from "child_process";
5324
4967
  function verifyCliTools(tools, cwd) {
5325
4968
  const out = [];
5326
4969
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -5353,7 +4996,7 @@ function verifyOne(tool, cwd) {
5353
4996
  }
5354
4997
  function runShell2(cmd, cwd, timeoutMs = 3e4) {
5355
4998
  try {
5356
- execFileSync21("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
4999
+ execFileSync19("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
5357
5000
  return true;
5358
5001
  } catch {
5359
5002
  return false;
@@ -5683,7 +5326,7 @@ function detectPackageManager2(cwd) {
5683
5326
  }
5684
5327
  function shellOut(cmd, args, cwd, stream = true) {
5685
5328
  try {
5686
- execFileSync22(cmd, args, {
5329
+ execFileSync20(cmd, args, {
5687
5330
  cwd,
5688
5331
  stdio: stream ? "inherit" : "pipe",
5689
5332
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -5696,7 +5339,7 @@ function shellOut(cmd, args, cwd, stream = true) {
5696
5339
  }
5697
5340
  function isOnPath(bin) {
5698
5341
  try {
5699
- execFileSync22("which", [bin], { stdio: "pipe" });
5342
+ execFileSync20("which", [bin], { stdio: "pipe" });
5700
5343
  return true;
5701
5344
  } catch {
5702
5345
  return false;
@@ -5730,7 +5373,7 @@ function installLitellmIfNeeded(cwd) {
5730
5373
  } catch {
5731
5374
  }
5732
5375
  try {
5733
- execFileSync22("python3", ["-c", "import litellm"], { stdio: "pipe" });
5376
+ execFileSync20("python3", ["-c", "import litellm"], { stdio: "pipe" });
5734
5377
  process.stdout.write("\u2192 kody2: litellm already installed\n");
5735
5378
  return 0;
5736
5379
  } catch {
@@ -5740,16 +5383,16 @@ function installLitellmIfNeeded(cwd) {
5740
5383
  }
5741
5384
  function configureGitIdentity(cwd) {
5742
5385
  try {
5743
- const name = execFileSync22("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
5386
+ const name = execFileSync20("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
5744
5387
  if (name) return;
5745
5388
  } catch {
5746
5389
  }
5747
5390
  try {
5748
- execFileSync22("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
5391
+ execFileSync20("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
5749
5392
  } catch {
5750
5393
  }
5751
5394
  try {
5752
- execFileSync22("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
5395
+ execFileSync20("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
5753
5396
  cwd,
5754
5397
  stdio: "pipe"
5755
5398
  });
@@ -5926,9 +5569,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
5926
5569
  if (paths.length === 0) return;
5927
5570
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
5928
5571
  try {
5929
- execFileSync23("git", ["add", ...paths], opts);
5930
- execFileSync23("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
5931
- execFileSync23("git", ["push", "--quiet", "origin", "HEAD"], opts);
5572
+ execFileSync21("git", ["add", ...paths], opts);
5573
+ execFileSync21("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
5574
+ execFileSync21("git", ["push", "--quiet", "origin", "HEAD"], opts);
5932
5575
  } catch (err) {
5933
5576
  const msg = err instanceof Error ? err.message : String(err);
5934
5577
  process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
@@ -49,21 +49,11 @@
49
49
  "description": "kody2: applying review feedback"
50
50
  }
51
51
  },
52
- {
53
- "script": "fixFlow"
54
- },
55
- {
56
- "script": "loadTaskState"
57
- },
58
- {
59
- "script": "loadConventions"
60
- },
61
- {
62
- "script": "loadCoverageRules"
63
- },
64
- {
65
- "script": "composePrompt"
66
- }
52
+ { "script": "fixFlow" },
53
+ { "script": "loadTaskState" },
54
+ { "script": "loadConventions" },
55
+ { "script": "loadCoverageRules" },
56
+ { "script": "composePrompt" }
67
57
  ],
68
58
  "postflight": [
69
59
  { "script": "parseAgentResult" },
@@ -71,12 +61,11 @@
71
61
  { "script": "verify" },
72
62
  { "script": "checkCoverageWithRetry" },
73
63
  { "script": "commitAndPush" },
74
- { "script": "riskGate" },
75
- { "script": "ensurePr", "runWhen": { "data.riskGate.decision": "allow" } },
76
- { "script": "postIssueComment", "runWhen": { "data.riskGate.decision": "allow" } },
64
+ { "script": "ensurePr" },
65
+ { "script": "postIssueComment" },
77
66
  { "script": "writeRunSummary" },
78
- { "script": "saveTaskState", "runWhen": { "data.riskGate.decision": "allow" } },
79
- { "script": "advanceFlow", "runWhen": { "data.riskGate.decision": "allow" } }
67
+ { "script": "saveTaskState" },
68
+ { "script": "advanceFlow" }
80
69
  ]
81
70
  },
82
71
  "output": {
@@ -42,24 +42,12 @@
42
42
  "description": "kody2: implementing the change"
43
43
  }
44
44
  },
45
- {
46
- "script": "runFlow"
47
- },
48
- {
49
- "script": "loadTaskState"
50
- },
51
- {
52
- "script": "resolveArtifacts"
53
- },
54
- {
55
- "script": "loadConventions"
56
- },
57
- {
58
- "script": "loadCoverageRules"
59
- },
60
- {
61
- "script": "composePrompt"
62
- }
45
+ { "script": "runFlow" },
46
+ { "script": "loadTaskState" },
47
+ { "script": "resolveArtifacts" },
48
+ { "script": "loadConventions" },
49
+ { "script": "loadCoverageRules" },
50
+ { "script": "composePrompt" }
63
51
  ],
64
52
  "postflight": [
65
53
  { "script": "parseAgentResult" },
@@ -67,13 +55,12 @@
67
55
  { "script": "verify" },
68
56
  { "script": "checkCoverageWithRetry" },
69
57
  { "script": "commitAndPush" },
70
- { "script": "riskGate" },
71
- { "script": "ensurePr", "runWhen": { "data.riskGate.decision": "allow" } },
72
- { "script": "postIssueComment", "runWhen": { "data.riskGate.decision": "allow" } },
58
+ { "script": "ensurePr" },
59
+ { "script": "postIssueComment" },
73
60
  { "script": "writeRunSummary" },
74
- { "script": "saveTaskState", "runWhen": { "data.riskGate.decision": "allow" } },
75
- { "script": "mirrorStateToPr", "runWhen": { "data.riskGate.decision": "allow" } },
76
- { "script": "advanceFlow", "runWhen": { "data.riskGate.decision": "allow" } }
61
+ { "script": "saveTaskState" },
62
+ { "script": "mirrorStateToPr" },
63
+ { "script": "advanceFlow" }
77
64
  ]
78
65
  },
79
66
  "input": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.2.61",
3
+ "version": "0.2.63",
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",
@@ -20,8 +20,6 @@
20
20
  # 2. Pushes from kody2 won't trigger downstream workflows.
21
21
  # 3. Any commit that modifies `.github/workflows/*` is REJECTED by
22
22
  # GitHub — the default GITHUB_TOKEN can't touch workflow files.
23
- # (The `workflow-edit` risk gate is effectively unreachable without
24
- # a token carrying the `workflow` scope.)
25
23
  # Set KODY_TOKEN in repo Settings → Secrets → Actions.
26
24
 
27
25
  name: kody2
@@ -1,47 +0,0 @@
1
- {
2
- "name": "approve",
3
- "role": "utility",
4
- "describe": "Acknowledge pending risk gate(s) on an issue or PR: applies kody-approve:* labels on both the issue and its PR (if discoverable via task state), then re-triggers the paused flow. Invoked by `@kody2 approve`.",
5
- "inputs": [
6
- {
7
- "name": "issue",
8
- "flag": "--issue",
9
- "type": "int",
10
- "required": false,
11
- "describe": "Issue number (when approving on the originating issue)."
12
- },
13
- {
14
- "name": "pr",
15
- "flag": "--pr",
16
- "type": "int",
17
- "required": false,
18
- "describe": "PR number (when approving on the PR side)."
19
- }
20
- ],
21
- "claudeCode": {
22
- "model": "inherit",
23
- "permissionMode": "default",
24
- "maxTurns": 0,
25
- "maxThinkingTokens": null,
26
- "systemPromptAppend": null,
27
- "tools": [],
28
- "hooks": [],
29
- "skills": [],
30
- "commands": [],
31
- "subagents": [],
32
- "plugins": [],
33
- "mcpServers": []
34
- },
35
- "cliTools": [],
36
- "scripts": {
37
- "preflight": [
38
- { "script": "skipAgent" }
39
- ],
40
- "postflight": [
41
- { "script": "applyApprovals" }
42
- ]
43
- },
44
- "output": {
45
- "actionTypes": []
46
- }
47
- }
@@ -1 +0,0 @@
1
- (approve is an agent-less utility; this file is a placeholder kept to satisfy the profile layout convention.)