@kody-ade/kody-engine 0.4.29 → 0.4.31
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 +1213 -406
- package/dist/executables/goal-tick/profile.json +49 -2
- package/package.json +1 -1
- package/dist/executables/goal-tick/tick.sh +0 -596
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.31",
|
|
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",
|
|
@@ -51,9 +51,9 @@ var package_default = {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
// src/chat-cli.ts
|
|
54
|
-
import { execFileSync as
|
|
55
|
-
import * as
|
|
56
|
-
import * as
|
|
54
|
+
import { execFileSync as execFileSync32 } from "child_process";
|
|
55
|
+
import * as fs30 from "fs";
|
|
56
|
+
import * as path28 from "path";
|
|
57
57
|
|
|
58
58
|
// src/chat/events.ts
|
|
59
59
|
import * as fs from "fs";
|
|
@@ -912,9 +912,9 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
912
912
|
}
|
|
913
913
|
|
|
914
914
|
// src/kody-cli.ts
|
|
915
|
-
import { execFileSync as
|
|
916
|
-
import * as
|
|
917
|
-
import * as
|
|
915
|
+
import { execFileSync as execFileSync31 } from "child_process";
|
|
916
|
+
import * as fs29 from "fs";
|
|
917
|
+
import * as path27 from "path";
|
|
918
918
|
|
|
919
919
|
// src/dispatch.ts
|
|
920
920
|
import * as fs7 from "fs";
|
|
@@ -1299,9 +1299,9 @@ function coerceBare(spec, value) {
|
|
|
1299
1299
|
}
|
|
1300
1300
|
|
|
1301
1301
|
// src/executor.ts
|
|
1302
|
-
import { execFileSync as
|
|
1303
|
-
import * as
|
|
1304
|
-
import * as
|
|
1302
|
+
import { execFileSync as execFileSync30, spawn as spawn5 } from "child_process";
|
|
1303
|
+
import * as fs28 from "fs";
|
|
1304
|
+
import * as path26 from "path";
|
|
1305
1305
|
|
|
1306
1306
|
// src/issue.ts
|
|
1307
1307
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
@@ -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 ?? [];
|
|
@@ -2787,6 +2749,301 @@ function defaultLabelMap() {
|
|
|
2787
2749
|
};
|
|
2788
2750
|
}
|
|
2789
2751
|
|
|
2752
|
+
// src/goal/operations.ts
|
|
2753
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
2754
|
+
|
|
2755
|
+
// src/goal/labels.ts
|
|
2756
|
+
function goalLabel(goalId) {
|
|
2757
|
+
return `goal:${goalId}`;
|
|
2758
|
+
}
|
|
2759
|
+
var DISPATCHED_LABEL = "goal-runner:dispatched";
|
|
2760
|
+
var FAILED_LABEL = "goal-runner:failed";
|
|
2761
|
+
var UMBRELLA_BUILDING_LABEL = "kody:building";
|
|
2762
|
+
var TICK_LABELS = [
|
|
2763
|
+
{
|
|
2764
|
+
name: DISPATCHED_LABEL,
|
|
2765
|
+
color: "ededed",
|
|
2766
|
+
description: "kody goal-runner: already dispatched this tick"
|
|
2767
|
+
},
|
|
2768
|
+
{
|
|
2769
|
+
name: FAILED_LABEL,
|
|
2770
|
+
color: "b60205",
|
|
2771
|
+
description: "kody goal-runner: task failed; needs human attention"
|
|
2772
|
+
}
|
|
2773
|
+
];
|
|
2774
|
+
|
|
2775
|
+
// src/goal/operations.ts
|
|
2776
|
+
function fail(err) {
|
|
2777
|
+
if (err instanceof Error) {
|
|
2778
|
+
const lines = err.message.split("\n").filter(Boolean);
|
|
2779
|
+
return { ok: false, error: lines[0] ?? err.message };
|
|
2780
|
+
}
|
|
2781
|
+
return { ok: false, error: String(err) };
|
|
2782
|
+
}
|
|
2783
|
+
function listGoalIssues(goalId, excludeIssueNumber, cwd) {
|
|
2784
|
+
try {
|
|
2785
|
+
const out = gh(
|
|
2786
|
+
[
|
|
2787
|
+
"api",
|
|
2788
|
+
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
2789
|
+
"--jq",
|
|
2790
|
+
"[.[] | select(.pull_request == null) | {number, state: (.state | ascii_upcase), labels: [.labels[].name]}]"
|
|
2791
|
+
],
|
|
2792
|
+
{ cwd }
|
|
2793
|
+
);
|
|
2794
|
+
const arr = JSON.parse(out);
|
|
2795
|
+
const filtered = excludeIssueNumber !== void 0 ? arr.filter((i) => i.number !== excludeIssueNumber) : arr;
|
|
2796
|
+
return { ok: true, value: filtered };
|
|
2797
|
+
} catch (err) {
|
|
2798
|
+
return fail(err);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
function ensureLabel(name, color, description, cwd) {
|
|
2802
|
+
try {
|
|
2803
|
+
gh(["label", "create", name, "--color", color, "--description", description, "--force"], { cwd });
|
|
2804
|
+
return { ok: true };
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
return fail(err);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
function addLabel2(issueNumber, label, cwd) {
|
|
2810
|
+
try {
|
|
2811
|
+
gh(["issue", "edit", String(issueNumber), "--add-label", label], { cwd });
|
|
2812
|
+
return { ok: true };
|
|
2813
|
+
} catch (err) {
|
|
2814
|
+
return fail(err);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
function commentOnIssue(issueNumber, body, cwd) {
|
|
2818
|
+
try {
|
|
2819
|
+
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
2820
|
+
return { ok: true };
|
|
2821
|
+
} catch (err) {
|
|
2822
|
+
return fail(err);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
function closeIssue(issueNumber, options, cwd) {
|
|
2826
|
+
try {
|
|
2827
|
+
if (options.comment) {
|
|
2828
|
+
gh(["issue", "comment", String(issueNumber), "--body", options.comment], { cwd });
|
|
2829
|
+
}
|
|
2830
|
+
const args = ["issue", "close", String(issueNumber)];
|
|
2831
|
+
if (options.reason) args.push("--reason", options.reason);
|
|
2832
|
+
gh(args, { cwd });
|
|
2833
|
+
return { ok: true };
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
return fail(err);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
function getIssueState(issueNumber, cwd) {
|
|
2839
|
+
try {
|
|
2840
|
+
const out = gh(["issue", "view", String(issueNumber), "--json", "state", "--jq", ".state"], {
|
|
2841
|
+
cwd
|
|
2842
|
+
});
|
|
2843
|
+
const norm = out.trim().toUpperCase();
|
|
2844
|
+
if (norm !== "OPEN" && norm !== "CLOSED") {
|
|
2845
|
+
return { ok: false, error: `unexpected state: ${out}` };
|
|
2846
|
+
}
|
|
2847
|
+
return { ok: true, value: norm };
|
|
2848
|
+
} catch (err) {
|
|
2849
|
+
return fail(err);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
function findUmbrellaByTitle(goalId, title, cwd) {
|
|
2853
|
+
try {
|
|
2854
|
+
const out = gh(
|
|
2855
|
+
[
|
|
2856
|
+
"api",
|
|
2857
|
+
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
2858
|
+
"--jq",
|
|
2859
|
+
`[.[] | select(.pull_request == null) | select(.title == "${title.replace(/"/g, '\\"')}")] | (map(select(.state == "open")) + map(select(.state != "open")))[0].number // empty`
|
|
2860
|
+
],
|
|
2861
|
+
{ cwd }
|
|
2862
|
+
);
|
|
2863
|
+
const trimmed = out.trim();
|
|
2864
|
+
if (!trimmed) return { ok: true, value: null };
|
|
2865
|
+
const n = Number.parseInt(trimmed, 10);
|
|
2866
|
+
if (!Number.isFinite(n)) return { ok: true, value: null };
|
|
2867
|
+
return { ok: true, value: n };
|
|
2868
|
+
} catch (err) {
|
|
2869
|
+
return fail(err);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
function createIssue(args, cwd) {
|
|
2873
|
+
try {
|
|
2874
|
+
const cliArgs = ["issue", "create", "--title", args.title, "--body", args.body];
|
|
2875
|
+
for (const l of args.labels) cliArgs.push("--label", l);
|
|
2876
|
+
const url = gh(cliArgs, { cwd });
|
|
2877
|
+
const match = url.match(/\/issues\/(\d+)/);
|
|
2878
|
+
if (!match?.[1]) return { ok: false, error: `couldn't parse issue number from URL: ${url}` };
|
|
2879
|
+
return { ok: true, value: Number.parseInt(match[1], 10) };
|
|
2880
|
+
} catch (err) {
|
|
2881
|
+
return fail(err);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
function listPrsByBase(base, state, cwd) {
|
|
2885
|
+
try {
|
|
2886
|
+
const out = gh(
|
|
2887
|
+
[
|
|
2888
|
+
"pr",
|
|
2889
|
+
"list",
|
|
2890
|
+
"--base",
|
|
2891
|
+
base,
|
|
2892
|
+
"--state",
|
|
2893
|
+
state,
|
|
2894
|
+
"--limit",
|
|
2895
|
+
"50",
|
|
2896
|
+
"--json",
|
|
2897
|
+
"number,isDraft,mergeable,mergeStateStatus,url,headRefName,body"
|
|
2898
|
+
],
|
|
2899
|
+
{ cwd }
|
|
2900
|
+
);
|
|
2901
|
+
return { ok: true, value: JSON.parse(out) };
|
|
2902
|
+
} catch (err) {
|
|
2903
|
+
return fail(err);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
function listPrsByHead(head, state, cwd) {
|
|
2907
|
+
try {
|
|
2908
|
+
const out = gh(
|
|
2909
|
+
[
|
|
2910
|
+
"pr",
|
|
2911
|
+
"list",
|
|
2912
|
+
"--head",
|
|
2913
|
+
head,
|
|
2914
|
+
"--state",
|
|
2915
|
+
state,
|
|
2916
|
+
"--json",
|
|
2917
|
+
"number,isDraft,mergeable,mergeStateStatus,url,headRefName,body"
|
|
2918
|
+
],
|
|
2919
|
+
{ cwd }
|
|
2920
|
+
);
|
|
2921
|
+
return { ok: true, value: JSON.parse(out) };
|
|
2922
|
+
} catch (err) {
|
|
2923
|
+
return fail(err);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
function mergePrSquash(prNumber, cwd) {
|
|
2927
|
+
try {
|
|
2928
|
+
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
2929
|
+
return { ok: true };
|
|
2930
|
+
} catch (err) {
|
|
2931
|
+
return fail(err);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
function closePr(prNumber, comment, cwd) {
|
|
2935
|
+
try {
|
|
2936
|
+
gh(["pr", "close", String(prNumber), "--comment", comment], { cwd });
|
|
2937
|
+
return { ok: true };
|
|
2938
|
+
} catch (err) {
|
|
2939
|
+
return fail(err);
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
function createPr(args, cwd) {
|
|
2943
|
+
try {
|
|
2944
|
+
const cli = ["pr", "create", "--head", args.head, "--base", args.base, "--title", args.title, "--body", args.body];
|
|
2945
|
+
if (args.draft) cli.push("--draft");
|
|
2946
|
+
const url = gh(cli, { cwd });
|
|
2947
|
+
if (!url.includes("/pull/")) return { ok: false, error: `gh pr create returned unexpected output: ${url}` };
|
|
2948
|
+
return { ok: true, value: url.trim() };
|
|
2949
|
+
} catch (err) {
|
|
2950
|
+
return fail(err);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
function editPrBody(prNumber, body, cwd) {
|
|
2954
|
+
try {
|
|
2955
|
+
gh(["pr", "edit", String(prNumber), "--body", body], { cwd });
|
|
2956
|
+
return { ok: true };
|
|
2957
|
+
} catch (err) {
|
|
2958
|
+
return fail(err);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
function markPrReady(prNumber, cwd) {
|
|
2962
|
+
try {
|
|
2963
|
+
gh(["pr", "ready", String(prNumber)], { cwd });
|
|
2964
|
+
return { ok: true };
|
|
2965
|
+
} catch (err) {
|
|
2966
|
+
return fail(err);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
function ghTokenEnv() {
|
|
2970
|
+
const token = process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
2971
|
+
return token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
2972
|
+
}
|
|
2973
|
+
function remoteBranchExists(ref, cwd) {
|
|
2974
|
+
try {
|
|
2975
|
+
execFileSync9("git", ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${ref}`], {
|
|
2976
|
+
cwd,
|
|
2977
|
+
stdio: "pipe",
|
|
2978
|
+
env: ghTokenEnv()
|
|
2979
|
+
});
|
|
2980
|
+
return true;
|
|
2981
|
+
} catch {
|
|
2982
|
+
return false;
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
function fetchOrigin(cwd) {
|
|
2986
|
+
try {
|
|
2987
|
+
execFileSync9("git", ["fetch", "origin", "--quiet"], { cwd, stdio: "pipe", env: ghTokenEnv() });
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
function createBranchFrom(branch, base, cwd) {
|
|
2992
|
+
try {
|
|
2993
|
+
execFileSync9("git", ["push", "origin", `refs/remotes/origin/${base}:refs/heads/${branch}`, "--quiet"], {
|
|
2994
|
+
cwd,
|
|
2995
|
+
stdio: "pipe",
|
|
2996
|
+
env: ghTokenEnv()
|
|
2997
|
+
});
|
|
2998
|
+
return { ok: true };
|
|
2999
|
+
} catch (err) {
|
|
3000
|
+
return fail(err);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
function inferLinkedIssue(pr) {
|
|
3004
|
+
const body = pr.body ?? "";
|
|
3005
|
+
const m = body.match(/\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/i);
|
|
3006
|
+
if (m?.[1]) return Number.parseInt(m[1], 10);
|
|
3007
|
+
const ref = pr.headRefName ?? "";
|
|
3008
|
+
const bm = ref.match(/^(\d+)-/);
|
|
3009
|
+
if (bm?.[1]) return Number.parseInt(bm[1], 10);
|
|
3010
|
+
return void 0;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
// src/scripts/closeMergedTaskIssues.ts
|
|
3014
|
+
var closeMergedTaskIssues = async (ctx) => {
|
|
3015
|
+
const goal = ctx.data.goal;
|
|
3016
|
+
if (!goal) return;
|
|
3017
|
+
const merged = listPrsByBase(goal.goalBranch, "merged", ctx.cwd);
|
|
3018
|
+
if (!merged.ok) {
|
|
3019
|
+
process.stderr.write(`[goal-tick] closeMergedTaskIssues: list failed: ${merged.error}
|
|
3020
|
+
`);
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3024
|
+
for (const pr of merged.value ?? []) {
|
|
3025
|
+
const linked = inferLinkedIssue(pr);
|
|
3026
|
+
if (linked === void 0 || seen.has(linked)) continue;
|
|
3027
|
+
seen.add(linked);
|
|
3028
|
+
const stateRes = getIssueState(linked, ctx.cwd);
|
|
3029
|
+
if (!stateRes.ok || stateRes.value !== "OPEN") continue;
|
|
3030
|
+
process.stdout.write(`[goal-tick] closing #${linked} (PR merged into ${goal.goalBranch})
|
|
3031
|
+
`);
|
|
3032
|
+
const r = closeIssue(
|
|
3033
|
+
linked,
|
|
3034
|
+
{
|
|
3035
|
+
comment: `_Closed by goal-tick: PR for this task merged into \`${goal.goalBranch}\`._`,
|
|
3036
|
+
reason: "completed"
|
|
3037
|
+
},
|
|
3038
|
+
ctx.cwd
|
|
3039
|
+
);
|
|
3040
|
+
if (!r.ok) {
|
|
3041
|
+
process.stderr.write(`[goal-tick] failed to close #${linked}: ${r.error} (continuing)
|
|
3042
|
+
`);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
|
|
2790
3047
|
// src/scripts/commitAndPush.ts
|
|
2791
3048
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
2792
3049
|
var commitAndPush2 = async (ctx) => {
|
|
@@ -2830,17 +3087,69 @@ var commitAndPush2 = async (ctx) => {
|
|
|
2830
3087
|
ctx.data.hasCommitsAhead = hasCommitsAhead(branch, ctx.config.git.defaultBranch, ctx.cwd);
|
|
2831
3088
|
};
|
|
2832
3089
|
|
|
3090
|
+
// src/scripts/commitGoalState.ts
|
|
3091
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3092
|
+
import * as path12 from "path";
|
|
3093
|
+
var commitGoalState = async (ctx) => {
|
|
3094
|
+
const goal = ctx.data.goal;
|
|
3095
|
+
if (!goal) return;
|
|
3096
|
+
const stateRel = path12.posix.join(".kody", "goals", goal.id, "state.json");
|
|
3097
|
+
try {
|
|
3098
|
+
execFileSync10("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3099
|
+
} catch (err) {
|
|
3100
|
+
process.stderr.write(
|
|
3101
|
+
`[goal-tick] commitGoalState: git add failed: ${err instanceof Error ? err.message : String(err)}
|
|
3102
|
+
`
|
|
3103
|
+
);
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
try {
|
|
3107
|
+
execFileSync10("git", ["diff", "--cached", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3108
|
+
return;
|
|
3109
|
+
} catch {
|
|
3110
|
+
}
|
|
3111
|
+
const msg = describeCommitMessage(goal);
|
|
3112
|
+
try {
|
|
3113
|
+
execFileSync10("git", ["commit", "-m", msg, "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3114
|
+
} catch (err) {
|
|
3115
|
+
process.stderr.write(
|
|
3116
|
+
`[goal-tick] commitGoalState: git commit failed: ${err instanceof Error ? err.message : String(err)}
|
|
3117
|
+
`
|
|
3118
|
+
);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
try {
|
|
3122
|
+
execFileSync10("git", ["push", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
|
|
3123
|
+
} catch {
|
|
3124
|
+
process.stderr.write("[goal-tick] commitGoalState: push failed (will retry next tick)\n");
|
|
3125
|
+
}
|
|
3126
|
+
};
|
|
3127
|
+
function describeCommitMessage(goal) {
|
|
3128
|
+
if (goal.state === "closed") return `chore(goals): abandon ${goal.id} (cleanup complete)`;
|
|
3129
|
+
if (goal.state === "done") return `chore(goals): mark ${goal.id} done`;
|
|
3130
|
+
if (goal.lastDispatchedIssue !== void 0) {
|
|
3131
|
+
return `chore(goals): dispatched #${goal.lastDispatchedIssue} for ${goal.id}`;
|
|
3132
|
+
}
|
|
3133
|
+
if (goal.phase === "in-flight") {
|
|
3134
|
+
return `chore(goals): tick ${goal.id} (waiting for in-flight task)`;
|
|
3135
|
+
}
|
|
3136
|
+
if (goal.phase === "blocked-by-failure") {
|
|
3137
|
+
return `chore(goals): tick ${goal.id} (blocked by failed task)`;
|
|
3138
|
+
}
|
|
3139
|
+
return `chore(goals): tick ${goal.id} (idle)`;
|
|
3140
|
+
}
|
|
3141
|
+
|
|
2833
3142
|
// src/scripts/composePrompt.ts
|
|
2834
3143
|
import * as fs13 from "fs";
|
|
2835
|
-
import * as
|
|
3144
|
+
import * as path13 from "path";
|
|
2836
3145
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
2837
3146
|
var composePrompt = async (ctx, profile) => {
|
|
2838
3147
|
const explicit = ctx.data.promptTemplate;
|
|
2839
3148
|
const mode = ctx.args.mode;
|
|
2840
3149
|
const candidates = [
|
|
2841
|
-
explicit ?
|
|
2842
|
-
mode ?
|
|
2843
|
-
|
|
3150
|
+
explicit ? path13.join(profile.dir, explicit) : null,
|
|
3151
|
+
mode ? path13.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
3152
|
+
path13.join(profile.dir, "prompt.md")
|
|
2844
3153
|
].filter(Boolean);
|
|
2845
3154
|
let templatePath = "";
|
|
2846
3155
|
for (const c of candidates) {
|
|
@@ -2929,9 +3238,9 @@ function formatToolsUsage(profile) {
|
|
|
2929
3238
|
}
|
|
2930
3239
|
|
|
2931
3240
|
// src/scripts/createQaGoal.ts
|
|
2932
|
-
import { execFileSync as
|
|
3241
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
2933
3242
|
import * as fs14 from "fs";
|
|
2934
|
-
import * as
|
|
3243
|
+
import * as path14 from "path";
|
|
2935
3244
|
|
|
2936
3245
|
// src/scripts/postReviewResult.ts
|
|
2937
3246
|
function detectVerdict(body) {
|
|
@@ -3113,7 +3422,7 @@ ${json}
|
|
|
3113
3422
|
${MANIFEST_END}
|
|
3114
3423
|
`;
|
|
3115
3424
|
}
|
|
3116
|
-
function
|
|
3425
|
+
function ensureLabel2(name, color, description, cwd) {
|
|
3117
3426
|
try {
|
|
3118
3427
|
gh(["label", "create", name, "--color", color, "--description", description, "--force"], { cwd });
|
|
3119
3428
|
} catch {
|
|
@@ -3133,7 +3442,7 @@ function ensureSeverityLabels(findings, cwd) {
|
|
|
3133
3442
|
for (const f of findings) {
|
|
3134
3443
|
if (seen.has(f.severity)) continue;
|
|
3135
3444
|
seen.add(f.severity);
|
|
3136
|
-
|
|
3445
|
+
ensureLabel2(severityLabel(f.severity), SEVERITY_COLORS[f.severity], `kody QA finding severity ${f.severity}`, cwd);
|
|
3137
3446
|
}
|
|
3138
3447
|
}
|
|
3139
3448
|
function buildIssueBody(f, goalId, parentManifestNumber) {
|
|
@@ -3167,7 +3476,7 @@ function buildIssueBody(f, goalId, parentManifestNumber) {
|
|
|
3167
3476
|
return lines.join("\n");
|
|
3168
3477
|
}
|
|
3169
3478
|
function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
3170
|
-
|
|
3479
|
+
ensureLabel2(MANIFEST_LABEL, "8b5cf6", "kody: goals manifest", cwd);
|
|
3171
3480
|
const body = serializeManifestBody(manifest);
|
|
3172
3481
|
if (number !== null) {
|
|
3173
3482
|
gh(["issue", "edit", String(number), "--body-file", "-"], { input: body, cwd });
|
|
@@ -3183,7 +3492,7 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
|
3183
3492
|
return { number: Number(m[1]), created: true };
|
|
3184
3493
|
}
|
|
3185
3494
|
function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
3186
|
-
const dir =
|
|
3495
|
+
const dir = path14.join(cwd, ".kody", "goals", goalId);
|
|
3187
3496
|
fs14.mkdirSync(dir, { recursive: true });
|
|
3188
3497
|
const state = {
|
|
3189
3498
|
version: 1,
|
|
@@ -3192,7 +3501,7 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
|
3192
3501
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3193
3502
|
...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
|
|
3194
3503
|
};
|
|
3195
|
-
const filePath =
|
|
3504
|
+
const filePath = path14.join(dir, "state.json");
|
|
3196
3505
|
fs14.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
|
|
3197
3506
|
`);
|
|
3198
3507
|
return filePath;
|
|
@@ -3200,7 +3509,7 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
|
3200
3509
|
function gitTry(args, cwd) {
|
|
3201
3510
|
const env = { ...process.env, SKIP_HOOKS: "1", HUSKY: "0" };
|
|
3202
3511
|
try {
|
|
3203
|
-
|
|
3512
|
+
execFileSync11("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], env });
|
|
3204
3513
|
return { ok: true, stderr: "" };
|
|
3205
3514
|
} catch (err) {
|
|
3206
3515
|
const e = err;
|
|
@@ -3282,8 +3591,8 @@ ${tail}
|
|
|
3282
3591
|
}
|
|
3283
3592
|
function createTaskIssue(finding, goalId, manifestNumber, cwd) {
|
|
3284
3593
|
const labels = [`goal:${goalId}`, severityLabel(finding.severity), FINDING_LABEL];
|
|
3285
|
-
|
|
3286
|
-
|
|
3594
|
+
ensureLabel2(`goal:${goalId}`, "1d76db", `goal: ${goalId}`, cwd);
|
|
3595
|
+
ensureLabel2(FINDING_LABEL, "ededed", "kody: QA finding", cwd);
|
|
3287
3596
|
const title = `[${finding.severity}] ${finding.title}`.slice(0, 240);
|
|
3288
3597
|
const body = buildIssueBody(finding, goalId, manifestNumber);
|
|
3289
3598
|
const args = ["issue", "create", "--title", title, "--body-file", "-"];
|
|
@@ -3342,7 +3651,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
|
|
|
3342
3651
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
3343
3652
|
return;
|
|
3344
3653
|
}
|
|
3345
|
-
|
|
3654
|
+
ensureLabel2(FINDING_LABEL, "ededed", "kody: QA finding", ctx.cwd);
|
|
3346
3655
|
const scope2 = ctx.args.scope;
|
|
3347
3656
|
const title = `QA [${verdict}]: ${scope2?.trim() || "smoke"} \u2014 ${todayIso()}`.slice(0, 240);
|
|
3348
3657
|
let url = "";
|
|
@@ -3472,14 +3781,57 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
|
3472
3781
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
3473
3782
|
};
|
|
3474
3783
|
|
|
3784
|
+
// src/goal/phase.ts
|
|
3785
|
+
function derivePhase(snap) {
|
|
3786
|
+
if (snap.lifecycleState === void 0) return "missing";
|
|
3787
|
+
if (snap.lifecycleState === "abandoned") return "abandoned";
|
|
3788
|
+
if (snap.lifecycleState === "closed" || snap.lifecycleState === "done") return "terminal";
|
|
3789
|
+
if (snap.childTasks.length === 0) return "no-tasks";
|
|
3790
|
+
const allClosed = snap.childTasks.every((t) => t.state === "CLOSED");
|
|
3791
|
+
if (allClosed) return "all-done";
|
|
3792
|
+
const anyFailed = snap.childTasks.some((t) => t.labels.includes(FAILED_LABEL));
|
|
3793
|
+
if (anyFailed) return "blocked-by-failure";
|
|
3794
|
+
const inFlight = snap.childTasks.some((t) => t.state === "OPEN" && t.labels.includes(DISPATCHED_LABEL));
|
|
3795
|
+
if (inFlight) return "in-flight";
|
|
3796
|
+
const dispatchable = snap.childTasks.some((t) => t.state === "OPEN" && !t.labels.includes(DISPATCHED_LABEL));
|
|
3797
|
+
if (dispatchable) return "ready-to-dispatch";
|
|
3798
|
+
return "idle";
|
|
3799
|
+
}
|
|
3800
|
+
function pickNextDispatchable(snap) {
|
|
3801
|
+
const candidates = snap.childTasks.filter((t) => t.state === "OPEN" && !t.labels.includes(DISPATCHED_LABEL)).sort((a, b) => a.number - b.number);
|
|
3802
|
+
return candidates[0];
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
// src/scripts/deriveGoalPhase.ts
|
|
3806
|
+
var deriveGoalPhase = async (ctx) => {
|
|
3807
|
+
const goal = ctx.data.goal;
|
|
3808
|
+
if (!goal) return;
|
|
3809
|
+
const issues = listGoalIssues(goal.id, goal.goalIssueNumber, ctx.cwd);
|
|
3810
|
+
if (!issues.ok) {
|
|
3811
|
+
process.stderr.write(`[goal-tick] deriveGoalPhase: list failed: ${issues.error}
|
|
3812
|
+
`);
|
|
3813
|
+
goal.childTasks = [];
|
|
3814
|
+
goal.phase = "idle";
|
|
3815
|
+
return;
|
|
3816
|
+
}
|
|
3817
|
+
const childTasks = issues.value ?? [];
|
|
3818
|
+
goal.childTasks = childTasks;
|
|
3819
|
+
goal.phase = derivePhase({
|
|
3820
|
+
lifecycleState: goal.state,
|
|
3821
|
+
childTasks
|
|
3822
|
+
});
|
|
3823
|
+
process.stdout.write(`[goal-tick] phase=${goal.phase} goal=${goal.id} tasks=${childTasks.length}
|
|
3824
|
+
`);
|
|
3825
|
+
};
|
|
3826
|
+
|
|
3475
3827
|
// src/scripts/diagMcp.ts
|
|
3476
|
-
import { execFileSync as
|
|
3828
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
3477
3829
|
import * as fs15 from "fs";
|
|
3478
3830
|
import * as os3 from "os";
|
|
3479
|
-
import * as
|
|
3831
|
+
import * as path15 from "path";
|
|
3480
3832
|
var diagMcp = async (_ctx) => {
|
|
3481
3833
|
const home = os3.homedir();
|
|
3482
|
-
const cacheDir =
|
|
3834
|
+
const cacheDir = path15.join(home, ".cache", "ms-playwright");
|
|
3483
3835
|
let entries = [];
|
|
3484
3836
|
try {
|
|
3485
3837
|
entries = fs15.readdirSync(cacheDir);
|
|
@@ -3493,7 +3845,7 @@ var diagMcp = async (_ctx) => {
|
|
|
3493
3845
|
process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
|
|
3494
3846
|
`);
|
|
3495
3847
|
try {
|
|
3496
|
-
const v =
|
|
3848
|
+
const v = execFileSync12("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
|
|
3497
3849
|
stdio: "pipe",
|
|
3498
3850
|
timeout: 6e4,
|
|
3499
3851
|
encoding: "utf8"
|
|
@@ -3509,16 +3861,16 @@ var diagMcp = async (_ctx) => {
|
|
|
3509
3861
|
|
|
3510
3862
|
// src/scripts/discoverQaContext.ts
|
|
3511
3863
|
import * as fs17 from "fs";
|
|
3512
|
-
import * as
|
|
3864
|
+
import * as path17 from "path";
|
|
3513
3865
|
|
|
3514
3866
|
// src/scripts/frameworkDetectors.ts
|
|
3515
3867
|
import * as fs16 from "fs";
|
|
3516
|
-
import * as
|
|
3868
|
+
import * as path16 from "path";
|
|
3517
3869
|
function detectFrameworks(cwd) {
|
|
3518
3870
|
const out = [];
|
|
3519
3871
|
let deps = {};
|
|
3520
3872
|
try {
|
|
3521
|
-
const pkg = JSON.parse(fs16.readFileSync(
|
|
3873
|
+
const pkg = JSON.parse(fs16.readFileSync(path16.join(cwd, "package.json"), "utf-8"));
|
|
3522
3874
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3523
3875
|
} catch {
|
|
3524
3876
|
return out;
|
|
@@ -3555,7 +3907,7 @@ function detectFrameworks(cwd) {
|
|
|
3555
3907
|
}
|
|
3556
3908
|
function findFile(cwd, candidates) {
|
|
3557
3909
|
for (const c of candidates) {
|
|
3558
|
-
if (fs16.existsSync(
|
|
3910
|
+
if (fs16.existsSync(path16.join(cwd, c))) return c;
|
|
3559
3911
|
}
|
|
3560
3912
|
return null;
|
|
3561
3913
|
}
|
|
@@ -3568,7 +3920,7 @@ var COLLECTION_DIRS = [
|
|
|
3568
3920
|
function discoverPayloadCollections(cwd) {
|
|
3569
3921
|
const out = [];
|
|
3570
3922
|
for (const dir of COLLECTION_DIRS) {
|
|
3571
|
-
const full =
|
|
3923
|
+
const full = path16.join(cwd, dir);
|
|
3572
3924
|
if (!fs16.existsSync(full)) continue;
|
|
3573
3925
|
let files;
|
|
3574
3926
|
try {
|
|
@@ -3578,7 +3930,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
3578
3930
|
}
|
|
3579
3931
|
for (const file of files) {
|
|
3580
3932
|
try {
|
|
3581
|
-
const filePath =
|
|
3933
|
+
const filePath = path16.join(full, file);
|
|
3582
3934
|
const content = fs16.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
3583
3935
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
3584
3936
|
if (!slugMatch) continue;
|
|
@@ -3593,7 +3945,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
3593
3945
|
out.push({
|
|
3594
3946
|
name,
|
|
3595
3947
|
slug,
|
|
3596
|
-
filePath:
|
|
3948
|
+
filePath: path16.relative(cwd, filePath),
|
|
3597
3949
|
fields: fields.slice(0, 20),
|
|
3598
3950
|
hasAdmin
|
|
3599
3951
|
});
|
|
@@ -3607,7 +3959,7 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
3607
3959
|
function discoverAdminComponents(cwd, collections) {
|
|
3608
3960
|
const out = [];
|
|
3609
3961
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
3610
|
-
const full =
|
|
3962
|
+
const full = path16.join(cwd, dir);
|
|
3611
3963
|
if (!fs16.existsSync(full)) continue;
|
|
3612
3964
|
let entries;
|
|
3613
3965
|
try {
|
|
@@ -3616,19 +3968,19 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
3616
3968
|
continue;
|
|
3617
3969
|
}
|
|
3618
3970
|
for (const entry of entries) {
|
|
3619
|
-
const entryPath =
|
|
3971
|
+
const entryPath = path16.join(full, entry.name);
|
|
3620
3972
|
let name;
|
|
3621
3973
|
let filePath;
|
|
3622
3974
|
if (entry.isDirectory()) {
|
|
3623
3975
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
3624
|
-
(f) => fs16.existsSync(
|
|
3976
|
+
(f) => fs16.existsSync(path16.join(entryPath, f))
|
|
3625
3977
|
);
|
|
3626
3978
|
if (!indexFile) continue;
|
|
3627
3979
|
name = entry.name;
|
|
3628
|
-
filePath =
|
|
3980
|
+
filePath = path16.relative(cwd, path16.join(entryPath, indexFile));
|
|
3629
3981
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
3630
3982
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
3631
|
-
filePath =
|
|
3983
|
+
filePath = path16.relative(cwd, entryPath);
|
|
3632
3984
|
} else {
|
|
3633
3985
|
continue;
|
|
3634
3986
|
}
|
|
@@ -3636,7 +3988,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
3636
3988
|
if (collections) {
|
|
3637
3989
|
for (const col of collections) {
|
|
3638
3990
|
try {
|
|
3639
|
-
const colContent = fs16.readFileSync(
|
|
3991
|
+
const colContent = fs16.readFileSync(path16.join(cwd, col.filePath), "utf-8");
|
|
3640
3992
|
if (colContent.includes(name)) {
|
|
3641
3993
|
usedInCollection = col.slug;
|
|
3642
3994
|
break;
|
|
@@ -3655,7 +4007,7 @@ function scanApiRoutes(cwd) {
|
|
|
3655
4007
|
const out = [];
|
|
3656
4008
|
const appDirs = ["src/app", "app"];
|
|
3657
4009
|
for (const appDir of appDirs) {
|
|
3658
|
-
const apiDir =
|
|
4010
|
+
const apiDir = path16.join(cwd, appDir, "api");
|
|
3659
4011
|
if (!fs16.existsSync(apiDir)) continue;
|
|
3660
4012
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
3661
4013
|
break;
|
|
@@ -3672,7 +4024,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3672
4024
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
3673
4025
|
if (routeFile) {
|
|
3674
4026
|
try {
|
|
3675
|
-
const content = fs16.readFileSync(
|
|
4027
|
+
const content = fs16.readFileSync(path16.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
3676
4028
|
const methods = HTTP_METHODS.filter(
|
|
3677
4029
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
3678
4030
|
);
|
|
@@ -3680,7 +4032,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3680
4032
|
out.push({
|
|
3681
4033
|
path: prefix,
|
|
3682
4034
|
methods,
|
|
3683
|
-
filePath:
|
|
4035
|
+
filePath: path16.relative(cwd, path16.join(dir, routeFile.name))
|
|
3684
4036
|
});
|
|
3685
4037
|
}
|
|
3686
4038
|
} catch {
|
|
@@ -3691,7 +4043,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3691
4043
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
3692
4044
|
let segment = entry.name;
|
|
3693
4045
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
3694
|
-
walkApiRoutes(
|
|
4046
|
+
walkApiRoutes(path16.join(dir, entry.name), prefix, cwd, out);
|
|
3695
4047
|
continue;
|
|
3696
4048
|
}
|
|
3697
4049
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -3699,7 +4051,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
3699
4051
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
3700
4052
|
segment = `:${segment.slice(1, -1)}`;
|
|
3701
4053
|
}
|
|
3702
|
-
walkApiRoutes(
|
|
4054
|
+
walkApiRoutes(path16.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
3703
4055
|
}
|
|
3704
4056
|
}
|
|
3705
4057
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -3719,7 +4071,7 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
3719
4071
|
function scanEnvVars(cwd) {
|
|
3720
4072
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
3721
4073
|
for (const envFile of candidates) {
|
|
3722
|
-
const envPath =
|
|
4074
|
+
const envPath = path16.join(cwd, envFile);
|
|
3723
4075
|
if (!fs16.existsSync(envPath)) continue;
|
|
3724
4076
|
try {
|
|
3725
4077
|
const content = fs16.readFileSync(envPath, "utf-8");
|
|
@@ -3770,9 +4122,9 @@ function runQaDiscovery(cwd) {
|
|
|
3770
4122
|
}
|
|
3771
4123
|
function detectDevServer(cwd, out) {
|
|
3772
4124
|
try {
|
|
3773
|
-
const pkg = JSON.parse(fs17.readFileSync(
|
|
4125
|
+
const pkg = JSON.parse(fs17.readFileSync(path17.join(cwd, "package.json"), "utf-8"));
|
|
3774
4126
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3775
|
-
const pm = fs17.existsSync(
|
|
4127
|
+
const pm = fs17.existsSync(path17.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs17.existsSync(path17.join(cwd, "yarn.lock")) ? "yarn" : fs17.existsSync(path17.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
3776
4128
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
3777
4129
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
3778
4130
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -3782,7 +4134,7 @@ function detectDevServer(cwd, out) {
|
|
|
3782
4134
|
function scanFrontendRoutes(cwd, out) {
|
|
3783
4135
|
const appDirs = ["src/app", "app"];
|
|
3784
4136
|
for (const appDir of appDirs) {
|
|
3785
|
-
const full =
|
|
4137
|
+
const full = path17.join(cwd, appDir);
|
|
3786
4138
|
if (!fs17.existsSync(full)) continue;
|
|
3787
4139
|
walkFrontendRoutes(full, "", out);
|
|
3788
4140
|
break;
|
|
@@ -3808,7 +4160,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
3808
4160
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
3809
4161
|
let segment = entry.name;
|
|
3810
4162
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
3811
|
-
walkFrontendRoutes(
|
|
4163
|
+
walkFrontendRoutes(path17.join(dir, entry.name), prefix, out);
|
|
3812
4164
|
continue;
|
|
3813
4165
|
}
|
|
3814
4166
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -3816,7 +4168,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
3816
4168
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
3817
4169
|
segment = `:${segment.slice(1, -1)}`;
|
|
3818
4170
|
}
|
|
3819
|
-
walkFrontendRoutes(
|
|
4171
|
+
walkFrontendRoutes(path17.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
3820
4172
|
}
|
|
3821
4173
|
}
|
|
3822
4174
|
function detectAuthFiles(cwd, out) {
|
|
@@ -3833,13 +4185,13 @@ function detectAuthFiles(cwd, out) {
|
|
|
3833
4185
|
"src/app/api/oauth"
|
|
3834
4186
|
];
|
|
3835
4187
|
for (const c of candidates) {
|
|
3836
|
-
if (fs17.existsSync(
|
|
4188
|
+
if (fs17.existsSync(path17.join(cwd, c))) out.authFiles.push(c);
|
|
3837
4189
|
}
|
|
3838
4190
|
}
|
|
3839
4191
|
function detectRoles(cwd, out) {
|
|
3840
4192
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
3841
4193
|
for (const rp of rolePaths) {
|
|
3842
|
-
const dir =
|
|
4194
|
+
const dir = path17.join(cwd, rp);
|
|
3843
4195
|
if (!fs17.existsSync(dir)) continue;
|
|
3844
4196
|
let files;
|
|
3845
4197
|
try {
|
|
@@ -3849,7 +4201,7 @@ function detectRoles(cwd, out) {
|
|
|
3849
4201
|
}
|
|
3850
4202
|
for (const f of files) {
|
|
3851
4203
|
try {
|
|
3852
|
-
const content = fs17.readFileSync(
|
|
4204
|
+
const content = fs17.readFileSync(path17.join(dir, f), "utf-8").slice(0, 5e3);
|
|
3853
4205
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
3854
4206
|
if (roleMatches) {
|
|
3855
4207
|
for (const m of roleMatches) {
|
|
@@ -3995,7 +4347,7 @@ var discoverQaContext = async (ctx) => {
|
|
|
3995
4347
|
};
|
|
3996
4348
|
|
|
3997
4349
|
// src/scripts/dispatch.ts
|
|
3998
|
-
import { execFileSync as
|
|
4350
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
3999
4351
|
var API_TIMEOUT_MS4 = 3e4;
|
|
4000
4352
|
var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
4001
4353
|
const next = args?.next;
|
|
@@ -4031,7 +4383,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
|
|
|
4031
4383
|
const sub = usePr ? "pr" : "issue";
|
|
4032
4384
|
const body = `@kody ${next}`;
|
|
4033
4385
|
try {
|
|
4034
|
-
|
|
4386
|
+
execFileSync13("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
4035
4387
|
timeout: API_TIMEOUT_MS4,
|
|
4036
4388
|
cwd: ctx.cwd,
|
|
4037
4389
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4051,7 +4403,7 @@ function parsePr(url) {
|
|
|
4051
4403
|
}
|
|
4052
4404
|
|
|
4053
4405
|
// src/scripts/dispatchClassified.ts
|
|
4054
|
-
import { execFileSync as
|
|
4406
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
4055
4407
|
var API_TIMEOUT_MS5 = 3e4;
|
|
4056
4408
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
4057
4409
|
var dispatchClassified = async (ctx) => {
|
|
@@ -4060,7 +4412,7 @@ var dispatchClassified = async (ctx) => {
|
|
|
4060
4412
|
const classification = ctx.data.classification;
|
|
4061
4413
|
if (!classification || !VALID_CLASSES2.has(classification)) return;
|
|
4062
4414
|
try {
|
|
4063
|
-
|
|
4415
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
4064
4416
|
cwd: ctx.cwd,
|
|
4065
4417
|
timeout: API_TIMEOUT_MS5,
|
|
4066
4418
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4081,7 +4433,7 @@ function failedAction3(reason) {
|
|
|
4081
4433
|
|
|
4082
4434
|
// src/scripts/dispatchJobFileTicks.ts
|
|
4083
4435
|
import * as fs19 from "fs";
|
|
4084
|
-
import * as
|
|
4436
|
+
import * as path19 from "path";
|
|
4085
4437
|
|
|
4086
4438
|
// src/scripts/jobFrontmatter.ts
|
|
4087
4439
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -4333,7 +4685,7 @@ var ContentsApiBackend = class {
|
|
|
4333
4685
|
|
|
4334
4686
|
// src/scripts/jobState/localFileBackend.ts
|
|
4335
4687
|
import * as fs18 from "fs";
|
|
4336
|
-
import * as
|
|
4688
|
+
import * as path18 from "path";
|
|
4337
4689
|
var LocalFileBackend = class {
|
|
4338
4690
|
name = "local-file";
|
|
4339
4691
|
cwd;
|
|
@@ -4348,7 +4700,7 @@ var LocalFileBackend = class {
|
|
|
4348
4700
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
4349
4701
|
this.cwd = opts.cwd;
|
|
4350
4702
|
this.jobsDir = opts.jobsDir;
|
|
4351
|
-
this.absDir =
|
|
4703
|
+
this.absDir = path18.join(opts.cwd, opts.jobsDir);
|
|
4352
4704
|
this.owner = opts.owner;
|
|
4353
4705
|
this.repo = opts.repo;
|
|
4354
4706
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -4408,7 +4760,7 @@ var LocalFileBackend = class {
|
|
|
4408
4760
|
}
|
|
4409
4761
|
load(slug) {
|
|
4410
4762
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
4411
|
-
const absPath =
|
|
4763
|
+
const absPath = path18.join(this.cwd, relPath);
|
|
4412
4764
|
if (!fs18.existsSync(absPath)) {
|
|
4413
4765
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
4414
4766
|
}
|
|
@@ -4429,8 +4781,8 @@ var LocalFileBackend = class {
|
|
|
4429
4781
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
4430
4782
|
return false;
|
|
4431
4783
|
}
|
|
4432
|
-
const absPath =
|
|
4433
|
-
fs18.mkdirSync(
|
|
4784
|
+
const absPath = path18.join(this.cwd, loaded.path);
|
|
4785
|
+
fs18.mkdirSync(path18.dirname(absPath), { recursive: true });
|
|
4434
4786
|
const body = JSON.stringify(next, null, 2) + "\n";
|
|
4435
4787
|
fs18.writeFileSync(absPath, body, "utf-8");
|
|
4436
4788
|
return true;
|
|
@@ -4509,7 +4861,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
4509
4861
|
await backend.hydrate();
|
|
4510
4862
|
}
|
|
4511
4863
|
try {
|
|
4512
|
-
const slugs = listJobSlugs(
|
|
4864
|
+
const slugs = listJobSlugs(path19.join(ctx.cwd, jobsDir));
|
|
4513
4865
|
ctx.data.jobSlugCount = slugs.length;
|
|
4514
4866
|
if (slugs.length === 0) {
|
|
4515
4867
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -4608,7 +4960,7 @@ function formatAgo(ms) {
|
|
|
4608
4960
|
}
|
|
4609
4961
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
4610
4962
|
try {
|
|
4611
|
-
const raw = fs19.readFileSync(
|
|
4963
|
+
const raw = fs19.readFileSync(path19.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
4612
4964
|
return splitFrontmatter(raw).frontmatter;
|
|
4613
4965
|
} catch {
|
|
4614
4966
|
return {};
|
|
@@ -4688,6 +5040,134 @@ function listIssuesByLabel(label, cwd) {
|
|
|
4688
5040
|
return list.filter((x) => typeof x.number === "number" && typeof x.title === "string").map((x) => ({ number: x.number, title: x.title }));
|
|
4689
5041
|
}
|
|
4690
5042
|
|
|
5043
|
+
// src/scripts/dispatchNextTask.ts
|
|
5044
|
+
var dispatchNextTask = async (ctx) => {
|
|
5045
|
+
const goal = ctx.data.goal;
|
|
5046
|
+
if (!goal?.childTasks) return;
|
|
5047
|
+
const next = pickNextDispatchable({
|
|
5048
|
+
lifecycleState: goal.state,
|
|
5049
|
+
childTasks: goal.childTasks
|
|
5050
|
+
});
|
|
5051
|
+
if (!next) {
|
|
5052
|
+
process.stdout.write("[goal-tick] no undispatched open task \u2014 idle\n");
|
|
5053
|
+
return;
|
|
5054
|
+
}
|
|
5055
|
+
process.stdout.write(`[goal-tick] dispatching @kody on task #${next.number} (--base ${goal.goalBranch})
|
|
5056
|
+
`);
|
|
5057
|
+
const comment = commentOnIssue(next.number, `@kody --base ${goal.goalBranch}`, ctx.cwd);
|
|
5058
|
+
if (!comment.ok) {
|
|
5059
|
+
process.stderr.write(`[goal-tick] dispatchNextTask: comment failed on #${next.number}: ${comment.error}
|
|
5060
|
+
`);
|
|
5061
|
+
return;
|
|
5062
|
+
}
|
|
5063
|
+
const label = addLabel2(next.number, DISPATCHED_LABEL, ctx.cwd);
|
|
5064
|
+
if (!label.ok) {
|
|
5065
|
+
process.stderr.write(
|
|
5066
|
+
`[goal-tick] dispatchNextTask: add-label failed on #${next.number}: ${label.error} (continuing \u2014 comment already posted)
|
|
5067
|
+
`
|
|
5068
|
+
);
|
|
5069
|
+
}
|
|
5070
|
+
goal.lastDispatchedIssue = next.number;
|
|
5071
|
+
};
|
|
5072
|
+
|
|
5073
|
+
// src/scripts/ensureGoalBranch.ts
|
|
5074
|
+
var ensureGoalBranch = async (ctx) => {
|
|
5075
|
+
const goal = ctx.data.goal;
|
|
5076
|
+
if (!goal) return;
|
|
5077
|
+
fetchOrigin(ctx.cwd);
|
|
5078
|
+
if (remoteBranchExists(goal.goalBranch, ctx.cwd)) {
|
|
5079
|
+
process.stdout.write(`[goal-tick] origin/${goal.goalBranch} already exists \u2014 leaving as-is
|
|
5080
|
+
`);
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
if (!remoteBranchExists(goal.defaultBranch, ctx.cwd)) {
|
|
5084
|
+
process.stderr.write(`[goal-tick] cannot create goal branch: origin/${goal.defaultBranch} missing
|
|
5085
|
+
`);
|
|
5086
|
+
return;
|
|
5087
|
+
}
|
|
5088
|
+
process.stdout.write(`[goal-tick] creating origin/${goal.goalBranch} from origin/${goal.defaultBranch}
|
|
5089
|
+
`);
|
|
5090
|
+
const r = createBranchFrom(goal.goalBranch, goal.defaultBranch, ctx.cwd);
|
|
5091
|
+
if (!r.ok) {
|
|
5092
|
+
process.stderr.write(
|
|
5093
|
+
`[goal-tick] push of ${goal.goalBranch} failed: ${r.error} \u2014 task dispatch will fall back to defaultBranch
|
|
5094
|
+
`
|
|
5095
|
+
);
|
|
5096
|
+
}
|
|
5097
|
+
};
|
|
5098
|
+
|
|
5099
|
+
// src/scripts/ensureGoalPr.ts
|
|
5100
|
+
var ensureGoalPr = async (ctx) => {
|
|
5101
|
+
const goal = ctx.data.goal;
|
|
5102
|
+
if (!goal) return;
|
|
5103
|
+
if (goal.goalPrUrl) return;
|
|
5104
|
+
if (!remoteBranchExists(goal.goalBranch, ctx.cwd)) return;
|
|
5105
|
+
const existing = listPrsByHead(goal.goalBranch, "open", ctx.cwd);
|
|
5106
|
+
if (existing.ok && existing.value && existing.value.length > 0) {
|
|
5107
|
+
goal.goalPrUrl = existing.value[0].url;
|
|
5108
|
+
return;
|
|
5109
|
+
}
|
|
5110
|
+
const title = `goal: ${goal.id}`;
|
|
5111
|
+
const body = goal.goalIssueNumber ? `Tracking integration PR for goal **${goal.id}**.
|
|
5112
|
+
|
|
5113
|
+
Child task PRs merge into \`${goal.goalBranch}\`. This PR is held in **draft** until every task is complete, then promoted to ready-for-review by goal-tick.
|
|
5114
|
+
|
|
5115
|
+
Closes #${goal.goalIssueNumber}
|
|
5116
|
+
` : `Tracking integration PR for goal **${goal.id}**.
|
|
5117
|
+
|
|
5118
|
+
Child task PRs merge into \`${goal.goalBranch}\`. Held in **draft** until every task is complete.
|
|
5119
|
+
`;
|
|
5120
|
+
const created = createPr(
|
|
5121
|
+
{
|
|
5122
|
+
head: goal.goalBranch,
|
|
5123
|
+
base: goal.defaultBranch,
|
|
5124
|
+
title,
|
|
5125
|
+
body,
|
|
5126
|
+
draft: true
|
|
5127
|
+
},
|
|
5128
|
+
ctx.cwd
|
|
5129
|
+
);
|
|
5130
|
+
if (!created.ok) {
|
|
5131
|
+
process.stderr.write(
|
|
5132
|
+
`[goal-tick] ensureGoalPr: gh pr create failed: ${created.error} (continuing without goal PR)
|
|
5133
|
+
`
|
|
5134
|
+
);
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
process.stdout.write(`[goal-tick] opened draft goal PR ${created.value} for ${goal.id}
|
|
5138
|
+
`);
|
|
5139
|
+
goal.goalPrUrl = created.value;
|
|
5140
|
+
};
|
|
5141
|
+
|
|
5142
|
+
// src/scripts/ensureLifecycleLabels.ts
|
|
5143
|
+
var ensureLifecycleLabels = async (ctx) => {
|
|
5144
|
+
const goal = ctx.data.goal;
|
|
5145
|
+
if (!goal) return;
|
|
5146
|
+
for (const spec of TICK_LABELS) {
|
|
5147
|
+
const r2 = ensureLabel(spec.name, spec.color, spec.description, ctx.cwd);
|
|
5148
|
+
if (!r2.ok) {
|
|
5149
|
+
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${spec.name}: ${r2.error}
|
|
5150
|
+
`);
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
const goalLbl = goalLabel(goal.id);
|
|
5154
|
+
const r = ensureLabel(goalLbl, "0e8a16", `kody goal task: belongs to goal ${goal.id}`, ctx.cwd);
|
|
5155
|
+
if (!r.ok) {
|
|
5156
|
+
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${goalLbl}: ${r.error}
|
|
5157
|
+
`);
|
|
5158
|
+
}
|
|
5159
|
+
const u = ensureLabel(
|
|
5160
|
+
UMBRELLA_BUILDING_LABEL,
|
|
5161
|
+
"1d76db",
|
|
5162
|
+
"kody: in-flight (work being assembled on a branch)",
|
|
5163
|
+
ctx.cwd
|
|
5164
|
+
);
|
|
5165
|
+
if (!u.ok) {
|
|
5166
|
+
process.stderr.write(`[goal-tick] ensureLifecycleLabels: ${UMBRELLA_BUILDING_LABEL}: ${u.error}
|
|
5167
|
+
`);
|
|
5168
|
+
}
|
|
5169
|
+
};
|
|
5170
|
+
|
|
4691
5171
|
// src/pr.ts
|
|
4692
5172
|
var TITLE_MAX = 72;
|
|
4693
5173
|
function stripTitlePrefixes(raw) {
|
|
@@ -4901,8 +5381,203 @@ function collectExpectedTests(raw) {
|
|
|
4901
5381
|
return out;
|
|
4902
5382
|
}
|
|
4903
5383
|
|
|
5384
|
+
// src/scripts/ensureUmbrellaIssue.ts
|
|
5385
|
+
var ensureUmbrellaIssue = async (ctx) => {
|
|
5386
|
+
const goal = ctx.data.goal;
|
|
5387
|
+
if (!goal) return;
|
|
5388
|
+
if (goal.goalIssueNumber !== void 0) return;
|
|
5389
|
+
const title = `goal: ${goal.id}`;
|
|
5390
|
+
const body = `Umbrella issue for goal **${goal.id}**.
|
|
5391
|
+
|
|
5392
|
+
Closed automatically when the goal PR (\`${goal.goalBranch}\` \u2192 \`${goal.defaultBranch}\`) merges.
|
|
5393
|
+
`;
|
|
5394
|
+
const existing = findUmbrellaByTitle(goal.id, title, ctx.cwd);
|
|
5395
|
+
if (existing.ok && existing.value !== null && existing.value !== void 0) {
|
|
5396
|
+
process.stdout.write(`[goal-tick] adopted existing umbrella issue #${existing.value} for ${goal.id}
|
|
5397
|
+
`);
|
|
5398
|
+
goal.goalIssueNumber = existing.value;
|
|
5399
|
+
return;
|
|
5400
|
+
}
|
|
5401
|
+
const created = createIssue(
|
|
5402
|
+
{
|
|
5403
|
+
title,
|
|
5404
|
+
body,
|
|
5405
|
+
labels: [goalLabel(goal.id), UMBRELLA_BUILDING_LABEL]
|
|
5406
|
+
},
|
|
5407
|
+
ctx.cwd
|
|
5408
|
+
);
|
|
5409
|
+
if (!created.ok) {
|
|
5410
|
+
process.stderr.write(
|
|
5411
|
+
`[goal-tick] ensureUmbrellaIssue: gh issue create failed: ${created.error} \u2014 continuing without umbrella issue
|
|
5412
|
+
`
|
|
5413
|
+
);
|
|
5414
|
+
return;
|
|
5415
|
+
}
|
|
5416
|
+
process.stdout.write(`[goal-tick] opened umbrella issue #${created.value} for ${goal.id}
|
|
5417
|
+
`);
|
|
5418
|
+
goal.goalIssueNumber = created.value;
|
|
5419
|
+
};
|
|
5420
|
+
|
|
5421
|
+
// src/goal/state.ts
|
|
5422
|
+
import * as fs20 from "fs";
|
|
5423
|
+
import * as path20 from "path";
|
|
5424
|
+
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
5425
|
+
var GoalStateError = class extends Error {
|
|
5426
|
+
constructor(path29, message) {
|
|
5427
|
+
super(`Invalid goal state at ${path29}:
|
|
5428
|
+
${message}`);
|
|
5429
|
+
this.path = path29;
|
|
5430
|
+
this.name = "GoalStateError";
|
|
5431
|
+
}
|
|
5432
|
+
path;
|
|
5433
|
+
};
|
|
5434
|
+
function parseGoalState(filePath, raw) {
|
|
5435
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5436
|
+
throw new GoalStateError(filePath, "must be a JSON object");
|
|
5437
|
+
}
|
|
5438
|
+
const r = raw;
|
|
5439
|
+
const stateValue = r.state;
|
|
5440
|
+
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5441
|
+
throw new GoalStateError(
|
|
5442
|
+
filePath,
|
|
5443
|
+
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5444
|
+
);
|
|
5445
|
+
}
|
|
5446
|
+
const parsed = {
|
|
5447
|
+
state: stateValue,
|
|
5448
|
+
extra: {}
|
|
5449
|
+
};
|
|
5450
|
+
if (typeof r.goalIssueNumber === "number" && Number.isFinite(r.goalIssueNumber)) {
|
|
5451
|
+
parsed.goalIssueNumber = r.goalIssueNumber;
|
|
5452
|
+
}
|
|
5453
|
+
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
5454
|
+
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
5455
|
+
}
|
|
5456
|
+
if (typeof r.goalPrUrl === "string" && r.goalPrUrl.length > 0) {
|
|
5457
|
+
parsed.goalPrUrl = r.goalPrUrl;
|
|
5458
|
+
}
|
|
5459
|
+
for (const ts of ["updatedAt", "createdAt", "startedAt", "completedAt"]) {
|
|
5460
|
+
const v = r[ts];
|
|
5461
|
+
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5462
|
+
}
|
|
5463
|
+
const known = /* @__PURE__ */ new Set([
|
|
5464
|
+
"state",
|
|
5465
|
+
"goalIssueNumber",
|
|
5466
|
+
"lastDispatchedIssue",
|
|
5467
|
+
"goalPrUrl",
|
|
5468
|
+
"updatedAt",
|
|
5469
|
+
"createdAt",
|
|
5470
|
+
"startedAt",
|
|
5471
|
+
"completedAt"
|
|
5472
|
+
]);
|
|
5473
|
+
for (const [k, v] of Object.entries(r)) {
|
|
5474
|
+
if (!known.has(k)) parsed.extra[k] = v;
|
|
5475
|
+
}
|
|
5476
|
+
return parsed;
|
|
5477
|
+
}
|
|
5478
|
+
function serializeGoalState(s) {
|
|
5479
|
+
const obj = { ...s.extra, state: s.state };
|
|
5480
|
+
if (s.goalIssueNumber !== void 0) obj.goalIssueNumber = s.goalIssueNumber;
|
|
5481
|
+
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
5482
|
+
if (s.goalPrUrl !== void 0) obj.goalPrUrl = s.goalPrUrl;
|
|
5483
|
+
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5484
|
+
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5485
|
+
if (s.completedAt !== void 0) obj.completedAt = s.completedAt;
|
|
5486
|
+
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5487
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
5488
|
+
`;
|
|
5489
|
+
}
|
|
5490
|
+
function goalStatePath(cwd, goalId) {
|
|
5491
|
+
return path20.join(cwd, ".kody", "goals", goalId, "state.json");
|
|
5492
|
+
}
|
|
5493
|
+
function readGoalState(cwd, goalId) {
|
|
5494
|
+
const file = goalStatePath(cwd, goalId);
|
|
5495
|
+
if (!fs20.existsSync(file)) {
|
|
5496
|
+
throw new GoalStateError(file, "file not found");
|
|
5497
|
+
}
|
|
5498
|
+
let raw;
|
|
5499
|
+
try {
|
|
5500
|
+
raw = JSON.parse(fs20.readFileSync(file, "utf-8"));
|
|
5501
|
+
} catch (err) {
|
|
5502
|
+
throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
5503
|
+
}
|
|
5504
|
+
return parseGoalState(file, raw);
|
|
5505
|
+
}
|
|
5506
|
+
function writeGoalState(cwd, goalId, state) {
|
|
5507
|
+
const file = goalStatePath(cwd, goalId);
|
|
5508
|
+
fs20.mkdirSync(path20.dirname(file), { recursive: true });
|
|
5509
|
+
fs20.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
5510
|
+
}
|
|
5511
|
+
function nowIso() {
|
|
5512
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
// src/scripts/finalizeGoal.ts
|
|
5516
|
+
var finalizeGoal = async (ctx) => {
|
|
5517
|
+
const goal = ctx.data.goal;
|
|
5518
|
+
if (!goal) return;
|
|
5519
|
+
process.stdout.write(`[goal-tick] all task(s) closed \u2014 finalising goal ${goal.id}
|
|
5520
|
+
`);
|
|
5521
|
+
if (!remoteBranchExists(goal.goalBranch, ctx.cwd)) {
|
|
5522
|
+
process.stderr.write(`[goal-tick] goal branch ${goal.goalBranch} not found on origin \u2014 skipping final PR
|
|
5523
|
+
`);
|
|
5524
|
+
finishState(goal);
|
|
5525
|
+
return;
|
|
5526
|
+
}
|
|
5527
|
+
const title = `goal: ${goal.id}`;
|
|
5528
|
+
const closesLine = goal.goalIssueNumber ? `
|
|
5529
|
+
|
|
5530
|
+
Closes #${goal.goalIssueNumber}
|
|
5531
|
+
` : "\n";
|
|
5532
|
+
const body = `Final integration PR for goal **${goal.id}**.
|
|
5533
|
+
|
|
5534
|
+
All task issues are closed and merged into \`${goal.goalBranch}\`. Ready for review.${closesLine}`;
|
|
5535
|
+
const existing = listPrsByHead(goal.goalBranch, "open", ctx.cwd);
|
|
5536
|
+
if (existing.ok && existing.value && existing.value.length > 0) {
|
|
5537
|
+
const pr = existing.value[0];
|
|
5538
|
+
goal.goalPrUrl = pr.url;
|
|
5539
|
+
const edit = editPrBody(pr.number, body, ctx.cwd);
|
|
5540
|
+
if (!edit.ok) {
|
|
5541
|
+
process.stderr.write(`[goal-tick] finalizeGoal: editPrBody failed: ${edit.error}
|
|
5542
|
+
`);
|
|
5543
|
+
}
|
|
5544
|
+
if (pr.isDraft) {
|
|
5545
|
+
process.stdout.write(`[goal-tick] promoting draft goal PR #${pr.number} to ready-for-review
|
|
5546
|
+
`);
|
|
5547
|
+
const ready = markPrReady(pr.number, ctx.cwd);
|
|
5548
|
+
if (!ready.ok) {
|
|
5549
|
+
process.stderr.write(`[goal-tick] finalizeGoal: markPrReady failed: ${ready.error}
|
|
5550
|
+
`);
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
} else {
|
|
5554
|
+
const created = createPr(
|
|
5555
|
+
{
|
|
5556
|
+
head: goal.goalBranch,
|
|
5557
|
+
base: goal.defaultBranch,
|
|
5558
|
+
title,
|
|
5559
|
+
body,
|
|
5560
|
+
// ready-for-review (not draft) since we're finalizing.
|
|
5561
|
+
draft: false
|
|
5562
|
+
},
|
|
5563
|
+
ctx.cwd
|
|
5564
|
+
);
|
|
5565
|
+
if (!created.ok) {
|
|
5566
|
+
process.stderr.write(`[goal-tick] finalizeGoal: gh pr create failed: ${created.error}
|
|
5567
|
+
`);
|
|
5568
|
+
} else {
|
|
5569
|
+
goal.goalPrUrl = created.value;
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
finishState(goal);
|
|
5573
|
+
};
|
|
5574
|
+
function finishState(goal) {
|
|
5575
|
+
goal.state = "done";
|
|
5576
|
+
goal.completedAt = nowIso();
|
|
5577
|
+
}
|
|
5578
|
+
|
|
4904
5579
|
// src/scripts/finishFlow.ts
|
|
4905
|
-
import { execFileSync as
|
|
5580
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
4906
5581
|
var API_TIMEOUT_MS6 = 3e4;
|
|
4907
5582
|
var STATUS_ICON = {
|
|
4908
5583
|
"review-passed": "\u2705",
|
|
@@ -4936,7 +5611,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
4936
5611
|
**PR:** ${state.core.prUrl}` : "";
|
|
4937
5612
|
const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
4938
5613
|
try {
|
|
4939
|
-
|
|
5614
|
+
execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
4940
5615
|
timeout: API_TIMEOUT_MS6,
|
|
4941
5616
|
cwd: ctx.cwd,
|
|
4942
5617
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4950,7 +5625,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
4950
5625
|
};
|
|
4951
5626
|
|
|
4952
5627
|
// src/branch.ts
|
|
4953
|
-
import { execFileSync as
|
|
5628
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
4954
5629
|
var UncommittedChangesError = class extends Error {
|
|
4955
5630
|
constructor(branch) {
|
|
4956
5631
|
super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
|
|
@@ -4960,7 +5635,7 @@ var UncommittedChangesError = class extends Error {
|
|
|
4960
5635
|
branch;
|
|
4961
5636
|
};
|
|
4962
5637
|
function git2(args, cwd) {
|
|
4963
|
-
return
|
|
5638
|
+
return execFileSync16("git", args, {
|
|
4964
5639
|
encoding: "utf-8",
|
|
4965
5640
|
timeout: 3e4,
|
|
4966
5641
|
cwd,
|
|
@@ -4985,7 +5660,7 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
4985
5660
|
SKIP_HOOKS: "1",
|
|
4986
5661
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
4987
5662
|
};
|
|
4988
|
-
|
|
5663
|
+
execFileSync16("gh", ["pr", "checkout", String(prNumber)], {
|
|
4989
5664
|
cwd,
|
|
4990
5665
|
env,
|
|
4991
5666
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -5099,8 +5774,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
|
|
|
5099
5774
|
}
|
|
5100
5775
|
|
|
5101
5776
|
// src/gha.ts
|
|
5102
|
-
import { execFileSync as
|
|
5103
|
-
import * as
|
|
5777
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
5778
|
+
import * as fs21 from "fs";
|
|
5104
5779
|
function getRunUrl() {
|
|
5105
5780
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5106
5781
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5111,10 +5786,10 @@ function getRunUrl() {
|
|
|
5111
5786
|
function reactToTriggerComment(cwd) {
|
|
5112
5787
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5113
5788
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5114
|
-
if (!eventPath || !
|
|
5789
|
+
if (!eventPath || !fs21.existsSync(eventPath)) return;
|
|
5115
5790
|
let event = null;
|
|
5116
5791
|
try {
|
|
5117
|
-
event = JSON.parse(
|
|
5792
|
+
event = JSON.parse(fs21.readFileSync(eventPath, "utf-8"));
|
|
5118
5793
|
} catch {
|
|
5119
5794
|
return;
|
|
5120
5795
|
}
|
|
@@ -5142,7 +5817,7 @@ function reactToTriggerComment(cwd) {
|
|
|
5142
5817
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
5143
5818
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
5144
5819
|
try {
|
|
5145
|
-
|
|
5820
|
+
execFileSync17("gh", args, opts);
|
|
5146
5821
|
return;
|
|
5147
5822
|
} catch (err) {
|
|
5148
5823
|
lastErr = err;
|
|
@@ -5155,13 +5830,13 @@ function reactToTriggerComment(cwd) {
|
|
|
5155
5830
|
}
|
|
5156
5831
|
function sleepMs(ms) {
|
|
5157
5832
|
try {
|
|
5158
|
-
|
|
5833
|
+
execFileSync17("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
5159
5834
|
} catch {
|
|
5160
5835
|
}
|
|
5161
5836
|
}
|
|
5162
5837
|
|
|
5163
5838
|
// src/workflow.ts
|
|
5164
|
-
import { execFileSync as
|
|
5839
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
5165
5840
|
var GH_TIMEOUT_MS = 3e4;
|
|
5166
5841
|
function ghToken3() {
|
|
5167
5842
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -5169,7 +5844,7 @@ function ghToken3() {
|
|
|
5169
5844
|
function gh3(args, cwd) {
|
|
5170
5845
|
const token = ghToken3();
|
|
5171
5846
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
5172
|
-
return
|
|
5847
|
+
return execFileSync18("gh", args, {
|
|
5173
5848
|
encoding: "utf-8",
|
|
5174
5849
|
timeout: GH_TIMEOUT_MS,
|
|
5175
5850
|
cwd,
|
|
@@ -5352,24 +6027,63 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
5352
6027
|
}
|
|
5353
6028
|
}
|
|
5354
6029
|
|
|
6030
|
+
// src/scripts/handleAbandonedGoal.ts
|
|
6031
|
+
var handleAbandonedGoal = async (ctx) => {
|
|
6032
|
+
const goal = ctx.data.goal;
|
|
6033
|
+
if (!goal || goal.state !== "abandoned") return;
|
|
6034
|
+
process.stdout.write(`[goal-tick] ${goal.id} is abandoned \u2014 running cleanup
|
|
6035
|
+
`);
|
|
6036
|
+
const issues = listGoalIssues(goal.id, goal.goalIssueNumber, ctx.cwd);
|
|
6037
|
+
if (!issues.ok) {
|
|
6038
|
+
process.stderr.write(`[goal-tick] handleAbandonedGoal: list failed: ${issues.error}
|
|
6039
|
+
`);
|
|
6040
|
+
} else {
|
|
6041
|
+
for (const i of issues.value ?? []) {
|
|
6042
|
+
if (i.state !== "OPEN") continue;
|
|
6043
|
+
const r = closeIssue(
|
|
6044
|
+
i.number,
|
|
6045
|
+
{
|
|
6046
|
+
comment: "_Goal abandoned \u2014 closing this task without dispatch._",
|
|
6047
|
+
reason: "not planned"
|
|
6048
|
+
},
|
|
6049
|
+
ctx.cwd
|
|
6050
|
+
);
|
|
6051
|
+
if (!r.ok) {
|
|
6052
|
+
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close #${i.number}: ${r.error}
|
|
6053
|
+
`);
|
|
6054
|
+
}
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
const goalPrs = listPrsByHead(goal.goalBranch, "open", ctx.cwd);
|
|
6058
|
+
if (goalPrs.ok && goalPrs.value && goalPrs.value.length > 0) {
|
|
6059
|
+
const pr = goalPrs.value[0];
|
|
6060
|
+
const r = closePr(pr.number, "_Goal abandoned by operator \u2014 closing without merge._", ctx.cwd);
|
|
6061
|
+
if (!r.ok) {
|
|
6062
|
+
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close goal PR #${pr.number}: ${r.error}
|
|
6063
|
+
`);
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
goal.state = "closed";
|
|
6067
|
+
};
|
|
6068
|
+
|
|
5355
6069
|
// src/scripts/initFlow.ts
|
|
5356
|
-
import { execFileSync as
|
|
5357
|
-
import * as
|
|
5358
|
-
import * as
|
|
6070
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
6071
|
+
import * as fs23 from "fs";
|
|
6072
|
+
import * as path22 from "path";
|
|
5359
6073
|
|
|
5360
6074
|
// src/scripts/loadQaGuide.ts
|
|
5361
|
-
import * as
|
|
5362
|
-
import * as
|
|
6075
|
+
import * as fs22 from "fs";
|
|
6076
|
+
import * as path21 from "path";
|
|
5363
6077
|
var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
|
|
5364
6078
|
var loadQaGuide = async (ctx) => {
|
|
5365
|
-
const full =
|
|
5366
|
-
if (!
|
|
6079
|
+
const full = path21.join(ctx.cwd, QA_GUIDE_REL_PATH);
|
|
6080
|
+
if (!fs22.existsSync(full)) {
|
|
5367
6081
|
ctx.data.qaGuide = "";
|
|
5368
6082
|
ctx.data.qaGuidePath = "";
|
|
5369
6083
|
return;
|
|
5370
6084
|
}
|
|
5371
6085
|
try {
|
|
5372
|
-
ctx.data.qaGuide =
|
|
6086
|
+
ctx.data.qaGuide = fs22.readFileSync(full, "utf-8");
|
|
5373
6087
|
ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
|
|
5374
6088
|
} catch {
|
|
5375
6089
|
ctx.data.qaGuide = "";
|
|
@@ -5379,9 +6093,9 @@ var loadQaGuide = async (ctx) => {
|
|
|
5379
6093
|
|
|
5380
6094
|
// src/scripts/initFlow.ts
|
|
5381
6095
|
function detectPackageManager(cwd) {
|
|
5382
|
-
if (
|
|
5383
|
-
if (
|
|
5384
|
-
if (
|
|
6096
|
+
if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
6097
|
+
if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) return "yarn";
|
|
6098
|
+
if (fs23.existsSync(path22.join(cwd, "bun.lockb"))) return "bun";
|
|
5385
6099
|
return "npm";
|
|
5386
6100
|
}
|
|
5387
6101
|
function qualityCommandsFor(pm) {
|
|
@@ -5394,7 +6108,7 @@ function qualityCommandsFor(pm) {
|
|
|
5394
6108
|
function detectOwnerRepo(cwd) {
|
|
5395
6109
|
let url;
|
|
5396
6110
|
try {
|
|
5397
|
-
url =
|
|
6111
|
+
url = execFileSync19("git", ["remote", "get-url", "origin"], {
|
|
5398
6112
|
cwd,
|
|
5399
6113
|
encoding: "utf-8",
|
|
5400
6114
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5479,7 +6193,7 @@ jobs:
|
|
|
5479
6193
|
`;
|
|
5480
6194
|
function defaultBranchFromGit(cwd) {
|
|
5481
6195
|
try {
|
|
5482
|
-
const ref =
|
|
6196
|
+
const ref = execFileSync19("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
5483
6197
|
cwd,
|
|
5484
6198
|
encoding: "utf-8",
|
|
5485
6199
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5487,7 +6201,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
5487
6201
|
return ref.replace("refs/remotes/origin/", "");
|
|
5488
6202
|
} catch {
|
|
5489
6203
|
try {
|
|
5490
|
-
return
|
|
6204
|
+
return execFileSync19("git", ["branch", "--show-current"], {
|
|
5491
6205
|
cwd,
|
|
5492
6206
|
encoding: "utf-8",
|
|
5493
6207
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5503,48 +6217,48 @@ function performInit(cwd, force) {
|
|
|
5503
6217
|
const pm = detectPackageManager(cwd);
|
|
5504
6218
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
5505
6219
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
5506
|
-
const configPath =
|
|
5507
|
-
if (
|
|
6220
|
+
const configPath = path22.join(cwd, "kody.config.json");
|
|
6221
|
+
if (fs23.existsSync(configPath) && !force) {
|
|
5508
6222
|
skipped.push("kody.config.json");
|
|
5509
6223
|
} else {
|
|
5510
6224
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
5511
|
-
|
|
6225
|
+
fs23.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
5512
6226
|
`);
|
|
5513
6227
|
wrote.push("kody.config.json");
|
|
5514
6228
|
}
|
|
5515
|
-
const workflowDir =
|
|
5516
|
-
const workflowPath =
|
|
5517
|
-
if (
|
|
6229
|
+
const workflowDir = path22.join(cwd, ".github", "workflows");
|
|
6230
|
+
const workflowPath = path22.join(workflowDir, "kody.yml");
|
|
6231
|
+
if (fs23.existsSync(workflowPath) && !force) {
|
|
5518
6232
|
skipped.push(".github/workflows/kody.yml");
|
|
5519
6233
|
} else {
|
|
5520
|
-
|
|
5521
|
-
|
|
6234
|
+
fs23.mkdirSync(workflowDir, { recursive: true });
|
|
6235
|
+
fs23.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
5522
6236
|
wrote.push(".github/workflows/kody.yml");
|
|
5523
6237
|
}
|
|
5524
|
-
const hasUi =
|
|
6238
|
+
const hasUi = fs23.existsSync(path22.join(cwd, "src/app")) || fs23.existsSync(path22.join(cwd, "app")) || fs23.existsSync(path22.join(cwd, "pages"));
|
|
5525
6239
|
if (hasUi) {
|
|
5526
|
-
const qaGuidePath =
|
|
5527
|
-
if (
|
|
6240
|
+
const qaGuidePath = path22.join(cwd, QA_GUIDE_REL_PATH);
|
|
6241
|
+
if (fs23.existsSync(qaGuidePath) && !force) {
|
|
5528
6242
|
skipped.push(QA_GUIDE_REL_PATH);
|
|
5529
6243
|
} else {
|
|
5530
|
-
|
|
6244
|
+
fs23.mkdirSync(path22.dirname(qaGuidePath), { recursive: true });
|
|
5531
6245
|
const discovery = runQaDiscovery(cwd);
|
|
5532
|
-
|
|
6246
|
+
fs23.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
|
|
5533
6247
|
wrote.push(QA_GUIDE_REL_PATH);
|
|
5534
6248
|
}
|
|
5535
6249
|
}
|
|
5536
6250
|
const builtinJobs = listBuiltinJobs();
|
|
5537
6251
|
if (builtinJobs.length > 0) {
|
|
5538
|
-
const jobsDir =
|
|
5539
|
-
|
|
6252
|
+
const jobsDir = path22.join(cwd, ".kody", "jobs");
|
|
6253
|
+
fs23.mkdirSync(jobsDir, { recursive: true });
|
|
5540
6254
|
for (const job of builtinJobs) {
|
|
5541
|
-
const rel =
|
|
5542
|
-
const target =
|
|
5543
|
-
if (
|
|
6255
|
+
const rel = path22.join(".kody", "jobs", `${job.slug}.md`);
|
|
6256
|
+
const target = path22.join(cwd, rel);
|
|
6257
|
+
if (fs23.existsSync(target) && !force) {
|
|
5544
6258
|
skipped.push(rel);
|
|
5545
6259
|
continue;
|
|
5546
6260
|
}
|
|
5547
|
-
|
|
6261
|
+
fs23.writeFileSync(target, fs23.readFileSync(job.filePath, "utf-8"));
|
|
5548
6262
|
wrote.push(rel);
|
|
5549
6263
|
}
|
|
5550
6264
|
}
|
|
@@ -5556,12 +6270,12 @@ function performInit(cwd, force) {
|
|
|
5556
6270
|
continue;
|
|
5557
6271
|
}
|
|
5558
6272
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
5559
|
-
const target =
|
|
5560
|
-
if (
|
|
6273
|
+
const target = path22.join(workflowDir, `kody-${exe.name}.yml`);
|
|
6274
|
+
if (fs23.existsSync(target) && !force) {
|
|
5561
6275
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
5562
6276
|
continue;
|
|
5563
6277
|
}
|
|
5564
|
-
|
|
6278
|
+
fs23.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
5565
6279
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
5566
6280
|
}
|
|
5567
6281
|
let labels;
|
|
@@ -5649,6 +6363,48 @@ var loadCoverageRules = async (ctx) => {
|
|
|
5649
6363
|
ctx.data.coverageRules = ctx.config.testRequirements ?? [];
|
|
5650
6364
|
};
|
|
5651
6365
|
|
|
6366
|
+
// src/scripts/loadGoalState.ts
|
|
6367
|
+
var loadGoalState = async (ctx) => {
|
|
6368
|
+
const goalId = ctx.args.goal;
|
|
6369
|
+
if (typeof goalId !== "string" || goalId.length === 0) {
|
|
6370
|
+
ctx.skipAgent = true;
|
|
6371
|
+
ctx.output.exitCode = 1;
|
|
6372
|
+
ctx.output.reason = "missing --goal";
|
|
6373
|
+
return;
|
|
6374
|
+
}
|
|
6375
|
+
if (goalId.includes("/") || goalId.includes("..")) {
|
|
6376
|
+
ctx.skipAgent = true;
|
|
6377
|
+
ctx.output.exitCode = 1;
|
|
6378
|
+
ctx.output.reason = "invalid goal id (no slashes or '..' allowed)";
|
|
6379
|
+
return;
|
|
6380
|
+
}
|
|
6381
|
+
try {
|
|
6382
|
+
const state = readGoalState(ctx.cwd, goalId);
|
|
6383
|
+
ctx.data.goal = {
|
|
6384
|
+
id: goalId,
|
|
6385
|
+
state: state.state,
|
|
6386
|
+
goalIssueNumber: state.goalIssueNumber,
|
|
6387
|
+
lastDispatchedIssue: state.lastDispatchedIssue,
|
|
6388
|
+
goalPrUrl: state.goalPrUrl,
|
|
6389
|
+
// Cache the full parsed object so saveGoalState can preserve `extra`.
|
|
6390
|
+
raw: state,
|
|
6391
|
+
// `phase` is populated by deriveGoalPhase later in the chain. Initialize
|
|
6392
|
+
// to undefined so runWhen on `data.goal.phase` can match correctly.
|
|
6393
|
+
phase: void 0,
|
|
6394
|
+
// Populated by ensureGoalBranch / configured by config.git.defaultBranch.
|
|
6395
|
+
defaultBranch: ctx.config.git.defaultBranch,
|
|
6396
|
+
// Convenience derivations.
|
|
6397
|
+
goalBranch: `goal-${goalId}`
|
|
6398
|
+
};
|
|
6399
|
+
} catch (err) {
|
|
6400
|
+
process.stdout.write(`[goal-tick] ${err instanceof Error ? err.message : String(err)}
|
|
6401
|
+
`);
|
|
6402
|
+
ctx.skipAgent = true;
|
|
6403
|
+
ctx.output.exitCode = 0;
|
|
6404
|
+
ctx.output.reason = "no goal state to tick";
|
|
6405
|
+
}
|
|
6406
|
+
};
|
|
6407
|
+
|
|
5652
6408
|
// src/scripts/loadIssueContext.ts
|
|
5653
6409
|
var DEFAULT_COMMENT_LIMIT = 12;
|
|
5654
6410
|
var DEFAULT_COMMENT_MAX_BYTES = 16e3;
|
|
@@ -5699,8 +6455,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
5699
6455
|
};
|
|
5700
6456
|
|
|
5701
6457
|
// src/scripts/loadJobFromFile.ts
|
|
5702
|
-
import * as
|
|
5703
|
-
import * as
|
|
6458
|
+
import * as fs24 from "fs";
|
|
6459
|
+
import * as path23 from "path";
|
|
5704
6460
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
5705
6461
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
5706
6462
|
const slugArg = String(args?.slugArg ?? "job");
|
|
@@ -5708,11 +6464,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
5708
6464
|
if (!slug) {
|
|
5709
6465
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
5710
6466
|
}
|
|
5711
|
-
const absPath =
|
|
5712
|
-
if (!
|
|
6467
|
+
const absPath = path23.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
6468
|
+
if (!fs24.existsSync(absPath)) {
|
|
5713
6469
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
5714
6470
|
}
|
|
5715
|
-
const raw =
|
|
6471
|
+
const raw = fs24.readFileSync(absPath, "utf-8");
|
|
5716
6472
|
const { title, body } = parseJobFile(raw, slug);
|
|
5717
6473
|
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
5718
6474
|
const loaded = await backend.load(slug);
|
|
@@ -5744,16 +6500,16 @@ function humanizeSlug(slug) {
|
|
|
5744
6500
|
}
|
|
5745
6501
|
|
|
5746
6502
|
// src/scripts/loadMemoryContext.ts
|
|
5747
|
-
import * as
|
|
5748
|
-
import * as
|
|
6503
|
+
import * as fs25 from "fs";
|
|
6504
|
+
import * as path24 from "path";
|
|
5749
6505
|
var MEMORY_DIR_RELATIVE = ".kody/memory";
|
|
5750
6506
|
var MAX_PAGES = 8;
|
|
5751
6507
|
var PER_PAGE_MAX_BYTES = 4e3;
|
|
5752
6508
|
var TOTAL_MAX_BYTES = 24e3;
|
|
5753
6509
|
var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
5754
6510
|
var loadMemoryContext = async (ctx) => {
|
|
5755
|
-
const memoryAbs =
|
|
5756
|
-
if (!
|
|
6511
|
+
const memoryAbs = path24.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
6512
|
+
if (!fs25.existsSync(memoryAbs)) {
|
|
5757
6513
|
ctx.data.memoryContext = "";
|
|
5758
6514
|
return;
|
|
5759
6515
|
}
|
|
@@ -5778,21 +6534,21 @@ function collectPages(memoryAbs) {
|
|
|
5778
6534
|
walkMd(memoryAbs, (file) => {
|
|
5779
6535
|
let stat;
|
|
5780
6536
|
try {
|
|
5781
|
-
stat =
|
|
6537
|
+
stat = fs25.statSync(file);
|
|
5782
6538
|
} catch {
|
|
5783
6539
|
return;
|
|
5784
6540
|
}
|
|
5785
6541
|
let raw;
|
|
5786
6542
|
try {
|
|
5787
|
-
raw =
|
|
6543
|
+
raw = fs25.readFileSync(file, "utf-8");
|
|
5788
6544
|
} catch {
|
|
5789
6545
|
return;
|
|
5790
6546
|
}
|
|
5791
6547
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
5792
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
6548
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path24.basename(file, ".md");
|
|
5793
6549
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
5794
6550
|
out.push({
|
|
5795
|
-
relPath:
|
|
6551
|
+
relPath: path24.relative(memoryAbs, file),
|
|
5796
6552
|
title,
|
|
5797
6553
|
updated,
|
|
5798
6554
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
@@ -5860,16 +6616,16 @@ function walkMd(root, visit) {
|
|
|
5860
6616
|
const dir = stack.pop();
|
|
5861
6617
|
let names;
|
|
5862
6618
|
try {
|
|
5863
|
-
names =
|
|
6619
|
+
names = fs25.readdirSync(dir);
|
|
5864
6620
|
} catch {
|
|
5865
6621
|
continue;
|
|
5866
6622
|
}
|
|
5867
6623
|
for (const name of names) {
|
|
5868
6624
|
if (name.startsWith(".")) continue;
|
|
5869
|
-
const full =
|
|
6625
|
+
const full = path24.join(dir, name);
|
|
5870
6626
|
let stat;
|
|
5871
6627
|
try {
|
|
5872
|
-
stat =
|
|
6628
|
+
stat = fs25.statSync(full);
|
|
5873
6629
|
} catch {
|
|
5874
6630
|
continue;
|
|
5875
6631
|
}
|
|
@@ -5992,8 +6748,32 @@ var markFlowSuccess = async (ctx) => {
|
|
|
5992
6748
|
}
|
|
5993
6749
|
};
|
|
5994
6750
|
|
|
6751
|
+
// src/scripts/mergeReadyTaskPRs.ts
|
|
6752
|
+
var mergeReadyTaskPRs = async (ctx) => {
|
|
6753
|
+
const goal = ctx.data.goal;
|
|
6754
|
+
if (!goal) return;
|
|
6755
|
+
const open = listPrsByBase(goal.goalBranch, "open", ctx.cwd);
|
|
6756
|
+
if (!open.ok) {
|
|
6757
|
+
process.stderr.write(`[goal-tick] mergeReadyTaskPRs: list failed: ${open.error}
|
|
6758
|
+
`);
|
|
6759
|
+
return;
|
|
6760
|
+
}
|
|
6761
|
+
for (const pr of open.value ?? []) {
|
|
6762
|
+
if (pr.isDraft) continue;
|
|
6763
|
+
if (pr.mergeable !== "MERGEABLE") continue;
|
|
6764
|
+
if (pr.mergeStateStatus !== "CLEAN") continue;
|
|
6765
|
+
process.stdout.write(`[goal-tick] merging PR #${pr.number} into ${goal.goalBranch}
|
|
6766
|
+
`);
|
|
6767
|
+
const r = mergePrSquash(pr.number, ctx.cwd);
|
|
6768
|
+
if (!r.ok) {
|
|
6769
|
+
process.stderr.write(`[goal-tick] failed to merge PR #${pr.number}: ${r.error} (continuing)
|
|
6770
|
+
`);
|
|
6771
|
+
}
|
|
6772
|
+
}
|
|
6773
|
+
};
|
|
6774
|
+
|
|
5995
6775
|
// src/scripts/mergeReleasePr.ts
|
|
5996
|
-
import { execFileSync as
|
|
6776
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
5997
6777
|
var API_TIMEOUT_MS7 = 6e4;
|
|
5998
6778
|
var mergeReleasePr = async (ctx) => {
|
|
5999
6779
|
const state = ctx.data.taskState;
|
|
@@ -6012,7 +6792,7 @@ var mergeReleasePr = async (ctx) => {
|
|
|
6012
6792
|
process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
|
|
6013
6793
|
`);
|
|
6014
6794
|
try {
|
|
6015
|
-
const out =
|
|
6795
|
+
const out = execFileSync20("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
6016
6796
|
timeout: API_TIMEOUT_MS7,
|
|
6017
6797
|
cwd: ctx.cwd,
|
|
6018
6798
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6130,7 +6910,7 @@ function buildIssueTitle(scope, verdict) {
|
|
|
6130
6910
|
const verdictTag = verdict === "UNKNOWN" ? "REPORT" : verdict;
|
|
6131
6911
|
return `QA [${verdictTag}]: ${focus} \u2014 ${date}`.slice(0, 240);
|
|
6132
6912
|
}
|
|
6133
|
-
function
|
|
6913
|
+
function ensureLabel3(cwd) {
|
|
6134
6914
|
try {
|
|
6135
6915
|
gh(["label", "create", QA_LABEL, "--color", "8b5cf6", "--description", "kody: QA report", "--force"], { cwd });
|
|
6136
6916
|
return true;
|
|
@@ -6190,7 +6970,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
|
|
|
6190
6970
|
}
|
|
6191
6971
|
const scope = ctx.args.scope;
|
|
6192
6972
|
const title = buildIssueTitle(scope, verdict);
|
|
6193
|
-
const hasLabel =
|
|
6973
|
+
const hasLabel = ensureLabel3(ctx.cwd);
|
|
6194
6974
|
let created;
|
|
6195
6975
|
try {
|
|
6196
6976
|
created = createQaIssue(title, reportBody, hasLabel, ctx.cwd);
|
|
@@ -6595,7 +7375,7 @@ ${body}`;
|
|
|
6595
7375
|
}
|
|
6596
7376
|
|
|
6597
7377
|
// src/scripts/recordClassification.ts
|
|
6598
|
-
import { execFileSync as
|
|
7378
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
6599
7379
|
var API_TIMEOUT_MS8 = 3e4;
|
|
6600
7380
|
var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
6601
7381
|
var recordClassification = async (ctx) => {
|
|
@@ -6643,7 +7423,7 @@ function parseClassification(prSummary) {
|
|
|
6643
7423
|
}
|
|
6644
7424
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
6645
7425
|
try {
|
|
6646
|
-
|
|
7426
|
+
execFileSync21("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
6647
7427
|
cwd,
|
|
6648
7428
|
timeout: API_TIMEOUT_MS8,
|
|
6649
7429
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -6682,14 +7462,14 @@ var requireFeedbackActions = async (ctx, profile) => {
|
|
|
6682
7462
|
const items = countActionItems(actions);
|
|
6683
7463
|
ctx.data.feedbackAgentItemCount = items;
|
|
6684
7464
|
if (items < MIN_ITEMS) {
|
|
6685
|
-
|
|
7465
|
+
fail2(
|
|
6686
7466
|
ctx,
|
|
6687
7467
|
profile,
|
|
6688
7468
|
actions.length === 0 ? "agent omitted required FEEDBACK_ACTIONS block \u2014 cannot verify that review feedback was addressed" : "agent FEEDBACK_ACTIONS block listed no items \u2014 cannot verify that review feedback was addressed"
|
|
6689
7469
|
);
|
|
6690
7470
|
}
|
|
6691
7471
|
};
|
|
6692
|
-
function
|
|
7472
|
+
function fail2(ctx, profile, reason) {
|
|
6693
7473
|
ctx.data.agentDone = false;
|
|
6694
7474
|
ctx.data.agentFailureReason = reason;
|
|
6695
7475
|
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
@@ -6717,13 +7497,19 @@ var requirePlanDeviations = async (ctx, profile) => {
|
|
|
6717
7497
|
if (!planContent) return;
|
|
6718
7498
|
const raw = String(ctx.data.planDeviations ?? "").trim();
|
|
6719
7499
|
if (raw.length === 0) {
|
|
6720
|
-
|
|
7500
|
+
process.stderr.write(
|
|
7501
|
+
"[kody requirePlanDeviations] warning: agent omitted PLAN_DEVIATIONS block \u2014 proceeding anyway (verify/tests are the real gate)\n"
|
|
7502
|
+
);
|
|
7503
|
+
ctx.data.planDeviationsOmitted = true;
|
|
6721
7504
|
return;
|
|
6722
7505
|
}
|
|
6723
7506
|
if (isNoneSentinel(raw)) return;
|
|
6724
7507
|
const bullets = raw.split("\n").filter((l) => /^\s*[-*]\s+/.test(l));
|
|
6725
7508
|
if (bullets.length === 0) {
|
|
6726
|
-
|
|
7509
|
+
process.stderr.write(
|
|
7510
|
+
"[kody requirePlanDeviations] warning: PLAN_DEVIATIONS block is not 'none' and lists no bullet items \u2014 proceeding anyway\n"
|
|
7511
|
+
);
|
|
7512
|
+
ctx.data.planDeviationsMalformed = true;
|
|
6727
7513
|
return;
|
|
6728
7514
|
}
|
|
6729
7515
|
ctx.data.planDeviationCount = bullets.length;
|
|
@@ -6735,17 +7521,6 @@ function isNoneSentinel(block) {
|
|
|
6735
7521
|
if (stripped.length !== 1) return false;
|
|
6736
7522
|
return stripped[0] === "none";
|
|
6737
7523
|
}
|
|
6738
|
-
function fail2(ctx, profile, reason) {
|
|
6739
|
-
ctx.data.agentDone = false;
|
|
6740
|
-
ctx.data.agentFailureReason = reason;
|
|
6741
|
-
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
6742
|
-
const failedAction6 = {
|
|
6743
|
-
type: `${modeSeg}_FAILED`,
|
|
6744
|
-
payload: { reason },
|
|
6745
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6746
|
-
};
|
|
6747
|
-
ctx.data.action = failedAction6;
|
|
6748
|
-
}
|
|
6749
7524
|
|
|
6750
7525
|
// src/scripts/resolveArtifacts.ts
|
|
6751
7526
|
var resolveArtifacts = async (ctx, profile) => {
|
|
@@ -6771,7 +7546,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
6771
7546
|
};
|
|
6772
7547
|
|
|
6773
7548
|
// src/scripts/resolveFlow.ts
|
|
6774
|
-
import { execFileSync as
|
|
7549
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
6775
7550
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
6776
7551
|
var resolveFlow = async (ctx) => {
|
|
6777
7552
|
const prNumber = ctx.args.pr;
|
|
@@ -6841,7 +7616,7 @@ function buildPreferBlock(prefer, baseBranch) {
|
|
|
6841
7616
|
}
|
|
6842
7617
|
function getConflictedFiles(cwd) {
|
|
6843
7618
|
try {
|
|
6844
|
-
const out =
|
|
7619
|
+
const out = execFileSync22("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
6845
7620
|
encoding: "utf-8",
|
|
6846
7621
|
cwd,
|
|
6847
7622
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -6856,7 +7631,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
6856
7631
|
let total = 0;
|
|
6857
7632
|
for (const f of files) {
|
|
6858
7633
|
try {
|
|
6859
|
-
const content =
|
|
7634
|
+
const content = execFileSync22("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
6860
7635
|
const snippet = `### ${f}
|
|
6861
7636
|
|
|
6862
7637
|
\`\`\`
|
|
@@ -6957,10 +7732,10 @@ var resolvePreviewUrl = async (ctx) => {
|
|
|
6957
7732
|
};
|
|
6958
7733
|
|
|
6959
7734
|
// src/scripts/resolveQaUrl.ts
|
|
6960
|
-
import { execFileSync as
|
|
7735
|
+
import { execFileSync as execFileSync23 } from "child_process";
|
|
6961
7736
|
function ghQuery(args, cwd) {
|
|
6962
7737
|
try {
|
|
6963
|
-
const out =
|
|
7738
|
+
const out = execFileSync23("gh", args, {
|
|
6964
7739
|
cwd,
|
|
6965
7740
|
stdio: ["ignore", "pipe", "pipe"],
|
|
6966
7741
|
encoding: "utf-8",
|
|
@@ -7030,7 +7805,7 @@ var resolveQaUrl = async (ctx) => {
|
|
|
7030
7805
|
};
|
|
7031
7806
|
|
|
7032
7807
|
// src/scripts/revertFlow.ts
|
|
7033
|
-
import { execFileSync as
|
|
7808
|
+
import { execFileSync as execFileSync24 } from "child_process";
|
|
7034
7809
|
var SHA_RE = /^[0-9a-f]{4,40}$/i;
|
|
7035
7810
|
var revertFlow = async (ctx) => {
|
|
7036
7811
|
const prNumber = ctx.args.pr;
|
|
@@ -7112,7 +7887,7 @@ function buildPrSummary(resolved) {
|
|
|
7112
7887
|
return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
|
|
7113
7888
|
}
|
|
7114
7889
|
function git3(args, cwd) {
|
|
7115
|
-
return
|
|
7890
|
+
return execFileSync24("git", args, {
|
|
7116
7891
|
encoding: "utf-8",
|
|
7117
7892
|
timeout: 3e4,
|
|
7118
7893
|
cwd,
|
|
@@ -7122,7 +7897,7 @@ function git3(args, cwd) {
|
|
|
7122
7897
|
}
|
|
7123
7898
|
function isAncestorOfHead(sha, cwd) {
|
|
7124
7899
|
try {
|
|
7125
|
-
|
|
7900
|
+
execFileSync24("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
|
|
7126
7901
|
cwd,
|
|
7127
7902
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
7128
7903
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -7218,17 +7993,17 @@ function resolveBaseOverride(value) {
|
|
|
7218
7993
|
}
|
|
7219
7994
|
function resolveBaseFromLabels(labels) {
|
|
7220
7995
|
if (!labels.includes("goal-runner:dispatched")) return null;
|
|
7221
|
-
const
|
|
7222
|
-
if (!
|
|
7223
|
-
const goalId =
|
|
7996
|
+
const goalLabel2 = labels.find((l) => l.startsWith("goal:"));
|
|
7997
|
+
if (!goalLabel2) return null;
|
|
7998
|
+
const goalId = goalLabel2.slice("goal:".length);
|
|
7224
7999
|
if (!/^[a-z0-9-]+$/.test(goalId)) return null;
|
|
7225
8000
|
return `goal-${goalId}`;
|
|
7226
8001
|
}
|
|
7227
8002
|
|
|
7228
8003
|
// src/scripts/runTickScript.ts
|
|
7229
8004
|
import { spawnSync } from "child_process";
|
|
7230
|
-
import * as
|
|
7231
|
-
import * as
|
|
8005
|
+
import * as fs26 from "fs";
|
|
8006
|
+
import * as path25 from "path";
|
|
7232
8007
|
var runTickScript = async (ctx, _profile, args) => {
|
|
7233
8008
|
ctx.skipAgent = true;
|
|
7234
8009
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
@@ -7240,13 +8015,13 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
7240
8015
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
7241
8016
|
return;
|
|
7242
8017
|
}
|
|
7243
|
-
const jobPath =
|
|
7244
|
-
if (!
|
|
8018
|
+
const jobPath = path25.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
8019
|
+
if (!fs26.existsSync(jobPath)) {
|
|
7245
8020
|
ctx.output.exitCode = 99;
|
|
7246
8021
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
7247
8022
|
return;
|
|
7248
8023
|
}
|
|
7249
|
-
const raw =
|
|
8024
|
+
const raw = fs26.readFileSync(jobPath, "utf-8");
|
|
7250
8025
|
const { frontmatter } = splitFrontmatter(raw);
|
|
7251
8026
|
const tickScript = frontmatter.tickScript;
|
|
7252
8027
|
if (!tickScript) {
|
|
@@ -7254,8 +8029,8 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
7254
8029
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
7255
8030
|
return;
|
|
7256
8031
|
}
|
|
7257
|
-
const scriptPath =
|
|
7258
|
-
if (!
|
|
8032
|
+
const scriptPath = path25.isAbsolute(tickScript) ? tickScript : path25.join(ctx.cwd, tickScript);
|
|
8033
|
+
if (!fs26.existsSync(scriptPath)) {
|
|
7259
8034
|
ctx.output.exitCode = 99;
|
|
7260
8035
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
7261
8036
|
return;
|
|
@@ -7356,6 +8131,26 @@ function buildChildEnv(parent, force) {
|
|
|
7356
8131
|
return out;
|
|
7357
8132
|
}
|
|
7358
8133
|
|
|
8134
|
+
// src/scripts/saveGoalState.ts
|
|
8135
|
+
var saveGoalState = async (ctx) => {
|
|
8136
|
+
const goal = ctx.data.goal;
|
|
8137
|
+
if (!goal) {
|
|
8138
|
+
ctx.skipAgent = true;
|
|
8139
|
+
return;
|
|
8140
|
+
}
|
|
8141
|
+
const updated = {
|
|
8142
|
+
...goal.raw ?? { state: goal.state, extra: {} },
|
|
8143
|
+
state: goal.state,
|
|
8144
|
+
goalIssueNumber: goal.goalIssueNumber,
|
|
8145
|
+
lastDispatchedIssue: goal.lastDispatchedIssue,
|
|
8146
|
+
goalPrUrl: goal.goalPrUrl,
|
|
8147
|
+
completedAt: goal.completedAt ?? goal.raw?.completedAt,
|
|
8148
|
+
updatedAt: nowIso()
|
|
8149
|
+
};
|
|
8150
|
+
writeGoalState(ctx.cwd, goal.id, updated);
|
|
8151
|
+
ctx.skipAgent = true;
|
|
8152
|
+
};
|
|
8153
|
+
|
|
7359
8154
|
// src/scripts/saveTaskState.ts
|
|
7360
8155
|
var saveTaskState = async (ctx, profile) => {
|
|
7361
8156
|
const target = ctx.data.commentTargetType;
|
|
@@ -7431,11 +8226,11 @@ var skipAgent = async (ctx) => {
|
|
|
7431
8226
|
};
|
|
7432
8227
|
|
|
7433
8228
|
// src/scripts/stageMergeConflicts.ts
|
|
7434
|
-
import { execFileSync as
|
|
8229
|
+
import { execFileSync as execFileSync25 } from "child_process";
|
|
7435
8230
|
var stageMergeConflicts = async (ctx) => {
|
|
7436
8231
|
if (ctx.data.agentDone === false) return;
|
|
7437
8232
|
try {
|
|
7438
|
-
|
|
8233
|
+
execFileSync25("git", ["add", "-A"], {
|
|
7439
8234
|
cwd: ctx.cwd,
|
|
7440
8235
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
7441
8236
|
stdio: "pipe"
|
|
@@ -7445,7 +8240,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
7445
8240
|
};
|
|
7446
8241
|
|
|
7447
8242
|
// src/scripts/startFlow.ts
|
|
7448
|
-
import { execFileSync as
|
|
8243
|
+
import { execFileSync as execFileSync26 } from "child_process";
|
|
7449
8244
|
var API_TIMEOUT_MS9 = 3e4;
|
|
7450
8245
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
7451
8246
|
const entry = args?.entry;
|
|
@@ -7479,7 +8274,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
7479
8274
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
7480
8275
|
const body = `@kody ${next}`;
|
|
7481
8276
|
try {
|
|
7482
|
-
|
|
8277
|
+
execFileSync26("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
7483
8278
|
timeout: API_TIMEOUT_MS9,
|
|
7484
8279
|
cwd,
|
|
7485
8280
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7493,7 +8288,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
7493
8288
|
}
|
|
7494
8289
|
|
|
7495
8290
|
// src/scripts/syncFlow.ts
|
|
7496
|
-
import { execFileSync as
|
|
8291
|
+
import { execFileSync as execFileSync27 } from "child_process";
|
|
7497
8292
|
var syncFlow = async (ctx, _profile, args) => {
|
|
7498
8293
|
const announceOnSuccess = Boolean(args?.announceOnSuccess);
|
|
7499
8294
|
const prNumber = ctx.args.pr;
|
|
@@ -7565,7 +8360,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
7565
8360
|
}
|
|
7566
8361
|
function revParseHead(cwd) {
|
|
7567
8362
|
try {
|
|
7568
|
-
return
|
|
8363
|
+
return execFileSync27("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
7569
8364
|
} catch {
|
|
7570
8365
|
return "";
|
|
7571
8366
|
}
|
|
@@ -7573,9 +8368,9 @@ function revParseHead(cwd) {
|
|
|
7573
8368
|
function pushBranch(branch, cwd) {
|
|
7574
8369
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
7575
8370
|
try {
|
|
7576
|
-
|
|
8371
|
+
execFileSync27("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
7577
8372
|
} catch {
|
|
7578
|
-
|
|
8373
|
+
execFileSync27("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
7579
8374
|
cwd,
|
|
7580
8375
|
env,
|
|
7581
8376
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7802,7 +8597,7 @@ function downgrade2(ctx, reason) {
|
|
|
7802
8597
|
}
|
|
7803
8598
|
|
|
7804
8599
|
// src/scripts/waitForCi.ts
|
|
7805
|
-
import { execFileSync as
|
|
8600
|
+
import { execFileSync as execFileSync28 } from "child_process";
|
|
7806
8601
|
var API_TIMEOUT_MS10 = 3e4;
|
|
7807
8602
|
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
7808
8603
|
const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
|
|
@@ -7880,7 +8675,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
7880
8675
|
};
|
|
7881
8676
|
function fetchChecks(prNumber, cwd) {
|
|
7882
8677
|
try {
|
|
7883
|
-
const raw =
|
|
8678
|
+
const raw = execFileSync28("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
|
|
7884
8679
|
encoding: "utf-8",
|
|
7885
8680
|
timeout: API_TIMEOUT_MS10,
|
|
7886
8681
|
cwd,
|
|
@@ -8138,7 +8933,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
8138
8933
|
};
|
|
8139
8934
|
|
|
8140
8935
|
// src/scripts/writeRunSummary.ts
|
|
8141
|
-
import * as
|
|
8936
|
+
import * as fs27 from "fs";
|
|
8142
8937
|
var writeRunSummary = async (ctx, profile) => {
|
|
8143
8938
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
8144
8939
|
if (!summaryPath) return;
|
|
@@ -8160,7 +8955,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
8160
8955
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
8161
8956
|
lines.push("");
|
|
8162
8957
|
try {
|
|
8163
|
-
|
|
8958
|
+
fs27.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
8164
8959
|
`);
|
|
8165
8960
|
} catch {
|
|
8166
8961
|
}
|
|
@@ -8199,7 +8994,19 @@ var preflightScripts = {
|
|
|
8199
8994
|
warmupMcp,
|
|
8200
8995
|
dispatchJobTicks,
|
|
8201
8996
|
dispatchJobFileTicks,
|
|
8202
|
-
runTickScript
|
|
8997
|
+
runTickScript,
|
|
8998
|
+
loadGoalState,
|
|
8999
|
+
handleAbandonedGoal,
|
|
9000
|
+
ensureLifecycleLabels,
|
|
9001
|
+
ensureUmbrellaIssue,
|
|
9002
|
+
ensureGoalPr,
|
|
9003
|
+
mergeReadyTaskPRs,
|
|
9004
|
+
closeMergedTaskIssues,
|
|
9005
|
+
deriveGoalPhase,
|
|
9006
|
+
ensureGoalBranch,
|
|
9007
|
+
dispatchNextTask,
|
|
9008
|
+
finalizeGoal,
|
|
9009
|
+
saveGoalState
|
|
8203
9010
|
};
|
|
8204
9011
|
var postflightScripts = {
|
|
8205
9012
|
parseAgentResult: parseAgentResult2,
|
|
@@ -8238,7 +9045,8 @@ var postflightScripts = {
|
|
|
8238
9045
|
recordOutcome,
|
|
8239
9046
|
mergeReleasePr,
|
|
8240
9047
|
waitForCi,
|
|
8241
|
-
markFlowSuccess
|
|
9048
|
+
markFlowSuccess,
|
|
9049
|
+
commitGoalState
|
|
8242
9050
|
};
|
|
8243
9051
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
8244
9052
|
...Object.keys(preflightScripts),
|
|
@@ -8246,7 +9054,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
8246
9054
|
]);
|
|
8247
9055
|
|
|
8248
9056
|
// src/tools.ts
|
|
8249
|
-
import { execFileSync as
|
|
9057
|
+
import { execFileSync as execFileSync29 } from "child_process";
|
|
8250
9058
|
function verifyCliTools(tools, cwd) {
|
|
8251
9059
|
const out = [];
|
|
8252
9060
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -8279,7 +9087,7 @@ function verifyOne(tool, cwd) {
|
|
|
8279
9087
|
}
|
|
8280
9088
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
8281
9089
|
try {
|
|
8282
|
-
|
|
9090
|
+
execFileSync29("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
8283
9091
|
return true;
|
|
8284
9092
|
} catch {
|
|
8285
9093
|
return false;
|
|
@@ -8348,9 +9156,9 @@ async function runExecutable(profileName, input) {
|
|
|
8348
9156
|
data: {},
|
|
8349
9157
|
output: { exitCode: 0 }
|
|
8350
9158
|
};
|
|
8351
|
-
const ndjsonDir =
|
|
9159
|
+
const ndjsonDir = path26.join(input.cwd, ".kody");
|
|
8352
9160
|
const invokeAgent = async (prompt) => {
|
|
8353
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
9161
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path26.isAbsolute(p) ? p : path26.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
8354
9162
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
8355
9163
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
8356
9164
|
return runAgent({
|
|
@@ -8396,7 +9204,6 @@ async function runExecutable(profileName, input) {
|
|
|
8396
9204
|
return finish({ exitCode: 99, reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)" });
|
|
8397
9205
|
}
|
|
8398
9206
|
agentResult = await invokeAgent(prompt);
|
|
8399
|
-
agentResult = await rescueMissingMarker(agentResult, invokeAgent);
|
|
8400
9207
|
}
|
|
8401
9208
|
for (const entry of profile.scripts.postflight) {
|
|
8402
9209
|
const entryLabel = entry.script ?? entry.shell ?? "<unknown>";
|
|
@@ -8460,17 +9267,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
8460
9267
|
function resolveProfilePath(profileName) {
|
|
8461
9268
|
const found = resolveExecutable(profileName);
|
|
8462
9269
|
if (found) return found;
|
|
8463
|
-
const here =
|
|
9270
|
+
const here = path26.dirname(new URL(import.meta.url).pathname);
|
|
8464
9271
|
const candidates = [
|
|
8465
|
-
|
|
9272
|
+
path26.join(here, "executables", profileName, "profile.json"),
|
|
8466
9273
|
// same-dir sibling (dev)
|
|
8467
|
-
|
|
9274
|
+
path26.join(here, "..", "executables", profileName, "profile.json"),
|
|
8468
9275
|
// up one (prod: dist/bin → dist/executables)
|
|
8469
|
-
|
|
9276
|
+
path26.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
8470
9277
|
// fallback
|
|
8471
9278
|
];
|
|
8472
9279
|
for (const c of candidates) {
|
|
8473
|
-
if (
|
|
9280
|
+
if (fs28.existsSync(c)) return c;
|
|
8474
9281
|
}
|
|
8475
9282
|
return candidates[0];
|
|
8476
9283
|
}
|
|
@@ -8574,8 +9381,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
8574
9381
|
var SIGKILL_GRACE_MS = 5e3;
|
|
8575
9382
|
async function runShellEntry(entry, ctx, profile) {
|
|
8576
9383
|
const shellName = entry.shell;
|
|
8577
|
-
const shellPath =
|
|
8578
|
-
if (!
|
|
9384
|
+
const shellPath = path26.join(profile.dir, shellName);
|
|
9385
|
+
if (!fs28.existsSync(shellPath)) {
|
|
8579
9386
|
ctx.skipAgent = true;
|
|
8580
9387
|
ctx.output.exitCode = 99;
|
|
8581
9388
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -8836,7 +9643,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
8836
9643
|
}
|
|
8837
9644
|
function resetWorkingTree(cwd) {
|
|
8838
9645
|
try {
|
|
8839
|
-
|
|
9646
|
+
execFileSync30("git", ["reset", "--hard", "HEAD"], {
|
|
8840
9647
|
cwd,
|
|
8841
9648
|
stdio: ["ignore", "pipe", "pipe"],
|
|
8842
9649
|
timeout: 3e4
|
|
@@ -8988,14 +9795,14 @@ function resolveAuthToken(env = process.env) {
|
|
|
8988
9795
|
return token;
|
|
8989
9796
|
}
|
|
8990
9797
|
function detectPackageManager2(cwd) {
|
|
8991
|
-
if (
|
|
8992
|
-
if (
|
|
8993
|
-
if (
|
|
9798
|
+
if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9799
|
+
if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
|
|
9800
|
+
if (fs29.existsSync(path27.join(cwd, "bun.lockb"))) return "bun";
|
|
8994
9801
|
return "npm";
|
|
8995
9802
|
}
|
|
8996
9803
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
8997
9804
|
try {
|
|
8998
|
-
|
|
9805
|
+
execFileSync31(cmd, args, {
|
|
8999
9806
|
cwd,
|
|
9000
9807
|
stdio: stream ? "inherit" : "pipe",
|
|
9001
9808
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -9008,7 +9815,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
9008
9815
|
}
|
|
9009
9816
|
function isOnPath(bin) {
|
|
9010
9817
|
try {
|
|
9011
|
-
|
|
9818
|
+
execFileSync31("which", [bin], { stdio: "pipe" });
|
|
9012
9819
|
return true;
|
|
9013
9820
|
} catch {
|
|
9014
9821
|
return false;
|
|
@@ -9049,7 +9856,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
9049
9856
|
} catch {
|
|
9050
9857
|
}
|
|
9051
9858
|
try {
|
|
9052
|
-
|
|
9859
|
+
execFileSync31("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
9053
9860
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
9054
9861
|
return 0;
|
|
9055
9862
|
} catch {
|
|
@@ -9059,16 +9866,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
9059
9866
|
}
|
|
9060
9867
|
function configureGitIdentity(cwd) {
|
|
9061
9868
|
try {
|
|
9062
|
-
const name =
|
|
9869
|
+
const name = execFileSync31("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
9063
9870
|
if (name) return;
|
|
9064
9871
|
} catch {
|
|
9065
9872
|
}
|
|
9066
9873
|
try {
|
|
9067
|
-
|
|
9874
|
+
execFileSync31("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
9068
9875
|
} catch {
|
|
9069
9876
|
}
|
|
9070
9877
|
try {
|
|
9071
|
-
|
|
9878
|
+
execFileSync31("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
9072
9879
|
cwd,
|
|
9073
9880
|
stdio: "pipe"
|
|
9074
9881
|
});
|
|
@@ -9077,11 +9884,11 @@ function configureGitIdentity(cwd) {
|
|
|
9077
9884
|
}
|
|
9078
9885
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
9079
9886
|
if (!issueNumber) return;
|
|
9080
|
-
const logPath =
|
|
9887
|
+
const logPath = path27.join(cwd, ".kody", "last-run.jsonl");
|
|
9081
9888
|
let tail = "";
|
|
9082
9889
|
try {
|
|
9083
|
-
if (
|
|
9084
|
-
const content =
|
|
9890
|
+
if (fs29.existsSync(logPath)) {
|
|
9891
|
+
const content = fs29.readFileSync(logPath, "utf-8");
|
|
9085
9892
|
tail = content.slice(-3e3);
|
|
9086
9893
|
}
|
|
9087
9894
|
} catch {
|
|
@@ -9106,7 +9913,7 @@ async function runCi(argv) {
|
|
|
9106
9913
|
return 0;
|
|
9107
9914
|
}
|
|
9108
9915
|
const args = parseCiArgs(argv);
|
|
9109
|
-
const cwd = args.cwd ?
|
|
9916
|
+
const cwd = args.cwd ? path27.resolve(args.cwd) : process.cwd();
|
|
9110
9917
|
let earlyConfig;
|
|
9111
9918
|
try {
|
|
9112
9919
|
earlyConfig = loadConfig(cwd);
|
|
@@ -9116,9 +9923,9 @@ async function runCi(argv) {
|
|
|
9116
9923
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
9117
9924
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
9118
9925
|
let manualWorkflowDispatch = false;
|
|
9119
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
9926
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs29.existsSync(dispatchEventPath)) {
|
|
9120
9927
|
try {
|
|
9121
|
-
const evt = JSON.parse(
|
|
9928
|
+
const evt = JSON.parse(fs29.readFileSync(dispatchEventPath, "utf-8"));
|
|
9122
9929
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
9123
9930
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
9124
9931
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -9333,15 +10140,15 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
9333
10140
|
return result;
|
|
9334
10141
|
}
|
|
9335
10142
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
9336
|
-
const sessionFile =
|
|
9337
|
-
const eventsFile =
|
|
9338
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
10143
|
+
const sessionFile = path28.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
10144
|
+
const eventsFile = path28.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
10145
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs30.existsSync(path28.join(cwd, p)));
|
|
9339
10146
|
if (paths.length === 0) return;
|
|
9340
10147
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
9341
10148
|
try {
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
10149
|
+
execFileSync32("git", ["add", "-f", ...paths], opts);
|
|
10150
|
+
execFileSync32("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
10151
|
+
execFileSync32("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
9345
10152
|
} catch (err) {
|
|
9346
10153
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9347
10154
|
process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
|
|
@@ -9373,7 +10180,7 @@ async function runChat(argv) {
|
|
|
9373
10180
|
${CHAT_HELP}`);
|
|
9374
10181
|
return 64;
|
|
9375
10182
|
}
|
|
9376
|
-
const cwd = args.cwd ?
|
|
10183
|
+
const cwd = args.cwd ? path28.resolve(args.cwd) : process.cwd();
|
|
9377
10184
|
const sessionId = args.sessionId;
|
|
9378
10185
|
const unpackedSecrets = unpackAllSecrets();
|
|
9379
10186
|
if (unpackedSecrets > 0) {
|
|
@@ -9425,7 +10232,7 @@ ${CHAT_HELP}`);
|
|
|
9425
10232
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
9426
10233
|
const meta = readMeta(sessionFile);
|
|
9427
10234
|
process.stdout.write(
|
|
9428
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
10235
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs30.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
9429
10236
|
`
|
|
9430
10237
|
);
|
|
9431
10238
|
try {
|