@kody-ade/kody-engine 0.4.28 → 0.4.30

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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +227 -215
  2. package/package.json +1 -1
package/dist/bin/kody.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.4.28",
6
+ version: "0.4.30",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -1977,153 +1977,10 @@ function stripBlockingEnv(env) {
1977
1977
  return out;
1978
1978
  }
1979
1979
 
1980
- // src/prompt.ts
1981
- import * as fs10 from "fs";
1982
- import * as path9 from "path";
1983
- var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
1984
- var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
1985
- function loadProjectConventions(projectDir) {
1986
- const out = [];
1987
- for (const rel of CONVENTION_FILES) {
1988
- const abs = path9.join(projectDir, rel);
1989
- if (!fs10.existsSync(abs)) continue;
1990
- let content;
1991
- try {
1992
- content = fs10.readFileSync(abs, "utf-8");
1993
- } catch {
1994
- continue;
1995
- }
1996
- const truncated = content.length > CONVENTIONS_PER_FILE_MAX_BYTES;
1997
- if (truncated) content = `${content.slice(0, CONVENTIONS_PER_FILE_MAX_BYTES)}
1998
-
1999
- \u2026 (truncated)`;
2000
- out.push({ path: rel, content, truncated });
2001
- }
2002
- return out;
2003
- }
2004
- function parseAgentResult(finalText) {
2005
- const text = (finalText || "").trim();
2006
- if (!text)
2007
- return {
2008
- done: false,
2009
- commitMessage: "",
2010
- prSummary: "",
2011
- feedbackActions: "",
2012
- planDeviations: "",
2013
- priorArt: "",
2014
- failureReason: "agent produced no final message",
2015
- markerMissing: false
2016
- };
2017
- const MARKDOWN_PREFIX = "[\\s>*_#`~\\-]*";
2018
- const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "is");
2019
- const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b`, "i");
2020
- const failedMatch = text.match(FAILED_RE);
2021
- if (failedMatch) {
2022
- return {
2023
- done: false,
2024
- commitMessage: "",
2025
- prSummary: "",
2026
- feedbackActions: "",
2027
- planDeviations: "",
2028
- priorArt: "",
2029
- failureReason: stripMarkdownEmphasis(failedMatch[1]),
2030
- markerMissing: false
2031
- };
2032
- }
2033
- const hasDoneMarker = DONE_RE.test(text);
2034
- const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text);
2035
- const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text);
2036
- if (!hasDoneMarker && !hasCommitMsg && !hasPrSummary) {
2037
- const tail = text.length > 400 ? `\u2026${text.slice(-400)}` : text;
2038
- return {
2039
- done: false,
2040
- commitMessage: "",
2041
- prSummary: "",
2042
- feedbackActions: "",
2043
- planDeviations: "",
2044
- priorArt: "",
2045
- failureReason: `no DONE or FAILED marker in agent output \u2014 agent tail: ${tail}`,
2046
- markerMissing: true
2047
- };
2048
- }
2049
- const commitMatch = text.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
2050
- const commitMessage = commitMatch ? stripMarkdownEmphasis(commitMatch[1]) : "";
2051
- const feedbackActions = extractBlock(
2052
- text,
2053
- /(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
2054
- /(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
2055
- );
2056
- let planDeviations = extractBlock(
2057
- text,
2058
- /(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
2059
- /(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
2060
- );
2061
- if (!planDeviations) {
2062
- const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
2063
- if (inline) planDeviations = inline[1].trim();
2064
- }
2065
- let priorArt = "";
2066
- const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
2067
- if (priorArtInline) priorArt = priorArtInline[1].trim();
2068
- const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
2069
- let prSummary = "";
2070
- if (summaryStart !== -1) {
2071
- const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
2072
- prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
2073
- }
2074
- return {
2075
- done: true,
2076
- commitMessage,
2077
- prSummary,
2078
- feedbackActions,
2079
- planDeviations,
2080
- priorArt,
2081
- failureReason: "",
2082
- markerMissing: false
2083
- };
2084
- }
2085
- function stripMarkdownEmphasis(s) {
2086
- return s.trim().replace(/^[*_`~]+|[*_`~]+$/g, "").trim();
2087
- }
2088
- function extractBlock(text, startMarker, endMarker) {
2089
- const startIdx = text.search(startMarker);
2090
- if (startIdx === -1) return "";
2091
- const afterStart = text.slice(startIdx).replace(startMarker, "");
2092
- const endIdx = afterStart.search(endMarker);
2093
- const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
2094
- return body.replace(/\n\s*```\s*$/g, "").trim();
2095
- }
2096
-
2097
- // src/rescueMissingMarker.ts
2098
- var NUDGE_PROMPT = "Your previous message did not contain the required terminator. Reply with EXACTLY one of:\n DONE\n COMMIT_MSG: <one-line commit message>\nor, if the work failed:\n FAILED: <one-line reason>\nDo not repeat any earlier content \u2014 emit only the marker line(s).";
2099
- async function rescueMissingMarker(result, invoke) {
2100
- if (result.outcome !== "completed") return result;
2101
- const parsed = parseAgentResult(result.finalText);
2102
- if (!parsed.markerMissing) return result;
2103
- try {
2104
- const rescue = await invoke(NUDGE_PROMPT);
2105
- if (!rescue.finalText || !rescue.finalText.trim()) return result;
2106
- return {
2107
- ...result,
2108
- finalText: `${result.finalText}
2109
-
2110
- ---
2111
-
2112
- ${rescue.finalText}`,
2113
- outcome: rescue.outcome === "failed" ? result.outcome : rescue.outcome
2114
- };
2115
- } catch (err) {
2116
- const msg = err instanceof Error ? err.message : String(err);
2117
- process.stderr.write(`[kody] marker-rescue turn failed: ${msg}
2118
- `);
2119
- return result;
2120
- }
2121
- }
2122
-
2123
1980
  // src/commit.ts
2124
1981
  import { execFileSync as execFileSync5 } from "child_process";
2125
- import * as fs11 from "fs";
2126
- import * as path10 from "path";
1982
+ import * as fs10 from "fs";
1983
+ import * as path9 from "path";
2127
1984
  var FORBIDDEN_PATH_PREFIXES = [
2128
1985
  ".kody/",
2129
1986
  ".kody-engine/",
@@ -2179,18 +2036,18 @@ function tryGit(args, cwd) {
2179
2036
  }
2180
2037
  function abortUnfinishedGitOps(cwd) {
2181
2038
  const aborted = [];
2182
- const gitDir = path10.join(cwd ?? process.cwd(), ".git");
2183
- if (!fs11.existsSync(gitDir)) return aborted;
2184
- if (fs11.existsSync(path10.join(gitDir, "MERGE_HEAD"))) {
2039
+ const gitDir = path9.join(cwd ?? process.cwd(), ".git");
2040
+ if (!fs10.existsSync(gitDir)) return aborted;
2041
+ if (fs10.existsSync(path9.join(gitDir, "MERGE_HEAD"))) {
2185
2042
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
2186
2043
  }
2187
- if (fs11.existsSync(path10.join(gitDir, "CHERRY_PICK_HEAD"))) {
2044
+ if (fs10.existsSync(path9.join(gitDir, "CHERRY_PICK_HEAD"))) {
2188
2045
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
2189
2046
  }
2190
- if (fs11.existsSync(path10.join(gitDir, "REVERT_HEAD"))) {
2047
+ if (fs10.existsSync(path9.join(gitDir, "REVERT_HEAD"))) {
2191
2048
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
2192
2049
  }
2193
- if (fs11.existsSync(path10.join(gitDir, "rebase-merge")) || fs11.existsSync(path10.join(gitDir, "rebase-apply"))) {
2050
+ if (fs10.existsSync(path9.join(gitDir, "rebase-merge")) || fs10.existsSync(path9.join(gitDir, "rebase-apply"))) {
2194
2051
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
2195
2052
  }
2196
2053
  try {
@@ -2246,7 +2103,7 @@ function normalizeCommitMessage(raw) {
2246
2103
  function commitAndPush(branch, agentMessage, cwd) {
2247
2104
  const allChanged = listChangedFiles(cwd);
2248
2105
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
2249
- const mergeHeadExists = fs11.existsSync(path10.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
2106
+ const mergeHeadExists = fs10.existsSync(path9.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
2250
2107
  if (allowedFiles.length === 0 && !mergeHeadExists) {
2251
2108
  return { committed: false, pushed: false, sha: "", message: "" };
2252
2109
  }
@@ -2558,21 +2415,21 @@ var advanceFlow = async (ctx, profile) => {
2558
2415
  };
2559
2416
 
2560
2417
  // src/scripts/buildSyntheticPlugin.ts
2561
- import * as fs12 from "fs";
2418
+ import * as fs11 from "fs";
2562
2419
  import * as os2 from "os";
2563
- import * as path11 from "path";
2420
+ import * as path10 from "path";
2564
2421
  function getPluginsCatalogRoot() {
2565
- const here = path11.dirname(new URL(import.meta.url).pathname);
2422
+ const here = path10.dirname(new URL(import.meta.url).pathname);
2566
2423
  const candidates = [
2567
- path11.join(here, "..", "plugins"),
2424
+ path10.join(here, "..", "plugins"),
2568
2425
  // dev: src/scripts → src/plugins
2569
- path11.join(here, "..", "..", "plugins"),
2426
+ path10.join(here, "..", "..", "plugins"),
2570
2427
  // built: dist/scripts → dist/plugins
2571
- path11.join(here, "..", "..", "src", "plugins")
2428
+ path10.join(here, "..", "..", "src", "plugins")
2572
2429
  // fallback
2573
2430
  ];
2574
2431
  for (const c of candidates) {
2575
- if (fs12.existsSync(c) && fs12.statSync(c).isDirectory()) return c;
2432
+ if (fs11.existsSync(c) && fs11.statSync(c).isDirectory()) return c;
2576
2433
  }
2577
2434
  return candidates[0];
2578
2435
  }
@@ -2582,52 +2439,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
2582
2439
  if (!needsSynthetic) return;
2583
2440
  const catalog = getPluginsCatalogRoot();
2584
2441
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2585
- const root = path11.join(os2.tmpdir(), `kody-synth-${runId}`);
2586
- fs12.mkdirSync(path11.join(root, ".claude-plugin"), { recursive: true });
2442
+ const root = path10.join(os2.tmpdir(), `kody-synth-${runId}`);
2443
+ fs11.mkdirSync(path10.join(root, ".claude-plugin"), { recursive: true });
2587
2444
  const resolvePart = (bucket, entry) => {
2588
- const local = path11.join(profile.dir, bucket, entry);
2589
- if (fs12.existsSync(local)) return local;
2590
- const central = path11.join(catalog, bucket, entry);
2591
- if (fs12.existsSync(central)) return central;
2445
+ const local = path10.join(profile.dir, bucket, entry);
2446
+ if (fs11.existsSync(local)) return local;
2447
+ const central = path10.join(catalog, bucket, entry);
2448
+ if (fs11.existsSync(central)) return central;
2592
2449
  throw new Error(
2593
2450
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
2594
2451
  );
2595
2452
  };
2596
2453
  if (cc.skills.length > 0) {
2597
- const dst = path11.join(root, "skills");
2598
- fs12.mkdirSync(dst, { recursive: true });
2454
+ const dst = path10.join(root, "skills");
2455
+ fs11.mkdirSync(dst, { recursive: true });
2599
2456
  for (const name of cc.skills) {
2600
- copyDir(resolvePart("skills", name), path11.join(dst, name));
2457
+ copyDir(resolvePart("skills", name), path10.join(dst, name));
2601
2458
  }
2602
2459
  }
2603
2460
  if (cc.commands.length > 0) {
2604
- const dst = path11.join(root, "commands");
2605
- fs12.mkdirSync(dst, { recursive: true });
2461
+ const dst = path10.join(root, "commands");
2462
+ fs11.mkdirSync(dst, { recursive: true });
2606
2463
  for (const name of cc.commands) {
2607
- fs12.copyFileSync(resolvePart("commands", `${name}.md`), path11.join(dst, `${name}.md`));
2464
+ fs11.copyFileSync(resolvePart("commands", `${name}.md`), path10.join(dst, `${name}.md`));
2608
2465
  }
2609
2466
  }
2610
2467
  if (cc.subagents.length > 0) {
2611
- const dst = path11.join(root, "agents");
2612
- fs12.mkdirSync(dst, { recursive: true });
2468
+ const dst = path10.join(root, "agents");
2469
+ fs11.mkdirSync(dst, { recursive: true });
2613
2470
  for (const name of cc.subagents) {
2614
- fs12.copyFileSync(resolvePart("agents", `${name}.md`), path11.join(dst, `${name}.md`));
2471
+ fs11.copyFileSync(resolvePart("agents", `${name}.md`), path10.join(dst, `${name}.md`));
2615
2472
  }
2616
2473
  }
2617
2474
  if (cc.hooks.length > 0) {
2618
- const dst = path11.join(root, "hooks");
2619
- fs12.mkdirSync(dst, { recursive: true });
2475
+ const dst = path10.join(root, "hooks");
2476
+ fs11.mkdirSync(dst, { recursive: true });
2620
2477
  const merged = { hooks: {} };
2621
2478
  for (const name of cc.hooks) {
2622
2479
  const src = resolvePart("hooks", `${name}.json`);
2623
- const parsed = JSON.parse(fs12.readFileSync(src, "utf-8"));
2480
+ const parsed = JSON.parse(fs11.readFileSync(src, "utf-8"));
2624
2481
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
2625
2482
  if (!Array.isArray(entries)) continue;
2626
2483
  if (!merged.hooks[event]) merged.hooks[event] = [];
2627
2484
  merged.hooks[event].push(...entries);
2628
2485
  }
2629
2486
  }
2630
- fs12.writeFileSync(path11.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2487
+ fs11.writeFileSync(path10.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2631
2488
  `);
2632
2489
  }
2633
2490
  const manifest = {
@@ -2638,17 +2495,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
2638
2495
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
2639
2496
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
2640
2497
  if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
2641
- fs12.writeFileSync(path11.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2498
+ fs11.writeFileSync(path10.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2642
2499
  `);
2643
2500
  ctx.data.syntheticPluginPath = root;
2644
2501
  };
2645
2502
  function copyDir(src, dst) {
2646
- fs12.mkdirSync(dst, { recursive: true });
2647
- for (const ent of fs12.readdirSync(src, { withFileTypes: true })) {
2648
- const s = path11.join(src, ent.name);
2649
- const d = path11.join(dst, ent.name);
2503
+ fs11.mkdirSync(dst, { recursive: true });
2504
+ for (const ent of fs11.readdirSync(src, { withFileTypes: true })) {
2505
+ const s = path10.join(src, ent.name);
2506
+ const d = path10.join(dst, ent.name);
2650
2507
  if (ent.isDirectory()) copyDir(s, d);
2651
- else if (ent.isFile()) fs12.copyFileSync(s, d);
2508
+ else if (ent.isFile()) fs11.copyFileSync(s, d);
2652
2509
  }
2653
2510
  }
2654
2511
 
@@ -2713,6 +2570,111 @@ function formatMissesForFeedback(misses) {
2713
2570
  return lines.join("\n");
2714
2571
  }
2715
2572
 
2573
+ // src/prompt.ts
2574
+ import * as fs12 from "fs";
2575
+ import * as path11 from "path";
2576
+ var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
2577
+ var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
2578
+ function loadProjectConventions(projectDir) {
2579
+ const out = [];
2580
+ for (const rel of CONVENTION_FILES) {
2581
+ const abs = path11.join(projectDir, rel);
2582
+ if (!fs12.existsSync(abs)) continue;
2583
+ let content;
2584
+ try {
2585
+ content = fs12.readFileSync(abs, "utf-8");
2586
+ } catch {
2587
+ continue;
2588
+ }
2589
+ const truncated = content.length > CONVENTIONS_PER_FILE_MAX_BYTES;
2590
+ if (truncated) content = `${content.slice(0, CONVENTIONS_PER_FILE_MAX_BYTES)}
2591
+
2592
+ \u2026 (truncated)`;
2593
+ out.push({ path: rel, content, truncated });
2594
+ }
2595
+ return out;
2596
+ }
2597
+ function parseAgentResult(finalText) {
2598
+ const text = (finalText || "").trim();
2599
+ if (!text)
2600
+ return {
2601
+ done: false,
2602
+ commitMessage: "",
2603
+ prSummary: "",
2604
+ feedbackActions: "",
2605
+ planDeviations: "",
2606
+ priorArt: "",
2607
+ failureReason: "agent produced no final message",
2608
+ markerMissing: false
2609
+ };
2610
+ const MARKDOWN_PREFIX = "[\\s>*_#`~\\-]*";
2611
+ const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "is");
2612
+ const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b`, "i");
2613
+ const failedMatch = text.match(FAILED_RE);
2614
+ if (failedMatch) {
2615
+ return {
2616
+ done: false,
2617
+ commitMessage: "",
2618
+ prSummary: "",
2619
+ feedbackActions: "",
2620
+ planDeviations: "",
2621
+ priorArt: "",
2622
+ failureReason: stripMarkdownEmphasis(failedMatch[1]),
2623
+ markerMissing: false
2624
+ };
2625
+ }
2626
+ const hasDoneMarker = DONE_RE.test(text);
2627
+ const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text);
2628
+ const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text);
2629
+ const markerMissing = !hasDoneMarker && !hasCommitMsg && !hasPrSummary;
2630
+ const commitMatch = text.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
2631
+ const commitMessage = commitMatch ? stripMarkdownEmphasis(commitMatch[1]) : "";
2632
+ const feedbackActions = extractBlock(
2633
+ text,
2634
+ /(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
2635
+ /(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
2636
+ );
2637
+ let planDeviations = extractBlock(
2638
+ text,
2639
+ /(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
2640
+ /(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
2641
+ );
2642
+ if (!planDeviations) {
2643
+ const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
2644
+ if (inline) planDeviations = inline[1].trim();
2645
+ }
2646
+ let priorArt = "";
2647
+ const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
2648
+ if (priorArtInline) priorArt = priorArtInline[1].trim();
2649
+ const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
2650
+ let prSummary = "";
2651
+ if (summaryStart !== -1) {
2652
+ const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
2653
+ prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
2654
+ }
2655
+ return {
2656
+ done: true,
2657
+ commitMessage,
2658
+ prSummary,
2659
+ feedbackActions,
2660
+ planDeviations,
2661
+ priorArt,
2662
+ failureReason: "",
2663
+ markerMissing
2664
+ };
2665
+ }
2666
+ function stripMarkdownEmphasis(s) {
2667
+ return s.trim().replace(/^[*_`~]+|[*_`~]+$/g, "").trim();
2668
+ }
2669
+ function extractBlock(text, startMarker, endMarker) {
2670
+ const startIdx = text.search(startMarker);
2671
+ if (startIdx === -1) return "";
2672
+ const afterStart = text.slice(startIdx).replace(startMarker, "");
2673
+ const endIdx = afterStart.search(endMarker);
2674
+ const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
2675
+ return body.replace(/\n\s*```\s*$/g, "").trim();
2676
+ }
2677
+
2716
2678
  // src/scripts/checkCoverageWithRetry.ts
2717
2679
  var checkCoverageWithRetry = async (ctx) => {
2718
2680
  const reqs = ctx.data.coverageRules ?? [];
@@ -4521,14 +4483,15 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
4521
4483
  const results = [];
4522
4484
  const now = Date.now();
4523
4485
  for (const slug of slugs) {
4524
- const decision = await decideShouldFire(ctx.cwd, jobsDir, slug, backend, now);
4486
+ const frontmatter = readJobFrontmatter(ctx.cwd, jobsDir, slug);
4487
+ const decision = await decideShouldFire(frontmatter.every, slug, backend, now);
4525
4488
  if (decision.skip) {
4526
4489
  process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
4527
4490
  `);
4528
4491
  results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
4529
4492
  continue;
4530
4493
  }
4531
- const slugTarget = readTickScript(ctx.cwd, jobsDir, slug) ? "job-tick-scripted" : targetExecutable;
4494
+ const slugTarget = frontmatter.tickScript ? "job-tick-scripted" : targetExecutable;
4532
4495
  process.stdout.write(`[jobs] \u2192 tick ${slug} (${slugTarget})
4533
4496
  `);
4534
4497
  try {
@@ -4565,14 +4528,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
4565
4528
  }
4566
4529
  }
4567
4530
  };
4568
- async function decideShouldFire(cwd, jobsDir, slug, backend, now) {
4569
- let every;
4570
- try {
4571
- const raw = fs19.readFileSync(path18.join(cwd, jobsDir, `${slug}.md`), "utf-8");
4572
- every = splitFrontmatter(raw).frontmatter.every;
4573
- } catch {
4574
- return { skip: false, reason: "frontmatter unreadable" };
4575
- }
4531
+ async function decideShouldFire(every, slug, backend, now) {
4576
4532
  if (!every) return { skip: false, reason: "no schedule (every cron tick)" };
4577
4533
  if (every === "manual") {
4578
4534
  return { skip: true, reason: "manual-only (no auto-fire; trigger via dashboard Run now)" };
@@ -4612,12 +4568,12 @@ function formatAgo(ms) {
4612
4568
  const day = Math.round(hr / 24);
4613
4569
  return `${day}d`;
4614
4570
  }
4615
- function readTickScript(cwd, jobsDir, slug) {
4571
+ function readJobFrontmatter(cwd, jobsDir, slug) {
4616
4572
  try {
4617
4573
  const raw = fs19.readFileSync(path18.join(cwd, jobsDir, `${slug}.md`), "utf-8");
4618
- return splitFrontmatter(raw).frontmatter.tickScript ?? null;
4574
+ return splitFrontmatter(raw).frontmatter;
4619
4575
  } catch {
4620
- return null;
4576
+ return {};
4621
4577
  }
4622
4578
  }
4623
4579
  function listJobSlugs(absDir) {
@@ -6723,13 +6679,19 @@ var requirePlanDeviations = async (ctx, profile) => {
6723
6679
  if (!planContent) return;
6724
6680
  const raw = String(ctx.data.planDeviations ?? "").trim();
6725
6681
  if (raw.length === 0) {
6726
- fail2(ctx, profile, "agent omitted required PLAN_DEVIATIONS block \u2014 cannot verify whether the plan was followed");
6682
+ process.stderr.write(
6683
+ "[kody requirePlanDeviations] warning: agent omitted PLAN_DEVIATIONS block \u2014 proceeding anyway (verify/tests are the real gate)\n"
6684
+ );
6685
+ ctx.data.planDeviationsOmitted = true;
6727
6686
  return;
6728
6687
  }
6729
6688
  if (isNoneSentinel(raw)) return;
6730
6689
  const bullets = raw.split("\n").filter((l) => /^\s*[-*]\s+/.test(l));
6731
6690
  if (bullets.length === 0) {
6732
- fail2(ctx, profile, "agent PLAN_DEVIATIONS block is not 'none' and lists no bullet items");
6691
+ process.stderr.write(
6692
+ "[kody requirePlanDeviations] warning: PLAN_DEVIATIONS block is not 'none' and lists no bullet items \u2014 proceeding anyway\n"
6693
+ );
6694
+ ctx.data.planDeviationsMalformed = true;
6733
6695
  return;
6734
6696
  }
6735
6697
  ctx.data.planDeviationCount = bullets.length;
@@ -6741,17 +6703,6 @@ function isNoneSentinel(block) {
6741
6703
  if (stripped.length !== 1) return false;
6742
6704
  return stripped[0] === "none";
6743
6705
  }
6744
- function fail2(ctx, profile, reason) {
6745
- ctx.data.agentDone = false;
6746
- ctx.data.agentFailureReason = reason;
6747
- const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
6748
- const failedAction6 = {
6749
- type: `${modeSeg}_FAILED`,
6750
- payload: { reason },
6751
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
6752
- };
6753
- ctx.data.action = failedAction6;
6754
- }
6755
6706
 
6756
6707
  // src/scripts/resolveArtifacts.ts
6757
6708
  var resolveArtifacts = async (ctx, profile) => {
@@ -7267,15 +7218,29 @@ var runTickScript = async (ctx, _profile, args) => {
7267
7218
  return;
7268
7219
  }
7269
7220
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
7270
- const loaded = await backend.load(slug);
7221
+ let loaded;
7222
+ try {
7223
+ loaded = await backend.load(slug);
7224
+ } catch (err) {
7225
+ ctx.output.exitCode = 99;
7226
+ ctx.output.reason = `runTickScript: state load failed: ${err instanceof Error ? err.message : String(err)}`;
7227
+ return;
7228
+ }
7271
7229
  ctx.data.jobSlug = slug;
7272
7230
  ctx.data.jobState = loaded;
7231
+ const childEnv = buildChildEnv(process.env, Boolean(ctx.args.force));
7273
7232
  const result = spawnSync("bash", [scriptPath], {
7274
7233
  cwd: ctx.cwd,
7275
- env: process.env,
7234
+ env: childEnv,
7276
7235
  stdio: ["ignore", "pipe", "pipe"],
7277
7236
  encoding: "utf-8",
7278
- timeout: 5 * 60 * 1e3
7237
+ timeout: 5 * 60 * 1e3,
7238
+ // Default maxBuffer is 1MB — a chatty `gh pr list --json …` over a
7239
+ // busy repo (or an accidental `set -x`) can blow that and silently
7240
+ // truncate stdout, which is the exact "silent state drop" failure
7241
+ // mode this whole executable was written to prevent. 16MB is well
7242
+ // above any realistic tick output.
7243
+ maxBuffer: 16 * 1024 * 1024
7279
7244
  });
7280
7245
  if (result.stdout) process.stdout.write(result.stdout);
7281
7246
  if (result.stderr) process.stderr.write(result.stderr);
@@ -7284,6 +7249,11 @@ var runTickScript = async (ctx, _profile, args) => {
7284
7249
  ctx.output.reason = `runTickScript: spawn error: ${result.error.message}`;
7285
7250
  return;
7286
7251
  }
7252
+ if (result.signal) {
7253
+ ctx.output.exitCode = 124;
7254
+ ctx.output.reason = `runTickScript: ${tickScript} killed by ${result.signal} (likely 5min timeout)`;
7255
+ return;
7256
+ }
7287
7257
  if (result.status !== 0) {
7288
7258
  ctx.output.exitCode = result.status ?? 99;
7289
7259
  ctx.output.reason = `runTickScript: ${tickScript} exited ${result.status}`;
@@ -7299,6 +7269,49 @@ var runTickScript = async (ctx, _profile, args) => {
7299
7269
  }
7300
7270
  ctx.data.nextJobState = parsed.envelope;
7301
7271
  };
7272
+ function buildChildEnv(parent, force) {
7273
+ const allow = /* @__PURE__ */ new Set([
7274
+ "PATH",
7275
+ "HOME",
7276
+ "USER",
7277
+ "LOGNAME",
7278
+ "SHELL",
7279
+ "TMPDIR",
7280
+ "LANG",
7281
+ "LC_ALL",
7282
+ "TERM",
7283
+ // GitHub auth — `gh` reads these.
7284
+ "GH_TOKEN",
7285
+ "GH_PAT",
7286
+ "GITHUB_TOKEN",
7287
+ // CI metadata commonly read by tick scripts (`gh repo view`,
7288
+ // workflow run links, etc.). All public values from GitHub Actions.
7289
+ "GITHUB_ACTIONS",
7290
+ "GITHUB_ACTOR",
7291
+ "GITHUB_REPOSITORY",
7292
+ "GITHUB_REPOSITORY_OWNER",
7293
+ "GITHUB_REF",
7294
+ "GITHUB_SHA",
7295
+ "GITHUB_RUN_ID",
7296
+ "GITHUB_RUN_NUMBER",
7297
+ "GITHUB_WORKFLOW",
7298
+ "GITHUB_JOB",
7299
+ "GITHUB_SERVER_URL",
7300
+ "GITHUB_API_URL",
7301
+ "GITHUB_EVENT_NAME",
7302
+ "RUNNER_OS",
7303
+ "RUNNER_ARCH"
7304
+ ]);
7305
+ const out = {};
7306
+ for (const [key, value] of Object.entries(parent)) {
7307
+ if (value === void 0) continue;
7308
+ if (allow.has(key) || key.startsWith("KODY_PUBLIC_")) {
7309
+ out[key] = value;
7310
+ }
7311
+ }
7312
+ if (force) out.KODY_FORCE = "1";
7313
+ return out;
7314
+ }
7302
7315
 
7303
7316
  // src/scripts/saveTaskState.ts
7304
7317
  var saveTaskState = async (ctx, profile) => {
@@ -8340,7 +8353,6 @@ async function runExecutable(profileName, input) {
8340
8353
  return finish({ exitCode: 99, reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)" });
8341
8354
  }
8342
8355
  agentResult = await invokeAgent(prompt);
8343
- agentResult = await rescueMissingMarker(agentResult, invokeAgent);
8344
8356
  }
8345
8357
  for (const entry of profile.scripts.postflight) {
8346
8358
  const entryLabel = entry.script ?? entry.shell ?? "<unknown>";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
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",