@kody-ade/kody-engine 0.4.223 → 0.4.225
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/README.md +8 -7
- package/dist/bin/kody.js +1380 -1263
- package/dist/executables/run/profile.json +1 -1
- package/dist/executables/types.ts +3 -1
- package/package.json +23 -22
- package/dist/duties/duty-scheduler/duty.md +0 -3
- package/dist/duties/duty-scheduler/profile.json +0 -6
- package/dist/duties/duty-tick/duty.md +0 -3
- package/dist/duties/duty-tick/profile.json +0 -6
- package/dist/duties/duty-tick-scripted/duty.md +0 -3
- package/dist/duties/duty-tick-scripted/profile.json +0 -6
- package/dist/duties/fix/duty.md +0 -10
- package/dist/duties/fix/profile.json +0 -6
- package/dist/duties/fix-ci/duty.md +0 -10
- package/dist/duties/fix-ci/profile.json +0 -6
- package/dist/duties/goal-scheduler/duty.md +0 -3
- package/dist/duties/goal-scheduler/profile.json +0 -6
- package/dist/duties/goal-tick/duty.md +0 -3
- package/dist/duties/goal-tick/profile.json +0 -6
- package/dist/duties/init/duty.md +0 -10
- package/dist/duties/init/profile.json +0 -6
- package/dist/duties/job-live-verify/duty.md +0 -3
- package/dist/duties/job-live-verify/profile.json +0 -6
- package/dist/duties/merge/duty.md +0 -10
- package/dist/duties/merge/profile.json +0 -6
- package/dist/duties/plan-verify/duty.md +0 -3
- package/dist/duties/plan-verify/profile.json +0 -6
- package/dist/duties/preview-build/duty.md +0 -10
- package/dist/duties/preview-build/profile.json +0 -6
- package/dist/duties/probe-skill/duty.md +0 -3
- package/dist/duties/probe-skill/profile.json +0 -6
- package/dist/duties/qa-goal/duty.md +0 -3
- package/dist/duties/qa-goal/profile.json +0 -6
- package/dist/duties/release/duty.md +0 -10
- package/dist/duties/release/profile.json +0 -6
- package/dist/duties/release-deploy/duty.md +0 -10
- package/dist/duties/release-deploy/profile.json +0 -6
- package/dist/duties/release-prepare/duty.md +0 -10
- package/dist/duties/release-prepare/profile.json +0 -6
- package/dist/duties/release-publish/duty.md +0 -10
- package/dist/duties/release-publish/profile.json +0 -6
- package/dist/duties/resolve/duty.md +0 -10
- package/dist/duties/resolve/profile.json +0 -6
- package/dist/duties/revert/duty.md +0 -10
- package/dist/duties/revert/profile.json +0 -6
- package/dist/duties/sync/duty.md +0 -10
- package/dist/duties/sync/profile.json +0 -6
- package/dist/duties/task-job-fail-once/duty.md +0 -3
- package/dist/duties/task-job-fail-once/profile.json +0 -6
- package/dist/duties/task-job-pass-a/duty.md +0 -3
- package/dist/duties/task-job-pass-a/profile.json +0 -6
- package/dist/duties/task-job-pass-b/duty.md +0 -3
- package/dist/duties/task-job-pass-b/profile.json +0 -6
- package/dist/duties/task-jobs/duty.md +0 -3
- package/dist/duties/task-jobs/profile.json +0 -6
- package/dist/duties/worker-ask/duty.md +0 -10
- package/dist/duties/worker-ask/profile.json +0 -6
- package/dist/executables/duty-scheduler/profile.json +0 -57
- package/dist/executables/duty-tick/profile.json +0 -78
- package/dist/executables/duty-tick/prompt.md +0 -77
- package/dist/executables/duty-tick/prompts/locked.md +0 -50
- package/dist/executables/duty-tick-scripted/profile.json +0 -72
- package/dist/executables/fix/profile.json +0 -91
- package/dist/executables/fix/prompt.md +0 -90
- package/dist/executables/fix-ci/profile.json +0 -71
- package/dist/executables/fix-ci/prompt.md +0 -78
- package/dist/executables/goal-scheduler/profile.json +0 -53
- package/dist/executables/goal-scheduler/scheduler.sh +0 -82
- package/dist/executables/goal-tick/profile.json +0 -69
- package/dist/executables/init/profile.json +0 -36
- package/dist/executables/job-live-verify/profile.json +0 -36
- package/dist/executables/job-live-verify/prompt.md +0 -42
- package/dist/executables/merge/profile.json +0 -54
- package/dist/executables/plan-verify/profile.json +0 -38
- package/dist/executables/plan-verify/prompt.md +0 -72
- package/dist/executables/preview-build/profile.json +0 -61
- package/dist/executables/preview-health/profile.json +0 -48
- package/dist/executables/preview-health/tick.sh +0 -319
- package/dist/executables/probe-skill/profile.json +0 -35
- package/dist/executables/probe-skill/prompt.md +0 -31
- package/dist/executables/probe-skill/skills/probe-skill-marker/SKILL.md +0 -18
- package/dist/executables/qa-goal/profile.json +0 -65
- package/dist/executables/release/deploy.sh +0 -157
- package/dist/executables/release/prepare.sh +0 -353
- package/dist/executables/release/profile.json +0 -95
- package/dist/executables/release/publish.sh +0 -88
- package/dist/executables/release/release.sh +0 -183
- package/dist/executables/release/wait.sh +0 -88
- package/dist/executables/release-deploy/deploy.sh +0 -222
- package/dist/executables/release-deploy/profile.json +0 -53
- package/dist/executables/release-prepare/prepare.sh +0 -368
- package/dist/executables/release-prepare/profile.json +0 -70
- package/dist/executables/release-publish/profile.json +0 -53
- package/dist/executables/release-publish/publish.sh +0 -137
- package/dist/executables/resolve/apply-prefer.sh +0 -54
- package/dist/executables/resolve/profile.json +0 -107
- package/dist/executables/resolve/prompt.md +0 -51
- package/dist/executables/revert/profile.json +0 -82
- package/dist/executables/revert/revert.sh +0 -33
- package/dist/executables/sync/profile.json +0 -48
- package/dist/executables/task-job-fail-once/profile.json +0 -44
- package/dist/executables/task-job-fail-once/prompt.md +0 -1
- package/dist/executables/task-job-pass-a/profile.json +0 -43
- package/dist/executables/task-job-pass-a/prompt.md +0 -1
- package/dist/executables/task-job-pass-b/profile.json +0 -43
- package/dist/executables/task-job-pass-b/prompt.md +0 -1
- package/dist/executables/task-jobs/profile.json +0 -41
- package/dist/executables/task-jobs/prompt.md +0 -1
- package/dist/executables/worker-ask/profile.json +0 -70
- package/dist/executables/worker-ask/prompt.md +0 -60
package/dist/bin/kody.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "@kody-ade/kody-engine",
|
|
18
|
-
version: "0.4.
|
|
18
|
+
version: "0.4.225",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -205,6 +205,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
205
205
|
agent: {
|
|
206
206
|
model: String(agent.model),
|
|
207
207
|
...parsePerExecutable(agent.perExecutable),
|
|
208
|
+
...parsePerExecutableReasoningEffort(agent.perExecutableReasoningEffort),
|
|
208
209
|
...parseAgentReasoningEffort(agent.reasoningEffort)
|
|
209
210
|
},
|
|
210
211
|
issueContext: parseIssueContext(raw.issueContext),
|
|
@@ -216,6 +217,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
216
217
|
classify: parseClassifyConfig(raw.classify),
|
|
217
218
|
release: parseReleaseConfig(raw.release),
|
|
218
219
|
jobs: parseJobsConfig(raw.jobs),
|
|
220
|
+
company: parseCompanyConfig(raw.company),
|
|
219
221
|
access: parseAccessConfig(raw.access)
|
|
220
222
|
};
|
|
221
223
|
}
|
|
@@ -262,6 +264,28 @@ function parseJobsConfig(raw) {
|
|
|
262
264
|
}
|
|
263
265
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
264
266
|
}
|
|
267
|
+
function parseCompanyConfig(raw) {
|
|
268
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
269
|
+
const r = raw;
|
|
270
|
+
const out = {};
|
|
271
|
+
if (r.activeDuties !== void 0) out.activeDuties = parseSlugArray(r.activeDuties, "company.activeDuties");
|
|
272
|
+
if (r.activeGoals !== void 0) out.activeGoals = parseSlugArray(r.activeGoals, "company.activeGoals");
|
|
273
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
274
|
+
}
|
|
275
|
+
function parseSlugArray(raw, field) {
|
|
276
|
+
if (!Array.isArray(raw)) throw new Error(`kody.config.json: ${field} must be an array of strings`);
|
|
277
|
+
const out = [];
|
|
278
|
+
for (const value of raw) {
|
|
279
|
+
if (typeof value !== "string") throw new Error(`kody.config.json: ${field} entries must be strings`);
|
|
280
|
+
const slug = value.trim();
|
|
281
|
+
if (!slug) continue;
|
|
282
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(slug)) {
|
|
283
|
+
throw new Error(`kody.config.json: ${field} contains invalid slug "${value}"`);
|
|
284
|
+
}
|
|
285
|
+
out.push(slug);
|
|
286
|
+
}
|
|
287
|
+
return [...new Set(out)];
|
|
288
|
+
}
|
|
265
289
|
function recordValue(raw) {
|
|
266
290
|
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
|
|
267
291
|
}
|
|
@@ -282,6 +306,15 @@ function parsePerExecutable(raw) {
|
|
|
282
306
|
}
|
|
283
307
|
return Object.keys(out).length > 0 ? { perExecutable: out } : {};
|
|
284
308
|
}
|
|
309
|
+
function parsePerExecutableReasoningEffort(raw) {
|
|
310
|
+
if (!raw || typeof raw !== "object") return {};
|
|
311
|
+
const out = {};
|
|
312
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
313
|
+
const effort = typeof v === "string" ? parseReasoningEffort(v) : null;
|
|
314
|
+
if (effort) out[k] = effort;
|
|
315
|
+
}
|
|
316
|
+
return Object.keys(out).length > 0 ? { perExecutableReasoningEffort: out } : {};
|
|
317
|
+
}
|
|
285
318
|
function parseAgentReasoningEffort(raw) {
|
|
286
319
|
if (typeof raw !== "string") return {};
|
|
287
320
|
return { reasoningEffort: parseReasoningEffort(raw) ?? void 0 };
|
|
@@ -562,7 +595,7 @@ var init_events = __esm({
|
|
|
562
595
|
// src/verify.ts
|
|
563
596
|
import { spawn } from "child_process";
|
|
564
597
|
function runCommand(command, cwd) {
|
|
565
|
-
return new Promise((
|
|
598
|
+
return new Promise((resolve7) => {
|
|
566
599
|
const start = Date.now();
|
|
567
600
|
const child = spawn(command, {
|
|
568
601
|
cwd,
|
|
@@ -591,11 +624,11 @@ function runCommand(command, cwd) {
|
|
|
591
624
|
child.on("exit", (code) => {
|
|
592
625
|
clearTimeout(timer);
|
|
593
626
|
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
594
|
-
|
|
627
|
+
resolve7({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
595
628
|
});
|
|
596
629
|
child.on("error", (err) => {
|
|
597
630
|
clearTimeout(timer);
|
|
598
|
-
|
|
631
|
+
resolve7({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
599
632
|
});
|
|
600
633
|
});
|
|
601
634
|
}
|
|
@@ -1450,7 +1483,7 @@ var init_dutyMcp = __esm({
|
|
|
1450
1483
|
TRUST_STATE_BRANCH = "kody-state";
|
|
1451
1484
|
THREAD_BODY_MAX = 4e3;
|
|
1452
1485
|
CHECK_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "STARTUP_FAILURE", "ACTION_REQUIRED"]);
|
|
1453
|
-
DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "
|
|
1486
|
+
DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "worker-ask", "chat"];
|
|
1454
1487
|
trackMarker = (key) => `<!-- kody-track:${key} -->`;
|
|
1455
1488
|
commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
|
|
1456
1489
|
GATE_EXEMPT_DUTIES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
|
|
@@ -1513,7 +1546,7 @@ var init_repoWorkspace = __esm({
|
|
|
1513
1546
|
defaultCloneRepo = (repo, token, dir) => {
|
|
1514
1547
|
fs4.mkdirSync(path4.dirname(dir), { recursive: true });
|
|
1515
1548
|
const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
|
|
1516
|
-
return new Promise((
|
|
1549
|
+
return new Promise((resolve7, reject) => {
|
|
1517
1550
|
const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
|
|
1518
1551
|
stdio: "inherit"
|
|
1519
1552
|
});
|
|
@@ -1529,7 +1562,7 @@ var init_repoWorkspace = __esm({
|
|
|
1529
1562
|
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
1530
1563
|
} catch {
|
|
1531
1564
|
}
|
|
1532
|
-
|
|
1565
|
+
resolve7();
|
|
1533
1566
|
});
|
|
1534
1567
|
child.on("error", reject);
|
|
1535
1568
|
});
|
|
@@ -1614,6 +1647,10 @@ function classifySubtype(subtype) {
|
|
|
1614
1647
|
if (lower.includes("error")) return "model_error";
|
|
1615
1648
|
return "generic_failed";
|
|
1616
1649
|
}
|
|
1650
|
+
function isClaudeLoginRequiredText(text) {
|
|
1651
|
+
const normalized = text.toLowerCase();
|
|
1652
|
+
return normalized.includes("not logged in") && normalized.includes("/login");
|
|
1653
|
+
}
|
|
1617
1654
|
function resolveTurnTimeoutMs(opts) {
|
|
1618
1655
|
if (opts.maxTurnTimeoutMs !== void 0 && opts.maxTurnTimeoutMs !== null) {
|
|
1619
1656
|
return opts.maxTurnTimeoutMs > 0 ? opts.maxTurnTimeoutMs : 0;
|
|
@@ -1696,6 +1733,7 @@ async function runAgent(opts) {
|
|
|
1696
1733
|
messageCount = 0;
|
|
1697
1734
|
let sawMutatingTool = false;
|
|
1698
1735
|
let sawTerminalSuccess = false;
|
|
1736
|
+
let sawLoginRequired = false;
|
|
1699
1737
|
let noWorkSuccess = false;
|
|
1700
1738
|
try {
|
|
1701
1739
|
const queryOptions = {
|
|
@@ -1809,10 +1847,10 @@ async function runAgent(opts) {
|
|
|
1809
1847
|
let timer;
|
|
1810
1848
|
let next;
|
|
1811
1849
|
if (turnTimeoutMs > 0) {
|
|
1812
|
-
const timeoutPromise = new Promise((
|
|
1850
|
+
const timeoutPromise = new Promise((resolve7) => {
|
|
1813
1851
|
timer = setTimeout(() => {
|
|
1814
1852
|
timedOut = true;
|
|
1815
|
-
|
|
1853
|
+
resolve7({ done: true, value: void 0 });
|
|
1816
1854
|
}, turnTimeoutMs);
|
|
1817
1855
|
});
|
|
1818
1856
|
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
@@ -1845,8 +1883,11 @@ async function runAgent(opts) {
|
|
|
1845
1883
|
}
|
|
1846
1884
|
}
|
|
1847
1885
|
const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
|
|
1848
|
-
if (line)
|
|
1886
|
+
if (line) {
|
|
1887
|
+
if (isClaudeLoginRequiredText(line)) sawLoginRequired = true;
|
|
1888
|
+
process.stdout.write(`${line}
|
|
1849
1889
|
`);
|
|
1890
|
+
}
|
|
1850
1891
|
const m = msg;
|
|
1851
1892
|
if (opts.onProgress) {
|
|
1852
1893
|
const blocks = m.message?.content ?? [];
|
|
@@ -1919,6 +1960,7 @@ async function runAgent(opts) {
|
|
|
1919
1960
|
outcomeKind = "ok";
|
|
1920
1961
|
sawTerminalSuccess = true;
|
|
1921
1962
|
const text = (typeof m.result === "string" ? m.result : "").trim();
|
|
1963
|
+
if (isClaudeLoginRequiredText(text)) sawLoginRequired = true;
|
|
1922
1964
|
if (text) resultTexts.push(text);
|
|
1923
1965
|
} else {
|
|
1924
1966
|
outcome = "failed";
|
|
@@ -1948,6 +1990,11 @@ async function runAgent(opts) {
|
|
|
1948
1990
|
);
|
|
1949
1991
|
}
|
|
1950
1992
|
finalText = resultTexts.join("\n\n---\n\n");
|
|
1993
|
+
if (outcome === "completed" && sawLoginRequired) {
|
|
1994
|
+
outcome = "failed";
|
|
1995
|
+
outcomeKind = "model_error";
|
|
1996
|
+
errorMessage = "Claude Code reported it is not logged in; refusing to mark agent run successful";
|
|
1997
|
+
}
|
|
1951
1998
|
if (outcome === "completed" && !sawMutatingTool) {
|
|
1952
1999
|
const backendDead = opts.isBackendHealthy ? !await opts.isBackendHealthy() : false;
|
|
1953
2000
|
const zeroOutput = tokens.output === 0 && finalText === "";
|
|
@@ -2011,6 +2058,107 @@ var init_agent = __esm({
|
|
|
2011
2058
|
}
|
|
2012
2059
|
});
|
|
2013
2060
|
|
|
2061
|
+
// src/companyStore.ts
|
|
2062
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2063
|
+
import * as crypto2 from "crypto";
|
|
2064
|
+
import * as fs6 from "fs";
|
|
2065
|
+
import * as os2 from "os";
|
|
2066
|
+
import * as path6 from "path";
|
|
2067
|
+
function getCompanyStoreRoot() {
|
|
2068
|
+
const store = resolveCompanyStore();
|
|
2069
|
+
if (!store) return null;
|
|
2070
|
+
const key = `${store.repo}#${store.ref}`;
|
|
2071
|
+
if (memo?.key === key) return memo.root;
|
|
2072
|
+
const root = fetchCompanyStore(store.repo, store.ref);
|
|
2073
|
+
memo = { key, root };
|
|
2074
|
+
return root;
|
|
2075
|
+
}
|
|
2076
|
+
function getCompanyStoreAssetRoot(kind) {
|
|
2077
|
+
const root = getCompanyStoreRoot();
|
|
2078
|
+
if (!root) return null;
|
|
2079
|
+
return path6.join(root, ".kody", kind);
|
|
2080
|
+
}
|
|
2081
|
+
function resolveCompanyStore() {
|
|
2082
|
+
const envStore = process.env[STORE_ENV]?.trim();
|
|
2083
|
+
if (!envStore && process.env.VITEST) return null;
|
|
2084
|
+
const repo = envStore || DEFAULT_COMPANY_STORE;
|
|
2085
|
+
if (repo === "0" || repo === "false" || repo === "off") return null;
|
|
2086
|
+
const ref = process.env[REF_ENV]?.trim() || DEFAULT_COMPANY_STORE_REF;
|
|
2087
|
+
if (!ref) return null;
|
|
2088
|
+
return { repo, ref };
|
|
2089
|
+
}
|
|
2090
|
+
function fetchCompanyStore(repo, ref) {
|
|
2091
|
+
const localRoot = localStoreRoot(repo);
|
|
2092
|
+
if (localRoot) return localRoot;
|
|
2093
|
+
const url = repoToGitUrl(repo);
|
|
2094
|
+
const cacheDir = path6.join(cacheRoot(), cacheKey(repo, ref));
|
|
2095
|
+
try {
|
|
2096
|
+
if (isGitHubShorthand(repo)) setupGithubGitAuth();
|
|
2097
|
+
fs6.mkdirSync(path6.dirname(cacheDir), { recursive: true });
|
|
2098
|
+
if (!fs6.existsSync(path6.join(cacheDir, ".git"))) {
|
|
2099
|
+
fs6.rmSync(cacheDir, { recursive: true, force: true });
|
|
2100
|
+
runGit(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
|
|
2101
|
+
}
|
|
2102
|
+
runGit(["-C", cacheDir, "remote", "set-url", "origin", url]);
|
|
2103
|
+
runGit(["-C", cacheDir, "fetch", "--depth=1", "origin", ref]);
|
|
2104
|
+
runGit(["-C", cacheDir, "checkout", "--detach", "FETCH_HEAD"]);
|
|
2105
|
+
return cacheDir;
|
|
2106
|
+
} catch (err) {
|
|
2107
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2108
|
+
process.stderr.write(`[company-store] failed to fetch ${repo}#${ref}: ${msg}
|
|
2109
|
+
`);
|
|
2110
|
+
return null;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
function localStoreRoot(repo) {
|
|
2114
|
+
if (!path6.isAbsolute(repo) && !repo.startsWith(".")) return null;
|
|
2115
|
+
const root = path6.resolve(repo);
|
|
2116
|
+
if (!fs6.existsSync(path6.join(root, ".kody"))) return null;
|
|
2117
|
+
return root;
|
|
2118
|
+
}
|
|
2119
|
+
function repoToGitUrl(repo) {
|
|
2120
|
+
if (/^(https?:|ssh:|git@|file:)/.test(repo)) return repo;
|
|
2121
|
+
if (isGitHubShorthand(repo)) {
|
|
2122
|
+
return `https://github.com/${repo}.git`;
|
|
2123
|
+
}
|
|
2124
|
+
return repo;
|
|
2125
|
+
}
|
|
2126
|
+
function isGitHubShorthand(repo) {
|
|
2127
|
+
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo);
|
|
2128
|
+
}
|
|
2129
|
+
function cacheRoot() {
|
|
2130
|
+
return process.env[CACHE_ENV]?.trim() || path6.join(os2.homedir(), ".cache", "kody", "company-store");
|
|
2131
|
+
}
|
|
2132
|
+
function cacheKey(repo, ref) {
|
|
2133
|
+
return crypto2.createHash("sha256").update(`${repo}#${ref}`).digest("hex").slice(0, 24);
|
|
2134
|
+
}
|
|
2135
|
+
function runGit(args) {
|
|
2136
|
+
execFileSync2("git", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
2137
|
+
}
|
|
2138
|
+
function setupGithubGitAuth() {
|
|
2139
|
+
const token = process.env.KODY_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_PAT;
|
|
2140
|
+
if (!token) return;
|
|
2141
|
+
try {
|
|
2142
|
+
execFileSync2("gh", ["auth", "setup-git", "--hostname", "github.com"], {
|
|
2143
|
+
env: { ...process.env, GH_TOKEN: token },
|
|
2144
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
2145
|
+
});
|
|
2146
|
+
} catch {
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
var DEFAULT_COMPANY_STORE, DEFAULT_COMPANY_STORE_REF, STORE_ENV, REF_ENV, CACHE_ENV, memo;
|
|
2150
|
+
var init_companyStore = __esm({
|
|
2151
|
+
"src/companyStore.ts"() {
|
|
2152
|
+
"use strict";
|
|
2153
|
+
DEFAULT_COMPANY_STORE = "aharonyaircohen/kody-company-store";
|
|
2154
|
+
DEFAULT_COMPANY_STORE_REF = "stable";
|
|
2155
|
+
STORE_ENV = "KODY_COMPANY_STORE";
|
|
2156
|
+
REF_ENV = "KODY_COMPANY_STORE_REF";
|
|
2157
|
+
CACHE_ENV = "KODY_COMPANY_STORE_CACHE";
|
|
2158
|
+
memo = null;
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2014
2162
|
// src/scripts/scheduleEvery.ts
|
|
2015
2163
|
function isScheduleEvery(value) {
|
|
2016
2164
|
return typeof value === "string" && SCHEDULE_EVERY_VALUES.includes(value);
|
|
@@ -2062,30 +2210,30 @@ var init_scheduleEvery = __esm({
|
|
|
2062
2210
|
});
|
|
2063
2211
|
|
|
2064
2212
|
// src/dutyFolders.ts
|
|
2065
|
-
import * as
|
|
2066
|
-
import * as
|
|
2213
|
+
import * as fs7 from "fs";
|
|
2214
|
+
import * as path7 from "path";
|
|
2067
2215
|
function listDutyFolderSlugs(absDir) {
|
|
2068
|
-
if (!
|
|
2216
|
+
if (!fs7.existsSync(absDir)) return [];
|
|
2069
2217
|
let entries;
|
|
2070
2218
|
try {
|
|
2071
|
-
entries =
|
|
2219
|
+
entries = fs7.readdirSync(absDir, { withFileTypes: true });
|
|
2072
2220
|
} catch {
|
|
2073
2221
|
return [];
|
|
2074
2222
|
}
|
|
2075
|
-
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isDutyFolder(
|
|
2223
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isDutyFolder(path7.join(absDir, e.name))).map((e) => e.name).sort();
|
|
2076
2224
|
}
|
|
2077
2225
|
function isDutyFolder(dir) {
|
|
2078
|
-
return
|
|
2226
|
+
return fs7.existsSync(path7.join(dir, DUTY_PROFILE_FILE)) && fs7.existsSync(path7.join(dir, DUTY_BODY_FILE));
|
|
2079
2227
|
}
|
|
2080
2228
|
function readDutyFolder(root, slug) {
|
|
2081
|
-
const dir =
|
|
2082
|
-
const profilePath =
|
|
2083
|
-
const bodyPath =
|
|
2084
|
-
if (!
|
|
2085
|
-
if (!
|
|
2229
|
+
const dir = path7.join(root, slug);
|
|
2230
|
+
const profilePath = path7.join(dir, DUTY_PROFILE_FILE);
|
|
2231
|
+
const bodyPath = path7.join(dir, DUTY_BODY_FILE);
|
|
2232
|
+
if (!fs7.existsSync(profilePath) || !fs7.statSync(profilePath).isFile()) return null;
|
|
2233
|
+
if (!fs7.existsSync(bodyPath) || !fs7.statSync(bodyPath).isFile()) return null;
|
|
2086
2234
|
try {
|
|
2087
|
-
const rawProfile = JSON.parse(
|
|
2088
|
-
const rawBody =
|
|
2235
|
+
const rawProfile = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
|
|
2236
|
+
const rawBody = fs7.readFileSync(bodyPath, "utf-8");
|
|
2089
2237
|
const { title, body } = parseDutyBody(rawBody, slug);
|
|
2090
2238
|
return {
|
|
2091
2239
|
slug,
|
|
@@ -2164,70 +2312,76 @@ var init_dutyFolders = __esm({
|
|
|
2164
2312
|
});
|
|
2165
2313
|
|
|
2166
2314
|
// src/registry.ts
|
|
2167
|
-
import * as
|
|
2168
|
-
import * as
|
|
2315
|
+
import * as fs8 from "fs";
|
|
2316
|
+
import * as path8 from "path";
|
|
2169
2317
|
function getExecutablesRoot() {
|
|
2170
|
-
const here =
|
|
2318
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
2171
2319
|
const candidates = [
|
|
2172
|
-
|
|
2320
|
+
path8.join(here, "executables"),
|
|
2173
2321
|
// dev: src/
|
|
2174
|
-
|
|
2322
|
+
path8.join(here, "..", "executables"),
|
|
2175
2323
|
// built: dist/bin → dist/executables
|
|
2176
|
-
|
|
2324
|
+
path8.join(here, "..", "src", "executables")
|
|
2177
2325
|
// fallback
|
|
2178
2326
|
];
|
|
2179
2327
|
for (const c of candidates) {
|
|
2180
|
-
if (
|
|
2328
|
+
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
2181
2329
|
}
|
|
2182
2330
|
return candidates[0];
|
|
2183
2331
|
}
|
|
2184
2332
|
function getProjectExecutablesRoot() {
|
|
2185
|
-
return
|
|
2333
|
+
return path8.join(process.cwd(), ".kody", "executables");
|
|
2186
2334
|
}
|
|
2187
2335
|
function getProjectDutiesRoot() {
|
|
2188
|
-
return
|
|
2336
|
+
return path8.join(process.cwd(), ".kody", "duties");
|
|
2337
|
+
}
|
|
2338
|
+
function getCompanyStoreExecutablesRoot() {
|
|
2339
|
+
return getCompanyStoreAssetRoot("executables");
|
|
2340
|
+
}
|
|
2341
|
+
function getCompanyStoreDutiesRoot() {
|
|
2342
|
+
return getCompanyStoreAssetRoot("duties");
|
|
2189
2343
|
}
|
|
2190
2344
|
function getBuiltinJobsRoot() {
|
|
2191
|
-
const here =
|
|
2345
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
2192
2346
|
const candidates = [
|
|
2193
|
-
|
|
2347
|
+
path8.join(here, "jobs"),
|
|
2194
2348
|
// dev: src/
|
|
2195
|
-
|
|
2349
|
+
path8.join(here, "..", "jobs"),
|
|
2196
2350
|
// built: dist/bin → dist/jobs
|
|
2197
|
-
|
|
2351
|
+
path8.join(here, "..", "src", "jobs")
|
|
2198
2352
|
// fallback
|
|
2199
2353
|
];
|
|
2200
2354
|
for (const c of candidates) {
|
|
2201
|
-
if (
|
|
2355
|
+
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
2202
2356
|
}
|
|
2203
2357
|
return candidates[0];
|
|
2204
2358
|
}
|
|
2205
2359
|
function getBuiltinDutiesRoot() {
|
|
2206
|
-
const here =
|
|
2360
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
2207
2361
|
const candidates = [
|
|
2208
|
-
|
|
2362
|
+
path8.join(here, "duties"),
|
|
2209
2363
|
// dev: src/
|
|
2210
|
-
|
|
2364
|
+
path8.join(here, "..", "duties"),
|
|
2211
2365
|
// built: dist/bin → dist/duties
|
|
2212
|
-
|
|
2366
|
+
path8.join(here, "..", "src", "duties")
|
|
2213
2367
|
// fallback
|
|
2214
2368
|
];
|
|
2215
2369
|
for (const c of candidates) {
|
|
2216
|
-
if (
|
|
2370
|
+
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
2217
2371
|
}
|
|
2218
2372
|
return candidates[0];
|
|
2219
2373
|
}
|
|
2220
2374
|
function listBuiltinJobs(root = getBuiltinJobsRoot()) {
|
|
2221
|
-
if (!
|
|
2375
|
+
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
2222
2376
|
const out = [];
|
|
2223
|
-
for (const ent of
|
|
2377
|
+
for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
|
|
2224
2378
|
if (ent.name.startsWith("_") || ent.name.startsWith(".")) continue;
|
|
2225
|
-
const full =
|
|
2379
|
+
const full = path8.join(root, ent.name);
|
|
2226
2380
|
if (ent.isDirectory()) {
|
|
2227
|
-
const profilePath =
|
|
2228
|
-
const bodyPath =
|
|
2229
|
-
if (!
|
|
2230
|
-
if (!
|
|
2381
|
+
const profilePath = path8.join(full, DUTY_PROFILE_FILE);
|
|
2382
|
+
const bodyPath = path8.join(full, DUTY_BODY_FILE);
|
|
2383
|
+
if (!fs8.existsSync(profilePath) || !fs8.statSync(profilePath).isFile()) continue;
|
|
2384
|
+
if (!fs8.existsSync(bodyPath) || !fs8.statSync(bodyPath).isFile()) continue;
|
|
2231
2385
|
out.push({ slug: ent.name, dir: full, profilePath, bodyPath });
|
|
2232
2386
|
}
|
|
2233
2387
|
}
|
|
@@ -2235,20 +2389,25 @@ function listBuiltinJobs(root = getBuiltinJobsRoot()) {
|
|
|
2235
2389
|
return out;
|
|
2236
2390
|
}
|
|
2237
2391
|
function getExecutableRoots() {
|
|
2238
|
-
|
|
2392
|
+
const storeRoot = getCompanyStoreExecutablesRoot();
|
|
2393
|
+
return [getProjectExecutablesRoot(), ...storeRoot ? [storeRoot] : [], getExecutablesRoot()];
|
|
2394
|
+
}
|
|
2395
|
+
function getDutyRoots(projectDutiesRoot = getProjectDutiesRoot()) {
|
|
2396
|
+
const storeRoot = getCompanyStoreDutiesRoot();
|
|
2397
|
+
return [projectDutiesRoot, ...storeRoot ? [storeRoot] : [], getBuiltinDutiesRoot()];
|
|
2239
2398
|
}
|
|
2240
2399
|
function listExecutables(roots = getExecutableRoots()) {
|
|
2241
2400
|
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
2242
2401
|
const seen = /* @__PURE__ */ new Set();
|
|
2243
2402
|
const out = [];
|
|
2244
2403
|
for (const root of rootList) {
|
|
2245
|
-
if (!
|
|
2246
|
-
const entries =
|
|
2404
|
+
if (!fs8.existsSync(root)) continue;
|
|
2405
|
+
const entries = fs8.readdirSync(root, { withFileTypes: true });
|
|
2247
2406
|
for (const ent of entries) {
|
|
2248
2407
|
if (!ent.isDirectory()) continue;
|
|
2249
2408
|
if (seen.has(ent.name)) continue;
|
|
2250
|
-
const profilePath =
|
|
2251
|
-
if (
|
|
2409
|
+
const profilePath = path8.join(root, ent.name, DUTY_PROFILE_FILE);
|
|
2410
|
+
if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
|
|
2252
2411
|
out.push({ name: ent.name, profilePath });
|
|
2253
2412
|
seen.add(ent.name);
|
|
2254
2413
|
}
|
|
@@ -2260,8 +2419,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
|
|
|
2260
2419
|
if (!isSafeName(name)) return null;
|
|
2261
2420
|
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
2262
2421
|
for (const root of rootList) {
|
|
2263
|
-
const profilePath =
|
|
2264
|
-
if (
|
|
2422
|
+
const profilePath = path8.join(root, name, "profile.json");
|
|
2423
|
+
if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
|
|
2265
2424
|
return profilePath;
|
|
2266
2425
|
}
|
|
2267
2426
|
}
|
|
@@ -2276,8 +2435,14 @@ function listDutyActions(projectDutiesRoot = getProjectDutiesRoot()) {
|
|
|
2276
2435
|
seen.add(action.action);
|
|
2277
2436
|
out.push(action);
|
|
2278
2437
|
};
|
|
2279
|
-
|
|
2280
|
-
for (const action of
|
|
2438
|
+
const roots = getDutyRoots(projectDutiesRoot);
|
|
2439
|
+
for (const action of listFolderDutyActions(roots[0], "project-folder")) add(action);
|
|
2440
|
+
if (roots.length === 3) {
|
|
2441
|
+
for (const action of listFolderDutyActions(roots[1], "company-store")) add(action);
|
|
2442
|
+
for (const action of listBuiltinDutyActions(roots[2])) add(action);
|
|
2443
|
+
} else {
|
|
2444
|
+
for (const action of listBuiltinDutyActions(roots[1])) add(action);
|
|
2445
|
+
}
|
|
2281
2446
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2282
2447
|
}
|
|
2283
2448
|
function resolveDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
|
|
@@ -2287,11 +2452,19 @@ function resolveDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
|
|
|
2287
2452
|
function hasDutyAction(action, projectDutiesRoot = getProjectDutiesRoot()) {
|
|
2288
2453
|
return resolveDutyAction(action, projectDutiesRoot) !== null;
|
|
2289
2454
|
}
|
|
2455
|
+
function resolveDutyFolder(slug, projectDutiesRoot = getProjectDutiesRoot()) {
|
|
2456
|
+
if (!isSafeName(slug)) return null;
|
|
2457
|
+
for (const root of getDutyRoots(projectDutiesRoot)) {
|
|
2458
|
+
const duty = readDutyFolder(root, slug);
|
|
2459
|
+
if (duty) return duty;
|
|
2460
|
+
}
|
|
2461
|
+
return null;
|
|
2462
|
+
}
|
|
2290
2463
|
function isSafeName(name) {
|
|
2291
2464
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
2292
2465
|
}
|
|
2293
|
-
function
|
|
2294
|
-
if (!
|
|
2466
|
+
function listFolderDutyActions(root, source) {
|
|
2467
|
+
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
2295
2468
|
const out = [];
|
|
2296
2469
|
for (const slug of listDutyFolderSlugs(root)) {
|
|
2297
2470
|
if (!isSafeName(slug)) continue;
|
|
@@ -2304,7 +2477,7 @@ function listProjectFolderDutyActions(root) {
|
|
|
2304
2477
|
duty: slug,
|
|
2305
2478
|
executable,
|
|
2306
2479
|
cliArgs: duty.config.executable ? {} : { duty: slug },
|
|
2307
|
-
source
|
|
2480
|
+
source,
|
|
2308
2481
|
describe: duty.config.describe ?? duty.title,
|
|
2309
2482
|
profilePath: duty.profilePath,
|
|
2310
2483
|
bodyPath: duty.bodyPath
|
|
@@ -2313,7 +2486,7 @@ function listProjectFolderDutyActions(root) {
|
|
|
2313
2486
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2314
2487
|
}
|
|
2315
2488
|
function listBuiltinDutyActions(root = getBuiltinDutiesRoot()) {
|
|
2316
|
-
if (!
|
|
2489
|
+
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
2317
2490
|
const out = [];
|
|
2318
2491
|
for (const slug of listDutyFolderSlugs(root)) {
|
|
2319
2492
|
if (!isSafeName(slug)) continue;
|
|
@@ -2338,7 +2511,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
|
|
|
2338
2511
|
const profilePath = resolveExecutable(name, roots);
|
|
2339
2512
|
if (!profilePath) return null;
|
|
2340
2513
|
try {
|
|
2341
|
-
const raw = JSON.parse(
|
|
2514
|
+
const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
|
|
2342
2515
|
if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
|
|
2343
2516
|
return raw.inputs;
|
|
2344
2517
|
} catch {
|
|
@@ -2373,26 +2546,27 @@ function parseGenericFlags(argv) {
|
|
|
2373
2546
|
var init_registry = __esm({
|
|
2374
2547
|
"src/registry.ts"() {
|
|
2375
2548
|
"use strict";
|
|
2549
|
+
init_companyStore();
|
|
2376
2550
|
init_dutyFolders();
|
|
2377
2551
|
}
|
|
2378
2552
|
});
|
|
2379
2553
|
|
|
2380
2554
|
// src/task-artifacts.ts
|
|
2381
|
-
import
|
|
2382
|
-
import
|
|
2555
|
+
import fs9 from "fs";
|
|
2556
|
+
import path9 from "path";
|
|
2383
2557
|
function prepareTaskArtifactsDir(cwd, taskId) {
|
|
2384
2558
|
const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
2385
|
-
const relDir =
|
|
2386
|
-
const absDir =
|
|
2387
|
-
|
|
2559
|
+
const relDir = path9.join(".kody", "tasks", safeId);
|
|
2560
|
+
const absDir = path9.join(cwd, relDir);
|
|
2561
|
+
fs9.mkdirSync(absDir, { recursive: true });
|
|
2388
2562
|
return { taskId: safeId, absDir, relDir };
|
|
2389
2563
|
}
|
|
2390
2564
|
function verifyTaskArtifacts(absDir) {
|
|
2391
2565
|
const missing = [];
|
|
2392
2566
|
for (const name of TASK_ARTIFACT_FILES) {
|
|
2393
|
-
const full =
|
|
2567
|
+
const full = path9.join(absDir, name);
|
|
2394
2568
|
try {
|
|
2395
|
-
const stat =
|
|
2569
|
+
const stat = fs9.statSync(full);
|
|
2396
2570
|
if (!stat.isFile() || stat.size === 0) missing.push(name);
|
|
2397
2571
|
} catch {
|
|
2398
2572
|
missing.push(name);
|
|
@@ -2467,8 +2641,8 @@ var init_task_artifacts = __esm({
|
|
|
2467
2641
|
});
|
|
2468
2642
|
|
|
2469
2643
|
// src/gha.ts
|
|
2470
|
-
import { execFileSync as
|
|
2471
|
-
import * as
|
|
2644
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
2645
|
+
import * as fs15 from "fs";
|
|
2472
2646
|
function getRunUrl() {
|
|
2473
2647
|
const server = process.env.GITHUB_SERVER_URL;
|
|
2474
2648
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -2479,10 +2653,10 @@ function getRunUrl() {
|
|
|
2479
2653
|
function reactToTriggerComment(cwd) {
|
|
2480
2654
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
2481
2655
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2482
|
-
if (!eventPath || !
|
|
2656
|
+
if (!eventPath || !fs15.existsSync(eventPath)) return;
|
|
2483
2657
|
let event = null;
|
|
2484
2658
|
try {
|
|
2485
|
-
event = JSON.parse(
|
|
2659
|
+
event = JSON.parse(fs15.readFileSync(eventPath, "utf-8"));
|
|
2486
2660
|
} catch {
|
|
2487
2661
|
return;
|
|
2488
2662
|
}
|
|
@@ -2510,7 +2684,7 @@ function reactToTriggerComment(cwd) {
|
|
|
2510
2684
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2511
2685
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
2512
2686
|
try {
|
|
2513
|
-
|
|
2687
|
+
execFileSync3("gh", args, opts);
|
|
2514
2688
|
return;
|
|
2515
2689
|
} catch (err) {
|
|
2516
2690
|
lastErr = err;
|
|
@@ -2523,7 +2697,7 @@ function reactToTriggerComment(cwd) {
|
|
|
2523
2697
|
}
|
|
2524
2698
|
function sleepMs(ms) {
|
|
2525
2699
|
try {
|
|
2526
|
-
|
|
2700
|
+
execFileSync3("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
2527
2701
|
} catch {
|
|
2528
2702
|
}
|
|
2529
2703
|
}
|
|
@@ -2693,31 +2867,31 @@ var init_lifecycles = __esm({
|
|
|
2693
2867
|
});
|
|
2694
2868
|
|
|
2695
2869
|
// src/scripts/buildSyntheticPlugin.ts
|
|
2696
|
-
import * as
|
|
2697
|
-
import * as
|
|
2698
|
-
import * as
|
|
2870
|
+
import * as fs16 from "fs";
|
|
2871
|
+
import * as os3 from "os";
|
|
2872
|
+
import * as path14 from "path";
|
|
2699
2873
|
function getPluginsCatalogRoot() {
|
|
2700
|
-
const here =
|
|
2874
|
+
const here = path14.dirname(new URL(import.meta.url).pathname);
|
|
2701
2875
|
const candidates = [
|
|
2702
|
-
|
|
2876
|
+
path14.join(here, "..", "plugins"),
|
|
2703
2877
|
// dev: src/scripts → src/plugins
|
|
2704
|
-
|
|
2878
|
+
path14.join(here, "..", "..", "plugins"),
|
|
2705
2879
|
// built: dist/scripts → dist/plugins
|
|
2706
|
-
|
|
2880
|
+
path14.join(here, "..", "..", "src", "plugins")
|
|
2707
2881
|
// fallback
|
|
2708
2882
|
];
|
|
2709
2883
|
for (const c of candidates) {
|
|
2710
|
-
if (
|
|
2884
|
+
if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
|
|
2711
2885
|
}
|
|
2712
2886
|
return candidates[0];
|
|
2713
2887
|
}
|
|
2714
2888
|
function copyDir(src, dst) {
|
|
2715
|
-
|
|
2716
|
-
for (const ent of
|
|
2717
|
-
const s =
|
|
2718
|
-
const d =
|
|
2889
|
+
fs16.mkdirSync(dst, { recursive: true });
|
|
2890
|
+
for (const ent of fs16.readdirSync(src, { withFileTypes: true })) {
|
|
2891
|
+
const s = path14.join(src, ent.name);
|
|
2892
|
+
const d = path14.join(dst, ent.name);
|
|
2719
2893
|
if (ent.isDirectory()) copyDir(s, d);
|
|
2720
|
-
else if (ent.isFile())
|
|
2894
|
+
else if (ent.isFile()) fs16.copyFileSync(s, d);
|
|
2721
2895
|
}
|
|
2722
2896
|
}
|
|
2723
2897
|
var buildSyntheticPlugin;
|
|
@@ -2730,45 +2904,45 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2730
2904
|
if (!needsSynthetic) return;
|
|
2731
2905
|
const catalog = getPluginsCatalogRoot();
|
|
2732
2906
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2733
|
-
const root =
|
|
2734
|
-
|
|
2907
|
+
const root = path14.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
2908
|
+
fs16.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
|
|
2735
2909
|
const resolvePart = (bucket, entry) => {
|
|
2736
|
-
const local =
|
|
2737
|
-
if (
|
|
2738
|
-
const central =
|
|
2739
|
-
if (
|
|
2910
|
+
const local = path14.join(profile.dir, bucket, entry);
|
|
2911
|
+
if (fs16.existsSync(local)) return local;
|
|
2912
|
+
const central = path14.join(catalog, bucket, entry);
|
|
2913
|
+
if (fs16.existsSync(central)) return central;
|
|
2740
2914
|
throw new Error(
|
|
2741
2915
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
2742
2916
|
);
|
|
2743
2917
|
};
|
|
2744
2918
|
if (cc.skills.length > 0) {
|
|
2745
|
-
const dst =
|
|
2746
|
-
|
|
2919
|
+
const dst = path14.join(root, "skills");
|
|
2920
|
+
fs16.mkdirSync(dst, { recursive: true });
|
|
2747
2921
|
for (const name of cc.skills) {
|
|
2748
|
-
copyDir(resolvePart("skills", name),
|
|
2922
|
+
copyDir(resolvePart("skills", name), path14.join(dst, name));
|
|
2749
2923
|
}
|
|
2750
2924
|
}
|
|
2751
2925
|
if (cc.commands.length > 0) {
|
|
2752
|
-
const dst =
|
|
2753
|
-
|
|
2926
|
+
const dst = path14.join(root, "commands");
|
|
2927
|
+
fs16.mkdirSync(dst, { recursive: true });
|
|
2754
2928
|
for (const name of cc.commands) {
|
|
2755
|
-
|
|
2929
|
+
fs16.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
|
|
2756
2930
|
}
|
|
2757
2931
|
}
|
|
2758
2932
|
if (cc.hooks.length > 0) {
|
|
2759
|
-
const dst =
|
|
2760
|
-
|
|
2933
|
+
const dst = path14.join(root, "hooks");
|
|
2934
|
+
fs16.mkdirSync(dst, { recursive: true });
|
|
2761
2935
|
const merged = { hooks: {} };
|
|
2762
2936
|
for (const name of cc.hooks) {
|
|
2763
2937
|
const src = resolvePart("hooks", `${name}.json`);
|
|
2764
|
-
const parsed = JSON.parse(
|
|
2938
|
+
const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
|
|
2765
2939
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
2766
2940
|
if (!Array.isArray(entries)) continue;
|
|
2767
2941
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
2768
2942
|
merged.hooks[event].push(...entries);
|
|
2769
2943
|
}
|
|
2770
2944
|
}
|
|
2771
|
-
|
|
2945
|
+
fs16.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
2772
2946
|
`);
|
|
2773
2947
|
}
|
|
2774
2948
|
const manifest = {
|
|
@@ -2778,7 +2952,7 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2778
2952
|
};
|
|
2779
2953
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
2780
2954
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
2781
|
-
|
|
2955
|
+
fs16.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
2782
2956
|
`);
|
|
2783
2957
|
ctx.data.syntheticPluginPath = root;
|
|
2784
2958
|
};
|
|
@@ -2786,8 +2960,8 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2786
2960
|
});
|
|
2787
2961
|
|
|
2788
2962
|
// src/subagents.ts
|
|
2789
|
-
import * as
|
|
2790
|
-
import * as
|
|
2963
|
+
import * as fs17 from "fs";
|
|
2964
|
+
import * as path15 from "path";
|
|
2791
2965
|
function splitFrontmatter(raw) {
|
|
2792
2966
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
2793
2967
|
if (!match) return { fm: {}, body: raw.trim() };
|
|
@@ -2800,10 +2974,10 @@ function splitFrontmatter(raw) {
|
|
|
2800
2974
|
return { fm, body: (match[2] ?? "").trim() };
|
|
2801
2975
|
}
|
|
2802
2976
|
function resolveAgentFile(profileDir, name) {
|
|
2803
|
-
const local =
|
|
2804
|
-
if (
|
|
2805
|
-
const central =
|
|
2806
|
-
if (
|
|
2977
|
+
const local = path15.join(profileDir, "agents", `${name}.md`);
|
|
2978
|
+
if (fs17.existsSync(local)) return local;
|
|
2979
|
+
const central = path15.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
2980
|
+
if (fs17.existsSync(central)) return central;
|
|
2807
2981
|
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
2808
2982
|
}
|
|
2809
2983
|
function captureSubagentTemplates(profile) {
|
|
@@ -2812,7 +2986,7 @@ function captureSubagentTemplates(profile) {
|
|
|
2812
2986
|
const out = {};
|
|
2813
2987
|
for (const name of names) {
|
|
2814
2988
|
try {
|
|
2815
|
-
out[name] =
|
|
2989
|
+
out[name] = fs17.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
2816
2990
|
} catch {
|
|
2817
2991
|
}
|
|
2818
2992
|
}
|
|
@@ -2823,7 +2997,7 @@ function loadSubagents(profile) {
|
|
|
2823
2997
|
if (!names || names.length === 0) return void 0;
|
|
2824
2998
|
const agents = {};
|
|
2825
2999
|
for (const name of names) {
|
|
2826
|
-
const raw = profile.subagentTemplates?.[name] ??
|
|
3000
|
+
const raw = profile.subagentTemplates?.[name] ?? fs17.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
2827
3001
|
const { fm, body } = splitFrontmatter(raw);
|
|
2828
3002
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
2829
3003
|
const def = {
|
|
@@ -2847,15 +3021,15 @@ var init_subagents = __esm({
|
|
|
2847
3021
|
});
|
|
2848
3022
|
|
|
2849
3023
|
// src/profile.ts
|
|
2850
|
-
import * as
|
|
2851
|
-
import * as
|
|
3024
|
+
import * as fs18 from "fs";
|
|
3025
|
+
import * as path16 from "path";
|
|
2852
3026
|
function loadProfile(profilePath) {
|
|
2853
|
-
if (!
|
|
3027
|
+
if (!fs18.existsSync(profilePath)) {
|
|
2854
3028
|
throw new ProfileError(profilePath, "file not found");
|
|
2855
3029
|
}
|
|
2856
3030
|
let raw;
|
|
2857
3031
|
try {
|
|
2858
|
-
raw = JSON.parse(
|
|
3032
|
+
raw = JSON.parse(fs18.readFileSync(profilePath, "utf-8"));
|
|
2859
3033
|
} catch (err) {
|
|
2860
3034
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
2861
3035
|
}
|
|
@@ -2866,7 +3040,7 @@ function loadProfile(profilePath) {
|
|
|
2866
3040
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
2867
3041
|
if (unknownKeys.length > 0) {
|
|
2868
3042
|
process.stderr.write(
|
|
2869
|
-
`[kody profile] ${
|
|
3043
|
+
`[kody profile] ${path16.basename(path16.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
2870
3044
|
`
|
|
2871
3045
|
);
|
|
2872
3046
|
}
|
|
@@ -2955,8 +3129,8 @@ function loadProfile(profilePath) {
|
|
|
2955
3129
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
2956
3130
|
// flip to true after end-to-end verification.
|
|
2957
3131
|
preloadContext: r.preloadContext === true,
|
|
2958
|
-
dir:
|
|
2959
|
-
promptTemplates: readPromptTemplates(
|
|
3132
|
+
dir: path16.dirname(profilePath),
|
|
3133
|
+
promptTemplates: readPromptTemplates(path16.dirname(profilePath))
|
|
2960
3134
|
};
|
|
2961
3135
|
if (lifecycle) {
|
|
2962
3136
|
applyLifecycle(profile, profilePath);
|
|
@@ -2988,16 +3162,16 @@ function readPromptTemplates(dir) {
|
|
|
2988
3162
|
const out = {};
|
|
2989
3163
|
const read = (p) => {
|
|
2990
3164
|
try {
|
|
2991
|
-
out[p] =
|
|
3165
|
+
out[p] = fs18.readFileSync(p, "utf-8");
|
|
2992
3166
|
} catch {
|
|
2993
3167
|
}
|
|
2994
3168
|
};
|
|
2995
|
-
read(
|
|
2996
|
-
read(
|
|
3169
|
+
read(path16.join(dir, "prompt.md"));
|
|
3170
|
+
read(path16.join(dir, "duty.md"));
|
|
2997
3171
|
try {
|
|
2998
|
-
const promptsDir =
|
|
2999
|
-
for (const ent of
|
|
3000
|
-
if (ent.endsWith(".md")) read(
|
|
3172
|
+
const promptsDir = path16.join(dir, "prompts");
|
|
3173
|
+
for (const ent of fs18.readdirSync(promptsDir)) {
|
|
3174
|
+
if (ent.endsWith(".md")) read(path16.join(promptsDir, ent));
|
|
3001
3175
|
}
|
|
3002
3176
|
} catch {
|
|
3003
3177
|
}
|
|
@@ -3072,6 +3246,7 @@ function parseClaudeCode(p, raw) {
|
|
|
3072
3246
|
permissionMode,
|
|
3073
3247
|
maxTurns: typeof r.maxTurns === "number" ? r.maxTurns : null,
|
|
3074
3248
|
maxThinkingTokens: typeof r.maxThinkingTokens === "number" ? r.maxThinkingTokens : null,
|
|
3249
|
+
reasoningEffort: typeof r.reasoningEffort === "string" ? parseReasoningEffort(r.reasoningEffort) : null,
|
|
3075
3250
|
maxTurnTimeoutSec: typeof r.maxTurnTimeoutSec === "number" ? r.maxTurnTimeoutSec : null,
|
|
3076
3251
|
systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
|
|
3077
3252
|
cacheable: r.cacheable === true,
|
|
@@ -3255,6 +3430,7 @@ var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHIL
|
|
|
3255
3430
|
var init_profile = __esm({
|
|
3256
3431
|
"src/profile.ts"() {
|
|
3257
3432
|
"use strict";
|
|
3433
|
+
init_config();
|
|
3258
3434
|
init_dutyMcp();
|
|
3259
3435
|
init_lifecycles();
|
|
3260
3436
|
init_profile_error();
|
|
@@ -3304,7 +3480,7 @@ var init_profile = __esm({
|
|
|
3304
3480
|
});
|
|
3305
3481
|
|
|
3306
3482
|
// src/state.ts
|
|
3307
|
-
import { execFileSync as
|
|
3483
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
3308
3484
|
function emptyState() {
|
|
3309
3485
|
return {
|
|
3310
3486
|
schemaVersion: 1,
|
|
@@ -3327,7 +3503,7 @@ function ghToken2() {
|
|
|
3327
3503
|
function gh2(args, input, cwd) {
|
|
3328
3504
|
const token = ghToken2();
|
|
3329
3505
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
3330
|
-
return
|
|
3506
|
+
return execFileSync4("gh", args, {
|
|
3331
3507
|
encoding: "utf-8",
|
|
3332
3508
|
timeout: API_TIMEOUT_MS2,
|
|
3333
3509
|
cwd,
|
|
@@ -3674,16 +3850,16 @@ var init_state = __esm({
|
|
|
3674
3850
|
});
|
|
3675
3851
|
|
|
3676
3852
|
// src/prompt.ts
|
|
3677
|
-
import * as
|
|
3678
|
-
import * as
|
|
3853
|
+
import * as fs19 from "fs";
|
|
3854
|
+
import * as path17 from "path";
|
|
3679
3855
|
function loadProjectConventions(projectDir) {
|
|
3680
3856
|
const out = [];
|
|
3681
3857
|
for (const rel of CONVENTION_FILES) {
|
|
3682
|
-
const abs =
|
|
3683
|
-
if (!
|
|
3858
|
+
const abs = path17.join(projectDir, rel);
|
|
3859
|
+
if (!fs19.existsSync(abs)) continue;
|
|
3684
3860
|
let content;
|
|
3685
3861
|
try {
|
|
3686
|
-
content =
|
|
3862
|
+
content = fs19.readFileSync(abs, "utf-8");
|
|
3687
3863
|
} catch {
|
|
3688
3864
|
continue;
|
|
3689
3865
|
}
|
|
@@ -3918,28 +4094,28 @@ var loadMemoryContext_exports = {};
|
|
|
3918
4094
|
__export(loadMemoryContext_exports, {
|
|
3919
4095
|
loadMemoryContext: () => loadMemoryContext
|
|
3920
4096
|
});
|
|
3921
|
-
import * as
|
|
3922
|
-
import * as
|
|
4097
|
+
import * as fs20 from "fs";
|
|
4098
|
+
import * as path18 from "path";
|
|
3923
4099
|
function collectPages(memoryAbs) {
|
|
3924
4100
|
const out = [];
|
|
3925
4101
|
walkMd(memoryAbs, (file) => {
|
|
3926
4102
|
let stat;
|
|
3927
4103
|
try {
|
|
3928
|
-
stat =
|
|
4104
|
+
stat = fs20.statSync(file);
|
|
3929
4105
|
} catch {
|
|
3930
4106
|
return;
|
|
3931
4107
|
}
|
|
3932
4108
|
let raw;
|
|
3933
4109
|
try {
|
|
3934
|
-
raw =
|
|
4110
|
+
raw = fs20.readFileSync(file, "utf-8");
|
|
3935
4111
|
} catch {
|
|
3936
4112
|
return;
|
|
3937
4113
|
}
|
|
3938
4114
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
3939
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
4115
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path18.basename(file, ".md");
|
|
3940
4116
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
3941
4117
|
out.push({
|
|
3942
|
-
relPath:
|
|
4118
|
+
relPath: path18.relative(memoryAbs, file),
|
|
3943
4119
|
title,
|
|
3944
4120
|
updated,
|
|
3945
4121
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
|
|
@@ -4007,16 +4183,16 @@ function walkMd(root, visit) {
|
|
|
4007
4183
|
const dir = stack.pop();
|
|
4008
4184
|
let names;
|
|
4009
4185
|
try {
|
|
4010
|
-
names =
|
|
4186
|
+
names = fs20.readdirSync(dir);
|
|
4011
4187
|
} catch {
|
|
4012
4188
|
continue;
|
|
4013
4189
|
}
|
|
4014
4190
|
for (const name of names) {
|
|
4015
4191
|
if (name.startsWith(".")) continue;
|
|
4016
|
-
const full =
|
|
4192
|
+
const full = path18.join(dir, name);
|
|
4017
4193
|
let stat;
|
|
4018
4194
|
try {
|
|
4019
|
-
stat =
|
|
4195
|
+
stat = fs20.statSync(full);
|
|
4020
4196
|
} catch {
|
|
4021
4197
|
continue;
|
|
4022
4198
|
}
|
|
@@ -4039,8 +4215,8 @@ var init_loadMemoryContext = __esm({
|
|
|
4039
4215
|
TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
|
|
4040
4216
|
loadMemoryContext = async (ctx) => {
|
|
4041
4217
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
4042
|
-
const memoryAbs =
|
|
4043
|
-
if (!
|
|
4218
|
+
const memoryAbs = path18.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
4219
|
+
if (!fs20.existsSync(memoryAbs)) {
|
|
4044
4220
|
ctx.data.memoryContext = "";
|
|
4045
4221
|
return;
|
|
4046
4222
|
}
|
|
@@ -4083,12 +4259,12 @@ var init_loadCoverageRules = __esm({
|
|
|
4083
4259
|
});
|
|
4084
4260
|
|
|
4085
4261
|
// src/container.ts
|
|
4086
|
-
import { execFileSync as
|
|
4087
|
-
import * as
|
|
4262
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
4263
|
+
import * as fs21 from "fs";
|
|
4088
4264
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
4089
4265
|
try {
|
|
4090
4266
|
const profilePath = resolveProfilePath(profileName);
|
|
4091
|
-
if (!
|
|
4267
|
+
if (!fs21.existsSync(profilePath)) return null;
|
|
4092
4268
|
return loadProfile(profilePath).inputs;
|
|
4093
4269
|
} catch {
|
|
4094
4270
|
return null;
|
|
@@ -4322,7 +4498,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
4322
4498
|
}
|
|
4323
4499
|
function resetWorkingTree(cwd) {
|
|
4324
4500
|
try {
|
|
4325
|
-
|
|
4501
|
+
execFileSync5("git", ["reset", "--hard", "HEAD"], {
|
|
4326
4502
|
cwd,
|
|
4327
4503
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4328
4504
|
timeout: 3e4
|
|
@@ -4421,6 +4597,91 @@ here \u2014 never substitute it for the truth.`;
|
|
|
4421
4597
|
}
|
|
4422
4598
|
});
|
|
4423
4599
|
|
|
4600
|
+
// src/dutyReport.ts
|
|
4601
|
+
function parseDutyReportsFromText(text) {
|
|
4602
|
+
const reports = [];
|
|
4603
|
+
for (const match of text.matchAll(REPORT_LINE)) {
|
|
4604
|
+
const raw = match[1]?.trim();
|
|
4605
|
+
if (!raw) continue;
|
|
4606
|
+
try {
|
|
4607
|
+
const parsed = parseDutyReport(JSON.parse(raw));
|
|
4608
|
+
if (parsed) reports.push(parsed);
|
|
4609
|
+
} catch {
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
return reports;
|
|
4613
|
+
}
|
|
4614
|
+
function parseDutyReport(raw) {
|
|
4615
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4616
|
+
const obj = raw;
|
|
4617
|
+
const target = parseTarget(obj.target);
|
|
4618
|
+
if (!target) return null;
|
|
4619
|
+
const evidence = parseBooleanRecord(obj.evidence);
|
|
4620
|
+
const facts = parseFacts(obj.facts);
|
|
4621
|
+
if (!evidence && !facts) return null;
|
|
4622
|
+
return {
|
|
4623
|
+
target,
|
|
4624
|
+
...evidence ? { evidence } : {},
|
|
4625
|
+
...facts ? { facts } : {}
|
|
4626
|
+
};
|
|
4627
|
+
}
|
|
4628
|
+
function applyDutyReportToGoalState(state, report) {
|
|
4629
|
+
const priorFacts = parseFacts(state.extra.facts) ?? {};
|
|
4630
|
+
const nextFacts = { ...priorFacts };
|
|
4631
|
+
for (const [key, value] of Object.entries(report.facts ?? {})) {
|
|
4632
|
+
if (CONTROL_FACT_KEYS.has(key)) continue;
|
|
4633
|
+
nextFacts[key] = value;
|
|
4634
|
+
}
|
|
4635
|
+
for (const [key, value] of Object.entries(report.evidence ?? {})) {
|
|
4636
|
+
nextFacts[key] = value;
|
|
4637
|
+
}
|
|
4638
|
+
const pending = nextFacts.pendingEvidence;
|
|
4639
|
+
if (typeof pending === "string" && Object.hasOwn(report.evidence ?? {}, pending)) {
|
|
4640
|
+
delete nextFacts.pendingEvidence;
|
|
4641
|
+
}
|
|
4642
|
+
return {
|
|
4643
|
+
...state,
|
|
4644
|
+
extra: {
|
|
4645
|
+
...state.extra,
|
|
4646
|
+
facts: nextFacts
|
|
4647
|
+
}
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
function parseTarget(raw) {
|
|
4651
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4652
|
+
const target = raw;
|
|
4653
|
+
if (target.type !== "goal" && target.type !== "task" && target.type !== "duty") return null;
|
|
4654
|
+
if (typeof target.id !== "string" || target.id.trim().length === 0) return null;
|
|
4655
|
+
return { type: target.type, id: target.id.trim() };
|
|
4656
|
+
}
|
|
4657
|
+
function parseBooleanRecord(raw) {
|
|
4658
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4659
|
+
const out = {};
|
|
4660
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4661
|
+
if (typeof key !== "string" || key.length === 0 || typeof value !== "boolean") return null;
|
|
4662
|
+
out[key] = value;
|
|
4663
|
+
}
|
|
4664
|
+
return out;
|
|
4665
|
+
}
|
|
4666
|
+
function parseFacts(raw) {
|
|
4667
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4668
|
+
const out = {};
|
|
4669
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4670
|
+
if (typeof key !== "string" || key.length === 0) return null;
|
|
4671
|
+
if (CONTROL_FACT_KEYS.has(key)) continue;
|
|
4672
|
+
out[key] = value;
|
|
4673
|
+
}
|
|
4674
|
+
return out;
|
|
4675
|
+
}
|
|
4676
|
+
var REPORT_LINE, CONTROL_FACT_KEYS;
|
|
4677
|
+
var init_dutyReport = __esm({
|
|
4678
|
+
"src/dutyReport.ts"() {
|
|
4679
|
+
"use strict";
|
|
4680
|
+
REPORT_LINE = /^KODY_DUTY_REPORT=(.+)$/gm;
|
|
4681
|
+
CONTROL_FACT_KEYS = /* @__PURE__ */ new Set(["blockers", "destination", "duties", "route", "stage", "state"]);
|
|
4682
|
+
}
|
|
4683
|
+
});
|
|
4684
|
+
|
|
4424
4685
|
// src/lifecycleLabels.ts
|
|
4425
4686
|
function groupOf(label) {
|
|
4426
4687
|
const idx = label.indexOf(":");
|
|
@@ -4549,10 +4810,10 @@ var init_lifecycleLabels = __esm({
|
|
|
4549
4810
|
});
|
|
4550
4811
|
|
|
4551
4812
|
// src/litellm.ts
|
|
4552
|
-
import { execFileSync as
|
|
4553
|
-
import * as
|
|
4554
|
-
import * as
|
|
4555
|
-
import * as
|
|
4813
|
+
import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
|
|
4814
|
+
import * as fs22 from "fs";
|
|
4815
|
+
import * as os4 from "os";
|
|
4816
|
+
import * as path19 from "path";
|
|
4556
4817
|
async function checkLitellmHealth(url) {
|
|
4557
4818
|
try {
|
|
4558
4819
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -4582,7 +4843,7 @@ function generateLitellmConfigYaml(model) {
|
|
|
4582
4843
|
}
|
|
4583
4844
|
function litellmImportable() {
|
|
4584
4845
|
try {
|
|
4585
|
-
|
|
4846
|
+
execFileSync6("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
|
|
4586
4847
|
return true;
|
|
4587
4848
|
} catch {
|
|
4588
4849
|
return false;
|
|
@@ -4590,7 +4851,7 @@ function litellmImportable() {
|
|
|
4590
4851
|
}
|
|
4591
4852
|
function locateLitellmScript() {
|
|
4592
4853
|
try {
|
|
4593
|
-
const out =
|
|
4854
|
+
const out = execFileSync6(
|
|
4594
4855
|
"python3",
|
|
4595
4856
|
[
|
|
4596
4857
|
"-c",
|
|
@@ -4605,7 +4866,7 @@ function locateLitellmScript() {
|
|
|
4605
4866
|
}
|
|
4606
4867
|
function resolveLitellmCommand() {
|
|
4607
4868
|
try {
|
|
4608
|
-
|
|
4869
|
+
execFileSync6("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
4609
4870
|
return "litellm";
|
|
4610
4871
|
} catch {
|
|
4611
4872
|
if (!litellmImportable()) {
|
|
@@ -4613,7 +4874,7 @@ function resolveLitellmCommand() {
|
|
|
4613
4874
|
let installed = false;
|
|
4614
4875
|
for (const pip of ["pip", "pip3"]) {
|
|
4615
4876
|
try {
|
|
4616
|
-
|
|
4877
|
+
execFileSync6(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
|
|
4617
4878
|
installed = true;
|
|
4618
4879
|
break;
|
|
4619
4880
|
} catch {
|
|
@@ -4637,13 +4898,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4637
4898
|
let child;
|
|
4638
4899
|
let logPath;
|
|
4639
4900
|
const spawnProxy = () => {
|
|
4640
|
-
const configPath =
|
|
4641
|
-
|
|
4901
|
+
const configPath = path19.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
|
|
4902
|
+
fs22.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
4642
4903
|
const args = ["--config", configPath, "--port", port];
|
|
4643
|
-
const nextLogPath =
|
|
4644
|
-
const outFd =
|
|
4904
|
+
const nextLogPath = path19.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
|
|
4905
|
+
const outFd = fs22.openSync(nextLogPath, "w");
|
|
4645
4906
|
child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
4646
|
-
|
|
4907
|
+
fs22.closeSync(outFd);
|
|
4647
4908
|
logPath = nextLogPath;
|
|
4648
4909
|
};
|
|
4649
4910
|
const waitForHealth = async () => {
|
|
@@ -4657,7 +4918,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4657
4918
|
const readLogTail = () => {
|
|
4658
4919
|
if (!logPath) return "";
|
|
4659
4920
|
try {
|
|
4660
|
-
return
|
|
4921
|
+
return fs22.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
4661
4922
|
} catch {
|
|
4662
4923
|
return "";
|
|
4663
4924
|
}
|
|
@@ -4709,10 +4970,10 @@ ${tail}`
|
|
|
4709
4970
|
return { url, kill: killChild, isHealthy, ensureHealthy };
|
|
4710
4971
|
}
|
|
4711
4972
|
function readDotenvApiKeys(projectDir) {
|
|
4712
|
-
const dotenvPath =
|
|
4713
|
-
if (!
|
|
4973
|
+
const dotenvPath = path19.join(projectDir, ".env");
|
|
4974
|
+
if (!fs22.existsSync(dotenvPath)) return {};
|
|
4714
4975
|
const result = {};
|
|
4715
|
-
for (const rawLine of
|
|
4976
|
+
for (const rawLine of fs22.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
4716
4977
|
const line = rawLine.trim();
|
|
4717
4978
|
if (!line || line.startsWith("#")) continue;
|
|
4718
4979
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -4744,14 +5005,14 @@ var init_litellm = __esm({
|
|
|
4744
5005
|
});
|
|
4745
5006
|
|
|
4746
5007
|
// src/pushWithRetry.ts
|
|
4747
|
-
import { execFileSync as
|
|
5008
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
4748
5009
|
function sleepSync(ms) {
|
|
4749
5010
|
if (ms <= 0) return;
|
|
4750
5011
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
4751
5012
|
}
|
|
4752
|
-
function
|
|
5013
|
+
function runGit2(args, cwd) {
|
|
4753
5014
|
try {
|
|
4754
|
-
const stdout =
|
|
5015
|
+
const stdout = execFileSync7("git", args, {
|
|
4755
5016
|
cwd,
|
|
4756
5017
|
encoding: "utf-8",
|
|
4757
5018
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -4767,7 +5028,7 @@ function runGit(args, cwd) {
|
|
|
4767
5028
|
}
|
|
4768
5029
|
function resolveBranch(cwd, explicit) {
|
|
4769
5030
|
if (explicit?.trim()) return explicit.trim();
|
|
4770
|
-
const r =
|
|
5031
|
+
const r = runGit2(["symbolic-ref", "--short", "HEAD"], cwd);
|
|
4771
5032
|
return r.ok ? r.stdout.trim() : "";
|
|
4772
5033
|
}
|
|
4773
5034
|
function pushWithRetry(opts = {}) {
|
|
@@ -4781,14 +5042,14 @@ function pushWithRetry(opts = {}) {
|
|
|
4781
5042
|
const pushArgs = opts.setUpstream ? ["push", "-u", "origin", `HEAD:${branch}`] : ["push", "origin", "HEAD"];
|
|
4782
5043
|
let lastError = "";
|
|
4783
5044
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
4784
|
-
const push =
|
|
5045
|
+
const push = runGit2(pushArgs, cwd);
|
|
4785
5046
|
if (push.ok) return { ok: true, attempts: attempt };
|
|
4786
5047
|
lastError = push.stderr || push.stdout || "(no error detail)";
|
|
4787
5048
|
if (!NON_FAST_FORWARD_RE.test(lastError)) {
|
|
4788
5049
|
return { ok: false, reason: `push failed (not retryable): ${lastError.trim().slice(-400)}`, attempts: attempt };
|
|
4789
5050
|
}
|
|
4790
5051
|
if (attempt === maxRetries) break;
|
|
4791
|
-
const fetch2 =
|
|
5052
|
+
const fetch2 = runGit2(["fetch", "origin", branch], cwd);
|
|
4792
5053
|
if (!fetch2.ok) {
|
|
4793
5054
|
return {
|
|
4794
5055
|
ok: false,
|
|
@@ -4796,9 +5057,9 @@ function pushWithRetry(opts = {}) {
|
|
|
4796
5057
|
attempts: attempt
|
|
4797
5058
|
};
|
|
4798
5059
|
}
|
|
4799
|
-
const rebase =
|
|
5060
|
+
const rebase = runGit2(["rebase", "--rebase-merges", `origin/${branch}`], cwd);
|
|
4800
5061
|
if (!rebase.ok) {
|
|
4801
|
-
|
|
5062
|
+
runGit2(["rebase", "--abort"], cwd);
|
|
4802
5063
|
return {
|
|
4803
5064
|
ok: false,
|
|
4804
5065
|
reason: `rebase onto origin/${branch} failed (conflict?): ${(rebase.stderr || rebase.stdout).trim().slice(-400)}`,
|
|
@@ -4826,12 +5087,12 @@ var init_pushWithRetry = __esm({
|
|
|
4826
5087
|
});
|
|
4827
5088
|
|
|
4828
5089
|
// src/commit.ts
|
|
4829
|
-
import { execFileSync as
|
|
4830
|
-
import * as
|
|
4831
|
-
import * as
|
|
5090
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
5091
|
+
import * as fs23 from "fs";
|
|
5092
|
+
import * as path20 from "path";
|
|
4832
5093
|
function git(args, cwd) {
|
|
4833
5094
|
try {
|
|
4834
|
-
return
|
|
5095
|
+
return execFileSync8("git", args, {
|
|
4835
5096
|
encoding: "utf-8",
|
|
4836
5097
|
timeout: 12e4,
|
|
4837
5098
|
cwd,
|
|
@@ -4866,18 +5127,18 @@ function ensureGitIdentity(cwd) {
|
|
|
4866
5127
|
}
|
|
4867
5128
|
function abortUnfinishedGitOps(cwd) {
|
|
4868
5129
|
const aborted = [];
|
|
4869
|
-
const gitDir =
|
|
4870
|
-
if (!
|
|
4871
|
-
if (
|
|
5130
|
+
const gitDir = path20.join(cwd ?? process.cwd(), ".git");
|
|
5131
|
+
if (!fs23.existsSync(gitDir)) return aborted;
|
|
5132
|
+
if (fs23.existsSync(path20.join(gitDir, "MERGE_HEAD"))) {
|
|
4872
5133
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
4873
5134
|
}
|
|
4874
|
-
if (
|
|
5135
|
+
if (fs23.existsSync(path20.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
4875
5136
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
4876
5137
|
}
|
|
4877
|
-
if (
|
|
5138
|
+
if (fs23.existsSync(path20.join(gitDir, "REVERT_HEAD"))) {
|
|
4878
5139
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
4879
5140
|
}
|
|
4880
|
-
if (
|
|
5141
|
+
if (fs23.existsSync(path20.join(gitDir, "rebase-merge")) || fs23.existsSync(path20.join(gitDir, "rebase-apply"))) {
|
|
4881
5142
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
4882
5143
|
}
|
|
4883
5144
|
try {
|
|
@@ -4898,7 +5159,7 @@ function isForbiddenPath(p) {
|
|
|
4898
5159
|
return false;
|
|
4899
5160
|
}
|
|
4900
5161
|
function listChangedFiles(cwd) {
|
|
4901
|
-
const raw =
|
|
5162
|
+
const raw = execFileSync8("git", ["status", "--porcelain=v1", "-z"], {
|
|
4902
5163
|
encoding: "utf-8",
|
|
4903
5164
|
cwd,
|
|
4904
5165
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -4910,7 +5171,7 @@ function listChangedFiles(cwd) {
|
|
|
4910
5171
|
}
|
|
4911
5172
|
function listFilesInCommit(ref = "HEAD", cwd) {
|
|
4912
5173
|
try {
|
|
4913
|
-
const raw =
|
|
5174
|
+
const raw = execFileSync8("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
|
|
4914
5175
|
encoding: "utf-8",
|
|
4915
5176
|
cwd,
|
|
4916
5177
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -4933,7 +5194,7 @@ function normalizeCommitMessage(raw) {
|
|
|
4933
5194
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
4934
5195
|
const allChanged = listChangedFiles(cwd);
|
|
4935
5196
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
4936
|
-
const mergeHeadExists =
|
|
5197
|
+
const mergeHeadExists = fs23.existsSync(path20.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
4937
5198
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
4938
5199
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
4939
5200
|
}
|
|
@@ -5089,10 +5350,10 @@ var init_saveTaskState = __esm({
|
|
|
5089
5350
|
});
|
|
5090
5351
|
|
|
5091
5352
|
// src/scripts/advanceFlow.ts
|
|
5092
|
-
import { execFileSync as
|
|
5353
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
5093
5354
|
function ghComment(issueNumber, body, cwd, label) {
|
|
5094
5355
|
try {
|
|
5095
|
-
|
|
5356
|
+
execFileSync9("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
5096
5357
|
timeout: API_TIMEOUT_MS3,
|
|
5097
5358
|
cwd,
|
|
5098
5359
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5168,6 +5429,291 @@ var init_advanceFlow = __esm({
|
|
|
5168
5429
|
}
|
|
5169
5430
|
});
|
|
5170
5431
|
|
|
5432
|
+
// src/goal/manager.ts
|
|
5433
|
+
function evidenceSatisfied(goal, evidence) {
|
|
5434
|
+
return goal.facts[evidence] === true;
|
|
5435
|
+
}
|
|
5436
|
+
function firstMissingEvidence(goal) {
|
|
5437
|
+
return goal.destination.evidence.find((evidence) => !evidenceSatisfied(goal, evidence));
|
|
5438
|
+
}
|
|
5439
|
+
function pushBlocker(goal, reason) {
|
|
5440
|
+
if (!goal.blockers.includes(reason)) goal.blockers.push(reason);
|
|
5441
|
+
}
|
|
5442
|
+
function isSimpleGoal(goal) {
|
|
5443
|
+
return goal.type === SIMPLE_GOAL_TYPE;
|
|
5444
|
+
}
|
|
5445
|
+
function applySimpleGoalTaskSummary(goal, summary) {
|
|
5446
|
+
goal.facts.simpleAttachedTaskCount = summary.total;
|
|
5447
|
+
goal.facts.simpleOpenTaskCount = summary.open;
|
|
5448
|
+
goal.facts[SIMPLE_GOAL_EVIDENCE] = summary.total > 0 && summary.open === 0;
|
|
5449
|
+
}
|
|
5450
|
+
function isFactReference(value) {
|
|
5451
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
5452
|
+
const record = value;
|
|
5453
|
+
return Object.keys(record).length === 1 && typeof record.fact === "string" && record.fact.length > 0;
|
|
5454
|
+
}
|
|
5455
|
+
function isCliArgValue(value) {
|
|
5456
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
5457
|
+
}
|
|
5458
|
+
function resolveRouteArgs(goal, step) {
|
|
5459
|
+
const cliArgs = {};
|
|
5460
|
+
for (const [name, value] of Object.entries(step.args ?? {})) {
|
|
5461
|
+
if (!isFactReference(value)) {
|
|
5462
|
+
cliArgs[name] = value;
|
|
5463
|
+
continue;
|
|
5464
|
+
}
|
|
5465
|
+
const factValue = goal.facts[value.fact];
|
|
5466
|
+
if (factValue === void 0 || factValue === null) {
|
|
5467
|
+
return { ok: false, reason: `route arg ${name} needs missing fact ${value.fact}` };
|
|
5468
|
+
}
|
|
5469
|
+
if (!isCliArgValue(factValue)) {
|
|
5470
|
+
return { ok: false, reason: `route arg ${name} needs scalar fact ${value.fact}` };
|
|
5471
|
+
}
|
|
5472
|
+
cliArgs[name] = factValue;
|
|
5473
|
+
}
|
|
5474
|
+
return { ok: true, cliArgs };
|
|
5475
|
+
}
|
|
5476
|
+
function planManagedGoalTick(goal) {
|
|
5477
|
+
goal.blockers = [];
|
|
5478
|
+
const missing = firstMissingEvidence(goal);
|
|
5479
|
+
if (!missing) {
|
|
5480
|
+
goal.stage = "done";
|
|
5481
|
+
delete goal.facts.pendingEvidence;
|
|
5482
|
+
return { kind: "done" };
|
|
5483
|
+
}
|
|
5484
|
+
const pending = goal.facts.pendingEvidence;
|
|
5485
|
+
if (pending === missing) {
|
|
5486
|
+
const stage = typeof goal.stage === "string" ? goal.stage : "waiting";
|
|
5487
|
+
return { kind: "wait", evidence: missing, stage, reason: `waiting for evidence: ${missing}` };
|
|
5488
|
+
}
|
|
5489
|
+
const step = goal.route.find((candidate) => candidate.evidence === missing);
|
|
5490
|
+
if (!step) {
|
|
5491
|
+
if (isSimpleGoal(goal) && missing === SIMPLE_GOAL_EVIDENCE) {
|
|
5492
|
+
goal.stage = "waiting";
|
|
5493
|
+
delete goal.facts.pendingEvidence;
|
|
5494
|
+
const total = typeof goal.facts.simpleAttachedTaskCount === "number" ? goal.facts.simpleAttachedTaskCount : 0;
|
|
5495
|
+
const open = typeof goal.facts.simpleOpenTaskCount === "number" ? goal.facts.simpleOpenTaskCount : 0;
|
|
5496
|
+
return {
|
|
5497
|
+
kind: "wait",
|
|
5498
|
+
evidence: missing,
|
|
5499
|
+
stage: "waiting",
|
|
5500
|
+
reason: total === 0 ? "waiting for labelled tasks" : `waiting for ${open} open labelled task(s)`
|
|
5501
|
+
};
|
|
5502
|
+
}
|
|
5503
|
+
const reason = `no route step for evidence: ${missing}`;
|
|
5504
|
+
goal.stage = "blocked";
|
|
5505
|
+
pushBlocker(goal, reason);
|
|
5506
|
+
return { kind: "blocked", evidence: missing, stage: "blocked", reason };
|
|
5507
|
+
}
|
|
5508
|
+
if (!goal.duties.includes(step.duty)) {
|
|
5509
|
+
const reason = `route duty ${step.duty} is not attached to this goal`;
|
|
5510
|
+
goal.stage = "blocked";
|
|
5511
|
+
pushBlocker(goal, reason);
|
|
5512
|
+
return { kind: "blocked", evidence: missing, stage: step.stage, reason };
|
|
5513
|
+
}
|
|
5514
|
+
const resolved = resolveRouteArgs(goal, step);
|
|
5515
|
+
if (!resolved.ok) {
|
|
5516
|
+
goal.stage = "blocked";
|
|
5517
|
+
pushBlocker(goal, resolved.reason);
|
|
5518
|
+
return { kind: "blocked", evidence: missing, stage: step.stage, reason: resolved.reason };
|
|
5519
|
+
}
|
|
5520
|
+
goal.stage = step.stage;
|
|
5521
|
+
goal.facts.pendingEvidence = missing;
|
|
5522
|
+
return {
|
|
5523
|
+
kind: "dispatch",
|
|
5524
|
+
evidence: missing,
|
|
5525
|
+
stage: step.stage,
|
|
5526
|
+
duty: step.duty,
|
|
5527
|
+
executable: step.executable,
|
|
5528
|
+
cliArgs: resolved.cliArgs
|
|
5529
|
+
};
|
|
5530
|
+
}
|
|
5531
|
+
function asStringArray(value) {
|
|
5532
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return null;
|
|
5533
|
+
return [...value];
|
|
5534
|
+
}
|
|
5535
|
+
function asRecord(value) {
|
|
5536
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
5537
|
+
return { ...value };
|
|
5538
|
+
}
|
|
5539
|
+
function asRoute(value) {
|
|
5540
|
+
if (!Array.isArray(value)) return null;
|
|
5541
|
+
const route = [];
|
|
5542
|
+
for (const item of value) {
|
|
5543
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
5544
|
+
const raw = item;
|
|
5545
|
+
if (typeof raw.evidence !== "string" || typeof raw.stage !== "string" || typeof raw.duty !== "string") {
|
|
5546
|
+
return null;
|
|
5547
|
+
}
|
|
5548
|
+
const args = raw.args === void 0 ? void 0 : asRecord(raw.args);
|
|
5549
|
+
if (raw.args !== void 0 && !args) return null;
|
|
5550
|
+
route.push({
|
|
5551
|
+
evidence: raw.evidence,
|
|
5552
|
+
stage: raw.stage,
|
|
5553
|
+
duty: raw.duty,
|
|
5554
|
+
executable: typeof raw.executable === "string" ? raw.executable : void 0,
|
|
5555
|
+
args: args ?? void 0
|
|
5556
|
+
});
|
|
5557
|
+
}
|
|
5558
|
+
return route;
|
|
5559
|
+
}
|
|
5560
|
+
function managedGoalFromState(state) {
|
|
5561
|
+
const extra = state.extra;
|
|
5562
|
+
const destination = asRecord(extra.destination);
|
|
5563
|
+
const evidence = asStringArray(destination?.evidence);
|
|
5564
|
+
const duties = asStringArray(extra.duties);
|
|
5565
|
+
const route = asRoute(extra.route);
|
|
5566
|
+
const facts = asRecord(extra.facts);
|
|
5567
|
+
const blockers = asStringArray(extra.blockers);
|
|
5568
|
+
if (typeof extra.type !== "string" || !destination || typeof destination.outcome !== "string" || !evidence || !duties || !route || !facts || !blockers) {
|
|
5569
|
+
return null;
|
|
5570
|
+
}
|
|
5571
|
+
return {
|
|
5572
|
+
type: extra.type,
|
|
5573
|
+
destination: { outcome: destination.outcome, evidence },
|
|
5574
|
+
duties,
|
|
5575
|
+
route,
|
|
5576
|
+
stage: typeof extra.stage === "string" ? extra.stage : void 0,
|
|
5577
|
+
facts,
|
|
5578
|
+
blockers
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5581
|
+
function writeManagedGoalToState(state, goal) {
|
|
5582
|
+
return {
|
|
5583
|
+
...state,
|
|
5584
|
+
extra: {
|
|
5585
|
+
...state.extra,
|
|
5586
|
+
type: goal.type,
|
|
5587
|
+
destination: goal.destination,
|
|
5588
|
+
duties: goal.duties,
|
|
5589
|
+
route: goal.route,
|
|
5590
|
+
stage: goal.stage,
|
|
5591
|
+
facts: goal.facts,
|
|
5592
|
+
blockers: goal.blockers
|
|
5593
|
+
}
|
|
5594
|
+
};
|
|
5595
|
+
}
|
|
5596
|
+
var SIMPLE_GOAL_TYPE, SIMPLE_GOAL_EVIDENCE;
|
|
5597
|
+
var init_manager = __esm({
|
|
5598
|
+
"src/goal/manager.ts"() {
|
|
5599
|
+
"use strict";
|
|
5600
|
+
SIMPLE_GOAL_TYPE = "simple";
|
|
5601
|
+
SIMPLE_GOAL_EVIDENCE = "labelledTasksComplete";
|
|
5602
|
+
}
|
|
5603
|
+
});
|
|
5604
|
+
|
|
5605
|
+
// src/goal/state.ts
|
|
5606
|
+
import * as fs24 from "fs";
|
|
5607
|
+
import * as path21 from "path";
|
|
5608
|
+
function parseGoalState(filePath, raw) {
|
|
5609
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5610
|
+
throw new GoalStateError(filePath, "must be JSON object");
|
|
5611
|
+
}
|
|
5612
|
+
const r = raw;
|
|
5613
|
+
const stateValue = r.state;
|
|
5614
|
+
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5615
|
+
throw new GoalStateError(
|
|
5616
|
+
filePath,
|
|
5617
|
+
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5618
|
+
);
|
|
5619
|
+
}
|
|
5620
|
+
const parsed = {
|
|
5621
|
+
state: stateValue,
|
|
5622
|
+
extra: {}
|
|
5623
|
+
};
|
|
5624
|
+
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
5625
|
+
const v = r[ts];
|
|
5626
|
+
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5627
|
+
}
|
|
5628
|
+
const known = /* @__PURE__ */ new Set(["state", "updatedAt", "createdAt", "startedAt"]);
|
|
5629
|
+
for (const [k, v] of Object.entries(r)) {
|
|
5630
|
+
if (!known.has(k)) parsed.extra[k] = v;
|
|
5631
|
+
}
|
|
5632
|
+
return parsed;
|
|
5633
|
+
}
|
|
5634
|
+
function serializeGoalState(s) {
|
|
5635
|
+
const obj = { ...s.extra, state: s.state };
|
|
5636
|
+
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5637
|
+
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5638
|
+
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5639
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
5640
|
+
`;
|
|
5641
|
+
}
|
|
5642
|
+
function nowIso() {
|
|
5643
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5644
|
+
}
|
|
5645
|
+
var VALID_STATES, GoalStateError;
|
|
5646
|
+
var init_state2 = __esm({
|
|
5647
|
+
"src/goal/state.ts"() {
|
|
5648
|
+
"use strict";
|
|
5649
|
+
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
5650
|
+
GoalStateError = class extends Error {
|
|
5651
|
+
constructor(path45, message) {
|
|
5652
|
+
super(`Invalid goal state at ${path45}:
|
|
5653
|
+
${message}`);
|
|
5654
|
+
this.path = path45;
|
|
5655
|
+
this.name = "GoalStateError";
|
|
5656
|
+
}
|
|
5657
|
+
path;
|
|
5658
|
+
};
|
|
5659
|
+
}
|
|
5660
|
+
});
|
|
5661
|
+
|
|
5662
|
+
// src/scripts/advanceManagedGoal.ts
|
|
5663
|
+
function readSimpleGoalTaskSummary(goalId, cwd) {
|
|
5664
|
+
const raw = gh(
|
|
5665
|
+
["issue", "list", "--state", "all", "--label", `goal:${goalId}`, "--limit", "1000", "--json", "number,state"],
|
|
5666
|
+
{ cwd }
|
|
5667
|
+
);
|
|
5668
|
+
const issues = JSON.parse(raw);
|
|
5669
|
+
const total = issues.length;
|
|
5670
|
+
const open = issues.filter((issue) => String(issue.state ?? "").toLowerCase() === "open").length;
|
|
5671
|
+
return { total, open };
|
|
5672
|
+
}
|
|
5673
|
+
var advanceManagedGoal;
|
|
5674
|
+
var init_advanceManagedGoal = __esm({
|
|
5675
|
+
"src/scripts/advanceManagedGoal.ts"() {
|
|
5676
|
+
"use strict";
|
|
5677
|
+
init_manager();
|
|
5678
|
+
init_state2();
|
|
5679
|
+
init_issue();
|
|
5680
|
+
advanceManagedGoal = async (ctx) => {
|
|
5681
|
+
ctx.skipAgent = true;
|
|
5682
|
+
const goal = ctx.data.goal;
|
|
5683
|
+
if (!goal?.raw) {
|
|
5684
|
+
ctx.output.exitCode = 1;
|
|
5685
|
+
ctx.output.reason = "advanceManagedGoal requires loadGoalState first";
|
|
5686
|
+
return;
|
|
5687
|
+
}
|
|
5688
|
+
ctx.data.goalOriginalStateText = serializeGoalState(goal.raw);
|
|
5689
|
+
const managed = managedGoalFromState(goal.raw);
|
|
5690
|
+
if (!managed) {
|
|
5691
|
+
ctx.output.reason = "goal has no managed-goal contract; nothing to advance";
|
|
5692
|
+
return;
|
|
5693
|
+
}
|
|
5694
|
+
if (isSimpleGoal(managed)) {
|
|
5695
|
+
applySimpleGoalTaskSummary(managed, readSimpleGoalTaskSummary(goal.id, ctx.cwd));
|
|
5696
|
+
}
|
|
5697
|
+
const decision = planManagedGoalTick(managed);
|
|
5698
|
+
ctx.data.managedGoalDecision = decision;
|
|
5699
|
+
if (decision.kind === "done") {
|
|
5700
|
+
goal.state = "done";
|
|
5701
|
+
}
|
|
5702
|
+
goal.raw = writeManagedGoalToState({ ...goal.raw, state: goal.state }, managed);
|
|
5703
|
+
if (decision.kind === "blocked" || decision.kind === "wait" || decision.kind === "idle" || decision.kind === "done") {
|
|
5704
|
+
ctx.output.reason = decision.kind === "done" ? "managed goal complete" : decision.reason;
|
|
5705
|
+
return;
|
|
5706
|
+
}
|
|
5707
|
+
ctx.output.nextDispatch = {
|
|
5708
|
+
duty: decision.duty,
|
|
5709
|
+
executable: decision.executable,
|
|
5710
|
+
cliArgs: decision.cliArgs
|
|
5711
|
+
};
|
|
5712
|
+
ctx.output.reason = `dispatch ${decision.duty} for ${decision.evidence}`;
|
|
5713
|
+
};
|
|
5714
|
+
}
|
|
5715
|
+
});
|
|
5716
|
+
|
|
5171
5717
|
// src/stateBranch.ts
|
|
5172
5718
|
function is404(err) {
|
|
5173
5719
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5285,14 +5831,126 @@ var init_appendCompanyActivity = __esm({
|
|
|
5285
5831
|
}
|
|
5286
5832
|
});
|
|
5287
5833
|
|
|
5288
|
-
// src/
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
return
|
|
5295
|
-
}
|
|
5834
|
+
// src/goal/stateStore.ts
|
|
5835
|
+
function statePath(goalId) {
|
|
5836
|
+
return `.kody/goals/instances/${goalId}/state.json`;
|
|
5837
|
+
}
|
|
5838
|
+
function is4042(err) {
|
|
5839
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5840
|
+
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5841
|
+
}
|
|
5842
|
+
function fetchGoalState(owner, repo, goalId, cwd) {
|
|
5843
|
+
const filePath = statePath(goalId);
|
|
5844
|
+
let raw;
|
|
5845
|
+
try {
|
|
5846
|
+
raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5847
|
+
} catch (err) {
|
|
5848
|
+
if (is4042(err)) return null;
|
|
5849
|
+
throw err;
|
|
5850
|
+
}
|
|
5851
|
+
const o = JSON.parse(raw);
|
|
5852
|
+
if (!o.content) return null;
|
|
5853
|
+
const decoded = Buffer.from(o.content, "base64").toString("utf-8");
|
|
5854
|
+
return parseGoalState(filePath, JSON.parse(decoded));
|
|
5855
|
+
}
|
|
5856
|
+
function putGoalState(owner, repo, goalId, state, message, cwd) {
|
|
5857
|
+
ensureStateBranch(owner, repo, cwd);
|
|
5858
|
+
const filePath = statePath(goalId);
|
|
5859
|
+
const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
|
|
5860
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
5861
|
+
let sha;
|
|
5862
|
+
try {
|
|
5863
|
+
const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5864
|
+
const o = JSON.parse(cur);
|
|
5865
|
+
if (o.sha) sha = o.sha;
|
|
5866
|
+
} catch (err) {
|
|
5867
|
+
if (!is4042(err)) throw err;
|
|
5868
|
+
}
|
|
5869
|
+
const payload = { message, content, branch: STATE_BRANCH };
|
|
5870
|
+
if (sha) payload.sha = sha;
|
|
5871
|
+
try {
|
|
5872
|
+
gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
|
|
5873
|
+
cwd,
|
|
5874
|
+
input: JSON.stringify(payload)
|
|
5875
|
+
});
|
|
5876
|
+
return;
|
|
5877
|
+
} catch (err) {
|
|
5878
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5879
|
+
const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
5880
|
+
if (!conflict || attempt === 3) throw err;
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
var init_stateStore = __esm({
|
|
5885
|
+
"src/goal/stateStore.ts"() {
|
|
5886
|
+
"use strict";
|
|
5887
|
+
init_issue();
|
|
5888
|
+
init_stateBranch();
|
|
5889
|
+
init_state2();
|
|
5890
|
+
}
|
|
5891
|
+
});
|
|
5892
|
+
|
|
5893
|
+
// src/scripts/applyDutyReports.ts
|
|
5894
|
+
function collectReports(raw, agentResult) {
|
|
5895
|
+
const out = [];
|
|
5896
|
+
if (Array.isArray(raw)) {
|
|
5897
|
+
for (const item of raw) {
|
|
5898
|
+
if (isDutyReport(item)) out.push(item);
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
if (agentResult?.finalText) out.push(...parseDutyReportsFromText(agentResult.finalText));
|
|
5902
|
+
return out;
|
|
5903
|
+
}
|
|
5904
|
+
function isDutyReport(raw) {
|
|
5905
|
+
return !!raw && typeof raw === "object" && !Array.isArray(raw) && !!raw.target && (raw.target.type === "goal" || raw.target.type === "task" || raw.target.type === "duty") && typeof raw.target.id === "string";
|
|
5906
|
+
}
|
|
5907
|
+
function describeMessage(report) {
|
|
5908
|
+
const keys = [
|
|
5909
|
+
...Object.keys(report.evidence ?? {}).map((key) => `evidence:${key}`),
|
|
5910
|
+
...Object.keys(report.facts ?? {}).map((key) => `fact:${key}`)
|
|
5911
|
+
];
|
|
5912
|
+
return `chore(goals): apply duty report for ${report.target.id}${keys.length ? ` (${keys.join(", ")})` : ""}`;
|
|
5913
|
+
}
|
|
5914
|
+
var applyDutyReports;
|
|
5915
|
+
var init_applyDutyReports = __esm({
|
|
5916
|
+
"src/scripts/applyDutyReports.ts"() {
|
|
5917
|
+
"use strict";
|
|
5918
|
+
init_dutyReport();
|
|
5919
|
+
init_state2();
|
|
5920
|
+
init_stateStore();
|
|
5921
|
+
applyDutyReports = async (ctx, _profile, agentResult) => {
|
|
5922
|
+
const reports = collectReports(ctx.data.dutyReports, agentResult);
|
|
5923
|
+
if (reports.length === 0) return;
|
|
5924
|
+
const owner = ctx.config.github?.owner;
|
|
5925
|
+
const repo = ctx.config.github?.repo;
|
|
5926
|
+
if (!owner || !repo) {
|
|
5927
|
+
process.stderr.write("[kody duty-report] missing github owner/repo; cannot apply reports\n");
|
|
5928
|
+
return;
|
|
5929
|
+
}
|
|
5930
|
+
const goalReports = reports.filter((report) => report.target.type === "goal");
|
|
5931
|
+
for (const report of goalReports) {
|
|
5932
|
+
const prior = fetchGoalState(owner, repo, report.target.id, ctx.cwd);
|
|
5933
|
+
if (!prior) {
|
|
5934
|
+
process.stderr.write(`[kody duty-report] goal ${report.target.id} missing on kody-state; report skipped
|
|
5935
|
+
`);
|
|
5936
|
+
continue;
|
|
5937
|
+
}
|
|
5938
|
+
const next = applyDutyReportToGoalState(prior, report);
|
|
5939
|
+
if (serializeGoalState(next) === serializeGoalState(prior)) continue;
|
|
5940
|
+
putGoalState(owner, repo, report.target.id, { ...next, updatedAt: nowIso() }, describeMessage(report), ctx.cwd);
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
}
|
|
5944
|
+
});
|
|
5945
|
+
|
|
5946
|
+
// src/coverage.ts
|
|
5947
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
5948
|
+
function patternToRegex(pattern) {
|
|
5949
|
+
let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
5950
|
+
s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
|
|
5951
|
+
s = s.replace(/§S/g, "(?:.*/)?").replace(/§A/g, ".*");
|
|
5952
|
+
return new RegExp(`^${s}$`);
|
|
5953
|
+
}
|
|
5296
5954
|
function renderSiblingPath(file, requireSibling) {
|
|
5297
5955
|
const lastSlash = file.lastIndexOf("/");
|
|
5298
5956
|
const dir = lastSlash === -1 ? "" : file.slice(0, lastSlash + 1);
|
|
@@ -5304,7 +5962,7 @@ function renderSiblingPath(file, requireSibling) {
|
|
|
5304
5962
|
}
|
|
5305
5963
|
function safeGit(args, cwd) {
|
|
5306
5964
|
try {
|
|
5307
|
-
return
|
|
5965
|
+
return execFileSync10("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
|
|
5308
5966
|
} catch {
|
|
5309
5967
|
return "";
|
|
5310
5968
|
}
|
|
@@ -5451,11 +6109,11 @@ var init_classifyByLabel = __esm({
|
|
|
5451
6109
|
});
|
|
5452
6110
|
|
|
5453
6111
|
// src/scripts/commitAndPush.ts
|
|
5454
|
-
import * as
|
|
5455
|
-
import * as
|
|
6112
|
+
import * as fs25 from "fs";
|
|
6113
|
+
import * as path22 from "path";
|
|
5456
6114
|
function sentinelPathForStage(cwd, profileName) {
|
|
5457
6115
|
const runId = resolveRunId();
|
|
5458
|
-
return
|
|
6116
|
+
return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
5459
6117
|
}
|
|
5460
6118
|
var DEFAULT_COMMIT_MESSAGE, commitAndPush2;
|
|
5461
6119
|
var init_commitAndPush = __esm({
|
|
@@ -5472,9 +6130,9 @@ var init_commitAndPush = __esm({
|
|
|
5472
6130
|
}
|
|
5473
6131
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
5474
6132
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
5475
|
-
if (sentinel &&
|
|
6133
|
+
if (sentinel && fs25.existsSync(sentinel)) {
|
|
5476
6134
|
try {
|
|
5477
|
-
const replay = JSON.parse(
|
|
6135
|
+
const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
|
|
5478
6136
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
5479
6137
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
5480
6138
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -5527,8 +6185,8 @@ var init_commitAndPush = __esm({
|
|
|
5527
6185
|
const result = ctx.data.commitResult;
|
|
5528
6186
|
if (sentinel && result?.committed) {
|
|
5529
6187
|
try {
|
|
5530
|
-
|
|
5531
|
-
|
|
6188
|
+
fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
|
|
6189
|
+
fs25.writeFileSync(
|
|
5532
6190
|
sentinel,
|
|
5533
6191
|
JSON.stringify(
|
|
5534
6192
|
{
|
|
@@ -5549,148 +6207,17 @@ var init_commitAndPush = __esm({
|
|
|
5549
6207
|
}
|
|
5550
6208
|
});
|
|
5551
6209
|
|
|
5552
|
-
// src/goal/state.ts
|
|
5553
|
-
import * as fs24 from "fs";
|
|
5554
|
-
import * as path21 from "path";
|
|
5555
|
-
function parseGoalState(filePath, raw) {
|
|
5556
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5557
|
-
throw new GoalStateError(filePath, "must be a JSON object");
|
|
5558
|
-
}
|
|
5559
|
-
const r = raw;
|
|
5560
|
-
const stateValue = r.state;
|
|
5561
|
-
if (typeof stateValue !== "string" || !VALID_STATES.has(stateValue)) {
|
|
5562
|
-
throw new GoalStateError(
|
|
5563
|
-
filePath,
|
|
5564
|
-
`"state" is required and must be one of: ${[...VALID_STATES].join(" | ")} (got ${JSON.stringify(stateValue)})`
|
|
5565
|
-
);
|
|
5566
|
-
}
|
|
5567
|
-
const parsed = {
|
|
5568
|
-
state: stateValue,
|
|
5569
|
-
extra: {}
|
|
5570
|
-
};
|
|
5571
|
-
if (typeof r.mergeApproved === "boolean") {
|
|
5572
|
-
parsed.mergeApproved = r.mergeApproved;
|
|
5573
|
-
}
|
|
5574
|
-
if (typeof r.lastDispatchedIssue === "number" && Number.isFinite(r.lastDispatchedIssue)) {
|
|
5575
|
-
parsed.lastDispatchedIssue = r.lastDispatchedIssue;
|
|
5576
|
-
}
|
|
5577
|
-
for (const ts of ["updatedAt", "createdAt", "startedAt"]) {
|
|
5578
|
-
const v = r[ts];
|
|
5579
|
-
if (typeof v === "string" && v.length > 0) parsed[ts] = v;
|
|
5580
|
-
}
|
|
5581
|
-
const known = /* @__PURE__ */ new Set(["state", "mergeApproved", "lastDispatchedIssue", "updatedAt", "createdAt", "startedAt"]);
|
|
5582
|
-
for (const [k, v] of Object.entries(r)) {
|
|
5583
|
-
if (!known.has(k)) parsed.extra[k] = v;
|
|
5584
|
-
}
|
|
5585
|
-
return parsed;
|
|
5586
|
-
}
|
|
5587
|
-
function serializeGoalState(s) {
|
|
5588
|
-
const obj = { ...s.extra, state: s.state };
|
|
5589
|
-
if (s.mergeApproved !== void 0) obj.mergeApproved = s.mergeApproved;
|
|
5590
|
-
if (s.lastDispatchedIssue !== void 0) obj.lastDispatchedIssue = s.lastDispatchedIssue;
|
|
5591
|
-
if (s.createdAt !== void 0) obj.createdAt = s.createdAt;
|
|
5592
|
-
if (s.startedAt !== void 0) obj.startedAt = s.startedAt;
|
|
5593
|
-
if (s.updatedAt !== void 0) obj.updatedAt = s.updatedAt;
|
|
5594
|
-
return `${JSON.stringify(obj, null, 2)}
|
|
5595
|
-
`;
|
|
5596
|
-
}
|
|
5597
|
-
function nowIso() {
|
|
5598
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5599
|
-
}
|
|
5600
|
-
var VALID_STATES, GoalStateError;
|
|
5601
|
-
var init_state2 = __esm({
|
|
5602
|
-
"src/goal/state.ts"() {
|
|
5603
|
-
"use strict";
|
|
5604
|
-
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
5605
|
-
GoalStateError = class extends Error {
|
|
5606
|
-
constructor(path45, message) {
|
|
5607
|
-
super(`Invalid goal state at ${path45}:
|
|
5608
|
-
${message}`);
|
|
5609
|
-
this.path = path45;
|
|
5610
|
-
this.name = "GoalStateError";
|
|
5611
|
-
}
|
|
5612
|
-
path;
|
|
5613
|
-
};
|
|
5614
|
-
}
|
|
5615
|
-
});
|
|
5616
|
-
|
|
5617
|
-
// src/goal/stateStore.ts
|
|
5618
|
-
function statePath(goalId) {
|
|
5619
|
-
return `.kody/goals/${goalId}/state.json`;
|
|
5620
|
-
}
|
|
5621
|
-
function is4042(err) {
|
|
5622
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5623
|
-
return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
|
|
5624
|
-
}
|
|
5625
|
-
function fetchGoalState(owner, repo, goalId, cwd) {
|
|
5626
|
-
const filePath = statePath(goalId);
|
|
5627
|
-
let raw;
|
|
5628
|
-
try {
|
|
5629
|
-
raw = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5630
|
-
} catch (err) {
|
|
5631
|
-
if (is4042(err)) return null;
|
|
5632
|
-
throw err;
|
|
5633
|
-
}
|
|
5634
|
-
const o = JSON.parse(raw);
|
|
5635
|
-
if (!o.content) return null;
|
|
5636
|
-
const decoded = Buffer.from(o.content, "base64").toString("utf-8");
|
|
5637
|
-
return parseGoalState(filePath, JSON.parse(decoded));
|
|
5638
|
-
}
|
|
5639
|
-
function putGoalState(owner, repo, goalId, state, message, cwd) {
|
|
5640
|
-
ensureStateBranch(owner, repo, cwd);
|
|
5641
|
-
const filePath = statePath(goalId);
|
|
5642
|
-
const content = Buffer.from(serializeGoalState(state), "utf-8").toString("base64");
|
|
5643
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
5644
|
-
let sha;
|
|
5645
|
-
try {
|
|
5646
|
-
const cur = gh(["api", `/repos/${owner}/${repo}/contents/${filePath}?ref=${STATE_BRANCH}`], { cwd });
|
|
5647
|
-
const o = JSON.parse(cur);
|
|
5648
|
-
if (o.sha) sha = o.sha;
|
|
5649
|
-
} catch (err) {
|
|
5650
|
-
if (!is4042(err)) throw err;
|
|
5651
|
-
}
|
|
5652
|
-
const payload = { message, content, branch: STATE_BRANCH };
|
|
5653
|
-
if (sha) payload.sha = sha;
|
|
5654
|
-
try {
|
|
5655
|
-
gh(["api", "--method", "PUT", `/repos/${owner}/${repo}/contents/${filePath}`, "--input", "-"], {
|
|
5656
|
-
cwd,
|
|
5657
|
-
input: JSON.stringify(payload)
|
|
5658
|
-
});
|
|
5659
|
-
return;
|
|
5660
|
-
} catch (err) {
|
|
5661
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5662
|
-
const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
5663
|
-
if (!conflict || attempt === 3) throw err;
|
|
5664
|
-
}
|
|
5665
|
-
}
|
|
5666
|
-
}
|
|
5667
|
-
var init_stateStore = __esm({
|
|
5668
|
-
"src/goal/stateStore.ts"() {
|
|
5669
|
-
"use strict";
|
|
5670
|
-
init_issue();
|
|
5671
|
-
init_stateBranch();
|
|
5672
|
-
init_state2();
|
|
5673
|
-
}
|
|
5674
|
-
});
|
|
5675
|
-
|
|
5676
6210
|
// src/scripts/commitGoalState.ts
|
|
5677
6211
|
function describeCommitMessage(goal) {
|
|
5678
|
-
if (goal.state === "
|
|
5679
|
-
|
|
5680
|
-
if (goal.state === "done") return `chore(goals): mark ${goal.id} done`;
|
|
5681
|
-
if (goal.lastDispatchedIssue !== void 0) {
|
|
5682
|
-
return `chore(goals): dispatched #${goal.lastDispatchedIssue} for ${goal.id}`;
|
|
5683
|
-
}
|
|
5684
|
-
if (goal.phase === "in-flight") {
|
|
5685
|
-
return `chore(goals): tick ${goal.id} (waiting for in-flight task)`;
|
|
5686
|
-
}
|
|
5687
|
-
return `chore(goals): tick ${goal.id} (idle)`;
|
|
6212
|
+
if (goal.state === "done") return `chore(goals): complete ${goal.id}`;
|
|
6213
|
+
return `chore(goals): update ${goal.id}`;
|
|
5688
6214
|
}
|
|
5689
|
-
var
|
|
6215
|
+
var STATE_BRANCH_LABEL, commitGoalState;
|
|
5690
6216
|
var init_commitGoalState = __esm({
|
|
5691
6217
|
"src/scripts/commitGoalState.ts"() {
|
|
5692
6218
|
"use strict";
|
|
5693
6219
|
init_stateStore();
|
|
6220
|
+
STATE_BRANCH_LABEL = "kody-state";
|
|
5694
6221
|
commitGoalState = async (ctx) => {
|
|
5695
6222
|
const goal = ctx.data.goal;
|
|
5696
6223
|
if (!goal) return;
|
|
@@ -5700,7 +6227,7 @@ var init_commitGoalState = __esm({
|
|
|
5700
6227
|
const owner = ctx.config.github?.owner;
|
|
5701
6228
|
const repo = ctx.config.github?.repo;
|
|
5702
6229
|
if (!owner || !repo) {
|
|
5703
|
-
process.stderr.write(`[goal-
|
|
6230
|
+
process.stderr.write(`[goal-manager] commitGoalState: missing github owner/repo; cannot persist ${goal.id}
|
|
5704
6231
|
`);
|
|
5705
6232
|
return;
|
|
5706
6233
|
}
|
|
@@ -5708,18 +6235,17 @@ var init_commitGoalState = __esm({
|
|
|
5708
6235
|
putGoalState(owner, repo, goal.id, updated, describeCommitMessage(goal), ctx.cwd);
|
|
5709
6236
|
} catch (err) {
|
|
5710
6237
|
process.stderr.write(
|
|
5711
|
-
`[goal-
|
|
6238
|
+
`[goal-manager] commitGoalState: persist to ${STATE_BRANCH_LABEL} failed (${err instanceof Error ? err.message : String(err)}); will retry next tick
|
|
5712
6239
|
`
|
|
5713
6240
|
);
|
|
5714
6241
|
}
|
|
5715
6242
|
};
|
|
5716
|
-
STATE_BRANCH_LABEL = "kody-state";
|
|
5717
6243
|
}
|
|
5718
6244
|
});
|
|
5719
6245
|
|
|
5720
6246
|
// src/scripts/composePrompt.ts
|
|
5721
|
-
import * as
|
|
5722
|
-
import * as
|
|
6247
|
+
import * as fs26 from "fs";
|
|
6248
|
+
import * as path23 from "path";
|
|
5723
6249
|
function fenceUntrusted(value) {
|
|
5724
6250
|
if (value.trim().length === 0) return value;
|
|
5725
6251
|
const safe = value.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
|
|
@@ -5840,10 +6366,10 @@ var init_composePrompt = __esm({
|
|
|
5840
6366
|
const explicit = ctx.data.promptTemplate;
|
|
5841
6367
|
const mode = ctx.args.mode;
|
|
5842
6368
|
const candidates = [
|
|
5843
|
-
explicit ?
|
|
5844
|
-
mode ?
|
|
5845
|
-
|
|
5846
|
-
|
|
6369
|
+
explicit ? path23.join(profile.dir, explicit) : null,
|
|
6370
|
+
mode ? path23.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
6371
|
+
path23.join(profile.dir, "prompt.md"),
|
|
6372
|
+
path23.join(profile.dir, "duty.md")
|
|
5847
6373
|
].filter(Boolean);
|
|
5848
6374
|
let templatePath = "";
|
|
5849
6375
|
let template = "";
|
|
@@ -5856,7 +6382,7 @@ var init_composePrompt = __esm({
|
|
|
5856
6382
|
break;
|
|
5857
6383
|
}
|
|
5858
6384
|
try {
|
|
5859
|
-
template =
|
|
6385
|
+
template = fs26.readFileSync(c, "utf-8");
|
|
5860
6386
|
templatePath = c;
|
|
5861
6387
|
break;
|
|
5862
6388
|
} catch (err) {
|
|
@@ -5867,7 +6393,7 @@ var init_composePrompt = __esm({
|
|
|
5867
6393
|
if (!templatePath) {
|
|
5868
6394
|
let dirState;
|
|
5869
6395
|
try {
|
|
5870
|
-
dirState = `dir contents: [${
|
|
6396
|
+
dirState = `dir contents: [${fs26.readdirSync(profile.dir).join(", ")}]`;
|
|
5871
6397
|
} catch (err) {
|
|
5872
6398
|
dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
|
|
5873
6399
|
}
|
|
@@ -6168,6 +6694,25 @@ function createTaskIssue(finding, goalId, manifestNumber, cwd) {
|
|
|
6168
6694
|
if (!m) throw new Error(`gh issue create returned unexpected output: ${out}`);
|
|
6169
6695
|
return { number: Number(m[1]), url };
|
|
6170
6696
|
}
|
|
6697
|
+
function buildQaManagedGoal(goalId, verdict, opened, failed) {
|
|
6698
|
+
return {
|
|
6699
|
+
type: "qa",
|
|
6700
|
+
destination: {
|
|
6701
|
+
outcome: `QA findings filed for ${goalId}`,
|
|
6702
|
+
evidence: ["qaFindingsFiled"]
|
|
6703
|
+
},
|
|
6704
|
+
duties: ["qa-goal"],
|
|
6705
|
+
route: [],
|
|
6706
|
+
stage: "filed",
|
|
6707
|
+
facts: {
|
|
6708
|
+
qaFindingsFiled: true,
|
|
6709
|
+
verdict,
|
|
6710
|
+
findingsOpened: opened,
|
|
6711
|
+
findingsFailed: failed
|
|
6712
|
+
},
|
|
6713
|
+
blockers: failed > 0 ? [`${failed} QA finding issue(s) failed to open`] : []
|
|
6714
|
+
};
|
|
6715
|
+
}
|
|
6171
6716
|
async function promoteReportToGoal(ctx, finalText, scopeArg, explicitGoalArg) {
|
|
6172
6717
|
const { markdown, data, jsonError } = splitReport(finalText);
|
|
6173
6718
|
const verdict = detectVerdict(markdown);
|
|
@@ -6271,7 +6816,13 @@ ${markdown}`, ctx.cwd);
|
|
|
6271
6816
|
}
|
|
6272
6817
|
}
|
|
6273
6818
|
const now = nowIso();
|
|
6274
|
-
const
|
|
6819
|
+
const managedGoal = buildQaManagedGoal(goalId, verdict, opened.length, failed.length);
|
|
6820
|
+
const goalState = {
|
|
6821
|
+
state: "active",
|
|
6822
|
+
startedAt: now,
|
|
6823
|
+
updatedAt: now,
|
|
6824
|
+
extra: { version: 1, ...managedGoal }
|
|
6825
|
+
};
|
|
6275
6826
|
try {
|
|
6276
6827
|
putGoalState(
|
|
6277
6828
|
ctx.config.github.owner,
|
|
@@ -6384,279 +6935,6 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
|
|
|
6384
6935
|
}
|
|
6385
6936
|
});
|
|
6386
6937
|
|
|
6387
|
-
// src/goal/labels.ts
|
|
6388
|
-
function goalLabel(goalId) {
|
|
6389
|
-
return `goal:${goalId}`;
|
|
6390
|
-
}
|
|
6391
|
-
var QA_GATE_LABEL;
|
|
6392
|
-
var init_labels = __esm({
|
|
6393
|
-
"src/goal/labels.ts"() {
|
|
6394
|
-
"use strict";
|
|
6395
|
-
QA_GATE_LABEL = "kody:qa-gate";
|
|
6396
|
-
}
|
|
6397
|
-
});
|
|
6398
|
-
|
|
6399
|
-
// src/goal/operations.ts
|
|
6400
|
-
function fail(err) {
|
|
6401
|
-
if (err instanceof Error) {
|
|
6402
|
-
const lines = err.message.split("\n").filter(Boolean);
|
|
6403
|
-
return { ok: false, error: lines[0] ?? err.message };
|
|
6404
|
-
}
|
|
6405
|
-
return { ok: false, error: String(err) };
|
|
6406
|
-
}
|
|
6407
|
-
function listGoalIssues(goalId, cwd) {
|
|
6408
|
-
try {
|
|
6409
|
-
const out = gh(
|
|
6410
|
-
[
|
|
6411
|
-
"api",
|
|
6412
|
-
`repos/{owner}/{repo}/issues?labels=${goalLabel(goalId)}&state=all&per_page=100`,
|
|
6413
|
-
"--jq",
|
|
6414
|
-
`[.[] | select(.pull_request == null) | {number, state: (.state | ascii_upcase), isQaGate: ([.labels[].name] | any(. == "${QA_GATE_LABEL}"))}]`
|
|
6415
|
-
],
|
|
6416
|
-
{ cwd }
|
|
6417
|
-
);
|
|
6418
|
-
return { ok: true, value: JSON.parse(out) };
|
|
6419
|
-
} catch (err) {
|
|
6420
|
-
return fail(err);
|
|
6421
|
-
}
|
|
6422
|
-
}
|
|
6423
|
-
function listOpenPrs(cwd) {
|
|
6424
|
-
try {
|
|
6425
|
-
const out = gh(
|
|
6426
|
-
["pr", "list", "--state", "open", "--limit", "200", "--json", "number,url,isDraft,headRefName,baseRefName,body"],
|
|
6427
|
-
{ cwd }
|
|
6428
|
-
);
|
|
6429
|
-
return { ok: true, value: JSON.parse(out) };
|
|
6430
|
-
} catch (err) {
|
|
6431
|
-
return fail(err);
|
|
6432
|
-
}
|
|
6433
|
-
}
|
|
6434
|
-
function pairIssuesWithPrs(issues, openPrs) {
|
|
6435
|
-
const prByIssue = /* @__PURE__ */ new Map();
|
|
6436
|
-
for (const pr of openPrs) claimPrForIssue(pr, prByIssue);
|
|
6437
|
-
return issues.map((i) => {
|
|
6438
|
-
const pr = prByIssue.get(i.number);
|
|
6439
|
-
let prState = "absent";
|
|
6440
|
-
if (pr) prState = pr.isDraft ? "draft" : "ready";
|
|
6441
|
-
return { number: i.number, state: i.state, prState, isQaGate: i.isQaGate };
|
|
6442
|
-
});
|
|
6443
|
-
}
|
|
6444
|
-
function claimPrForIssue(pr, prByIssue) {
|
|
6445
|
-
for (const issueNum of extractClosesIssues(pr.body)) {
|
|
6446
|
-
if (!prByIssue.has(issueNum)) {
|
|
6447
|
-
prByIssue.set(issueNum, pr);
|
|
6448
|
-
return;
|
|
6449
|
-
}
|
|
6450
|
-
}
|
|
6451
|
-
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
6452
|
-
if (headMatch) {
|
|
6453
|
-
const n = Number.parseInt(headMatch[1], 10);
|
|
6454
|
-
if (Number.isFinite(n) && !prByIssue.has(n)) prByIssue.set(n, pr);
|
|
6455
|
-
}
|
|
6456
|
-
}
|
|
6457
|
-
function extractClosesIssues(body) {
|
|
6458
|
-
const out = [];
|
|
6459
|
-
const re = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/gi;
|
|
6460
|
-
for (let m = re.exec(body); m !== null; m = re.exec(body)) {
|
|
6461
|
-
const n = Number.parseInt(m[1], 10);
|
|
6462
|
-
if (Number.isFinite(n)) out.push(n);
|
|
6463
|
-
}
|
|
6464
|
-
return out;
|
|
6465
|
-
}
|
|
6466
|
-
function pickLeafPr(prs) {
|
|
6467
|
-
if (prs.length === 0) return void 0;
|
|
6468
|
-
const bases = new Set(prs.map((p) => p.baseRefName));
|
|
6469
|
-
const leaves = prs.filter((p) => !bases.has(p.headRefName));
|
|
6470
|
-
return leaves.sort((a, b) => b.number - a.number)[0];
|
|
6471
|
-
}
|
|
6472
|
-
function commentOnIssue(issueNumber, body, cwd) {
|
|
6473
|
-
try {
|
|
6474
|
-
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
6475
|
-
return { ok: true };
|
|
6476
|
-
} catch (err) {
|
|
6477
|
-
return fail(err);
|
|
6478
|
-
}
|
|
6479
|
-
}
|
|
6480
|
-
function dispatchTaskRun(issueNumber, base, cwd) {
|
|
6481
|
-
try {
|
|
6482
|
-
gh(
|
|
6483
|
-
[
|
|
6484
|
-
"workflow",
|
|
6485
|
-
"run",
|
|
6486
|
-
GOAL_TASK_WORKFLOW,
|
|
6487
|
-
"-f",
|
|
6488
|
-
`issue_number=${issueNumber}`,
|
|
6489
|
-
"-f",
|
|
6490
|
-
"duty=classify",
|
|
6491
|
-
"-f",
|
|
6492
|
-
`base=${base}`
|
|
6493
|
-
],
|
|
6494
|
-
{ cwd }
|
|
6495
|
-
);
|
|
6496
|
-
return { ok: true };
|
|
6497
|
-
} catch (err) {
|
|
6498
|
-
return fail(err);
|
|
6499
|
-
}
|
|
6500
|
-
}
|
|
6501
|
-
function closeIssue(issueNumber, options, cwd) {
|
|
6502
|
-
try {
|
|
6503
|
-
if (options.comment) {
|
|
6504
|
-
gh(["issue", "comment", String(issueNumber), "--body", options.comment], { cwd });
|
|
6505
|
-
}
|
|
6506
|
-
const args = ["issue", "close", String(issueNumber)];
|
|
6507
|
-
if (options.reason) args.push("--reason", options.reason);
|
|
6508
|
-
gh(args, { cwd });
|
|
6509
|
-
return { ok: true };
|
|
6510
|
-
} catch (err) {
|
|
6511
|
-
return fail(err);
|
|
6512
|
-
}
|
|
6513
|
-
}
|
|
6514
|
-
function closePr(prNumber, comment, cwd) {
|
|
6515
|
-
try {
|
|
6516
|
-
gh(["pr", "close", String(prNumber), "--comment", comment], { cwd });
|
|
6517
|
-
return { ok: true };
|
|
6518
|
-
} catch (err) {
|
|
6519
|
-
return fail(err);
|
|
6520
|
-
}
|
|
6521
|
-
}
|
|
6522
|
-
function mergePrSquash(prNumber, cwd) {
|
|
6523
|
-
try {
|
|
6524
|
-
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
6525
|
-
return { ok: true };
|
|
6526
|
-
} catch (err) {
|
|
6527
|
-
return fail(err);
|
|
6528
|
-
}
|
|
6529
|
-
}
|
|
6530
|
-
function editPrBase(prNumber, baseBranch, cwd) {
|
|
6531
|
-
try {
|
|
6532
|
-
gh(["api", "--method", "PATCH", `repos/{owner}/{repo}/pulls/${prNumber}`, "-f", `base=${baseBranch}`], { cwd });
|
|
6533
|
-
return { ok: true };
|
|
6534
|
-
} catch (err) {
|
|
6535
|
-
return fail(err);
|
|
6536
|
-
}
|
|
6537
|
-
}
|
|
6538
|
-
function branchContains(leafHead, candidateHead, cwd) {
|
|
6539
|
-
if (leafHead === candidateHead) return { ok: true, value: true };
|
|
6540
|
-
try {
|
|
6541
|
-
const out = gh(
|
|
6542
|
-
[
|
|
6543
|
-
"api",
|
|
6544
|
-
`repos/{owner}/{repo}/compare/${encodeURIComponent(leafHead)}...${encodeURIComponent(candidateHead)}`,
|
|
6545
|
-
"--jq",
|
|
6546
|
-
".ahead_by"
|
|
6547
|
-
],
|
|
6548
|
-
{ cwd }
|
|
6549
|
-
);
|
|
6550
|
-
return { ok: true, value: Number.parseInt(out.trim(), 10) === 0 };
|
|
6551
|
-
} catch (err) {
|
|
6552
|
-
return fail(err);
|
|
6553
|
-
}
|
|
6554
|
-
}
|
|
6555
|
-
function markPrReady(prNumber, cwd) {
|
|
6556
|
-
try {
|
|
6557
|
-
gh(["pr", "ready", String(prNumber)], { cwd });
|
|
6558
|
-
return { ok: true };
|
|
6559
|
-
} catch (err) {
|
|
6560
|
-
return fail(err);
|
|
6561
|
-
}
|
|
6562
|
-
}
|
|
6563
|
-
var GOAL_TASK_WORKFLOW;
|
|
6564
|
-
var init_operations = __esm({
|
|
6565
|
-
"src/goal/operations.ts"() {
|
|
6566
|
-
"use strict";
|
|
6567
|
-
init_issue();
|
|
6568
|
-
init_labels();
|
|
6569
|
-
GOAL_TASK_WORKFLOW = "kody.yml";
|
|
6570
|
-
}
|
|
6571
|
-
});
|
|
6572
|
-
|
|
6573
|
-
// src/goal/phase.ts
|
|
6574
|
-
function derivePhase(snap) {
|
|
6575
|
-
if (!snap.lifecycleState) return "missing";
|
|
6576
|
-
if (snap.lifecycleState === "abandoned") return "abandoned";
|
|
6577
|
-
if (snap.lifecycleState === "closed" || snap.lifecycleState === "done") return "terminal";
|
|
6578
|
-
if (snap.lifecycleState === "awaiting-merge") return "awaiting-merge";
|
|
6579
|
-
const tasks = snap.childTasks.filter((t) => !t.isQaGate);
|
|
6580
|
-
const qaGateOpen = snap.childTasks.some((t) => t.isQaGate && t.state === "OPEN");
|
|
6581
|
-
const hasInFlight = tasks.some((t) => t.state === "OPEN" && t.prState === "draft");
|
|
6582
|
-
if (hasInFlight) return "in-flight";
|
|
6583
|
-
if (tasks.length === 0) return "idle";
|
|
6584
|
-
const dispatchable = tasks.some((t) => t.state === "OPEN" && t.prState === "absent");
|
|
6585
|
-
if (dispatchable) return "ready-to-dispatch";
|
|
6586
|
-
const allDone = tasks.every((t) => t.state === "CLOSED" || t.prState === "ready");
|
|
6587
|
-
if (allDone) return qaGateOpen ? "idle" : "all-done";
|
|
6588
|
-
return "idle";
|
|
6589
|
-
}
|
|
6590
|
-
function pickNextDispatchable(snap) {
|
|
6591
|
-
return snap.childTasks.filter((t) => !t.isQaGate && t.state === "OPEN" && t.prState === "absent").sort((a, b) => a.number - b.number)[0];
|
|
6592
|
-
}
|
|
6593
|
-
var init_phase = __esm({
|
|
6594
|
-
"src/goal/phase.ts"() {
|
|
6595
|
-
"use strict";
|
|
6596
|
-
}
|
|
6597
|
-
});
|
|
6598
|
-
|
|
6599
|
-
// src/scripts/deriveGoalPhase.ts
|
|
6600
|
-
function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
6601
|
-
const taskSet = new Set(taskIssueNumbers);
|
|
6602
|
-
return prs.filter((pr) => {
|
|
6603
|
-
for (const n of extractClosesIssues(pr.body)) {
|
|
6604
|
-
if (taskSet.has(n)) return true;
|
|
6605
|
-
}
|
|
6606
|
-
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
6607
|
-
if (headMatch) {
|
|
6608
|
-
const n = Number.parseInt(headMatch[1], 10);
|
|
6609
|
-
if (Number.isFinite(n) && taskSet.has(n)) return true;
|
|
6610
|
-
}
|
|
6611
|
-
return false;
|
|
6612
|
-
});
|
|
6613
|
-
}
|
|
6614
|
-
var deriveGoalPhase;
|
|
6615
|
-
var init_deriveGoalPhase = __esm({
|
|
6616
|
-
"src/scripts/deriveGoalPhase.ts"() {
|
|
6617
|
-
"use strict";
|
|
6618
|
-
init_operations();
|
|
6619
|
-
init_phase();
|
|
6620
|
-
deriveGoalPhase = async (ctx) => {
|
|
6621
|
-
const goal = ctx.data.goal;
|
|
6622
|
-
if (!goal) return;
|
|
6623
|
-
const issues = listGoalIssues(goal.id, ctx.cwd);
|
|
6624
|
-
if (!issues.ok) {
|
|
6625
|
-
process.stderr.write(`[goal-tick] deriveGoalPhase: list issues failed: ${issues.error}
|
|
6626
|
-
`);
|
|
6627
|
-
goal.childTasks = [];
|
|
6628
|
-
goal.openTaskPrs = [];
|
|
6629
|
-
goal.phase = "idle";
|
|
6630
|
-
return;
|
|
6631
|
-
}
|
|
6632
|
-
const rawIssues = issues.value ?? [];
|
|
6633
|
-
const allPrs = listOpenPrs(ctx.cwd);
|
|
6634
|
-
if (!allPrs.ok) {
|
|
6635
|
-
process.stderr.write(`[goal-tick] deriveGoalPhase: list PRs failed: ${allPrs.error}
|
|
6636
|
-
`);
|
|
6637
|
-
goal.childTasks = rawIssues.map((i) => ({ ...i, prState: "absent" }));
|
|
6638
|
-
goal.openTaskPrs = [];
|
|
6639
|
-
goal.phase = "idle";
|
|
6640
|
-
return;
|
|
6641
|
-
}
|
|
6642
|
-
const taskPrs = filterGoalTaskPrs(
|
|
6643
|
-
allPrs.value ?? [],
|
|
6644
|
-
rawIssues.map((i) => i.number)
|
|
6645
|
-
);
|
|
6646
|
-
goal.openTaskPrs = taskPrs;
|
|
6647
|
-
goal.leafPr = pickLeafPr(taskPrs);
|
|
6648
|
-
goal.childTasks = pairIssuesWithPrs(rawIssues, taskPrs);
|
|
6649
|
-
goal.phase = derivePhase({
|
|
6650
|
-
lifecycleState: goal.state,
|
|
6651
|
-
childTasks: goal.childTasks
|
|
6652
|
-
});
|
|
6653
|
-
process.stdout.write(
|
|
6654
|
-
`[goal-tick] phase=${goal.phase} goal=${goal.id} tasks=${rawIssues.length} stack=${taskPrs.length}` + (goal.leafPr ? ` leaf=#${goal.leafPr.number}` : "") + "\n"
|
|
6655
|
-
);
|
|
6656
|
-
};
|
|
6657
|
-
}
|
|
6658
|
-
});
|
|
6659
|
-
|
|
6660
6938
|
// src/scripts/deriveQaScopeFromIssue.ts
|
|
6661
6939
|
var TITLE_PATTERN, deriveQaScopeFromIssue;
|
|
6662
6940
|
var init_deriveQaScopeFromIssue = __esm({
|
|
@@ -6693,20 +6971,20 @@ var init_deriveQaScopeFromIssue = __esm({
|
|
|
6693
6971
|
});
|
|
6694
6972
|
|
|
6695
6973
|
// src/scripts/diagMcp.ts
|
|
6696
|
-
import { execFileSync as
|
|
6697
|
-
import * as
|
|
6698
|
-
import * as
|
|
6699
|
-
import * as
|
|
6974
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
6975
|
+
import * as fs27 from "fs";
|
|
6976
|
+
import * as os5 from "os";
|
|
6977
|
+
import * as path24 from "path";
|
|
6700
6978
|
var diagMcp;
|
|
6701
6979
|
var init_diagMcp = __esm({
|
|
6702
6980
|
"src/scripts/diagMcp.ts"() {
|
|
6703
6981
|
"use strict";
|
|
6704
6982
|
diagMcp = async (_ctx) => {
|
|
6705
|
-
const home =
|
|
6706
|
-
const cacheDir =
|
|
6983
|
+
const home = os5.homedir();
|
|
6984
|
+
const cacheDir = path24.join(home, ".cache", "ms-playwright");
|
|
6707
6985
|
let entries = [];
|
|
6708
6986
|
try {
|
|
6709
|
-
entries =
|
|
6987
|
+
entries = fs27.readdirSync(cacheDir);
|
|
6710
6988
|
} catch {
|
|
6711
6989
|
}
|
|
6712
6990
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -6717,7 +6995,7 @@ var init_diagMcp = __esm({
|
|
|
6717
6995
|
process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
|
|
6718
6996
|
`);
|
|
6719
6997
|
try {
|
|
6720
|
-
const v =
|
|
6998
|
+
const v = execFileSync11("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
|
|
6721
6999
|
stdio: "pipe",
|
|
6722
7000
|
timeout: 6e4,
|
|
6723
7001
|
encoding: "utf8"
|
|
@@ -6734,13 +7012,13 @@ var init_diagMcp = __esm({
|
|
|
6734
7012
|
});
|
|
6735
7013
|
|
|
6736
7014
|
// src/scripts/frameworkDetectors.ts
|
|
6737
|
-
import * as
|
|
6738
|
-
import * as
|
|
7015
|
+
import * as fs28 from "fs";
|
|
7016
|
+
import * as path25 from "path";
|
|
6739
7017
|
function detectFrameworks(cwd) {
|
|
6740
7018
|
const out = [];
|
|
6741
7019
|
let deps = {};
|
|
6742
7020
|
try {
|
|
6743
|
-
const pkg = JSON.parse(
|
|
7021
|
+
const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
|
|
6744
7022
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6745
7023
|
} catch {
|
|
6746
7024
|
return out;
|
|
@@ -6777,25 +7055,25 @@ function detectFrameworks(cwd) {
|
|
|
6777
7055
|
}
|
|
6778
7056
|
function findFile(cwd, candidates) {
|
|
6779
7057
|
for (const c of candidates) {
|
|
6780
|
-
if (
|
|
7058
|
+
if (fs28.existsSync(path25.join(cwd, c))) return c;
|
|
6781
7059
|
}
|
|
6782
7060
|
return null;
|
|
6783
7061
|
}
|
|
6784
7062
|
function discoverPayloadCollections(cwd) {
|
|
6785
7063
|
const out = [];
|
|
6786
7064
|
for (const dir of COLLECTION_DIRS) {
|
|
6787
|
-
const full =
|
|
6788
|
-
if (!
|
|
7065
|
+
const full = path25.join(cwd, dir);
|
|
7066
|
+
if (!fs28.existsSync(full)) continue;
|
|
6789
7067
|
let files;
|
|
6790
7068
|
try {
|
|
6791
|
-
files =
|
|
7069
|
+
files = fs28.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
6792
7070
|
} catch {
|
|
6793
7071
|
continue;
|
|
6794
7072
|
}
|
|
6795
7073
|
for (const file of files) {
|
|
6796
7074
|
try {
|
|
6797
|
-
const filePath =
|
|
6798
|
-
const content =
|
|
7075
|
+
const filePath = path25.join(full, file);
|
|
7076
|
+
const content = fs28.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
6799
7077
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
6800
7078
|
if (!slugMatch) continue;
|
|
6801
7079
|
const slug = slugMatch[1];
|
|
@@ -6809,7 +7087,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
6809
7087
|
out.push({
|
|
6810
7088
|
name,
|
|
6811
7089
|
slug,
|
|
6812
|
-
filePath:
|
|
7090
|
+
filePath: path25.relative(cwd, filePath),
|
|
6813
7091
|
fields: fields.slice(0, 20),
|
|
6814
7092
|
hasAdmin
|
|
6815
7093
|
});
|
|
@@ -6822,28 +7100,28 @@ function discoverPayloadCollections(cwd) {
|
|
|
6822
7100
|
function discoverAdminComponents(cwd, collections) {
|
|
6823
7101
|
const out = [];
|
|
6824
7102
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
6825
|
-
const full =
|
|
6826
|
-
if (!
|
|
7103
|
+
const full = path25.join(cwd, dir);
|
|
7104
|
+
if (!fs28.existsSync(full)) continue;
|
|
6827
7105
|
let entries;
|
|
6828
7106
|
try {
|
|
6829
|
-
entries =
|
|
7107
|
+
entries = fs28.readdirSync(full, { withFileTypes: true });
|
|
6830
7108
|
} catch {
|
|
6831
7109
|
continue;
|
|
6832
7110
|
}
|
|
6833
7111
|
for (const entry of entries) {
|
|
6834
|
-
const entryPath =
|
|
7112
|
+
const entryPath = path25.join(full, entry.name);
|
|
6835
7113
|
let name;
|
|
6836
7114
|
let filePath;
|
|
6837
7115
|
if (entry.isDirectory()) {
|
|
6838
7116
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
6839
|
-
(f) =>
|
|
7117
|
+
(f) => fs28.existsSync(path25.join(entryPath, f))
|
|
6840
7118
|
);
|
|
6841
7119
|
if (!indexFile) continue;
|
|
6842
7120
|
name = entry.name;
|
|
6843
|
-
filePath =
|
|
7121
|
+
filePath = path25.relative(cwd, path25.join(entryPath, indexFile));
|
|
6844
7122
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
6845
7123
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
6846
|
-
filePath =
|
|
7124
|
+
filePath = path25.relative(cwd, entryPath);
|
|
6847
7125
|
} else {
|
|
6848
7126
|
continue;
|
|
6849
7127
|
}
|
|
@@ -6851,7 +7129,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6851
7129
|
if (collections) {
|
|
6852
7130
|
for (const col of collections) {
|
|
6853
7131
|
try {
|
|
6854
|
-
const colContent =
|
|
7132
|
+
const colContent = fs28.readFileSync(path25.join(cwd, col.filePath), "utf-8");
|
|
6855
7133
|
if (colContent.includes(name)) {
|
|
6856
7134
|
usedInCollection = col.slug;
|
|
6857
7135
|
break;
|
|
@@ -6869,8 +7147,8 @@ function scanApiRoutes(cwd) {
|
|
|
6869
7147
|
const out = [];
|
|
6870
7148
|
const appDirs = ["src/app", "app"];
|
|
6871
7149
|
for (const appDir of appDirs) {
|
|
6872
|
-
const apiDir =
|
|
6873
|
-
if (!
|
|
7150
|
+
const apiDir = path25.join(cwd, appDir, "api");
|
|
7151
|
+
if (!fs28.existsSync(apiDir)) continue;
|
|
6874
7152
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
6875
7153
|
break;
|
|
6876
7154
|
}
|
|
@@ -6879,14 +7157,14 @@ function scanApiRoutes(cwd) {
|
|
|
6879
7157
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
6880
7158
|
let entries;
|
|
6881
7159
|
try {
|
|
6882
|
-
entries =
|
|
7160
|
+
entries = fs28.readdirSync(dir, { withFileTypes: true });
|
|
6883
7161
|
} catch {
|
|
6884
7162
|
return;
|
|
6885
7163
|
}
|
|
6886
7164
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
6887
7165
|
if (routeFile) {
|
|
6888
7166
|
try {
|
|
6889
|
-
const content =
|
|
7167
|
+
const content = fs28.readFileSync(path25.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
6890
7168
|
const methods = HTTP_METHODS.filter(
|
|
6891
7169
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
6892
7170
|
);
|
|
@@ -6894,7 +7172,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6894
7172
|
out.push({
|
|
6895
7173
|
path: prefix,
|
|
6896
7174
|
methods,
|
|
6897
|
-
filePath:
|
|
7175
|
+
filePath: path25.relative(cwd, path25.join(dir, routeFile.name))
|
|
6898
7176
|
});
|
|
6899
7177
|
}
|
|
6900
7178
|
} catch {
|
|
@@ -6905,7 +7183,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6905
7183
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
6906
7184
|
let segment = entry.name;
|
|
6907
7185
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
6908
|
-
walkApiRoutes(
|
|
7186
|
+
walkApiRoutes(path25.join(dir, entry.name), prefix, cwd, out);
|
|
6909
7187
|
continue;
|
|
6910
7188
|
}
|
|
6911
7189
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -6913,16 +7191,16 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6913
7191
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
6914
7192
|
segment = `:${segment.slice(1, -1)}`;
|
|
6915
7193
|
}
|
|
6916
|
-
walkApiRoutes(
|
|
7194
|
+
walkApiRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
6917
7195
|
}
|
|
6918
7196
|
}
|
|
6919
7197
|
function scanEnvVars(cwd) {
|
|
6920
7198
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
6921
7199
|
for (const envFile of candidates) {
|
|
6922
|
-
const envPath =
|
|
6923
|
-
if (!
|
|
7200
|
+
const envPath = path25.join(cwd, envFile);
|
|
7201
|
+
if (!fs28.existsSync(envPath)) continue;
|
|
6924
7202
|
try {
|
|
6925
|
-
const content =
|
|
7203
|
+
const content = fs28.readFileSync(envPath, "utf-8");
|
|
6926
7204
|
const vars = [];
|
|
6927
7205
|
for (const line of content.split("\n")) {
|
|
6928
7206
|
const trimmed = line.trim();
|
|
@@ -6967,8 +7245,8 @@ var init_frameworkDetectors = __esm({
|
|
|
6967
7245
|
});
|
|
6968
7246
|
|
|
6969
7247
|
// src/scripts/discoverQaContext.ts
|
|
6970
|
-
import * as
|
|
6971
|
-
import * as
|
|
7248
|
+
import * as fs29 from "fs";
|
|
7249
|
+
import * as path26 from "path";
|
|
6972
7250
|
function runQaDiscovery(cwd) {
|
|
6973
7251
|
const out = {
|
|
6974
7252
|
routes: [],
|
|
@@ -6999,9 +7277,9 @@ function runQaDiscovery(cwd) {
|
|
|
6999
7277
|
}
|
|
7000
7278
|
function detectDevServer(cwd, out) {
|
|
7001
7279
|
try {
|
|
7002
|
-
const pkg = JSON.parse(
|
|
7280
|
+
const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
|
|
7003
7281
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7004
|
-
const pm =
|
|
7282
|
+
const pm = fs29.existsSync(path26.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs29.existsSync(path26.join(cwd, "yarn.lock")) ? "yarn" : fs29.existsSync(path26.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
7005
7283
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
7006
7284
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
7007
7285
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -7011,8 +7289,8 @@ function detectDevServer(cwd, out) {
|
|
|
7011
7289
|
function scanFrontendRoutes(cwd, out) {
|
|
7012
7290
|
const appDirs = ["src/app", "app"];
|
|
7013
7291
|
for (const appDir of appDirs) {
|
|
7014
|
-
const full =
|
|
7015
|
-
if (!
|
|
7292
|
+
const full = path26.join(cwd, appDir);
|
|
7293
|
+
if (!fs29.existsSync(full)) continue;
|
|
7016
7294
|
walkFrontendRoutes(full, "", out);
|
|
7017
7295
|
break;
|
|
7018
7296
|
}
|
|
@@ -7020,7 +7298,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
7020
7298
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
7021
7299
|
let entries;
|
|
7022
7300
|
try {
|
|
7023
|
-
entries =
|
|
7301
|
+
entries = fs29.readdirSync(dir, { withFileTypes: true });
|
|
7024
7302
|
} catch {
|
|
7025
7303
|
return;
|
|
7026
7304
|
}
|
|
@@ -7037,7 +7315,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7037
7315
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7038
7316
|
let segment = entry.name;
|
|
7039
7317
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7040
|
-
walkFrontendRoutes(
|
|
7318
|
+
walkFrontendRoutes(path26.join(dir, entry.name), prefix, out);
|
|
7041
7319
|
continue;
|
|
7042
7320
|
}
|
|
7043
7321
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7045,7 +7323,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7045
7323
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7046
7324
|
segment = `:${segment.slice(1, -1)}`;
|
|
7047
7325
|
}
|
|
7048
|
-
walkFrontendRoutes(
|
|
7326
|
+
walkFrontendRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
7049
7327
|
}
|
|
7050
7328
|
}
|
|
7051
7329
|
function detectAuthFiles(cwd, out) {
|
|
@@ -7062,23 +7340,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
7062
7340
|
"src/app/api/oauth"
|
|
7063
7341
|
];
|
|
7064
7342
|
for (const c of candidates) {
|
|
7065
|
-
if (
|
|
7343
|
+
if (fs29.existsSync(path26.join(cwd, c))) out.authFiles.push(c);
|
|
7066
7344
|
}
|
|
7067
7345
|
}
|
|
7068
7346
|
function detectRoles(cwd, out) {
|
|
7069
7347
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
7070
7348
|
for (const rp of rolePaths) {
|
|
7071
|
-
const dir =
|
|
7072
|
-
if (!
|
|
7349
|
+
const dir = path26.join(cwd, rp);
|
|
7350
|
+
if (!fs29.existsSync(dir)) continue;
|
|
7073
7351
|
let files;
|
|
7074
7352
|
try {
|
|
7075
|
-
files =
|
|
7353
|
+
files = fs29.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7076
7354
|
} catch {
|
|
7077
7355
|
continue;
|
|
7078
7356
|
}
|
|
7079
7357
|
for (const f of files) {
|
|
7080
7358
|
try {
|
|
7081
|
-
const content =
|
|
7359
|
+
const content = fs29.readFileSync(path26.join(dir, f), "utf-8").slice(0, 5e3);
|
|
7082
7360
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
7083
7361
|
if (roleMatches) {
|
|
7084
7362
|
for (const m of roleMatches) {
|
|
@@ -7221,7 +7499,7 @@ var init_dispatch = __esm({
|
|
|
7221
7499
|
});
|
|
7222
7500
|
|
|
7223
7501
|
// src/scripts/dispatchClassified.ts
|
|
7224
|
-
import { execFileSync as
|
|
7502
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
7225
7503
|
var API_TIMEOUT_MS4, VALID_CLASSES2, dispatchClassified;
|
|
7226
7504
|
var init_dispatchClassified = __esm({
|
|
7227
7505
|
"src/scripts/dispatchClassified.ts"() {
|
|
@@ -7251,7 +7529,7 @@ ${stateBody}`;
|
|
|
7251
7529
|
try {
|
|
7252
7530
|
const existing = findStateComment("issue", issueNumber, ctx.cwd);
|
|
7253
7531
|
if (existing) {
|
|
7254
|
-
|
|
7532
|
+
execFileSync12(
|
|
7255
7533
|
"gh",
|
|
7256
7534
|
["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"],
|
|
7257
7535
|
{
|
|
@@ -7262,7 +7540,7 @@ ${stateBody}`;
|
|
|
7262
7540
|
}
|
|
7263
7541
|
);
|
|
7264
7542
|
} else {
|
|
7265
|
-
|
|
7543
|
+
execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
7266
7544
|
cwd: ctx.cwd,
|
|
7267
7545
|
timeout: API_TIMEOUT_MS4,
|
|
7268
7546
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7504,8 +7782,8 @@ var init_contentsApiBackend = __esm({
|
|
|
7504
7782
|
});
|
|
7505
7783
|
|
|
7506
7784
|
// src/scripts/jobState/localFileBackend.ts
|
|
7507
|
-
import * as
|
|
7508
|
-
import * as
|
|
7785
|
+
import * as fs30 from "fs";
|
|
7786
|
+
import * as path27 from "path";
|
|
7509
7787
|
function sanitizeKey(s) {
|
|
7510
7788
|
return s.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
7511
7789
|
}
|
|
@@ -7561,7 +7839,7 @@ var init_localFileBackend = __esm({
|
|
|
7561
7839
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
7562
7840
|
this.cwd = opts.cwd;
|
|
7563
7841
|
this.jobsDir = opts.jobsDir;
|
|
7564
|
-
this.absDir =
|
|
7842
|
+
this.absDir = path27.join(opts.cwd, opts.jobsDir);
|
|
7565
7843
|
this.owner = opts.owner;
|
|
7566
7844
|
this.repo = opts.repo;
|
|
7567
7845
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -7576,7 +7854,7 @@ var init_localFileBackend = __esm({
|
|
|
7576
7854
|
`);
|
|
7577
7855
|
return;
|
|
7578
7856
|
}
|
|
7579
|
-
|
|
7857
|
+
fs30.mkdirSync(this.absDir, { recursive: true });
|
|
7580
7858
|
const prefix = this.cacheKeyPrefix();
|
|
7581
7859
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
7582
7860
|
try {
|
|
@@ -7605,7 +7883,7 @@ var init_localFileBackend = __esm({
|
|
|
7605
7883
|
`);
|
|
7606
7884
|
return;
|
|
7607
7885
|
}
|
|
7608
|
-
if (!
|
|
7886
|
+
if (!fs30.existsSync(this.absDir)) {
|
|
7609
7887
|
return;
|
|
7610
7888
|
}
|
|
7611
7889
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -7621,11 +7899,11 @@ var init_localFileBackend = __esm({
|
|
|
7621
7899
|
}
|
|
7622
7900
|
load(slug) {
|
|
7623
7901
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
7624
|
-
const absPath =
|
|
7625
|
-
if (!
|
|
7902
|
+
const absPath = path27.join(this.cwd, relPath);
|
|
7903
|
+
if (!fs30.existsSync(absPath)) {
|
|
7626
7904
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
7627
7905
|
}
|
|
7628
|
-
const raw =
|
|
7906
|
+
const raw = fs30.readFileSync(absPath, "utf-8");
|
|
7629
7907
|
let parsed;
|
|
7630
7908
|
try {
|
|
7631
7909
|
parsed = JSON.parse(raw);
|
|
@@ -7642,13 +7920,13 @@ var init_localFileBackend = __esm({
|
|
|
7642
7920
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
7643
7921
|
return false;
|
|
7644
7922
|
}
|
|
7645
|
-
const absPath =
|
|
7646
|
-
|
|
7923
|
+
const absPath = path27.join(this.cwd, loaded.path);
|
|
7924
|
+
fs30.mkdirSync(path27.dirname(absPath), { recursive: true });
|
|
7647
7925
|
const body = `${JSON.stringify(next, null, 2)}
|
|
7648
7926
|
`;
|
|
7649
7927
|
const tmpPath = `${absPath}.${process.pid}.tmp`;
|
|
7650
|
-
|
|
7651
|
-
|
|
7928
|
+
fs30.writeFileSync(tmpPath, body, "utf-8");
|
|
7929
|
+
fs30.renameSync(tmpPath, absPath);
|
|
7652
7930
|
return true;
|
|
7653
7931
|
}
|
|
7654
7932
|
cacheKeyPrefix() {
|
|
@@ -7823,7 +8101,7 @@ var init_planTaskJobs = __esm({
|
|
|
7823
8101
|
});
|
|
7824
8102
|
|
|
7825
8103
|
// src/scripts/dispatchDutyFileTicks.ts
|
|
7826
|
-
import * as
|
|
8104
|
+
import * as path28 from "path";
|
|
7827
8105
|
async function decideShouldFire(every, slug, backend, now) {
|
|
7828
8106
|
if (!every) return { skip: false, reason: "no schedule (every cron tick)" };
|
|
7829
8107
|
if (every === "manual") {
|
|
@@ -7870,6 +8148,24 @@ function parseDutyFilter(raw) {
|
|
|
7870
8148
|
function filterSlugs(slugs, onlyDuty) {
|
|
7871
8149
|
return onlyDuty ? slugs.filter((slug) => slug === onlyDuty) : slugs;
|
|
7872
8150
|
}
|
|
8151
|
+
function listActivatedDutySlugs(projectRoot, storeRoot, activeStoreDuties) {
|
|
8152
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8153
|
+
const out = [];
|
|
8154
|
+
for (const slug of listDutyFolderSlugs(projectRoot)) {
|
|
8155
|
+
if (seen.has(slug)) continue;
|
|
8156
|
+
seen.add(slug);
|
|
8157
|
+
out.push(slug);
|
|
8158
|
+
}
|
|
8159
|
+
const active = new Set(activeStoreDuties ?? []);
|
|
8160
|
+
if (storeRoot && active.size > 0) {
|
|
8161
|
+
for (const slug of listDutyFolderSlugs(storeRoot)) {
|
|
8162
|
+
if (!active.has(slug) || seen.has(slug)) continue;
|
|
8163
|
+
seen.add(slug);
|
|
8164
|
+
out.push(slug);
|
|
8165
|
+
}
|
|
8166
|
+
}
|
|
8167
|
+
return out.sort();
|
|
8168
|
+
}
|
|
7873
8169
|
function createDutyTaskIssue(opts) {
|
|
7874
8170
|
const title = `Duty ${opts.slug} - multi-executable task`;
|
|
7875
8171
|
const body = buildDutyTaskIssueBody(opts.slug, opts.body, opts.config);
|
|
@@ -7920,6 +8216,7 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
7920
8216
|
init_dutyFolders();
|
|
7921
8217
|
init_issue();
|
|
7922
8218
|
init_job();
|
|
8219
|
+
init_registry();
|
|
7923
8220
|
init_jobState();
|
|
7924
8221
|
init_planTaskJobs();
|
|
7925
8222
|
init_scheduleEvery();
|
|
@@ -7938,8 +8235,12 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
7938
8235
|
}
|
|
7939
8236
|
try {
|
|
7940
8237
|
const onlyDuty = parseDutyFilter(ctx.args.duty);
|
|
7941
|
-
const jobsPath =
|
|
7942
|
-
const
|
|
8238
|
+
const jobsPath = path28.join(ctx.cwd, jobsDir);
|
|
8239
|
+
const storeJobsPath = getCompanyStoreDutiesRoot();
|
|
8240
|
+
const slugs = filterSlugs(
|
|
8241
|
+
listActivatedDutySlugs(jobsPath, storeJobsPath, ctx.config.company?.activeDuties),
|
|
8242
|
+
onlyDuty
|
|
8243
|
+
);
|
|
7943
8244
|
ctx.data.jobSlugCount = slugs.length;
|
|
7944
8245
|
if (slugs.length === 0) {
|
|
7945
8246
|
const filter = onlyDuty ? ` matching ${onlyDuty}` : "";
|
|
@@ -7953,7 +8254,7 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
7953
8254
|
const results = [];
|
|
7954
8255
|
const now = Date.now();
|
|
7955
8256
|
for (const slug of slugs) {
|
|
7956
|
-
const duty =
|
|
8257
|
+
const duty = resolveDutyFolder(slug, jobsPath);
|
|
7957
8258
|
if (!duty) {
|
|
7958
8259
|
process.stderr.write(`[jobs] \u23ED skip ${slug}: duty folder is missing profile.json or duty.md
|
|
7959
8260
|
`);
|
|
@@ -8130,38 +8431,6 @@ var init_dispatchDutyTicks = __esm({
|
|
|
8130
8431
|
}
|
|
8131
8432
|
});
|
|
8132
8433
|
|
|
8133
|
-
// src/scripts/dispatchNextTask.ts
|
|
8134
|
-
var dispatchNextTask;
|
|
8135
|
-
var init_dispatchNextTask = __esm({
|
|
8136
|
-
"src/scripts/dispatchNextTask.ts"() {
|
|
8137
|
-
"use strict";
|
|
8138
|
-
init_operations();
|
|
8139
|
-
init_phase();
|
|
8140
|
-
dispatchNextTask = async (ctx) => {
|
|
8141
|
-
const goal = ctx.data.goal;
|
|
8142
|
-
if (!goal?.childTasks) return;
|
|
8143
|
-
const next = pickNextDispatchable({
|
|
8144
|
-
lifecycleState: goal.state,
|
|
8145
|
-
childTasks: goal.childTasks
|
|
8146
|
-
});
|
|
8147
|
-
if (!next) {
|
|
8148
|
-
process.stdout.write("[goal-tick] no dispatchable task \u2014 idle\n");
|
|
8149
|
-
return;
|
|
8150
|
-
}
|
|
8151
|
-
const base = goal.leafPr?.headRefName ?? goal.defaultBranch;
|
|
8152
|
-
process.stdout.write(`[goal-tick] dispatching #${next.number} via workflow_dispatch (classify, --base ${base})
|
|
8153
|
-
`);
|
|
8154
|
-
const res = dispatchTaskRun(next.number, base, ctx.cwd);
|
|
8155
|
-
if (!res.ok) {
|
|
8156
|
-
process.stderr.write(`[goal-tick] dispatchNextTask: workflow_dispatch on #${next.number} failed: ${res.error}
|
|
8157
|
-
`);
|
|
8158
|
-
return;
|
|
8159
|
-
}
|
|
8160
|
-
goal.lastDispatchedIssue = next.number;
|
|
8161
|
-
};
|
|
8162
|
-
}
|
|
8163
|
-
});
|
|
8164
|
-
|
|
8165
8434
|
// src/scripts/dispatchNextTaskJob.ts
|
|
8166
8435
|
function taskJobToJob(job, issueArg) {
|
|
8167
8436
|
const target = typeof job.target === "number" ? job.target : typeof issueArg === "number" ? issueArg : void 0;
|
|
@@ -8206,7 +8475,7 @@ var init_dispatchNextTaskJob = __esm({
|
|
|
8206
8475
|
});
|
|
8207
8476
|
|
|
8208
8477
|
// src/pr.ts
|
|
8209
|
-
import { execFileSync as
|
|
8478
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
8210
8479
|
function prMergeStatus(prNumber, cwd) {
|
|
8211
8480
|
try {
|
|
8212
8481
|
const out = gh(["pr", "view", String(prNumber), "--json", "mergeable,mergeStateStatus"], { cwd });
|
|
@@ -8327,7 +8596,7 @@ function isAlreadyExistsError(err) {
|
|
|
8327
8596
|
return ALREADY_EXISTS_RE.test(msg);
|
|
8328
8597
|
}
|
|
8329
8598
|
function git2(args, cwd) {
|
|
8330
|
-
return
|
|
8599
|
+
return execFileSync13("git", args, {
|
|
8331
8600
|
encoding: "utf-8",
|
|
8332
8601
|
timeout: 3e4,
|
|
8333
8602
|
cwd,
|
|
@@ -8528,140 +8797,13 @@ var init_failOnceTaskJob = __esm({
|
|
|
8528
8797
|
const state = ctx.data.taskState;
|
|
8529
8798
|
const runs = state?.jobs?.[jobKey]?.runs ?? [];
|
|
8530
8799
|
const hasFailedBefore = runs.some((run) => run.status === "failed");
|
|
8531
|
-
if (!hasFailedBefore) {
|
|
8532
|
-
ctx.output.exitCode = 1;
|
|
8533
|
-
ctx.output.reason = "intentional first-attempt failure for task job live test";
|
|
8534
|
-
return;
|
|
8535
|
-
}
|
|
8536
|
-
ctx.output.exitCode = 0;
|
|
8537
|
-
ctx.output.reason = "task job live test succeeded after prior failure";
|
|
8538
|
-
};
|
|
8539
|
-
}
|
|
8540
|
-
});
|
|
8541
|
-
|
|
8542
|
-
// src/scripts/finalizeGoal.ts
|
|
8543
|
-
function prIssueNumbers(pr) {
|
|
8544
|
-
const nums = new Set(extractClosesIssues(pr.body));
|
|
8545
|
-
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
8546
|
-
if (headMatch) {
|
|
8547
|
-
const n = Number.parseInt(headMatch[1], 10);
|
|
8548
|
-
if (Number.isFinite(n)) nums.add(n);
|
|
8549
|
-
}
|
|
8550
|
-
return [...nums];
|
|
8551
|
-
}
|
|
8552
|
-
var finalizeGoal;
|
|
8553
|
-
var init_finalizeGoal = __esm({
|
|
8554
|
-
"src/scripts/finalizeGoal.ts"() {
|
|
8555
|
-
"use strict";
|
|
8556
|
-
init_operations();
|
|
8557
|
-
finalizeGoal = async (ctx) => {
|
|
8558
|
-
const goal = ctx.data.goal;
|
|
8559
|
-
if (!goal) return;
|
|
8560
|
-
process.stdout.write(`[goal-tick] all task(s) done \u2014 preparing deliverable PR for goal ${goal.id}
|
|
8561
|
-
`);
|
|
8562
|
-
const leaf = goal.leafPr;
|
|
8563
|
-
if (!leaf) {
|
|
8564
|
-
process.stdout.write(`[goal-tick] no leaf PR \u2014 marking goal done without a deliverable PR
|
|
8565
|
-
`);
|
|
8566
|
-
goal.state = "done";
|
|
8567
|
-
return;
|
|
8568
|
-
}
|
|
8569
|
-
if (leaf.baseRefName !== goal.defaultBranch) {
|
|
8570
|
-
process.stdout.write(
|
|
8571
|
-
`[goal-tick] retargeting leaf PR #${leaf.number} base ${leaf.baseRefName} \u2192 ${goal.defaultBranch}
|
|
8572
|
-
`
|
|
8573
|
-
);
|
|
8574
|
-
const retarget = editPrBase(leaf.number, goal.defaultBranch, ctx.cwd);
|
|
8575
|
-
if (!retarget.ok) {
|
|
8576
|
-
process.stderr.write(`[goal-tick] finalizeGoal: editPrBase #${leaf.number} failed: ${retarget.error}
|
|
8577
|
-
`);
|
|
8578
|
-
return;
|
|
8579
|
-
}
|
|
8580
|
-
}
|
|
8581
|
-
if (leaf.isDraft) {
|
|
8582
|
-
process.stdout.write(`[goal-tick] promoting draft leaf PR #${leaf.number} \u2192 ready-for-review
|
|
8583
|
-
`);
|
|
8584
|
-
const ready = markPrReady(leaf.number, ctx.cwd);
|
|
8585
|
-
if (!ready.ok) {
|
|
8586
|
-
process.stderr.write(`[goal-tick] finalizeGoal: markPrReady #${leaf.number} failed: ${ready.error}
|
|
8587
|
-
`);
|
|
8588
|
-
return;
|
|
8589
|
-
}
|
|
8590
|
-
}
|
|
8591
|
-
process.stdout.write(
|
|
8592
|
-
`[goal-tick] leaf PR #${leaf.number} is the deliverable (cumulative goal diff vs ${goal.defaultBranch}) \u2014 left open for human merge
|
|
8593
|
-
`
|
|
8594
|
-
);
|
|
8595
|
-
const uncarriedIssues = /* @__PURE__ */ new Set();
|
|
8596
|
-
const others = (goal.openTaskPrs ?? []).filter((p) => p.number !== leaf.number);
|
|
8597
|
-
for (const pr of others) {
|
|
8598
|
-
const contained = branchContains(leaf.headRefName, pr.headRefName, ctx.cwd);
|
|
8599
|
-
if (!contained.ok || contained.value !== true) {
|
|
8600
|
-
const why = contained.ok ? `commits on \`${pr.headRefName}\` are NOT reachable from the deliverable leaf \`${leaf.headRefName}\`` : `could not verify containment (${contained.error})`;
|
|
8601
|
-
process.stderr.write(
|
|
8602
|
-
`[goal-tick] finalizeGoal: NOT closing PR #${pr.number} \u2014 ${why}; leaving it open (broken stack)
|
|
8603
|
-
`
|
|
8604
|
-
);
|
|
8605
|
-
for (const n of prIssueNumbers(pr)) uncarriedIssues.add(n);
|
|
8606
|
-
commentOnIssue(
|
|
8607
|
-
pr.number,
|
|
8608
|
-
`\u26A0\uFE0F _Stacked-PR finalize: this PR's commits are **not** carried by the goal's deliverable PR #${leaf.number} (the stack chain was broken). Leaving this PR open so its work isn't lost \u2014 review and land it manually._`,
|
|
8609
|
-
ctx.cwd
|
|
8610
|
-
);
|
|
8611
|
-
continue;
|
|
8612
|
-
}
|
|
8613
|
-
process.stdout.write(`[goal-tick] closing intermediate stacked PR #${pr.number} (carried by deliverable leaf)
|
|
8614
|
-
`);
|
|
8615
|
-
const closed = closePr(
|
|
8616
|
-
pr.number,
|
|
8617
|
-
`_Stacked-PR finalize: this PR's content is carried by the goal's single deliverable PR #${leaf.number} (open against \`${goal.defaultBranch}\`, awaiting human merge)._`,
|
|
8618
|
-
ctx.cwd
|
|
8619
|
-
);
|
|
8620
|
-
if (!closed.ok) {
|
|
8621
|
-
process.stderr.write(`[goal-tick] finalizeGoal: closePr #${pr.number} failed: ${closed.error}
|
|
8622
|
-
`);
|
|
8623
|
-
}
|
|
8624
|
-
}
|
|
8625
|
-
const leafIssues = new Set(prIssueNumbers(leaf));
|
|
8626
|
-
const openIssues = (goal.childTasks ?? []).filter((t) => t.state === "OPEN");
|
|
8627
|
-
for (const t of openIssues) {
|
|
8628
|
-
if (uncarriedIssues.has(t.number)) {
|
|
8629
|
-
process.stderr.write(
|
|
8630
|
-
`[goal-tick] finalizeGoal: NOT closing task issue #${t.number} \u2014 its PR's work is not carried by the deliverable (broken stack)
|
|
8631
|
-
`
|
|
8632
|
-
);
|
|
8633
|
-
continue;
|
|
8634
|
-
}
|
|
8635
|
-
if (leafIssues.has(t.number)) {
|
|
8636
|
-
process.stdout.write(
|
|
8637
|
-
`[goal-tick] leaving leaf task issue #${t.number} OPEN as the review anchor for deliverable PR #${leaf.number}
|
|
8638
|
-
`
|
|
8639
|
-
);
|
|
8640
|
-
commentOnIssue(
|
|
8641
|
-
t.number,
|
|
8642
|
-
`_Goal \`${goal.id}\` finalized \u2014 this task's PR #${leaf.number} (open against \`${goal.defaultBranch}\`) is the goal's single deliverable and carries every task's changes. This issue stays open as the review anchor; merge PR #${leaf.number} to ship._`,
|
|
8643
|
-
ctx.cwd
|
|
8644
|
-
);
|
|
8645
|
-
continue;
|
|
8646
|
-
}
|
|
8647
|
-
process.stdout.write(
|
|
8648
|
-
`[goal-tick] closing task issue #${t.number} (goal finalized \u2014 carried by PR #${leaf.number})
|
|
8649
|
-
`
|
|
8650
|
-
);
|
|
8651
|
-
const closed = closeIssue(
|
|
8652
|
-
t.number,
|
|
8653
|
-
{
|
|
8654
|
-
comment: `_Goal \`${goal.id}\` finalized \u2014 its single deliverable PR #${leaf.number} (open against \`${goal.defaultBranch}\`) carries this task's changes. Merge that PR to ship._`,
|
|
8655
|
-
reason: "completed"
|
|
8656
|
-
},
|
|
8657
|
-
ctx.cwd
|
|
8658
|
-
);
|
|
8659
|
-
if (!closed.ok) {
|
|
8660
|
-
process.stderr.write(`[goal-tick] finalizeGoal: closeIssue #${t.number} failed: ${closed.error}
|
|
8661
|
-
`);
|
|
8662
|
-
}
|
|
8800
|
+
if (!hasFailedBefore) {
|
|
8801
|
+
ctx.output.exitCode = 1;
|
|
8802
|
+
ctx.output.reason = "intentional first-attempt failure for task job live test";
|
|
8803
|
+
return;
|
|
8663
8804
|
}
|
|
8664
|
-
|
|
8805
|
+
ctx.output.exitCode = 0;
|
|
8806
|
+
ctx.output.reason = "task job live test succeeded after prior failure";
|
|
8665
8807
|
};
|
|
8666
8808
|
}
|
|
8667
8809
|
});
|
|
@@ -8721,7 +8863,7 @@ var init_finalizeTerminal = __esm({
|
|
|
8721
8863
|
});
|
|
8722
8864
|
|
|
8723
8865
|
// src/scripts/finishFlow.ts
|
|
8724
|
-
import { execFileSync as
|
|
8866
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
8725
8867
|
var TERMINAL_PHASE, API_TIMEOUT_MS5, STATUS_ICON, finishFlow;
|
|
8726
8868
|
var init_finishFlow = __esm({
|
|
8727
8869
|
"src/scripts/finishFlow.ts"() {
|
|
@@ -8768,7 +8910,7 @@ var init_finishFlow = __esm({
|
|
|
8768
8910
|
**PR:** ${state.core.prUrl}` : "";
|
|
8769
8911
|
const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
8770
8912
|
try {
|
|
8771
|
-
|
|
8913
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
8772
8914
|
timeout: API_TIMEOUT_MS5,
|
|
8773
8915
|
cwd: ctx.cwd,
|
|
8774
8916
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -8800,9 +8942,9 @@ var init_finishFlow = __esm({
|
|
|
8800
8942
|
});
|
|
8801
8943
|
|
|
8802
8944
|
// src/branch.ts
|
|
8803
|
-
import { execFileSync as
|
|
8945
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
8804
8946
|
function git3(args, cwd) {
|
|
8805
|
-
return
|
|
8947
|
+
return execFileSync15("git", args, {
|
|
8806
8948
|
encoding: "utf-8",
|
|
8807
8949
|
timeout: 3e4,
|
|
8808
8950
|
cwd,
|
|
@@ -8819,11 +8961,11 @@ function getCurrentBranch(cwd) {
|
|
|
8819
8961
|
}
|
|
8820
8962
|
function resetWorkingTree2(cwd) {
|
|
8821
8963
|
try {
|
|
8822
|
-
|
|
8964
|
+
execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
8823
8965
|
} catch {
|
|
8824
8966
|
}
|
|
8825
8967
|
try {
|
|
8826
|
-
|
|
8968
|
+
execFileSync15("git", ["clean", "-fd", "-e", ".kody"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
8827
8969
|
} catch {
|
|
8828
8970
|
}
|
|
8829
8971
|
}
|
|
@@ -8835,11 +8977,11 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
8835
8977
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
8836
8978
|
};
|
|
8837
8979
|
try {
|
|
8838
|
-
|
|
8980
|
+
execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
8839
8981
|
} catch {
|
|
8840
8982
|
}
|
|
8841
8983
|
try {
|
|
8842
|
-
|
|
8984
|
+
execFileSync15("git", ["clean", "-fd", "-e", ".kody"], {
|
|
8843
8985
|
cwd,
|
|
8844
8986
|
env,
|
|
8845
8987
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -8847,7 +8989,7 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
8847
8989
|
});
|
|
8848
8990
|
} catch {
|
|
8849
8991
|
}
|
|
8850
|
-
|
|
8992
|
+
execFileSync15("gh", ["pr", "checkout", String(prNumber)], {
|
|
8851
8993
|
cwd,
|
|
8852
8994
|
env,
|
|
8853
8995
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -8880,7 +9022,7 @@ function mergeBase(baseBranch, cwd) {
|
|
|
8880
9022
|
}
|
|
8881
9023
|
function restoreKodyAssets(cwd) {
|
|
8882
9024
|
try {
|
|
8883
|
-
|
|
9025
|
+
execFileSync15("git", ["checkout", "HEAD", "--", ".kody"], {
|
|
8884
9026
|
cwd,
|
|
8885
9027
|
stdio: ["ignore", "pipe", "pipe"],
|
|
8886
9028
|
timeout: 3e4
|
|
@@ -8994,14 +9136,14 @@ var init_branch = __esm({
|
|
|
8994
9136
|
});
|
|
8995
9137
|
|
|
8996
9138
|
// src/workflow.ts
|
|
8997
|
-
import { execFileSync as
|
|
9139
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
8998
9140
|
function ghToken3() {
|
|
8999
9141
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
9000
9142
|
}
|
|
9001
9143
|
function gh3(args, cwd) {
|
|
9002
9144
|
const token = ghToken3();
|
|
9003
9145
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
9004
|
-
return
|
|
9146
|
+
return execFileSync16("gh", args, {
|
|
9005
9147
|
encoding: "utf-8",
|
|
9006
9148
|
timeout: GH_TIMEOUT_MS,
|
|
9007
9149
|
cwd,
|
|
@@ -9215,66 +9357,14 @@ var init_fixFlow = __esm({
|
|
|
9215
9357
|
}
|
|
9216
9358
|
});
|
|
9217
9359
|
|
|
9218
|
-
// src/scripts/handleAbandonedGoal.ts
|
|
9219
|
-
var handleAbandonedGoal;
|
|
9220
|
-
var init_handleAbandonedGoal = __esm({
|
|
9221
|
-
"src/scripts/handleAbandonedGoal.ts"() {
|
|
9222
|
-
"use strict";
|
|
9223
|
-
init_operations();
|
|
9224
|
-
handleAbandonedGoal = async (ctx) => {
|
|
9225
|
-
const goal = ctx.data.goal;
|
|
9226
|
-
if (!goal || goal.state !== "abandoned") return;
|
|
9227
|
-
process.stdout.write(`[goal-tick] ${goal.id} is abandoned \u2014 running cleanup
|
|
9228
|
-
`);
|
|
9229
|
-
const issues = listGoalIssues(goal.id, ctx.cwd);
|
|
9230
|
-
if (!issues.ok) {
|
|
9231
|
-
process.stderr.write(`[goal-tick] handleAbandonedGoal: list issues failed: ${issues.error}
|
|
9232
|
-
`);
|
|
9233
|
-
} else {
|
|
9234
|
-
const issueNumbers = /* @__PURE__ */ new Set();
|
|
9235
|
-
for (const i of issues.value ?? []) {
|
|
9236
|
-
if (i.state !== "OPEN") continue;
|
|
9237
|
-
issueNumbers.add(i.number);
|
|
9238
|
-
const r = closeIssue(
|
|
9239
|
-
i.number,
|
|
9240
|
-
{
|
|
9241
|
-
comment: "_Goal abandoned \u2014 closing this task without dispatch._",
|
|
9242
|
-
reason: "not planned"
|
|
9243
|
-
},
|
|
9244
|
-
ctx.cwd
|
|
9245
|
-
);
|
|
9246
|
-
if (!r.ok) {
|
|
9247
|
-
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close #${i.number}: ${r.error}
|
|
9248
|
-
`);
|
|
9249
|
-
}
|
|
9250
|
-
}
|
|
9251
|
-
const prs = listOpenPrs(ctx.cwd);
|
|
9252
|
-
if (prs.ok) {
|
|
9253
|
-
for (const pr of prs.value ?? []) {
|
|
9254
|
-
const headMatch = pr.headRefName.match(/^(\d+)-/);
|
|
9255
|
-
const headIssue = headMatch ? Number.parseInt(headMatch[1], 10) : NaN;
|
|
9256
|
-
if (!Number.isFinite(headIssue) || !issueNumbers.has(headIssue)) continue;
|
|
9257
|
-
const r = closePr(pr.number, "_Goal abandoned \u2014 closing stacked PR._", ctx.cwd);
|
|
9258
|
-
if (!r.ok) {
|
|
9259
|
-
process.stderr.write(`[goal-tick] handleAbandonedGoal: failed to close PR #${pr.number}: ${r.error}
|
|
9260
|
-
`);
|
|
9261
|
-
}
|
|
9262
|
-
}
|
|
9263
|
-
}
|
|
9264
|
-
}
|
|
9265
|
-
goal.state = "closed";
|
|
9266
|
-
};
|
|
9267
|
-
}
|
|
9268
|
-
});
|
|
9269
|
-
|
|
9270
9360
|
// src/scripts/initFlow.ts
|
|
9271
|
-
import { execFileSync as
|
|
9272
|
-
import * as
|
|
9273
|
-
import * as
|
|
9361
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
9362
|
+
import * as fs31 from "fs";
|
|
9363
|
+
import * as path29 from "path";
|
|
9274
9364
|
function detectPackageManager(cwd) {
|
|
9275
|
-
if (
|
|
9276
|
-
if (
|
|
9277
|
-
if (
|
|
9365
|
+
if (fs31.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9366
|
+
if (fs31.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
|
|
9367
|
+
if (fs31.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
|
|
9278
9368
|
return "npm";
|
|
9279
9369
|
}
|
|
9280
9370
|
function qualityCommandsFor(pm) {
|
|
@@ -9294,7 +9384,7 @@ function schemaUrlFromPkg() {
|
|
|
9294
9384
|
function detectOwnerRepo(cwd) {
|
|
9295
9385
|
let url;
|
|
9296
9386
|
try {
|
|
9297
|
-
url =
|
|
9387
|
+
url = execFileSync17("git", ["remote", "get-url", "origin"], {
|
|
9298
9388
|
cwd,
|
|
9299
9389
|
encoding: "utf-8",
|
|
9300
9390
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9322,7 +9412,7 @@ function makeConfig(pm, ownerRepo, defaultBranch2) {
|
|
|
9322
9412
|
}
|
|
9323
9413
|
function defaultBranchFromGit(cwd) {
|
|
9324
9414
|
try {
|
|
9325
|
-
const ref =
|
|
9415
|
+
const ref = execFileSync17("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
9326
9416
|
cwd,
|
|
9327
9417
|
encoding: "utf-8",
|
|
9328
9418
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9330,7 +9420,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
9330
9420
|
return ref.replace("refs/remotes/origin/", "");
|
|
9331
9421
|
} catch {
|
|
9332
9422
|
try {
|
|
9333
|
-
return
|
|
9423
|
+
return execFileSync17("git", ["branch", "--show-current"], {
|
|
9334
9424
|
cwd,
|
|
9335
9425
|
encoding: "utf-8",
|
|
9336
9426
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9346,51 +9436,51 @@ function performInit(cwd, force) {
|
|
|
9346
9436
|
const pm = detectPackageManager(cwd);
|
|
9347
9437
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
9348
9438
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
9349
|
-
const configPath =
|
|
9350
|
-
if (
|
|
9439
|
+
const configPath = path29.join(cwd, "kody.config.json");
|
|
9440
|
+
if (fs31.existsSync(configPath) && !force) {
|
|
9351
9441
|
skipped.push("kody.config.json");
|
|
9352
9442
|
} else {
|
|
9353
9443
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
|
|
9354
|
-
|
|
9444
|
+
fs31.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
9355
9445
|
`);
|
|
9356
9446
|
wrote.push("kody.config.json");
|
|
9357
9447
|
}
|
|
9358
|
-
const workflowDir =
|
|
9359
|
-
const workflowPath =
|
|
9360
|
-
if (
|
|
9448
|
+
const workflowDir = path29.join(cwd, ".github", "workflows");
|
|
9449
|
+
const workflowPath = path29.join(workflowDir, "kody.yml");
|
|
9450
|
+
if (fs31.existsSync(workflowPath) && !force) {
|
|
9361
9451
|
skipped.push(".github/workflows/kody.yml");
|
|
9362
9452
|
} else {
|
|
9363
|
-
|
|
9364
|
-
|
|
9453
|
+
fs31.mkdirSync(workflowDir, { recursive: true });
|
|
9454
|
+
fs31.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
9365
9455
|
wrote.push(".github/workflows/kody.yml");
|
|
9366
9456
|
}
|
|
9367
9457
|
const builtinJobs = listBuiltinJobs();
|
|
9368
9458
|
if (builtinJobs.length > 0) {
|
|
9369
|
-
const jobsDir =
|
|
9370
|
-
|
|
9459
|
+
const jobsDir = path29.join(cwd, ".kody", "duties");
|
|
9460
|
+
fs31.mkdirSync(jobsDir, { recursive: true });
|
|
9371
9461
|
for (const job of builtinJobs) {
|
|
9372
|
-
const targetDir =
|
|
9373
|
-
const relProfile =
|
|
9374
|
-
const relBody =
|
|
9375
|
-
if (
|
|
9462
|
+
const targetDir = path29.join(jobsDir, job.slug);
|
|
9463
|
+
const relProfile = path29.join(".kody", "duties", job.slug, "profile.json");
|
|
9464
|
+
const relBody = path29.join(".kody", "duties", job.slug, "duty.md");
|
|
9465
|
+
if (fs31.existsSync(targetDir) && fs31.existsSync(path29.join(targetDir, "profile.json")) && !force) {
|
|
9376
9466
|
skipped.push(relProfile);
|
|
9377
9467
|
skipped.push(relBody);
|
|
9378
9468
|
continue;
|
|
9379
9469
|
}
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9470
|
+
fs31.mkdirSync(targetDir, { recursive: true });
|
|
9471
|
+
fs31.writeFileSync(path29.join(targetDir, "profile.json"), fs31.readFileSync(job.profilePath, "utf-8"));
|
|
9472
|
+
fs31.writeFileSync(path29.join(targetDir, "duty.md"), fs31.readFileSync(job.bodyPath, "utf-8"));
|
|
9383
9473
|
wrote.push(relProfile);
|
|
9384
9474
|
wrote.push(relBody);
|
|
9385
9475
|
}
|
|
9386
9476
|
}
|
|
9387
|
-
const staffDir =
|
|
9388
|
-
const staffPath =
|
|
9389
|
-
if (
|
|
9477
|
+
const staffDir = path29.join(cwd, ".kody", "staff");
|
|
9478
|
+
const staffPath = path29.join(staffDir, "kody.md");
|
|
9479
|
+
if (fs31.existsSync(staffPath) && !force) {
|
|
9390
9480
|
skipped.push(".kody/staff/kody.md");
|
|
9391
9481
|
} else {
|
|
9392
|
-
|
|
9393
|
-
|
|
9482
|
+
fs31.mkdirSync(staffDir, { recursive: true });
|
|
9483
|
+
fs31.writeFileSync(staffPath, DEFAULT_STAFF_PERSONA);
|
|
9394
9484
|
wrote.push(".kody/staff/kody.md");
|
|
9395
9485
|
}
|
|
9396
9486
|
for (const exe of listExecutables()) {
|
|
@@ -9401,12 +9491,12 @@ function performInit(cwd, force) {
|
|
|
9401
9491
|
continue;
|
|
9402
9492
|
}
|
|
9403
9493
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
9404
|
-
const target =
|
|
9405
|
-
if (
|
|
9494
|
+
const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
|
|
9495
|
+
if (fs31.existsSync(target) && !force) {
|
|
9406
9496
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9407
9497
|
continue;
|
|
9408
9498
|
}
|
|
9409
|
-
|
|
9499
|
+
fs31.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
9410
9500
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9411
9501
|
}
|
|
9412
9502
|
let labels;
|
|
@@ -9419,7 +9509,7 @@ function performInit(cwd, force) {
|
|
|
9419
9509
|
}
|
|
9420
9510
|
function renderScheduledWorkflow(name, cron) {
|
|
9421
9511
|
return `# Scheduled kody duty: ${name}
|
|
9422
|
-
# Generated by \`kody init\`. Regenerate with \`kody init --force\`.
|
|
9512
|
+
# Generated by \`kody-engine init\`. Regenerate with \`kody-engine init --force\`.
|
|
9423
9513
|
# Edit the cron below or the duty's implementation profile.json#schedule.
|
|
9424
9514
|
|
|
9425
9515
|
name: kody ${name}
|
|
@@ -9482,6 +9572,16 @@ on:
|
|
|
9482
9572
|
description: "GitHub issue number"
|
|
9483
9573
|
required: true
|
|
9484
9574
|
type: string
|
|
9575
|
+
duty:
|
|
9576
|
+
description: "Duty action to run (default: run)"
|
|
9577
|
+
required: false
|
|
9578
|
+
type: string
|
|
9579
|
+
default: ""
|
|
9580
|
+
executable:
|
|
9581
|
+
description: "Legacy alias for duty action"
|
|
9582
|
+
required: false
|
|
9583
|
+
type: string
|
|
9584
|
+
default: ""
|
|
9485
9585
|
issue_comment:
|
|
9486
9586
|
types: [created]
|
|
9487
9587
|
|
|
@@ -9527,7 +9627,7 @@ When a duty writes a report or dispatches work, keep the output factual and conc
|
|
|
9527
9627
|
const force = ctx.args.force === true;
|
|
9528
9628
|
const cwd = ctx.cwd;
|
|
9529
9629
|
const { wrote, skipped, labels } = performInit(cwd, force);
|
|
9530
|
-
process.stdout.write("\u2192 kody init\n");
|
|
9630
|
+
process.stdout.write("\u2192 kody-engine init\n");
|
|
9531
9631
|
for (const f of wrote) process.stdout.write(` wrote ${f}
|
|
9532
9632
|
`);
|
|
9533
9633
|
for (const f of skipped) process.stdout.write(` skipped ${f} (already exists; pass --force to overwrite)
|
|
@@ -9630,7 +9730,7 @@ var init_loadGoalState = __esm({
|
|
|
9630
9730
|
try {
|
|
9631
9731
|
const state = fetchGoalState(owner, repo, goalId, ctx.cwd);
|
|
9632
9732
|
if (!state) {
|
|
9633
|
-
process.stdout.write(`[goal-
|
|
9733
|
+
process.stdout.write(`[goal-manager] no goal state for ${goalId} on ${owner}/${repo}; nothing to tick
|
|
9634
9734
|
`);
|
|
9635
9735
|
ctx.skipAgent = true;
|
|
9636
9736
|
ctx.output.exitCode = 0;
|
|
@@ -9640,17 +9740,11 @@ var init_loadGoalState = __esm({
|
|
|
9640
9740
|
ctx.data.goal = {
|
|
9641
9741
|
id: goalId,
|
|
9642
9742
|
state: state.state,
|
|
9643
|
-
lastDispatchedIssue: state.lastDispatchedIssue,
|
|
9644
|
-
// Cache the full parsed object so saveGoalState can preserve `extra`.
|
|
9645
9743
|
raw: state,
|
|
9646
|
-
// `phase`, `childTasks`, `openTaskPrs`, `leafPr` are populated by
|
|
9647
|
-
// deriveGoalPhase later in the chain. Initialize to undefined so
|
|
9648
|
-
// runWhen on `data.goal.phase` can match correctly.
|
|
9649
|
-
phase: void 0,
|
|
9650
9744
|
defaultBranch: ctx.config.git.defaultBranch
|
|
9651
9745
|
};
|
|
9652
9746
|
} catch (err) {
|
|
9653
|
-
process.stdout.write(`[goal-
|
|
9747
|
+
process.stdout.write(`[goal-manager] ${err instanceof Error ? err.message : String(err)}
|
|
9654
9748
|
`);
|
|
9655
9749
|
ctx.skipAgent = true;
|
|
9656
9750
|
ctx.output.exitCode = 0;
|
|
@@ -9725,9 +9819,63 @@ var init_loadIssueStateComment = __esm({
|
|
|
9725
9819
|
}
|
|
9726
9820
|
});
|
|
9727
9821
|
|
|
9822
|
+
// src/staff.ts
|
|
9823
|
+
import * as fs32 from "fs";
|
|
9824
|
+
import * as path30 from "path";
|
|
9825
|
+
function stripFrontmatter(raw) {
|
|
9826
|
+
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
9827
|
+
return (match ? match[1] : raw).trim();
|
|
9828
|
+
}
|
|
9829
|
+
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
9830
|
+
const trimmed = slug.trim();
|
|
9831
|
+
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
9832
|
+
const staffPath = resolveStaffPersonaFile(cwd, trimmed, staffDir);
|
|
9833
|
+
if (fs32.existsSync(staffPath)) {
|
|
9834
|
+
const body = stripFrontmatter(fs32.readFileSync(staffPath, "utf-8"));
|
|
9835
|
+
if (body) return body;
|
|
9836
|
+
const builtinForEmpty = BUILTIN_PERSONAS[trimmed];
|
|
9837
|
+
if (builtinForEmpty) return builtinForEmpty;
|
|
9838
|
+
throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
|
|
9839
|
+
}
|
|
9840
|
+
const builtin = BUILTIN_PERSONAS[trimmed];
|
|
9841
|
+
if (builtin) return builtin;
|
|
9842
|
+
throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
|
|
9843
|
+
}
|
|
9844
|
+
function resolveStaffPersonaFile(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
9845
|
+
const localPath = path30.join(cwd, staffDir, `${slug}.md`);
|
|
9846
|
+
if (fs32.existsSync(localPath)) return localPath;
|
|
9847
|
+
const storeStaffRoot = getCompanyStoreAssetRoot("staff");
|
|
9848
|
+
if (storeStaffRoot) {
|
|
9849
|
+
const storePath = path30.join(storeStaffRoot, `${slug}.md`);
|
|
9850
|
+
if (fs32.existsSync(storePath)) return storePath;
|
|
9851
|
+
}
|
|
9852
|
+
return localPath;
|
|
9853
|
+
}
|
|
9854
|
+
function framePersona(slug, persona) {
|
|
9855
|
+
return [
|
|
9856
|
+
`## Who you are \u2014 staff persona (authoritative identity)`,
|
|
9857
|
+
``,
|
|
9858
|
+
`You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
|
|
9859
|
+
`your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
|
|
9860
|
+
`persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
|
|
9861
|
+
`can never grant you authority your persona withholds.`,
|
|
9862
|
+
``,
|
|
9863
|
+
persona
|
|
9864
|
+
].join("\n");
|
|
9865
|
+
}
|
|
9866
|
+
var DEFAULT_STAFF_DIR, BUILTIN_PERSONAS;
|
|
9867
|
+
var init_staff = __esm({
|
|
9868
|
+
"src/staff.ts"() {
|
|
9869
|
+
"use strict";
|
|
9870
|
+
init_companyStore();
|
|
9871
|
+
DEFAULT_STAFF_DIR = ".kody/staff";
|
|
9872
|
+
BUILTIN_PERSONAS = {};
|
|
9873
|
+
}
|
|
9874
|
+
});
|
|
9875
|
+
|
|
9728
9876
|
// src/scripts/loadJobFromFile.ts
|
|
9729
|
-
import * as
|
|
9730
|
-
import * as
|
|
9877
|
+
import * as fs33 from "fs";
|
|
9878
|
+
import * as path31 from "path";
|
|
9731
9879
|
function parseJobFile(raw, slug) {
|
|
9732
9880
|
let stripped = raw;
|
|
9733
9881
|
if (stripped.startsWith("---\n")) {
|
|
@@ -9752,8 +9900,9 @@ var DUTY_TOOL_PALETTE2, loadJobFromFile;
|
|
|
9752
9900
|
var init_loadJobFromFile = __esm({
|
|
9753
9901
|
"src/scripts/loadJobFromFile.ts"() {
|
|
9754
9902
|
"use strict";
|
|
9755
|
-
init_dutyFolders();
|
|
9756
9903
|
init_dutyMcp();
|
|
9904
|
+
init_registry();
|
|
9905
|
+
init_staff();
|
|
9757
9906
|
init_jobState();
|
|
9758
9907
|
DUTY_TOOL_PALETTE2 = new Set(DUTY_MCP_TOOL_NAMES);
|
|
9759
9908
|
loadJobFromFile = async (ctx, profile, args) => {
|
|
@@ -9764,9 +9913,9 @@ var init_loadJobFromFile = __esm({
|
|
|
9764
9913
|
if (!slug) {
|
|
9765
9914
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
9766
9915
|
}
|
|
9767
|
-
const duty =
|
|
9916
|
+
const duty = resolveDutyFolder(slug, path31.join(ctx.cwd, jobsDir));
|
|
9768
9917
|
if (!duty) {
|
|
9769
|
-
throw new Error(`loadJobFromFile: duty folder not found or incomplete: ${
|
|
9918
|
+
throw new Error(`loadJobFromFile: duty folder not found or incomplete: ${path31.join(ctx.cwd, jobsDir, slug)}`);
|
|
9770
9919
|
}
|
|
9771
9920
|
const { title, body, config } = duty;
|
|
9772
9921
|
const mentions = (config.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
@@ -9774,11 +9923,11 @@ var init_loadJobFromFile = __esm({
|
|
|
9774
9923
|
let workerTitle = "";
|
|
9775
9924
|
let workerPersona = "";
|
|
9776
9925
|
if (workerSlug) {
|
|
9777
|
-
const workerPath =
|
|
9778
|
-
if (!
|
|
9926
|
+
const workerPath = resolveStaffPersonaFile(ctx.cwd, workerSlug, workersDir);
|
|
9927
|
+
if (!fs33.existsSync(workerPath)) {
|
|
9779
9928
|
throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
|
|
9780
9929
|
}
|
|
9781
|
-
const workerRaw =
|
|
9930
|
+
const workerRaw = fs33.readFileSync(workerPath, "utf-8");
|
|
9782
9931
|
const parsed = parseJobFile(workerRaw, workerSlug);
|
|
9783
9932
|
workerTitle = parsed.title;
|
|
9784
9933
|
workerPersona = parsed.body;
|
|
@@ -9852,13 +10001,13 @@ ${truncate2(issue.body, FINDING_BODY_MAX_BYTES)}`;
|
|
|
9852
10001
|
});
|
|
9853
10002
|
|
|
9854
10003
|
// src/scripts/kodyVariables.ts
|
|
9855
|
-
import * as
|
|
9856
|
-
import * as
|
|
10004
|
+
import * as fs34 from "fs";
|
|
10005
|
+
import * as path32 from "path";
|
|
9857
10006
|
function readKodyVariables(cwd) {
|
|
9858
|
-
const full =
|
|
10007
|
+
const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
9859
10008
|
let raw;
|
|
9860
10009
|
try {
|
|
9861
|
-
raw =
|
|
10010
|
+
raw = fs34.readFileSync(full, "utf-8");
|
|
9862
10011
|
} catch {
|
|
9863
10012
|
return {};
|
|
9864
10013
|
}
|
|
@@ -9883,8 +10032,8 @@ var init_kodyVariables = __esm({
|
|
|
9883
10032
|
});
|
|
9884
10033
|
|
|
9885
10034
|
// src/scripts/loadQaContext.ts
|
|
9886
|
-
import * as
|
|
9887
|
-
import * as
|
|
10035
|
+
import * as fs35 from "fs";
|
|
10036
|
+
import * as path33 from "path";
|
|
9888
10037
|
function parseSlugList(value) {
|
|
9889
10038
|
const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
|
9890
10039
|
return inner.split(",").map(
|
|
@@ -9913,18 +10062,18 @@ function readProfileStaff(raw) {
|
|
|
9913
10062
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
9914
10063
|
}
|
|
9915
10064
|
function readProfile(cwd) {
|
|
9916
|
-
const dir =
|
|
9917
|
-
if (!
|
|
10065
|
+
const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
10066
|
+
if (!fs35.existsSync(dir)) return "";
|
|
9918
10067
|
let entries;
|
|
9919
10068
|
try {
|
|
9920
|
-
entries =
|
|
10069
|
+
entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
9921
10070
|
} catch {
|
|
9922
10071
|
return "";
|
|
9923
10072
|
}
|
|
9924
10073
|
const blocks = [];
|
|
9925
10074
|
for (const file of entries) {
|
|
9926
10075
|
try {
|
|
9927
|
-
const raw =
|
|
10076
|
+
const raw = fs35.readFileSync(path33.join(dir, file), "utf-8");
|
|
9928
10077
|
const { staff, body } = readProfileStaff(raw);
|
|
9929
10078
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
9930
10079
|
blocks.push(`## ${file}
|
|
@@ -9970,8 +10119,8 @@ var init_loadQaContext = __esm({
|
|
|
9970
10119
|
});
|
|
9971
10120
|
|
|
9972
10121
|
// src/taskContext.ts
|
|
9973
|
-
import * as
|
|
9974
|
-
import * as
|
|
10122
|
+
import * as fs36 from "fs";
|
|
10123
|
+
import * as path34 from "path";
|
|
9975
10124
|
function buildTaskContext(args) {
|
|
9976
10125
|
return {
|
|
9977
10126
|
schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
|
|
@@ -9986,10 +10135,10 @@ function buildTaskContext(args) {
|
|
|
9986
10135
|
}
|
|
9987
10136
|
function persistTaskContext(cwd, ctx) {
|
|
9988
10137
|
try {
|
|
9989
|
-
const dir =
|
|
9990
|
-
|
|
9991
|
-
const file =
|
|
9992
|
-
|
|
10138
|
+
const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
|
|
10139
|
+
fs36.mkdirSync(dir, { recursive: true });
|
|
10140
|
+
const file = path34.join(dir, "task-context.json");
|
|
10141
|
+
fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
9993
10142
|
`);
|
|
9994
10143
|
return file;
|
|
9995
10144
|
} catch (err) {
|
|
@@ -10077,8 +10226,7 @@ var init_loadTaskState = __esm({
|
|
|
10077
10226
|
});
|
|
10078
10227
|
|
|
10079
10228
|
// src/scripts/loadWorkerAdhoc.ts
|
|
10080
|
-
import * as
|
|
10081
|
-
import * as path33 from "path";
|
|
10229
|
+
import * as fs37 from "fs";
|
|
10082
10230
|
function resolveMessage(messageArg) {
|
|
10083
10231
|
const fromComment = readCommentBody();
|
|
10084
10232
|
if (fromComment) return stripDirective(fromComment);
|
|
@@ -10086,9 +10234,9 @@ function resolveMessage(messageArg) {
|
|
|
10086
10234
|
}
|
|
10087
10235
|
function readCommentBody() {
|
|
10088
10236
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
10089
|
-
if (!eventPath || !
|
|
10237
|
+
if (!eventPath || !fs37.existsSync(eventPath)) return "";
|
|
10090
10238
|
try {
|
|
10091
|
-
const event = JSON.parse(
|
|
10239
|
+
const event = JSON.parse(fs37.readFileSync(eventPath, "utf-8"));
|
|
10092
10240
|
return String(event.comment?.body ?? "");
|
|
10093
10241
|
} catch {
|
|
10094
10242
|
return "";
|
|
@@ -10133,17 +10281,18 @@ var loadWorkerAdhoc;
|
|
|
10133
10281
|
var init_loadWorkerAdhoc = __esm({
|
|
10134
10282
|
"src/scripts/loadWorkerAdhoc.ts"() {
|
|
10135
10283
|
"use strict";
|
|
10284
|
+
init_staff();
|
|
10136
10285
|
loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
10137
10286
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
10138
10287
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
10139
10288
|
if (!workerSlug) {
|
|
10140
10289
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
10141
10290
|
}
|
|
10142
|
-
const workerPath =
|
|
10143
|
-
if (!
|
|
10291
|
+
const workerPath = resolveStaffPersonaFile(ctx.cwd, workerSlug, workersDir);
|
|
10292
|
+
if (!fs37.existsSync(workerPath)) {
|
|
10144
10293
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
10145
10294
|
}
|
|
10146
|
-
const { title, body } = parsePersona(
|
|
10295
|
+
const { title, body } = parsePersona(fs37.readFileSync(workerPath, "utf-8"), workerSlug);
|
|
10147
10296
|
const message = resolveMessage(ctx.args.message);
|
|
10148
10297
|
if (!message) {
|
|
10149
10298
|
throw new Error(
|
|
@@ -10173,6 +10322,37 @@ var init_markFlowSuccess = __esm({
|
|
|
10173
10322
|
}
|
|
10174
10323
|
});
|
|
10175
10324
|
|
|
10325
|
+
// src/goal/operations.ts
|
|
10326
|
+
function fail(err) {
|
|
10327
|
+
if (err instanceof Error) {
|
|
10328
|
+
const lines = err.message.split("\n").filter(Boolean);
|
|
10329
|
+
return { ok: false, error: lines[0] ?? err.message };
|
|
10330
|
+
}
|
|
10331
|
+
return { ok: false, error: String(err) };
|
|
10332
|
+
}
|
|
10333
|
+
function commentOnIssue(issueNumber, body, cwd) {
|
|
10334
|
+
try {
|
|
10335
|
+
gh(["issue", "comment", String(issueNumber), "--body", body], { cwd });
|
|
10336
|
+
return { ok: true };
|
|
10337
|
+
} catch (err) {
|
|
10338
|
+
return fail(err);
|
|
10339
|
+
}
|
|
10340
|
+
}
|
|
10341
|
+
function mergePrSquash(prNumber, cwd) {
|
|
10342
|
+
try {
|
|
10343
|
+
gh(["pr", "merge", String(prNumber), "--squash", "--delete-branch"], { cwd });
|
|
10344
|
+
return { ok: true };
|
|
10345
|
+
} catch (err) {
|
|
10346
|
+
return fail(err);
|
|
10347
|
+
}
|
|
10348
|
+
}
|
|
10349
|
+
var init_operations = __esm({
|
|
10350
|
+
"src/goal/operations.ts"() {
|
|
10351
|
+
"use strict";
|
|
10352
|
+
init_issue();
|
|
10353
|
+
}
|
|
10354
|
+
});
|
|
10355
|
+
|
|
10176
10356
|
// src/scripts/mergeFlow.ts
|
|
10177
10357
|
function readPr(prNumber, cwd) {
|
|
10178
10358
|
const out = gh(["pr", "view", String(prNumber), "--json", "state,isDraft,mergeable,mergeStateStatus,title,url"], {
|
|
@@ -10286,7 +10466,7 @@ var init_mergeFlow = __esm({
|
|
|
10286
10466
|
});
|
|
10287
10467
|
|
|
10288
10468
|
// src/scripts/mergeReleasePr.ts
|
|
10289
|
-
import { execFileSync as
|
|
10469
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
10290
10470
|
function makeAction(type, payload) {
|
|
10291
10471
|
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10292
10472
|
}
|
|
@@ -10318,7 +10498,7 @@ var init_mergeReleasePr = __esm({
|
|
|
10318
10498
|
process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
|
|
10319
10499
|
`);
|
|
10320
10500
|
try {
|
|
10321
|
-
const out =
|
|
10501
|
+
const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
10322
10502
|
timeout: API_TIMEOUT_MS6,
|
|
10323
10503
|
cwd: ctx.cwd,
|
|
10324
10504
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -11114,7 +11294,7 @@ var init_promoteQaGoal = __esm({
|
|
|
11114
11294
|
});
|
|
11115
11295
|
|
|
11116
11296
|
// src/scripts/recordClassification.ts
|
|
11117
|
-
import { execFileSync as
|
|
11297
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
11118
11298
|
function parseClassification(prSummary) {
|
|
11119
11299
|
if (!prSummary) return null;
|
|
11120
11300
|
const classMatch = prSummary.match(/classification:\s*(feature|bug|spec|chore)\b/i);
|
|
@@ -11126,7 +11306,7 @@ function parseClassification(prSummary) {
|
|
|
11126
11306
|
}
|
|
11127
11307
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
11128
11308
|
try {
|
|
11129
|
-
|
|
11309
|
+
execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
11130
11310
|
cwd,
|
|
11131
11311
|
timeout: API_TIMEOUT_MS7,
|
|
11132
11312
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -11306,7 +11486,7 @@ var init_resolveArtifacts = __esm({
|
|
|
11306
11486
|
});
|
|
11307
11487
|
|
|
11308
11488
|
// src/scripts/resolveFlow.ts
|
|
11309
|
-
import { execFileSync as
|
|
11489
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
11310
11490
|
function buildPreferBlock(prefer, baseBranch) {
|
|
11311
11491
|
if (prefer !== "ours" && prefer !== "theirs") return "";
|
|
11312
11492
|
const keepSide = prefer === "ours" ? "HEAD (this PR branch)" : `origin/${baseBranch} (base branch)`;
|
|
@@ -11326,7 +11506,7 @@ function buildPreferBlock(prefer, baseBranch) {
|
|
|
11326
11506
|
}
|
|
11327
11507
|
function getConflictedFiles(cwd) {
|
|
11328
11508
|
try {
|
|
11329
|
-
const out =
|
|
11509
|
+
const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
11330
11510
|
encoding: "utf-8",
|
|
11331
11511
|
cwd,
|
|
11332
11512
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -11341,7 +11521,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
11341
11521
|
let total = 0;
|
|
11342
11522
|
for (const f of files) {
|
|
11343
11523
|
try {
|
|
11344
|
-
const content =
|
|
11524
|
+
const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
11345
11525
|
const snippet = `### ${f}
|
|
11346
11526
|
|
|
11347
11527
|
\`\`\`
|
|
@@ -11365,12 +11545,12 @@ function tryPostPr3(prNumber, body, cwd) {
|
|
|
11365
11545
|
function pushEmptyCommit(branch, cwd) {
|
|
11366
11546
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
11367
11547
|
try {
|
|
11368
|
-
|
|
11548
|
+
execFileSync20(
|
|
11369
11549
|
"git",
|
|
11370
11550
|
["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
|
|
11371
11551
|
{ cwd, env, stdio: ["ignore", "pipe", "pipe"] }
|
|
11372
11552
|
);
|
|
11373
|
-
|
|
11553
|
+
execFileSync20("git", ["push", "-u", "origin", branch], {
|
|
11374
11554
|
cwd,
|
|
11375
11555
|
env,
|
|
11376
11556
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -11556,10 +11736,10 @@ var init_resolvePreviewUrl = __esm({
|
|
|
11556
11736
|
});
|
|
11557
11737
|
|
|
11558
11738
|
// src/scripts/resolveQaUrl.ts
|
|
11559
|
-
import { execFileSync as
|
|
11739
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
11560
11740
|
function ghQuery(args, cwd) {
|
|
11561
11741
|
try {
|
|
11562
|
-
const out =
|
|
11742
|
+
const out = execFileSync21("gh", args, {
|
|
11563
11743
|
cwd,
|
|
11564
11744
|
stdio: ["ignore", "pipe", "pipe"],
|
|
11565
11745
|
encoding: "utf-8",
|
|
@@ -11636,7 +11816,7 @@ var init_resolveQaUrl = __esm({
|
|
|
11636
11816
|
});
|
|
11637
11817
|
|
|
11638
11818
|
// src/scripts/revertFlow.ts
|
|
11639
|
-
import { execFileSync as
|
|
11819
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
11640
11820
|
function buildCommitMessage(resolved) {
|
|
11641
11821
|
if (resolved.length === 1) {
|
|
11642
11822
|
const { full, subject } = resolved[0];
|
|
@@ -11649,7 +11829,7 @@ function buildPrSummary(resolved) {
|
|
|
11649
11829
|
return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
|
|
11650
11830
|
}
|
|
11651
11831
|
function git4(args, cwd) {
|
|
11652
|
-
return
|
|
11832
|
+
return execFileSync22("git", args, {
|
|
11653
11833
|
encoding: "utf-8",
|
|
11654
11834
|
timeout: 3e4,
|
|
11655
11835
|
cwd,
|
|
@@ -11659,7 +11839,7 @@ function git4(args, cwd) {
|
|
|
11659
11839
|
}
|
|
11660
11840
|
function isAncestorOfHead(sha, cwd) {
|
|
11661
11841
|
try {
|
|
11662
|
-
|
|
11842
|
+
execFileSync22("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
|
|
11663
11843
|
cwd,
|
|
11664
11844
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
11665
11845
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -11854,9 +12034,9 @@ var init_runFlow = __esm({
|
|
|
11854
12034
|
});
|
|
11855
12035
|
|
|
11856
12036
|
// src/scripts/previewBuildHelpers.ts
|
|
11857
|
-
import { createDecipheriv, createHash } from "crypto";
|
|
12037
|
+
import { createDecipheriv, createHash as createHash2 } from "crypto";
|
|
11858
12038
|
function shortHash(s) {
|
|
11859
|
-
return
|
|
12039
|
+
return createHash2("sha256").update(s).digest("hex").slice(0, 6);
|
|
11860
12040
|
}
|
|
11861
12041
|
function previewAppName(repo, pr) {
|
|
11862
12042
|
const [owner, name] = repo.split("/");
|
|
@@ -11910,7 +12090,7 @@ function formatPreviewComment(args) {
|
|
|
11910
12090
|
].join("\n");
|
|
11911
12091
|
}
|
|
11912
12092
|
function defaultImageTag(repo, ref) {
|
|
11913
|
-
return
|
|
12093
|
+
return createHash2("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
|
|
11914
12094
|
}
|
|
11915
12095
|
var NEVER_PASS_TO_BUILD;
|
|
11916
12096
|
var init_previewBuildHelpers = __esm({
|
|
@@ -11930,7 +12110,7 @@ var init_previewBuildHelpers = __esm({
|
|
|
11930
12110
|
// src/scripts/previewBuildRun.ts
|
|
11931
12111
|
import { spawn as spawn4 } from "child_process";
|
|
11932
12112
|
async function runCmd(cmd, args, opts = {}) {
|
|
11933
|
-
await new Promise((
|
|
12113
|
+
await new Promise((resolve7, reject) => {
|
|
11934
12114
|
const child = spawn4(cmd, args, {
|
|
11935
12115
|
cwd: opts.cwd,
|
|
11936
12116
|
env: { ...process.env, ...opts.env ?? {} },
|
|
@@ -11942,7 +12122,7 @@ async function runCmd(cmd, args, opts = {}) {
|
|
|
11942
12122
|
}
|
|
11943
12123
|
child.on("error", reject);
|
|
11944
12124
|
child.on("close", (code) => {
|
|
11945
|
-
if (code === 0)
|
|
12125
|
+
if (code === 0) resolve7();
|
|
11946
12126
|
else reject(new Error(`${cmd} ${args.join(" ")} exited ${code}`));
|
|
11947
12127
|
});
|
|
11948
12128
|
});
|
|
@@ -12014,12 +12194,12 @@ fi
|
|
|
12014
12194
|
|
|
12015
12195
|
// src/scripts/runPreviewBuild.ts
|
|
12016
12196
|
import { copyFile, writeFile } from "fs/promises";
|
|
12017
|
-
import * as
|
|
12197
|
+
import * as path35 from "path";
|
|
12018
12198
|
import { fileURLToPath } from "url";
|
|
12019
12199
|
function bundledDockerfilePath(mode) {
|
|
12020
|
-
const here =
|
|
12200
|
+
const here = path35.dirname(fileURLToPath(import.meta.url));
|
|
12021
12201
|
const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
|
|
12022
|
-
return
|
|
12202
|
+
return path35.join(here, "preview-build-templates", file);
|
|
12023
12203
|
}
|
|
12024
12204
|
function required(name) {
|
|
12025
12205
|
const v = (process.env[name] ?? "").trim();
|
|
@@ -12278,10 +12458,10 @@ var init_runPreviewBuild = __esm({
|
|
|
12278
12458
|
console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
|
|
12279
12459
|
if (Object.keys(buildEnv).length > 0) {
|
|
12280
12460
|
const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
12281
|
-
await writeFile(
|
|
12461
|
+
await writeFile(path35.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
|
|
12282
12462
|
`, "utf8");
|
|
12283
12463
|
}
|
|
12284
|
-
const consumerDockerfile =
|
|
12464
|
+
const consumerDockerfile = path35.join(ctx.cwd, "Dockerfile.preview");
|
|
12285
12465
|
const { stat } = await import("fs/promises");
|
|
12286
12466
|
let hasConsumerDockerfile = false;
|
|
12287
12467
|
try {
|
|
@@ -12461,8 +12641,8 @@ var init_tickShellRunner = __esm({
|
|
|
12461
12641
|
});
|
|
12462
12642
|
|
|
12463
12643
|
// src/scripts/runScheduledExecutableTick.ts
|
|
12464
|
-
import * as
|
|
12465
|
-
import * as
|
|
12644
|
+
import * as fs38 from "fs";
|
|
12645
|
+
import * as path36 from "path";
|
|
12466
12646
|
var runScheduledExecutableTick;
|
|
12467
12647
|
var init_runScheduledExecutableTick = __esm({
|
|
12468
12648
|
"src/scripts/runScheduledExecutableTick.ts"() {
|
|
@@ -12482,14 +12662,14 @@ var init_runScheduledExecutableTick = __esm({
|
|
|
12482
12662
|
ctx.output.reason = `runScheduledExecutableTick: ctx.args.${slugArg} must be non-empty duty slug`;
|
|
12483
12663
|
return;
|
|
12484
12664
|
}
|
|
12485
|
-
const duty = readDutyFolder(
|
|
12665
|
+
const duty = readDutyFolder(path36.join(ctx.cwd, jobsDir), slug);
|
|
12486
12666
|
if (!duty) {
|
|
12487
12667
|
ctx.output.exitCode = 99;
|
|
12488
|
-
ctx.output.reason = `runScheduledExecutableTick: duty folder not found or incomplete: ${
|
|
12668
|
+
ctx.output.reason = `runScheduledExecutableTick: duty folder not found or incomplete: ${path36.join(jobsDir, slug)}`;
|
|
12489
12669
|
return;
|
|
12490
12670
|
}
|
|
12491
|
-
const shellPath =
|
|
12492
|
-
if (!
|
|
12671
|
+
const shellPath = path36.join(profile.dir, shell);
|
|
12672
|
+
if (!fs38.existsSync(shellPath)) {
|
|
12493
12673
|
ctx.output.exitCode = 99;
|
|
12494
12674
|
ctx.output.reason = `runScheduledExecutableTick: shell not found: ${shell} (looked in ${profile.dir})`;
|
|
12495
12675
|
return;
|
|
@@ -12520,8 +12700,8 @@ var init_runScheduledExecutableTick = __esm({
|
|
|
12520
12700
|
});
|
|
12521
12701
|
|
|
12522
12702
|
// src/scripts/runTickScript.ts
|
|
12523
|
-
import * as
|
|
12524
|
-
import * as
|
|
12703
|
+
import * as fs39 from "fs";
|
|
12704
|
+
import * as path37 from "path";
|
|
12525
12705
|
var runTickScript;
|
|
12526
12706
|
var init_runTickScript = __esm({
|
|
12527
12707
|
"src/scripts/runTickScript.ts"() {
|
|
@@ -12540,10 +12720,10 @@ var init_runTickScript = __esm({
|
|
|
12540
12720
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
12541
12721
|
return;
|
|
12542
12722
|
}
|
|
12543
|
-
const duty = readDutyFolder(
|
|
12723
|
+
const duty = readDutyFolder(path37.join(ctx.cwd, jobsDir), slug);
|
|
12544
12724
|
if (!duty) {
|
|
12545
12725
|
ctx.output.exitCode = 99;
|
|
12546
|
-
ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${
|
|
12726
|
+
ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${path37.join(ctx.cwd, jobsDir, slug)}`;
|
|
12547
12727
|
return;
|
|
12548
12728
|
}
|
|
12549
12729
|
const tickScript = duty.config.tickScript;
|
|
@@ -12552,8 +12732,8 @@ var init_runTickScript = __esm({
|
|
|
12552
12732
|
ctx.output.reason = `runTickScript: duty ${slug} has no \`tickScript\` in profile.json \u2014 route via duty-tick instead`;
|
|
12553
12733
|
return;
|
|
12554
12734
|
}
|
|
12555
|
-
const scriptPath =
|
|
12556
|
-
if (!
|
|
12735
|
+
const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
|
|
12736
|
+
if (!fs39.existsSync(scriptPath)) {
|
|
12557
12737
|
ctx.output.exitCode = 99;
|
|
12558
12738
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
12559
12739
|
return;
|
|
@@ -12582,29 +12762,21 @@ var init_runTickScript = __esm({
|
|
|
12582
12762
|
}
|
|
12583
12763
|
});
|
|
12584
12764
|
|
|
12585
|
-
// src/scripts/
|
|
12586
|
-
var
|
|
12587
|
-
var
|
|
12588
|
-
"src/scripts/
|
|
12765
|
+
// src/scripts/saveManagedGoalState.ts
|
|
12766
|
+
var saveManagedGoalState;
|
|
12767
|
+
var init_saveManagedGoalState = __esm({
|
|
12768
|
+
"src/scripts/saveManagedGoalState.ts"() {
|
|
12589
12769
|
"use strict";
|
|
12590
12770
|
init_state2();
|
|
12591
|
-
|
|
12771
|
+
saveManagedGoalState = async (ctx) => {
|
|
12772
|
+
ctx.skipAgent = true;
|
|
12592
12773
|
const goal = ctx.data.goal;
|
|
12593
|
-
if (!goal)
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
}
|
|
12597
|
-
const prev = goal.raw;
|
|
12598
|
-
const changed = !prev || prev.state !== goal.state || prev.lastDispatchedIssue !== goal.lastDispatchedIssue;
|
|
12599
|
-
const updated = {
|
|
12600
|
-
...prev ?? { state: goal.state, extra: {} },
|
|
12601
|
-
state: goal.state,
|
|
12602
|
-
lastDispatchedIssue: goal.lastDispatchedIssue,
|
|
12603
|
-
updatedAt: changed ? nowIso() : prev?.updatedAt
|
|
12604
|
-
};
|
|
12774
|
+
if (!goal?.raw) return;
|
|
12775
|
+
const original = typeof ctx.data.goalOriginalStateText === "string" ? ctx.data.goalOriginalStateText : "";
|
|
12776
|
+
const changedBeforeTimestamp = original !== serializeGoalState(goal.raw);
|
|
12777
|
+
const updated = changedBeforeTimestamp ? { ...goal.raw, updatedAt: nowIso() } : goal.raw;
|
|
12605
12778
|
ctx.data.goalPersistState = updated;
|
|
12606
|
-
ctx.data.goalPersistChanged =
|
|
12607
|
-
ctx.skipAgent = true;
|
|
12779
|
+
ctx.data.goalPersistChanged = original !== serializeGoalState(updated);
|
|
12608
12780
|
};
|
|
12609
12781
|
}
|
|
12610
12782
|
});
|
|
@@ -12674,7 +12846,7 @@ var init_skipAgent = __esm({
|
|
|
12674
12846
|
});
|
|
12675
12847
|
|
|
12676
12848
|
// src/scripts/stageMergeConflicts.ts
|
|
12677
|
-
import { execFileSync as
|
|
12849
|
+
import { execFileSync as execFileSync23 } from "child_process";
|
|
12678
12850
|
var stageMergeConflicts;
|
|
12679
12851
|
var init_stageMergeConflicts = __esm({
|
|
12680
12852
|
"src/scripts/stageMergeConflicts.ts"() {
|
|
@@ -12682,7 +12854,7 @@ var init_stageMergeConflicts = __esm({
|
|
|
12682
12854
|
stageMergeConflicts = async (ctx) => {
|
|
12683
12855
|
if (ctx.data.agentDone === false) return;
|
|
12684
12856
|
try {
|
|
12685
|
-
|
|
12857
|
+
execFileSync23("git", ["add", "-A"], {
|
|
12686
12858
|
cwd: ctx.cwd,
|
|
12687
12859
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
12688
12860
|
stdio: "pipe"
|
|
@@ -12736,7 +12908,7 @@ var init_startFlow = __esm({
|
|
|
12736
12908
|
});
|
|
12737
12909
|
|
|
12738
12910
|
// src/scripts/syncFlow.ts
|
|
12739
|
-
import { execFileSync as
|
|
12911
|
+
import { execFileSync as execFileSync24 } from "child_process";
|
|
12740
12912
|
function restoreDone(prNumber, cwd) {
|
|
12741
12913
|
try {
|
|
12742
12914
|
setKodyLabel(prNumber, DONE2, cwd);
|
|
@@ -12753,7 +12925,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
12753
12925
|
}
|
|
12754
12926
|
function revParseHead(cwd) {
|
|
12755
12927
|
try {
|
|
12756
|
-
return
|
|
12928
|
+
return execFileSync24("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
12757
12929
|
} catch {
|
|
12758
12930
|
return "";
|
|
12759
12931
|
}
|
|
@@ -12896,7 +13068,7 @@ function stripAnsi2(s) {
|
|
|
12896
13068
|
return s.replace(ANSI_RE2, "");
|
|
12897
13069
|
}
|
|
12898
13070
|
function runCommand2(command, cwd) {
|
|
12899
|
-
return new Promise((
|
|
13071
|
+
return new Promise((resolve7) => {
|
|
12900
13072
|
const child = spawn5(command, {
|
|
12901
13073
|
cwd,
|
|
12902
13074
|
shell: true,
|
|
@@ -12923,11 +13095,11 @@ function runCommand2(command, cwd) {
|
|
|
12923
13095
|
}, TEST_TIMEOUT_MS);
|
|
12924
13096
|
child.on("exit", (code) => {
|
|
12925
13097
|
clearTimeout(timer);
|
|
12926
|
-
|
|
13098
|
+
resolve7({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
|
|
12927
13099
|
});
|
|
12928
13100
|
child.on("error", (err) => {
|
|
12929
13101
|
clearTimeout(timer);
|
|
12930
|
-
|
|
13102
|
+
resolve7({ exitCode: -1, output: err.message });
|
|
12931
13103
|
});
|
|
12932
13104
|
});
|
|
12933
13105
|
}
|
|
@@ -13094,10 +13266,10 @@ var init_verifyWithRetry = __esm({
|
|
|
13094
13266
|
});
|
|
13095
13267
|
|
|
13096
13268
|
// src/scripts/waitForCi.ts
|
|
13097
|
-
import { execFileSync as
|
|
13269
|
+
import { execFileSync as execFileSync25 } from "child_process";
|
|
13098
13270
|
function fetchChecks(prNumber, cwd) {
|
|
13099
13271
|
try {
|
|
13100
|
-
const raw =
|
|
13272
|
+
const raw = execFileSync25("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
|
|
13101
13273
|
encoding: "utf-8",
|
|
13102
13274
|
timeout: API_TIMEOUT_MS8,
|
|
13103
13275
|
cwd,
|
|
@@ -13332,21 +13504,21 @@ function lineStream(stream) {
|
|
|
13332
13504
|
tryDeliver();
|
|
13333
13505
|
});
|
|
13334
13506
|
return {
|
|
13335
|
-
next: (timeoutMs) => new Promise((
|
|
13507
|
+
next: (timeoutMs) => new Promise((resolve7) => {
|
|
13336
13508
|
if (queue.length > 0) {
|
|
13337
|
-
|
|
13509
|
+
resolve7(queue.shift());
|
|
13338
13510
|
return;
|
|
13339
13511
|
}
|
|
13340
13512
|
if (ended) {
|
|
13341
|
-
|
|
13513
|
+
resolve7(null);
|
|
13342
13514
|
return;
|
|
13343
13515
|
}
|
|
13344
|
-
waiter =
|
|
13516
|
+
waiter = resolve7;
|
|
13345
13517
|
const t = setTimeout(
|
|
13346
13518
|
() => {
|
|
13347
|
-
if (waiter ===
|
|
13519
|
+
if (waiter === resolve7) {
|
|
13348
13520
|
waiter = null;
|
|
13349
|
-
|
|
13521
|
+
resolve7(null);
|
|
13350
13522
|
}
|
|
13351
13523
|
},
|
|
13352
13524
|
Math.max(0, timeoutMs)
|
|
@@ -13486,7 +13658,7 @@ var init_writeJobStateFile = __esm({
|
|
|
13486
13658
|
});
|
|
13487
13659
|
|
|
13488
13660
|
// src/scripts/writeRunSummary.ts
|
|
13489
|
-
import * as
|
|
13661
|
+
import * as fs40 from "fs";
|
|
13490
13662
|
var writeRunSummary;
|
|
13491
13663
|
var init_writeRunSummary = __esm({
|
|
13492
13664
|
"src/scripts/writeRunSummary.ts"() {
|
|
@@ -13512,7 +13684,7 @@ var init_writeRunSummary = __esm({
|
|
|
13512
13684
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
13513
13685
|
lines.push("");
|
|
13514
13686
|
try {
|
|
13515
|
-
|
|
13687
|
+
fs40.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
13516
13688
|
`);
|
|
13517
13689
|
} catch {
|
|
13518
13690
|
}
|
|
@@ -13527,7 +13699,9 @@ var init_scripts = __esm({
|
|
|
13527
13699
|
"use strict";
|
|
13528
13700
|
init_abortUnfinishedGitOps();
|
|
13529
13701
|
init_advanceFlow();
|
|
13702
|
+
init_advanceManagedGoal();
|
|
13530
13703
|
init_appendCompanyActivity();
|
|
13704
|
+
init_applyDutyReports();
|
|
13531
13705
|
init_buildSyntheticPlugin();
|
|
13532
13706
|
init_checkCoverageWithRetry();
|
|
13533
13707
|
init_classifyByLabel();
|
|
@@ -13535,7 +13709,6 @@ var init_scripts = __esm({
|
|
|
13535
13709
|
init_commitGoalState();
|
|
13536
13710
|
init_composePrompt();
|
|
13537
13711
|
init_createQaGoal();
|
|
13538
|
-
init_deriveGoalPhase();
|
|
13539
13712
|
init_deriveQaScopeFromIssue();
|
|
13540
13713
|
init_diagMcp();
|
|
13541
13714
|
init_discoverQaContext();
|
|
@@ -13543,16 +13716,13 @@ var init_scripts = __esm({
|
|
|
13543
13716
|
init_dispatchClassified();
|
|
13544
13717
|
init_dispatchDutyFileTicks();
|
|
13545
13718
|
init_dispatchDutyTicks();
|
|
13546
|
-
init_dispatchNextTask();
|
|
13547
13719
|
init_dispatchNextTaskJob();
|
|
13548
13720
|
init_ensurePr();
|
|
13549
13721
|
init_failOnceTaskJob();
|
|
13550
|
-
init_finalizeGoal();
|
|
13551
13722
|
init_finalizeTerminal();
|
|
13552
13723
|
init_finishFlow();
|
|
13553
13724
|
init_fixCiFlow();
|
|
13554
13725
|
init_fixFlow();
|
|
13555
|
-
init_handleAbandonedGoal();
|
|
13556
13726
|
init_initFlow();
|
|
13557
13727
|
init_loadConventions();
|
|
13558
13728
|
init_loadCoverageRules();
|
|
@@ -13601,7 +13771,7 @@ var init_scripts = __esm({
|
|
|
13601
13771
|
init_runPreviewBuild();
|
|
13602
13772
|
init_runScheduledExecutableTick();
|
|
13603
13773
|
init_runTickScript();
|
|
13604
|
-
|
|
13774
|
+
init_saveManagedGoalState();
|
|
13605
13775
|
init_saveTaskState();
|
|
13606
13776
|
init_setCommentTarget();
|
|
13607
13777
|
init_setLifecycleLabel();
|
|
@@ -13661,12 +13831,9 @@ var init_scripts = __esm({
|
|
|
13661
13831
|
runScheduledExecutableTick,
|
|
13662
13832
|
runTickScript,
|
|
13663
13833
|
runPreviewBuild,
|
|
13834
|
+
advanceManagedGoal,
|
|
13664
13835
|
loadGoalState,
|
|
13665
|
-
|
|
13666
|
-
deriveGoalPhase,
|
|
13667
|
-
dispatchNextTask,
|
|
13668
|
-
finalizeGoal,
|
|
13669
|
-
saveGoalState
|
|
13836
|
+
saveManagedGoalState
|
|
13670
13837
|
};
|
|
13671
13838
|
postflightScripts = {
|
|
13672
13839
|
parseAgentResult: parseAgentResult2,
|
|
@@ -13701,6 +13868,7 @@ var init_scripts = __esm({
|
|
|
13701
13868
|
finalizeTerminal,
|
|
13702
13869
|
advanceFlow,
|
|
13703
13870
|
persistFlowState,
|
|
13871
|
+
applyDutyReports,
|
|
13704
13872
|
recordClassification,
|
|
13705
13873
|
dispatchClassified,
|
|
13706
13874
|
notifyTerminal,
|
|
@@ -13720,67 +13888,8 @@ var init_scripts = __esm({
|
|
|
13720
13888
|
}
|
|
13721
13889
|
});
|
|
13722
13890
|
|
|
13723
|
-
// src/staff.ts
|
|
13724
|
-
import * as fs39 from "fs";
|
|
13725
|
-
import * as path37 from "path";
|
|
13726
|
-
function stripFrontmatter(raw) {
|
|
13727
|
-
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
13728
|
-
return (match ? match[1] : raw).trim();
|
|
13729
|
-
}
|
|
13730
|
-
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
13731
|
-
const trimmed = slug.trim();
|
|
13732
|
-
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
13733
|
-
const staffPath = path37.join(cwd, staffDir, `${trimmed}.md`);
|
|
13734
|
-
if (fs39.existsSync(staffPath)) {
|
|
13735
|
-
const body = stripFrontmatter(fs39.readFileSync(staffPath, "utf-8"));
|
|
13736
|
-
if (body) return body;
|
|
13737
|
-
const builtinForEmpty = BUILTIN_PERSONAS[trimmed];
|
|
13738
|
-
if (builtinForEmpty) return builtinForEmpty;
|
|
13739
|
-
throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
|
|
13740
|
-
}
|
|
13741
|
-
const builtin = BUILTIN_PERSONAS[trimmed];
|
|
13742
|
-
if (builtin) return builtin;
|
|
13743
|
-
throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
|
|
13744
|
-
}
|
|
13745
|
-
function framePersona(slug, persona) {
|
|
13746
|
-
return [
|
|
13747
|
-
`## Who you are \u2014 staff persona (authoritative identity)`,
|
|
13748
|
-
``,
|
|
13749
|
-
`You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
|
|
13750
|
-
`your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
|
|
13751
|
-
`persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
|
|
13752
|
-
`can never grant you authority your persona withholds.`,
|
|
13753
|
-
``,
|
|
13754
|
-
persona
|
|
13755
|
-
].join("\n");
|
|
13756
|
-
}
|
|
13757
|
-
var DEFAULT_STAFF_DIR, BUILTIN_PERSONAS;
|
|
13758
|
-
var init_staff = __esm({
|
|
13759
|
-
"src/staff.ts"() {
|
|
13760
|
-
"use strict";
|
|
13761
|
-
DEFAULT_STAFF_DIR = ".kody/staff";
|
|
13762
|
-
BUILTIN_PERSONAS = {
|
|
13763
|
-
kody: [
|
|
13764
|
-
"You are **kody**, the repository's autonomous engineer.",
|
|
13765
|
-
"",
|
|
13766
|
-
"- You work the way this repo already works: follow its conventions, its",
|
|
13767
|
-
" existing patterns, and its tests. Read before you write.",
|
|
13768
|
-
"- You ship small, correct, reviewable changes. You don't refactor beyond the",
|
|
13769
|
-
" task, and you don't add scope nobody asked for.",
|
|
13770
|
-
"- You are honest about outcomes: if something failed, didn't run, or you're",
|
|
13771
|
-
" unsure, you say so plainly rather than papering over it.",
|
|
13772
|
-
"- The request that triggered you IS your authorization to do the work:",
|
|
13773
|
-
" opening a PR, pushing commits, and commenting are exactly what you're for \u2014",
|
|
13774
|
-
" do them without waiting for extra approval.",
|
|
13775
|
-
"- You still don't weaken security, delete work you didn't create, or take",
|
|
13776
|
-
" destructive actions beyond the task you were given."
|
|
13777
|
-
].join("\n")
|
|
13778
|
-
};
|
|
13779
|
-
}
|
|
13780
|
-
});
|
|
13781
|
-
|
|
13782
13891
|
// src/tools.ts
|
|
13783
|
-
import { execFileSync as
|
|
13892
|
+
import { execFileSync as execFileSync26 } from "child_process";
|
|
13784
13893
|
function verifyCliTools(tools, cwd) {
|
|
13785
13894
|
const out = [];
|
|
13786
13895
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -13817,7 +13926,7 @@ function verifyOne(tool5, cwd) {
|
|
|
13817
13926
|
}
|
|
13818
13927
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
13819
13928
|
try {
|
|
13820
|
-
const stdout =
|
|
13929
|
+
const stdout = execFileSync26("sh", ["-c", cmd], {
|
|
13821
13930
|
cwd,
|
|
13822
13931
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13823
13932
|
timeout: timeoutMs,
|
|
@@ -13845,7 +13954,7 @@ var init_tools = __esm({
|
|
|
13845
13954
|
|
|
13846
13955
|
// src/executor.ts
|
|
13847
13956
|
import { spawn as spawn7 } from "child_process";
|
|
13848
|
-
import * as
|
|
13957
|
+
import * as fs41 from "fs";
|
|
13849
13958
|
import * as path38 from "path";
|
|
13850
13959
|
function isMutatingPostflight(scriptName) {
|
|
13851
13960
|
return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
|
|
@@ -13950,6 +14059,8 @@ async function runExecutable(profileName, input) {
|
|
|
13950
14059
|
}
|
|
13951
14060
|
const perExecutableModel = config.agent.perExecutable?.[profileName];
|
|
13952
14061
|
const modelSpec = perExecutableModel ? perExecutableModel : profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
|
|
14062
|
+
const profileHasThinkingTokens = typeof profile.claudeCode.maxThinkingTokens === "number" && profile.claudeCode.maxThinkingTokens > 0;
|
|
14063
|
+
const reasoningEffort = config.agent.perExecutableReasoningEffort?.[profileName] ?? profile.claudeCode.reasoningEffort ?? (profileHasThinkingTokens ? void 0 : config.agent.reasoningEffort);
|
|
13953
14064
|
let model;
|
|
13954
14065
|
try {
|
|
13955
14066
|
model = parseProviderModel(modelSpec);
|
|
@@ -14025,6 +14136,7 @@ async function runExecutable(profileName, input) {
|
|
|
14025
14136
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
14026
14137
|
agents,
|
|
14027
14138
|
maxTurns: profile.claudeCode.maxTurns,
|
|
14139
|
+
reasoningEffort,
|
|
14028
14140
|
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|
|
14029
14141
|
maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
|
|
14030
14142
|
// DISCIPLINE leads so the stable, role-agnostic block sits at the front
|
|
@@ -14382,7 +14494,7 @@ function resolveProfilePath(profileName) {
|
|
|
14382
14494
|
// fallback
|
|
14383
14495
|
];
|
|
14384
14496
|
for (const c of candidates) {
|
|
14385
|
-
if (
|
|
14497
|
+
if (fs41.existsSync(c)) return c;
|
|
14386
14498
|
}
|
|
14387
14499
|
return candidates[0];
|
|
14388
14500
|
}
|
|
@@ -14481,7 +14593,7 @@ function resolveShellTimeoutMs(entry) {
|
|
|
14481
14593
|
async function runShellEntry(entry, ctx, profile) {
|
|
14482
14594
|
const shellName = entry.shell;
|
|
14483
14595
|
const shellPath = path38.join(profile.dir, shellName);
|
|
14484
|
-
if (!
|
|
14596
|
+
if (!fs41.existsSync(shellPath)) {
|
|
14485
14597
|
ctx.skipAgent = true;
|
|
14486
14598
|
ctx.output.exitCode = 99;
|
|
14487
14599
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -14519,14 +14631,14 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
14519
14631
|
let killTimer;
|
|
14520
14632
|
let escalateTimer;
|
|
14521
14633
|
const result = await new Promise(
|
|
14522
|
-
(
|
|
14634
|
+
(resolve7) => {
|
|
14523
14635
|
let settled = false;
|
|
14524
14636
|
const settle = (code, signal, spawnErr) => {
|
|
14525
14637
|
if (settled) return;
|
|
14526
14638
|
settled = true;
|
|
14527
14639
|
if (killTimer) clearTimeout(killTimer);
|
|
14528
14640
|
if (escalateTimer) clearTimeout(escalateTimer);
|
|
14529
|
-
|
|
14641
|
+
resolve7({ code, signal, spawnErr });
|
|
14530
14642
|
};
|
|
14531
14643
|
child.on("error", (err) => settle(null, null, err));
|
|
14532
14644
|
child.on("close", (code, signal) => settle(code, signal));
|
|
@@ -14562,6 +14674,11 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
14562
14674
|
if (prUrlMatch?.[1]) ctx.output.prUrl = prUrlMatch[1].trim();
|
|
14563
14675
|
const reasonMatch = stdout.match(/^KODY_REASON=(.+)$/m);
|
|
14564
14676
|
if (reasonMatch?.[1]) ctx.output.reason = reasonMatch[1].trim();
|
|
14677
|
+
const dutyReports = parseDutyReportsFromText(stdout);
|
|
14678
|
+
if (dutyReports.length > 0) {
|
|
14679
|
+
const prior = Array.isArray(ctx.data.dutyReports) ? ctx.data.dutyReports : [];
|
|
14680
|
+
ctx.data.dutyReports = [...prior, ...dutyReports];
|
|
14681
|
+
}
|
|
14565
14682
|
if (timedOut) {
|
|
14566
14683
|
ctx.skipAgent = true;
|
|
14567
14684
|
const seconds = Math.round(timeoutMs / 1e3);
|
|
@@ -14611,6 +14728,7 @@ var init_executor = __esm({
|
|
|
14611
14728
|
init_config();
|
|
14612
14729
|
init_container();
|
|
14613
14730
|
init_discipline();
|
|
14731
|
+
init_dutyReport();
|
|
14614
14732
|
init_events();
|
|
14615
14733
|
init_lifecycleLabels();
|
|
14616
14734
|
init_litellm();
|
|
@@ -14621,7 +14739,7 @@ var init_executor = __esm({
|
|
|
14621
14739
|
init_subagents();
|
|
14622
14740
|
init_task_artifacts();
|
|
14623
14741
|
init_tools();
|
|
14624
|
-
MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set(["commitAndPush", "ensurePr"]);
|
|
14742
|
+
MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set(["commitAndPush", "ensurePr", "applyDutyReports"]);
|
|
14625
14743
|
MAX_CHAIN_HOPS = 60;
|
|
14626
14744
|
DEFAULT_SHELL_TIMEOUT_MS = 3e5;
|
|
14627
14745
|
SIGKILL_GRACE_MS = 5e3;
|
|
@@ -14733,7 +14851,7 @@ async function runJob(job, base) {
|
|
|
14733
14851
|
}
|
|
14734
14852
|
function loadDutyContext(slug, cwd) {
|
|
14735
14853
|
if (!slug) return null;
|
|
14736
|
-
return
|
|
14854
|
+
return resolveDutyFolder(slug, path39.join(cwd, ".kody", "duties"));
|
|
14737
14855
|
}
|
|
14738
14856
|
function mintInstantJob(dispatch2, opts) {
|
|
14739
14857
|
return {
|
|
@@ -14762,7 +14880,6 @@ var DEFAULT_INSTANT_PERSONA, localJobSeq, InvalidJobError;
|
|
|
14762
14880
|
var init_job = __esm({
|
|
14763
14881
|
"src/job.ts"() {
|
|
14764
14882
|
"use strict";
|
|
14765
|
-
init_dutyFolders();
|
|
14766
14883
|
init_executor();
|
|
14767
14884
|
init_registry();
|
|
14768
14885
|
init_jobIdentity();
|
|
@@ -14885,7 +15002,7 @@ function translateOpenAISseToBrain(opts) {
|
|
|
14885
15002
|
}
|
|
14886
15003
|
|
|
14887
15004
|
// src/servers/brain-serve.ts
|
|
14888
|
-
import * as
|
|
15005
|
+
import * as fs44 from "fs";
|
|
14889
15006
|
import { createServer } from "http";
|
|
14890
15007
|
import * as path42 from "path";
|
|
14891
15008
|
|
|
@@ -14893,12 +15010,12 @@ import * as path42 from "path";
|
|
|
14893
15010
|
init_agent();
|
|
14894
15011
|
init_registry();
|
|
14895
15012
|
init_task_artifacts();
|
|
14896
|
-
import * as
|
|
14897
|
-
import * as
|
|
15013
|
+
import * as fs13 from "fs";
|
|
15014
|
+
import * as path13 from "path";
|
|
14898
15015
|
|
|
14899
15016
|
// src/chat/attachments.ts
|
|
14900
|
-
import * as
|
|
14901
|
-
import * as
|
|
15017
|
+
import * as fs10 from "fs";
|
|
15018
|
+
import * as path10 from "path";
|
|
14902
15019
|
var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
|
|
14903
15020
|
var EXT_BY_MIME = {
|
|
14904
15021
|
"image/png": "png",
|
|
@@ -14914,7 +15031,7 @@ function extFor(mime) {
|
|
|
14914
15031
|
return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
|
|
14915
15032
|
}
|
|
14916
15033
|
function attachmentsDir(cwd, sessionId) {
|
|
14917
|
-
return
|
|
15034
|
+
return path10.join(cwd, ".kody", "tmp", "attachments", sessionId);
|
|
14918
15035
|
}
|
|
14919
15036
|
function prepareAttachments(turns, cwd, sessionId) {
|
|
14920
15037
|
const imagePaths = [];
|
|
@@ -14931,11 +15048,11 @@ function prepareAttachments(turns, cwd, sessionId) {
|
|
|
14931
15048
|
if (!isImage) return `[File: ${name}]`;
|
|
14932
15049
|
try {
|
|
14933
15050
|
if (!dirEnsured) {
|
|
14934
|
-
|
|
15051
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
14935
15052
|
dirEnsured = true;
|
|
14936
15053
|
}
|
|
14937
|
-
const filePath =
|
|
14938
|
-
|
|
15054
|
+
const filePath = path10.join(dir, `${imageCounter}.${extFor(mime)}`);
|
|
15055
|
+
fs10.writeFileSync(filePath, Buffer.from(data, "base64"));
|
|
14939
15056
|
imageCounter += 1;
|
|
14940
15057
|
imagePaths.push(filePath);
|
|
14941
15058
|
return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it.]`;
|
|
@@ -14951,10 +15068,10 @@ function prepareAttachments(turns, cwd, sessionId) {
|
|
|
14951
15068
|
}
|
|
14952
15069
|
|
|
14953
15070
|
// src/chat/events.ts
|
|
14954
|
-
import * as
|
|
14955
|
-
import * as
|
|
15071
|
+
import * as fs11 from "fs";
|
|
15072
|
+
import * as path11 from "path";
|
|
14956
15073
|
function eventsFilePath(cwd, sessionId) {
|
|
14957
|
-
return
|
|
15074
|
+
return path11.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
|
|
14958
15075
|
}
|
|
14959
15076
|
var FileSink = class {
|
|
14960
15077
|
constructor(file) {
|
|
@@ -14962,8 +15079,8 @@ var FileSink = class {
|
|
|
14962
15079
|
}
|
|
14963
15080
|
file;
|
|
14964
15081
|
async emit(event) {
|
|
14965
|
-
|
|
14966
|
-
|
|
15082
|
+
fs11.mkdirSync(path11.dirname(this.file), { recursive: true });
|
|
15083
|
+
fs11.appendFileSync(this.file, `${JSON.stringify(event)}
|
|
14967
15084
|
`);
|
|
14968
15085
|
}
|
|
14969
15086
|
};
|
|
@@ -15017,14 +15134,14 @@ function makeRunId(sessionId, suffix) {
|
|
|
15017
15134
|
}
|
|
15018
15135
|
|
|
15019
15136
|
// src/chat/session.ts
|
|
15020
|
-
import * as
|
|
15021
|
-
import * as
|
|
15137
|
+
import * as fs12 from "fs";
|
|
15138
|
+
import * as path12 from "path";
|
|
15022
15139
|
function sessionFilePath(cwd, sessionId) {
|
|
15023
|
-
return
|
|
15140
|
+
return path12.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
|
|
15024
15141
|
}
|
|
15025
15142
|
function readMeta(file) {
|
|
15026
|
-
if (!
|
|
15027
|
-
const raw =
|
|
15143
|
+
if (!fs12.existsSync(file)) return null;
|
|
15144
|
+
const raw = fs12.readFileSync(file, "utf-8");
|
|
15028
15145
|
const firstLine2 = raw.split("\n", 1)[0]?.trim();
|
|
15029
15146
|
if (!firstLine2) return null;
|
|
15030
15147
|
try {
|
|
@@ -15037,8 +15154,8 @@ function readMeta(file) {
|
|
|
15037
15154
|
}
|
|
15038
15155
|
}
|
|
15039
15156
|
function readSession(file) {
|
|
15040
|
-
if (!
|
|
15041
|
-
const raw =
|
|
15157
|
+
if (!fs12.existsSync(file)) return [];
|
|
15158
|
+
const raw = fs12.readFileSync(file, "utf-8").trim();
|
|
15042
15159
|
if (!raw) return [];
|
|
15043
15160
|
const turns = [];
|
|
15044
15161
|
for (const line of raw.split("\n")) {
|
|
@@ -15054,14 +15171,14 @@ function readSession(file) {
|
|
|
15054
15171
|
return turns;
|
|
15055
15172
|
}
|
|
15056
15173
|
function appendTurn(file, turn) {
|
|
15057
|
-
|
|
15174
|
+
fs12.mkdirSync(path12.dirname(file), { recursive: true });
|
|
15058
15175
|
const line = JSON.stringify({
|
|
15059
15176
|
role: turn.role,
|
|
15060
15177
|
content: turn.content,
|
|
15061
15178
|
timestamp: turn.timestamp,
|
|
15062
15179
|
toolCalls: turn.toolCalls ?? []
|
|
15063
15180
|
});
|
|
15064
|
-
|
|
15181
|
+
fs12.appendFileSync(file, `${line}
|
|
15065
15182
|
`);
|
|
15066
15183
|
}
|
|
15067
15184
|
function seedInitialMessage(file, message) {
|
|
@@ -15180,7 +15297,7 @@ function buildExecutableCatalog() {
|
|
|
15180
15297
|
const entries = [];
|
|
15181
15298
|
for (const { name, profilePath } of discovered) {
|
|
15182
15299
|
try {
|
|
15183
|
-
const raw = JSON.parse(
|
|
15300
|
+
const raw = JSON.parse(fs13.readFileSync(profilePath, "utf-8"));
|
|
15184
15301
|
const describe = typeof raw.describe === "string" ? raw.describe : "";
|
|
15185
15302
|
const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
|
|
15186
15303
|
entries.push({ name, describe: firstSentence.trim() });
|
|
@@ -15349,10 +15466,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
15349
15466
|
var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
|
|
15350
15467
|
var MAX_INDEX_BYTES = 8e3;
|
|
15351
15468
|
function readMemoryIndexBlock(cwd) {
|
|
15352
|
-
const indexPath =
|
|
15469
|
+
const indexPath = path13.join(cwd, MEMORY_INDEX_REL);
|
|
15353
15470
|
let raw;
|
|
15354
15471
|
try {
|
|
15355
|
-
raw =
|
|
15472
|
+
raw = fs13.readFileSync(indexPath, "utf-8");
|
|
15356
15473
|
} catch {
|
|
15357
15474
|
return "";
|
|
15358
15475
|
}
|
|
@@ -15370,17 +15487,17 @@ function readMemoryIndexBlock(cwd) {
|
|
|
15370
15487
|
var CONTEXT_DIR_REL = ".kody/context";
|
|
15371
15488
|
var MAX_CONTEXT_BYTES = 12e3;
|
|
15372
15489
|
function readContextBlock(cwd) {
|
|
15373
|
-
const dir =
|
|
15490
|
+
const dir = path13.join(cwd, CONTEXT_DIR_REL);
|
|
15374
15491
|
let files;
|
|
15375
15492
|
try {
|
|
15376
|
-
files =
|
|
15493
|
+
files = fs13.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
15377
15494
|
} catch {
|
|
15378
15495
|
return "";
|
|
15379
15496
|
}
|
|
15380
15497
|
const sections = [];
|
|
15381
15498
|
for (const file of files) {
|
|
15382
15499
|
try {
|
|
15383
|
-
const content =
|
|
15500
|
+
const content = fs13.readFileSync(path13.join(dir, file), "utf-8").trim();
|
|
15384
15501
|
if (content) sections.push(`### ${file.replace(/\.md$/, "")}
|
|
15385
15502
|
|
|
15386
15503
|
${content}`);
|
|
@@ -15403,10 +15520,10 @@ _\u2026 (context truncated; see \`.kody/context/\` for the full text)_` : joined
|
|
|
15403
15520
|
var INSTRUCTIONS_REL = ".kody/instructions.md";
|
|
15404
15521
|
var MAX_INSTRUCTIONS_BYTES = 8e3;
|
|
15405
15522
|
function readInstructionsBlock(cwd) {
|
|
15406
|
-
const instructionsPath =
|
|
15523
|
+
const instructionsPath = path13.join(cwd, INSTRUCTIONS_REL);
|
|
15407
15524
|
let raw;
|
|
15408
15525
|
try {
|
|
15409
|
-
raw =
|
|
15526
|
+
raw = fs13.readFileSync(instructionsPath, "utf-8");
|
|
15410
15527
|
} catch {
|
|
15411
15528
|
return "";
|
|
15412
15529
|
}
|
|
@@ -15428,8 +15545,8 @@ _\u2026 (instructions truncated)_` : trimmed;
|
|
|
15428
15545
|
init_config();
|
|
15429
15546
|
|
|
15430
15547
|
// src/kody-cli.ts
|
|
15431
|
-
import { execFileSync as
|
|
15432
|
-
import * as
|
|
15548
|
+
import { execFileSync as execFileSync27 } from "child_process";
|
|
15549
|
+
import * as fs42 from "fs";
|
|
15433
15550
|
import * as path40 from "path";
|
|
15434
15551
|
|
|
15435
15552
|
// src/app-auth.ts
|
|
@@ -15507,7 +15624,7 @@ init_config();
|
|
|
15507
15624
|
|
|
15508
15625
|
// src/dispatch.ts
|
|
15509
15626
|
init_config();
|
|
15510
|
-
import * as
|
|
15627
|
+
import * as fs14 from "fs";
|
|
15511
15628
|
|
|
15512
15629
|
// src/cron-match.ts
|
|
15513
15630
|
var FIELD_BOUNDS = [
|
|
@@ -15609,10 +15726,10 @@ function autoDispatch(opts) {
|
|
|
15609
15726
|
}
|
|
15610
15727
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15611
15728
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
15612
|
-
if (!eventName || !eventPath || !
|
|
15729
|
+
if (!eventName || !eventPath || !fs14.existsSync(eventPath)) return null;
|
|
15613
15730
|
let event = {};
|
|
15614
15731
|
try {
|
|
15615
|
-
event = JSON.parse(
|
|
15732
|
+
event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
|
|
15616
15733
|
} catch {
|
|
15617
15734
|
return null;
|
|
15618
15735
|
}
|
|
@@ -15723,7 +15840,7 @@ function autoDispatchTyped(opts) {
|
|
|
15723
15840
|
if (legacy) return { kind: "route", ...legacy };
|
|
15724
15841
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15725
15842
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
15726
|
-
if (!eventName || !eventPath || !
|
|
15843
|
+
if (!eventName || !eventPath || !fs14.existsSync(eventPath)) {
|
|
15727
15844
|
return { kind: "silent", reason: "no GHA event context" };
|
|
15728
15845
|
}
|
|
15729
15846
|
if (eventName !== "issue_comment") {
|
|
@@ -15731,7 +15848,7 @@ function autoDispatchTyped(opts) {
|
|
|
15731
15848
|
}
|
|
15732
15849
|
let event = {};
|
|
15733
15850
|
try {
|
|
15734
|
-
event = JSON.parse(
|
|
15851
|
+
event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
|
|
15735
15852
|
} catch {
|
|
15736
15853
|
return { kind: "silent", reason: "GHA event payload unreadable" };
|
|
15737
15854
|
}
|
|
@@ -15775,7 +15892,7 @@ function dispatchScheduledWatches(opts) {
|
|
|
15775
15892
|
for (const exe of listExecutables()) {
|
|
15776
15893
|
let raw;
|
|
15777
15894
|
try {
|
|
15778
|
-
raw =
|
|
15895
|
+
raw = fs14.readFileSync(exe.profilePath, "utf-8");
|
|
15779
15896
|
} catch {
|
|
15780
15897
|
continue;
|
|
15781
15898
|
}
|
|
@@ -16025,14 +16142,14 @@ async function resolveAuthToken(env = process.env) {
|
|
|
16025
16142
|
return void 0;
|
|
16026
16143
|
}
|
|
16027
16144
|
function detectPackageManager2(cwd) {
|
|
16028
|
-
if (
|
|
16029
|
-
if (
|
|
16030
|
-
if (
|
|
16145
|
+
if (fs42.existsSync(path40.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
16146
|
+
if (fs42.existsSync(path40.join(cwd, "yarn.lock"))) return "yarn";
|
|
16147
|
+
if (fs42.existsSync(path40.join(cwd, "bun.lockb"))) return "bun";
|
|
16031
16148
|
return "npm";
|
|
16032
16149
|
}
|
|
16033
16150
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
16034
16151
|
try {
|
|
16035
|
-
|
|
16152
|
+
execFileSync27(cmd, args, {
|
|
16036
16153
|
cwd,
|
|
16037
16154
|
stdio: stream ? "inherit" : "pipe",
|
|
16038
16155
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -16045,7 +16162,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
16045
16162
|
}
|
|
16046
16163
|
function isOnPath(bin) {
|
|
16047
16164
|
try {
|
|
16048
|
-
|
|
16165
|
+
execFileSync27("which", [bin], { stdio: "pipe" });
|
|
16049
16166
|
return true;
|
|
16050
16167
|
} catch {
|
|
16051
16168
|
return false;
|
|
@@ -16086,7 +16203,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
16086
16203
|
} catch {
|
|
16087
16204
|
}
|
|
16088
16205
|
try {
|
|
16089
|
-
|
|
16206
|
+
execFileSync27("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
16090
16207
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
16091
16208
|
return 0;
|
|
16092
16209
|
} catch {
|
|
@@ -16096,16 +16213,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
16096
16213
|
}
|
|
16097
16214
|
function configureGitIdentity(cwd) {
|
|
16098
16215
|
try {
|
|
16099
|
-
const name =
|
|
16216
|
+
const name = execFileSync27("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
16100
16217
|
if (name) return;
|
|
16101
16218
|
} catch {
|
|
16102
16219
|
}
|
|
16103
16220
|
try {
|
|
16104
|
-
|
|
16221
|
+
execFileSync27("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
16105
16222
|
} catch {
|
|
16106
16223
|
}
|
|
16107
16224
|
try {
|
|
16108
|
-
|
|
16225
|
+
execFileSync27("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
16109
16226
|
cwd,
|
|
16110
16227
|
stdio: "pipe"
|
|
16111
16228
|
});
|
|
@@ -16117,8 +16234,8 @@ function postFailureTail(issueNumber, cwd, reason) {
|
|
|
16117
16234
|
const logPath = path40.join(cwd, ".kody", "last-run.jsonl");
|
|
16118
16235
|
let tail = "";
|
|
16119
16236
|
try {
|
|
16120
|
-
if (
|
|
16121
|
-
const content =
|
|
16237
|
+
if (fs42.existsSync(logPath)) {
|
|
16238
|
+
const content = fs42.readFileSync(logPath, "utf-8");
|
|
16122
16239
|
tail = content.slice(-3e3);
|
|
16123
16240
|
}
|
|
16124
16241
|
} catch {
|
|
@@ -16156,9 +16273,9 @@ async function runCi(argv) {
|
|
|
16156
16273
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
16157
16274
|
let manualWorkflowDispatch = false;
|
|
16158
16275
|
let forceRunAction = null;
|
|
16159
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
16276
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs42.existsSync(dispatchEventPath)) {
|
|
16160
16277
|
try {
|
|
16161
|
-
const evt = JSON.parse(
|
|
16278
|
+
const evt = JSON.parse(fs42.readFileSync(dispatchEventPath, "utf-8"));
|
|
16162
16279
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
16163
16280
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
16164
16281
|
const dutyInput = String(evt?.inputs?.duty ?? evt?.inputs?.executable ?? "").trim();
|
|
@@ -16458,7 +16575,7 @@ init_litellm();
|
|
|
16458
16575
|
init_repoWorkspace();
|
|
16459
16576
|
|
|
16460
16577
|
// src/scripts/brainTurnLog.ts
|
|
16461
|
-
import * as
|
|
16578
|
+
import * as fs43 from "fs";
|
|
16462
16579
|
import * as path41 from "path";
|
|
16463
16580
|
var live = /* @__PURE__ */ new Map();
|
|
16464
16581
|
function eventsPath(dir, chatId) {
|
|
@@ -16466,8 +16583,8 @@ function eventsPath(dir, chatId) {
|
|
|
16466
16583
|
}
|
|
16467
16584
|
function lastPersistedSeq(dir, chatId) {
|
|
16468
16585
|
const p = eventsPath(dir, chatId);
|
|
16469
|
-
if (!
|
|
16470
|
-
const lines =
|
|
16586
|
+
if (!fs43.existsSync(p)) return 0;
|
|
16587
|
+
const lines = fs43.readFileSync(p, "utf-8").split("\n").filter(Boolean);
|
|
16471
16588
|
if (lines.length === 0) return 0;
|
|
16472
16589
|
try {
|
|
16473
16590
|
return JSON.parse(lines[lines.length - 1]).seq || 0;
|
|
@@ -16477,9 +16594,9 @@ function lastPersistedSeq(dir, chatId) {
|
|
|
16477
16594
|
}
|
|
16478
16595
|
function readSince(dir, chatId, since) {
|
|
16479
16596
|
const p = eventsPath(dir, chatId);
|
|
16480
|
-
if (!
|
|
16597
|
+
if (!fs43.existsSync(p)) return [];
|
|
16481
16598
|
const out = [];
|
|
16482
|
-
for (const line of
|
|
16599
|
+
for (const line of fs43.readFileSync(p, "utf-8").split("\n")) {
|
|
16483
16600
|
if (!line) continue;
|
|
16484
16601
|
try {
|
|
16485
16602
|
const rec = JSON.parse(line);
|
|
@@ -16505,12 +16622,12 @@ function beginTurn(dir, chatId) {
|
|
|
16505
16622
|
};
|
|
16506
16623
|
live.set(chatId, state);
|
|
16507
16624
|
const p = eventsPath(dir, chatId);
|
|
16508
|
-
|
|
16625
|
+
fs43.mkdirSync(path41.dirname(p), { recursive: true });
|
|
16509
16626
|
return (event) => {
|
|
16510
16627
|
state.seq += 1;
|
|
16511
16628
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
16512
16629
|
try {
|
|
16513
|
-
|
|
16630
|
+
fs43.appendFileSync(p, `${JSON.stringify(rec)}
|
|
16514
16631
|
`);
|
|
16515
16632
|
} catch (err) {
|
|
16516
16633
|
process.stderr.write(
|
|
@@ -16549,7 +16666,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
|
|
|
16549
16666
|
event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
|
|
16550
16667
|
};
|
|
16551
16668
|
try {
|
|
16552
|
-
|
|
16669
|
+
fs43.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
|
|
16553
16670
|
`);
|
|
16554
16671
|
} catch {
|
|
16555
16672
|
}
|
|
@@ -16642,17 +16759,17 @@ function authOk(req, expected) {
|
|
|
16642
16759
|
return false;
|
|
16643
16760
|
}
|
|
16644
16761
|
function readJsonBody(req) {
|
|
16645
|
-
return new Promise((
|
|
16762
|
+
return new Promise((resolve7, reject) => {
|
|
16646
16763
|
const chunks = [];
|
|
16647
16764
|
req.on("data", (c) => chunks.push(c));
|
|
16648
16765
|
req.on("end", () => {
|
|
16649
16766
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
16650
16767
|
if (!raw.trim()) {
|
|
16651
|
-
|
|
16768
|
+
resolve7({});
|
|
16652
16769
|
return;
|
|
16653
16770
|
}
|
|
16654
16771
|
try {
|
|
16655
|
-
|
|
16772
|
+
resolve7(JSON.parse(raw));
|
|
16656
16773
|
} catch (err) {
|
|
16657
16774
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
16658
16775
|
}
|
|
@@ -16780,7 +16897,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
16780
16897
|
const repo = strField(body, "repo");
|
|
16781
16898
|
const repoToken = strField(body, "repoToken");
|
|
16782
16899
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
16783
|
-
|
|
16900
|
+
fs44.mkdirSync(path42.dirname(sessionFile), { recursive: true });
|
|
16784
16901
|
appendTurn(sessionFile, {
|
|
16785
16902
|
role: "user",
|
|
16786
16903
|
content: message,
|
|
@@ -16901,11 +17018,11 @@ async function brainServe(opts) {
|
|
|
16901
17018
|
model,
|
|
16902
17019
|
litellmUrl
|
|
16903
17020
|
});
|
|
16904
|
-
await new Promise((
|
|
17021
|
+
await new Promise((resolve7) => {
|
|
16905
17022
|
server.listen(port, "0.0.0.0", () => {
|
|
16906
17023
|
process.stdout.write(`[brain-serve] listening on 0.0.0.0:${port} (cwd=${opts.cwd})
|
|
16907
17024
|
`);
|
|
16908
|
-
|
|
17025
|
+
resolve7();
|
|
16909
17026
|
});
|
|
16910
17027
|
});
|
|
16911
17028
|
const shutdown = (signal) => {
|
|
@@ -17155,14 +17272,14 @@ async function startBrainProxy(opts) {
|
|
|
17155
17272
|
const { httpServer, handler } = buildBrainProxy(opts);
|
|
17156
17273
|
const port = opts.port ?? 0;
|
|
17157
17274
|
const host = opts.host ?? "127.0.0.1";
|
|
17158
|
-
await new Promise((
|
|
17275
|
+
await new Promise((resolve7) => httpServer.listen(port, host, () => resolve7()));
|
|
17159
17276
|
const addr = httpServer.address();
|
|
17160
17277
|
return {
|
|
17161
17278
|
httpServer,
|
|
17162
17279
|
port: addr.port,
|
|
17163
17280
|
url: `http://${host}:${addr.port}`,
|
|
17164
|
-
stop: () => new Promise((
|
|
17165
|
-
httpServer.close(() =>
|
|
17281
|
+
stop: () => new Promise((resolve7) => {
|
|
17282
|
+
httpServer.close(() => resolve7());
|
|
17166
17283
|
}),
|
|
17167
17284
|
handler
|
|
17168
17285
|
};
|
|
@@ -17312,23 +17429,23 @@ function buildMcpHttpServer(opts) {
|
|
|
17312
17429
|
httpServer,
|
|
17313
17430
|
routes,
|
|
17314
17431
|
port,
|
|
17315
|
-
stop: () => new Promise((
|
|
17432
|
+
stop: () => new Promise((resolve7) => {
|
|
17316
17433
|
let pending = transports.size;
|
|
17317
17434
|
if (pending === 0) {
|
|
17318
|
-
httpServer.close(() =>
|
|
17435
|
+
httpServer.close(() => resolve7());
|
|
17319
17436
|
return;
|
|
17320
17437
|
}
|
|
17321
17438
|
for (const transport of transports.values()) {
|
|
17322
17439
|
void transport.close().finally(() => {
|
|
17323
17440
|
pending--;
|
|
17324
|
-
if (pending === 0) httpServer.close(() =>
|
|
17441
|
+
if (pending === 0) httpServer.close(() => resolve7());
|
|
17325
17442
|
});
|
|
17326
17443
|
}
|
|
17327
17444
|
})
|
|
17328
17445
|
};
|
|
17329
17446
|
}
|
|
17330
17447
|
function listenMcpHttpServer(server, host = "127.0.0.1") {
|
|
17331
|
-
return new Promise((
|
|
17448
|
+
return new Promise((resolve7, reject) => {
|
|
17332
17449
|
server.httpServer.once("error", reject);
|
|
17333
17450
|
server.httpServer.listen(server.port, host, () => {
|
|
17334
17451
|
server.httpServer.off("error", reject);
|
|
@@ -17336,7 +17453,7 @@ function listenMcpHttpServer(server, host = "127.0.0.1") {
|
|
|
17336
17453
|
if (addr && typeof addr === "object") {
|
|
17337
17454
|
server.port = addr.port;
|
|
17338
17455
|
}
|
|
17339
|
-
|
|
17456
|
+
resolve7();
|
|
17340
17457
|
});
|
|
17341
17458
|
});
|
|
17342
17459
|
}
|
|
@@ -17419,18 +17536,18 @@ async function loadConfigSafe() {
|
|
|
17419
17536
|
}
|
|
17420
17537
|
|
|
17421
17538
|
// src/chat-cli.ts
|
|
17422
|
-
import { execFileSync as
|
|
17423
|
-
import * as
|
|
17539
|
+
import { execFileSync as execFileSync30 } from "child_process";
|
|
17540
|
+
import * as fs46 from "fs";
|
|
17424
17541
|
import * as path44 from "path";
|
|
17425
17542
|
|
|
17426
17543
|
// src/chat/modes/interactive.ts
|
|
17427
17544
|
init_issue();
|
|
17428
|
-
import { execFileSync as
|
|
17429
|
-
import * as
|
|
17545
|
+
import { execFileSync as execFileSync29 } from "child_process";
|
|
17546
|
+
import * as fs45 from "fs";
|
|
17430
17547
|
import * as path43 from "path";
|
|
17431
17548
|
|
|
17432
17549
|
// src/chat/inbox.ts
|
|
17433
|
-
import { execFileSync as
|
|
17550
|
+
import { execFileSync as execFileSync28 } from "child_process";
|
|
17434
17551
|
var DEFAULT_POLL_MS = 3e3;
|
|
17435
17552
|
async function waitForNextUserMessage(opts) {
|
|
17436
17553
|
const pollMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
@@ -17447,13 +17564,13 @@ async function waitForNextUserMessage(opts) {
|
|
|
17447
17564
|
try {
|
|
17448
17565
|
const branch = currentBranch(opts.cwd);
|
|
17449
17566
|
if (branch) {
|
|
17450
|
-
|
|
17451
|
-
|
|
17567
|
+
execFileSync28("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
|
|
17568
|
+
execFileSync28("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
|
|
17452
17569
|
cwd: opts.cwd,
|
|
17453
17570
|
stdio: "pipe"
|
|
17454
17571
|
});
|
|
17455
17572
|
} else {
|
|
17456
|
-
|
|
17573
|
+
execFileSync28("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
|
|
17457
17574
|
}
|
|
17458
17575
|
} catch (err) {
|
|
17459
17576
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -17475,11 +17592,11 @@ async function waitForNextUserMessage(opts) {
|
|
|
17475
17592
|
}
|
|
17476
17593
|
}
|
|
17477
17594
|
function sleep2(ms) {
|
|
17478
|
-
return new Promise((
|
|
17595
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
17479
17596
|
}
|
|
17480
17597
|
function currentBranch(cwd) {
|
|
17481
17598
|
try {
|
|
17482
|
-
const out =
|
|
17599
|
+
const out = execFileSync28("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
17483
17600
|
cwd,
|
|
17484
17601
|
stdio: ["ignore", "pipe", "ignore"]
|
|
17485
17602
|
});
|
|
@@ -17589,7 +17706,7 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
17589
17706
|
function commitTurn(cwd, sessionId, _verbose) {
|
|
17590
17707
|
const sessionRel = path43.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
17591
17708
|
const eventsRel = path43.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
17592
|
-
const rels = [sessionRel, eventsRel].filter((p) =>
|
|
17709
|
+
const rels = [sessionRel, eventsRel].filter((p) => fs45.existsSync(path43.join(cwd, p)));
|
|
17593
17710
|
if (rels.length === 0) return;
|
|
17594
17711
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
17595
17712
|
if (!repository) {
|
|
@@ -17602,7 +17719,7 @@ function commitTurn(cwd, sessionId, _verbose) {
|
|
|
17602
17719
|
const branch = defaultBranch(cwd) ?? "main";
|
|
17603
17720
|
for (const rel of rels) {
|
|
17604
17721
|
const repoPath = rel.split(path43.sep).join("/");
|
|
17605
|
-
const localText =
|
|
17722
|
+
const localText = fs45.readFileSync(path43.join(cwd, rel), "utf-8");
|
|
17606
17723
|
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
17607
17724
|
}
|
|
17608
17725
|
}
|
|
@@ -17663,7 +17780,7 @@ function putJsonlViaContents(repository, branch, repoPath, localText, sessionId,
|
|
|
17663
17780
|
}
|
|
17664
17781
|
function defaultBranch(cwd) {
|
|
17665
17782
|
try {
|
|
17666
|
-
const out =
|
|
17783
|
+
const out = execFileSync29("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], {
|
|
17667
17784
|
cwd,
|
|
17668
17785
|
stdio: ["ignore", "pipe", "ignore"]
|
|
17669
17786
|
});
|
|
@@ -17752,12 +17869,12 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
17752
17869
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
17753
17870
|
const tasksDir = path44.join(".kody", "tasks", safeSession);
|
|
17754
17871
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
17755
|
-
const paths = candidatePaths.filter((p) =>
|
|
17872
|
+
const paths = candidatePaths.filter((p) => fs46.existsSync(path44.join(cwd, p)));
|
|
17756
17873
|
if (paths.length === 0) return;
|
|
17757
17874
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
17758
17875
|
try {
|
|
17759
|
-
|
|
17760
|
-
|
|
17876
|
+
execFileSync30("git", ["add", "-f", ...paths], opts);
|
|
17877
|
+
execFileSync30("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
17761
17878
|
} catch (err) {
|
|
17762
17879
|
const msg = err instanceof Error ? err.message : String(err);
|
|
17763
17880
|
process.stderr.write(`[kody:chat] commit skipped: ${msg}
|
|
@@ -17849,7 +17966,7 @@ ${CHAT_HELP}`);
|
|
|
17849
17966
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
17850
17967
|
const meta = readMeta(sessionFile);
|
|
17851
17968
|
process.stdout.write(
|
|
17852
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
17969
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs46.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
17853
17970
|
`
|
|
17854
17971
|
);
|
|
17855
17972
|
try {
|
|
@@ -18590,14 +18707,14 @@ function sendJson2(res, status, body) {
|
|
|
18590
18707
|
res.end(JSON.stringify(body));
|
|
18591
18708
|
}
|
|
18592
18709
|
function readJsonBody2(req) {
|
|
18593
|
-
return new Promise((
|
|
18710
|
+
return new Promise((resolve7, reject) => {
|
|
18594
18711
|
const chunks = [];
|
|
18595
18712
|
req.on("data", (c) => chunks.push(c));
|
|
18596
18713
|
req.on("end", () => {
|
|
18597
18714
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
18598
|
-
if (!raw.trim()) return
|
|
18715
|
+
if (!raw.trim()) return resolve7({});
|
|
18599
18716
|
try {
|
|
18600
|
-
|
|
18717
|
+
resolve7(JSON.parse(raw));
|
|
18601
18718
|
} catch (err) {
|
|
18602
18719
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
18603
18720
|
}
|
|
@@ -18723,10 +18840,10 @@ async function poolServe() {
|
|
|
18723
18840
|
}
|
|
18724
18841
|
});
|
|
18725
18842
|
const apiHost = process.env.POOL_API_HOST ?? "::";
|
|
18726
|
-
await new Promise((
|
|
18843
|
+
await new Promise((resolve7) => {
|
|
18727
18844
|
server.listen(apiPort, apiHost, () => {
|
|
18728
18845
|
log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
|
|
18729
|
-
|
|
18846
|
+
resolve7();
|
|
18730
18847
|
});
|
|
18731
18848
|
});
|
|
18732
18849
|
const shutdown = (signal) => {
|
|
@@ -18744,7 +18861,7 @@ async function poolServe() {
|
|
|
18744
18861
|
|
|
18745
18862
|
// src/servers/runner-serve.ts
|
|
18746
18863
|
import { spawn as spawn8 } from "child_process";
|
|
18747
|
-
import * as
|
|
18864
|
+
import * as fs47 from "fs";
|
|
18748
18865
|
import { createServer as createServer5 } from "http";
|
|
18749
18866
|
var DEFAULT_PORT2 = 8080;
|
|
18750
18867
|
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
@@ -18765,17 +18882,17 @@ function authOk2(req, expected) {
|
|
|
18765
18882
|
return false;
|
|
18766
18883
|
}
|
|
18767
18884
|
function readJsonBody3(req) {
|
|
18768
|
-
return new Promise((
|
|
18885
|
+
return new Promise((resolve7, reject) => {
|
|
18769
18886
|
const chunks = [];
|
|
18770
18887
|
req.on("data", (c) => chunks.push(c));
|
|
18771
18888
|
req.on("end", () => {
|
|
18772
18889
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
18773
18890
|
if (!raw.trim()) {
|
|
18774
|
-
|
|
18891
|
+
resolve7({});
|
|
18775
18892
|
return;
|
|
18776
18893
|
}
|
|
18777
18894
|
try {
|
|
18778
|
-
|
|
18895
|
+
resolve7(JSON.parse(raw));
|
|
18779
18896
|
} catch (err) {
|
|
18780
18897
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
18781
18898
|
}
|
|
@@ -18824,8 +18941,8 @@ async function defaultRunJob(job) {
|
|
|
18824
18941
|
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
18825
18942
|
const branch = job.ref ?? "main";
|
|
18826
18943
|
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
18827
|
-
|
|
18828
|
-
|
|
18944
|
+
fs47.rmSync(workdir, { recursive: true, force: true });
|
|
18945
|
+
fs47.mkdirSync(workdir, { recursive: true });
|
|
18829
18946
|
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
18830
18947
|
const interactive = job.mode === "interactive";
|
|
18831
18948
|
const scheduled = job.mode === "scheduled";
|
|
@@ -18854,13 +18971,13 @@ async function defaultRunJob(job) {
|
|
|
18854
18971
|
...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
|
|
18855
18972
|
...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
|
|
18856
18973
|
};
|
|
18857
|
-
const run = (cmd, args, cwd) => new Promise((
|
|
18974
|
+
const run = (cmd, args, cwd) => new Promise((resolve7) => {
|
|
18858
18975
|
const child = spawn8(cmd, args, { stdio: "inherit", env: childEnv, cwd });
|
|
18859
|
-
child.on("exit", (code) =>
|
|
18976
|
+
child.on("exit", (code) => resolve7(code ?? 0));
|
|
18860
18977
|
child.on("error", (err) => {
|
|
18861
18978
|
process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
|
|
18862
18979
|
`);
|
|
18863
|
-
|
|
18980
|
+
resolve7(1);
|
|
18864
18981
|
});
|
|
18865
18982
|
});
|
|
18866
18983
|
process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
|
|
@@ -18937,11 +19054,11 @@ async function runnerServe() {
|
|
|
18937
19054
|
const port = Number(process.env.PORT ?? DEFAULT_PORT2);
|
|
18938
19055
|
const server = buildServer2({ apiKey });
|
|
18939
19056
|
const host = process.env.RUNNER_HOST ?? "::";
|
|
18940
|
-
await new Promise((
|
|
19057
|
+
await new Promise((resolve7) => {
|
|
18941
19058
|
server.listen(port, host, () => {
|
|
18942
19059
|
process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
|
|
18943
19060
|
`);
|
|
18944
|
-
|
|
19061
|
+
resolve7();
|
|
18945
19062
|
});
|
|
18946
19063
|
});
|
|
18947
19064
|
const shutdown = (signal) => {
|
|
@@ -18960,7 +19077,7 @@ async function runnerServe() {
|
|
|
18960
19077
|
init_config();
|
|
18961
19078
|
init_litellm();
|
|
18962
19079
|
import { spawn as spawn9 } from "child_process";
|
|
18963
|
-
function
|
|
19080
|
+
function parseTarget2(positional) {
|
|
18964
19081
|
if (!Array.isArray(positional) || positional.length === 0) return "none";
|
|
18965
19082
|
const first = String(positional[0]).toLowerCase();
|
|
18966
19083
|
if (first === "vscode" || first === "code") return "vscode";
|
|
@@ -18975,7 +19092,7 @@ function buildProxyEnv(url) {
|
|
|
18975
19092
|
};
|
|
18976
19093
|
}
|
|
18977
19094
|
async function serve(opts) {
|
|
18978
|
-
const target =
|
|
19095
|
+
const target = parseTarget2(opts.args);
|
|
18979
19096
|
const model = parseProviderModel(opts.config.agent.model);
|
|
18980
19097
|
const usesProxy = needsLitellmProxy(model);
|
|
18981
19098
|
let handle = null;
|
|
@@ -19010,14 +19127,14 @@ async function serve(opts) {
|
|
|
19010
19127
|
`);
|
|
19011
19128
|
const args = ["--dangerously-skip-permissions", "--model", model.model];
|
|
19012
19129
|
const child = spawn9("claude", args, { stdio: "inherit", env: editorEnv, cwd: opts.cwd });
|
|
19013
|
-
const exitCode = await new Promise((
|
|
19014
|
-
child.on("exit", (code) =>
|
|
19130
|
+
const exitCode = await new Promise((resolve7) => {
|
|
19131
|
+
child.on("exit", (code) => resolve7(code ?? 0));
|
|
19015
19132
|
child.on("error", (err) => {
|
|
19016
19133
|
process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
|
|
19017
19134
|
`);
|
|
19018
19135
|
process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
|
|
19019
19136
|
`);
|
|
19020
|
-
|
|
19137
|
+
resolve7(1);
|
|
19021
19138
|
});
|
|
19022
19139
|
});
|
|
19023
19140
|
killProxy();
|