@kody-ade/kody-engine 0.4.372 → 0.4.373

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
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.372",
18
+ version: "0.4.373",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -3451,7 +3451,10 @@ async function runAgent(opts) {
3451
3451
  errorMessage2 = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
3452
3452
  if (typeof iterator.return === "function") {
3453
3453
  try {
3454
- await iterator.return(void 0);
3454
+ await Promise.race([
3455
+ iterator.return(void 0).catch(() => void 0),
3456
+ new Promise((resolve10) => setTimeout(resolve10, 1e4).unref())
3457
+ ]);
3455
3458
  } catch {
3456
3459
  }
3457
3460
  }
@@ -6334,7 +6337,7 @@ function locateLitellmScript() {
6334
6337
  "python3",
6335
6338
  [
6336
6339
  "-c",
6337
- "import os,sys; p=os.path.join(os.path.dirname(sys.implementation),'litellm'); print(p if os.path.exists(p) else '')"
6340
+ "import os,sys,site,sysconfig; c=[os.path.join(os.path.dirname(sys.executable),'litellm'),os.path.join(sysconfig.get_path('scripts'),'litellm'),os.path.join(site.USER_BASE,'bin','litellm')]; m=[p for p in c if os.path.exists(p)]; print(m[0] if m else '')"
6338
6341
  ],
6339
6342
  { encoding: "utf-8", timeout: 1e4 }
6340
6343
  ).trim();
@@ -11054,6 +11057,13 @@ var init_commitAndPush = __esm({
11054
11057
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
11055
11058
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
11056
11059
  if (replay.salvagedFromMissingMarker) ctx.data.salvagedFromMissingMarker = true;
11060
+ if (typeof replay.commitCrash === "string") {
11061
+ ctx.data.commitCrash = replay.commitCrash;
11062
+ if (typeof replay.exitCode === "number" && (ctx.output.exitCode === void 0 || ctx.output.exitCode === 0)) {
11063
+ ctx.output.exitCode = replay.exitCode;
11064
+ }
11065
+ if (!ctx.output.reason && replay.reason) ctx.output.reason = replay.reason;
11066
+ }
11057
11067
  ctx.data.commitIdempotencyReplay = true;
11058
11068
  process.stderr.write(`[kody commitAndPush] idempotency replay (sentinel ${sentinel})
11059
11069
  `);
@@ -11111,6 +11121,9 @@ var init_commitAndPush = __esm({
11111
11121
  changedFiles: ctx.data.changedFiles,
11112
11122
  hasCommitsAhead: ctx.data.hasCommitsAhead,
11113
11123
  salvagedFromMissingMarker: ctx.data.salvagedFromMissingMarker === true,
11124
+ commitCrash: typeof ctx.data.commitCrash === "string" ? ctx.data.commitCrash : void 0,
11125
+ exitCode: ctx.output.exitCode,
11126
+ reason: ctx.output.reason,
11114
11127
  writtenAt: (/* @__PURE__ */ new Date()).toISOString()
11115
11128
  },
11116
11129
  null,
@@ -11309,7 +11322,10 @@ var init_composePrompt = __esm({
11309
11322
  "issue.body",
11310
11323
  "issue.commentsFormatted",
11311
11324
  "pr.body",
11312
- "pr.commentsFormatted"
11325
+ "pr.commentsFormatted",
11326
+ // Prior-art bundles PR diffs and review/issue comments — authorable by any
11327
+ // external GitHub user, so exactly as attacker-controllable as issue bodies.
11328
+ "priorArt"
11313
11329
  ]);
11314
11330
  FENCE_END = "----- END UNTRUSTED INPUT -----";
11315
11331
  composePrompt = async (ctx, profile) => {
@@ -12832,23 +12848,34 @@ function firstLine(s) {
12832
12848
  const head = nl === -1 ? trimmed : trimmed.slice(0, nl);
12833
12849
  return head.length > 200 ? `${head.slice(0, 197)}\u2026` : head;
12834
12850
  }
12835
- function findExistingPr(branch, cwd) {
12851
+ function lookupExistingPr(branch, cwd) {
12836
12852
  try {
12837
12853
  const output = gh(
12838
- ["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,body", "--limit", "1"],
12854
+ ["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,body,title,isDraft", "--limit", "1"],
12839
12855
  { cwd, preferRepoToken: true }
12840
12856
  );
12841
12857
  const arr = JSON.parse(output);
12842
12858
  const first = Array.isArray(arr) ? arr[0] : null;
12843
12859
  if (first && typeof first.number === "number" && typeof first.url === "string") {
12844
- const body = typeof first.body === "string" ? first.body : "";
12845
- return { number: first.number, url: first.url, body };
12860
+ return {
12861
+ pr: {
12862
+ number: first.number,
12863
+ url: first.url,
12864
+ body: typeof first.body === "string" ? first.body : "",
12865
+ title: typeof first.title === "string" ? first.title : "",
12866
+ isDraft: first.isDraft === true
12867
+ },
12868
+ error: null
12869
+ };
12846
12870
  }
12847
- return null;
12848
- } catch {
12849
- return null;
12871
+ return { pr: null, error: null };
12872
+ } catch (err) {
12873
+ return { pr: null, error: err instanceof Error ? err.message : String(err) };
12850
12874
  }
12851
12875
  }
12876
+ function findExistingPr(branch, cwd) {
12877
+ return lookupExistingPr(branch, cwd).pr;
12878
+ }
12852
12879
  function recoverSourceIssueNumber(existingBody, branch, prNumber) {
12853
12880
  const bodyMatch = existingBody.match(/\bCloses #(\d+)\b/i);
12854
12881
  if (bodyMatch) {
@@ -12875,16 +12902,35 @@ function git2(args, cwd) {
12875
12902
  stdio: ["pipe", "pipe", "pipe"]
12876
12903
  }).trim();
12877
12904
  }
12878
- function updateExistingPr(existing, body, draft, cwd) {
12905
+ function updateExistingPr(existing, body, draft, cwd, preserveBody) {
12879
12906
  const stripped = existing.url.replace(/^https:\/\/github\.com\//, "");
12880
12907
  const [owner, repo] = stripped.split("/");
12881
- try {
12882
- gh(["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `body=${body}`], {
12883
- cwd,
12884
- preferRepoToken: true
12885
- });
12886
- } catch (err) {
12887
- throw new Error(`gh api PATCH #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`);
12908
+ if (!preserveBody) {
12909
+ try {
12910
+ gh(["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `body=${body}`], {
12911
+ cwd,
12912
+ preferRepoToken: true
12913
+ });
12914
+ } catch (err) {
12915
+ throw new Error(`gh api PATCH #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`);
12916
+ }
12917
+ }
12918
+ if (existing.isDraft === true && !draft) {
12919
+ try {
12920
+ gh(["pr", "ready", String(existing.number)], { cwd, preferRepoToken: true });
12921
+ const promotedTitle = existing.title?.replace(/^\[WIP\]\s*/, "");
12922
+ if (promotedTitle && promotedTitle !== existing.title) {
12923
+ gh(
12924
+ ["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `title=${promotedTitle}`],
12925
+ { cwd, preferRepoToken: true }
12926
+ );
12927
+ }
12928
+ } catch (err) {
12929
+ process.stderr.write(
12930
+ `[kody ensurePr] draft\u2192ready promotion of #${existing.number} failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
12931
+ `
12932
+ );
12933
+ }
12888
12934
  }
12889
12935
  return { url: existing.url, number: existing.number, draft, action: "updated" };
12890
12936
  }
@@ -12897,8 +12943,13 @@ function createPr(branch, base, title, body, draft, cwd) {
12897
12943
  return { url, number, draft, action: "created" };
12898
12944
  }
12899
12945
  function recoverFromExistingPr(branch, base, title, body, draft, cwd) {
12900
- const raced = findExistingPr(branch, cwd);
12946
+ const { pr: raced, error: lookupError } = lookupExistingPr(branch, cwd);
12901
12947
  if (raced) return updateExistingPr(raced, body, draft, cwd);
12948
+ if (lookupError) {
12949
+ throw new Error(
12950
+ `refusing phantom-PR recovery for '${branch}': PR lookup failed (${lookupError}) \u2014 a live PR may own this branch`
12951
+ );
12952
+ }
12902
12953
  try {
12903
12954
  git2(["push", "origin", "--delete", branch], cwd);
12904
12955
  } catch {
@@ -12916,7 +12967,7 @@ function ensurePr(opts) {
12916
12967
  const title = buildPrTitle(effectiveOpts.issueNumber, effectiveOpts.issueTitle, effectiveOpts.draft);
12917
12968
  const body = buildPrBody(effectiveOpts);
12918
12969
  if (existing) {
12919
- return updateExistingPr(existing, body, opts.draft, opts.cwd);
12970
+ return updateExistingPr(existing, body, opts.draft, opts.cwd, opts.preserveBodyOnUpdate === true);
12920
12971
  }
12921
12972
  const base = opts.baseBranch && opts.baseBranch.length > 0 ? opts.baseBranch : opts.defaultBranch;
12922
12973
  try {
@@ -13032,6 +13083,9 @@ var init_ensurePr = __esm({
13032
13083
  changedFiles,
13033
13084
  agentSummary: ctx.data.prSummary,
13034
13085
  baseBranch,
13086
+ // No fresh commit this run → don't rebuild the body of an existing PR;
13087
+ // it would replace the original agent summary with the empty fallback.
13088
+ preserveBodyOnUpdate: !commitResult?.committed,
13035
13089
  cwd: ctx.cwd
13036
13090
  });
13037
13091
  if (!result.url || result.url.trim().length === 0) {
@@ -18369,7 +18423,9 @@ function validateModelBundle(bundle, expectedKind) {
18369
18423
  function readExpectedModelKind(args) {
18370
18424
  const value = args?.modelKind;
18371
18425
  if (typeof value === "string" && isModelKind(value)) return value;
18372
- throw new Error("validateAgencyModelProposal: with.modelKind must be agent, capability, goal, agentLoop, or workflow");
18426
+ throw new Error(
18427
+ "validateAgencyModelProposal: with.modelKind must be intent, operation, agent, capability, goal, agentLoop, or workflow"
18428
+ );
18373
18429
  }
18374
18430
  function validateOneModel(rawModel, files, label, strictSingleModel, failures, expectedKind) {
18375
18431
  if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) {
@@ -18378,7 +18434,9 @@ function validateOneModel(rawModel, files, label, strictSingleModel, failures, e
18378
18434
  }
18379
18435
  const model = rawModel;
18380
18436
  const kind = stringField6(model.kind);
18381
- if (!isModelKind(kind)) failures.push(`${label}.kind must be agent, capability, goal, agentLoop, or workflow`);
18437
+ if (!isModelKind(kind)) {
18438
+ failures.push(`${label}.kind must be intent, operation, agent, capability, goal, agentLoop, or workflow`);
18439
+ }
18382
18440
  if (expectedKind && kind !== expectedKind) failures.push(`proposal must output model.kind ${expectedKind}`);
18383
18441
  const slug = stringField6(model.slug);
18384
18442
  if (!isSlug(slug)) failures.push(`${label}.slug must be a lowercase slug`);
@@ -18396,6 +18454,14 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
18396
18454
  if (paths.some((filePath) => filePath === "implementations" || filePath.startsWith("implementations/"))) {
18397
18455
  failures.push("files must not use obsolete implementation storage");
18398
18456
  }
18457
+ if (kind === "intent") {
18458
+ requirePath(paths, `intents/${slug}/intent.json`, "intent state", failures);
18459
+ if (strictSingleModel) rejectOtherRoots(paths, [`intents/${slug}/`], "intent", failures);
18460
+ }
18461
+ if (kind === "operation") {
18462
+ requirePath(paths, `operations/${slug}/operation.json`, "operation contract", failures);
18463
+ if (strictSingleModel) rejectOtherRoots(paths, [`operations/${slug}/`], "operation", failures);
18464
+ }
18399
18465
  if (kind === "agent") {
18400
18466
  requirePath(paths, `agents/${slug}.md`, "agent file", failures);
18401
18467
  if (strictSingleModel) rejectOtherRoots(paths, ["agents/"], "agent", failures);
@@ -18444,6 +18510,44 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
18444
18510
  }
18445
18511
  }
18446
18512
  function validateModelShape(kind, model, files, slug, failures) {
18513
+ if (kind === "intent") {
18514
+ const intent = parseJsonFile(files, `intents/${slug}/intent.json`, failures);
18515
+ if (!stringField6(model.direction)) failures.push("intent model must declare direction");
18516
+ if (!stringField6(intent?.for)) failures.push("intent file must declare direction");
18517
+ if (!isFiniteNumber(model.priority)) failures.push("intent model must declare numeric priority");
18518
+ if (!isFiniteNumber(intent?.priority)) failures.push("intent file must declare numeric priority");
18519
+ if (!hasScope(model.scope)) failures.push("intent model scope must include a repo or area");
18520
+ if (!hasScope(intent?.scope)) failures.push("intent file scope must include a repo or area");
18521
+ if (stringArray4(model.principles).length === 0) failures.push("intent model principles must be non-empty");
18522
+ if (stringArray4(intent?.principles).length === 0) failures.push("intent file principles must be non-empty");
18523
+ if (stringArray4(model.successMeasures).length === 0) {
18524
+ failures.push("intent model successMeasures must be non-empty");
18525
+ }
18526
+ if (stringArray4(intent?.metrics).length === 0) failures.push("intent file metrics must be non-empty");
18527
+ if (!recordField7(model.policy)) failures.push("intent model must declare policy");
18528
+ if (!recordField7(intent?.policy)) failures.push("intent file must declare policy");
18529
+ if (stringField6(model.status) !== "paused" || stringField6(intent?.status) !== "paused") {
18530
+ failures.push("intent proposal status must be paused");
18531
+ }
18532
+ if (intent?.version !== 1) failures.push("intent file version must be 1");
18533
+ if (intent && stringField6(intent.id) !== slug) failures.push("intent id must match model.slug");
18534
+ requireStringArrayIncludes(model.doesNotOwn, "operations", "intent doesNotOwn", failures);
18535
+ requireStringArrayIncludes(model.doesNotOwn, "capability implementation", "intent doesNotOwn", failures);
18536
+ }
18537
+ if (kind === "operation") {
18538
+ const operation = parseJsonFile(files, `operations/${slug}/operation.json`, failures);
18539
+ if (!stringField6(model.responsibility)) failures.push("operation model must declare responsibility");
18540
+ if (!stringField6(operation?.responsibility)) failures.push("operation file must declare responsibility");
18541
+ if (stringArray4(model.intentIds).length === 0) failures.push("operation model intentIds must be non-empty");
18542
+ if (stringArray4(operation?.intentIds).length === 0) failures.push("operation file intentIds must be non-empty");
18543
+ if (stringArray4(model.doesNotOwn).length === 0) failures.push("operation model doesNotOwn must be non-empty");
18544
+ if (stringArray4(operation?.doesNotOwn).length === 0) failures.push("operation file doesNotOwn must be non-empty");
18545
+ if (stringField6(model.status) !== "proposed" || stringField6(operation?.status) !== "proposed") {
18546
+ failures.push("operation proposal status must be proposed");
18547
+ }
18548
+ if (operation?.version !== 1) failures.push("operation file version must be 1");
18549
+ if (operation && stringField6(operation.id) !== slug) failures.push("operation id must match model.slug");
18550
+ }
18447
18551
  if (kind === "agent") {
18448
18552
  const agentFile = textFile(files, `agents/${slug}.md`);
18449
18553
  if (!stringArray4(model.owns).includes("identity") && !containsWord(agentFile, "identity")) {
@@ -18565,6 +18669,16 @@ function stringArray4(value) {
18565
18669
  if (!Array.isArray(value)) return [];
18566
18670
  return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
18567
18671
  }
18672
+ function recordField7(value) {
18673
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
18674
+ }
18675
+ function isFiniteNumber(value) {
18676
+ return typeof value === "number" && Number.isFinite(value);
18677
+ }
18678
+ function hasScope(value) {
18679
+ const scope = recordField7(value);
18680
+ return stringArray4(scope?.repos).length > 0 || stringArray4(scope?.areas).length > 0;
18681
+ }
18568
18682
  function requireStringArrayIncludes(value, expected, label, failures) {
18569
18683
  if (!stringArray4(value).includes(expected)) failures.push(`${label} must include ${expected}`);
18570
18684
  }
@@ -18572,7 +18686,7 @@ function isSlug(value) {
18572
18686
  return /^[a-z][a-z0-9-]{0,63}$/.test(value);
18573
18687
  }
18574
18688
  function isModelKind(value) {
18575
- return value === "agent" || value === "capability" || value === "goal" || value === "agentLoop" || value === "workflow";
18689
+ return value === "intent" || value === "operation" || value === "agent" || value === "capability" || value === "goal" || value === "agentLoop" || value === "workflow";
18576
18690
  }
18577
18691
  var REQUIRED_DOCS, validateAgencyModelProposal;
18578
18692
  var init_validateAgencyModelProposal = __esm({
@@ -18580,6 +18694,8 @@ var init_validateAgencyModelProposal = __esm({
18580
18694
  "use strict";
18581
18695
  init_openAgencyModelReviewPr();
18582
18696
  REQUIRED_DOCS = {
18697
+ intent: ["docs/intents.md", "docs/engine-company.md"],
18698
+ operation: ["docs/operations.md", "docs/engine-company.md"],
18583
18699
  agent: ["docs/agents.md"],
18584
18700
  capability: ["docs/capabilities.md", "docs/capability-kind-map.md", "docs/capability-implementations.md"],
18585
18701
  goal: ["docs/goals.md", "docs/jobs-model.md", "docs/capabilities.md"],
@@ -19678,6 +19794,7 @@ var init_tools = __esm({
19678
19794
  // src/executor.ts
19679
19795
  import { spawn as spawn7 } from "child_process";
19680
19796
  import * as fs46 from "fs";
19797
+ import * as os8 from "os";
19681
19798
  import * as path44 from "path";
19682
19799
  function isMutatingPostflight(scriptName) {
19683
19800
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
@@ -19972,6 +20089,11 @@ async function runImplementation(profileName, input) {
19972
20089
  });
19973
20090
  };
19974
20091
  ctx.data.__invokeAgent = invokeAgent;
20092
+ const cc = profile.claudeCode;
20093
+ const declaresPluginParts = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
20094
+ if (declaresPluginParts && !profile.scripts.preflight.some((e) => e.script === "buildSyntheticPlugin")) {
20095
+ profile.scripts.preflight = [{ script: "buildSyntheticPlugin" }, ...profile.scripts.preflight];
20096
+ }
19975
20097
  try {
19976
20098
  for (const entry of profile.scripts.preflight) {
19977
20099
  const preLabel = entry.script ?? entry.shell ?? "<unknown>";
@@ -20147,6 +20269,9 @@ async function runImplementation(profileName, input) {
20147
20269
  afterNextJob: ctx.output.afterNextJob,
20148
20270
  taskState: ctx.data.taskState
20149
20271
  });
20272
+ } catch (err) {
20273
+ const msg = err instanceof Error ? err.message : String(err);
20274
+ return finishAndEnd({ exitCode: 99, reason: ctx.output.reason ?? msg });
20150
20275
  } finally {
20151
20276
  clearStampedLifecycleLabels(profile, ctx);
20152
20277
  if (taskArtifacts) {
@@ -20463,7 +20588,11 @@ async function runShellEntry(entry, ctx, profile) {
20463
20588
  return;
20464
20589
  }
20465
20590
  const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
20466
- const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
20591
+ const outputFile = path44.join(
20592
+ os8.tmpdir(),
20593
+ `kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
20594
+ );
20595
+ const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", KODY_OUTPUT: outputFile };
20467
20596
  for (const [k, v] of Object.entries(ctx.args)) {
20468
20597
  if (v === void 0 || v === null) continue;
20469
20598
  env[`KODY_ARG_${envKey(k)}`] = String(v);
@@ -20529,7 +20658,25 @@ async function runShellEntry(entry, ctx, profile) {
20529
20658
  ctx.output.reason = `shell '${shellName}' failed to spawn: ${result.spawnErr.message}`;
20530
20659
  return;
20531
20660
  }
20532
- collectShellSideChannels(ctx, stdout);
20661
+ let sideChannelText = "";
20662
+ try {
20663
+ if (fs46.existsSync(outputFile)) {
20664
+ sideChannelText = fs46.readFileSync(outputFile, "utf-8");
20665
+ fs46.rmSync(outputFile, { force: true });
20666
+ }
20667
+ } catch {
20668
+ }
20669
+ if (sideChannelText.trim().length > 0) {
20670
+ collectShellSideChannels(ctx, sideChannelText);
20671
+ } else {
20672
+ if (SHELL_MARKER_RE.test(stdout)) {
20673
+ process.stderr.write(
20674
+ `[kody] shell '${shellName}': KODY_* markers read from stdout are deprecated \u2014 write them to "$KODY_OUTPUT" instead (stdout markers are forgeable by echoed untrusted text)
20675
+ `
20676
+ );
20677
+ }
20678
+ collectShellSideChannels(ctx, stdout);
20679
+ }
20533
20680
  if (timedOut) {
20534
20681
  ctx.skipAgent = true;
20535
20682
  const seconds = Math.round(timeoutMs / 1e3);
@@ -20571,7 +20718,7 @@ function flattenConfig(obj, prefix = "") {
20571
20718
  }
20572
20719
  return out;
20573
20720
  }
20574
- var MUTATING_POSTFLIGHTS, MAX_CHAIN_HOPS, DEFAULT_SHELL_TIMEOUT_MS, SIGKILL_GRACE_MS;
20721
+ var MUTATING_POSTFLIGHTS, SHELL_MARKER_RE, MAX_CHAIN_HOPS, DEFAULT_SHELL_TIMEOUT_MS, SIGKILL_GRACE_MS;
20575
20722
  var init_executor = __esm({
20576
20723
  "src/executor.ts"() {
20577
20724
  "use strict";
@@ -20602,6 +20749,7 @@ var init_executor = __esm({
20602
20749
  "publishReport",
20603
20750
  "openAgencyModelReviewPr"
20604
20751
  ]);
20752
+ SHELL_MARKER_RE = /^KODY_(SKIP_AGENT|PR_URL|REASON|CAPABILITY_REPORT|CAPABILITY_RESULT)=/m;
20605
20753
  MAX_CHAIN_HOPS = 60;
20606
20754
  DEFAULT_SHELL_TIMEOUT_MS = 3e5;
20607
20755
  SIGKILL_GRACE_MS = 5e3;
@@ -22116,7 +22264,7 @@ function autoDispatch(opts) {
22116
22264
  if (!hasKodyMention(rawBody)) return null;
22117
22265
  const isBotAuthor = authorLogin === "kody-bot" || authorType === "Bot";
22118
22266
  if (!associationAllowed(event, opts?.config)) return null;
22119
- const body = rawBody.toLowerCase();
22267
+ const body = rawBody;
22120
22268
  const targetNum = Number(issue?.number ?? 0);
22121
22269
  const isPr = !!issue?.pull_request;
22122
22270
  if (!targetNum) return null;
@@ -22215,7 +22363,7 @@ function autoDispatchTyped(opts) {
22215
22363
  if (!targetNum) {
22216
22364
  return { kind: "silent", reason: "comment has no associated issue/PR number" };
22217
22365
  }
22218
- const afterTag = extractAfterTag(rawBody.toLowerCase());
22366
+ const afterTag = extractAfterTag(rawBody);
22219
22367
  const tokenRaw = extractSubcommand(afterTag) ?? "";
22220
22368
  if ((authorLogin === "kody-bot" || authorType === "Bot") && tokenRaw && !POLITE_WORDS.has(tokenRaw)) {
22221
22369
  return {
@@ -22315,12 +22463,12 @@ function hasKodyMention(body) {
22315
22463
  function extractAfterTag(body) {
22316
22464
  const m = body.match(KODY_MENTION_RE);
22317
22465
  if (!m || m.index === void 0) return "";
22318
- const at = body.indexOf("@kody", m.index);
22466
+ const at = body.toLowerCase().indexOf("@kody", m.index);
22319
22467
  return body.slice(at + "@kody".length).trim();
22320
22468
  }
22321
22469
  function extractSubcommand(afterTag) {
22322
- const match = afterTag.match(/^([a-z][a-z0-9-]{1,40})\b/);
22323
- return match ? match[1] : null;
22470
+ const match = afterTag.match(/^([a-zA-Z][a-zA-Z0-9-]{1,40})\b/);
22471
+ return match ? match[1].toLowerCase() : null;
22324
22472
  }
22325
22473
  function extractCommentRest(afterTag, consumedToken) {
22326
22474
  let rest = afterTag;
@@ -22339,7 +22487,7 @@ function parseCommentArgs(rest, inputs) {
22339
22487
  const t = tokens[i];
22340
22488
  if (t.startsWith("--")) {
22341
22489
  const eq = t.indexOf("=");
22342
- const key = eq >= 0 ? t.slice(2, eq) : t.slice(2);
22490
+ const key = (eq >= 0 ? t.slice(2, eq) : t.slice(2)).toLowerCase();
22343
22491
  const inlineValue = eq >= 0 ? t.slice(eq + 1) : void 0;
22344
22492
  const spec = findInputByFlag(inputs, key);
22345
22493
  if (!spec) {
@@ -22359,9 +22507,12 @@ function parseCommentArgs(rest, inputs) {
22359
22507
  if (inlineValue === void 0) i++;
22360
22508
  continue;
22361
22509
  }
22362
- const enumHit = inputs.find((s) => s.type === "enum" && s.values?.includes(t) && args[s.name] === void 0);
22510
+ const tLower = t.toLowerCase();
22511
+ const enumHit = inputs.find(
22512
+ (s) => s.type === "enum" && s.values?.some((v) => v.toLowerCase() === tLower) && args[s.name] === void 0
22513
+ );
22363
22514
  if (enumHit) {
22364
- args[enumHit.name] = t;
22515
+ args[enumHit.name] = enumHit.values.find((v) => v.toLowerCase() === tLower);
22365
22516
  continue;
22366
22517
  }
22367
22518
  if (/^-?\d+$/.test(t)) {
@@ -22371,7 +22522,7 @@ function parseCommentArgs(rest, inputs) {
22371
22522
  continue;
22372
22523
  }
22373
22524
  }
22374
- const boolHit = inputs.find((s) => s.type === "bool" && s.flag === `--${t}` && args[s.name] === void 0);
22525
+ const boolHit = inputs.find((s) => s.type === "bool" && s.flag === `--${tLower}` && args[s.name] === void 0);
22375
22526
  if (boolHit) {
22376
22527
  args[boolHit.name] = true;
22377
22528
  continue;
@@ -364,9 +364,12 @@ export interface ScriptEntry {
364
364
  * Filename of a shell script colocated with the implementation
365
365
  * (e.g. "apply-prefer.sh"). Resolved relative to the profile's
366
366
  * directory. Invoked via `bash <path> <with-args>` with ctx.args
367
- * exposed as env vars (KODY_ARG_<UPPER_NAME>=<value>). A stdout
368
- * line `KODY_SKIP_AGENT=true` signals the executor to bypass the
369
- * agent. Non-zero exit is treated as a preflight failure.
367
+ * exposed as env vars (KODY_ARG_<UPPER_NAME>=<value>). Side-channel
368
+ * markers (KODY_SKIP_AGENT=true, KODY_PR_URL=, KODY_REASON=,
369
+ * KODY_CAPABILITY_RESULT=, KODY_CAPABILITY_REPORT=) are read from the
370
+ * file named by $KODY_OUTPUT (preferred — not forgeable by echoed
371
+ * untrusted text); plain-stdout markers remain a deprecated fallback.
372
+ * Non-zero exit is treated as a preflight failure.
370
373
  */
371
374
  shell?: string
372
375
  /**
@@ -7,7 +7,7 @@
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "node -e 'let s=\"\";process.stdin.on(\"data\",c=>s+=c).on(\"end\",()=>{try{const d=JSON.parse(s);const cmd=(d.tool_input&&d.tool_input.command)||\"\";if(cmd.split(/[;&|\\n]+/).some(p=>/^(git|gh)(\\s|$)/.test(p.trim()))){process.stderr.write(\"kody blocks git/gh — the wrapper handles VCS; do not run git or gh commands\\n\");process.exit(2)}}catch{}})'"
10
+ "command": "node -e 'let s=\"\";process.stdin.on(\"data\",c=>s+=c).on(\"end\",()=>{try{const d=JSON.parse(s);const cmd=(d.tool_input&&d.tool_input.command)||\"\";const vcs=t=>/^(?:\\S*\\/)?(git|gh)$/.test(t);const wrap=new Set([\"command\",\"exec\",\"xargs\",\"env\",\"nohup\",\"time\",\"nice\",\"sudo\",\"doas\"]);const seg=cmd.split(/[;&|\\n]+/).some(p=>{const t=p.trim().split(/\\s+/).filter(Boolean);let i=0;while(i<t.length&&(wrap.has(t[i])||/^[A-Za-z_][A-Za-z0-9_]*=/.test(t[i])))i++;return t[i]!==undefined&&vcs(t[i])});const shc=/\\b(?:ba|z|da|k)?sh\\s+[^;|&]*-c\\s*[\"\\x27`]+\\s*(?:\\S*\\/)?(?:git|gh)\\b/.test(cmd);const sub=/[$`]\\(?\\s*(?:\\S*\\/)?(?:git|gh)\\s/.test(cmd);if(seg||shc||sub){process.stderr.write(\"kody blocks git/gh — the wrapper handles VCS; do not run git or gh commands (any invocation form)\\n\");process.exit(2)}}catch{}})'"
11
11
  }
12
12
  ]
13
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.372",
3
+ "version": "0.4.373",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",