@kody-ade/kody-engine 0.4.27 → 0.4.29
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 +391 -222
- package/dist/executables/job-tick-scripted/profile.json +69 -0
- 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.
|
|
6
|
+
version: "0.4.29",
|
|
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;
|
|
@@ -4493,17 +4521,19 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
4493
4521
|
const results = [];
|
|
4494
4522
|
const now = Date.now();
|
|
4495
4523
|
for (const slug of slugs) {
|
|
4496
|
-
const
|
|
4524
|
+
const frontmatter = readJobFrontmatter(ctx.cwd, jobsDir, slug);
|
|
4525
|
+
const decision = await decideShouldFire(frontmatter.every, slug, backend, now);
|
|
4497
4526
|
if (decision.skip) {
|
|
4498
4527
|
process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
|
|
4499
4528
|
`);
|
|
4500
4529
|
results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
|
|
4501
4530
|
continue;
|
|
4502
4531
|
}
|
|
4503
|
-
|
|
4532
|
+
const slugTarget = frontmatter.tickScript ? "job-tick-scripted" : targetExecutable;
|
|
4533
|
+
process.stdout.write(`[jobs] \u2192 tick ${slug} (${slugTarget})
|
|
4504
4534
|
`);
|
|
4505
4535
|
try {
|
|
4506
|
-
const out = await runExecutable(
|
|
4536
|
+
const out = await runExecutable(slugTarget, {
|
|
4507
4537
|
cliArgs: { [slugArg]: slug },
|
|
4508
4538
|
cwd: ctx.cwd,
|
|
4509
4539
|
config: ctx.config,
|
|
@@ -4536,14 +4566,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
4536
4566
|
}
|
|
4537
4567
|
}
|
|
4538
4568
|
};
|
|
4539
|
-
async function decideShouldFire(
|
|
4540
|
-
let every;
|
|
4541
|
-
try {
|
|
4542
|
-
const raw = fs19.readFileSync(path18.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
4543
|
-
every = splitFrontmatter(raw).frontmatter.every;
|
|
4544
|
-
} catch {
|
|
4545
|
-
return { skip: false, reason: "frontmatter unreadable" };
|
|
4546
|
-
}
|
|
4569
|
+
async function decideShouldFire(every, slug, backend, now) {
|
|
4547
4570
|
if (!every) return { skip: false, reason: "no schedule (every cron tick)" };
|
|
4548
4571
|
if (every === "manual") {
|
|
4549
4572
|
return { skip: true, reason: "manual-only (no auto-fire; trigger via dashboard Run now)" };
|
|
@@ -4583,6 +4606,14 @@ function formatAgo(ms) {
|
|
|
4583
4606
|
const day = Math.round(hr / 24);
|
|
4584
4607
|
return `${day}d`;
|
|
4585
4608
|
}
|
|
4609
|
+
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
4610
|
+
try {
|
|
4611
|
+
const raw = fs19.readFileSync(path18.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
4612
|
+
return splitFrontmatter(raw).frontmatter;
|
|
4613
|
+
} catch {
|
|
4614
|
+
return {};
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4586
4617
|
function listJobSlugs(absDir) {
|
|
4587
4618
|
if (!fs19.existsSync(absDir)) return [];
|
|
4588
4619
|
let entries;
|
|
@@ -6268,42 +6299,47 @@ function isPartialEnvelope2(x) {
|
|
|
6268
6299
|
const o = x;
|
|
6269
6300
|
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
6301
|
}
|
|
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
|
-
}
|
|
6302
|
+
function extractNextStateFromText(text, fenceLabel, prevRev) {
|
|
6280
6303
|
const fenceRegex = new RegExp(`\`\`\`${escapeRegex2(fenceLabel)}\\s*\\n([\\s\\S]*?)\\n\`\`\``, "m");
|
|
6281
|
-
const match = fenceRegex.exec(
|
|
6304
|
+
const match = fenceRegex.exec(text);
|
|
6282
6305
|
if (!match) {
|
|
6283
|
-
|
|
6284
|
-
return;
|
|
6306
|
+
return { error: `missing \`${fenceLabel}\` fenced block` };
|
|
6285
6307
|
}
|
|
6286
6308
|
let parsed;
|
|
6287
6309
|
try {
|
|
6288
6310
|
parsed = JSON.parse(match[1].trim());
|
|
6289
6311
|
} catch (err) {
|
|
6290
|
-
|
|
6291
|
-
return;
|
|
6312
|
+
return { error: `state JSON parse error: ${err instanceof Error ? err.message : String(err)}` };
|
|
6292
6313
|
}
|
|
6293
6314
|
if (!isPartialEnvelope2(parsed)) {
|
|
6294
|
-
|
|
6295
|
-
return;
|
|
6315
|
+
return { error: "state must be an object with string `cursor`, object `data`, and boolean `done`" };
|
|
6296
6316
|
}
|
|
6297
|
-
const
|
|
6298
|
-
const prevRev = loaded?.state.rev ?? 0;
|
|
6299
|
-
const next = {
|
|
6317
|
+
const envelope = {
|
|
6300
6318
|
version: 1,
|
|
6301
6319
|
rev: prevRev + 1,
|
|
6302
6320
|
cursor: parsed.cursor,
|
|
6303
6321
|
data: parsed.data,
|
|
6304
6322
|
done: parsed.done
|
|
6305
6323
|
};
|
|
6306
|
-
|
|
6324
|
+
return { envelope };
|
|
6325
|
+
}
|
|
6326
|
+
var parseJobStateFromAgentResult = async (ctx, _profile, agentResult, args) => {
|
|
6327
|
+
const fenceLabel = String(args?.fenceLabel ?? "");
|
|
6328
|
+
if (!fenceLabel) {
|
|
6329
|
+
throw new Error("parseJobStateFromAgentResult: `with.fenceLabel` is required");
|
|
6330
|
+
}
|
|
6331
|
+
if (!agentResult) {
|
|
6332
|
+
ctx.data.nextStateParseError = "agent did not run";
|
|
6333
|
+
return;
|
|
6334
|
+
}
|
|
6335
|
+
const loaded = ctx.data.jobState;
|
|
6336
|
+
const prevRev = loaded?.state.rev ?? 0;
|
|
6337
|
+
const result = extractNextStateFromText(agentResult.finalText, fenceLabel, prevRev);
|
|
6338
|
+
if (result.error) {
|
|
6339
|
+
ctx.data.nextStateParseError = result.error.startsWith("missing `") ? `agent did not emit a \`${fenceLabel}\` fenced block` : result.error;
|
|
6340
|
+
return;
|
|
6341
|
+
}
|
|
6342
|
+
ctx.data.nextJobState = result.envelope;
|
|
6307
6343
|
};
|
|
6308
6344
|
function escapeRegex2(s) {
|
|
6309
6345
|
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
@@ -7189,6 +7225,137 @@ function resolveBaseFromLabels(labels) {
|
|
|
7189
7225
|
return `goal-${goalId}`;
|
|
7190
7226
|
}
|
|
7191
7227
|
|
|
7228
|
+
// src/scripts/runTickScript.ts
|
|
7229
|
+
import { spawnSync } from "child_process";
|
|
7230
|
+
import * as fs25 from "fs";
|
|
7231
|
+
import * as path23 from "path";
|
|
7232
|
+
var runTickScript = async (ctx, _profile, args) => {
|
|
7233
|
+
ctx.skipAgent = true;
|
|
7234
|
+
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
7235
|
+
const slugArg = String(args?.slugArg ?? "job");
|
|
7236
|
+
const fenceLabel = String(args?.fenceLabel ?? "kody-job-next-state");
|
|
7237
|
+
const slug = String(ctx.args[slugArg] ?? "").trim();
|
|
7238
|
+
if (!slug) {
|
|
7239
|
+
ctx.output.exitCode = 99;
|
|
7240
|
+
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
7241
|
+
return;
|
|
7242
|
+
}
|
|
7243
|
+
const jobPath = path23.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
7244
|
+
if (!fs25.existsSync(jobPath)) {
|
|
7245
|
+
ctx.output.exitCode = 99;
|
|
7246
|
+
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
7247
|
+
return;
|
|
7248
|
+
}
|
|
7249
|
+
const raw = fs25.readFileSync(jobPath, "utf-8");
|
|
7250
|
+
const { frontmatter } = splitFrontmatter(raw);
|
|
7251
|
+
const tickScript = frontmatter.tickScript;
|
|
7252
|
+
if (!tickScript) {
|
|
7253
|
+
ctx.output.exitCode = 99;
|
|
7254
|
+
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
7255
|
+
return;
|
|
7256
|
+
}
|
|
7257
|
+
const scriptPath = path23.isAbsolute(tickScript) ? tickScript : path23.join(ctx.cwd, tickScript);
|
|
7258
|
+
if (!fs25.existsSync(scriptPath)) {
|
|
7259
|
+
ctx.output.exitCode = 99;
|
|
7260
|
+
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
7261
|
+
return;
|
|
7262
|
+
}
|
|
7263
|
+
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
7264
|
+
let loaded;
|
|
7265
|
+
try {
|
|
7266
|
+
loaded = await backend.load(slug);
|
|
7267
|
+
} catch (err) {
|
|
7268
|
+
ctx.output.exitCode = 99;
|
|
7269
|
+
ctx.output.reason = `runTickScript: state load failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
7270
|
+
return;
|
|
7271
|
+
}
|
|
7272
|
+
ctx.data.jobSlug = slug;
|
|
7273
|
+
ctx.data.jobState = loaded;
|
|
7274
|
+
const childEnv = buildChildEnv(process.env, Boolean(ctx.args.force));
|
|
7275
|
+
const result = spawnSync("bash", [scriptPath], {
|
|
7276
|
+
cwd: ctx.cwd,
|
|
7277
|
+
env: childEnv,
|
|
7278
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7279
|
+
encoding: "utf-8",
|
|
7280
|
+
timeout: 5 * 60 * 1e3,
|
|
7281
|
+
// Default maxBuffer is 1MB — a chatty `gh pr list --json …` over a
|
|
7282
|
+
// busy repo (or an accidental `set -x`) can blow that and silently
|
|
7283
|
+
// truncate stdout, which is the exact "silent state drop" failure
|
|
7284
|
+
// mode this whole executable was written to prevent. 16MB is well
|
|
7285
|
+
// above any realistic tick output.
|
|
7286
|
+
maxBuffer: 16 * 1024 * 1024
|
|
7287
|
+
});
|
|
7288
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
7289
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
7290
|
+
if (result.error) {
|
|
7291
|
+
ctx.output.exitCode = 99;
|
|
7292
|
+
ctx.output.reason = `runTickScript: spawn error: ${result.error.message}`;
|
|
7293
|
+
return;
|
|
7294
|
+
}
|
|
7295
|
+
if (result.signal) {
|
|
7296
|
+
ctx.output.exitCode = 124;
|
|
7297
|
+
ctx.output.reason = `runTickScript: ${tickScript} killed by ${result.signal} (likely 5min timeout)`;
|
|
7298
|
+
return;
|
|
7299
|
+
}
|
|
7300
|
+
if (result.status !== 0) {
|
|
7301
|
+
ctx.output.exitCode = result.status ?? 99;
|
|
7302
|
+
ctx.output.reason = `runTickScript: ${tickScript} exited ${result.status}`;
|
|
7303
|
+
return;
|
|
7304
|
+
}
|
|
7305
|
+
const prevRev = loaded.state.rev ?? 0;
|
|
7306
|
+
const parsed = extractNextStateFromText(result.stdout ?? "", fenceLabel, prevRev);
|
|
7307
|
+
if (parsed.error) {
|
|
7308
|
+
ctx.data.nextStateParseError = parsed.error;
|
|
7309
|
+
ctx.output.exitCode = 1;
|
|
7310
|
+
ctx.output.reason = `runTickScript: ${parsed.error}`;
|
|
7311
|
+
return;
|
|
7312
|
+
}
|
|
7313
|
+
ctx.data.nextJobState = parsed.envelope;
|
|
7314
|
+
};
|
|
7315
|
+
function buildChildEnv(parent, force) {
|
|
7316
|
+
const allow = /* @__PURE__ */ new Set([
|
|
7317
|
+
"PATH",
|
|
7318
|
+
"HOME",
|
|
7319
|
+
"USER",
|
|
7320
|
+
"LOGNAME",
|
|
7321
|
+
"SHELL",
|
|
7322
|
+
"TMPDIR",
|
|
7323
|
+
"LANG",
|
|
7324
|
+
"LC_ALL",
|
|
7325
|
+
"TERM",
|
|
7326
|
+
// GitHub auth — `gh` reads these.
|
|
7327
|
+
"GH_TOKEN",
|
|
7328
|
+
"GH_PAT",
|
|
7329
|
+
"GITHUB_TOKEN",
|
|
7330
|
+
// CI metadata commonly read by tick scripts (`gh repo view`,
|
|
7331
|
+
// workflow run links, etc.). All public values from GitHub Actions.
|
|
7332
|
+
"GITHUB_ACTIONS",
|
|
7333
|
+
"GITHUB_ACTOR",
|
|
7334
|
+
"GITHUB_REPOSITORY",
|
|
7335
|
+
"GITHUB_REPOSITORY_OWNER",
|
|
7336
|
+
"GITHUB_REF",
|
|
7337
|
+
"GITHUB_SHA",
|
|
7338
|
+
"GITHUB_RUN_ID",
|
|
7339
|
+
"GITHUB_RUN_NUMBER",
|
|
7340
|
+
"GITHUB_WORKFLOW",
|
|
7341
|
+
"GITHUB_JOB",
|
|
7342
|
+
"GITHUB_SERVER_URL",
|
|
7343
|
+
"GITHUB_API_URL",
|
|
7344
|
+
"GITHUB_EVENT_NAME",
|
|
7345
|
+
"RUNNER_OS",
|
|
7346
|
+
"RUNNER_ARCH"
|
|
7347
|
+
]);
|
|
7348
|
+
const out = {};
|
|
7349
|
+
for (const [key, value] of Object.entries(parent)) {
|
|
7350
|
+
if (value === void 0) continue;
|
|
7351
|
+
if (allow.has(key) || key.startsWith("KODY_PUBLIC_")) {
|
|
7352
|
+
out[key] = value;
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
if (force) out.KODY_FORCE = "1";
|
|
7356
|
+
return out;
|
|
7357
|
+
}
|
|
7358
|
+
|
|
7192
7359
|
// src/scripts/saveTaskState.ts
|
|
7193
7360
|
var saveTaskState = async (ctx, profile) => {
|
|
7194
7361
|
const target = ctx.data.commentTargetType;
|
|
@@ -7971,7 +8138,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
7971
8138
|
};
|
|
7972
8139
|
|
|
7973
8140
|
// src/scripts/writeRunSummary.ts
|
|
7974
|
-
import * as
|
|
8141
|
+
import * as fs26 from "fs";
|
|
7975
8142
|
var writeRunSummary = async (ctx, profile) => {
|
|
7976
8143
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
7977
8144
|
if (!summaryPath) return;
|
|
@@ -7993,7 +8160,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
7993
8160
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
7994
8161
|
lines.push("");
|
|
7995
8162
|
try {
|
|
7996
|
-
|
|
8163
|
+
fs26.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
7997
8164
|
`);
|
|
7998
8165
|
} catch {
|
|
7999
8166
|
}
|
|
@@ -8031,7 +8198,8 @@ var preflightScripts = {
|
|
|
8031
8198
|
diagMcp,
|
|
8032
8199
|
warmupMcp,
|
|
8033
8200
|
dispatchJobTicks,
|
|
8034
|
-
dispatchJobFileTicks
|
|
8201
|
+
dispatchJobFileTicks,
|
|
8202
|
+
runTickScript
|
|
8035
8203
|
};
|
|
8036
8204
|
var postflightScripts = {
|
|
8037
8205
|
parseAgentResult: parseAgentResult2,
|
|
@@ -8180,9 +8348,9 @@ async function runExecutable(profileName, input) {
|
|
|
8180
8348
|
data: {},
|
|
8181
8349
|
output: { exitCode: 0 }
|
|
8182
8350
|
};
|
|
8183
|
-
const ndjsonDir =
|
|
8351
|
+
const ndjsonDir = path24.join(input.cwd, ".kody");
|
|
8184
8352
|
const invokeAgent = async (prompt) => {
|
|
8185
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
8353
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path24.isAbsolute(p) ? p : path24.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
8186
8354
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
8187
8355
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
8188
8356
|
return runAgent({
|
|
@@ -8228,6 +8396,7 @@ async function runExecutable(profileName, input) {
|
|
|
8228
8396
|
return finish({ exitCode: 99, reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)" });
|
|
8229
8397
|
}
|
|
8230
8398
|
agentResult = await invokeAgent(prompt);
|
|
8399
|
+
agentResult = await rescueMissingMarker(agentResult, invokeAgent);
|
|
8231
8400
|
}
|
|
8232
8401
|
for (const entry of profile.scripts.postflight) {
|
|
8233
8402
|
const entryLabel = entry.script ?? entry.shell ?? "<unknown>";
|
|
@@ -8291,17 +8460,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
8291
8460
|
function resolveProfilePath(profileName) {
|
|
8292
8461
|
const found = resolveExecutable(profileName);
|
|
8293
8462
|
if (found) return found;
|
|
8294
|
-
const here =
|
|
8463
|
+
const here = path24.dirname(new URL(import.meta.url).pathname);
|
|
8295
8464
|
const candidates = [
|
|
8296
|
-
|
|
8465
|
+
path24.join(here, "executables", profileName, "profile.json"),
|
|
8297
8466
|
// same-dir sibling (dev)
|
|
8298
|
-
|
|
8467
|
+
path24.join(here, "..", "executables", profileName, "profile.json"),
|
|
8299
8468
|
// up one (prod: dist/bin → dist/executables)
|
|
8300
|
-
|
|
8469
|
+
path24.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
8301
8470
|
// fallback
|
|
8302
8471
|
];
|
|
8303
8472
|
for (const c of candidates) {
|
|
8304
|
-
if (
|
|
8473
|
+
if (fs27.existsSync(c)) return c;
|
|
8305
8474
|
}
|
|
8306
8475
|
return candidates[0];
|
|
8307
8476
|
}
|
|
@@ -8405,8 +8574,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
8405
8574
|
var SIGKILL_GRACE_MS = 5e3;
|
|
8406
8575
|
async function runShellEntry(entry, ctx, profile) {
|
|
8407
8576
|
const shellName = entry.shell;
|
|
8408
|
-
const shellPath =
|
|
8409
|
-
if (!
|
|
8577
|
+
const shellPath = path24.join(profile.dir, shellName);
|
|
8578
|
+
if (!fs27.existsSync(shellPath)) {
|
|
8410
8579
|
ctx.skipAgent = true;
|
|
8411
8580
|
ctx.output.exitCode = 99;
|
|
8412
8581
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -8819,9 +8988,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
8819
8988
|
return token;
|
|
8820
8989
|
}
|
|
8821
8990
|
function detectPackageManager2(cwd) {
|
|
8822
|
-
if (
|
|
8823
|
-
if (
|
|
8824
|
-
if (
|
|
8991
|
+
if (fs28.existsSync(path25.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
8992
|
+
if (fs28.existsSync(path25.join(cwd, "yarn.lock"))) return "yarn";
|
|
8993
|
+
if (fs28.existsSync(path25.join(cwd, "bun.lockb"))) return "bun";
|
|
8825
8994
|
return "npm";
|
|
8826
8995
|
}
|
|
8827
8996
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -8908,11 +9077,11 @@ function configureGitIdentity(cwd) {
|
|
|
8908
9077
|
}
|
|
8909
9078
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
8910
9079
|
if (!issueNumber) return;
|
|
8911
|
-
const logPath =
|
|
9080
|
+
const logPath = path25.join(cwd, ".kody", "last-run.jsonl");
|
|
8912
9081
|
let tail = "";
|
|
8913
9082
|
try {
|
|
8914
|
-
if (
|
|
8915
|
-
const content =
|
|
9083
|
+
if (fs28.existsSync(logPath)) {
|
|
9084
|
+
const content = fs28.readFileSync(logPath, "utf-8");
|
|
8916
9085
|
tail = content.slice(-3e3);
|
|
8917
9086
|
}
|
|
8918
9087
|
} catch {
|
|
@@ -8937,7 +9106,7 @@ async function runCi(argv) {
|
|
|
8937
9106
|
return 0;
|
|
8938
9107
|
}
|
|
8939
9108
|
const args = parseCiArgs(argv);
|
|
8940
|
-
const cwd = args.cwd ?
|
|
9109
|
+
const cwd = args.cwd ? path25.resolve(args.cwd) : process.cwd();
|
|
8941
9110
|
let earlyConfig;
|
|
8942
9111
|
try {
|
|
8943
9112
|
earlyConfig = loadConfig(cwd);
|
|
@@ -8947,9 +9116,9 @@ async function runCi(argv) {
|
|
|
8947
9116
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
8948
9117
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
8949
9118
|
let manualWorkflowDispatch = false;
|
|
8950
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
9119
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs28.existsSync(dispatchEventPath)) {
|
|
8951
9120
|
try {
|
|
8952
|
-
const evt = JSON.parse(
|
|
9121
|
+
const evt = JSON.parse(fs28.readFileSync(dispatchEventPath, "utf-8"));
|
|
8953
9122
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
8954
9123
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
8955
9124
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -9164,9 +9333,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
9164
9333
|
return result;
|
|
9165
9334
|
}
|
|
9166
9335
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
9167
|
-
const sessionFile =
|
|
9168
|
-
const eventsFile =
|
|
9169
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
9336
|
+
const sessionFile = path26.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
9337
|
+
const eventsFile = path26.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
9338
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs29.existsSync(path26.join(cwd, p)));
|
|
9170
9339
|
if (paths.length === 0) return;
|
|
9171
9340
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
9172
9341
|
try {
|
|
@@ -9204,7 +9373,7 @@ async function runChat(argv) {
|
|
|
9204
9373
|
${CHAT_HELP}`);
|
|
9205
9374
|
return 64;
|
|
9206
9375
|
}
|
|
9207
|
-
const cwd = args.cwd ?
|
|
9376
|
+
const cwd = args.cwd ? path26.resolve(args.cwd) : process.cwd();
|
|
9208
9377
|
const sessionId = args.sessionId;
|
|
9209
9378
|
const unpackedSecrets = unpackAllSecrets();
|
|
9210
9379
|
if (unpackedSecrets > 0) {
|
|
@@ -9256,7 +9425,7 @@ ${CHAT_HELP}`);
|
|
|
9256
9425
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
9257
9426
|
const meta = readMeta(sessionFile);
|
|
9258
9427
|
process.stdout.write(
|
|
9259
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
9428
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs29.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
9260
9429
|
`
|
|
9261
9430
|
);
|
|
9262
9431
|
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
|
+
}
|
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.29",
|
|
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",
|