@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.
- package/dist/bin/kody.js +227 -215
- 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.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
|
|
2126
|
-
import * as
|
|
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 =
|
|
2183
|
-
if (!
|
|
2184
|
-
if (
|
|
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 (
|
|
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 (
|
|
2047
|
+
if (fs10.existsSync(path9.join(gitDir, "REVERT_HEAD"))) {
|
|
2191
2048
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
2192
2049
|
}
|
|
2193
|
-
if (
|
|
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 =
|
|
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
|
|
2418
|
+
import * as fs11 from "fs";
|
|
2562
2419
|
import * as os2 from "os";
|
|
2563
|
-
import * as
|
|
2420
|
+
import * as path10 from "path";
|
|
2564
2421
|
function getPluginsCatalogRoot() {
|
|
2565
|
-
const here =
|
|
2422
|
+
const here = path10.dirname(new URL(import.meta.url).pathname);
|
|
2566
2423
|
const candidates = [
|
|
2567
|
-
|
|
2424
|
+
path10.join(here, "..", "plugins"),
|
|
2568
2425
|
// dev: src/scripts → src/plugins
|
|
2569
|
-
|
|
2426
|
+
path10.join(here, "..", "..", "plugins"),
|
|
2570
2427
|
// built: dist/scripts → dist/plugins
|
|
2571
|
-
|
|
2428
|
+
path10.join(here, "..", "..", "src", "plugins")
|
|
2572
2429
|
// fallback
|
|
2573
2430
|
];
|
|
2574
2431
|
for (const c of candidates) {
|
|
2575
|
-
if (
|
|
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 =
|
|
2586
|
-
|
|
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 =
|
|
2589
|
-
if (
|
|
2590
|
-
const central =
|
|
2591
|
-
if (
|
|
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 =
|
|
2598
|
-
|
|
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),
|
|
2457
|
+
copyDir(resolvePart("skills", name), path10.join(dst, name));
|
|
2601
2458
|
}
|
|
2602
2459
|
}
|
|
2603
2460
|
if (cc.commands.length > 0) {
|
|
2604
|
-
const dst =
|
|
2605
|
-
|
|
2461
|
+
const dst = path10.join(root, "commands");
|
|
2462
|
+
fs11.mkdirSync(dst, { recursive: true });
|
|
2606
2463
|
for (const name of cc.commands) {
|
|
2607
|
-
|
|
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 =
|
|
2612
|
-
|
|
2468
|
+
const dst = path10.join(root, "agents");
|
|
2469
|
+
fs11.mkdirSync(dst, { recursive: true });
|
|
2613
2470
|
for (const name of cc.subagents) {
|
|
2614
|
-
|
|
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 =
|
|
2619
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2647
|
-
for (const ent of
|
|
2648
|
-
const s =
|
|
2649
|
-
const d =
|
|
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())
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
4574
|
+
return splitFrontmatter(raw).frontmatter;
|
|
4619
4575
|
} catch {
|
|
4620
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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",
|