@kody-ade/kody-engine 0.4.219 → 0.4.220
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 +3 -3
- package/dist/bin/kody.js +570 -641
- package/dist/duties/fix/duty.md +10 -0
- package/dist/duties/fix/profile.json +6 -0
- package/dist/duties/fix-ci/duty.md +10 -0
- package/dist/duties/fix-ci/profile.json +6 -0
- package/dist/duties/init/duty.md +10 -0
- package/dist/duties/init/profile.json +6 -0
- package/dist/duties/merge/duty.md +10 -0
- package/dist/duties/merge/profile.json +6 -0
- package/dist/duties/preview-build/duty.md +10 -0
- package/dist/duties/preview-build/profile.json +6 -0
- package/dist/duties/release/duty.md +10 -0
- package/dist/duties/release/profile.json +6 -0
- package/dist/duties/release-deploy/duty.md +10 -0
- package/dist/duties/release-deploy/profile.json +6 -0
- package/dist/duties/release-prepare/duty.md +10 -0
- package/dist/duties/release-prepare/profile.json +6 -0
- package/dist/duties/release-publish/duty.md +10 -0
- package/dist/duties/release-publish/profile.json +6 -0
- package/dist/duties/resolve/duty.md +10 -0
- package/dist/duties/resolve/profile.json +6 -0
- package/dist/duties/revert/duty.md +10 -0
- package/dist/duties/revert/profile.json +6 -0
- package/dist/duties/run/duty.md +10 -0
- package/dist/duties/run/profile.json +6 -0
- package/dist/duties/sync/duty.md +10 -0
- package/dist/duties/sync/profile.json +6 -0
- package/dist/duties/worker-ask/duty.md +10 -0
- package/dist/duties/worker-ask/profile.json +6 -0
- package/dist/executables/duty-scheduler/profile.json +1 -1
- package/dist/executables/duty-tick/profile.json +2 -2
- package/dist/executables/duty-tick-scripted/profile.json +3 -3
- package/dist/executables/goal-scheduler/scheduler.sh +0 -0
- package/dist/executables/release-deploy/deploy.sh +0 -0
- package/dist/executables/release-prepare/prepare.sh +0 -0
- package/dist/executables/release-publish/publish.sh +0 -0
- package/dist/executables/resolve/apply-prefer.sh +0 -0
- package/dist/executables/revert/revert.sh +0 -0
- package/dist/executables/types.ts +3 -3
- package/dist/executables/worker-ask/profile.json +1 -1
- package/package.json +22 -23
- package/dist/duties/public-actions.json +0 -86
- /package/dist/jobs/watch-stale-prs/{prompt.md → duty.md} +0 -0
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.220",
|
|
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",
|
|
@@ -1981,14 +1981,7 @@ var init_agent = __esm({
|
|
|
1981
1981
|
}
|
|
1982
1982
|
});
|
|
1983
1983
|
|
|
1984
|
-
// src/scripts/
|
|
1985
|
-
function splitFrontmatter(raw) {
|
|
1986
|
-
const match = FRONTMATTER_RE.exec(raw);
|
|
1987
|
-
if (!match) return { frontmatter: {}, body: raw };
|
|
1988
|
-
const inner = match[1] ?? "";
|
|
1989
|
-
const body = raw.slice(match[0].length);
|
|
1990
|
-
return { frontmatter: parseFlatYaml(inner), body };
|
|
1991
|
-
}
|
|
1984
|
+
// src/scripts/scheduleEvery.ts
|
|
1992
1985
|
function isScheduleEvery(value) {
|
|
1993
1986
|
return typeof value === "string" && SCHEDULE_EVERY_VALUES.includes(value);
|
|
1994
1987
|
}
|
|
@@ -2019,55 +2012,9 @@ function scheduleEveryToMs(every) {
|
|
|
2019
2012
|
return Number.POSITIVE_INFINITY;
|
|
2020
2013
|
}
|
|
2021
2014
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
const line = rawLine.trim();
|
|
2026
|
-
if (!line || line.startsWith("#")) continue;
|
|
2027
|
-
const colon = line.indexOf(":");
|
|
2028
|
-
if (colon < 0) continue;
|
|
2029
|
-
const key = line.slice(0, colon).trim();
|
|
2030
|
-
const value = stripQuotes(line.slice(colon + 1).trim());
|
|
2031
|
-
if (key === "action" && value.length > 0) {
|
|
2032
|
-
out.action = value;
|
|
2033
|
-
} else if (key === "executable" && value.length > 0) {
|
|
2034
|
-
out.executable = value;
|
|
2035
|
-
} else if (key === "every" && isScheduleEvery(value)) {
|
|
2036
|
-
out.every = value;
|
|
2037
|
-
} else if (key === "tickScript" && value.length > 0) {
|
|
2038
|
-
out.tickScript = value;
|
|
2039
|
-
} else if (key === "disabled") {
|
|
2040
|
-
const lower = value.toLowerCase();
|
|
2041
|
-
if (lower === "true") out.disabled = true;
|
|
2042
|
-
else if (lower === "false") out.disabled = false;
|
|
2043
|
-
} else if (key === "staff" && value.length > 0) {
|
|
2044
|
-
out.staff = value;
|
|
2045
|
-
} else if (key === "mentions") {
|
|
2046
|
-
const logins = value.split(",").map((s) => s.trim().replace(/^@/, "")).filter(Boolean);
|
|
2047
|
-
if (logins.length > 0) out.mentions = logins;
|
|
2048
|
-
} else if (key === "tools") {
|
|
2049
|
-
const names = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2050
|
-
if (names.length > 0) out.tools = names;
|
|
2051
|
-
} else if (key === "executables") {
|
|
2052
|
-
const names = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2053
|
-
if (names.length > 0) out.executables = names;
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
return out;
|
|
2057
|
-
}
|
|
2058
|
-
function stripQuotes(value) {
|
|
2059
|
-
if (value.length >= 2) {
|
|
2060
|
-
const first = value[0];
|
|
2061
|
-
const last = value[value.length - 1];
|
|
2062
|
-
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
2063
|
-
return value.slice(1, -1);
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
return value;
|
|
2067
|
-
}
|
|
2068
|
-
var SCHEDULE_EVERY_VALUES, FRONTMATTER_RE;
|
|
2069
|
-
var init_jobFrontmatter = __esm({
|
|
2070
|
-
"src/scripts/jobFrontmatter.ts"() {
|
|
2015
|
+
var SCHEDULE_EVERY_VALUES;
|
|
2016
|
+
var init_scheduleEvery = __esm({
|
|
2017
|
+
"src/scripts/scheduleEvery.ts"() {
|
|
2071
2018
|
"use strict";
|
|
2072
2019
|
SCHEDULE_EVERY_VALUES = [
|
|
2073
2020
|
"15m",
|
|
@@ -2081,87 +2028,177 @@ var init_jobFrontmatter = __esm({
|
|
|
2081
2028
|
"7d",
|
|
2082
2029
|
"manual"
|
|
2083
2030
|
];
|
|
2084
|
-
FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
2085
2031
|
}
|
|
2086
2032
|
});
|
|
2087
2033
|
|
|
2088
|
-
// src/
|
|
2034
|
+
// src/dutyFolders.ts
|
|
2089
2035
|
import * as fs6 from "fs";
|
|
2090
2036
|
import * as path6 from "path";
|
|
2037
|
+
function listDutyFolderSlugs(absDir) {
|
|
2038
|
+
if (!fs6.existsSync(absDir)) return [];
|
|
2039
|
+
let entries;
|
|
2040
|
+
try {
|
|
2041
|
+
entries = fs6.readdirSync(absDir, { withFileTypes: true });
|
|
2042
|
+
} catch {
|
|
2043
|
+
return [];
|
|
2044
|
+
}
|
|
2045
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isDutyFolder(path6.join(absDir, e.name))).map((e) => e.name).sort();
|
|
2046
|
+
}
|
|
2047
|
+
function isDutyFolder(dir) {
|
|
2048
|
+
return fs6.existsSync(path6.join(dir, DUTY_PROFILE_FILE)) && fs6.existsSync(path6.join(dir, DUTY_BODY_FILE));
|
|
2049
|
+
}
|
|
2050
|
+
function readDutyFolder(root, slug) {
|
|
2051
|
+
const dir = path6.join(root, slug);
|
|
2052
|
+
const profilePath = path6.join(dir, DUTY_PROFILE_FILE);
|
|
2053
|
+
const bodyPath = path6.join(dir, DUTY_BODY_FILE);
|
|
2054
|
+
if (!fs6.existsSync(profilePath) || !fs6.statSync(profilePath).isFile()) return null;
|
|
2055
|
+
if (!fs6.existsSync(bodyPath) || !fs6.statSync(bodyPath).isFile()) return null;
|
|
2056
|
+
try {
|
|
2057
|
+
const rawProfile = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
|
|
2058
|
+
const rawBody = fs6.readFileSync(bodyPath, "utf-8");
|
|
2059
|
+
const { title, body } = parseDutyBody(rawBody, slug);
|
|
2060
|
+
return {
|
|
2061
|
+
slug,
|
|
2062
|
+
dir,
|
|
2063
|
+
profilePath,
|
|
2064
|
+
bodyPath,
|
|
2065
|
+
title,
|
|
2066
|
+
body,
|
|
2067
|
+
rawBody,
|
|
2068
|
+
config: parseDutyConfig(rawProfile),
|
|
2069
|
+
rawProfile
|
|
2070
|
+
};
|
|
2071
|
+
} catch {
|
|
2072
|
+
return null;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function parseDutyConfig(raw) {
|
|
2076
|
+
const tools = stringList(raw.tools ?? raw.dutyTools);
|
|
2077
|
+
return {
|
|
2078
|
+
action: stringField(raw.action),
|
|
2079
|
+
executable: stringField(raw.executable),
|
|
2080
|
+
every: isScheduleEvery(raw.every) ? raw.every : void 0,
|
|
2081
|
+
tickScript: stringField(raw.tickScript),
|
|
2082
|
+
disabled: typeof raw.disabled === "boolean" ? raw.disabled : void 0,
|
|
2083
|
+
staff: stringField(raw.staff),
|
|
2084
|
+
mentions: stringList(raw.mentions).map((m) => m.replace(/^@/, "")),
|
|
2085
|
+
tools,
|
|
2086
|
+
executables: stringList(raw.executables),
|
|
2087
|
+
describe: stringField(raw.describe),
|
|
2088
|
+
stage: stringField(raw.stage),
|
|
2089
|
+
readsFrom: stringList(raw.readsFrom ?? raw.reads_from),
|
|
2090
|
+
writesTo: stringList(raw.writesTo ?? raw.writes_to)
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
function parseDutyBody(raw, slug) {
|
|
2094
|
+
const trimmed = raw.trim();
|
|
2095
|
+
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
2096
|
+
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
2097
|
+
const title = h1 ? h1[1].trim() : humanizeSlug(slug);
|
|
2098
|
+
const body = stripLeadingH1(raw);
|
|
2099
|
+
return { title, body };
|
|
2100
|
+
}
|
|
2101
|
+
function stripLeadingH1(raw) {
|
|
2102
|
+
const lines = raw.replace(/^\uFEFF/, "").split("\n");
|
|
2103
|
+
let i = 0;
|
|
2104
|
+
for (; ; ) {
|
|
2105
|
+
while (i < lines.length && lines[i].trim() === "") i++;
|
|
2106
|
+
if (i < lines.length && /^#\s+.+/.test(lines[i])) i++;
|
|
2107
|
+
else break;
|
|
2108
|
+
}
|
|
2109
|
+
return lines.slice(i).join("\n");
|
|
2110
|
+
}
|
|
2111
|
+
function humanizeSlug(slug) {
|
|
2112
|
+
return slug.split(/[-_]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
2113
|
+
}
|
|
2114
|
+
function stringField(value) {
|
|
2115
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
2116
|
+
}
|
|
2117
|
+
function stringList(value) {
|
|
2118
|
+
if (Array.isArray(value)) {
|
|
2119
|
+
return value.map((v) => String(v).trim()).filter(Boolean);
|
|
2120
|
+
}
|
|
2121
|
+
if (typeof value === "string") {
|
|
2122
|
+
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
2123
|
+
}
|
|
2124
|
+
return [];
|
|
2125
|
+
}
|
|
2126
|
+
var DUTY_PROFILE_FILE, DUTY_BODY_FILE;
|
|
2127
|
+
var init_dutyFolders = __esm({
|
|
2128
|
+
"src/dutyFolders.ts"() {
|
|
2129
|
+
"use strict";
|
|
2130
|
+
init_scheduleEvery();
|
|
2131
|
+
DUTY_PROFILE_FILE = "profile.json";
|
|
2132
|
+
DUTY_BODY_FILE = "duty.md";
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
// src/registry.ts
|
|
2137
|
+
import * as fs7 from "fs";
|
|
2138
|
+
import * as path7 from "path";
|
|
2091
2139
|
function getExecutablesRoot() {
|
|
2092
|
-
const here =
|
|
2140
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2093
2141
|
const candidates = [
|
|
2094
|
-
|
|
2142
|
+
path7.join(here, "executables"),
|
|
2095
2143
|
// dev: src/
|
|
2096
|
-
|
|
2144
|
+
path7.join(here, "..", "executables"),
|
|
2097
2145
|
// built: dist/bin → dist/executables
|
|
2098
|
-
|
|
2146
|
+
path7.join(here, "..", "src", "executables")
|
|
2099
2147
|
// fallback
|
|
2100
2148
|
];
|
|
2101
2149
|
for (const c of candidates) {
|
|
2102
|
-
if (
|
|
2150
|
+
if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
|
|
2103
2151
|
}
|
|
2104
2152
|
return candidates[0];
|
|
2105
2153
|
}
|
|
2106
2154
|
function getProjectExecutablesRoot() {
|
|
2107
|
-
return
|
|
2155
|
+
return path7.join(process.cwd(), ".kody", "executables");
|
|
2108
2156
|
}
|
|
2109
2157
|
function getProjectDutiesRoot() {
|
|
2110
|
-
return
|
|
2158
|
+
return path7.join(process.cwd(), ".kody", "duties");
|
|
2111
2159
|
}
|
|
2112
2160
|
function getBuiltinJobsRoot() {
|
|
2113
|
-
const here =
|
|
2161
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2114
2162
|
const candidates = [
|
|
2115
|
-
|
|
2163
|
+
path7.join(here, "jobs"),
|
|
2116
2164
|
// dev: src/
|
|
2117
|
-
|
|
2165
|
+
path7.join(here, "..", "jobs"),
|
|
2118
2166
|
// built: dist/bin → dist/jobs
|
|
2119
|
-
|
|
2167
|
+
path7.join(here, "..", "src", "jobs")
|
|
2120
2168
|
// fallback
|
|
2121
2169
|
];
|
|
2122
2170
|
for (const c of candidates) {
|
|
2123
|
-
if (
|
|
2171
|
+
if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
|
|
2124
2172
|
}
|
|
2125
2173
|
return candidates[0];
|
|
2126
2174
|
}
|
|
2127
2175
|
function getBuiltinDutiesRoot() {
|
|
2128
|
-
const here =
|
|
2176
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2129
2177
|
const candidates = [
|
|
2130
|
-
|
|
2178
|
+
path7.join(here, "duties"),
|
|
2131
2179
|
// dev: src/
|
|
2132
|
-
|
|
2180
|
+
path7.join(here, "..", "duties"),
|
|
2133
2181
|
// built: dist/bin → dist/duties
|
|
2134
|
-
|
|
2182
|
+
path7.join(here, "..", "src", "duties")
|
|
2135
2183
|
// fallback
|
|
2136
2184
|
];
|
|
2137
2185
|
for (const c of candidates) {
|
|
2138
|
-
if (
|
|
2186
|
+
if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
|
|
2139
2187
|
}
|
|
2140
2188
|
return candidates[0];
|
|
2141
2189
|
}
|
|
2142
2190
|
function listBuiltinJobs(root = getBuiltinJobsRoot()) {
|
|
2143
|
-
if (!
|
|
2191
|
+
if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
|
|
2144
2192
|
const out = [];
|
|
2145
|
-
for (const ent of
|
|
2193
|
+
for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
|
|
2146
2194
|
if (ent.name.startsWith("_") || ent.name.startsWith(".")) continue;
|
|
2147
|
-
const full =
|
|
2195
|
+
const full = path7.join(root, ent.name);
|
|
2148
2196
|
if (ent.isDirectory()) {
|
|
2149
|
-
const profilePath =
|
|
2150
|
-
const
|
|
2151
|
-
if (!
|
|
2152
|
-
if (!
|
|
2153
|
-
out.push({ slug: ent.name, dir: full, profilePath,
|
|
2154
|
-
continue;
|
|
2155
|
-
}
|
|
2156
|
-
if (ent.isFile() && ent.name.endsWith(".md")) {
|
|
2157
|
-
const slug = ent.name.slice(0, -3);
|
|
2158
|
-
out.push({
|
|
2159
|
-
slug,
|
|
2160
|
-
dir: full,
|
|
2161
|
-
profilePath: "",
|
|
2162
|
-
promptPath: "",
|
|
2163
|
-
filePath: full
|
|
2164
|
-
});
|
|
2197
|
+
const profilePath = path7.join(full, DUTY_PROFILE_FILE);
|
|
2198
|
+
const bodyPath = path7.join(full, DUTY_BODY_FILE);
|
|
2199
|
+
if (!fs7.existsSync(profilePath) || !fs7.statSync(profilePath).isFile()) continue;
|
|
2200
|
+
if (!fs7.existsSync(bodyPath) || !fs7.statSync(bodyPath).isFile()) continue;
|
|
2201
|
+
out.push({ slug: ent.name, dir: full, profilePath, bodyPath });
|
|
2165
2202
|
}
|
|
2166
2203
|
}
|
|
2167
2204
|
out.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
@@ -2175,8 +2212,8 @@ function builtinExecutableNames() {
|
|
|
2175
2212
|
const out = /* @__PURE__ */ new Set();
|
|
2176
2213
|
const root = getExecutablesRoot();
|
|
2177
2214
|
try {
|
|
2178
|
-
for (const ent of
|
|
2179
|
-
if (ent.isDirectory() &&
|
|
2215
|
+
for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
|
|
2216
|
+
if (ent.isDirectory() && fs7.existsSync(path7.join(root, ent.name, "profile.json"))) out.add(ent.name);
|
|
2180
2217
|
}
|
|
2181
2218
|
} catch {
|
|
2182
2219
|
}
|
|
@@ -2192,14 +2229,15 @@ function listExecutables(roots = getExecutableRoots()) {
|
|
|
2192
2229
|
const seen = /* @__PURE__ */ new Set();
|
|
2193
2230
|
const out = [];
|
|
2194
2231
|
for (const root of rootList) {
|
|
2195
|
-
if (!
|
|
2196
|
-
const entries =
|
|
2232
|
+
if (!fs7.existsSync(root)) continue;
|
|
2233
|
+
const entries = fs7.readdirSync(root, { withFileTypes: true });
|
|
2197
2234
|
for (const ent of entries) {
|
|
2198
2235
|
if (!ent.isDirectory()) continue;
|
|
2199
2236
|
if (seen.has(ent.name)) continue;
|
|
2200
2237
|
if (root === dutiesRoot && isBuiltinExecutable(ent.name)) continue;
|
|
2201
|
-
const profilePath =
|
|
2202
|
-
if (
|
|
2238
|
+
const profilePath = path7.join(root, ent.name, DUTY_PROFILE_FILE);
|
|
2239
|
+
if (root === dutiesRoot && !fs7.existsSync(path7.join(root, ent.name, DUTY_BODY_FILE))) continue;
|
|
2240
|
+
if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
|
|
2203
2241
|
out.push({ name: ent.name, profilePath });
|
|
2204
2242
|
seen.add(ent.name);
|
|
2205
2243
|
}
|
|
@@ -2213,8 +2251,9 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
|
|
|
2213
2251
|
const dutiesRoot = getProjectDutiesRoot();
|
|
2214
2252
|
for (const root of rootList) {
|
|
2215
2253
|
if (root === dutiesRoot && isBuiltinExecutable(name)) continue;
|
|
2216
|
-
|
|
2217
|
-
|
|
2254
|
+
if (root === dutiesRoot && !fs7.existsSync(path7.join(root, name, DUTY_BODY_FILE))) continue;
|
|
2255
|
+
const profilePath = path7.join(root, name, "profile.json");
|
|
2256
|
+
if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
|
|
2218
2257
|
return profilePath;
|
|
2219
2258
|
}
|
|
2220
2259
|
}
|
|
@@ -2233,7 +2272,6 @@ function listDutyActions(projectDutiesRoot = getProjectDutiesRoot()) {
|
|
|
2233
2272
|
out.push(action);
|
|
2234
2273
|
};
|
|
2235
2274
|
for (const action of listProjectFolderDutyActions(projectDutiesRoot)) add(action);
|
|
2236
|
-
for (const action of listProjectMarkdownDutyActions(projectDutiesRoot)) add(action);
|
|
2237
2275
|
for (const action of listBuiltinDutyActions()) add(action);
|
|
2238
2276
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2239
2277
|
}
|
|
@@ -2248,108 +2286,54 @@ function isSafeName(name) {
|
|
|
2248
2286
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
2249
2287
|
}
|
|
2250
2288
|
function listProjectFolderDutyActions(root) {
|
|
2251
|
-
if (!
|
|
2289
|
+
if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
|
|
2252
2290
|
const out = [];
|
|
2253
|
-
for (const
|
|
2254
|
-
if (!
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
profilePath
|
|
2270
|
-
});
|
|
2271
|
-
} catch {
|
|
2272
|
-
continue;
|
|
2273
|
-
}
|
|
2291
|
+
for (const slug of listDutyFolderSlugs(root)) {
|
|
2292
|
+
if (!isSafeName(slug)) continue;
|
|
2293
|
+
const duty = readDutyFolder(root, slug);
|
|
2294
|
+
if (!duty) continue;
|
|
2295
|
+
const action = duty.config.action ?? slug;
|
|
2296
|
+
const executable = duty.config.executable ?? duty.config.executables?.[0] ?? (duty.config.tickScript ? "duty-tick-scripted" : "duty-tick");
|
|
2297
|
+
out.push({
|
|
2298
|
+
action,
|
|
2299
|
+
duty: slug,
|
|
2300
|
+
executable,
|
|
2301
|
+
cliArgs: duty.config.executable ? {} : { duty: slug },
|
|
2302
|
+
source: "project-folder",
|
|
2303
|
+
describe: duty.config.describe ?? duty.title,
|
|
2304
|
+
profilePath: duty.profilePath,
|
|
2305
|
+
bodyPath: duty.bodyPath
|
|
2306
|
+
});
|
|
2274
2307
|
}
|
|
2275
2308
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2276
2309
|
}
|
|
2277
|
-
function
|
|
2278
|
-
if (!
|
|
2310
|
+
function listBuiltinDutyActions(root = getBuiltinDutiesRoot()) {
|
|
2311
|
+
if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
|
|
2279
2312
|
const out = [];
|
|
2280
|
-
for (const
|
|
2281
|
-
if (!
|
|
2282
|
-
const duty =
|
|
2283
|
-
if (!
|
|
2284
|
-
const
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
filePath
|
|
2297
|
-
});
|
|
2298
|
-
} catch {
|
|
2299
|
-
continue;
|
|
2300
|
-
}
|
|
2313
|
+
for (const slug of listDutyFolderSlugs(root)) {
|
|
2314
|
+
if (!isSafeName(slug)) continue;
|
|
2315
|
+
const duty = readDutyFolder(root, slug);
|
|
2316
|
+
if (!duty) continue;
|
|
2317
|
+
const action = duty.config.action ?? slug;
|
|
2318
|
+
const executable = duty.config.executable ?? slug;
|
|
2319
|
+
out.push({
|
|
2320
|
+
action,
|
|
2321
|
+
duty: slug,
|
|
2322
|
+
executable,
|
|
2323
|
+
cliArgs: {},
|
|
2324
|
+
source: "builtin",
|
|
2325
|
+
describe: duty.config.describe ?? duty.title,
|
|
2326
|
+
profilePath: duty.profilePath,
|
|
2327
|
+
bodyPath: duty.bodyPath
|
|
2328
|
+
});
|
|
2301
2329
|
}
|
|
2302
2330
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2303
2331
|
}
|
|
2304
|
-
function markdownDutyImplementation(duty, frontmatter) {
|
|
2305
|
-
if (frontmatter.executable?.trim()) {
|
|
2306
|
-
return { executable: frontmatter.executable.trim(), cliArgs: {} };
|
|
2307
|
-
}
|
|
2308
|
-
if (frontmatter.executables?.length === 1 && frontmatter.executables[0]?.trim()) {
|
|
2309
|
-
return { executable: frontmatter.executables[0].trim(), cliArgs: {} };
|
|
2310
|
-
}
|
|
2311
|
-
if (frontmatter.tickScript?.trim()) {
|
|
2312
|
-
return { executable: "duty-tick-scripted", cliArgs: { duty } };
|
|
2313
|
-
}
|
|
2314
|
-
return { executable: "duty-tick", cliArgs: { duty } };
|
|
2315
|
-
}
|
|
2316
|
-
function listBuiltinDutyActions(root = getBuiltinDutiesRoot()) {
|
|
2317
|
-
const filePath = path6.join(root, "public-actions.json");
|
|
2318
|
-
if (!fs6.existsSync(filePath) || !fs6.statSync(filePath).isFile()) return [];
|
|
2319
|
-
try {
|
|
2320
|
-
const raw = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
|
|
2321
|
-
if (!Array.isArray(raw)) return [];
|
|
2322
|
-
const out = [];
|
|
2323
|
-
for (const item of raw) {
|
|
2324
|
-
if (!item || typeof item !== "object") continue;
|
|
2325
|
-
const r = item;
|
|
2326
|
-
const duty = stringOr(r.duty, stringOr(r.name, ""));
|
|
2327
|
-
const action = stringOr(r.action, duty);
|
|
2328
|
-
const executable = stringOr(r.executable, duty);
|
|
2329
|
-
if (!duty || !action || !executable) continue;
|
|
2330
|
-
out.push({
|
|
2331
|
-
action,
|
|
2332
|
-
duty,
|
|
2333
|
-
executable,
|
|
2334
|
-
cliArgs: {},
|
|
2335
|
-
source: "builtin",
|
|
2336
|
-
describe: typeof r.describe === "string" ? r.describe : void 0,
|
|
2337
|
-
filePath
|
|
2338
|
-
});
|
|
2339
|
-
}
|
|
2340
|
-
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2341
|
-
} catch {
|
|
2342
|
-
return [];
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
function stringOr(value, fallback) {
|
|
2346
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
|
|
2347
|
-
}
|
|
2348
2332
|
function getProfileInputs(name, roots = getExecutableRoots()) {
|
|
2349
2333
|
const profilePath = resolveExecutable(name, roots);
|
|
2350
2334
|
if (!profilePath) return null;
|
|
2351
2335
|
try {
|
|
2352
|
-
const raw = JSON.parse(
|
|
2336
|
+
const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
|
|
2353
2337
|
if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
|
|
2354
2338
|
return raw.inputs;
|
|
2355
2339
|
} catch {
|
|
@@ -2385,27 +2369,27 @@ var _builtinNames;
|
|
|
2385
2369
|
var init_registry = __esm({
|
|
2386
2370
|
"src/registry.ts"() {
|
|
2387
2371
|
"use strict";
|
|
2388
|
-
|
|
2372
|
+
init_dutyFolders();
|
|
2389
2373
|
_builtinNames = null;
|
|
2390
2374
|
}
|
|
2391
2375
|
});
|
|
2392
2376
|
|
|
2393
2377
|
// src/task-artifacts.ts
|
|
2394
|
-
import
|
|
2395
|
-
import
|
|
2378
|
+
import fs8 from "fs";
|
|
2379
|
+
import path8 from "path";
|
|
2396
2380
|
function prepareTaskArtifactsDir(cwd, taskId) {
|
|
2397
2381
|
const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
2398
|
-
const relDir =
|
|
2399
|
-
const absDir =
|
|
2400
|
-
|
|
2382
|
+
const relDir = path8.join(".kody", "tasks", safeId);
|
|
2383
|
+
const absDir = path8.join(cwd, relDir);
|
|
2384
|
+
fs8.mkdirSync(absDir, { recursive: true });
|
|
2401
2385
|
return { taskId: safeId, absDir, relDir };
|
|
2402
2386
|
}
|
|
2403
2387
|
function verifyTaskArtifacts(absDir) {
|
|
2404
2388
|
const missing = [];
|
|
2405
2389
|
for (const name of TASK_ARTIFACT_FILES) {
|
|
2406
|
-
const full =
|
|
2390
|
+
const full = path8.join(absDir, name);
|
|
2407
2391
|
try {
|
|
2408
|
-
const stat =
|
|
2392
|
+
const stat = fs8.statSync(full);
|
|
2409
2393
|
if (!stat.isFile() || stat.size === 0) missing.push(name);
|
|
2410
2394
|
} catch {
|
|
2411
2395
|
missing.push(name);
|
|
@@ -2639,31 +2623,31 @@ var init_lifecycles = __esm({
|
|
|
2639
2623
|
});
|
|
2640
2624
|
|
|
2641
2625
|
// src/scripts/buildSyntheticPlugin.ts
|
|
2642
|
-
import * as
|
|
2626
|
+
import * as fs14 from "fs";
|
|
2643
2627
|
import * as os2 from "os";
|
|
2644
|
-
import * as
|
|
2628
|
+
import * as path13 from "path";
|
|
2645
2629
|
function getPluginsCatalogRoot() {
|
|
2646
|
-
const here =
|
|
2630
|
+
const here = path13.dirname(new URL(import.meta.url).pathname);
|
|
2647
2631
|
const candidates = [
|
|
2648
|
-
|
|
2632
|
+
path13.join(here, "..", "plugins"),
|
|
2649
2633
|
// dev: src/scripts → src/plugins
|
|
2650
|
-
|
|
2634
|
+
path13.join(here, "..", "..", "plugins"),
|
|
2651
2635
|
// built: dist/scripts → dist/plugins
|
|
2652
|
-
|
|
2636
|
+
path13.join(here, "..", "..", "src", "plugins")
|
|
2653
2637
|
// fallback
|
|
2654
2638
|
];
|
|
2655
2639
|
for (const c of candidates) {
|
|
2656
|
-
if (
|
|
2640
|
+
if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
|
|
2657
2641
|
}
|
|
2658
2642
|
return candidates[0];
|
|
2659
2643
|
}
|
|
2660
2644
|
function copyDir(src, dst) {
|
|
2661
|
-
|
|
2662
|
-
for (const ent of
|
|
2663
|
-
const s =
|
|
2664
|
-
const d =
|
|
2645
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
2646
|
+
for (const ent of fs14.readdirSync(src, { withFileTypes: true })) {
|
|
2647
|
+
const s = path13.join(src, ent.name);
|
|
2648
|
+
const d = path13.join(dst, ent.name);
|
|
2665
2649
|
if (ent.isDirectory()) copyDir(s, d);
|
|
2666
|
-
else if (ent.isFile())
|
|
2650
|
+
else if (ent.isFile()) fs14.copyFileSync(s, d);
|
|
2667
2651
|
}
|
|
2668
2652
|
}
|
|
2669
2653
|
var buildSyntheticPlugin;
|
|
@@ -2676,45 +2660,45 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2676
2660
|
if (!needsSynthetic) return;
|
|
2677
2661
|
const catalog = getPluginsCatalogRoot();
|
|
2678
2662
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2679
|
-
const root =
|
|
2680
|
-
|
|
2663
|
+
const root = path13.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
2664
|
+
fs14.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
|
|
2681
2665
|
const resolvePart = (bucket, entry) => {
|
|
2682
|
-
const local =
|
|
2683
|
-
if (
|
|
2684
|
-
const central =
|
|
2685
|
-
if (
|
|
2666
|
+
const local = path13.join(profile.dir, bucket, entry);
|
|
2667
|
+
if (fs14.existsSync(local)) return local;
|
|
2668
|
+
const central = path13.join(catalog, bucket, entry);
|
|
2669
|
+
if (fs14.existsSync(central)) return central;
|
|
2686
2670
|
throw new Error(
|
|
2687
2671
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
2688
2672
|
);
|
|
2689
2673
|
};
|
|
2690
2674
|
if (cc.skills.length > 0) {
|
|
2691
|
-
const dst =
|
|
2692
|
-
|
|
2675
|
+
const dst = path13.join(root, "skills");
|
|
2676
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
2693
2677
|
for (const name of cc.skills) {
|
|
2694
|
-
copyDir(resolvePart("skills", name),
|
|
2678
|
+
copyDir(resolvePart("skills", name), path13.join(dst, name));
|
|
2695
2679
|
}
|
|
2696
2680
|
}
|
|
2697
2681
|
if (cc.commands.length > 0) {
|
|
2698
|
-
const dst =
|
|
2699
|
-
|
|
2682
|
+
const dst = path13.join(root, "commands");
|
|
2683
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
2700
2684
|
for (const name of cc.commands) {
|
|
2701
|
-
|
|
2685
|
+
fs14.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
|
|
2702
2686
|
}
|
|
2703
2687
|
}
|
|
2704
2688
|
if (cc.hooks.length > 0) {
|
|
2705
|
-
const dst =
|
|
2706
|
-
|
|
2689
|
+
const dst = path13.join(root, "hooks");
|
|
2690
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
2707
2691
|
const merged = { hooks: {} };
|
|
2708
2692
|
for (const name of cc.hooks) {
|
|
2709
2693
|
const src = resolvePart("hooks", `${name}.json`);
|
|
2710
|
-
const parsed = JSON.parse(
|
|
2694
|
+
const parsed = JSON.parse(fs14.readFileSync(src, "utf-8"));
|
|
2711
2695
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
2712
2696
|
if (!Array.isArray(entries)) continue;
|
|
2713
2697
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
2714
2698
|
merged.hooks[event].push(...entries);
|
|
2715
2699
|
}
|
|
2716
2700
|
}
|
|
2717
|
-
|
|
2701
|
+
fs14.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
2718
2702
|
`);
|
|
2719
2703
|
}
|
|
2720
2704
|
const manifest = {
|
|
@@ -2724,7 +2708,7 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2724
2708
|
};
|
|
2725
2709
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
2726
2710
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
2727
|
-
|
|
2711
|
+
fs14.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
2728
2712
|
`);
|
|
2729
2713
|
ctx.data.syntheticPluginPath = root;
|
|
2730
2714
|
};
|
|
@@ -2732,9 +2716,9 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2732
2716
|
});
|
|
2733
2717
|
|
|
2734
2718
|
// src/subagents.ts
|
|
2735
|
-
import * as
|
|
2736
|
-
import * as
|
|
2737
|
-
function
|
|
2719
|
+
import * as fs15 from "fs";
|
|
2720
|
+
import * as path14 from "path";
|
|
2721
|
+
function splitFrontmatter(raw) {
|
|
2738
2722
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
2739
2723
|
if (!match) return { fm: {}, body: raw.trim() };
|
|
2740
2724
|
const fm = {};
|
|
@@ -2746,10 +2730,10 @@ function splitFrontmatter2(raw) {
|
|
|
2746
2730
|
return { fm, body: (match[2] ?? "").trim() };
|
|
2747
2731
|
}
|
|
2748
2732
|
function resolveAgentFile(profileDir, name) {
|
|
2749
|
-
const local =
|
|
2750
|
-
if (
|
|
2751
|
-
const central =
|
|
2752
|
-
if (
|
|
2733
|
+
const local = path14.join(profileDir, "agents", `${name}.md`);
|
|
2734
|
+
if (fs15.existsSync(local)) return local;
|
|
2735
|
+
const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
2736
|
+
if (fs15.existsSync(central)) return central;
|
|
2753
2737
|
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
2754
2738
|
}
|
|
2755
2739
|
function captureSubagentTemplates(profile) {
|
|
@@ -2758,7 +2742,7 @@ function captureSubagentTemplates(profile) {
|
|
|
2758
2742
|
const out = {};
|
|
2759
2743
|
for (const name of names) {
|
|
2760
2744
|
try {
|
|
2761
|
-
out[name] =
|
|
2745
|
+
out[name] = fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
2762
2746
|
} catch {
|
|
2763
2747
|
}
|
|
2764
2748
|
}
|
|
@@ -2769,8 +2753,8 @@ function loadSubagents(profile) {
|
|
|
2769
2753
|
if (!names || names.length === 0) return void 0;
|
|
2770
2754
|
const agents = {};
|
|
2771
2755
|
for (const name of names) {
|
|
2772
|
-
const raw = profile.subagentTemplates?.[name] ??
|
|
2773
|
-
const { fm, body } =
|
|
2756
|
+
const raw = profile.subagentTemplates?.[name] ?? fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
2757
|
+
const { fm, body } = splitFrontmatter(raw);
|
|
2774
2758
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
2775
2759
|
const def = {
|
|
2776
2760
|
description: fm.description ?? `Subagent ${name}`,
|
|
@@ -2793,15 +2777,15 @@ var init_subagents = __esm({
|
|
|
2793
2777
|
});
|
|
2794
2778
|
|
|
2795
2779
|
// src/profile.ts
|
|
2796
|
-
import * as
|
|
2797
|
-
import * as
|
|
2780
|
+
import * as fs16 from "fs";
|
|
2781
|
+
import * as path15 from "path";
|
|
2798
2782
|
function loadProfile(profilePath) {
|
|
2799
|
-
if (!
|
|
2783
|
+
if (!fs16.existsSync(profilePath)) {
|
|
2800
2784
|
throw new ProfileError(profilePath, "file not found");
|
|
2801
2785
|
}
|
|
2802
2786
|
let raw;
|
|
2803
2787
|
try {
|
|
2804
|
-
raw = JSON.parse(
|
|
2788
|
+
raw = JSON.parse(fs16.readFileSync(profilePath, "utf-8"));
|
|
2805
2789
|
} catch (err) {
|
|
2806
2790
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
2807
2791
|
}
|
|
@@ -2812,7 +2796,7 @@ function loadProfile(profilePath) {
|
|
|
2812
2796
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
2813
2797
|
if (unknownKeys.length > 0) {
|
|
2814
2798
|
process.stderr.write(
|
|
2815
|
-
`[kody profile] ${
|
|
2799
|
+
`[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
2816
2800
|
`
|
|
2817
2801
|
);
|
|
2818
2802
|
}
|
|
@@ -2831,7 +2815,7 @@ function loadProfile(profilePath) {
|
|
|
2831
2815
|
describe: typeof r.describe === "string" ? r.describe : base.describe,
|
|
2832
2816
|
staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : base.staff,
|
|
2833
2817
|
every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
|
|
2834
|
-
dutyTools:
|
|
2818
|
+
dutyTools: parseStringArray(r.dutyTools ?? r.tools) ?? base.dutyTools,
|
|
2835
2819
|
mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : base.mentions
|
|
2836
2820
|
};
|
|
2837
2821
|
}
|
|
@@ -2877,8 +2861,8 @@ function loadProfile(profilePath) {
|
|
|
2877
2861
|
staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
|
|
2878
2862
|
// Optional recurrence cadence (scheduled duty). Blank → undefined (on-demand).
|
|
2879
2863
|
every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
|
|
2880
|
-
// Locked-toolbox palette + mentions
|
|
2881
|
-
dutyTools:
|
|
2864
|
+
// Locked-toolbox palette + mentions from folder-duty profile metadata.
|
|
2865
|
+
dutyTools: parseStringArray(r.dutyTools ?? r.tools),
|
|
2882
2866
|
mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : void 0,
|
|
2883
2867
|
role,
|
|
2884
2868
|
kind,
|
|
@@ -2901,8 +2885,8 @@ function loadProfile(profilePath) {
|
|
|
2901
2885
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
2902
2886
|
// flip to true after end-to-end verification.
|
|
2903
2887
|
preloadContext: r.preloadContext === true,
|
|
2904
|
-
dir:
|
|
2905
|
-
promptTemplates: readPromptTemplates(
|
|
2888
|
+
dir: path15.dirname(profilePath),
|
|
2889
|
+
promptTemplates: readPromptTemplates(path15.dirname(profilePath))
|
|
2906
2890
|
};
|
|
2907
2891
|
if (lifecycle) {
|
|
2908
2892
|
applyLifecycle(profile, profilePath);
|
|
@@ -2934,15 +2918,16 @@ function readPromptTemplates(dir) {
|
|
|
2934
2918
|
const out = {};
|
|
2935
2919
|
const read = (p) => {
|
|
2936
2920
|
try {
|
|
2937
|
-
out[p] =
|
|
2921
|
+
out[p] = fs16.readFileSync(p, "utf-8");
|
|
2938
2922
|
} catch {
|
|
2939
2923
|
}
|
|
2940
2924
|
};
|
|
2941
|
-
read(
|
|
2925
|
+
read(path15.join(dir, "prompt.md"));
|
|
2926
|
+
read(path15.join(dir, "duty.md"));
|
|
2942
2927
|
try {
|
|
2943
|
-
const promptsDir =
|
|
2944
|
-
for (const ent of
|
|
2945
|
-
if (ent.endsWith(".md")) read(
|
|
2928
|
+
const promptsDir = path15.join(dir, "prompts");
|
|
2929
|
+
for (const ent of fs16.readdirSync(promptsDir)) {
|
|
2930
|
+
if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
|
|
2946
2931
|
}
|
|
2947
2932
|
} catch {
|
|
2948
2933
|
}
|
|
@@ -2962,6 +2947,11 @@ function requireString(p, r, key) {
|
|
|
2962
2947
|
}
|
|
2963
2948
|
return v;
|
|
2964
2949
|
}
|
|
2950
|
+
function parseStringArray(raw) {
|
|
2951
|
+
if (!Array.isArray(raw)) return void 0;
|
|
2952
|
+
const values = raw.map((t) => String(t).trim()).filter(Boolean);
|
|
2953
|
+
return values.length > 0 ? values : void 0;
|
|
2954
|
+
}
|
|
2965
2955
|
function parseInputs(p, raw) {
|
|
2966
2956
|
if (!Array.isArray(raw)) throw new ProfileError(p, `"inputs" must be an array`);
|
|
2967
2957
|
const out = [];
|
|
@@ -3213,7 +3203,11 @@ var init_profile = __esm({
|
|
|
3213
3203
|
"staff",
|
|
3214
3204
|
"every",
|
|
3215
3205
|
"dutyTools",
|
|
3206
|
+
"tools",
|
|
3216
3207
|
"mentions",
|
|
3208
|
+
"stage",
|
|
3209
|
+
"readsFrom",
|
|
3210
|
+
"writesTo",
|
|
3217
3211
|
"describe",
|
|
3218
3212
|
"role",
|
|
3219
3213
|
"kind",
|
|
@@ -3610,16 +3604,16 @@ var init_state = __esm({
|
|
|
3610
3604
|
});
|
|
3611
3605
|
|
|
3612
3606
|
// src/prompt.ts
|
|
3613
|
-
import * as
|
|
3614
|
-
import * as
|
|
3607
|
+
import * as fs17 from "fs";
|
|
3608
|
+
import * as path16 from "path";
|
|
3615
3609
|
function loadProjectConventions(projectDir) {
|
|
3616
3610
|
const out = [];
|
|
3617
3611
|
for (const rel of CONVENTION_FILES) {
|
|
3618
|
-
const abs =
|
|
3619
|
-
if (!
|
|
3612
|
+
const abs = path16.join(projectDir, rel);
|
|
3613
|
+
if (!fs17.existsSync(abs)) continue;
|
|
3620
3614
|
let content;
|
|
3621
3615
|
try {
|
|
3622
|
-
content =
|
|
3616
|
+
content = fs17.readFileSync(abs, "utf-8");
|
|
3623
3617
|
} catch {
|
|
3624
3618
|
continue;
|
|
3625
3619
|
}
|
|
@@ -3854,28 +3848,28 @@ var loadMemoryContext_exports = {};
|
|
|
3854
3848
|
__export(loadMemoryContext_exports, {
|
|
3855
3849
|
loadMemoryContext: () => loadMemoryContext
|
|
3856
3850
|
});
|
|
3857
|
-
import * as
|
|
3858
|
-
import * as
|
|
3851
|
+
import * as fs18 from "fs";
|
|
3852
|
+
import * as path17 from "path";
|
|
3859
3853
|
function collectPages(memoryAbs) {
|
|
3860
3854
|
const out = [];
|
|
3861
3855
|
walkMd(memoryAbs, (file) => {
|
|
3862
3856
|
let stat;
|
|
3863
3857
|
try {
|
|
3864
|
-
stat =
|
|
3858
|
+
stat = fs18.statSync(file);
|
|
3865
3859
|
} catch {
|
|
3866
3860
|
return;
|
|
3867
3861
|
}
|
|
3868
3862
|
let raw;
|
|
3869
3863
|
try {
|
|
3870
|
-
raw =
|
|
3864
|
+
raw = fs18.readFileSync(file, "utf-8");
|
|
3871
3865
|
} catch {
|
|
3872
3866
|
return;
|
|
3873
3867
|
}
|
|
3874
3868
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
3875
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
3869
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path17.basename(file, ".md");
|
|
3876
3870
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
3877
3871
|
out.push({
|
|
3878
|
-
relPath:
|
|
3872
|
+
relPath: path17.relative(memoryAbs, file),
|
|
3879
3873
|
title,
|
|
3880
3874
|
updated,
|
|
3881
3875
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
|
|
@@ -3943,16 +3937,16 @@ function walkMd(root, visit) {
|
|
|
3943
3937
|
const dir = stack.pop();
|
|
3944
3938
|
let names;
|
|
3945
3939
|
try {
|
|
3946
|
-
names =
|
|
3940
|
+
names = fs18.readdirSync(dir);
|
|
3947
3941
|
} catch {
|
|
3948
3942
|
continue;
|
|
3949
3943
|
}
|
|
3950
3944
|
for (const name of names) {
|
|
3951
3945
|
if (name.startsWith(".")) continue;
|
|
3952
|
-
const full =
|
|
3946
|
+
const full = path17.join(dir, name);
|
|
3953
3947
|
let stat;
|
|
3954
3948
|
try {
|
|
3955
|
-
stat =
|
|
3949
|
+
stat = fs18.statSync(full);
|
|
3956
3950
|
} catch {
|
|
3957
3951
|
continue;
|
|
3958
3952
|
}
|
|
@@ -3975,8 +3969,8 @@ var init_loadMemoryContext = __esm({
|
|
|
3975
3969
|
TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
|
|
3976
3970
|
loadMemoryContext = async (ctx) => {
|
|
3977
3971
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
3978
|
-
const memoryAbs =
|
|
3979
|
-
if (!
|
|
3972
|
+
const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
3973
|
+
if (!fs18.existsSync(memoryAbs)) {
|
|
3980
3974
|
ctx.data.memoryContext = "";
|
|
3981
3975
|
return;
|
|
3982
3976
|
}
|
|
@@ -4020,11 +4014,11 @@ var init_loadCoverageRules = __esm({
|
|
|
4020
4014
|
|
|
4021
4015
|
// src/container.ts
|
|
4022
4016
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
4023
|
-
import * as
|
|
4017
|
+
import * as fs19 from "fs";
|
|
4024
4018
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
4025
4019
|
try {
|
|
4026
4020
|
const profilePath = resolveProfilePath(profileName);
|
|
4027
|
-
if (!
|
|
4021
|
+
if (!fs19.existsSync(profilePath)) return null;
|
|
4028
4022
|
return loadProfile(profilePath).inputs;
|
|
4029
4023
|
} catch {
|
|
4030
4024
|
return null;
|
|
@@ -4486,9 +4480,9 @@ var init_lifecycleLabels = __esm({
|
|
|
4486
4480
|
|
|
4487
4481
|
// src/litellm.ts
|
|
4488
4482
|
import { execFileSync as execFileSync4, spawn as spawn3 } from "child_process";
|
|
4489
|
-
import * as
|
|
4483
|
+
import * as fs20 from "fs";
|
|
4490
4484
|
import * as os3 from "os";
|
|
4491
|
-
import * as
|
|
4485
|
+
import * as path18 from "path";
|
|
4492
4486
|
async function checkLitellmHealth(url) {
|
|
4493
4487
|
try {
|
|
4494
4488
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -4573,13 +4567,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4573
4567
|
let child;
|
|
4574
4568
|
let logPath;
|
|
4575
4569
|
const spawnProxy = () => {
|
|
4576
|
-
const configPath =
|
|
4577
|
-
|
|
4570
|
+
const configPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
|
|
4571
|
+
fs20.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
4578
4572
|
const args = ["--config", configPath, "--port", port];
|
|
4579
|
-
const nextLogPath =
|
|
4580
|
-
const outFd =
|
|
4573
|
+
const nextLogPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
|
|
4574
|
+
const outFd = fs20.openSync(nextLogPath, "w");
|
|
4581
4575
|
child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
4582
|
-
|
|
4576
|
+
fs20.closeSync(outFd);
|
|
4583
4577
|
logPath = nextLogPath;
|
|
4584
4578
|
};
|
|
4585
4579
|
const waitForHealth = async () => {
|
|
@@ -4593,7 +4587,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4593
4587
|
const readLogTail = () => {
|
|
4594
4588
|
if (!logPath) return "";
|
|
4595
4589
|
try {
|
|
4596
|
-
return
|
|
4590
|
+
return fs20.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
4597
4591
|
} catch {
|
|
4598
4592
|
return "";
|
|
4599
4593
|
}
|
|
@@ -4645,10 +4639,10 @@ ${tail}`
|
|
|
4645
4639
|
return { url, kill: killChild, isHealthy, ensureHealthy };
|
|
4646
4640
|
}
|
|
4647
4641
|
function readDotenvApiKeys(projectDir) {
|
|
4648
|
-
const dotenvPath =
|
|
4649
|
-
if (!
|
|
4642
|
+
const dotenvPath = path18.join(projectDir, ".env");
|
|
4643
|
+
if (!fs20.existsSync(dotenvPath)) return {};
|
|
4650
4644
|
const result = {};
|
|
4651
|
-
for (const rawLine of
|
|
4645
|
+
for (const rawLine of fs20.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
4652
4646
|
const line = rawLine.trim();
|
|
4653
4647
|
if (!line || line.startsWith("#")) continue;
|
|
4654
4648
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -4763,8 +4757,8 @@ var init_pushWithRetry = __esm({
|
|
|
4763
4757
|
|
|
4764
4758
|
// src/commit.ts
|
|
4765
4759
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
4766
|
-
import * as
|
|
4767
|
-
import * as
|
|
4760
|
+
import * as fs21 from "fs";
|
|
4761
|
+
import * as path19 from "path";
|
|
4768
4762
|
function git(args, cwd) {
|
|
4769
4763
|
try {
|
|
4770
4764
|
return execFileSync6("git", args, {
|
|
@@ -4802,18 +4796,18 @@ function ensureGitIdentity(cwd) {
|
|
|
4802
4796
|
}
|
|
4803
4797
|
function abortUnfinishedGitOps(cwd) {
|
|
4804
4798
|
const aborted = [];
|
|
4805
|
-
const gitDir =
|
|
4806
|
-
if (!
|
|
4807
|
-
if (
|
|
4799
|
+
const gitDir = path19.join(cwd ?? process.cwd(), ".git");
|
|
4800
|
+
if (!fs21.existsSync(gitDir)) return aborted;
|
|
4801
|
+
if (fs21.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
|
|
4808
4802
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
4809
4803
|
}
|
|
4810
|
-
if (
|
|
4804
|
+
if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
4811
4805
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
4812
4806
|
}
|
|
4813
|
-
if (
|
|
4807
|
+
if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
|
|
4814
4808
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
4815
4809
|
}
|
|
4816
|
-
if (
|
|
4810
|
+
if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
|
|
4817
4811
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
4818
4812
|
}
|
|
4819
4813
|
try {
|
|
@@ -4869,7 +4863,7 @@ function normalizeCommitMessage(raw) {
|
|
|
4869
4863
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
4870
4864
|
const allChanged = listChangedFiles(cwd);
|
|
4871
4865
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
4872
|
-
const mergeHeadExists =
|
|
4866
|
+
const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
4873
4867
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
4874
4868
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
4875
4869
|
}
|
|
@@ -5106,7 +5100,7 @@ var init_advanceFlow = __esm({
|
|
|
5106
5100
|
|
|
5107
5101
|
// src/gha.ts
|
|
5108
5102
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
5109
|
-
import * as
|
|
5103
|
+
import * as fs22 from "fs";
|
|
5110
5104
|
function getRunUrl() {
|
|
5111
5105
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5112
5106
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5117,10 +5111,10 @@ function getRunUrl() {
|
|
|
5117
5111
|
function reactToTriggerComment(cwd) {
|
|
5118
5112
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5119
5113
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5120
|
-
if (!eventPath || !
|
|
5114
|
+
if (!eventPath || !fs22.existsSync(eventPath)) return;
|
|
5121
5115
|
let event = null;
|
|
5122
5116
|
try {
|
|
5123
|
-
event = JSON.parse(
|
|
5117
|
+
event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
|
|
5124
5118
|
} catch {
|
|
5125
5119
|
return;
|
|
5126
5120
|
}
|
|
@@ -5454,11 +5448,11 @@ var init_classifyByLabel = __esm({
|
|
|
5454
5448
|
});
|
|
5455
5449
|
|
|
5456
5450
|
// src/scripts/commitAndPush.ts
|
|
5457
|
-
import * as
|
|
5458
|
-
import * as
|
|
5451
|
+
import * as fs23 from "fs";
|
|
5452
|
+
import * as path20 from "path";
|
|
5459
5453
|
function sentinelPathForStage(cwd, profileName) {
|
|
5460
5454
|
const runId = resolveRunId();
|
|
5461
|
-
return
|
|
5455
|
+
return path20.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
5462
5456
|
}
|
|
5463
5457
|
var DEFAULT_COMMIT_MESSAGE, commitAndPush2;
|
|
5464
5458
|
var init_commitAndPush = __esm({
|
|
@@ -5475,9 +5469,9 @@ var init_commitAndPush = __esm({
|
|
|
5475
5469
|
}
|
|
5476
5470
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
5477
5471
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
5478
|
-
if (sentinel &&
|
|
5472
|
+
if (sentinel && fs23.existsSync(sentinel)) {
|
|
5479
5473
|
try {
|
|
5480
|
-
const replay = JSON.parse(
|
|
5474
|
+
const replay = JSON.parse(fs23.readFileSync(sentinel, "utf-8"));
|
|
5481
5475
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
5482
5476
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
5483
5477
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -5530,8 +5524,8 @@ var init_commitAndPush = __esm({
|
|
|
5530
5524
|
const result = ctx.data.commitResult;
|
|
5531
5525
|
if (sentinel && result?.committed) {
|
|
5532
5526
|
try {
|
|
5533
|
-
|
|
5534
|
-
|
|
5527
|
+
fs23.mkdirSync(path20.dirname(sentinel), { recursive: true });
|
|
5528
|
+
fs23.writeFileSync(
|
|
5535
5529
|
sentinel,
|
|
5536
5530
|
JSON.stringify(
|
|
5537
5531
|
{
|
|
@@ -5553,8 +5547,8 @@ var init_commitAndPush = __esm({
|
|
|
5553
5547
|
});
|
|
5554
5548
|
|
|
5555
5549
|
// src/goal/state.ts
|
|
5556
|
-
import * as
|
|
5557
|
-
import * as
|
|
5550
|
+
import * as fs24 from "fs";
|
|
5551
|
+
import * as path21 from "path";
|
|
5558
5552
|
function parseGoalState(filePath, raw) {
|
|
5559
5553
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5560
5554
|
throw new GoalStateError(filePath, "must be a JSON object");
|
|
@@ -5606,10 +5600,10 @@ var init_state2 = __esm({
|
|
|
5606
5600
|
"use strict";
|
|
5607
5601
|
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
5608
5602
|
GoalStateError = class extends Error {
|
|
5609
|
-
constructor(
|
|
5610
|
-
super(`Invalid goal state at ${
|
|
5603
|
+
constructor(path43, message) {
|
|
5604
|
+
super(`Invalid goal state at ${path43}:
|
|
5611
5605
|
${message}`);
|
|
5612
|
-
this.path =
|
|
5606
|
+
this.path = path43;
|
|
5613
5607
|
this.name = "GoalStateError";
|
|
5614
5608
|
}
|
|
5615
5609
|
path;
|
|
@@ -5721,8 +5715,8 @@ var init_commitGoalState = __esm({
|
|
|
5721
5715
|
});
|
|
5722
5716
|
|
|
5723
5717
|
// src/scripts/composePrompt.ts
|
|
5724
|
-
import * as
|
|
5725
|
-
import * as
|
|
5718
|
+
import * as fs25 from "fs";
|
|
5719
|
+
import * as path22 from "path";
|
|
5726
5720
|
function fenceUntrusted(value) {
|
|
5727
5721
|
if (value.trim().length === 0) return value;
|
|
5728
5722
|
const safe = value.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
|
|
@@ -5811,6 +5805,10 @@ function formatDutyReference(data, profileName) {
|
|
|
5811
5805
|
if (dutySchedule) {
|
|
5812
5806
|
lines.push(`- Cadence: \`${dutySchedule}\``);
|
|
5813
5807
|
}
|
|
5808
|
+
const dutyBody = pickToken(data, "dutyIntent", "jobIntent");
|
|
5809
|
+
if (dutyBody) {
|
|
5810
|
+
lines.push("", "## Duty body", "", dutyBody);
|
|
5811
|
+
}
|
|
5814
5812
|
if (lines.length === 2) {
|
|
5815
5813
|
return "";
|
|
5816
5814
|
}
|
|
@@ -5839,9 +5837,10 @@ var init_composePrompt = __esm({
|
|
|
5839
5837
|
const explicit = ctx.data.promptTemplate;
|
|
5840
5838
|
const mode = ctx.args.mode;
|
|
5841
5839
|
const candidates = [
|
|
5842
|
-
explicit ?
|
|
5843
|
-
mode ?
|
|
5844
|
-
|
|
5840
|
+
explicit ? path22.join(profile.dir, explicit) : null,
|
|
5841
|
+
mode ? path22.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
5842
|
+
path22.join(profile.dir, "prompt.md"),
|
|
5843
|
+
path22.join(profile.dir, "duty.md")
|
|
5845
5844
|
].filter(Boolean);
|
|
5846
5845
|
let templatePath = "";
|
|
5847
5846
|
let template = "";
|
|
@@ -5854,7 +5853,7 @@ var init_composePrompt = __esm({
|
|
|
5854
5853
|
break;
|
|
5855
5854
|
}
|
|
5856
5855
|
try {
|
|
5857
|
-
template =
|
|
5856
|
+
template = fs25.readFileSync(c, "utf-8");
|
|
5858
5857
|
templatePath = c;
|
|
5859
5858
|
break;
|
|
5860
5859
|
} catch (err) {
|
|
@@ -5865,7 +5864,7 @@ var init_composePrompt = __esm({
|
|
|
5865
5864
|
if (!templatePath) {
|
|
5866
5865
|
let dirState;
|
|
5867
5866
|
try {
|
|
5868
|
-
dirState = `dir contents: [${
|
|
5867
|
+
dirState = `dir contents: [${fs25.readdirSync(profile.dir).join(", ")}]`;
|
|
5869
5868
|
} catch (err) {
|
|
5870
5869
|
dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
|
|
5871
5870
|
}
|
|
@@ -6692,19 +6691,19 @@ var init_deriveQaScopeFromIssue = __esm({
|
|
|
6692
6691
|
|
|
6693
6692
|
// src/scripts/diagMcp.ts
|
|
6694
6693
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
6695
|
-
import * as
|
|
6694
|
+
import * as fs26 from "fs";
|
|
6696
6695
|
import * as os4 from "os";
|
|
6697
|
-
import * as
|
|
6696
|
+
import * as path23 from "path";
|
|
6698
6697
|
var diagMcp;
|
|
6699
6698
|
var init_diagMcp = __esm({
|
|
6700
6699
|
"src/scripts/diagMcp.ts"() {
|
|
6701
6700
|
"use strict";
|
|
6702
6701
|
diagMcp = async (_ctx) => {
|
|
6703
6702
|
const home = os4.homedir();
|
|
6704
|
-
const cacheDir =
|
|
6703
|
+
const cacheDir = path23.join(home, ".cache", "ms-playwright");
|
|
6705
6704
|
let entries = [];
|
|
6706
6705
|
try {
|
|
6707
|
-
entries =
|
|
6706
|
+
entries = fs26.readdirSync(cacheDir);
|
|
6708
6707
|
} catch {
|
|
6709
6708
|
}
|
|
6710
6709
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -6732,13 +6731,13 @@ var init_diagMcp = __esm({
|
|
|
6732
6731
|
});
|
|
6733
6732
|
|
|
6734
6733
|
// src/scripts/frameworkDetectors.ts
|
|
6735
|
-
import * as
|
|
6736
|
-
import * as
|
|
6734
|
+
import * as fs27 from "fs";
|
|
6735
|
+
import * as path24 from "path";
|
|
6737
6736
|
function detectFrameworks(cwd) {
|
|
6738
6737
|
const out = [];
|
|
6739
6738
|
let deps = {};
|
|
6740
6739
|
try {
|
|
6741
|
-
const pkg = JSON.parse(
|
|
6740
|
+
const pkg = JSON.parse(fs27.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
|
|
6742
6741
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6743
6742
|
} catch {
|
|
6744
6743
|
return out;
|
|
@@ -6775,25 +6774,25 @@ function detectFrameworks(cwd) {
|
|
|
6775
6774
|
}
|
|
6776
6775
|
function findFile(cwd, candidates) {
|
|
6777
6776
|
for (const c of candidates) {
|
|
6778
|
-
if (
|
|
6777
|
+
if (fs27.existsSync(path24.join(cwd, c))) return c;
|
|
6779
6778
|
}
|
|
6780
6779
|
return null;
|
|
6781
6780
|
}
|
|
6782
6781
|
function discoverPayloadCollections(cwd) {
|
|
6783
6782
|
const out = [];
|
|
6784
6783
|
for (const dir of COLLECTION_DIRS) {
|
|
6785
|
-
const full =
|
|
6786
|
-
if (!
|
|
6784
|
+
const full = path24.join(cwd, dir);
|
|
6785
|
+
if (!fs27.existsSync(full)) continue;
|
|
6787
6786
|
let files;
|
|
6788
6787
|
try {
|
|
6789
|
-
files =
|
|
6788
|
+
files = fs27.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
6790
6789
|
} catch {
|
|
6791
6790
|
continue;
|
|
6792
6791
|
}
|
|
6793
6792
|
for (const file of files) {
|
|
6794
6793
|
try {
|
|
6795
|
-
const filePath =
|
|
6796
|
-
const content =
|
|
6794
|
+
const filePath = path24.join(full, file);
|
|
6795
|
+
const content = fs27.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
6797
6796
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
6798
6797
|
if (!slugMatch) continue;
|
|
6799
6798
|
const slug = slugMatch[1];
|
|
@@ -6807,7 +6806,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
6807
6806
|
out.push({
|
|
6808
6807
|
name,
|
|
6809
6808
|
slug,
|
|
6810
|
-
filePath:
|
|
6809
|
+
filePath: path24.relative(cwd, filePath),
|
|
6811
6810
|
fields: fields.slice(0, 20),
|
|
6812
6811
|
hasAdmin
|
|
6813
6812
|
});
|
|
@@ -6820,28 +6819,28 @@ function discoverPayloadCollections(cwd) {
|
|
|
6820
6819
|
function discoverAdminComponents(cwd, collections) {
|
|
6821
6820
|
const out = [];
|
|
6822
6821
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
6823
|
-
const full =
|
|
6824
|
-
if (!
|
|
6822
|
+
const full = path24.join(cwd, dir);
|
|
6823
|
+
if (!fs27.existsSync(full)) continue;
|
|
6825
6824
|
let entries;
|
|
6826
6825
|
try {
|
|
6827
|
-
entries =
|
|
6826
|
+
entries = fs27.readdirSync(full, { withFileTypes: true });
|
|
6828
6827
|
} catch {
|
|
6829
6828
|
continue;
|
|
6830
6829
|
}
|
|
6831
6830
|
for (const entry of entries) {
|
|
6832
|
-
const entryPath =
|
|
6831
|
+
const entryPath = path24.join(full, entry.name);
|
|
6833
6832
|
let name;
|
|
6834
6833
|
let filePath;
|
|
6835
6834
|
if (entry.isDirectory()) {
|
|
6836
6835
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
6837
|
-
(f) =>
|
|
6836
|
+
(f) => fs27.existsSync(path24.join(entryPath, f))
|
|
6838
6837
|
);
|
|
6839
6838
|
if (!indexFile) continue;
|
|
6840
6839
|
name = entry.name;
|
|
6841
|
-
filePath =
|
|
6840
|
+
filePath = path24.relative(cwd, path24.join(entryPath, indexFile));
|
|
6842
6841
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
6843
6842
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
6844
|
-
filePath =
|
|
6843
|
+
filePath = path24.relative(cwd, entryPath);
|
|
6845
6844
|
} else {
|
|
6846
6845
|
continue;
|
|
6847
6846
|
}
|
|
@@ -6849,7 +6848,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6849
6848
|
if (collections) {
|
|
6850
6849
|
for (const col of collections) {
|
|
6851
6850
|
try {
|
|
6852
|
-
const colContent =
|
|
6851
|
+
const colContent = fs27.readFileSync(path24.join(cwd, col.filePath), "utf-8");
|
|
6853
6852
|
if (colContent.includes(name)) {
|
|
6854
6853
|
usedInCollection = col.slug;
|
|
6855
6854
|
break;
|
|
@@ -6867,8 +6866,8 @@ function scanApiRoutes(cwd) {
|
|
|
6867
6866
|
const out = [];
|
|
6868
6867
|
const appDirs = ["src/app", "app"];
|
|
6869
6868
|
for (const appDir of appDirs) {
|
|
6870
|
-
const apiDir =
|
|
6871
|
-
if (!
|
|
6869
|
+
const apiDir = path24.join(cwd, appDir, "api");
|
|
6870
|
+
if (!fs27.existsSync(apiDir)) continue;
|
|
6872
6871
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
6873
6872
|
break;
|
|
6874
6873
|
}
|
|
@@ -6877,14 +6876,14 @@ function scanApiRoutes(cwd) {
|
|
|
6877
6876
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
6878
6877
|
let entries;
|
|
6879
6878
|
try {
|
|
6880
|
-
entries =
|
|
6879
|
+
entries = fs27.readdirSync(dir, { withFileTypes: true });
|
|
6881
6880
|
} catch {
|
|
6882
6881
|
return;
|
|
6883
6882
|
}
|
|
6884
6883
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
6885
6884
|
if (routeFile) {
|
|
6886
6885
|
try {
|
|
6887
|
-
const content =
|
|
6886
|
+
const content = fs27.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
6888
6887
|
const methods = HTTP_METHODS.filter(
|
|
6889
6888
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
6890
6889
|
);
|
|
@@ -6892,7 +6891,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6892
6891
|
out.push({
|
|
6893
6892
|
path: prefix,
|
|
6894
6893
|
methods,
|
|
6895
|
-
filePath:
|
|
6894
|
+
filePath: path24.relative(cwd, path24.join(dir, routeFile.name))
|
|
6896
6895
|
});
|
|
6897
6896
|
}
|
|
6898
6897
|
} catch {
|
|
@@ -6903,7 +6902,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6903
6902
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
6904
6903
|
let segment = entry.name;
|
|
6905
6904
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
6906
|
-
walkApiRoutes(
|
|
6905
|
+
walkApiRoutes(path24.join(dir, entry.name), prefix, cwd, out);
|
|
6907
6906
|
continue;
|
|
6908
6907
|
}
|
|
6909
6908
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -6911,16 +6910,16 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6911
6910
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
6912
6911
|
segment = `:${segment.slice(1, -1)}`;
|
|
6913
6912
|
}
|
|
6914
|
-
walkApiRoutes(
|
|
6913
|
+
walkApiRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
6915
6914
|
}
|
|
6916
6915
|
}
|
|
6917
6916
|
function scanEnvVars(cwd) {
|
|
6918
6917
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
6919
6918
|
for (const envFile of candidates) {
|
|
6920
|
-
const envPath =
|
|
6921
|
-
if (!
|
|
6919
|
+
const envPath = path24.join(cwd, envFile);
|
|
6920
|
+
if (!fs27.existsSync(envPath)) continue;
|
|
6922
6921
|
try {
|
|
6923
|
-
const content =
|
|
6922
|
+
const content = fs27.readFileSync(envPath, "utf-8");
|
|
6924
6923
|
const vars = [];
|
|
6925
6924
|
for (const line of content.split("\n")) {
|
|
6926
6925
|
const trimmed = line.trim();
|
|
@@ -6965,8 +6964,8 @@ var init_frameworkDetectors = __esm({
|
|
|
6965
6964
|
});
|
|
6966
6965
|
|
|
6967
6966
|
// src/scripts/discoverQaContext.ts
|
|
6968
|
-
import * as
|
|
6969
|
-
import * as
|
|
6967
|
+
import * as fs28 from "fs";
|
|
6968
|
+
import * as path25 from "path";
|
|
6970
6969
|
function runQaDiscovery(cwd) {
|
|
6971
6970
|
const out = {
|
|
6972
6971
|
routes: [],
|
|
@@ -6997,9 +6996,9 @@ function runQaDiscovery(cwd) {
|
|
|
6997
6996
|
}
|
|
6998
6997
|
function detectDevServer(cwd, out) {
|
|
6999
6998
|
try {
|
|
7000
|
-
const pkg = JSON.parse(
|
|
6999
|
+
const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
|
|
7001
7000
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7002
|
-
const pm =
|
|
7001
|
+
const pm = fs28.existsSync(path25.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs28.existsSync(path25.join(cwd, "yarn.lock")) ? "yarn" : fs28.existsSync(path25.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
7003
7002
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
7004
7003
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
7005
7004
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -7009,8 +7008,8 @@ function detectDevServer(cwd, out) {
|
|
|
7009
7008
|
function scanFrontendRoutes(cwd, out) {
|
|
7010
7009
|
const appDirs = ["src/app", "app"];
|
|
7011
7010
|
for (const appDir of appDirs) {
|
|
7012
|
-
const full =
|
|
7013
|
-
if (!
|
|
7011
|
+
const full = path25.join(cwd, appDir);
|
|
7012
|
+
if (!fs28.existsSync(full)) continue;
|
|
7014
7013
|
walkFrontendRoutes(full, "", out);
|
|
7015
7014
|
break;
|
|
7016
7015
|
}
|
|
@@ -7018,7 +7017,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
7018
7017
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
7019
7018
|
let entries;
|
|
7020
7019
|
try {
|
|
7021
|
-
entries =
|
|
7020
|
+
entries = fs28.readdirSync(dir, { withFileTypes: true });
|
|
7022
7021
|
} catch {
|
|
7023
7022
|
return;
|
|
7024
7023
|
}
|
|
@@ -7035,7 +7034,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7035
7034
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7036
7035
|
let segment = entry.name;
|
|
7037
7036
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7038
|
-
walkFrontendRoutes(
|
|
7037
|
+
walkFrontendRoutes(path25.join(dir, entry.name), prefix, out);
|
|
7039
7038
|
continue;
|
|
7040
7039
|
}
|
|
7041
7040
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7043,7 +7042,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7043
7042
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7044
7043
|
segment = `:${segment.slice(1, -1)}`;
|
|
7045
7044
|
}
|
|
7046
|
-
walkFrontendRoutes(
|
|
7045
|
+
walkFrontendRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
7047
7046
|
}
|
|
7048
7047
|
}
|
|
7049
7048
|
function detectAuthFiles(cwd, out) {
|
|
@@ -7060,23 +7059,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
7060
7059
|
"src/app/api/oauth"
|
|
7061
7060
|
];
|
|
7062
7061
|
for (const c of candidates) {
|
|
7063
|
-
if (
|
|
7062
|
+
if (fs28.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
|
|
7064
7063
|
}
|
|
7065
7064
|
}
|
|
7066
7065
|
function detectRoles(cwd, out) {
|
|
7067
7066
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
7068
7067
|
for (const rp of rolePaths) {
|
|
7069
|
-
const dir =
|
|
7070
|
-
if (!
|
|
7068
|
+
const dir = path25.join(cwd, rp);
|
|
7069
|
+
if (!fs28.existsSync(dir)) continue;
|
|
7071
7070
|
let files;
|
|
7072
7071
|
try {
|
|
7073
|
-
files =
|
|
7072
|
+
files = fs28.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7074
7073
|
} catch {
|
|
7075
7074
|
continue;
|
|
7076
7075
|
}
|
|
7077
7076
|
for (const f of files) {
|
|
7078
7077
|
try {
|
|
7079
|
-
const content =
|
|
7078
|
+
const content = fs28.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
|
|
7080
7079
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
7081
7080
|
if (roleMatches) {
|
|
7082
7081
|
for (const m of roleMatches) {
|
|
@@ -7369,6 +7368,23 @@ async function runJob(job, base) {
|
|
|
7369
7368
|
if (executableIdentity !== void 0 && executableIdentity.length > 0)
|
|
7370
7369
|
preloadedData.jobExecutable = executableIdentity;
|
|
7371
7370
|
if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
|
|
7371
|
+
const dutyContext = loadDutyContext(dutyIdentity ?? valid.duty);
|
|
7372
|
+
if (dutyContext) {
|
|
7373
|
+
preloadedData.dutySlug = dutyContext.slug;
|
|
7374
|
+
preloadedData.dutyTitle = dutyContext.title;
|
|
7375
|
+
preloadedData.dutyIntent = dutyContext.body;
|
|
7376
|
+
preloadedData.jobIntent = dutyContext.body;
|
|
7377
|
+
if (preloadedData.jobDuty === void 0) preloadedData.jobDuty = dutyContext.slug;
|
|
7378
|
+
if (dutyContext.config.staff && preloadedData.jobPersona === void 0) {
|
|
7379
|
+
preloadedData.jobPersona = dutyContext.config.staff;
|
|
7380
|
+
}
|
|
7381
|
+
if (dutyContext.config.every && preloadedData.jobSchedule === void 0) {
|
|
7382
|
+
preloadedData.jobSchedule = dutyContext.config.every;
|
|
7383
|
+
}
|
|
7384
|
+
if (dutyContext.config.mentions && dutyContext.config.mentions.length > 0) {
|
|
7385
|
+
preloadedData.mentions = dutyContext.config.mentions.map((login) => `@${login}`).join(" ");
|
|
7386
|
+
}
|
|
7387
|
+
}
|
|
7372
7388
|
if (valid.why !== void 0 && valid.why.length > 0) preloadedData.jobWhy = valid.why;
|
|
7373
7389
|
if (valid.persona !== void 0) preloadedData.jobPersona = valid.persona;
|
|
7374
7390
|
const input = {
|
|
@@ -7384,6 +7400,10 @@ async function runJob(job, base) {
|
|
|
7384
7400
|
const run = base.chain === false ? runExecutable : runExecutableChain;
|
|
7385
7401
|
return run(profileName, input);
|
|
7386
7402
|
}
|
|
7403
|
+
function loadDutyContext(slug) {
|
|
7404
|
+
if (!slug) return null;
|
|
7405
|
+
return readDutyFolder(getProjectDutiesRoot(), slug) ?? readDutyFolder(getBuiltinDutiesRoot(), slug);
|
|
7406
|
+
}
|
|
7387
7407
|
function mintInstantJob(dispatch2, opts) {
|
|
7388
7408
|
return {
|
|
7389
7409
|
action: dispatch2.action,
|
|
@@ -7411,6 +7431,7 @@ var init_job = __esm({
|
|
|
7411
7431
|
"src/job.ts"() {
|
|
7412
7432
|
"use strict";
|
|
7413
7433
|
init_executor();
|
|
7434
|
+
init_dutyFolders();
|
|
7414
7435
|
init_registry();
|
|
7415
7436
|
init_jobIdentity();
|
|
7416
7437
|
init_jobIdentity();
|
|
@@ -7642,8 +7663,8 @@ var init_contentsApiBackend = __esm({
|
|
|
7642
7663
|
});
|
|
7643
7664
|
|
|
7644
7665
|
// src/scripts/jobState/localFileBackend.ts
|
|
7645
|
-
import * as
|
|
7646
|
-
import * as
|
|
7666
|
+
import * as fs29 from "fs";
|
|
7667
|
+
import * as path26 from "path";
|
|
7647
7668
|
function sanitizeKey(s) {
|
|
7648
7669
|
return s.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
7649
7670
|
}
|
|
@@ -7699,7 +7720,7 @@ var init_localFileBackend = __esm({
|
|
|
7699
7720
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
7700
7721
|
this.cwd = opts.cwd;
|
|
7701
7722
|
this.jobsDir = opts.jobsDir;
|
|
7702
|
-
this.absDir =
|
|
7723
|
+
this.absDir = path26.join(opts.cwd, opts.jobsDir);
|
|
7703
7724
|
this.owner = opts.owner;
|
|
7704
7725
|
this.repo = opts.repo;
|
|
7705
7726
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -7714,7 +7735,7 @@ var init_localFileBackend = __esm({
|
|
|
7714
7735
|
`);
|
|
7715
7736
|
return;
|
|
7716
7737
|
}
|
|
7717
|
-
|
|
7738
|
+
fs29.mkdirSync(this.absDir, { recursive: true });
|
|
7718
7739
|
const prefix = this.cacheKeyPrefix();
|
|
7719
7740
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
7720
7741
|
try {
|
|
@@ -7743,7 +7764,7 @@ var init_localFileBackend = __esm({
|
|
|
7743
7764
|
`);
|
|
7744
7765
|
return;
|
|
7745
7766
|
}
|
|
7746
|
-
if (!
|
|
7767
|
+
if (!fs29.existsSync(this.absDir)) {
|
|
7747
7768
|
return;
|
|
7748
7769
|
}
|
|
7749
7770
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -7759,11 +7780,11 @@ var init_localFileBackend = __esm({
|
|
|
7759
7780
|
}
|
|
7760
7781
|
load(slug) {
|
|
7761
7782
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
7762
|
-
const absPath =
|
|
7763
|
-
if (!
|
|
7783
|
+
const absPath = path26.join(this.cwd, relPath);
|
|
7784
|
+
if (!fs29.existsSync(absPath)) {
|
|
7764
7785
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
7765
7786
|
}
|
|
7766
|
-
const raw =
|
|
7787
|
+
const raw = fs29.readFileSync(absPath, "utf-8");
|
|
7767
7788
|
let parsed;
|
|
7768
7789
|
try {
|
|
7769
7790
|
parsed = JSON.parse(raw);
|
|
@@ -7780,13 +7801,13 @@ var init_localFileBackend = __esm({
|
|
|
7780
7801
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
7781
7802
|
return false;
|
|
7782
7803
|
}
|
|
7783
|
-
const absPath =
|
|
7784
|
-
|
|
7804
|
+
const absPath = path26.join(this.cwd, loaded.path);
|
|
7805
|
+
fs29.mkdirSync(path26.dirname(absPath), { recursive: true });
|
|
7785
7806
|
const body = `${JSON.stringify(next, null, 2)}
|
|
7786
7807
|
`;
|
|
7787
7808
|
const tmpPath = `${absPath}.${process.pid}.tmp`;
|
|
7788
|
-
|
|
7789
|
-
|
|
7809
|
+
fs29.writeFileSync(tmpPath, body, "utf-8");
|
|
7810
|
+
fs29.renameSync(tmpPath, absPath);
|
|
7790
7811
|
return true;
|
|
7791
7812
|
}
|
|
7792
7813
|
cacheKeyPrefix() {
|
|
@@ -7938,8 +7959,7 @@ var init_planTaskJobs = __esm({
|
|
|
7938
7959
|
});
|
|
7939
7960
|
|
|
7940
7961
|
// src/scripts/dispatchDutyFileTicks.ts
|
|
7941
|
-
import * as
|
|
7942
|
-
import * as path26 from "path";
|
|
7962
|
+
import * as path27 from "path";
|
|
7943
7963
|
async function decideShouldFire(every, slug, backend, now) {
|
|
7944
7964
|
if (!every) return { skip: false, reason: "no schedule (every cron tick)" };
|
|
7945
7965
|
if (every === "manual") {
|
|
@@ -7980,14 +8000,6 @@ function formatAgo(ms) {
|
|
|
7980
8000
|
const day = Math.round(hr / 24);
|
|
7981
8001
|
return `${day}d`;
|
|
7982
8002
|
}
|
|
7983
|
-
function readJobFile(cwd, jobsDir, slug) {
|
|
7984
|
-
try {
|
|
7985
|
-
const raw = fs29.readFileSync(path26.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
7986
|
-
return splitFrontmatter(raw);
|
|
7987
|
-
} catch {
|
|
7988
|
-
return { frontmatter: {}, body: "" };
|
|
7989
|
-
}
|
|
7990
|
-
}
|
|
7991
8003
|
function parseDutyFilter(raw) {
|
|
7992
8004
|
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
7993
8005
|
}
|
|
@@ -7996,21 +8008,21 @@ function filterSlugs(slugs, onlyDuty) {
|
|
|
7996
8008
|
}
|
|
7997
8009
|
function createDutyTaskIssue(opts) {
|
|
7998
8010
|
const title = `Duty ${opts.slug} - multi-executable task`;
|
|
7999
|
-
const body = buildDutyTaskIssueBody(opts.slug, opts.body, opts.
|
|
8011
|
+
const body = buildDutyTaskIssueBody(opts.slug, opts.body, opts.config);
|
|
8000
8012
|
const out = gh(["issue", "create", "--title", title, "--body-file", "-"], { input: body, cwd: opts.cwd });
|
|
8001
8013
|
const url = out.split("\n").map((line) => line.trim()).filter(Boolean).pop() ?? "";
|
|
8002
8014
|
const match = url.match(/\/issues\/(\d+)\b/);
|
|
8003
8015
|
if (!match) throw new Error(`gh issue create returned unexpected output: ${out}`);
|
|
8004
8016
|
return { number: Number(match[1]), url };
|
|
8005
8017
|
}
|
|
8006
|
-
function buildDutyTaskIssueBody(slug, dutyBody,
|
|
8007
|
-
const specs = (
|
|
8018
|
+
function buildDutyTaskIssueBody(slug, dutyBody, config) {
|
|
8019
|
+
const specs = (config.executables ?? []).map((executable) => ({
|
|
8008
8020
|
executable,
|
|
8009
8021
|
duty: slug,
|
|
8010
|
-
...
|
|
8022
|
+
...config.staff ? { staff: config.staff } : {},
|
|
8011
8023
|
reason: `Duty \`${slug}\` slice for \`${executable}\`.`,
|
|
8012
8024
|
flavor: "scheduled",
|
|
8013
|
-
...
|
|
8025
|
+
...config.every ? { schedule: config.every } : {}
|
|
8014
8026
|
}));
|
|
8015
8027
|
return [
|
|
8016
8028
|
`# Duty task: ${slug}`,
|
|
@@ -8023,26 +8035,6 @@ function buildDutyTaskIssueBody(slug, dutyBody, frontmatter) {
|
|
|
8023
8035
|
""
|
|
8024
8036
|
].join("\n");
|
|
8025
8037
|
}
|
|
8026
|
-
function listJobSlugs(absDir) {
|
|
8027
|
-
if (!fs29.existsSync(absDir)) return [];
|
|
8028
|
-
let entries;
|
|
8029
|
-
try {
|
|
8030
|
-
entries = fs29.readdirSync(absDir, { withFileTypes: true });
|
|
8031
|
-
} catch {
|
|
8032
|
-
return [];
|
|
8033
|
-
}
|
|
8034
|
-
return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, "")).filter((slug) => slug.length > 0 && !slug.startsWith("_") && !slug.startsWith(".")).sort();
|
|
8035
|
-
}
|
|
8036
|
-
function listFolderDutySlugs(absDir) {
|
|
8037
|
-
if (!fs29.existsSync(absDir)) return [];
|
|
8038
|
-
let entries;
|
|
8039
|
-
try {
|
|
8040
|
-
entries = fs29.readdirSync(absDir, { withFileTypes: true });
|
|
8041
|
-
} catch {
|
|
8042
|
-
return [];
|
|
8043
|
-
}
|
|
8044
|
-
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => fs29.existsSync(path26.join(absDir, e.name, "profile.json"))).map((e) => e.name).sort();
|
|
8045
|
-
}
|
|
8046
8038
|
async function stampFired(backend, slug, now, task) {
|
|
8047
8039
|
try {
|
|
8048
8040
|
const loaded = await backend.load(slug);
|
|
@@ -8061,10 +8053,10 @@ var dispatchDutyFileTicks;
|
|
|
8061
8053
|
var init_dispatchDutyFileTicks = __esm({
|
|
8062
8054
|
"src/scripts/dispatchDutyFileTicks.ts"() {
|
|
8063
8055
|
"use strict";
|
|
8056
|
+
init_dutyFolders();
|
|
8064
8057
|
init_issue();
|
|
8065
8058
|
init_job();
|
|
8066
|
-
|
|
8067
|
-
init_jobFrontmatter();
|
|
8059
|
+
init_scheduleEvery();
|
|
8068
8060
|
init_jobState();
|
|
8069
8061
|
init_planTaskJobs();
|
|
8070
8062
|
dispatchDutyFileTicks = async (ctx, _profile, args) => {
|
|
@@ -8082,107 +8074,54 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8082
8074
|
}
|
|
8083
8075
|
try {
|
|
8084
8076
|
const onlyDuty = parseDutyFilter(ctx.args.duty);
|
|
8085
|
-
const jobsPath =
|
|
8086
|
-
const slugs = filterSlugs(
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
if (slugs.length === 0 && folderSlugList.length === 0) {
|
|
8077
|
+
const jobsPath = path27.join(ctx.cwd, jobsDir);
|
|
8078
|
+
const slugs = filterSlugs(listDutyFolderSlugs(jobsPath), onlyDuty);
|
|
8079
|
+
ctx.data.jobSlugCount = slugs.length;
|
|
8080
|
+
if (slugs.length === 0) {
|
|
8090
8081
|
const filter = onlyDuty ? ` matching ${onlyDuty}` : "";
|
|
8091
|
-
process.stdout.write(`[jobs] no
|
|
8082
|
+
process.stdout.write(`[jobs] no duty folders${filter} in ${jobsDir}
|
|
8092
8083
|
`);
|
|
8093
8084
|
return;
|
|
8094
8085
|
}
|
|
8095
8086
|
const filtered = onlyDuty ? ` matching ${onlyDuty}` : "";
|
|
8096
|
-
process.stdout.write(
|
|
8097
|
-
|
|
8098
|
-
`
|
|
8099
|
-
);
|
|
8087
|
+
process.stdout.write(`[jobs] ticking ${slugs.length} dut(y/ies)${filtered} via ${targetExecutable}
|
|
8088
|
+
`);
|
|
8100
8089
|
const results = [];
|
|
8101
8090
|
const now = Date.now();
|
|
8102
|
-
const folderDutySlugs = new Set(folderSlugList);
|
|
8103
|
-
const scheduledDuties = folderSlugList.map((slug) => {
|
|
8104
|
-
try {
|
|
8105
|
-
const p = loadProfile(path26.join(ctx.cwd, jobsDir, slug, "profile.json"));
|
|
8106
|
-
return { slug, every: p.every, staff: p.staff };
|
|
8107
|
-
} catch (err) {
|
|
8108
|
-
process.stderr.write(`[jobs] \u23ED skip folder-duty ${slug}: profile load failed: ${String(err)}
|
|
8109
|
-
`);
|
|
8110
|
-
return null;
|
|
8111
|
-
}
|
|
8112
|
-
}).filter((d) => d !== null && Boolean(d.every));
|
|
8113
|
-
process.stdout.write(`[jobs] ${scheduledDuties.length} scheduled folder-dut(y/ies) to consider
|
|
8114
|
-
`);
|
|
8115
|
-
for (const { slug, every, staff } of scheduledDuties) {
|
|
8116
|
-
if (!staff || staff.trim().length === 0) {
|
|
8117
|
-
process.stderr.write(`[jobs] \u23ED skip ${slug}: scheduled duty has no staff
|
|
8118
|
-
`);
|
|
8119
|
-
results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
|
|
8120
|
-
continue;
|
|
8121
|
-
}
|
|
8122
|
-
const decision = await decideShouldFire(every, slug, backend, now);
|
|
8123
|
-
if (decision.skip) {
|
|
8124
|
-
process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
|
|
8125
|
-
`);
|
|
8126
|
-
results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
|
|
8127
|
-
continue;
|
|
8128
|
-
}
|
|
8129
|
-
await stampFired(backend, slug, now);
|
|
8130
|
-
process.stdout.write(`[jobs] \u2192 run scheduled duty ${slug} (one-shot, as ${staff})
|
|
8131
|
-
`);
|
|
8132
|
-
try {
|
|
8133
|
-
const out = await runJob(mintScheduledJob({ duty: slug, executable: slug, schedule: every }), {
|
|
8134
|
-
cwd: ctx.cwd,
|
|
8135
|
-
config: ctx.config,
|
|
8136
|
-
verbose: ctx.verbose,
|
|
8137
|
-
quiet: ctx.quiet,
|
|
8138
|
-
chain: false
|
|
8139
|
-
});
|
|
8140
|
-
results.push({ slug, exitCode: out.exitCode, reason: out.reason });
|
|
8141
|
-
if (out.exitCode !== 0) {
|
|
8142
|
-
process.stderr.write(`[jobs] scheduled duty ${slug} failed (exit ${out.exitCode}): ${out.reason ?? ""}
|
|
8143
|
-
`);
|
|
8144
|
-
}
|
|
8145
|
-
} catch (err) {
|
|
8146
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
8147
|
-
process.stderr.write(`[jobs] scheduled duty ${slug} crashed: ${msg}
|
|
8148
|
-
`);
|
|
8149
|
-
results.push({ slug, exitCode: 99, reason: msg });
|
|
8150
|
-
}
|
|
8151
|
-
}
|
|
8152
8091
|
for (const slug of slugs) {
|
|
8153
|
-
|
|
8154
|
-
|
|
8092
|
+
const duty = readDutyFolder(jobsPath, slug);
|
|
8093
|
+
if (!duty) {
|
|
8094
|
+
process.stderr.write(`[jobs] \u23ED skip ${slug}: duty folder is missing profile.json or duty.md
|
|
8155
8095
|
`);
|
|
8156
|
-
results.push({ slug, exitCode: 0, skipped: true, reason: "
|
|
8096
|
+
results.push({ slug, exitCode: 0, skipped: true, reason: "incomplete duty folder" });
|
|
8157
8097
|
continue;
|
|
8158
8098
|
}
|
|
8159
|
-
const
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
process.stdout.write(`[jobs] \u23ED skip ${slug}: disabled in frontmatter
|
|
8099
|
+
const config = duty.config;
|
|
8100
|
+
if (config.disabled === true) {
|
|
8101
|
+
process.stdout.write(`[jobs] \u23ED skip ${slug}: disabled in profile.json
|
|
8163
8102
|
`);
|
|
8164
8103
|
results.push({ slug, exitCode: 0, skipped: true, reason: "disabled" });
|
|
8165
8104
|
continue;
|
|
8166
8105
|
}
|
|
8167
|
-
if (!
|
|
8168
|
-
process.stderr.write(`[jobs] \u23ED skip ${slug}: no staff assigned (add
|
|
8106
|
+
if (!config.staff || config.staff.trim().length === 0) {
|
|
8107
|
+
process.stderr.write(`[jobs] \u23ED skip ${slug}: no staff assigned (add "staff" to profile.json)
|
|
8169
8108
|
`);
|
|
8170
8109
|
results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
|
|
8171
8110
|
continue;
|
|
8172
8111
|
}
|
|
8173
|
-
const decision = await decideShouldFire(
|
|
8112
|
+
const decision = await decideShouldFire(config.every, slug, backend, now);
|
|
8174
8113
|
if (decision.skip) {
|
|
8175
8114
|
process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
|
|
8176
8115
|
`);
|
|
8177
8116
|
results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
|
|
8178
8117
|
continue;
|
|
8179
8118
|
}
|
|
8180
|
-
if (
|
|
8119
|
+
if (config.executables && config.executables.length > 0) {
|
|
8181
8120
|
try {
|
|
8182
8121
|
const task = createDutyTaskIssue({
|
|
8183
8122
|
slug,
|
|
8184
|
-
body:
|
|
8185
|
-
|
|
8123
|
+
body: duty.body,
|
|
8124
|
+
config,
|
|
8186
8125
|
cwd: ctx.cwd
|
|
8187
8126
|
});
|
|
8188
8127
|
await stampFired(backend, slug, now, task);
|
|
@@ -8192,8 +8131,8 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8192
8131
|
mintScheduledJob({
|
|
8193
8132
|
duty: slug,
|
|
8194
8133
|
executable: "task-jobs",
|
|
8195
|
-
schedule:
|
|
8196
|
-
persona:
|
|
8134
|
+
schedule: config.every,
|
|
8135
|
+
persona: config.staff,
|
|
8197
8136
|
cliArgs: { issue: task.number }
|
|
8198
8137
|
}),
|
|
8199
8138
|
{ cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet }
|
|
@@ -8211,7 +8150,8 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8211
8150
|
}
|
|
8212
8151
|
continue;
|
|
8213
8152
|
}
|
|
8214
|
-
const slugTarget =
|
|
8153
|
+
const slugTarget = config.tickScript ? scriptedExecutable : config.executable ?? targetExecutable;
|
|
8154
|
+
const cliArgs = config.executable ? {} : { [slugArg]: slug };
|
|
8215
8155
|
process.stdout.write(`[jobs] \u2192 tick ${slug} (${slugTarget})
|
|
8216
8156
|
`);
|
|
8217
8157
|
try {
|
|
@@ -8219,8 +8159,9 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8219
8159
|
mintScheduledJob({
|
|
8220
8160
|
duty: slug,
|
|
8221
8161
|
executable: slugTarget,
|
|
8222
|
-
schedule:
|
|
8223
|
-
|
|
8162
|
+
schedule: config.every,
|
|
8163
|
+
persona: config.staff,
|
|
8164
|
+
cliArgs
|
|
8224
8165
|
}),
|
|
8225
8166
|
{ cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false }
|
|
8226
8167
|
);
|
|
@@ -9463,11 +9404,11 @@ var init_handleAbandonedGoal = __esm({
|
|
|
9463
9404
|
// src/scripts/initFlow.ts
|
|
9464
9405
|
import { execFileSync as execFileSync16 } from "child_process";
|
|
9465
9406
|
import * as fs30 from "fs";
|
|
9466
|
-
import * as
|
|
9407
|
+
import * as path28 from "path";
|
|
9467
9408
|
function detectPackageManager(cwd) {
|
|
9468
|
-
if (fs30.existsSync(
|
|
9469
|
-
if (fs30.existsSync(
|
|
9470
|
-
if (fs30.existsSync(
|
|
9409
|
+
if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9410
|
+
if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
|
|
9411
|
+
if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
|
|
9471
9412
|
return "npm";
|
|
9472
9413
|
}
|
|
9473
9414
|
function qualityCommandsFor(pm) {
|
|
@@ -9539,7 +9480,7 @@ function performInit(cwd, force) {
|
|
|
9539
9480
|
const pm = detectPackageManager(cwd);
|
|
9540
9481
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
9541
9482
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
9542
|
-
const configPath =
|
|
9483
|
+
const configPath = path28.join(cwd, "kody.config.json");
|
|
9543
9484
|
if (fs30.existsSync(configPath) && !force) {
|
|
9544
9485
|
skipped.push("kody.config.json");
|
|
9545
9486
|
} else {
|
|
@@ -9548,8 +9489,8 @@ function performInit(cwd, force) {
|
|
|
9548
9489
|
`);
|
|
9549
9490
|
wrote.push("kody.config.json");
|
|
9550
9491
|
}
|
|
9551
|
-
const workflowDir =
|
|
9552
|
-
const workflowPath =
|
|
9492
|
+
const workflowDir = path28.join(cwd, ".github", "workflows");
|
|
9493
|
+
const workflowPath = path28.join(workflowDir, "kody.yml");
|
|
9553
9494
|
if (fs30.existsSync(workflowPath) && !force) {
|
|
9554
9495
|
skipped.push(".github/workflows/kody.yml");
|
|
9555
9496
|
} else {
|
|
@@ -9559,37 +9500,26 @@ function performInit(cwd, force) {
|
|
|
9559
9500
|
}
|
|
9560
9501
|
const builtinJobs = listBuiltinJobs();
|
|
9561
9502
|
if (builtinJobs.length > 0) {
|
|
9562
|
-
const jobsDir =
|
|
9503
|
+
const jobsDir = path28.join(cwd, ".kody", "duties");
|
|
9563
9504
|
fs30.mkdirSync(jobsDir, { recursive: true });
|
|
9564
9505
|
for (const job of builtinJobs) {
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
skipped.push(rel);
|
|
9570
|
-
continue;
|
|
9571
|
-
}
|
|
9572
|
-
fs30.writeFileSync(target, fs30.readFileSync(job.filePath, "utf-8"));
|
|
9573
|
-
wrote.push(rel);
|
|
9574
|
-
continue;
|
|
9575
|
-
}
|
|
9576
|
-
const targetDir = path27.join(jobsDir, job.slug);
|
|
9577
|
-
const relProfile = path27.join(".kody", "duties", job.slug, "profile.json");
|
|
9578
|
-
const relPrompt = path27.join(".kody", "duties", job.slug, "prompt.md");
|
|
9579
|
-
if (fs30.existsSync(targetDir) && fs30.existsSync(path27.join(targetDir, "profile.json")) && !force) {
|
|
9506
|
+
const targetDir = path28.join(jobsDir, job.slug);
|
|
9507
|
+
const relProfile = path28.join(".kody", "duties", job.slug, "profile.json");
|
|
9508
|
+
const relBody = path28.join(".kody", "duties", job.slug, "duty.md");
|
|
9509
|
+
if (fs30.existsSync(targetDir) && fs30.existsSync(path28.join(targetDir, "profile.json")) && !force) {
|
|
9580
9510
|
skipped.push(relProfile);
|
|
9581
|
-
skipped.push(
|
|
9511
|
+
skipped.push(relBody);
|
|
9582
9512
|
continue;
|
|
9583
9513
|
}
|
|
9584
9514
|
fs30.mkdirSync(targetDir, { recursive: true });
|
|
9585
|
-
fs30.writeFileSync(
|
|
9586
|
-
fs30.writeFileSync(
|
|
9515
|
+
fs30.writeFileSync(path28.join(targetDir, "profile.json"), fs30.readFileSync(job.profilePath, "utf-8"));
|
|
9516
|
+
fs30.writeFileSync(path28.join(targetDir, "duty.md"), fs30.readFileSync(job.bodyPath, "utf-8"));
|
|
9587
9517
|
wrote.push(relProfile);
|
|
9588
|
-
wrote.push(
|
|
9518
|
+
wrote.push(relBody);
|
|
9589
9519
|
}
|
|
9590
9520
|
}
|
|
9591
|
-
const staffDir =
|
|
9592
|
-
const staffPath =
|
|
9521
|
+
const staffDir = path28.join(cwd, ".kody", "staff");
|
|
9522
|
+
const staffPath = path28.join(staffDir, "kody.md");
|
|
9593
9523
|
if (fs30.existsSync(staffPath) && !force) {
|
|
9594
9524
|
skipped.push(".kody/staff/kody.md");
|
|
9595
9525
|
} else {
|
|
@@ -9605,7 +9535,7 @@ function performInit(cwd, force) {
|
|
|
9605
9535
|
continue;
|
|
9606
9536
|
}
|
|
9607
9537
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
9608
|
-
const target =
|
|
9538
|
+
const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
|
|
9609
9539
|
if (fs30.existsSync(target) && !force) {
|
|
9610
9540
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9611
9541
|
continue;
|
|
@@ -9931,7 +9861,7 @@ var init_loadIssueStateComment = __esm({
|
|
|
9931
9861
|
|
|
9932
9862
|
// src/scripts/loadJobFromFile.ts
|
|
9933
9863
|
import * as fs31 from "fs";
|
|
9934
|
-
import * as
|
|
9864
|
+
import * as path29 from "path";
|
|
9935
9865
|
function parseJobFile(raw, slug) {
|
|
9936
9866
|
let stripped = raw;
|
|
9937
9867
|
if (stripped.startsWith("---\n")) {
|
|
@@ -9947,17 +9877,17 @@ function parseJobFile(raw, slug) {
|
|
|
9947
9877
|
const rest = trimmed.slice(firstLine2.length).replace(/^\n+/, "");
|
|
9948
9878
|
return { title: h1[1].trim(), body: rest };
|
|
9949
9879
|
}
|
|
9950
|
-
return { title:
|
|
9880
|
+
return { title: humanizeSlug2(slug), body: trimmed };
|
|
9951
9881
|
}
|
|
9952
|
-
function
|
|
9882
|
+
function humanizeSlug2(slug) {
|
|
9953
9883
|
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
9954
9884
|
}
|
|
9955
9885
|
var DUTY_TOOL_PALETTE2, loadJobFromFile;
|
|
9956
9886
|
var init_loadJobFromFile = __esm({
|
|
9957
9887
|
"src/scripts/loadJobFromFile.ts"() {
|
|
9958
9888
|
"use strict";
|
|
9889
|
+
init_dutyFolders();
|
|
9959
9890
|
init_dutyMcp();
|
|
9960
|
-
init_jobFrontmatter();
|
|
9961
9891
|
init_jobState();
|
|
9962
9892
|
DUTY_TOOL_PALETTE2 = new Set(DUTY_MCP_TOOL_NAMES);
|
|
9963
9893
|
loadJobFromFile = async (ctx, profile, args) => {
|
|
@@ -9968,19 +9898,17 @@ var init_loadJobFromFile = __esm({
|
|
|
9968
9898
|
if (!slug) {
|
|
9969
9899
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
9970
9900
|
}
|
|
9971
|
-
const
|
|
9972
|
-
if (!
|
|
9973
|
-
throw new Error(`loadJobFromFile:
|
|
9901
|
+
const duty = readDutyFolder(path29.join(ctx.cwd, jobsDir), slug);
|
|
9902
|
+
if (!duty) {
|
|
9903
|
+
throw new Error(`loadJobFromFile: duty folder not found or incomplete: ${path29.join(ctx.cwd, jobsDir, slug)}`);
|
|
9974
9904
|
}
|
|
9975
|
-
const
|
|
9976
|
-
const
|
|
9977
|
-
const
|
|
9978
|
-
const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
9979
|
-
const workerSlug = (frontmatter.staff ?? "").trim();
|
|
9905
|
+
const { title, body, config } = duty;
|
|
9906
|
+
const mentions = (config.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
9907
|
+
const workerSlug = (config.staff ?? "").trim();
|
|
9980
9908
|
let workerTitle = "";
|
|
9981
9909
|
let workerPersona = "";
|
|
9982
9910
|
if (workerSlug) {
|
|
9983
|
-
const workerPath =
|
|
9911
|
+
const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
9984
9912
|
if (!fs31.existsSync(workerPath)) {
|
|
9985
9913
|
throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
|
|
9986
9914
|
}
|
|
@@ -10005,8 +9933,8 @@ var init_loadJobFromFile = __esm({
|
|
|
10005
9933
|
ctx.data.staffSlug = workerSlug;
|
|
10006
9934
|
ctx.data.staffTitle = workerTitle;
|
|
10007
9935
|
ctx.data.executableSlug = profile.name;
|
|
10008
|
-
ctx.data.dutySchedule = "";
|
|
10009
|
-
const declaredTools =
|
|
9936
|
+
ctx.data.dutySchedule = config.every ?? "";
|
|
9937
|
+
const declaredTools = config.tools ?? [];
|
|
10010
9938
|
if (declaredTools.length > 0) {
|
|
10011
9939
|
const unknown = declaredTools.filter((name) => !DUTY_TOOL_PALETTE2.has(name));
|
|
10012
9940
|
if (unknown.length > 0) {
|
|
@@ -10059,9 +9987,9 @@ ${truncate2(issue.body, FINDING_BODY_MAX_BYTES)}`;
|
|
|
10059
9987
|
|
|
10060
9988
|
// src/scripts/kodyVariables.ts
|
|
10061
9989
|
import * as fs32 from "fs";
|
|
10062
|
-
import * as
|
|
9990
|
+
import * as path30 from "path";
|
|
10063
9991
|
function readKodyVariables(cwd) {
|
|
10064
|
-
const full =
|
|
9992
|
+
const full = path30.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
10065
9993
|
let raw;
|
|
10066
9994
|
try {
|
|
10067
9995
|
raw = fs32.readFileSync(full, "utf-8");
|
|
@@ -10090,7 +10018,7 @@ var init_kodyVariables = __esm({
|
|
|
10090
10018
|
|
|
10091
10019
|
// src/scripts/loadQaContext.ts
|
|
10092
10020
|
import * as fs33 from "fs";
|
|
10093
|
-
import * as
|
|
10021
|
+
import * as path31 from "path";
|
|
10094
10022
|
function parseSlugList(value) {
|
|
10095
10023
|
const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
|
10096
10024
|
return inner.split(",").map(
|
|
@@ -10098,7 +10026,7 @@ function parseSlugList(value) {
|
|
|
10098
10026
|
).filter(Boolean);
|
|
10099
10027
|
}
|
|
10100
10028
|
function readProfileStaff(raw) {
|
|
10101
|
-
const m =
|
|
10029
|
+
const m = FRONTMATTER_RE.exec(raw);
|
|
10102
10030
|
if (!m) return { staff: ["kody"], body: raw };
|
|
10103
10031
|
const body = raw.slice(m[0].length);
|
|
10104
10032
|
let staff = null;
|
|
@@ -10119,7 +10047,7 @@ function readProfileStaff(raw) {
|
|
|
10119
10047
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
10120
10048
|
}
|
|
10121
10049
|
function readProfile(cwd) {
|
|
10122
|
-
const dir =
|
|
10050
|
+
const dir = path31.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
10123
10051
|
if (!fs33.existsSync(dir)) return "";
|
|
10124
10052
|
let entries;
|
|
10125
10053
|
try {
|
|
@@ -10130,7 +10058,7 @@ function readProfile(cwd) {
|
|
|
10130
10058
|
const blocks = [];
|
|
10131
10059
|
for (const file of entries) {
|
|
10132
10060
|
try {
|
|
10133
|
-
const raw = fs33.readFileSync(
|
|
10061
|
+
const raw = fs33.readFileSync(path31.join(dir, file), "utf-8");
|
|
10134
10062
|
const { staff, body } = readProfileStaff(raw);
|
|
10135
10063
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
10136
10064
|
blocks.push(`## ${file}
|
|
@@ -10153,7 +10081,7 @@ function composeAuthBlock(authProfile, login, password) {
|
|
|
10153
10081
|
}
|
|
10154
10082
|
return "Auth: no QA credentials configured (set the `LOGIN_USER` variable and the `LOGIN_PASSWORD` vault secret). Browse public routes only; note auth-gated surfaces as gaps.";
|
|
10155
10083
|
}
|
|
10156
|
-
var CONTEXT_DIR_REL_PATH, QA_STAFF, ALL_STAFF, LEGACY_AUDIENCE_TO_STAFF,
|
|
10084
|
+
var CONTEXT_DIR_REL_PATH, QA_STAFF, ALL_STAFF, LEGACY_AUDIENCE_TO_STAFF, FRONTMATTER_RE, loadQaContext;
|
|
10157
10085
|
var init_loadQaContext = __esm({
|
|
10158
10086
|
"src/scripts/loadQaContext.ts"() {
|
|
10159
10087
|
"use strict";
|
|
@@ -10162,7 +10090,7 @@ var init_loadQaContext = __esm({
|
|
|
10162
10090
|
QA_STAFF = "qa-engineer";
|
|
10163
10091
|
ALL_STAFF = "*";
|
|
10164
10092
|
LEGACY_AUDIENCE_TO_STAFF = { chat: "kody", qa: QA_STAFF };
|
|
10165
|
-
|
|
10093
|
+
FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
10166
10094
|
loadQaContext = async (ctx) => {
|
|
10167
10095
|
const vars = readKodyVariables(ctx.cwd);
|
|
10168
10096
|
const login = vars.LOGIN_USER ?? "";
|
|
@@ -10177,7 +10105,7 @@ var init_loadQaContext = __esm({
|
|
|
10177
10105
|
|
|
10178
10106
|
// src/taskContext.ts
|
|
10179
10107
|
import * as fs34 from "fs";
|
|
10180
|
-
import * as
|
|
10108
|
+
import * as path32 from "path";
|
|
10181
10109
|
function buildTaskContext(args) {
|
|
10182
10110
|
return {
|
|
10183
10111
|
schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
|
|
@@ -10192,9 +10120,9 @@ function buildTaskContext(args) {
|
|
|
10192
10120
|
}
|
|
10193
10121
|
function persistTaskContext(cwd, ctx) {
|
|
10194
10122
|
try {
|
|
10195
|
-
const dir =
|
|
10123
|
+
const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
|
|
10196
10124
|
fs34.mkdirSync(dir, { recursive: true });
|
|
10197
|
-
const file =
|
|
10125
|
+
const file = path32.join(dir, "task-context.json");
|
|
10198
10126
|
fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
10199
10127
|
`);
|
|
10200
10128
|
return file;
|
|
@@ -10284,7 +10212,7 @@ var init_loadTaskState = __esm({
|
|
|
10284
10212
|
|
|
10285
10213
|
// src/scripts/loadWorkerAdhoc.ts
|
|
10286
10214
|
import * as fs35 from "fs";
|
|
10287
|
-
import * as
|
|
10215
|
+
import * as path33 from "path";
|
|
10288
10216
|
function resolveMessage(messageArg) {
|
|
10289
10217
|
const fromComment = readCommentBody();
|
|
10290
10218
|
if (fromComment) return stripDirective(fromComment);
|
|
@@ -10318,7 +10246,7 @@ function stripDirective(body) {
|
|
|
10318
10246
|
return lines.slice(start).join("\n").trim();
|
|
10319
10247
|
}
|
|
10320
10248
|
function parsePersona(raw, slug) {
|
|
10321
|
-
const stripped =
|
|
10249
|
+
const stripped = stripLeadingFrontmatter(raw);
|
|
10322
10250
|
const trimmed = stripped.trim();
|
|
10323
10251
|
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
10324
10252
|
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
@@ -10326,23 +10254,26 @@ function parsePersona(raw, slug) {
|
|
|
10326
10254
|
const rest = trimmed.slice(firstLine2.length).replace(/^\n+/, "");
|
|
10327
10255
|
return { title: h1[1].trim(), body: rest };
|
|
10328
10256
|
}
|
|
10329
|
-
return { title:
|
|
10257
|
+
return { title: humanizeSlug3(slug), body: trimmed };
|
|
10330
10258
|
}
|
|
10331
|
-
function
|
|
10259
|
+
function stripLeadingFrontmatter(raw) {
|
|
10260
|
+
const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/.exec(raw);
|
|
10261
|
+
return match ? raw.slice(match[0].length) : raw;
|
|
10262
|
+
}
|
|
10263
|
+
function humanizeSlug3(slug) {
|
|
10332
10264
|
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
10333
10265
|
}
|
|
10334
10266
|
var loadWorkerAdhoc;
|
|
10335
10267
|
var init_loadWorkerAdhoc = __esm({
|
|
10336
10268
|
"src/scripts/loadWorkerAdhoc.ts"() {
|
|
10337
10269
|
"use strict";
|
|
10338
|
-
init_jobFrontmatter();
|
|
10339
10270
|
loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
10340
10271
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
10341
10272
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
10342
10273
|
if (!workerSlug) {
|
|
10343
10274
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
10344
10275
|
}
|
|
10345
|
-
const workerPath =
|
|
10276
|
+
const workerPath = path33.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
10346
10277
|
if (!fs35.existsSync(workerPath)) {
|
|
10347
10278
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
10348
10279
|
}
|
|
@@ -12217,12 +12148,12 @@ fi
|
|
|
12217
12148
|
|
|
12218
12149
|
// src/scripts/runPreviewBuild.ts
|
|
12219
12150
|
import { copyFile, writeFile } from "fs/promises";
|
|
12220
|
-
import * as
|
|
12151
|
+
import * as path34 from "path";
|
|
12221
12152
|
import { fileURLToPath } from "url";
|
|
12222
12153
|
function bundledDockerfilePath(mode) {
|
|
12223
|
-
const here =
|
|
12154
|
+
const here = path34.dirname(fileURLToPath(import.meta.url));
|
|
12224
12155
|
const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
|
|
12225
|
-
return
|
|
12156
|
+
return path34.join(here, "preview-build-templates", file);
|
|
12226
12157
|
}
|
|
12227
12158
|
function required(name) {
|
|
12228
12159
|
const v = (process.env[name] ?? "").trim();
|
|
@@ -12481,10 +12412,10 @@ var init_runPreviewBuild = __esm({
|
|
|
12481
12412
|
console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
|
|
12482
12413
|
if (Object.keys(buildEnv).length > 0) {
|
|
12483
12414
|
const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
12484
|
-
await writeFile(
|
|
12415
|
+
await writeFile(path34.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
|
|
12485
12416
|
`, "utf8");
|
|
12486
12417
|
}
|
|
12487
|
-
const consumerDockerfile =
|
|
12418
|
+
const consumerDockerfile = path34.join(ctx.cwd, "Dockerfile.preview");
|
|
12488
12419
|
const { stat } = await import("fs/promises");
|
|
12489
12420
|
let hasConsumerDockerfile = false;
|
|
12490
12421
|
try {
|
|
@@ -12587,7 +12518,7 @@ var init_runPreviewBuild = __esm({
|
|
|
12587
12518
|
// src/scripts/runTickScript.ts
|
|
12588
12519
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
12589
12520
|
import * as fs36 from "fs";
|
|
12590
|
-
import * as
|
|
12521
|
+
import * as path35 from "path";
|
|
12591
12522
|
function buildChildEnv(parent, force) {
|
|
12592
12523
|
const allow = /* @__PURE__ */ new Set([
|
|
12593
12524
|
"PATH",
|
|
@@ -12635,7 +12566,7 @@ var runTickScript;
|
|
|
12635
12566
|
var init_runTickScript = __esm({
|
|
12636
12567
|
"src/scripts/runTickScript.ts"() {
|
|
12637
12568
|
"use strict";
|
|
12638
|
-
|
|
12569
|
+
init_dutyFolders();
|
|
12639
12570
|
init_jobState();
|
|
12640
12571
|
init_parseJobStateFromAgentResult();
|
|
12641
12572
|
runTickScript = async (ctx, _profile, args) => {
|
|
@@ -12649,21 +12580,19 @@ var init_runTickScript = __esm({
|
|
|
12649
12580
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
12650
12581
|
return;
|
|
12651
12582
|
}
|
|
12652
|
-
const
|
|
12653
|
-
if (!
|
|
12583
|
+
const duty = readDutyFolder(path35.join(ctx.cwd, jobsDir), slug);
|
|
12584
|
+
if (!duty) {
|
|
12654
12585
|
ctx.output.exitCode = 99;
|
|
12655
|
-
ctx.output.reason = `runTickScript:
|
|
12586
|
+
ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${path35.join(ctx.cwd, jobsDir, slug)}`;
|
|
12656
12587
|
return;
|
|
12657
12588
|
}
|
|
12658
|
-
const
|
|
12659
|
-
const { frontmatter } = splitFrontmatter(raw);
|
|
12660
|
-
const tickScript = frontmatter.tickScript;
|
|
12589
|
+
const tickScript = duty.config.tickScript;
|
|
12661
12590
|
if (!tickScript) {
|
|
12662
12591
|
ctx.output.exitCode = 99;
|
|
12663
|
-
ctx.output.reason = `runTickScript: duty ${slug} has no \`tickScript
|
|
12592
|
+
ctx.output.reason = `runTickScript: duty ${slug} has no \`tickScript\` in profile.json \u2014 route via duty-tick instead`;
|
|
12664
12593
|
return;
|
|
12665
12594
|
}
|
|
12666
|
-
const scriptPath =
|
|
12595
|
+
const scriptPath = path35.isAbsolute(tickScript) ? tickScript : path35.join(ctx.cwd, tickScript);
|
|
12667
12596
|
if (!fs36.existsSync(scriptPath)) {
|
|
12668
12597
|
ctx.output.exitCode = 99;
|
|
12669
12598
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
@@ -13862,7 +13791,7 @@ var init_scripts = __esm({
|
|
|
13862
13791
|
|
|
13863
13792
|
// src/staff.ts
|
|
13864
13793
|
import * as fs38 from "fs";
|
|
13865
|
-
import * as
|
|
13794
|
+
import * as path36 from "path";
|
|
13866
13795
|
function stripFrontmatter(raw) {
|
|
13867
13796
|
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
13868
13797
|
return (match ? match[1] : raw).trim();
|
|
@@ -13870,7 +13799,7 @@ function stripFrontmatter(raw) {
|
|
|
13870
13799
|
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
13871
13800
|
const trimmed = slug.trim();
|
|
13872
13801
|
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
13873
|
-
const staffPath =
|
|
13802
|
+
const staffPath = path36.join(cwd, staffDir, `${trimmed}.md`);
|
|
13874
13803
|
if (fs38.existsSync(staffPath)) {
|
|
13875
13804
|
const body = stripFrontmatter(fs38.readFileSync(staffPath, "utf-8"));
|
|
13876
13805
|
if (body) return body;
|
|
@@ -13986,7 +13915,7 @@ var init_tools = __esm({
|
|
|
13986
13915
|
// src/executor.ts
|
|
13987
13916
|
import { spawn as spawn7 } from "child_process";
|
|
13988
13917
|
import * as fs39 from "fs";
|
|
13989
|
-
import * as
|
|
13918
|
+
import * as path37 from "path";
|
|
13990
13919
|
function isMutatingPostflight(scriptName) {
|
|
13991
13920
|
return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
|
|
13992
13921
|
}
|
|
@@ -14137,13 +14066,13 @@ async function runExecutable(profileName, input) {
|
|
|
14137
14066
|
})
|
|
14138
14067
|
};
|
|
14139
14068
|
})() : null;
|
|
14140
|
-
const ndjsonDir =
|
|
14069
|
+
const ndjsonDir = path37.join(input.cwd, ".kody");
|
|
14141
14070
|
const personaSlug = typeof profile.staff === "string" && profile.staff.length > 0 ? profile.staff : typeof ctx.data.jobPersona === "string" && ctx.data.jobPersona.length > 0 ? ctx.data.jobPersona : null;
|
|
14142
14071
|
const staffPersona = personaSlug ? framePersona(personaSlug, loadStaffPersona(input.cwd, personaSlug)) : null;
|
|
14143
14072
|
const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
|
|
14144
14073
|
const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
|
|
14145
14074
|
const invokeAgent = async (prompt) => {
|
|
14146
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
14075
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path37.isAbsolute(p) ? p : path37.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
14147
14076
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
14148
14077
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
14149
14078
|
const agents = loadSubagents(profile);
|
|
@@ -14184,7 +14113,7 @@ async function runExecutable(profileName, input) {
|
|
|
14184
14113
|
enableVerifyTool: profile.claudeCode.enableVerifyTool,
|
|
14185
14114
|
enableSubmitTool: profile.claudeCode.enableSubmitTool,
|
|
14186
14115
|
// Locked-toolbox duty mode: `loadJobFromFile` flips `ctx.data.dutyTools`
|
|
14187
|
-
// when a duty declares `tools
|
|
14116
|
+
// when a duty declares `tools` in profile.json. The executor doesn't need
|
|
14188
14117
|
// to know the palette — it just forwards the flag so agent.ts can spin
|
|
14189
14118
|
// up the in-process `kody-duty` MCP server with the right context.
|
|
14190
14119
|
enableDutyTool: Array.isArray(ctx.data.dutyTools) && ctx.data.dutyTools.length > 0,
|
|
@@ -14469,13 +14398,13 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
14469
14398
|
function resolveProfilePath(profileName) {
|
|
14470
14399
|
const found = resolveExecutable(profileName);
|
|
14471
14400
|
if (found) return found;
|
|
14472
|
-
const here =
|
|
14401
|
+
const here = path37.dirname(new URL(import.meta.url).pathname);
|
|
14473
14402
|
const candidates = [
|
|
14474
|
-
|
|
14403
|
+
path37.join(here, "executables", profileName, "profile.json"),
|
|
14475
14404
|
// same-dir sibling (dev)
|
|
14476
|
-
|
|
14405
|
+
path37.join(here, "..", "executables", profileName, "profile.json"),
|
|
14477
14406
|
// up one (prod: dist/bin → dist/executables)
|
|
14478
|
-
|
|
14407
|
+
path37.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
14479
14408
|
// fallback
|
|
14480
14409
|
];
|
|
14481
14410
|
for (const c of candidates) {
|
|
@@ -14577,7 +14506,7 @@ function resolveShellTimeoutMs(entry) {
|
|
|
14577
14506
|
}
|
|
14578
14507
|
async function runShellEntry(entry, ctx, profile) {
|
|
14579
14508
|
const shellName = entry.shell;
|
|
14580
|
-
const shellPath =
|
|
14509
|
+
const shellPath = path37.join(profile.dir, shellName);
|
|
14581
14510
|
if (!fs39.existsSync(shellPath)) {
|
|
14582
14511
|
ctx.skipAgent = true;
|
|
14583
14512
|
ctx.output.exitCode = 99;
|
|
@@ -14834,18 +14763,18 @@ function translateOpenAISseToBrain(opts) {
|
|
|
14834
14763
|
// src/servers/brain-serve.ts
|
|
14835
14764
|
import * as fs42 from "fs";
|
|
14836
14765
|
import { createServer } from "http";
|
|
14837
|
-
import * as
|
|
14766
|
+
import * as path40 from "path";
|
|
14838
14767
|
|
|
14839
14768
|
// src/chat/loop.ts
|
|
14840
14769
|
init_agent();
|
|
14841
14770
|
init_registry();
|
|
14842
14771
|
init_task_artifacts();
|
|
14843
|
-
import * as
|
|
14844
|
-
import * as
|
|
14772
|
+
import * as fs12 from "fs";
|
|
14773
|
+
import * as path12 from "path";
|
|
14845
14774
|
|
|
14846
14775
|
// src/chat/attachments.ts
|
|
14847
|
-
import * as
|
|
14848
|
-
import * as
|
|
14776
|
+
import * as fs9 from "fs";
|
|
14777
|
+
import * as path9 from "path";
|
|
14849
14778
|
var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
|
|
14850
14779
|
var EXT_BY_MIME = {
|
|
14851
14780
|
"image/png": "png",
|
|
@@ -14861,7 +14790,7 @@ function extFor(mime) {
|
|
|
14861
14790
|
return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
|
|
14862
14791
|
}
|
|
14863
14792
|
function attachmentsDir(cwd, sessionId) {
|
|
14864
|
-
return
|
|
14793
|
+
return path9.join(cwd, ".kody", "tmp", "attachments", sessionId);
|
|
14865
14794
|
}
|
|
14866
14795
|
function prepareAttachments(turns, cwd, sessionId) {
|
|
14867
14796
|
const imagePaths = [];
|
|
@@ -14878,11 +14807,11 @@ function prepareAttachments(turns, cwd, sessionId) {
|
|
|
14878
14807
|
if (!isImage) return `[File: ${name}]`;
|
|
14879
14808
|
try {
|
|
14880
14809
|
if (!dirEnsured) {
|
|
14881
|
-
|
|
14810
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
14882
14811
|
dirEnsured = true;
|
|
14883
14812
|
}
|
|
14884
|
-
const filePath =
|
|
14885
|
-
|
|
14813
|
+
const filePath = path9.join(dir, `${imageCounter}.${extFor(mime)}`);
|
|
14814
|
+
fs9.writeFileSync(filePath, Buffer.from(data, "base64"));
|
|
14886
14815
|
imageCounter += 1;
|
|
14887
14816
|
imagePaths.push(filePath);
|
|
14888
14817
|
return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it.]`;
|
|
@@ -14898,10 +14827,10 @@ function prepareAttachments(turns, cwd, sessionId) {
|
|
|
14898
14827
|
}
|
|
14899
14828
|
|
|
14900
14829
|
// src/chat/events.ts
|
|
14901
|
-
import * as
|
|
14902
|
-
import * as
|
|
14830
|
+
import * as fs10 from "fs";
|
|
14831
|
+
import * as path10 from "path";
|
|
14903
14832
|
function eventsFilePath(cwd, sessionId) {
|
|
14904
|
-
return
|
|
14833
|
+
return path10.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
|
|
14905
14834
|
}
|
|
14906
14835
|
var FileSink = class {
|
|
14907
14836
|
constructor(file) {
|
|
@@ -14909,8 +14838,8 @@ var FileSink = class {
|
|
|
14909
14838
|
}
|
|
14910
14839
|
file;
|
|
14911
14840
|
async emit(event) {
|
|
14912
|
-
|
|
14913
|
-
|
|
14841
|
+
fs10.mkdirSync(path10.dirname(this.file), { recursive: true });
|
|
14842
|
+
fs10.appendFileSync(this.file, `${JSON.stringify(event)}
|
|
14914
14843
|
`);
|
|
14915
14844
|
}
|
|
14916
14845
|
};
|
|
@@ -14964,14 +14893,14 @@ function makeRunId(sessionId, suffix) {
|
|
|
14964
14893
|
}
|
|
14965
14894
|
|
|
14966
14895
|
// src/chat/session.ts
|
|
14967
|
-
import * as
|
|
14968
|
-
import * as
|
|
14896
|
+
import * as fs11 from "fs";
|
|
14897
|
+
import * as path11 from "path";
|
|
14969
14898
|
function sessionFilePath(cwd, sessionId) {
|
|
14970
|
-
return
|
|
14899
|
+
return path11.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
|
|
14971
14900
|
}
|
|
14972
14901
|
function readMeta(file) {
|
|
14973
|
-
if (!
|
|
14974
|
-
const raw =
|
|
14902
|
+
if (!fs11.existsSync(file)) return null;
|
|
14903
|
+
const raw = fs11.readFileSync(file, "utf-8");
|
|
14975
14904
|
const firstLine2 = raw.split("\n", 1)[0]?.trim();
|
|
14976
14905
|
if (!firstLine2) return null;
|
|
14977
14906
|
try {
|
|
@@ -14984,8 +14913,8 @@ function readMeta(file) {
|
|
|
14984
14913
|
}
|
|
14985
14914
|
}
|
|
14986
14915
|
function readSession(file) {
|
|
14987
|
-
if (!
|
|
14988
|
-
const raw =
|
|
14916
|
+
if (!fs11.existsSync(file)) return [];
|
|
14917
|
+
const raw = fs11.readFileSync(file, "utf-8").trim();
|
|
14989
14918
|
if (!raw) return [];
|
|
14990
14919
|
const turns = [];
|
|
14991
14920
|
for (const line of raw.split("\n")) {
|
|
@@ -15001,14 +14930,14 @@ function readSession(file) {
|
|
|
15001
14930
|
return turns;
|
|
15002
14931
|
}
|
|
15003
14932
|
function appendTurn(file, turn) {
|
|
15004
|
-
|
|
14933
|
+
fs11.mkdirSync(path11.dirname(file), { recursive: true });
|
|
15005
14934
|
const line = JSON.stringify({
|
|
15006
14935
|
role: turn.role,
|
|
15007
14936
|
content: turn.content,
|
|
15008
14937
|
timestamp: turn.timestamp,
|
|
15009
14938
|
toolCalls: turn.toolCalls ?? []
|
|
15010
14939
|
});
|
|
15011
|
-
|
|
14940
|
+
fs11.appendFileSync(file, `${line}
|
|
15012
14941
|
`);
|
|
15013
14942
|
}
|
|
15014
14943
|
function seedInitialMessage(file, message) {
|
|
@@ -15127,7 +15056,7 @@ function buildExecutableCatalog() {
|
|
|
15127
15056
|
const entries = [];
|
|
15128
15057
|
for (const { name, profilePath } of discovered) {
|
|
15129
15058
|
try {
|
|
15130
|
-
const raw = JSON.parse(
|
|
15059
|
+
const raw = JSON.parse(fs12.readFileSync(profilePath, "utf-8"));
|
|
15131
15060
|
const describe = typeof raw.describe === "string" ? raw.describe : "";
|
|
15132
15061
|
const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
|
|
15133
15062
|
entries.push({ name, describe: firstSentence.trim() });
|
|
@@ -15295,10 +15224,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
15295
15224
|
var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
|
|
15296
15225
|
var MAX_INDEX_BYTES = 8e3;
|
|
15297
15226
|
function readMemoryIndexBlock(cwd) {
|
|
15298
|
-
const indexPath =
|
|
15227
|
+
const indexPath = path12.join(cwd, MEMORY_INDEX_REL);
|
|
15299
15228
|
let raw;
|
|
15300
15229
|
try {
|
|
15301
|
-
raw =
|
|
15230
|
+
raw = fs12.readFileSync(indexPath, "utf-8");
|
|
15302
15231
|
} catch {
|
|
15303
15232
|
return "";
|
|
15304
15233
|
}
|
|
@@ -15316,17 +15245,17 @@ function readMemoryIndexBlock(cwd) {
|
|
|
15316
15245
|
var CONTEXT_DIR_REL = ".kody/context";
|
|
15317
15246
|
var MAX_CONTEXT_BYTES = 12e3;
|
|
15318
15247
|
function readContextBlock(cwd) {
|
|
15319
|
-
const dir =
|
|
15248
|
+
const dir = path12.join(cwd, CONTEXT_DIR_REL);
|
|
15320
15249
|
let files;
|
|
15321
15250
|
try {
|
|
15322
|
-
files =
|
|
15251
|
+
files = fs12.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
15323
15252
|
} catch {
|
|
15324
15253
|
return "";
|
|
15325
15254
|
}
|
|
15326
15255
|
const sections = [];
|
|
15327
15256
|
for (const file of files) {
|
|
15328
15257
|
try {
|
|
15329
|
-
const content =
|
|
15258
|
+
const content = fs12.readFileSync(path12.join(dir, file), "utf-8").trim();
|
|
15330
15259
|
if (content) sections.push(`### ${file.replace(/\.md$/, "")}
|
|
15331
15260
|
|
|
15332
15261
|
${content}`);
|
|
@@ -15349,10 +15278,10 @@ _\u2026 (context truncated; see \`.kody/context/\` for the full text)_` : joined
|
|
|
15349
15278
|
var INSTRUCTIONS_REL = ".kody/instructions.md";
|
|
15350
15279
|
var MAX_INSTRUCTIONS_BYTES = 8e3;
|
|
15351
15280
|
function readInstructionsBlock(cwd) {
|
|
15352
|
-
const instructionsPath =
|
|
15281
|
+
const instructionsPath = path12.join(cwd, INSTRUCTIONS_REL);
|
|
15353
15282
|
let raw;
|
|
15354
15283
|
try {
|
|
15355
|
-
raw =
|
|
15284
|
+
raw = fs12.readFileSync(instructionsPath, "utf-8");
|
|
15356
15285
|
} catch {
|
|
15357
15286
|
return "";
|
|
15358
15287
|
}
|
|
@@ -15376,7 +15305,7 @@ init_config();
|
|
|
15376
15305
|
// src/kody-cli.ts
|
|
15377
15306
|
import { execFileSync as execFileSync26 } from "child_process";
|
|
15378
15307
|
import * as fs40 from "fs";
|
|
15379
|
-
import * as
|
|
15308
|
+
import * as path38 from "path";
|
|
15380
15309
|
|
|
15381
15310
|
// src/app-auth.ts
|
|
15382
15311
|
import { createSign } from "crypto";
|
|
@@ -15453,7 +15382,7 @@ init_config();
|
|
|
15453
15382
|
|
|
15454
15383
|
// src/dispatch.ts
|
|
15455
15384
|
init_config();
|
|
15456
|
-
import * as
|
|
15385
|
+
import * as fs13 from "fs";
|
|
15457
15386
|
|
|
15458
15387
|
// src/cron-match.ts
|
|
15459
15388
|
var FIELD_BOUNDS = [
|
|
@@ -15571,10 +15500,10 @@ function autoDispatch(opts) {
|
|
|
15571
15500
|
}
|
|
15572
15501
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15573
15502
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
15574
|
-
if (!eventName || !eventPath || !
|
|
15503
|
+
if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
|
|
15575
15504
|
let event = {};
|
|
15576
15505
|
try {
|
|
15577
|
-
event = JSON.parse(
|
|
15506
|
+
event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
|
|
15578
15507
|
} catch {
|
|
15579
15508
|
return null;
|
|
15580
15509
|
}
|
|
@@ -15685,7 +15614,7 @@ function autoDispatchTyped(opts) {
|
|
|
15685
15614
|
if (legacy) return { kind: "route", ...legacy };
|
|
15686
15615
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15687
15616
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
15688
|
-
if (!eventName || !eventPath || !
|
|
15617
|
+
if (!eventName || !eventPath || !fs13.existsSync(eventPath)) {
|
|
15689
15618
|
return { kind: "silent", reason: "no GHA event context" };
|
|
15690
15619
|
}
|
|
15691
15620
|
if (eventName !== "issue_comment") {
|
|
@@ -15693,7 +15622,7 @@ function autoDispatchTyped(opts) {
|
|
|
15693
15622
|
}
|
|
15694
15623
|
let event = {};
|
|
15695
15624
|
try {
|
|
15696
|
-
event = JSON.parse(
|
|
15625
|
+
event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
|
|
15697
15626
|
} catch {
|
|
15698
15627
|
return { kind: "silent", reason: "GHA event payload unreadable" };
|
|
15699
15628
|
}
|
|
@@ -15737,7 +15666,7 @@ function dispatchScheduledWatches(opts) {
|
|
|
15737
15666
|
for (const exe of listExecutables()) {
|
|
15738
15667
|
let raw;
|
|
15739
15668
|
try {
|
|
15740
|
-
raw =
|
|
15669
|
+
raw = fs13.readFileSync(exe.profilePath, "utf-8");
|
|
15741
15670
|
} catch {
|
|
15742
15671
|
continue;
|
|
15743
15672
|
}
|
|
@@ -15980,9 +15909,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
15980
15909
|
return void 0;
|
|
15981
15910
|
}
|
|
15982
15911
|
function detectPackageManager2(cwd) {
|
|
15983
|
-
if (fs40.existsSync(
|
|
15984
|
-
if (fs40.existsSync(
|
|
15985
|
-
if (fs40.existsSync(
|
|
15912
|
+
if (fs40.existsSync(path38.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
15913
|
+
if (fs40.existsSync(path38.join(cwd, "yarn.lock"))) return "yarn";
|
|
15914
|
+
if (fs40.existsSync(path38.join(cwd, "bun.lockb"))) return "bun";
|
|
15986
15915
|
return "npm";
|
|
15987
15916
|
}
|
|
15988
15917
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -16069,7 +15998,7 @@ function configureGitIdentity(cwd) {
|
|
|
16069
15998
|
}
|
|
16070
15999
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
16071
16000
|
if (!issueNumber) return;
|
|
16072
|
-
const logPath =
|
|
16001
|
+
const logPath = path38.join(cwd, ".kody", "last-run.jsonl");
|
|
16073
16002
|
let tail = "";
|
|
16074
16003
|
try {
|
|
16075
16004
|
if (fs40.existsSync(logPath)) {
|
|
@@ -16098,7 +16027,7 @@ async function runCi(argv) {
|
|
|
16098
16027
|
return 0;
|
|
16099
16028
|
}
|
|
16100
16029
|
const args = parseCiArgs(argv);
|
|
16101
|
-
const cwd = args.cwd ?
|
|
16030
|
+
const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
|
|
16102
16031
|
let earlyConfig;
|
|
16103
16032
|
let earlyConfigError;
|
|
16104
16033
|
try {
|
|
@@ -16409,10 +16338,10 @@ init_repoWorkspace();
|
|
|
16409
16338
|
|
|
16410
16339
|
// src/scripts/brainTurnLog.ts
|
|
16411
16340
|
import * as fs41 from "fs";
|
|
16412
|
-
import * as
|
|
16341
|
+
import * as path39 from "path";
|
|
16413
16342
|
var live = /* @__PURE__ */ new Map();
|
|
16414
16343
|
function eventsPath(dir, chatId) {
|
|
16415
|
-
return
|
|
16344
|
+
return path39.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
16416
16345
|
}
|
|
16417
16346
|
function lastPersistedSeq(dir, chatId) {
|
|
16418
16347
|
const p = eventsPath(dir, chatId);
|
|
@@ -16455,7 +16384,7 @@ function beginTurn(dir, chatId) {
|
|
|
16455
16384
|
};
|
|
16456
16385
|
live.set(chatId, state);
|
|
16457
16386
|
const p = eventsPath(dir, chatId);
|
|
16458
|
-
fs41.mkdirSync(
|
|
16387
|
+
fs41.mkdirSync(path39.dirname(p), { recursive: true });
|
|
16459
16388
|
return (event) => {
|
|
16460
16389
|
state.seq += 1;
|
|
16461
16390
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
@@ -16730,7 +16659,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
16730
16659
|
const repo = strField(body, "repo");
|
|
16731
16660
|
const repoToken = strField(body, "repoToken");
|
|
16732
16661
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
16733
|
-
fs42.mkdirSync(
|
|
16662
|
+
fs42.mkdirSync(path40.dirname(sessionFile), { recursive: true });
|
|
16734
16663
|
appendTurn(sessionFile, {
|
|
16735
16664
|
role: "user",
|
|
16736
16665
|
content: message,
|
|
@@ -16777,7 +16706,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
16777
16706
|
function buildServer(opts) {
|
|
16778
16707
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
16779
16708
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
16780
|
-
const reposRoot = opts.reposRoot ??
|
|
16709
|
+
const reposRoot = opts.reposRoot ?? path40.join(path40.dirname(path40.resolve(opts.cwd)), "repos");
|
|
16781
16710
|
return createServer(async (req, res) => {
|
|
16782
16711
|
if (!req.method || !req.url) {
|
|
16783
16712
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -17371,13 +17300,13 @@ async function loadConfigSafe() {
|
|
|
17371
17300
|
// src/chat-cli.ts
|
|
17372
17301
|
import { execFileSync as execFileSync29 } from "child_process";
|
|
17373
17302
|
import * as fs44 from "fs";
|
|
17374
|
-
import * as
|
|
17303
|
+
import * as path42 from "path";
|
|
17375
17304
|
|
|
17376
17305
|
// src/chat/modes/interactive.ts
|
|
17377
17306
|
init_issue();
|
|
17378
17307
|
import { execFileSync as execFileSync28 } from "child_process";
|
|
17379
17308
|
import * as fs43 from "fs";
|
|
17380
|
-
import * as
|
|
17309
|
+
import * as path41 from "path";
|
|
17381
17310
|
|
|
17382
17311
|
// src/chat/inbox.ts
|
|
17383
17312
|
import { execFileSync as execFileSync27 } from "child_process";
|
|
@@ -17536,9 +17465,9 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
17536
17465
|
return -1;
|
|
17537
17466
|
}
|
|
17538
17467
|
function commitTurn(cwd, sessionId, _verbose) {
|
|
17539
|
-
const sessionRel =
|
|
17540
|
-
const eventsRel =
|
|
17541
|
-
const rels = [sessionRel, eventsRel].filter((p) => fs43.existsSync(
|
|
17468
|
+
const sessionRel = path41.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
17469
|
+
const eventsRel = path41.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
17470
|
+
const rels = [sessionRel, eventsRel].filter((p) => fs43.existsSync(path41.join(cwd, p)));
|
|
17542
17471
|
if (rels.length === 0) return;
|
|
17543
17472
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
17544
17473
|
if (!repository) {
|
|
@@ -17550,8 +17479,8 @@ function commitTurn(cwd, sessionId, _verbose) {
|
|
|
17550
17479
|
}
|
|
17551
17480
|
const branch = defaultBranch(cwd) ?? "main";
|
|
17552
17481
|
for (const rel of rels) {
|
|
17553
|
-
const repoPath = rel.split(
|
|
17554
|
-
const localText = fs43.readFileSync(
|
|
17482
|
+
const repoPath = rel.split(path41.sep).join("/");
|
|
17483
|
+
const localText = fs43.readFileSync(path41.join(cwd, rel), "utf-8");
|
|
17555
17484
|
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
17556
17485
|
}
|
|
17557
17486
|
}
|
|
@@ -17688,12 +17617,12 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
17688
17617
|
return result;
|
|
17689
17618
|
}
|
|
17690
17619
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
17691
|
-
const sessionFile =
|
|
17692
|
-
const eventsFile =
|
|
17620
|
+
const sessionFile = path42.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
17621
|
+
const eventsFile = path42.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
17693
17622
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
17694
|
-
const tasksDir =
|
|
17623
|
+
const tasksDir = path42.join(".kody", "tasks", safeSession);
|
|
17695
17624
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
17696
|
-
const paths = candidatePaths.filter((p) => fs44.existsSync(
|
|
17625
|
+
const paths = candidatePaths.filter((p) => fs44.existsSync(path42.join(cwd, p)));
|
|
17697
17626
|
if (paths.length === 0) return;
|
|
17698
17627
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
17699
17628
|
try {
|
|
@@ -17737,7 +17666,7 @@ async function runChat(argv) {
|
|
|
17737
17666
|
${CHAT_HELP}`);
|
|
17738
17667
|
return 64;
|
|
17739
17668
|
}
|
|
17740
|
-
const cwd = args.cwd ?
|
|
17669
|
+
const cwd = args.cwd ? path42.resolve(args.cwd) : process.cwd();
|
|
17741
17670
|
const sessionId = args.sessionId;
|
|
17742
17671
|
const unpackedSecrets = unpackAllSecrets();
|
|
17743
17672
|
if (unpackedSecrets > 0) {
|
|
@@ -17946,8 +17875,8 @@ var FlyClient = class {
|
|
|
17946
17875
|
get fetch() {
|
|
17947
17876
|
return this.opts.fetchImpl ?? fetch;
|
|
17948
17877
|
}
|
|
17949
|
-
async call(
|
|
17950
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
17878
|
+
async call(path43, init = {}) {
|
|
17879
|
+
const res = await this.fetch(`${FLY_API_BASE}${path43}`, {
|
|
17951
17880
|
method: init.method ?? "GET",
|
|
17952
17881
|
headers: {
|
|
17953
17882
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -17958,7 +17887,7 @@ var FlyClient = class {
|
|
|
17958
17887
|
if (res.status === 404 && init.allow404) return null;
|
|
17959
17888
|
if (!res.ok) {
|
|
17960
17889
|
const text = await res.text().catch(() => "");
|
|
17961
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
17890
|
+
throw new Error(`Fly API ${res.status} on ${path43}: ${text.slice(0, 200) || res.statusText}`);
|
|
17962
17891
|
}
|
|
17963
17892
|
if (res.status === 204) return null;
|
|
17964
17893
|
const raw = await res.text();
|