@kody-ade/kody-engine 0.2.54 → 0.2.55

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.54",
6
+ version: "0.2.55",
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 execFileSync20 } 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
 
@@ -182,9 +182,24 @@ function loadConfig(projectDir = process.cwd()) {
182
182
  issueContext: parseIssueContext(raw.issueContext),
183
183
  testRequirements: parseTestRequirements(raw.testRequirements),
184
184
  defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable : void 0,
185
+ classify: parseClassifyConfig(raw.classify),
185
186
  release: parseReleaseConfig(raw.release)
186
187
  };
187
188
  }
189
+ function parseClassifyConfig(raw) {
190
+ if (!raw || typeof raw !== "object") return void 0;
191
+ const r = raw;
192
+ const out = {};
193
+ if (r.labelMap && typeof r.labelMap === "object") {
194
+ const entries = Object.entries(r.labelMap).filter(
195
+ ([, v]) => typeof v === "string" && v.length > 0
196
+ );
197
+ if (entries.length > 0) {
198
+ out.labelMap = Object.fromEntries(entries.map(([k, v]) => [k.toLowerCase(), String(v)]));
199
+ }
200
+ }
201
+ return Object.keys(out).length > 0 ? out : void 0;
202
+ }
188
203
  function parseReleaseConfig(raw) {
189
204
  if (!raw || typeof raw !== "object") return void 0;
190
205
  const r = raw;
@@ -528,7 +543,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
528
543
  }
529
544
 
530
545
  // src/kody2-cli.ts
531
- import { execFileSync as execFileSync19 } from "child_process";
546
+ import { execFileSync as execFileSync20 } from "child_process";
532
547
  import * as fs21 from "fs";
533
548
  import * as path18 from "path";
534
549
 
@@ -1499,6 +1514,41 @@ ${formatMissesForFeedback(misses)}`;
1499
1514
  ctx.data.coverageMisses = finalMisses;
1500
1515
  };
1501
1516
 
1517
+ // src/scripts/classifyByLabel.ts
1518
+ var VALID_CLASSES = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
1519
+ var classifyByLabel = async (ctx) => {
1520
+ const issue = ctx.data.issue;
1521
+ const labels = issue?.labels;
1522
+ if (!labels || labels.length === 0) return;
1523
+ const cfgMap = ctx.config.classify?.labelMap;
1524
+ const map = cfgMap ?? defaultLabelMap();
1525
+ for (const label of labels) {
1526
+ const candidate = map[label.toLowerCase()];
1527
+ if (candidate && VALID_CLASSES.has(candidate)) {
1528
+ ctx.data.classification = candidate;
1529
+ ctx.data.classificationSource = "label";
1530
+ ctx.data.classificationReason = `label \`${label}\` \u2192 ${candidate}`;
1531
+ ctx.skipAgent = true;
1532
+ return;
1533
+ }
1534
+ }
1535
+ };
1536
+ function defaultLabelMap() {
1537
+ return {
1538
+ bug: "bug",
1539
+ enhancement: "bug",
1540
+ refactor: "feature",
1541
+ feature: "feature",
1542
+ performance: "feature",
1543
+ rfc: "spec",
1544
+ design: "spec",
1545
+ spec: "spec",
1546
+ docs: "chore",
1547
+ chore: "chore",
1548
+ dependencies: "chore"
1549
+ };
1550
+ }
1551
+
1502
1552
  // src/scripts/commitAndPush.ts
1503
1553
  import { execFileSync as execFileSync6 } from "child_process";
1504
1554
 
@@ -2366,7 +2416,7 @@ function gh2(args, options) {
2366
2416
  }).trim();
2367
2417
  }
2368
2418
  function getIssue(issueNumber, cwd) {
2369
- const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments"], { cwd });
2419
+ const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
2370
2420
  const parsed = JSON.parse(output);
2371
2421
  if (typeof parsed?.title !== "string") {
2372
2422
  throw new Error(`Issue #${issueNumber}: unexpected response shape`);
@@ -2379,7 +2429,8 @@ function getIssue(issueNumber, cwd) {
2379
2429
  body: c.body ?? "",
2380
2430
  author: c.author?.login ?? "unknown",
2381
2431
  createdAt: c.createdAt ?? ""
2382
- }))
2432
+ })),
2433
+ labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
2383
2434
  };
2384
2435
  }
2385
2436
  function stripKody2Mentions(body) {
@@ -3534,7 +3585,9 @@ var loadIssueContext = async (ctx) => {
3534
3585
  const kept = sorted.slice(0, limit);
3535
3586
  const commentsFormatted = kept.length === 0 ? "(no comments yet)" : kept.map((c) => `- **${c.author}** (${c.createdAt}):
3536
3587
  ${truncate2(c.body, maxBytes).replace(/\n/g, "\n ")}`).join("\n\n");
3537
- ctx.data.issue = { ...issue, commentsFormatted };
3588
+ const labels = issue.labels ?? [];
3589
+ const labelsFormatted = labels.length === 0 ? "(no labels)" : labels.map((l) => `\`${l}\``).join(", ");
3590
+ ctx.data.issue = { ...issue, commentsFormatted, labelsFormatted };
3538
3591
  ctx.data.commentTargetType = "issue";
3539
3592
  ctx.data.commentTargetNumber = issueNumber;
3540
3593
  };
@@ -3659,6 +3712,86 @@ var persistFlowState = async (ctx) => {
3659
3712
  }
3660
3713
  };
3661
3714
 
3715
+ // src/scripts/postClassification.ts
3716
+ import { execFileSync as execFileSync14 } from "child_process";
3717
+ var API_TIMEOUT_MS6 = 3e4;
3718
+ var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
3719
+ var postClassification = async (ctx) => {
3720
+ const issueNumber = ctx.args.issue;
3721
+ if (!issueNumber) return;
3722
+ const presetClassification = ctx.data.classification;
3723
+ let classification = null;
3724
+ let reason = null;
3725
+ if (presetClassification && VALID_CLASSES2.has(presetClassification)) {
3726
+ classification = presetClassification;
3727
+ reason = ctx.data.classificationReason ?? "label-based match";
3728
+ } else {
3729
+ const parsed = parseClassification(ctx.data.prSummary ?? "");
3730
+ classification = parsed?.classification ?? null;
3731
+ reason = parsed?.reason ?? null;
3732
+ }
3733
+ if (!classification) {
3734
+ ctx.data.action = failedAction("classification missing or invalid");
3735
+ tryAuditComment(issueNumber, "\u26A0\uFE0F kody2 classifier could not decide \u2014 please re-run with an explicit `@kody2 <type>`.", ctx.cwd);
3736
+ ctx.output.exitCode = 1;
3737
+ ctx.output.reason = "classify: no decision";
3738
+ return;
3739
+ }
3740
+ tryAuditComment(
3741
+ issueNumber,
3742
+ `\u{1F50E} kody2 classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`,
3743
+ ctx.cwd
3744
+ );
3745
+ try {
3746
+ execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${classification}`], {
3747
+ cwd: ctx.cwd,
3748
+ timeout: API_TIMEOUT_MS6,
3749
+ stdio: ["ignore", "pipe", "pipe"]
3750
+ });
3751
+ } catch (err) {
3752
+ process.stderr.write(
3753
+ `[kody2 postClassification] failed to dispatch @kody2 ${classification}: ${err instanceof Error ? err.message : String(err)}
3754
+ `
3755
+ );
3756
+ ctx.data.action = failedAction("dispatch post failed");
3757
+ ctx.output.exitCode = 1;
3758
+ ctx.output.reason = "classify: dispatch failed";
3759
+ return;
3760
+ }
3761
+ ctx.data.action = makeAction2(`CLASSIFIED_AS_${classification.toUpperCase()}`, {
3762
+ classification,
3763
+ reason: reason ?? "",
3764
+ source: ctx.data.classificationSource ?? "agent"
3765
+ });
3766
+ ctx.data.classification = classification;
3767
+ ctx.data.classificationReason = reason ?? "";
3768
+ };
3769
+ function parseClassification(prSummary) {
3770
+ if (!prSummary) return null;
3771
+ const classMatch = prSummary.match(/classification:\s*(feature|bug|spec|chore)\b/i);
3772
+ if (!classMatch) return null;
3773
+ const classification = classMatch[1].toLowerCase();
3774
+ const reasonMatch = prSummary.match(/reason:\s*(.+)$/im);
3775
+ const reason = reasonMatch ? reasonMatch[1].trim() : "";
3776
+ return { classification, reason };
3777
+ }
3778
+ function tryAuditComment(issueNumber, body, cwd) {
3779
+ try {
3780
+ execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
3781
+ cwd,
3782
+ timeout: API_TIMEOUT_MS6,
3783
+ stdio: ["ignore", "pipe", "pipe"]
3784
+ });
3785
+ } catch {
3786
+ }
3787
+ }
3788
+ function makeAction2(type, payload) {
3789
+ return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
3790
+ }
3791
+ function failedAction(reason) {
3792
+ return { type: "CLASSIFY_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
3793
+ }
3794
+
3662
3795
  // src/scripts/postIssueComment.ts
3663
3796
  var postIssueComment2 = async (ctx) => {
3664
3797
  if (ctx.skipAgent && ctx.output.exitCode !== void 0) return;
@@ -3773,7 +3906,7 @@ function reviewAction(verdict, payload) {
3773
3906
  const type = verdict === "PASS" ? "REVIEW_PASS" : verdict === "CONCERNS" ? "REVIEW_CONCERNS" : verdict === "FAIL" ? "REVIEW_FAIL" : "REVIEW_COMPLETED";
3774
3907
  return { type, payload: { verdict, ...payload }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
3775
3908
  }
3776
- function failedAction(reason) {
3909
+ function failedAction2(reason) {
3777
3910
  return { type: "REVIEW_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
3778
3911
  }
3779
3912
  var postReviewResult = async (ctx, _profile, agentResult) => {
@@ -3781,7 +3914,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
3781
3914
  if (!prNumber) {
3782
3915
  ctx.output.exitCode = 99;
3783
3916
  ctx.output.reason = "review postflight: no PR number in context";
3784
- ctx.data.action = failedAction(ctx.output.reason);
3917
+ ctx.data.action = failedAction2(ctx.output.reason);
3785
3918
  return;
3786
3919
  }
3787
3920
  if (!agentResult || agentResult.outcome !== "completed") {
@@ -3792,7 +3925,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
3792
3925
  }
3793
3926
  ctx.output.exitCode = 1;
3794
3927
  ctx.output.reason = reason;
3795
- ctx.data.action = failedAction(reason);
3928
+ ctx.data.action = failedAction2(reason);
3796
3929
  return;
3797
3930
  }
3798
3931
  const reviewBody = agentResult.finalText.trim();
@@ -3803,7 +3936,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
3803
3936
  }
3804
3937
  ctx.output.exitCode = 1;
3805
3938
  ctx.output.reason = "empty review body";
3806
- ctx.data.action = failedAction("empty review body");
3939
+ ctx.data.action = failedAction2("empty review body");
3807
3940
  return;
3808
3941
  }
3809
3942
  try {
@@ -3812,7 +3945,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
3812
3945
  const msg = err instanceof Error ? err.message : String(err);
3813
3946
  ctx.output.exitCode = 4;
3814
3947
  ctx.output.reason = `failed to post review comment: ${msg}`;
3815
- ctx.data.action = failedAction(ctx.output.reason);
3948
+ ctx.data.action = failedAction2(ctx.output.reason);
3816
3949
  return;
3817
3950
  }
3818
3951
  const verdict = detectVerdict(reviewBody);
@@ -3828,7 +3961,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
3828
3961
  };
3829
3962
 
3830
3963
  // src/scripts/releaseFlow.ts
3831
- import { execFileSync as execFileSync14, spawnSync } from "child_process";
3964
+ import { execFileSync as execFileSync15, spawnSync } from "child_process";
3832
3965
  import * as fs18 from "fs";
3833
3966
  import * as path16 from "path";
3834
3967
  function bumpVersion(current, bump) {
@@ -3858,7 +3991,7 @@ function generateChangelog(cwd, newVersion, lastTag) {
3858
3991
  const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
3859
3992
  let log = "";
3860
3993
  try {
3861
- log = execFileSync14("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
3994
+ log = execFileSync15("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
3862
3995
  cwd,
3863
3996
  encoding: "utf-8",
3864
3997
  stdio: ["ignore", "pipe", "pipe"]
@@ -3915,7 +4048,7 @@ ${entry}${prior.slice(idx + 1)}`);
3915
4048
  }
3916
4049
  }
3917
4050
  function git3(args, cwd, timeout = 6e4) {
3918
- return execFileSync14("git", args, {
4051
+ return execFileSync15("git", args, {
3919
4052
  encoding: "utf-8",
3920
4053
  timeout,
3921
4054
  cwd,
@@ -4142,12 +4275,12 @@ function fail(ctx, profile, reason) {
4142
4275
  ctx.data.agentDone = false;
4143
4276
  ctx.data.agentFailureReason = reason;
4144
4277
  const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
4145
- const failedAction2 = {
4278
+ const failedAction3 = {
4146
4279
  type: `${modeSeg}_FAILED`,
4147
4280
  payload: { reason },
4148
4281
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4149
4282
  };
4150
- ctx.data.action = failedAction2;
4283
+ ctx.data.action = failedAction3;
4151
4284
  }
4152
4285
  function countActionItems(block) {
4153
4286
  if (!block.trim()) return 0;
@@ -4186,12 +4319,12 @@ function fail2(ctx, profile, reason) {
4186
4319
  ctx.data.agentDone = false;
4187
4320
  ctx.data.agentFailureReason = reason;
4188
4321
  const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
4189
- const failedAction2 = {
4322
+ const failedAction3 = {
4190
4323
  type: `${modeSeg}_FAILED`,
4191
4324
  payload: { reason },
4192
4325
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4193
4326
  };
4194
- ctx.data.action = failedAction2;
4327
+ ctx.data.action = failedAction3;
4195
4328
  }
4196
4329
 
4197
4330
  // src/scripts/resolveArtifacts.ts
@@ -4218,7 +4351,7 @@ var resolveArtifacts = async (ctx, profile) => {
4218
4351
  };
4219
4352
 
4220
4353
  // src/scripts/resolveFlow.ts
4221
- import { execFileSync as execFileSync15 } from "child_process";
4354
+ import { execFileSync as execFileSync16 } from "child_process";
4222
4355
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
4223
4356
  var resolveFlow = async (ctx) => {
4224
4357
  const prNumber = ctx.args.pr;
@@ -4270,7 +4403,7 @@ var resolveFlow = async (ctx) => {
4270
4403
  };
4271
4404
  function getConflictedFiles(cwd) {
4272
4405
  try {
4273
- const out = execFileSync15("git", ["diff", "--name-only", "--diff-filter=U"], {
4406
+ const out = execFileSync16("git", ["diff", "--name-only", "--diff-filter=U"], {
4274
4407
  encoding: "utf-8",
4275
4408
  cwd,
4276
4409
  env: { ...process.env, HUSKY: "0" }
@@ -4285,7 +4418,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
4285
4418
  let total = 0;
4286
4419
  for (const f of files) {
4287
4420
  try {
4288
- const content = execFileSync15("cat", [f], { encoding: "utf-8", cwd }).toString();
4421
+ const content = execFileSync16("cat", [f], { encoding: "utf-8", cwd }).toString();
4289
4422
  const snippet = `### ${f}
4290
4423
 
4291
4424
  \`\`\`
@@ -4449,8 +4582,8 @@ var skipAgent = async (ctx) => {
4449
4582
  };
4450
4583
 
4451
4584
  // src/scripts/startFlow.ts
4452
- import { execFileSync as execFileSync16 } from "child_process";
4453
- var API_TIMEOUT_MS6 = 3e4;
4585
+ import { execFileSync as execFileSync17 } from "child_process";
4586
+ var API_TIMEOUT_MS7 = 3e4;
4454
4587
  var startFlow = async (ctx, profile, _agentResult, args) => {
4455
4588
  const entry = args?.entry;
4456
4589
  if (!entry) {
@@ -4483,8 +4616,8 @@ function postKody2Comment(target, issueNumber, state, next, cwd) {
4483
4616
  const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
4484
4617
  const body = `@kody2 ${next}`;
4485
4618
  try {
4486
- execFileSync16("gh", [sub, "comment", String(targetNumber), "--body", body], {
4487
- timeout: API_TIMEOUT_MS6,
4619
+ execFileSync17("gh", [sub, "comment", String(targetNumber), "--body", body], {
4620
+ timeout: API_TIMEOUT_MS7,
4488
4621
  cwd,
4489
4622
  stdio: ["ignore", "pipe", "pipe"]
4490
4623
  });
@@ -4503,7 +4636,7 @@ function parsePr2(url) {
4503
4636
  }
4504
4637
 
4505
4638
  // src/scripts/syncFlow.ts
4506
- import { execFileSync as execFileSync17 } from "child_process";
4639
+ import { execFileSync as execFileSync18 } from "child_process";
4507
4640
  var syncFlow = async (ctx) => {
4508
4641
  ctx.skipAgent = true;
4509
4642
  const prNumber = ctx.args.pr;
@@ -4562,7 +4695,7 @@ function bail2(ctx, prNumber, reason) {
4562
4695
  }
4563
4696
  function revParseHead(cwd) {
4564
4697
  try {
4565
- return execFileSync17("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
4698
+ return execFileSync18("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
4566
4699
  } catch {
4567
4700
  return "";
4568
4701
  }
@@ -4570,9 +4703,9 @@ function revParseHead(cwd) {
4570
4703
  function pushBranch(branch, cwd) {
4571
4704
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
4572
4705
  try {
4573
- execFileSync17("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
4706
+ execFileSync18("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
4574
4707
  } catch {
4575
- execFileSync17("git", ["push", "--force-with-lease", "-u", "origin", branch], {
4708
+ execFileSync18("git", ["push", "--force-with-lease", "-u", "origin", branch], {
4576
4709
  cwd,
4577
4710
  env,
4578
4711
  stdio: ["ignore", "pipe", "pipe"]
@@ -4790,7 +4923,8 @@ var preflightScripts = {
4790
4923
  resolvePreviewUrl,
4791
4924
  composePrompt,
4792
4925
  setLifecycleLabel,
4793
- skipAgent
4926
+ skipAgent,
4927
+ classifyByLabel
4794
4928
  };
4795
4929
  var postflightScripts = {
4796
4930
  parseAgentResult: parseAgentResult2,
@@ -4812,7 +4946,8 @@ var postflightScripts = {
4812
4946
  dispatch,
4813
4947
  finishFlow,
4814
4948
  advanceFlow,
4815
- persistFlowState
4949
+ persistFlowState,
4950
+ postClassification
4816
4951
  };
4817
4952
  var allScriptNames = /* @__PURE__ */ new Set([
4818
4953
  ...Object.keys(preflightScripts),
@@ -4820,7 +4955,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
4820
4955
  ]);
4821
4956
 
4822
4957
  // src/tools.ts
4823
- import { execFileSync as execFileSync18 } from "child_process";
4958
+ import { execFileSync as execFileSync19 } from "child_process";
4824
4959
  function verifyCliTools(tools, cwd) {
4825
4960
  const out = [];
4826
4961
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -4853,7 +4988,7 @@ function verifyOne(tool, cwd) {
4853
4988
  }
4854
4989
  function runShell2(cmd, cwd, timeoutMs = 3e4) {
4855
4990
  try {
4856
- execFileSync18("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
4991
+ execFileSync19("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
4857
4992
  return true;
4858
4993
  } catch {
4859
4994
  return false;
@@ -5183,7 +5318,7 @@ function detectPackageManager2(cwd) {
5183
5318
  }
5184
5319
  function shellOut(cmd, args, cwd, stream = true) {
5185
5320
  try {
5186
- execFileSync19(cmd, args, {
5321
+ execFileSync20(cmd, args, {
5187
5322
  cwd,
5188
5323
  stdio: stream ? "inherit" : "pipe",
5189
5324
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -5196,7 +5331,7 @@ function shellOut(cmd, args, cwd, stream = true) {
5196
5331
  }
5197
5332
  function isOnPath(bin) {
5198
5333
  try {
5199
- execFileSync19("which", [bin], { stdio: "pipe" });
5334
+ execFileSync20("which", [bin], { stdio: "pipe" });
5200
5335
  return true;
5201
5336
  } catch {
5202
5337
  return false;
@@ -5230,7 +5365,7 @@ function installLitellmIfNeeded(cwd) {
5230
5365
  } catch {
5231
5366
  }
5232
5367
  try {
5233
- execFileSync19("python3", ["-c", "import litellm"], { stdio: "pipe" });
5368
+ execFileSync20("python3", ["-c", "import litellm"], { stdio: "pipe" });
5234
5369
  process.stdout.write("\u2192 kody2: litellm already installed\n");
5235
5370
  return 0;
5236
5371
  } catch {
@@ -5240,16 +5375,16 @@ function installLitellmIfNeeded(cwd) {
5240
5375
  }
5241
5376
  function configureGitIdentity(cwd) {
5242
5377
  try {
5243
- const name = execFileSync19("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
5378
+ const name = execFileSync20("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
5244
5379
  if (name) return;
5245
5380
  } catch {
5246
5381
  }
5247
5382
  try {
5248
- execFileSync19("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
5383
+ execFileSync20("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
5249
5384
  } catch {
5250
5385
  }
5251
5386
  try {
5252
- execFileSync19("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
5387
+ execFileSync20("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
5253
5388
  cwd,
5254
5389
  stdio: "pipe"
5255
5390
  });
@@ -5426,9 +5561,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
5426
5561
  if (paths.length === 0) return;
5427
5562
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
5428
5563
  try {
5429
- execFileSync20("git", ["add", ...paths], opts);
5430
- execFileSync20("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
5431
- execFileSync20("git", ["push", "--quiet", "origin", "HEAD"], opts);
5564
+ execFileSync21("git", ["add", ...paths], opts);
5565
+ execFileSync21("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
5566
+ execFileSync21("git", ["push", "--quiet", "origin", "HEAD"], opts);
5432
5567
  } catch (err) {
5433
5568
  const msg = err instanceof Error ? err.message : String(err);
5434
5569
  process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "classify",
3
+ "role": "primitive",
4
+ "describe": "Classify an issue into one of {feature, bug, spec, chore} and dispatch the matching sub-orchestrator. Label-first fast path; LLM fallback when labels don't decide.",
5
+ "inputs": [
6
+ {
7
+ "name": "issue",
8
+ "flag": "--issue",
9
+ "type": "int",
10
+ "required": true,
11
+ "describe": "GitHub issue number to classify."
12
+ }
13
+ ],
14
+ "claudeCode": {
15
+ "model": "inherit",
16
+ "permissionMode": "default",
17
+ "maxTurns": null,
18
+ "maxThinkingTokens": null,
19
+ "systemPromptAppend": null,
20
+ "tools": [
21
+ "Read",
22
+ "Bash"
23
+ ],
24
+ "hooks": [],
25
+ "skills": [],
26
+ "commands": [],
27
+ "subagents": [],
28
+ "plugins": [],
29
+ "mcpServers": []
30
+ },
31
+ "cliTools": [],
32
+ "scripts": {
33
+ "preflight": [
34
+ {
35
+ "script": "setLifecycleLabel",
36
+ "with": {
37
+ "label": "kody:classifying",
38
+ "color": "0e8a16",
39
+ "description": "kody2: classifying the issue"
40
+ }
41
+ },
42
+ { "script": "loadIssueContext" },
43
+ { "script": "loadTaskState" },
44
+ { "script": "classifyByLabel" },
45
+ { "script": "loadConventions" },
46
+ { "script": "composePrompt" }
47
+ ],
48
+ "postflight": [
49
+ { "script": "parseAgentResult" },
50
+ { "script": "postClassification" },
51
+ { "script": "writeRunSummary" },
52
+ { "script": "saveTaskState" }
53
+ ]
54
+ },
55
+ "output": {
56
+ "actionTypes": [
57
+ "CLASSIFIED_AS_FEATURE",
58
+ "CLASSIFIED_AS_BUG",
59
+ "CLASSIFIED_AS_SPEC",
60
+ "CLASSIFIED_AS_CHORE",
61
+ "CLASSIFY_FAILED"
62
+ ]
63
+ }
64
+ }
@@ -0,0 +1,49 @@
1
+ You are Kody's issue-triage classifier. Your only job: read the issue below and pick ONE of the four flow types.
2
+
3
+ # Repo
4
+ {{repoOwner}}/{{repoName}}, default branch `{{defaultBranch}}`
5
+
6
+ # Issue #{{issue.number}}: {{issue.title}}
7
+
8
+ Labels: {{issue.labelsFormatted}}
9
+
10
+ {{issue.body}}
11
+
12
+ Recent comments (most recent first, truncated):
13
+ {{issue.commentsFormatted}}
14
+
15
+ {{conventionsBlock}}
16
+
17
+ ---
18
+
19
+ # Classification rubric
20
+
21
+ Pick **exactly one** of:
22
+
23
+ - **feature** — new user-facing capability, refactor, performance work, or anything where scope is not fully known up front. Multi-file change likely. Use when the issue opens a design space (even if small).
24
+ - **bug** — fix broken behavior, enhancement to existing feature, or any targeted change where the scope is localized and well understood. Skip research; go straight to plan.
25
+ - **spec** — produce a design doc, RFC, architecture proposal, or exploration artifact. No code changes. Terminates at the plan artifact.
26
+ - **chore** — trivial maintenance: docs tweak, dep bump, lint fix, README update. No planning needed.
27
+
28
+ **If the issue ASKS for an RFC / design doc / spec / analysis with no implementation → `spec`.** Beats everything else.
29
+ **If the issue is plainly "fix X" or "add tiny Y to existing Z" with clear boundaries → `bug`.**
30
+ **If the issue is "tweak config / bump dep / fix typo" with no real design choice → `chore`.**
31
+ **Otherwise → `feature`.**
32
+
33
+ # Required output
34
+
35
+ Your FINAL message must be exactly this shape (no extra text before or after):
36
+
37
+ ```
38
+ DONE
39
+ COMMIT_MSG: classify: <classification>
40
+ PR_SUMMARY:
41
+ classification: <feature|bug|spec|chore>
42
+ reason: <one sentence explaining the pick, grounded in the issue text>
43
+ ```
44
+
45
+ # Rules
46
+
47
+ - Read-only. Do NOT modify any file. Do NOT run git or gh.
48
+ - Output `FAILED: <reason>` if the issue is incoherent or ambiguous beyond the rubric.
49
+ - Do not over-think. This is triage, not analysis.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.2.54",
3
+ "version": "0.2.55",
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",