@kody-ade/kody-engine 0.4.139 → 0.4.140

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js CHANGED
@@ -93,7 +93,7 @@ var init_events = __esm({
93
93
  // src/verify.ts
94
94
  import { spawn } from "child_process";
95
95
  function runCommand(command, cwd) {
96
- return new Promise((resolve4) => {
96
+ return new Promise((resolve5) => {
97
97
  const start = Date.now();
98
98
  const child = spawn(command, {
99
99
  cwd,
@@ -122,11 +122,11 @@ function runCommand(command, cwd) {
122
122
  child.on("exit", (code) => {
123
123
  clearTimeout(timer);
124
124
  const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
125
- resolve4({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
125
+ resolve5({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
126
126
  });
127
127
  child.on("error", (err) => {
128
128
  clearTimeout(timer);
129
- resolve4({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
129
+ resolve5({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
130
130
  });
131
131
  });
132
132
  }
@@ -926,7 +926,7 @@ var init_loadPriorArt = __esm({
926
926
  // package.json
927
927
  var package_default = {
928
928
  name: "@kody-ade/kody-engine",
929
- version: "0.4.139",
929
+ version: "0.4.140",
930
930
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
931
931
  license: "MIT",
932
932
  type: "module",
@@ -1641,10 +1641,10 @@ async function runAgent(opts) {
1641
1641
  let timer;
1642
1642
  let next;
1643
1643
  if (turnTimeoutMs > 0) {
1644
- const timeoutPromise = new Promise((resolve4) => {
1644
+ const timeoutPromise = new Promise((resolve5) => {
1645
1645
  timer = setTimeout(() => {
1646
1646
  timedOut = true;
1647
- resolve4({ done: true, value: void 0 });
1647
+ resolve5({ done: true, value: void 0 });
1648
1648
  }, turnTimeoutMs);
1649
1649
  });
1650
1650
  next = await Promise.race([nextPromise, timeoutPromise]);
@@ -2318,7 +2318,7 @@ async function waitForNextUserMessage(opts) {
2318
2318
  }
2319
2319
  }
2320
2320
  function sleep(ms) {
2321
- return new Promise((resolve4) => setTimeout(resolve4, ms));
2321
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
2322
2322
  }
2323
2323
  function currentBranch(cwd) {
2324
2324
  try {
@@ -2867,7 +2867,7 @@ function coerceBare(spec, value) {
2867
2867
  init_issue();
2868
2868
 
2869
2869
  // src/executor.ts
2870
- import { execFileSync as execFileSync30, spawn as spawn8 } from "child_process";
2870
+ import { execFileSync as execFileSync30, spawn as spawn9 } from "child_process";
2871
2871
  import * as fs40 from "fs";
2872
2872
  import * as path37 from "path";
2873
2873
 
@@ -4291,6 +4291,7 @@ var advanceFlow = async (ctx, profile) => {
4291
4291
 
4292
4292
  // src/scripts/brainServe.ts
4293
4293
  import { createServer } from "http";
4294
+ import { spawn as spawn3, spawnSync } from "child_process";
4294
4295
  import * as fs18 from "fs";
4295
4296
  import * as path17 from "path";
4296
4297
 
@@ -4473,17 +4474,17 @@ function authOk(req, expected) {
4473
4474
  return false;
4474
4475
  }
4475
4476
  function readJsonBody(req) {
4476
- return new Promise((resolve4, reject) => {
4477
+ return new Promise((resolve5, reject) => {
4477
4478
  const chunks = [];
4478
4479
  req.on("data", (c) => chunks.push(c));
4479
4480
  req.on("end", () => {
4480
4481
  const raw = Buffer.concat(chunks).toString("utf-8");
4481
4482
  if (!raw.trim()) {
4482
- resolve4({});
4483
+ resolve5({});
4483
4484
  return;
4484
4485
  }
4485
4486
  try {
4486
- resolve4(JSON.parse(raw));
4487
+ resolve5(JSON.parse(raw));
4487
4488
  } catch (err) {
4488
4489
  reject(err instanceof Error ? err : new Error(String(err)));
4489
4490
  }
@@ -4491,6 +4492,13 @@ function readJsonBody(req) {
4491
4492
  req.on("error", reject);
4492
4493
  });
4493
4494
  }
4495
+ function strField(body, key) {
4496
+ if (typeof body === "object" && body !== null && key in body) {
4497
+ const v = body[key];
4498
+ if (typeof v === "string" && v.trim()) return v.trim();
4499
+ }
4500
+ return void 0;
4501
+ }
4494
4502
  function sendJson(res, status, body) {
4495
4503
  res.writeHead(status, { "content-type": "application/json" });
4496
4504
  res.end(JSON.stringify(body));
@@ -4588,6 +4596,51 @@ function streamToRes(res, dir, chatId, since) {
4588
4596
  );
4589
4597
  res.on("close", unsubscribe);
4590
4598
  }
4599
+ var REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
4600
+ var repoClones = /* @__PURE__ */ new Map();
4601
+ async function ensureRepoCwd(opts) {
4602
+ const repo = opts.repo?.trim();
4603
+ if (!repo || !REPO_RE.test(repo)) return opts.baseCwd;
4604
+ const root = path17.resolve(opts.reposRoot);
4605
+ const dir = path17.resolve(root, repo);
4606
+ if (dir !== root && !dir.startsWith(root + path17.sep)) return opts.baseCwd;
4607
+ if (fs18.existsSync(path17.join(dir, ".git"))) return dir;
4608
+ const inflight = repoClones.get(dir);
4609
+ if (inflight) {
4610
+ await inflight;
4611
+ return dir;
4612
+ }
4613
+ const p = opts.cloneRepo(repo, opts.repoToken, dir).finally(() => {
4614
+ if (repoClones.get(dir) === p) repoClones.delete(dir);
4615
+ });
4616
+ repoClones.set(dir, p);
4617
+ await p;
4618
+ return dir;
4619
+ }
4620
+ var defaultCloneRepo = (repo, token, dir) => {
4621
+ fs18.mkdirSync(path17.dirname(dir), { recursive: true });
4622
+ const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
4623
+ return new Promise((resolve5, reject) => {
4624
+ const child = spawn3("git", ["clone", "--depth=1", authUrl, dir], {
4625
+ stdio: "inherit"
4626
+ });
4627
+ child.on("exit", (code) => {
4628
+ if (code !== 0) {
4629
+ reject(new Error(`git clone ${repo} failed (exit ${code})`));
4630
+ return;
4631
+ }
4632
+ try {
4633
+ const name = process.env.GIT_AUTHOR_NAME ?? "Kody Bot";
4634
+ const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
4635
+ spawnSync("git", ["-C", dir, "config", "user.name", name]);
4636
+ spawnSync("git", ["-C", dir, "config", "user.email", email]);
4637
+ } catch {
4638
+ }
4639
+ resolve5();
4640
+ });
4641
+ child.on("error", reject);
4642
+ });
4643
+ };
4591
4644
  async function handleChatTurn(req, res, chatId, opts) {
4592
4645
  let body;
4593
4646
  try {
@@ -4601,6 +4654,8 @@ async function handleChatTurn(req, res, chatId, opts) {
4601
4654
  sendJson(res, 400, { error: "message required" });
4602
4655
  return;
4603
4656
  }
4657
+ const repo = strField(body, "repo");
4658
+ const repoToken = strField(body, "repoToken");
4604
4659
  const sessionFile = sessionFilePath(opts.cwd, chatId);
4605
4660
  fs18.mkdirSync(path17.dirname(sessionFile), { recursive: true });
4606
4661
  appendTurn(sessionFile, {
@@ -4611,32 +4666,42 @@ async function handleChatTurn(req, res, chatId, opts) {
4611
4666
  const sinceFloor = getLastSeq(opts.cwd, chatId);
4612
4667
  const emitToLog = beginTurn(opts.cwd, chatId);
4613
4668
  const sink = new BrokerSink(emitToLog, chatId);
4614
- void enqueue(
4615
- chatId,
4616
- () => opts.runTurn({
4617
- sessionId: chatId,
4618
- sessionFile,
4619
- cwd: opts.cwd,
4620
- model: opts.model,
4621
- litellmUrl: opts.litellmUrl,
4622
- sink
4623
- }).catch((err) => {
4669
+ void enqueue(chatId, async () => {
4670
+ try {
4671
+ const agentCwd = await ensureRepoCwd({
4672
+ baseCwd: opts.cwd,
4673
+ reposRoot: opts.reposRoot,
4674
+ repo,
4675
+ repoToken,
4676
+ cloneRepo: opts.cloneRepo
4677
+ });
4678
+ await opts.runTurn({
4679
+ sessionId: chatId,
4680
+ sessionFile,
4681
+ cwd: agentCwd,
4682
+ model: opts.model,
4683
+ litellmUrl: opts.litellmUrl,
4684
+ sink
4685
+ });
4686
+ } catch (err) {
4624
4687
  const errMsg3 = err instanceof Error ? err.message : String(err);
4625
4688
  process.stderr.write(`[brain-serve] chat turn failed: ${errMsg3}
4626
4689
  `);
4627
4690
  endTurnIfUnterminated(opts.cwd, chatId, errMsg3);
4628
- }).finally(() => {
4691
+ } finally {
4629
4692
  endTurnIfUnterminated(
4630
4693
  opts.cwd,
4631
4694
  chatId,
4632
4695
  "Brain turn ended without a reply (the machine may have restarted mid-turn) \u2014 please resend your message"
4633
4696
  );
4634
- })
4635
- );
4697
+ }
4698
+ });
4636
4699
  streamToRes(res, opts.cwd, chatId, sinceFloor);
4637
4700
  }
4638
4701
  function buildServer(opts) {
4639
4702
  const runTurn = opts.runTurn ?? runChatTurn;
4703
+ const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
4704
+ const reposRoot = opts.reposRoot ?? path17.join(path17.dirname(path17.resolve(opts.cwd)), "repos");
4640
4705
  return createServer(async (req, res) => {
4641
4706
  if (!req.method || !req.url) {
4642
4707
  sendJson(res, 400, { error: "bad request" });
@@ -4660,6 +4725,8 @@ function buildServer(opts) {
4660
4725
  }
4661
4726
  await handleChatTurn(req, res, chatId, {
4662
4727
  cwd: opts.cwd,
4728
+ reposRoot,
4729
+ cloneRepo,
4663
4730
  model: opts.model,
4664
4731
  litellmUrl: opts.litellmUrl,
4665
4732
  runTurn
@@ -4710,16 +4777,18 @@ var brainServe = async (ctx) => {
4710
4777
  const server = buildServer({
4711
4778
  apiKey,
4712
4779
  cwd: ctx.cwd,
4780
+ // Per-repo clones live here; defaults to a `repos` sibling of cwd.
4781
+ reposRoot: process.env.BRAIN_REPOS_ROOT?.trim() || void 0,
4713
4782
  model,
4714
4783
  litellmUrl
4715
4784
  });
4716
- await new Promise((resolve4) => {
4785
+ await new Promise((resolve5) => {
4717
4786
  server.listen(port, "0.0.0.0", () => {
4718
4787
  process.stdout.write(
4719
4788
  `[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
4720
4789
  `
4721
4790
  );
4722
- resolve4();
4791
+ resolve5();
4723
4792
  });
4724
4793
  });
4725
4794
  const shutdown = (signal) => {
@@ -5514,36 +5583,46 @@ var createQaGoal = async (ctx, _profile, agentResult) => {
5514
5583
  ctx.data.action = failedAction2("empty report body");
5515
5584
  return;
5516
5585
  }
5586
+ const { markdown } = splitReport(finalText);
5587
+ const verdict = detectVerdict(markdown);
5588
+ const existingIssue = ctx.args.issue;
5589
+ if (typeof existingIssue === "number" && existingIssue > 0) {
5590
+ try {
5591
+ postIssueComment(existingIssue, finalText, ctx.cwd);
5592
+ } catch (err) {
5593
+ const msg = err instanceof Error ? err.message : String(err);
5594
+ ctx.output.exitCode = 4;
5595
+ ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
5596
+ ctx.data.action = failedAction2(ctx.output.reason);
5597
+ return;
5598
+ }
5599
+ process.stdout.write(
5600
+ `
5601
+ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
5602
+ `
5603
+ );
5604
+ ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
5605
+ ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
5606
+ return;
5607
+ }
5608
+ await promoteReportToGoal(
5609
+ ctx,
5610
+ finalText,
5611
+ ctx.args.scope,
5612
+ ctx.args.goal
5613
+ );
5614
+ };
5615
+ async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
5517
5616
  const { markdown, data, jsonError } = splitReport(finalText);
5518
5617
  const verdict = detectVerdict(markdown);
5519
5618
  const findings = data?.findings ?? [];
5520
- const existingIssue = ctx.args.issue;
5521
5619
  if (findings.length === 0 || jsonError) {
5522
5620
  if (jsonError) {
5523
- process.stderr.write(`[createQaGoal] JSON parse: ${jsonError} \u2014 falling back to single-issue mode
5621
+ process.stderr.write(`[promoteReportToGoal] JSON parse: ${jsonError} \u2014 falling back to single-issue mode
5524
5622
  `);
5525
5623
  }
5526
- if (typeof existingIssue === "number" && existingIssue > 0) {
5527
- try {
5528
- postIssueComment(existingIssue, finalText, ctx.cwd);
5529
- } catch (err) {
5530
- const msg = err instanceof Error ? err.message : String(err);
5531
- ctx.output.exitCode = 4;
5532
- ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
5533
- ctx.data.action = failedAction2(ctx.output.reason);
5534
- return;
5535
- }
5536
- process.stdout.write(
5537
- `
5538
- QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/issues/${existingIssue} (verdict: ${verdict})
5539
- `
5540
- );
5541
- ctx.data.action = qaAction(verdict, { issueNumber: existingIssue, mode: "comment" });
5542
- ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
5543
- return;
5544
- }
5545
5624
  ensureLabel(FINDING_LABEL, "ededed", "kody: QA finding", ctx.cwd);
5546
- const scope2 = ctx.args.scope;
5625
+ const scope2 = scopeArg;
5547
5626
  const title = `QA [${verdict}]: ${scope2?.trim() || "smoke"} \u2014 ${todayIso()}`.slice(0, 240);
5548
5627
  let url = "";
5549
5628
  try {
@@ -5571,8 +5650,8 @@ QA_REPORT_POSTED=${url} (verdict: ${verdict})
5571
5650
  ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
5572
5651
  return;
5573
5652
  }
5574
- const explicitGoal = ctx.args.goal?.trim();
5575
- const scope = ctx.args.scope;
5653
+ const explicitGoal = explicitGoalArg?.trim();
5654
+ const scope = scopeArg;
5576
5655
  let goalId;
5577
5656
  let manifestIssueNumber = null;
5578
5657
  let manifestCreated = false;
@@ -5670,7 +5749,7 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
5670
5749
  mode: explicitGoal ? "goal-attach" : manifestCreated ? "goal-create" : "goal-append"
5671
5750
  });
5672
5751
  ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
5673
- };
5752
+ }
5674
5753
 
5675
5754
  // src/goal/operations.ts
5676
5755
  init_issue();
@@ -9382,6 +9461,17 @@ var parseJobStateFromAgentResult = async (ctx, _profile, agentResult, args) => {
9382
9461
  }
9383
9462
  const result = extractNextStateFromText(agentResult.finalText, fenceLabel, prevRev);
9384
9463
  if (result.error) {
9464
+ const cleanFinishNoBlock = result.error.startsWith("missing `") && agentResult.outcome === "completed" && loaded != null;
9465
+ if (cleanFinishNoBlock) {
9466
+ ctx.data.nextJobState = {
9467
+ version: 1,
9468
+ rev: prevRev + 1,
9469
+ cursor: loaded.state.cursor,
9470
+ data: loaded.state.data,
9471
+ done: loaded.state.done
9472
+ };
9473
+ return;
9474
+ }
9385
9475
  ctx.data.nextStateParseError = result.error.startsWith("missing `") ? `agent did not emit a \`${fenceLabel}\` fenced block` : result.error;
9386
9476
  return;
9387
9477
  }
@@ -9502,7 +9592,7 @@ var persistFlowState = async (ctx) => {
9502
9592
  };
9503
9593
 
9504
9594
  // src/scripts/poolServe.ts
9505
- import { spawn as spawn3 } from "child_process";
9595
+ import { spawn as spawn4 } from "child_process";
9506
9596
  import { createServer as createServer2 } from "http";
9507
9597
 
9508
9598
  // src/pool/fly.ts
@@ -10072,14 +10162,14 @@ function sendJson2(res, status, body) {
10072
10162
  res.end(JSON.stringify(body));
10073
10163
  }
10074
10164
  function readJsonBody2(req) {
10075
- return new Promise((resolve4, reject) => {
10165
+ return new Promise((resolve5, reject) => {
10076
10166
  const chunks = [];
10077
10167
  req.on("data", (c) => chunks.push(c));
10078
10168
  req.on("end", () => {
10079
10169
  const raw = Buffer.concat(chunks).toString("utf-8");
10080
- if (!raw.trim()) return resolve4({});
10170
+ if (!raw.trim()) return resolve5({});
10081
10171
  try {
10082
- resolve4(JSON.parse(raw));
10172
+ resolve5(JSON.parse(raw));
10083
10173
  } catch (err) {
10084
10174
  reject(err instanceof Error ? err : new Error(String(err)));
10085
10175
  }
@@ -10121,7 +10211,7 @@ function superviseLitellm() {
10121
10211
  let restarts = 0;
10122
10212
  const start = () => {
10123
10213
  log(`starting litellm child (port ${port}, host ${host})`);
10124
- const child = spawn3("litellm", ["--config", config, "--port", port, "--host", host], {
10214
+ const child = spawn4("litellm", ["--config", config, "--port", port, "--host", host], {
10125
10215
  stdio: "inherit"
10126
10216
  });
10127
10217
  child.on("exit", (code) => {
@@ -10226,10 +10316,10 @@ var poolServe = async (ctx) => {
10226
10316
  }
10227
10317
  });
10228
10318
  const apiHost = process.env.POOL_API_HOST ?? "::";
10229
- await new Promise((resolve4) => {
10319
+ await new Promise((resolve5) => {
10230
10320
  server.listen(apiPort, apiHost, () => {
10231
10321
  log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
10232
- resolve4();
10322
+ resolve5();
10233
10323
  });
10234
10324
  });
10235
10325
  const shutdown = (signal) => {
@@ -10742,6 +10832,46 @@ function pushEmptyCommit(branch, cwd) {
10742
10832
  }
10743
10833
  }
10744
10834
 
10835
+ // src/scripts/promoteQaGoal.ts
10836
+ init_issue();
10837
+ var REPORT_JSON_OPEN2 = "<!-- KODY_QA_REPORT_JSON";
10838
+ var promoteQaGoal = async (ctx) => {
10839
+ ctx.skipAgent = true;
10840
+ const issueNum = ctx.args.issue;
10841
+ if (typeof issueNum !== "number" || issueNum <= 0) {
10842
+ ctx.output.exitCode = 2;
10843
+ ctx.output.reason = "qa-goal requires --issue <n>";
10844
+ process.stderr.write("[qa-goal] missing --issue\n");
10845
+ return;
10846
+ }
10847
+ let report;
10848
+ try {
10849
+ const issue = getIssue(issueNum, ctx.cwd);
10850
+ const reportComment = [...issue.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
10851
+ if (!reportComment) {
10852
+ ctx.output.exitCode = 3;
10853
+ ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
10854
+ process.stderr.write(`[qa-goal] ${ctx.output.reason}
10855
+ `);
10856
+ return;
10857
+ }
10858
+ report = reportComment.body;
10859
+ } catch (err) {
10860
+ const msg = err instanceof Error ? err.message : String(err);
10861
+ ctx.output.exitCode = 3;
10862
+ ctx.output.reason = `failed to read issue #${issueNum}: ${msg}`;
10863
+ process.stderr.write(`[qa-goal] ${ctx.output.reason}
10864
+ `);
10865
+ return;
10866
+ }
10867
+ await promoteReportToGoal(
10868
+ ctx,
10869
+ report,
10870
+ ctx.args.scope,
10871
+ ctx.args.goal
10872
+ );
10873
+ };
10874
+
10745
10875
  // src/deployments.ts
10746
10876
  init_issue();
10747
10877
  function findPreviewDeploymentUrl(prNumber, cwd) {
@@ -11083,7 +11213,7 @@ function resolveBaseOverride(value) {
11083
11213
  }
11084
11214
 
11085
11215
  // src/scripts/runnerServe.ts
11086
- import { spawn as spawn4 } from "child_process";
11216
+ import { spawn as spawn5 } from "child_process";
11087
11217
  import { createServer as createServer3 } from "http";
11088
11218
  import * as fs37 from "fs";
11089
11219
  var DEFAULT_PORT2 = 8080;
@@ -11107,17 +11237,17 @@ function authOk2(req, expected) {
11107
11237
  return false;
11108
11238
  }
11109
11239
  function readJsonBody3(req) {
11110
- return new Promise((resolve4, reject) => {
11240
+ return new Promise((resolve5, reject) => {
11111
11241
  const chunks = [];
11112
11242
  req.on("data", (c) => chunks.push(c));
11113
11243
  req.on("end", () => {
11114
11244
  const raw = Buffer.concat(chunks).toString("utf-8");
11115
11245
  if (!raw.trim()) {
11116
- resolve4({});
11246
+ resolve5({});
11117
11247
  return;
11118
11248
  }
11119
11249
  try {
11120
- resolve4(JSON.parse(raw));
11250
+ resolve5(JSON.parse(raw));
11121
11251
  } catch (err) {
11122
11252
  reject(err instanceof Error ? err : new Error(String(err)));
11123
11253
  }
@@ -11192,13 +11322,13 @@ async function defaultRunJob(job) {
11192
11322
  ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
11193
11323
  ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
11194
11324
  };
11195
- const run = (cmd, args, cwd) => new Promise((resolve4) => {
11196
- const child = spawn4(cmd, args, { stdio: "inherit", env: childEnv, cwd });
11197
- child.on("exit", (code) => resolve4(code ?? 0));
11325
+ const run = (cmd, args, cwd) => new Promise((resolve5) => {
11326
+ const child = spawn5(cmd, args, { stdio: "inherit", env: childEnv, cwd });
11327
+ child.on("exit", (code) => resolve5(code ?? 0));
11198
11328
  child.on("error", (err) => {
11199
11329
  process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
11200
11330
  `);
11201
- resolve4(1);
11331
+ resolve5(1);
11202
11332
  });
11203
11333
  });
11204
11334
  process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
@@ -11285,11 +11415,11 @@ var runnerServe = async (ctx) => {
11285
11415
  const port = Number(process.env.PORT ?? DEFAULT_PORT2);
11286
11416
  const server = buildServer2({ apiKey });
11287
11417
  const host = process.env.RUNNER_HOST ?? "::";
11288
- await new Promise((resolve4) => {
11418
+ await new Promise((resolve5) => {
11289
11419
  server.listen(port, host, () => {
11290
11420
  process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
11291
11421
  `);
11292
- resolve4();
11422
+ resolve5();
11293
11423
  });
11294
11424
  });
11295
11425
  const shutdown = (signal) => {
@@ -11304,7 +11434,7 @@ var runnerServe = async (ctx) => {
11304
11434
  };
11305
11435
 
11306
11436
  // src/scripts/runTickScript.ts
11307
- import { spawnSync } from "child_process";
11437
+ import { spawnSync as spawnSync2 } from "child_process";
11308
11438
  import * as fs38 from "fs";
11309
11439
  import * as path36 from "path";
11310
11440
  var runTickScript = async (ctx, _profile, args) => {
@@ -11350,7 +11480,7 @@ var runTickScript = async (ctx, _profile, args) => {
11350
11480
  ctx.data.jobSlug = slug;
11351
11481
  ctx.data.jobState = loaded;
11352
11482
  const childEnv = buildChildEnv(process.env, Boolean(ctx.args.force));
11353
- const result = spawnSync("bash", [scriptPath], {
11483
+ const result = spawnSync2("bash", [scriptPath], {
11354
11484
  cwd: ctx.cwd,
11355
11485
  env: childEnv,
11356
11486
  stdio: ["ignore", "pipe", "pipe"],
@@ -11483,7 +11613,7 @@ function synthesizeAction(ctx) {
11483
11613
  }
11484
11614
 
11485
11615
  // src/scripts/serveFlow.ts
11486
- import { spawn as spawn5 } from "child_process";
11616
+ import { spawn as spawn6 } from "child_process";
11487
11617
  function parseTarget(positional) {
11488
11618
  if (!Array.isArray(positional) || positional.length === 0) return "none";
11489
11619
  const first = String(positional[0]).toLowerCase();
@@ -11532,15 +11662,15 @@ var serveFlow = async (ctx) => {
11532
11662
  if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
11533
11663
  `);
11534
11664
  const args = ["--dangerously-skip-permissions", "--model", model.model];
11535
- const child = spawn5("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
11536
- const exitCode = await new Promise((resolve4) => {
11537
- child.on("exit", (code) => resolve4(code ?? 0));
11665
+ const child = spawn6("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
11666
+ const exitCode = await new Promise((resolve5) => {
11667
+ child.on("exit", (code) => resolve5(code ?? 0));
11538
11668
  child.on("error", (err) => {
11539
11669
  process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
11540
11670
  `);
11541
11671
  process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
11542
11672
  `);
11543
- resolve4(1);
11673
+ resolve5(1);
11544
11674
  });
11545
11675
  });
11546
11676
  killProxy();
@@ -11553,7 +11683,7 @@ var serveFlow = async (ctx) => {
11553
11683
  if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
11554
11684
  `);
11555
11685
  try {
11556
- const code = spawn5("code", [ctx.cwd], { stdio: "inherit", env: editorEnv, detached: true });
11686
+ const code = spawn6("code", [ctx.cwd], { stdio: "inherit", env: editorEnv, detached: true });
11557
11687
  code.on("error", (err) => {
11558
11688
  process.stderr.write(`[kody serve] failed to launch VS Code: ${err.message}
11559
11689
  `);
@@ -11808,7 +11938,7 @@ var verify = async (ctx) => {
11808
11938
  };
11809
11939
 
11810
11940
  // src/scripts/verifyReproFails.ts
11811
- import { spawn as spawn6 } from "child_process";
11941
+ import { spawn as spawn7 } from "child_process";
11812
11942
  var TEST_TIMEOUT_MS = 10 * 60 * 1e3;
11813
11943
  var TAIL_CHARS2 = 8e3;
11814
11944
  var ANSI_RE2 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
@@ -11876,8 +12006,8 @@ function stripAnsi2(s) {
11876
12006
  return s.replace(ANSI_RE2, "");
11877
12007
  }
11878
12008
  function runCommand2(command, cwd) {
11879
- return new Promise((resolve4) => {
11880
- const child = spawn6(command, {
12009
+ return new Promise((resolve5) => {
12010
+ const child = spawn7(command, {
11881
12011
  cwd,
11882
12012
  shell: true,
11883
12013
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
@@ -11903,11 +12033,11 @@ function runCommand2(command, cwd) {
11903
12033
  }, TEST_TIMEOUT_MS);
11904
12034
  child.on("exit", (code) => {
11905
12035
  clearTimeout(timer);
11906
- resolve4({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
12036
+ resolve5({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
11907
12037
  });
11908
12038
  child.on("error", (err) => {
11909
12039
  clearTimeout(timer);
11910
- resolve4({ exitCode: -1, output: err.message });
12040
+ resolve5({ exitCode: -1, output: err.message });
11911
12041
  });
11912
12042
  });
11913
12043
  }
@@ -12206,7 +12336,7 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
12206
12336
  };
12207
12337
 
12208
12338
  // src/scripts/warmupMcp.ts
12209
- import { spawn as spawn7 } from "child_process";
12339
+ import { spawn as spawn8 } from "child_process";
12210
12340
  var PER_SERVER_TIMEOUT_MS = 6e4;
12211
12341
  var PER_REQUEST_TIMEOUT_MS = 2e4;
12212
12342
  var warmupMcp = async (_ctx, profile) => {
@@ -12228,7 +12358,7 @@ var warmupMcp = async (_ctx, profile) => {
12228
12358
  }
12229
12359
  };
12230
12360
  async function warmupOne(command, args, env) {
12231
- const child = spawn7(command, args, {
12361
+ const child = spawn8(command, args, {
12232
12362
  stdio: ["pipe", "pipe", "pipe"],
12233
12363
  env: env ? { ...process.env, ...env } : process.env
12234
12364
  });
@@ -12329,20 +12459,20 @@ function lineStream(stream) {
12329
12459
  tryDeliver();
12330
12460
  });
12331
12461
  return {
12332
- next: (timeoutMs) => new Promise((resolve4) => {
12462
+ next: (timeoutMs) => new Promise((resolve5) => {
12333
12463
  if (queue.length > 0) {
12334
- resolve4(queue.shift());
12464
+ resolve5(queue.shift());
12335
12465
  return;
12336
12466
  }
12337
12467
  if (ended) {
12338
- resolve4(null);
12468
+ resolve5(null);
12339
12469
  return;
12340
12470
  }
12341
- waiter = resolve4;
12471
+ waiter = resolve5;
12342
12472
  const t = setTimeout(() => {
12343
- if (waiter === resolve4) {
12473
+ if (waiter === resolve5) {
12344
12474
  waiter = null;
12345
- resolve4(null);
12475
+ resolve5(null);
12346
12476
  }
12347
12477
  }, Math.max(0, timeoutMs));
12348
12478
  t.unref?.();
@@ -12495,6 +12625,7 @@ var preflightScripts = {
12495
12625
  discoverQaContext,
12496
12626
  resolvePreviewUrl,
12497
12627
  resolveQaUrl,
12628
+ promoteQaGoal,
12498
12629
  composePrompt,
12499
12630
  setCommentTarget,
12500
12631
  setLifecycleLabel,
@@ -13056,7 +13187,7 @@ async function runShellEntry(entry, ctx, profile) {
13056
13187
  env[`KODY_CFG_${k}`] = v;
13057
13188
  }
13058
13189
  const timeoutMs = resolveShellTimeoutMs(entry);
13059
- const child = spawn8("bash", [shellPath, ...positional], {
13190
+ const child = spawn9("bash", [shellPath, ...positional], {
13060
13191
  cwd: ctx.cwd,
13061
13192
  env,
13062
13193
  stdio: ["pipe", "pipe", "pipe"],
@@ -13078,14 +13209,14 @@ async function runShellEntry(entry, ctx, profile) {
13078
13209
  let killTimer;
13079
13210
  let escalateTimer;
13080
13211
  const result = await new Promise(
13081
- (resolve4) => {
13212
+ (resolve5) => {
13082
13213
  let settled = false;
13083
13214
  const settle = (code, signal, spawnErr) => {
13084
13215
  if (settled) return;
13085
13216
  settled = true;
13086
13217
  if (killTimer) clearTimeout(killTimer);
13087
13218
  if (escalateTimer) clearTimeout(escalateTimer);
13088
- resolve4({ code, signal, spawnErr });
13219
+ resolve5({ code, signal, spawnErr });
13089
13220
  };
13090
13221
  child.on("error", (err) => settle(null, null, err));
13091
13222
  child.on("close", (code, signal) => settle(code, signal));
@@ -21,7 +21,7 @@
21
21
  "claudeCode": {
22
22
  "model": "inherit",
23
23
  "permissionMode": "default",
24
- "maxTurns": 20,
24
+ "maxTurns": 100,
25
25
  "maxThinkingTokens": null,
26
26
  "systemPromptAppend": null,
27
27
  "enableSubmitTool": true,
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "qa-goal",
3
+ "role": "primitive",
4
+ "kind": "oneshot",
5
+ "describe": "Operator-gated half of QA: promotes a QA report (already posted on an issue by qa-engineer) into a goal — manifest entry + one fix-ticket per finding + a committed .kody/goals/<id>/state.json. Deterministic, no agent. The qa / qa-sweep duties surface a `@kody qa-goal --issue <n>` inbox rec; this runs when the operator approves it, so QA never auto-creates goals on its own.",
6
+ "inputs": [
7
+ {
8
+ "name": "issue",
9
+ "flag": "--issue",
10
+ "type": "int",
11
+ "required": true,
12
+ "describe": "Issue number carrying qa-engineer's QA report (the comment with the <!-- KODY_QA_REPORT_JSON --> block)."
13
+ },
14
+ {
15
+ "name": "scope",
16
+ "flag": "--scope",
17
+ "type": "string",
18
+ "required": false,
19
+ "describe": "Optional scope label for the goal name (e.g. the changelog entry title). Defaults to 'smoke'."
20
+ },
21
+ {
22
+ "name": "goal",
23
+ "flag": "--goal",
24
+ "type": "string",
25
+ "required": false,
26
+ "describe": "Optional existing goal id to attach findings to instead of creating a new one."
27
+ }
28
+ ],
29
+ "claudeCode": {
30
+ "model": "inherit",
31
+ "permissionMode": "default",
32
+ "maxTurns": null,
33
+ "maxThinkingTokens": null,
34
+ "systemPromptAppend": null,
35
+ "tools": [],
36
+ "hooks": [],
37
+ "skills": [],
38
+ "commands": [],
39
+ "subagents": [],
40
+ "plugins": [],
41
+ "mcpServers": []
42
+ },
43
+ "cliTools": [
44
+ {
45
+ "name": "gh",
46
+ "install": {
47
+ "required": true,
48
+ "checkCommand": "command -v gh"
49
+ },
50
+ "verify": "gh auth status",
51
+ "usage": "Reads the QA report (`gh issue view --json comments`), appends to the goals manifest, and opens fix-ticket issues.",
52
+ "allowedUses": ["issue", "api"]
53
+ }
54
+ ],
55
+ "inputArtifacts": [],
56
+ "outputArtifacts": [],
57
+ "scripts": {
58
+ "preflight": [
59
+ {
60
+ "script": "promoteQaGoal"
61
+ }
62
+ ],
63
+ "postflight": []
64
+ }
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.139",
3
+ "version": "0.4.140",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",