@kody-ade/kody-engine 0.4.26 → 0.4.28
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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.4.
|
|
6
|
+
version: "0.4.28",
|
|
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",
|
|
@@ -52,8 +52,8 @@ var package_default = {
|
|
|
52
52
|
|
|
53
53
|
// src/chat-cli.ts
|
|
54
54
|
import { execFileSync as execFileSync30 } from "child_process";
|
|
55
|
-
import * as
|
|
56
|
-
import * as
|
|
55
|
+
import * as fs29 from "fs";
|
|
56
|
+
import * as path26 from "path";
|
|
57
57
|
|
|
58
58
|
// src/chat/events.ts
|
|
59
59
|
import * as fs from "fs";
|
|
@@ -913,8 +913,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
913
913
|
|
|
914
914
|
// src/kody-cli.ts
|
|
915
915
|
import { execFileSync as execFileSync29 } from "child_process";
|
|
916
|
-
import * as
|
|
917
|
-
import * as
|
|
916
|
+
import * as fs28 from "fs";
|
|
917
|
+
import * as path25 from "path";
|
|
918
918
|
|
|
919
919
|
// src/dispatch.ts
|
|
920
920
|
import * as fs7 from "fs";
|
|
@@ -1300,8 +1300,8 @@ function coerceBare(spec, value) {
|
|
|
1300
1300
|
|
|
1301
1301
|
// src/executor.ts
|
|
1302
1302
|
import { execFileSync as execFileSync28, spawn as spawn5 } from "child_process";
|
|
1303
|
-
import * as
|
|
1304
|
-
import * as
|
|
1303
|
+
import * as fs27 from "fs";
|
|
1304
|
+
import * as path24 from "path";
|
|
1305
1305
|
|
|
1306
1306
|
// src/issue.ts
|
|
1307
1307
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
@@ -1977,10 +1977,153 @@ function stripBlockingEnv(env) {
|
|
|
1977
1977
|
return out;
|
|
1978
1978
|
}
|
|
1979
1979
|
|
|
1980
|
-
// src/
|
|
1981
|
-
import { execFileSync as execFileSync5 } from "child_process";
|
|
1980
|
+
// src/prompt.ts
|
|
1982
1981
|
import * as fs10 from "fs";
|
|
1983
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
|
+
// src/commit.ts
|
|
2124
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
2125
|
+
import * as fs11 from "fs";
|
|
2126
|
+
import * as path10 from "path";
|
|
1984
2127
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
1985
2128
|
".kody/",
|
|
1986
2129
|
".kody-engine/",
|
|
@@ -2036,18 +2179,18 @@ function tryGit(args, cwd) {
|
|
|
2036
2179
|
}
|
|
2037
2180
|
function abortUnfinishedGitOps(cwd) {
|
|
2038
2181
|
const aborted = [];
|
|
2039
|
-
const gitDir =
|
|
2040
|
-
if (!
|
|
2041
|
-
if (
|
|
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"))) {
|
|
2042
2185
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
2043
2186
|
}
|
|
2044
|
-
if (
|
|
2187
|
+
if (fs11.existsSync(path10.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
2045
2188
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
2046
2189
|
}
|
|
2047
|
-
if (
|
|
2190
|
+
if (fs11.existsSync(path10.join(gitDir, "REVERT_HEAD"))) {
|
|
2048
2191
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
2049
2192
|
}
|
|
2050
|
-
if (
|
|
2193
|
+
if (fs11.existsSync(path10.join(gitDir, "rebase-merge")) || fs11.existsSync(path10.join(gitDir, "rebase-apply"))) {
|
|
2051
2194
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
2052
2195
|
}
|
|
2053
2196
|
try {
|
|
@@ -2103,7 +2246,7 @@ function normalizeCommitMessage(raw) {
|
|
|
2103
2246
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
2104
2247
|
const allChanged = listChangedFiles(cwd);
|
|
2105
2248
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
2106
|
-
const mergeHeadExists =
|
|
2249
|
+
const mergeHeadExists = fs11.existsSync(path10.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
2107
2250
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
2108
2251
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
2109
2252
|
}
|
|
@@ -2415,21 +2558,21 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
2415
2558
|
};
|
|
2416
2559
|
|
|
2417
2560
|
// src/scripts/buildSyntheticPlugin.ts
|
|
2418
|
-
import * as
|
|
2561
|
+
import * as fs12 from "fs";
|
|
2419
2562
|
import * as os2 from "os";
|
|
2420
|
-
import * as
|
|
2563
|
+
import * as path11 from "path";
|
|
2421
2564
|
function getPluginsCatalogRoot() {
|
|
2422
|
-
const here =
|
|
2565
|
+
const here = path11.dirname(new URL(import.meta.url).pathname);
|
|
2423
2566
|
const candidates = [
|
|
2424
|
-
|
|
2567
|
+
path11.join(here, "..", "plugins"),
|
|
2425
2568
|
// dev: src/scripts → src/plugins
|
|
2426
|
-
|
|
2569
|
+
path11.join(here, "..", "..", "plugins"),
|
|
2427
2570
|
// built: dist/scripts → dist/plugins
|
|
2428
|
-
|
|
2571
|
+
path11.join(here, "..", "..", "src", "plugins")
|
|
2429
2572
|
// fallback
|
|
2430
2573
|
];
|
|
2431
2574
|
for (const c of candidates) {
|
|
2432
|
-
if (
|
|
2575
|
+
if (fs12.existsSync(c) && fs12.statSync(c).isDirectory()) return c;
|
|
2433
2576
|
}
|
|
2434
2577
|
return candidates[0];
|
|
2435
2578
|
}
|
|
@@ -2439,52 +2582,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
2439
2582
|
if (!needsSynthetic) return;
|
|
2440
2583
|
const catalog = getPluginsCatalogRoot();
|
|
2441
2584
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2442
|
-
const root =
|
|
2443
|
-
|
|
2585
|
+
const root = path11.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
2586
|
+
fs12.mkdirSync(path11.join(root, ".claude-plugin"), { recursive: true });
|
|
2444
2587
|
const resolvePart = (bucket, entry) => {
|
|
2445
|
-
const local =
|
|
2446
|
-
if (
|
|
2447
|
-
const central =
|
|
2448
|
-
if (
|
|
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;
|
|
2449
2592
|
throw new Error(
|
|
2450
2593
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
2451
2594
|
);
|
|
2452
2595
|
};
|
|
2453
2596
|
if (cc.skills.length > 0) {
|
|
2454
|
-
const dst =
|
|
2455
|
-
|
|
2597
|
+
const dst = path11.join(root, "skills");
|
|
2598
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2456
2599
|
for (const name of cc.skills) {
|
|
2457
|
-
copyDir(resolvePart("skills", name),
|
|
2600
|
+
copyDir(resolvePart("skills", name), path11.join(dst, name));
|
|
2458
2601
|
}
|
|
2459
2602
|
}
|
|
2460
2603
|
if (cc.commands.length > 0) {
|
|
2461
|
-
const dst =
|
|
2462
|
-
|
|
2604
|
+
const dst = path11.join(root, "commands");
|
|
2605
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2463
2606
|
for (const name of cc.commands) {
|
|
2464
|
-
|
|
2607
|
+
fs12.copyFileSync(resolvePart("commands", `${name}.md`), path11.join(dst, `${name}.md`));
|
|
2465
2608
|
}
|
|
2466
2609
|
}
|
|
2467
2610
|
if (cc.subagents.length > 0) {
|
|
2468
|
-
const dst =
|
|
2469
|
-
|
|
2611
|
+
const dst = path11.join(root, "agents");
|
|
2612
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2470
2613
|
for (const name of cc.subagents) {
|
|
2471
|
-
|
|
2614
|
+
fs12.copyFileSync(resolvePart("agents", `${name}.md`), path11.join(dst, `${name}.md`));
|
|
2472
2615
|
}
|
|
2473
2616
|
}
|
|
2474
2617
|
if (cc.hooks.length > 0) {
|
|
2475
|
-
const dst =
|
|
2476
|
-
|
|
2618
|
+
const dst = path11.join(root, "hooks");
|
|
2619
|
+
fs12.mkdirSync(dst, { recursive: true });
|
|
2477
2620
|
const merged = { hooks: {} };
|
|
2478
2621
|
for (const name of cc.hooks) {
|
|
2479
2622
|
const src = resolvePart("hooks", `${name}.json`);
|
|
2480
|
-
const parsed = JSON.parse(
|
|
2623
|
+
const parsed = JSON.parse(fs12.readFileSync(src, "utf-8"));
|
|
2481
2624
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
2482
2625
|
if (!Array.isArray(entries)) continue;
|
|
2483
2626
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
2484
2627
|
merged.hooks[event].push(...entries);
|
|
2485
2628
|
}
|
|
2486
2629
|
}
|
|
2487
|
-
|
|
2630
|
+
fs12.writeFileSync(path11.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
2488
2631
|
`);
|
|
2489
2632
|
}
|
|
2490
2633
|
const manifest = {
|
|
@@ -2495,17 +2638,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
2495
2638
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
2496
2639
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
2497
2640
|
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
2498
|
-
|
|
2641
|
+
fs12.writeFileSync(path11.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
2499
2642
|
`);
|
|
2500
2643
|
ctx.data.syntheticPluginPath = root;
|
|
2501
2644
|
};
|
|
2502
2645
|
function copyDir(src, dst) {
|
|
2503
|
-
|
|
2504
|
-
for (const ent of
|
|
2505
|
-
const s =
|
|
2506
|
-
const d =
|
|
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);
|
|
2507
2650
|
if (ent.isDirectory()) copyDir(s, d);
|
|
2508
|
-
else if (ent.isFile())
|
|
2651
|
+
else if (ent.isFile()) fs12.copyFileSync(s, d);
|
|
2509
2652
|
}
|
|
2510
2653
|
}
|
|
2511
2654
|
|
|
@@ -2570,123 +2713,6 @@ function formatMissesForFeedback(misses) {
|
|
|
2570
2713
|
return lines.join("\n");
|
|
2571
2714
|
}
|
|
2572
2715
|
|
|
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
|
-
if (!hasDoneMarker && !hasCommitMsg && !hasPrSummary) {
|
|
2630
|
-
const tail = text.length > 400 ? `\u2026${text.slice(-400)}` : text;
|
|
2631
|
-
return {
|
|
2632
|
-
done: false,
|
|
2633
|
-
commitMessage: "",
|
|
2634
|
-
prSummary: "",
|
|
2635
|
-
feedbackActions: "",
|
|
2636
|
-
planDeviations: "",
|
|
2637
|
-
priorArt: "",
|
|
2638
|
-
failureReason: `no DONE or FAILED marker in agent output \u2014 agent tail: ${tail}`,
|
|
2639
|
-
markerMissing: true
|
|
2640
|
-
};
|
|
2641
|
-
}
|
|
2642
|
-
const commitMatch = text.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
|
|
2643
|
-
const commitMessage = commitMatch ? stripMarkdownEmphasis(commitMatch[1]) : "";
|
|
2644
|
-
const feedbackActions = extractBlock(
|
|
2645
|
-
text,
|
|
2646
|
-
/(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
|
|
2647
|
-
/(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
|
|
2648
|
-
);
|
|
2649
|
-
let planDeviations = extractBlock(
|
|
2650
|
-
text,
|
|
2651
|
-
/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
|
|
2652
|
-
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
|
|
2653
|
-
);
|
|
2654
|
-
if (!planDeviations) {
|
|
2655
|
-
const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
2656
|
-
if (inline) planDeviations = inline[1].trim();
|
|
2657
|
-
}
|
|
2658
|
-
let priorArt = "";
|
|
2659
|
-
const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
2660
|
-
if (priorArtInline) priorArt = priorArtInline[1].trim();
|
|
2661
|
-
const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
|
|
2662
|
-
let prSummary = "";
|
|
2663
|
-
if (summaryStart !== -1) {
|
|
2664
|
-
const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
|
|
2665
|
-
prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
|
|
2666
|
-
}
|
|
2667
|
-
return {
|
|
2668
|
-
done: true,
|
|
2669
|
-
commitMessage,
|
|
2670
|
-
prSummary,
|
|
2671
|
-
feedbackActions,
|
|
2672
|
-
planDeviations,
|
|
2673
|
-
priorArt,
|
|
2674
|
-
failureReason: "",
|
|
2675
|
-
markerMissing: false
|
|
2676
|
-
};
|
|
2677
|
-
}
|
|
2678
|
-
function stripMarkdownEmphasis(s) {
|
|
2679
|
-
return s.trim().replace(/^[*_`~]+|[*_`~]+$/g, "").trim();
|
|
2680
|
-
}
|
|
2681
|
-
function extractBlock(text, startMarker, endMarker) {
|
|
2682
|
-
const startIdx = text.search(startMarker);
|
|
2683
|
-
if (startIdx === -1) return "";
|
|
2684
|
-
const afterStart = text.slice(startIdx).replace(startMarker, "");
|
|
2685
|
-
const endIdx = afterStart.search(endMarker);
|
|
2686
|
-
const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
|
|
2687
|
-
return body.replace(/\n\s*```\s*$/g, "").trim();
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
2716
|
// src/scripts/checkCoverageWithRetry.ts
|
|
2691
2717
|
var checkCoverageWithRetry = async (ctx) => {
|
|
2692
2718
|
const reqs = ctx.data.coverageRules ?? [];
|
|
@@ -4119,6 +4145,8 @@ function parseFlatYaml(text) {
|
|
|
4119
4145
|
const value = stripQuotes(line.slice(colon + 1).trim());
|
|
4120
4146
|
if (key === "every" && isScheduleEvery(value)) {
|
|
4121
4147
|
out.every = value;
|
|
4148
|
+
} else if (key === "tickScript" && value.length > 0) {
|
|
4149
|
+
out.tickScript = value;
|
|
4122
4150
|
}
|
|
4123
4151
|
}
|
|
4124
4152
|
return out;
|
|
@@ -4500,10 +4528,11 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
4500
4528
|
results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
|
|
4501
4529
|
continue;
|
|
4502
4530
|
}
|
|
4503
|
-
|
|
4531
|
+
const slugTarget = readTickScript(ctx.cwd, jobsDir, slug) ? "job-tick-scripted" : targetExecutable;
|
|
4532
|
+
process.stdout.write(`[jobs] \u2192 tick ${slug} (${slugTarget})
|
|
4504
4533
|
`);
|
|
4505
4534
|
try {
|
|
4506
|
-
const out = await runExecutable(
|
|
4535
|
+
const out = await runExecutable(slugTarget, {
|
|
4507
4536
|
cliArgs: { [slugArg]: slug },
|
|
4508
4537
|
cwd: ctx.cwd,
|
|
4509
4538
|
config: ctx.config,
|
|
@@ -4583,6 +4612,14 @@ function formatAgo(ms) {
|
|
|
4583
4612
|
const day = Math.round(hr / 24);
|
|
4584
4613
|
return `${day}d`;
|
|
4585
4614
|
}
|
|
4615
|
+
function readTickScript(cwd, jobsDir, slug) {
|
|
4616
|
+
try {
|
|
4617
|
+
const raw = fs19.readFileSync(path18.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
4618
|
+
return splitFrontmatter(raw).frontmatter.tickScript ?? null;
|
|
4619
|
+
} catch {
|
|
4620
|
+
return null;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4586
4623
|
function listJobSlugs(absDir) {
|
|
4587
4624
|
if (!fs19.existsSync(absDir)) return [];
|
|
4588
4625
|
let entries;
|
|
@@ -6268,42 +6305,47 @@ function isPartialEnvelope2(x) {
|
|
|
6268
6305
|
const o = x;
|
|
6269
6306
|
return typeof o.cursor === "string" && o.cursor.length > 0 && typeof o.done === "boolean" && o.data !== null && typeof o.data === "object" && !Array.isArray(o.data);
|
|
6270
6307
|
}
|
|
6271
|
-
|
|
6272
|
-
const fenceLabel = String(args?.fenceLabel ?? "");
|
|
6273
|
-
if (!fenceLabel) {
|
|
6274
|
-
throw new Error("parseJobStateFromAgentResult: `with.fenceLabel` is required");
|
|
6275
|
-
}
|
|
6276
|
-
if (!agentResult) {
|
|
6277
|
-
ctx.data.nextStateParseError = "agent did not run";
|
|
6278
|
-
return;
|
|
6279
|
-
}
|
|
6308
|
+
function extractNextStateFromText(text, fenceLabel, prevRev) {
|
|
6280
6309
|
const fenceRegex = new RegExp(`\`\`\`${escapeRegex2(fenceLabel)}\\s*\\n([\\s\\S]*?)\\n\`\`\``, "m");
|
|
6281
|
-
const match = fenceRegex.exec(
|
|
6310
|
+
const match = fenceRegex.exec(text);
|
|
6282
6311
|
if (!match) {
|
|
6283
|
-
|
|
6284
|
-
return;
|
|
6312
|
+
return { error: `missing \`${fenceLabel}\` fenced block` };
|
|
6285
6313
|
}
|
|
6286
6314
|
let parsed;
|
|
6287
6315
|
try {
|
|
6288
6316
|
parsed = JSON.parse(match[1].trim());
|
|
6289
6317
|
} catch (err) {
|
|
6290
|
-
|
|
6291
|
-
return;
|
|
6318
|
+
return { error: `state JSON parse error: ${err instanceof Error ? err.message : String(err)}` };
|
|
6292
6319
|
}
|
|
6293
6320
|
if (!isPartialEnvelope2(parsed)) {
|
|
6294
|
-
|
|
6295
|
-
return;
|
|
6321
|
+
return { error: "state must be an object with string `cursor`, object `data`, and boolean `done`" };
|
|
6296
6322
|
}
|
|
6297
|
-
const
|
|
6298
|
-
const prevRev = loaded?.state.rev ?? 0;
|
|
6299
|
-
const next = {
|
|
6323
|
+
const envelope = {
|
|
6300
6324
|
version: 1,
|
|
6301
6325
|
rev: prevRev + 1,
|
|
6302
6326
|
cursor: parsed.cursor,
|
|
6303
6327
|
data: parsed.data,
|
|
6304
6328
|
done: parsed.done
|
|
6305
6329
|
};
|
|
6306
|
-
|
|
6330
|
+
return { envelope };
|
|
6331
|
+
}
|
|
6332
|
+
var parseJobStateFromAgentResult = async (ctx, _profile, agentResult, args) => {
|
|
6333
|
+
const fenceLabel = String(args?.fenceLabel ?? "");
|
|
6334
|
+
if (!fenceLabel) {
|
|
6335
|
+
throw new Error("parseJobStateFromAgentResult: `with.fenceLabel` is required");
|
|
6336
|
+
}
|
|
6337
|
+
if (!agentResult) {
|
|
6338
|
+
ctx.data.nextStateParseError = "agent did not run";
|
|
6339
|
+
return;
|
|
6340
|
+
}
|
|
6341
|
+
const loaded = ctx.data.jobState;
|
|
6342
|
+
const prevRev = loaded?.state.rev ?? 0;
|
|
6343
|
+
const result = extractNextStateFromText(agentResult.finalText, fenceLabel, prevRev);
|
|
6344
|
+
if (result.error) {
|
|
6345
|
+
ctx.data.nextStateParseError = result.error.startsWith("missing `") ? `agent did not emit a \`${fenceLabel}\` fenced block` : result.error;
|
|
6346
|
+
return;
|
|
6347
|
+
}
|
|
6348
|
+
ctx.data.nextJobState = result.envelope;
|
|
6307
6349
|
};
|
|
6308
6350
|
function escapeRegex2(s) {
|
|
6309
6351
|
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
@@ -7189,6 +7231,75 @@ function resolveBaseFromLabels(labels) {
|
|
|
7189
7231
|
return `goal-${goalId}`;
|
|
7190
7232
|
}
|
|
7191
7233
|
|
|
7234
|
+
// src/scripts/runTickScript.ts
|
|
7235
|
+
import { spawnSync } from "child_process";
|
|
7236
|
+
import * as fs25 from "fs";
|
|
7237
|
+
import * as path23 from "path";
|
|
7238
|
+
var runTickScript = async (ctx, _profile, args) => {
|
|
7239
|
+
ctx.skipAgent = true;
|
|
7240
|
+
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
7241
|
+
const slugArg = String(args?.slugArg ?? "job");
|
|
7242
|
+
const fenceLabel = String(args?.fenceLabel ?? "kody-job-next-state");
|
|
7243
|
+
const slug = String(ctx.args[slugArg] ?? "").trim();
|
|
7244
|
+
if (!slug) {
|
|
7245
|
+
ctx.output.exitCode = 99;
|
|
7246
|
+
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
7247
|
+
return;
|
|
7248
|
+
}
|
|
7249
|
+
const jobPath = path23.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
7250
|
+
if (!fs25.existsSync(jobPath)) {
|
|
7251
|
+
ctx.output.exitCode = 99;
|
|
7252
|
+
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
7253
|
+
return;
|
|
7254
|
+
}
|
|
7255
|
+
const raw = fs25.readFileSync(jobPath, "utf-8");
|
|
7256
|
+
const { frontmatter } = splitFrontmatter(raw);
|
|
7257
|
+
const tickScript = frontmatter.tickScript;
|
|
7258
|
+
if (!tickScript) {
|
|
7259
|
+
ctx.output.exitCode = 99;
|
|
7260
|
+
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
7261
|
+
return;
|
|
7262
|
+
}
|
|
7263
|
+
const scriptPath = path23.isAbsolute(tickScript) ? tickScript : path23.join(ctx.cwd, tickScript);
|
|
7264
|
+
if (!fs25.existsSync(scriptPath)) {
|
|
7265
|
+
ctx.output.exitCode = 99;
|
|
7266
|
+
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
7267
|
+
return;
|
|
7268
|
+
}
|
|
7269
|
+
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
7270
|
+
const loaded = await backend.load(slug);
|
|
7271
|
+
ctx.data.jobSlug = slug;
|
|
7272
|
+
ctx.data.jobState = loaded;
|
|
7273
|
+
const result = spawnSync("bash", [scriptPath], {
|
|
7274
|
+
cwd: ctx.cwd,
|
|
7275
|
+
env: process.env,
|
|
7276
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7277
|
+
encoding: "utf-8",
|
|
7278
|
+
timeout: 5 * 60 * 1e3
|
|
7279
|
+
});
|
|
7280
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
7281
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
7282
|
+
if (result.error) {
|
|
7283
|
+
ctx.output.exitCode = 99;
|
|
7284
|
+
ctx.output.reason = `runTickScript: spawn error: ${result.error.message}`;
|
|
7285
|
+
return;
|
|
7286
|
+
}
|
|
7287
|
+
if (result.status !== 0) {
|
|
7288
|
+
ctx.output.exitCode = result.status ?? 99;
|
|
7289
|
+
ctx.output.reason = `runTickScript: ${tickScript} exited ${result.status}`;
|
|
7290
|
+
return;
|
|
7291
|
+
}
|
|
7292
|
+
const prevRev = loaded.state.rev ?? 0;
|
|
7293
|
+
const parsed = extractNextStateFromText(result.stdout ?? "", fenceLabel, prevRev);
|
|
7294
|
+
if (parsed.error) {
|
|
7295
|
+
ctx.data.nextStateParseError = parsed.error;
|
|
7296
|
+
ctx.output.exitCode = 1;
|
|
7297
|
+
ctx.output.reason = `runTickScript: ${parsed.error}`;
|
|
7298
|
+
return;
|
|
7299
|
+
}
|
|
7300
|
+
ctx.data.nextJobState = parsed.envelope;
|
|
7301
|
+
};
|
|
7302
|
+
|
|
7192
7303
|
// src/scripts/saveTaskState.ts
|
|
7193
7304
|
var saveTaskState = async (ctx, profile) => {
|
|
7194
7305
|
const target = ctx.data.commentTargetType;
|
|
@@ -7971,7 +8082,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
7971
8082
|
};
|
|
7972
8083
|
|
|
7973
8084
|
// src/scripts/writeRunSummary.ts
|
|
7974
|
-
import * as
|
|
8085
|
+
import * as fs26 from "fs";
|
|
7975
8086
|
var writeRunSummary = async (ctx, profile) => {
|
|
7976
8087
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
7977
8088
|
if (!summaryPath) return;
|
|
@@ -7993,7 +8104,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
7993
8104
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
7994
8105
|
lines.push("");
|
|
7995
8106
|
try {
|
|
7996
|
-
|
|
8107
|
+
fs26.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
7997
8108
|
`);
|
|
7998
8109
|
} catch {
|
|
7999
8110
|
}
|
|
@@ -8031,7 +8142,8 @@ var preflightScripts = {
|
|
|
8031
8142
|
diagMcp,
|
|
8032
8143
|
warmupMcp,
|
|
8033
8144
|
dispatchJobTicks,
|
|
8034
|
-
dispatchJobFileTicks
|
|
8145
|
+
dispatchJobFileTicks,
|
|
8146
|
+
runTickScript
|
|
8035
8147
|
};
|
|
8036
8148
|
var postflightScripts = {
|
|
8037
8149
|
parseAgentResult: parseAgentResult2,
|
|
@@ -8180,9 +8292,9 @@ async function runExecutable(profileName, input) {
|
|
|
8180
8292
|
data: {},
|
|
8181
8293
|
output: { exitCode: 0 }
|
|
8182
8294
|
};
|
|
8183
|
-
const ndjsonDir =
|
|
8295
|
+
const ndjsonDir = path24.join(input.cwd, ".kody");
|
|
8184
8296
|
const invokeAgent = async (prompt) => {
|
|
8185
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
8297
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path24.isAbsolute(p) ? p : path24.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
8186
8298
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
8187
8299
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
8188
8300
|
return runAgent({
|
|
@@ -8228,6 +8340,7 @@ async function runExecutable(profileName, input) {
|
|
|
8228
8340
|
return finish({ exitCode: 99, reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)" });
|
|
8229
8341
|
}
|
|
8230
8342
|
agentResult = await invokeAgent(prompt);
|
|
8343
|
+
agentResult = await rescueMissingMarker(agentResult, invokeAgent);
|
|
8231
8344
|
}
|
|
8232
8345
|
for (const entry of profile.scripts.postflight) {
|
|
8233
8346
|
const entryLabel = entry.script ?? entry.shell ?? "<unknown>";
|
|
@@ -8291,17 +8404,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
8291
8404
|
function resolveProfilePath(profileName) {
|
|
8292
8405
|
const found = resolveExecutable(profileName);
|
|
8293
8406
|
if (found) return found;
|
|
8294
|
-
const here =
|
|
8407
|
+
const here = path24.dirname(new URL(import.meta.url).pathname);
|
|
8295
8408
|
const candidates = [
|
|
8296
|
-
|
|
8409
|
+
path24.join(here, "executables", profileName, "profile.json"),
|
|
8297
8410
|
// same-dir sibling (dev)
|
|
8298
|
-
|
|
8411
|
+
path24.join(here, "..", "executables", profileName, "profile.json"),
|
|
8299
8412
|
// up one (prod: dist/bin → dist/executables)
|
|
8300
|
-
|
|
8413
|
+
path24.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
8301
8414
|
// fallback
|
|
8302
8415
|
];
|
|
8303
8416
|
for (const c of candidates) {
|
|
8304
|
-
if (
|
|
8417
|
+
if (fs27.existsSync(c)) return c;
|
|
8305
8418
|
}
|
|
8306
8419
|
return candidates[0];
|
|
8307
8420
|
}
|
|
@@ -8405,8 +8518,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
8405
8518
|
var SIGKILL_GRACE_MS = 5e3;
|
|
8406
8519
|
async function runShellEntry(entry, ctx, profile) {
|
|
8407
8520
|
const shellName = entry.shell;
|
|
8408
|
-
const shellPath =
|
|
8409
|
-
if (!
|
|
8521
|
+
const shellPath = path24.join(profile.dir, shellName);
|
|
8522
|
+
if (!fs27.existsSync(shellPath)) {
|
|
8410
8523
|
ctx.skipAgent = true;
|
|
8411
8524
|
ctx.output.exitCode = 99;
|
|
8412
8525
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -8819,9 +8932,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
8819
8932
|
return token;
|
|
8820
8933
|
}
|
|
8821
8934
|
function detectPackageManager2(cwd) {
|
|
8822
|
-
if (
|
|
8823
|
-
if (
|
|
8824
|
-
if (
|
|
8935
|
+
if (fs28.existsSync(path25.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
8936
|
+
if (fs28.existsSync(path25.join(cwd, "yarn.lock"))) return "yarn";
|
|
8937
|
+
if (fs28.existsSync(path25.join(cwd, "bun.lockb"))) return "bun";
|
|
8825
8938
|
return "npm";
|
|
8826
8939
|
}
|
|
8827
8940
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -8908,11 +9021,11 @@ function configureGitIdentity(cwd) {
|
|
|
8908
9021
|
}
|
|
8909
9022
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
8910
9023
|
if (!issueNumber) return;
|
|
8911
|
-
const logPath =
|
|
9024
|
+
const logPath = path25.join(cwd, ".kody", "last-run.jsonl");
|
|
8912
9025
|
let tail = "";
|
|
8913
9026
|
try {
|
|
8914
|
-
if (
|
|
8915
|
-
const content =
|
|
9027
|
+
if (fs28.existsSync(logPath)) {
|
|
9028
|
+
const content = fs28.readFileSync(logPath, "utf-8");
|
|
8916
9029
|
tail = content.slice(-3e3);
|
|
8917
9030
|
}
|
|
8918
9031
|
} catch {
|
|
@@ -8937,7 +9050,7 @@ async function runCi(argv) {
|
|
|
8937
9050
|
return 0;
|
|
8938
9051
|
}
|
|
8939
9052
|
const args = parseCiArgs(argv);
|
|
8940
|
-
const cwd = args.cwd ?
|
|
9053
|
+
const cwd = args.cwd ? path25.resolve(args.cwd) : process.cwd();
|
|
8941
9054
|
let earlyConfig;
|
|
8942
9055
|
try {
|
|
8943
9056
|
earlyConfig = loadConfig(cwd);
|
|
@@ -8947,9 +9060,9 @@ async function runCi(argv) {
|
|
|
8947
9060
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
8948
9061
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
8949
9062
|
let manualWorkflowDispatch = false;
|
|
8950
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
9063
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs28.existsSync(dispatchEventPath)) {
|
|
8951
9064
|
try {
|
|
8952
|
-
const evt = JSON.parse(
|
|
9065
|
+
const evt = JSON.parse(fs28.readFileSync(dispatchEventPath, "utf-8"));
|
|
8953
9066
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
8954
9067
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
8955
9068
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -9164,9 +9277,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
9164
9277
|
return result;
|
|
9165
9278
|
}
|
|
9166
9279
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
9167
|
-
const sessionFile =
|
|
9168
|
-
const eventsFile =
|
|
9169
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
9280
|
+
const sessionFile = path26.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
9281
|
+
const eventsFile = path26.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
9282
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs29.existsSync(path26.join(cwd, p)));
|
|
9170
9283
|
if (paths.length === 0) return;
|
|
9171
9284
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
9172
9285
|
try {
|
|
@@ -9204,7 +9317,7 @@ async function runChat(argv) {
|
|
|
9204
9317
|
${CHAT_HELP}`);
|
|
9205
9318
|
return 64;
|
|
9206
9319
|
}
|
|
9207
|
-
const cwd = args.cwd ?
|
|
9320
|
+
const cwd = args.cwd ? path26.resolve(args.cwd) : process.cwd();
|
|
9208
9321
|
const sessionId = args.sessionId;
|
|
9209
9322
|
const unpackedSecrets = unpackAllSecrets();
|
|
9210
9323
|
if (unpackedSecrets > 0) {
|
|
@@ -9256,7 +9369,7 @@ ${CHAT_HELP}`);
|
|
|
9256
9369
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
9257
9370
|
const meta = readMeta(sessionFile);
|
|
9258
9371
|
process.stdout.write(
|
|
9259
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
9372
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs29.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
9260
9373
|
`
|
|
9261
9374
|
);
|
|
9262
9375
|
try {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "job-tick-scripted",
|
|
3
|
+
"role": "utility",
|
|
4
|
+
"describe": "Deterministic job tick: runs the slug's `tickScript:` (declared in job frontmatter), parses next-state from its stdout, persists. No agent.",
|
|
5
|
+
"kind": "oneshot",
|
|
6
|
+
"inputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "job",
|
|
9
|
+
"flag": "--job",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"required": true,
|
|
12
|
+
"describe": "Job slug — basename (without .md) of the file under .kody/jobs/."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "force",
|
|
16
|
+
"flag": "--force",
|
|
17
|
+
"type": "bool",
|
|
18
|
+
"describe": "Accepted for parity with `job-tick`. Scripted ticks have no agent cadence guard to bypass — the dispatcher already gated on frontmatter `every:`. Forwarded to the script via env if it cares."
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"claudeCode": {
|
|
22
|
+
"model": "inherit",
|
|
23
|
+
"permissionMode": "default",
|
|
24
|
+
"maxTurns": null,
|
|
25
|
+
"maxThinkingTokens": null,
|
|
26
|
+
"systemPromptAppend": null,
|
|
27
|
+
"tools": [],
|
|
28
|
+
"hooks": [],
|
|
29
|
+
"skills": [],
|
|
30
|
+
"commands": [],
|
|
31
|
+
"subagents": [],
|
|
32
|
+
"plugins": [],
|
|
33
|
+
"mcpServers": []
|
|
34
|
+
},
|
|
35
|
+
"cliTools": [
|
|
36
|
+
{
|
|
37
|
+
"name": "gh",
|
|
38
|
+
"install": {
|
|
39
|
+
"required": true,
|
|
40
|
+
"checkCommand": "command -v gh"
|
|
41
|
+
},
|
|
42
|
+
"verify": "gh auth status",
|
|
43
|
+
"usage": "Available to the tickScript via PATH; this executable shells out to `bash <tickScript>`. Scripts use `gh pr list`, `gh pr comment`, etc.",
|
|
44
|
+
"allowedUses": ["pr", "api", "issue"]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"inputArtifacts": [],
|
|
48
|
+
"outputArtifacts": [],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"preflight": [
|
|
51
|
+
{
|
|
52
|
+
"script": "runTickScript",
|
|
53
|
+
"with": {
|
|
54
|
+
"jobsDir": ".kody/jobs",
|
|
55
|
+
"slugArg": "job",
|
|
56
|
+
"fenceLabel": "kody-job-next-state"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"postflight": [
|
|
61
|
+
{
|
|
62
|
+
"script": "writeJobStateFile",
|
|
63
|
+
"with": {
|
|
64
|
+
"jobsDir": ".kody/jobs"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -88,12 +88,13 @@ For EACH file you will change or create, include:
|
|
|
88
88
|
- Current state — what's there today (function/class/export names, relevant line ranges). Skip for new files.
|
|
89
89
|
- Target state — what will be there after the change, at the same level of specificity.
|
|
90
90
|
- Exact locations of edits (function name, line range if stable, or anchor like "after the `meta` group field, before the closing `fields: []`").
|
|
91
|
-
- For new files: rough shape including exports, key functions with signatures, and top-level module comment.
|
|
91
|
+
- For new files: rough shape including exports, key functions with signatures, and top-level module comment. **Do not paste full function bodies** — signatures and 1–2 sentence intent per export are enough for an implementer to write the body. Single-line type/interface declarations and short config snippets are fine.
|
|
92
92
|
- Dependencies touched (imports added/removed, new packages) — call out if anything needs installing.
|
|
93
93
|
|
|
94
94
|
## Algorithms & pseudocode
|
|
95
95
|
REQUIRED for any non-trivial logic (sorting, diffing, state transitions, concurrency, batching, caching, conflict resolution).
|
|
96
96
|
- Write pseudocode (not production code) showing the actual algorithm — inputs, steps, outputs.
|
|
97
|
+
- Pseudocode ≤ ~20 lines per algorithm. If it grows past that, the algorithm needs decomposing, not more lines.
|
|
97
98
|
- Call out invariants the algorithm preserves.
|
|
98
99
|
- Call out complexity (N swaps vs N-squared recalc vs single-batch write).
|
|
99
100
|
- If there's a choice between two algorithms, explain why you picked this one.
|
|
@@ -150,5 +151,6 @@ No filler. No marketing language. Depth over brevity.>
|
|
|
150
151
|
- Read-only. Do NOT modify any file.
|
|
151
152
|
- Do NOT run git or gh commands.
|
|
152
153
|
- No speculative scope — plan only what the issue asks for, but plan it THOROUGHLY.
|
|
154
|
+
- **Plan length ≤ ~1500 lines / ~15k tokens.** Larger plans get truncated by output token caps before the closing `DONE` marker — and a truncated plan is worse than a smaller one. If a feature legitimately needs more, output `FAILED: scope too large for single plan — split into <list of sub-issues>` instead of overrunning.
|
|
153
155
|
- If the issue is ambiguous and you cannot make progress without input, output `FAILED: <what's unclear>` instead of a plan.
|
|
154
156
|
- If the Research floor cannot be met because required files are missing or unreadable, output `FAILED: <what could not be read>` instead of a half-blind plan.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.28",
|
|
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",
|