@kody-ade/kody-engine 0.4.219 → 0.4.221
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 -665
- 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.221",
|
|
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",
|
|
@@ -1283,7 +1283,7 @@ function dutyToolDefinitions(opts) {
|
|
|
1283
1283
|
);
|
|
1284
1284
|
const recommendTool = {
|
|
1285
1285
|
name: "recommend_to_operator",
|
|
1286
|
-
description: "Post ONE comment on a PR with the operator @-mention prepended. Use this when a
|
|
1286
|
+
description: "Post ONE comment on a PR with the operator @-mention prepended. Use this when a duty is in ASK mode and you want the operator to confirm via the dashboard inbox. The mention handle is substituted from kody.config.json `github.operators` \u2014 do not type it yourself.",
|
|
1287
1287
|
inputSchema: {
|
|
1288
1288
|
pr: z2.number().int().positive().describe("PR number to comment on."),
|
|
1289
1289
|
body: z2.string().min(1).describe("Comment body (markdown). Do not include the operator mention \u2014 the engine prepends it.")
|
|
@@ -1298,9 +1298,9 @@ function dutyToolDefinitions(opts) {
|
|
|
1298
1298
|
};
|
|
1299
1299
|
const ledgerTool = {
|
|
1300
1300
|
name: "read_ledger",
|
|
1301
|
-
description: "Read
|
|
1301
|
+
description: "Read any sentinel-fenced JSON manifest stored on a labeled issue. Returns `{found, issueNumber, payload}` where payload is the parsed JSON between `<!-- <label>:start -->` and `<!-- <label>:end -->` sentinels.",
|
|
1302
1302
|
inputSchema: {
|
|
1303
|
-
label: z2.string().min(1).describe("GitHub issue label that identifies the manifest issue
|
|
1303
|
+
label: z2.string().min(1).describe("GitHub issue label that identifies the manifest issue.")
|
|
1304
1304
|
},
|
|
1305
1305
|
handler: async (args) => {
|
|
1306
1306
|
const label = String(args.label ?? "");
|
|
@@ -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,125 +2028,197 @@ 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));
|
|
2168
2205
|
return out;
|
|
2169
2206
|
}
|
|
2170
2207
|
function getExecutableRoots() {
|
|
2171
|
-
return [
|
|
2172
|
-
}
|
|
2173
|
-
function builtinExecutableNames() {
|
|
2174
|
-
if (_builtinNames) return _builtinNames;
|
|
2175
|
-
const out = /* @__PURE__ */ new Set();
|
|
2176
|
-
const root = getExecutablesRoot();
|
|
2177
|
-
try {
|
|
2178
|
-
for (const ent of fs6.readdirSync(root, { withFileTypes: true })) {
|
|
2179
|
-
if (ent.isDirectory() && fs6.existsSync(path6.join(root, ent.name, "profile.json"))) out.add(ent.name);
|
|
2180
|
-
}
|
|
2181
|
-
} catch {
|
|
2182
|
-
}
|
|
2183
|
-
_builtinNames = out;
|
|
2184
|
-
return out;
|
|
2185
|
-
}
|
|
2186
|
-
function isBuiltinExecutable(name) {
|
|
2187
|
-
return builtinExecutableNames().has(name);
|
|
2208
|
+
return [getProjectExecutablesRoot(), getExecutablesRoot()];
|
|
2188
2209
|
}
|
|
2189
2210
|
function listExecutables(roots = getExecutableRoots()) {
|
|
2190
2211
|
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
2191
|
-
const dutiesRoot = getProjectDutiesRoot();
|
|
2192
2212
|
const seen = /* @__PURE__ */ new Set();
|
|
2193
2213
|
const out = [];
|
|
2194
2214
|
for (const root of rootList) {
|
|
2195
|
-
if (!
|
|
2196
|
-
const entries =
|
|
2215
|
+
if (!fs7.existsSync(root)) continue;
|
|
2216
|
+
const entries = fs7.readdirSync(root, { withFileTypes: true });
|
|
2197
2217
|
for (const ent of entries) {
|
|
2198
2218
|
if (!ent.isDirectory()) continue;
|
|
2199
2219
|
if (seen.has(ent.name)) continue;
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
|
|
2220
|
+
const profilePath = path7.join(root, ent.name, DUTY_PROFILE_FILE);
|
|
2221
|
+
if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
|
|
2203
2222
|
out.push({ name: ent.name, profilePath });
|
|
2204
2223
|
seen.add(ent.name);
|
|
2205
2224
|
}
|
|
@@ -2210,11 +2229,9 @@ function listExecutables(roots = getExecutableRoots()) {
|
|
|
2210
2229
|
function resolveExecutable(name, roots = getExecutableRoots()) {
|
|
2211
2230
|
if (!isSafeName(name)) return null;
|
|
2212
2231
|
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
2213
|
-
const dutiesRoot = getProjectDutiesRoot();
|
|
2214
2232
|
for (const root of rootList) {
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
|
|
2233
|
+
const profilePath = path7.join(root, name, "profile.json");
|
|
2234
|
+
if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
|
|
2218
2235
|
return profilePath;
|
|
2219
2236
|
}
|
|
2220
2237
|
}
|
|
@@ -2233,7 +2250,6 @@ function listDutyActions(projectDutiesRoot = getProjectDutiesRoot()) {
|
|
|
2233
2250
|
out.push(action);
|
|
2234
2251
|
};
|
|
2235
2252
|
for (const action of listProjectFolderDutyActions(projectDutiesRoot)) add(action);
|
|
2236
|
-
for (const action of listProjectMarkdownDutyActions(projectDutiesRoot)) add(action);
|
|
2237
2253
|
for (const action of listBuiltinDutyActions()) add(action);
|
|
2238
2254
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2239
2255
|
}
|
|
@@ -2248,108 +2264,54 @@ function isSafeName(name) {
|
|
|
2248
2264
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
2249
2265
|
}
|
|
2250
2266
|
function listProjectFolderDutyActions(root) {
|
|
2251
|
-
if (!
|
|
2267
|
+
if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
|
|
2252
2268
|
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
|
-
}
|
|
2269
|
+
for (const slug of listDutyFolderSlugs(root)) {
|
|
2270
|
+
if (!isSafeName(slug)) continue;
|
|
2271
|
+
const duty = readDutyFolder(root, slug);
|
|
2272
|
+
if (!duty) continue;
|
|
2273
|
+
const action = duty.config.action ?? slug;
|
|
2274
|
+
const executable = duty.config.executable ?? duty.config.executables?.[0] ?? (duty.config.tickScript ? "duty-tick-scripted" : "duty-tick");
|
|
2275
|
+
out.push({
|
|
2276
|
+
action,
|
|
2277
|
+
duty: slug,
|
|
2278
|
+
executable,
|
|
2279
|
+
cliArgs: duty.config.executable ? {} : { duty: slug },
|
|
2280
|
+
source: "project-folder",
|
|
2281
|
+
describe: duty.config.describe ?? duty.title,
|
|
2282
|
+
profilePath: duty.profilePath,
|
|
2283
|
+
bodyPath: duty.bodyPath
|
|
2284
|
+
});
|
|
2274
2285
|
}
|
|
2275
2286
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2276
2287
|
}
|
|
2277
|
-
function
|
|
2278
|
-
if (!
|
|
2288
|
+
function listBuiltinDutyActions(root = getBuiltinDutiesRoot()) {
|
|
2289
|
+
if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
|
|
2279
2290
|
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
|
-
}
|
|
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 ?? slug;
|
|
2297
|
+
out.push({
|
|
2298
|
+
action,
|
|
2299
|
+
duty: slug,
|
|
2300
|
+
executable,
|
|
2301
|
+
cliArgs: {},
|
|
2302
|
+
source: "builtin",
|
|
2303
|
+
describe: duty.config.describe ?? duty.title,
|
|
2304
|
+
profilePath: duty.profilePath,
|
|
2305
|
+
bodyPath: duty.bodyPath
|
|
2306
|
+
});
|
|
2301
2307
|
}
|
|
2302
2308
|
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2303
2309
|
}
|
|
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
2310
|
function getProfileInputs(name, roots = getExecutableRoots()) {
|
|
2349
2311
|
const profilePath = resolveExecutable(name, roots);
|
|
2350
2312
|
if (!profilePath) return null;
|
|
2351
2313
|
try {
|
|
2352
|
-
const raw = JSON.parse(
|
|
2314
|
+
const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
|
|
2353
2315
|
if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
|
|
2354
2316
|
return raw.inputs;
|
|
2355
2317
|
} catch {
|
|
@@ -2381,31 +2343,29 @@ function parseGenericFlags(argv) {
|
|
|
2381
2343
|
if (positional.length > 0) args._ = positional;
|
|
2382
2344
|
return args;
|
|
2383
2345
|
}
|
|
2384
|
-
var _builtinNames;
|
|
2385
2346
|
var init_registry = __esm({
|
|
2386
2347
|
"src/registry.ts"() {
|
|
2387
2348
|
"use strict";
|
|
2388
|
-
|
|
2389
|
-
_builtinNames = null;
|
|
2349
|
+
init_dutyFolders();
|
|
2390
2350
|
}
|
|
2391
2351
|
});
|
|
2392
2352
|
|
|
2393
2353
|
// src/task-artifacts.ts
|
|
2394
|
-
import
|
|
2395
|
-
import
|
|
2354
|
+
import fs8 from "fs";
|
|
2355
|
+
import path8 from "path";
|
|
2396
2356
|
function prepareTaskArtifactsDir(cwd, taskId) {
|
|
2397
2357
|
const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
2398
|
-
const relDir =
|
|
2399
|
-
const absDir =
|
|
2400
|
-
|
|
2358
|
+
const relDir = path8.join(".kody", "tasks", safeId);
|
|
2359
|
+
const absDir = path8.join(cwd, relDir);
|
|
2360
|
+
fs8.mkdirSync(absDir, { recursive: true });
|
|
2401
2361
|
return { taskId: safeId, absDir, relDir };
|
|
2402
2362
|
}
|
|
2403
2363
|
function verifyTaskArtifacts(absDir) {
|
|
2404
2364
|
const missing = [];
|
|
2405
2365
|
for (const name of TASK_ARTIFACT_FILES) {
|
|
2406
|
-
const full =
|
|
2366
|
+
const full = path8.join(absDir, name);
|
|
2407
2367
|
try {
|
|
2408
|
-
const stat =
|
|
2368
|
+
const stat = fs8.statSync(full);
|
|
2409
2369
|
if (!stat.isFile() || stat.size === 0) missing.push(name);
|
|
2410
2370
|
} catch {
|
|
2411
2371
|
missing.push(name);
|
|
@@ -2639,31 +2599,31 @@ var init_lifecycles = __esm({
|
|
|
2639
2599
|
});
|
|
2640
2600
|
|
|
2641
2601
|
// src/scripts/buildSyntheticPlugin.ts
|
|
2642
|
-
import * as
|
|
2602
|
+
import * as fs14 from "fs";
|
|
2643
2603
|
import * as os2 from "os";
|
|
2644
|
-
import * as
|
|
2604
|
+
import * as path13 from "path";
|
|
2645
2605
|
function getPluginsCatalogRoot() {
|
|
2646
|
-
const here =
|
|
2606
|
+
const here = path13.dirname(new URL(import.meta.url).pathname);
|
|
2647
2607
|
const candidates = [
|
|
2648
|
-
|
|
2608
|
+
path13.join(here, "..", "plugins"),
|
|
2649
2609
|
// dev: src/scripts → src/plugins
|
|
2650
|
-
|
|
2610
|
+
path13.join(here, "..", "..", "plugins"),
|
|
2651
2611
|
// built: dist/scripts → dist/plugins
|
|
2652
|
-
|
|
2612
|
+
path13.join(here, "..", "..", "src", "plugins")
|
|
2653
2613
|
// fallback
|
|
2654
2614
|
];
|
|
2655
2615
|
for (const c of candidates) {
|
|
2656
|
-
if (
|
|
2616
|
+
if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
|
|
2657
2617
|
}
|
|
2658
2618
|
return candidates[0];
|
|
2659
2619
|
}
|
|
2660
2620
|
function copyDir(src, dst) {
|
|
2661
|
-
|
|
2662
|
-
for (const ent of
|
|
2663
|
-
const s =
|
|
2664
|
-
const d =
|
|
2621
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
2622
|
+
for (const ent of fs14.readdirSync(src, { withFileTypes: true })) {
|
|
2623
|
+
const s = path13.join(src, ent.name);
|
|
2624
|
+
const d = path13.join(dst, ent.name);
|
|
2665
2625
|
if (ent.isDirectory()) copyDir(s, d);
|
|
2666
|
-
else if (ent.isFile())
|
|
2626
|
+
else if (ent.isFile()) fs14.copyFileSync(s, d);
|
|
2667
2627
|
}
|
|
2668
2628
|
}
|
|
2669
2629
|
var buildSyntheticPlugin;
|
|
@@ -2676,45 +2636,45 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2676
2636
|
if (!needsSynthetic) return;
|
|
2677
2637
|
const catalog = getPluginsCatalogRoot();
|
|
2678
2638
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2679
|
-
const root =
|
|
2680
|
-
|
|
2639
|
+
const root = path13.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
2640
|
+
fs14.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
|
|
2681
2641
|
const resolvePart = (bucket, entry) => {
|
|
2682
|
-
const local =
|
|
2683
|
-
if (
|
|
2684
|
-
const central =
|
|
2685
|
-
if (
|
|
2642
|
+
const local = path13.join(profile.dir, bucket, entry);
|
|
2643
|
+
if (fs14.existsSync(local)) return local;
|
|
2644
|
+
const central = path13.join(catalog, bucket, entry);
|
|
2645
|
+
if (fs14.existsSync(central)) return central;
|
|
2686
2646
|
throw new Error(
|
|
2687
2647
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
2688
2648
|
);
|
|
2689
2649
|
};
|
|
2690
2650
|
if (cc.skills.length > 0) {
|
|
2691
|
-
const dst =
|
|
2692
|
-
|
|
2651
|
+
const dst = path13.join(root, "skills");
|
|
2652
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
2693
2653
|
for (const name of cc.skills) {
|
|
2694
|
-
copyDir(resolvePart("skills", name),
|
|
2654
|
+
copyDir(resolvePart("skills", name), path13.join(dst, name));
|
|
2695
2655
|
}
|
|
2696
2656
|
}
|
|
2697
2657
|
if (cc.commands.length > 0) {
|
|
2698
|
-
const dst =
|
|
2699
|
-
|
|
2658
|
+
const dst = path13.join(root, "commands");
|
|
2659
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
2700
2660
|
for (const name of cc.commands) {
|
|
2701
|
-
|
|
2661
|
+
fs14.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
|
|
2702
2662
|
}
|
|
2703
2663
|
}
|
|
2704
2664
|
if (cc.hooks.length > 0) {
|
|
2705
|
-
const dst =
|
|
2706
|
-
|
|
2665
|
+
const dst = path13.join(root, "hooks");
|
|
2666
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
2707
2667
|
const merged = { hooks: {} };
|
|
2708
2668
|
for (const name of cc.hooks) {
|
|
2709
2669
|
const src = resolvePart("hooks", `${name}.json`);
|
|
2710
|
-
const parsed = JSON.parse(
|
|
2670
|
+
const parsed = JSON.parse(fs14.readFileSync(src, "utf-8"));
|
|
2711
2671
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
2712
2672
|
if (!Array.isArray(entries)) continue;
|
|
2713
2673
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
2714
2674
|
merged.hooks[event].push(...entries);
|
|
2715
2675
|
}
|
|
2716
2676
|
}
|
|
2717
|
-
|
|
2677
|
+
fs14.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
2718
2678
|
`);
|
|
2719
2679
|
}
|
|
2720
2680
|
const manifest = {
|
|
@@ -2724,7 +2684,7 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2724
2684
|
};
|
|
2725
2685
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
2726
2686
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
2727
|
-
|
|
2687
|
+
fs14.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
2728
2688
|
`);
|
|
2729
2689
|
ctx.data.syntheticPluginPath = root;
|
|
2730
2690
|
};
|
|
@@ -2732,9 +2692,9 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2732
2692
|
});
|
|
2733
2693
|
|
|
2734
2694
|
// src/subagents.ts
|
|
2735
|
-
import * as
|
|
2736
|
-
import * as
|
|
2737
|
-
function
|
|
2695
|
+
import * as fs15 from "fs";
|
|
2696
|
+
import * as path14 from "path";
|
|
2697
|
+
function splitFrontmatter(raw) {
|
|
2738
2698
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
2739
2699
|
if (!match) return { fm: {}, body: raw.trim() };
|
|
2740
2700
|
const fm = {};
|
|
@@ -2746,10 +2706,10 @@ function splitFrontmatter2(raw) {
|
|
|
2746
2706
|
return { fm, body: (match[2] ?? "").trim() };
|
|
2747
2707
|
}
|
|
2748
2708
|
function resolveAgentFile(profileDir, name) {
|
|
2749
|
-
const local =
|
|
2750
|
-
if (
|
|
2751
|
-
const central =
|
|
2752
|
-
if (
|
|
2709
|
+
const local = path14.join(profileDir, "agents", `${name}.md`);
|
|
2710
|
+
if (fs15.existsSync(local)) return local;
|
|
2711
|
+
const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
2712
|
+
if (fs15.existsSync(central)) return central;
|
|
2753
2713
|
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
2754
2714
|
}
|
|
2755
2715
|
function captureSubagentTemplates(profile) {
|
|
@@ -2758,7 +2718,7 @@ function captureSubagentTemplates(profile) {
|
|
|
2758
2718
|
const out = {};
|
|
2759
2719
|
for (const name of names) {
|
|
2760
2720
|
try {
|
|
2761
|
-
out[name] =
|
|
2721
|
+
out[name] = fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
2762
2722
|
} catch {
|
|
2763
2723
|
}
|
|
2764
2724
|
}
|
|
@@ -2769,8 +2729,8 @@ function loadSubagents(profile) {
|
|
|
2769
2729
|
if (!names || names.length === 0) return void 0;
|
|
2770
2730
|
const agents = {};
|
|
2771
2731
|
for (const name of names) {
|
|
2772
|
-
const raw = profile.subagentTemplates?.[name] ??
|
|
2773
|
-
const { fm, body } =
|
|
2732
|
+
const raw = profile.subagentTemplates?.[name] ?? fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
2733
|
+
const { fm, body } = splitFrontmatter(raw);
|
|
2774
2734
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
2775
2735
|
const def = {
|
|
2776
2736
|
description: fm.description ?? `Subagent ${name}`,
|
|
@@ -2793,15 +2753,15 @@ var init_subagents = __esm({
|
|
|
2793
2753
|
});
|
|
2794
2754
|
|
|
2795
2755
|
// src/profile.ts
|
|
2796
|
-
import * as
|
|
2797
|
-
import * as
|
|
2756
|
+
import * as fs16 from "fs";
|
|
2757
|
+
import * as path15 from "path";
|
|
2798
2758
|
function loadProfile(profilePath) {
|
|
2799
|
-
if (!
|
|
2759
|
+
if (!fs16.existsSync(profilePath)) {
|
|
2800
2760
|
throw new ProfileError(profilePath, "file not found");
|
|
2801
2761
|
}
|
|
2802
2762
|
let raw;
|
|
2803
2763
|
try {
|
|
2804
|
-
raw = JSON.parse(
|
|
2764
|
+
raw = JSON.parse(fs16.readFileSync(profilePath, "utf-8"));
|
|
2805
2765
|
} catch (err) {
|
|
2806
2766
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
2807
2767
|
}
|
|
@@ -2812,7 +2772,7 @@ function loadProfile(profilePath) {
|
|
|
2812
2772
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
2813
2773
|
if (unknownKeys.length > 0) {
|
|
2814
2774
|
process.stderr.write(
|
|
2815
|
-
`[kody profile] ${
|
|
2775
|
+
`[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
2816
2776
|
`
|
|
2817
2777
|
);
|
|
2818
2778
|
}
|
|
@@ -2831,7 +2791,7 @@ function loadProfile(profilePath) {
|
|
|
2831
2791
|
describe: typeof r.describe === "string" ? r.describe : base.describe,
|
|
2832
2792
|
staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : base.staff,
|
|
2833
2793
|
every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
|
|
2834
|
-
dutyTools:
|
|
2794
|
+
dutyTools: parseStringArray(r.dutyTools ?? r.tools) ?? base.dutyTools,
|
|
2835
2795
|
mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : base.mentions
|
|
2836
2796
|
};
|
|
2837
2797
|
}
|
|
@@ -2877,8 +2837,8 @@ function loadProfile(profilePath) {
|
|
|
2877
2837
|
staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
|
|
2878
2838
|
// Optional recurrence cadence (scheduled duty). Blank → undefined (on-demand).
|
|
2879
2839
|
every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
|
|
2880
|
-
// Locked-toolbox palette + mentions
|
|
2881
|
-
dutyTools:
|
|
2840
|
+
// Locked-toolbox palette + mentions from folder-duty profile metadata.
|
|
2841
|
+
dutyTools: parseStringArray(r.dutyTools ?? r.tools),
|
|
2882
2842
|
mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : void 0,
|
|
2883
2843
|
role,
|
|
2884
2844
|
kind,
|
|
@@ -2901,8 +2861,8 @@ function loadProfile(profilePath) {
|
|
|
2901
2861
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
2902
2862
|
// flip to true after end-to-end verification.
|
|
2903
2863
|
preloadContext: r.preloadContext === true,
|
|
2904
|
-
dir:
|
|
2905
|
-
promptTemplates: readPromptTemplates(
|
|
2864
|
+
dir: path15.dirname(profilePath),
|
|
2865
|
+
promptTemplates: readPromptTemplates(path15.dirname(profilePath))
|
|
2906
2866
|
};
|
|
2907
2867
|
if (lifecycle) {
|
|
2908
2868
|
applyLifecycle(profile, profilePath);
|
|
@@ -2934,15 +2894,16 @@ function readPromptTemplates(dir) {
|
|
|
2934
2894
|
const out = {};
|
|
2935
2895
|
const read = (p) => {
|
|
2936
2896
|
try {
|
|
2937
|
-
out[p] =
|
|
2897
|
+
out[p] = fs16.readFileSync(p, "utf-8");
|
|
2938
2898
|
} catch {
|
|
2939
2899
|
}
|
|
2940
2900
|
};
|
|
2941
|
-
read(
|
|
2901
|
+
read(path15.join(dir, "prompt.md"));
|
|
2902
|
+
read(path15.join(dir, "duty.md"));
|
|
2942
2903
|
try {
|
|
2943
|
-
const promptsDir =
|
|
2944
|
-
for (const ent of
|
|
2945
|
-
if (ent.endsWith(".md")) read(
|
|
2904
|
+
const promptsDir = path15.join(dir, "prompts");
|
|
2905
|
+
for (const ent of fs16.readdirSync(promptsDir)) {
|
|
2906
|
+
if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
|
|
2946
2907
|
}
|
|
2947
2908
|
} catch {
|
|
2948
2909
|
}
|
|
@@ -2962,6 +2923,11 @@ function requireString(p, r, key) {
|
|
|
2962
2923
|
}
|
|
2963
2924
|
return v;
|
|
2964
2925
|
}
|
|
2926
|
+
function parseStringArray(raw) {
|
|
2927
|
+
if (!Array.isArray(raw)) return void 0;
|
|
2928
|
+
const values = raw.map((t) => String(t).trim()).filter(Boolean);
|
|
2929
|
+
return values.length > 0 ? values : void 0;
|
|
2930
|
+
}
|
|
2965
2931
|
function parseInputs(p, raw) {
|
|
2966
2932
|
if (!Array.isArray(raw)) throw new ProfileError(p, `"inputs" must be an array`);
|
|
2967
2933
|
const out = [];
|
|
@@ -3213,7 +3179,11 @@ var init_profile = __esm({
|
|
|
3213
3179
|
"staff",
|
|
3214
3180
|
"every",
|
|
3215
3181
|
"dutyTools",
|
|
3182
|
+
"tools",
|
|
3216
3183
|
"mentions",
|
|
3184
|
+
"stage",
|
|
3185
|
+
"readsFrom",
|
|
3186
|
+
"writesTo",
|
|
3217
3187
|
"describe",
|
|
3218
3188
|
"role",
|
|
3219
3189
|
"kind",
|
|
@@ -3610,16 +3580,16 @@ var init_state = __esm({
|
|
|
3610
3580
|
});
|
|
3611
3581
|
|
|
3612
3582
|
// src/prompt.ts
|
|
3613
|
-
import * as
|
|
3614
|
-
import * as
|
|
3583
|
+
import * as fs17 from "fs";
|
|
3584
|
+
import * as path16 from "path";
|
|
3615
3585
|
function loadProjectConventions(projectDir) {
|
|
3616
3586
|
const out = [];
|
|
3617
3587
|
for (const rel of CONVENTION_FILES) {
|
|
3618
|
-
const abs =
|
|
3619
|
-
if (!
|
|
3588
|
+
const abs = path16.join(projectDir, rel);
|
|
3589
|
+
if (!fs17.existsSync(abs)) continue;
|
|
3620
3590
|
let content;
|
|
3621
3591
|
try {
|
|
3622
|
-
content =
|
|
3592
|
+
content = fs17.readFileSync(abs, "utf-8");
|
|
3623
3593
|
} catch {
|
|
3624
3594
|
continue;
|
|
3625
3595
|
}
|
|
@@ -3854,28 +3824,28 @@ var loadMemoryContext_exports = {};
|
|
|
3854
3824
|
__export(loadMemoryContext_exports, {
|
|
3855
3825
|
loadMemoryContext: () => loadMemoryContext
|
|
3856
3826
|
});
|
|
3857
|
-
import * as
|
|
3858
|
-
import * as
|
|
3827
|
+
import * as fs18 from "fs";
|
|
3828
|
+
import * as path17 from "path";
|
|
3859
3829
|
function collectPages(memoryAbs) {
|
|
3860
3830
|
const out = [];
|
|
3861
3831
|
walkMd(memoryAbs, (file) => {
|
|
3862
3832
|
let stat;
|
|
3863
3833
|
try {
|
|
3864
|
-
stat =
|
|
3834
|
+
stat = fs18.statSync(file);
|
|
3865
3835
|
} catch {
|
|
3866
3836
|
return;
|
|
3867
3837
|
}
|
|
3868
3838
|
let raw;
|
|
3869
3839
|
try {
|
|
3870
|
-
raw =
|
|
3840
|
+
raw = fs18.readFileSync(file, "utf-8");
|
|
3871
3841
|
} catch {
|
|
3872
3842
|
return;
|
|
3873
3843
|
}
|
|
3874
3844
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
3875
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
3845
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path17.basename(file, ".md");
|
|
3876
3846
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
3877
3847
|
out.push({
|
|
3878
|
-
relPath:
|
|
3848
|
+
relPath: path17.relative(memoryAbs, file),
|
|
3879
3849
|
title,
|
|
3880
3850
|
updated,
|
|
3881
3851
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
|
|
@@ -3943,16 +3913,16 @@ function walkMd(root, visit) {
|
|
|
3943
3913
|
const dir = stack.pop();
|
|
3944
3914
|
let names;
|
|
3945
3915
|
try {
|
|
3946
|
-
names =
|
|
3916
|
+
names = fs18.readdirSync(dir);
|
|
3947
3917
|
} catch {
|
|
3948
3918
|
continue;
|
|
3949
3919
|
}
|
|
3950
3920
|
for (const name of names) {
|
|
3951
3921
|
if (name.startsWith(".")) continue;
|
|
3952
|
-
const full =
|
|
3922
|
+
const full = path17.join(dir, name);
|
|
3953
3923
|
let stat;
|
|
3954
3924
|
try {
|
|
3955
|
-
stat =
|
|
3925
|
+
stat = fs18.statSync(full);
|
|
3956
3926
|
} catch {
|
|
3957
3927
|
continue;
|
|
3958
3928
|
}
|
|
@@ -3975,8 +3945,8 @@ var init_loadMemoryContext = __esm({
|
|
|
3975
3945
|
TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
|
|
3976
3946
|
loadMemoryContext = async (ctx) => {
|
|
3977
3947
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
3978
|
-
const memoryAbs =
|
|
3979
|
-
if (!
|
|
3948
|
+
const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
3949
|
+
if (!fs18.existsSync(memoryAbs)) {
|
|
3980
3950
|
ctx.data.memoryContext = "";
|
|
3981
3951
|
return;
|
|
3982
3952
|
}
|
|
@@ -4020,11 +3990,11 @@ var init_loadCoverageRules = __esm({
|
|
|
4020
3990
|
|
|
4021
3991
|
// src/container.ts
|
|
4022
3992
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
4023
|
-
import * as
|
|
3993
|
+
import * as fs19 from "fs";
|
|
4024
3994
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
4025
3995
|
try {
|
|
4026
3996
|
const profilePath = resolveProfilePath(profileName);
|
|
4027
|
-
if (!
|
|
3997
|
+
if (!fs19.existsSync(profilePath)) return null;
|
|
4028
3998
|
return loadProfile(profilePath).inputs;
|
|
4029
3999
|
} catch {
|
|
4030
4000
|
return null;
|
|
@@ -4486,9 +4456,9 @@ var init_lifecycleLabels = __esm({
|
|
|
4486
4456
|
|
|
4487
4457
|
// src/litellm.ts
|
|
4488
4458
|
import { execFileSync as execFileSync4, spawn as spawn3 } from "child_process";
|
|
4489
|
-
import * as
|
|
4459
|
+
import * as fs20 from "fs";
|
|
4490
4460
|
import * as os3 from "os";
|
|
4491
|
-
import * as
|
|
4461
|
+
import * as path18 from "path";
|
|
4492
4462
|
async function checkLitellmHealth(url) {
|
|
4493
4463
|
try {
|
|
4494
4464
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -4573,13 +4543,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4573
4543
|
let child;
|
|
4574
4544
|
let logPath;
|
|
4575
4545
|
const spawnProxy = () => {
|
|
4576
|
-
const configPath =
|
|
4577
|
-
|
|
4546
|
+
const configPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
|
|
4547
|
+
fs20.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
4578
4548
|
const args = ["--config", configPath, "--port", port];
|
|
4579
|
-
const nextLogPath =
|
|
4580
|
-
const outFd =
|
|
4549
|
+
const nextLogPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
|
|
4550
|
+
const outFd = fs20.openSync(nextLogPath, "w");
|
|
4581
4551
|
child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
4582
|
-
|
|
4552
|
+
fs20.closeSync(outFd);
|
|
4583
4553
|
logPath = nextLogPath;
|
|
4584
4554
|
};
|
|
4585
4555
|
const waitForHealth = async () => {
|
|
@@ -4593,7 +4563,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4593
4563
|
const readLogTail = () => {
|
|
4594
4564
|
if (!logPath) return "";
|
|
4595
4565
|
try {
|
|
4596
|
-
return
|
|
4566
|
+
return fs20.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
4597
4567
|
} catch {
|
|
4598
4568
|
return "";
|
|
4599
4569
|
}
|
|
@@ -4645,10 +4615,10 @@ ${tail}`
|
|
|
4645
4615
|
return { url, kill: killChild, isHealthy, ensureHealthy };
|
|
4646
4616
|
}
|
|
4647
4617
|
function readDotenvApiKeys(projectDir) {
|
|
4648
|
-
const dotenvPath =
|
|
4649
|
-
if (!
|
|
4618
|
+
const dotenvPath = path18.join(projectDir, ".env");
|
|
4619
|
+
if (!fs20.existsSync(dotenvPath)) return {};
|
|
4650
4620
|
const result = {};
|
|
4651
|
-
for (const rawLine of
|
|
4621
|
+
for (const rawLine of fs20.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
4652
4622
|
const line = rawLine.trim();
|
|
4653
4623
|
if (!line || line.startsWith("#")) continue;
|
|
4654
4624
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -4763,8 +4733,8 @@ var init_pushWithRetry = __esm({
|
|
|
4763
4733
|
|
|
4764
4734
|
// src/commit.ts
|
|
4765
4735
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
4766
|
-
import * as
|
|
4767
|
-
import * as
|
|
4736
|
+
import * as fs21 from "fs";
|
|
4737
|
+
import * as path19 from "path";
|
|
4768
4738
|
function git(args, cwd) {
|
|
4769
4739
|
try {
|
|
4770
4740
|
return execFileSync6("git", args, {
|
|
@@ -4802,18 +4772,18 @@ function ensureGitIdentity(cwd) {
|
|
|
4802
4772
|
}
|
|
4803
4773
|
function abortUnfinishedGitOps(cwd) {
|
|
4804
4774
|
const aborted = [];
|
|
4805
|
-
const gitDir =
|
|
4806
|
-
if (!
|
|
4807
|
-
if (
|
|
4775
|
+
const gitDir = path19.join(cwd ?? process.cwd(), ".git");
|
|
4776
|
+
if (!fs21.existsSync(gitDir)) return aborted;
|
|
4777
|
+
if (fs21.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
|
|
4808
4778
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
4809
4779
|
}
|
|
4810
|
-
if (
|
|
4780
|
+
if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
4811
4781
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
4812
4782
|
}
|
|
4813
|
-
if (
|
|
4783
|
+
if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
|
|
4814
4784
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
4815
4785
|
}
|
|
4816
|
-
if (
|
|
4786
|
+
if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
|
|
4817
4787
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
4818
4788
|
}
|
|
4819
4789
|
try {
|
|
@@ -4869,7 +4839,7 @@ function normalizeCommitMessage(raw) {
|
|
|
4869
4839
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
4870
4840
|
const allChanged = listChangedFiles(cwd);
|
|
4871
4841
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
4872
|
-
const mergeHeadExists =
|
|
4842
|
+
const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
4873
4843
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
4874
4844
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
4875
4845
|
}
|
|
@@ -5106,7 +5076,7 @@ var init_advanceFlow = __esm({
|
|
|
5106
5076
|
|
|
5107
5077
|
// src/gha.ts
|
|
5108
5078
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
5109
|
-
import * as
|
|
5079
|
+
import * as fs22 from "fs";
|
|
5110
5080
|
function getRunUrl() {
|
|
5111
5081
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5112
5082
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5117,10 +5087,10 @@ function getRunUrl() {
|
|
|
5117
5087
|
function reactToTriggerComment(cwd) {
|
|
5118
5088
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5119
5089
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5120
|
-
if (!eventPath || !
|
|
5090
|
+
if (!eventPath || !fs22.existsSync(eventPath)) return;
|
|
5121
5091
|
let event = null;
|
|
5122
5092
|
try {
|
|
5123
|
-
event = JSON.parse(
|
|
5093
|
+
event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
|
|
5124
5094
|
} catch {
|
|
5125
5095
|
return;
|
|
5126
5096
|
}
|
|
@@ -5454,11 +5424,11 @@ var init_classifyByLabel = __esm({
|
|
|
5454
5424
|
});
|
|
5455
5425
|
|
|
5456
5426
|
// src/scripts/commitAndPush.ts
|
|
5457
|
-
import * as
|
|
5458
|
-
import * as
|
|
5427
|
+
import * as fs23 from "fs";
|
|
5428
|
+
import * as path20 from "path";
|
|
5459
5429
|
function sentinelPathForStage(cwd, profileName) {
|
|
5460
5430
|
const runId = resolveRunId();
|
|
5461
|
-
return
|
|
5431
|
+
return path20.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
5462
5432
|
}
|
|
5463
5433
|
var DEFAULT_COMMIT_MESSAGE, commitAndPush2;
|
|
5464
5434
|
var init_commitAndPush = __esm({
|
|
@@ -5475,9 +5445,9 @@ var init_commitAndPush = __esm({
|
|
|
5475
5445
|
}
|
|
5476
5446
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
5477
5447
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
5478
|
-
if (sentinel &&
|
|
5448
|
+
if (sentinel && fs23.existsSync(sentinel)) {
|
|
5479
5449
|
try {
|
|
5480
|
-
const replay = JSON.parse(
|
|
5450
|
+
const replay = JSON.parse(fs23.readFileSync(sentinel, "utf-8"));
|
|
5481
5451
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
5482
5452
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
5483
5453
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -5530,8 +5500,8 @@ var init_commitAndPush = __esm({
|
|
|
5530
5500
|
const result = ctx.data.commitResult;
|
|
5531
5501
|
if (sentinel && result?.committed) {
|
|
5532
5502
|
try {
|
|
5533
|
-
|
|
5534
|
-
|
|
5503
|
+
fs23.mkdirSync(path20.dirname(sentinel), { recursive: true });
|
|
5504
|
+
fs23.writeFileSync(
|
|
5535
5505
|
sentinel,
|
|
5536
5506
|
JSON.stringify(
|
|
5537
5507
|
{
|
|
@@ -5553,8 +5523,8 @@ var init_commitAndPush = __esm({
|
|
|
5553
5523
|
});
|
|
5554
5524
|
|
|
5555
5525
|
// src/goal/state.ts
|
|
5556
|
-
import * as
|
|
5557
|
-
import * as
|
|
5526
|
+
import * as fs24 from "fs";
|
|
5527
|
+
import * as path21 from "path";
|
|
5558
5528
|
function parseGoalState(filePath, raw) {
|
|
5559
5529
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5560
5530
|
throw new GoalStateError(filePath, "must be a JSON object");
|
|
@@ -5606,10 +5576,10 @@ var init_state2 = __esm({
|
|
|
5606
5576
|
"use strict";
|
|
5607
5577
|
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
5608
5578
|
GoalStateError = class extends Error {
|
|
5609
|
-
constructor(
|
|
5610
|
-
super(`Invalid goal state at ${
|
|
5579
|
+
constructor(path43, message) {
|
|
5580
|
+
super(`Invalid goal state at ${path43}:
|
|
5611
5581
|
${message}`);
|
|
5612
|
-
this.path =
|
|
5582
|
+
this.path = path43;
|
|
5613
5583
|
this.name = "GoalStateError";
|
|
5614
5584
|
}
|
|
5615
5585
|
path;
|
|
@@ -5721,8 +5691,8 @@ var init_commitGoalState = __esm({
|
|
|
5721
5691
|
});
|
|
5722
5692
|
|
|
5723
5693
|
// src/scripts/composePrompt.ts
|
|
5724
|
-
import * as
|
|
5725
|
-
import * as
|
|
5694
|
+
import * as fs25 from "fs";
|
|
5695
|
+
import * as path22 from "path";
|
|
5726
5696
|
function fenceUntrusted(value) {
|
|
5727
5697
|
if (value.trim().length === 0) return value;
|
|
5728
5698
|
const safe = value.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
|
|
@@ -5811,6 +5781,10 @@ function formatDutyReference(data, profileName) {
|
|
|
5811
5781
|
if (dutySchedule) {
|
|
5812
5782
|
lines.push(`- Cadence: \`${dutySchedule}\``);
|
|
5813
5783
|
}
|
|
5784
|
+
const dutyBody = pickToken(data, "dutyIntent", "jobIntent");
|
|
5785
|
+
if (dutyBody) {
|
|
5786
|
+
lines.push("", "## Duty body", "", dutyBody);
|
|
5787
|
+
}
|
|
5814
5788
|
if (lines.length === 2) {
|
|
5815
5789
|
return "";
|
|
5816
5790
|
}
|
|
@@ -5839,9 +5813,10 @@ var init_composePrompt = __esm({
|
|
|
5839
5813
|
const explicit = ctx.data.promptTemplate;
|
|
5840
5814
|
const mode = ctx.args.mode;
|
|
5841
5815
|
const candidates = [
|
|
5842
|
-
explicit ?
|
|
5843
|
-
mode ?
|
|
5844
|
-
|
|
5816
|
+
explicit ? path22.join(profile.dir, explicit) : null,
|
|
5817
|
+
mode ? path22.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
5818
|
+
path22.join(profile.dir, "prompt.md"),
|
|
5819
|
+
path22.join(profile.dir, "duty.md")
|
|
5845
5820
|
].filter(Boolean);
|
|
5846
5821
|
let templatePath = "";
|
|
5847
5822
|
let template = "";
|
|
@@ -5854,7 +5829,7 @@ var init_composePrompt = __esm({
|
|
|
5854
5829
|
break;
|
|
5855
5830
|
}
|
|
5856
5831
|
try {
|
|
5857
|
-
template =
|
|
5832
|
+
template = fs25.readFileSync(c, "utf-8");
|
|
5858
5833
|
templatePath = c;
|
|
5859
5834
|
break;
|
|
5860
5835
|
} catch (err) {
|
|
@@ -5865,7 +5840,7 @@ var init_composePrompt = __esm({
|
|
|
5865
5840
|
if (!templatePath) {
|
|
5866
5841
|
let dirState;
|
|
5867
5842
|
try {
|
|
5868
|
-
dirState = `dir contents: [${
|
|
5843
|
+
dirState = `dir contents: [${fs25.readdirSync(profile.dir).join(", ")}]`;
|
|
5869
5844
|
} catch (err) {
|
|
5870
5845
|
dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
|
|
5871
5846
|
}
|
|
@@ -6692,19 +6667,19 @@ var init_deriveQaScopeFromIssue = __esm({
|
|
|
6692
6667
|
|
|
6693
6668
|
// src/scripts/diagMcp.ts
|
|
6694
6669
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
6695
|
-
import * as
|
|
6670
|
+
import * as fs26 from "fs";
|
|
6696
6671
|
import * as os4 from "os";
|
|
6697
|
-
import * as
|
|
6672
|
+
import * as path23 from "path";
|
|
6698
6673
|
var diagMcp;
|
|
6699
6674
|
var init_diagMcp = __esm({
|
|
6700
6675
|
"src/scripts/diagMcp.ts"() {
|
|
6701
6676
|
"use strict";
|
|
6702
6677
|
diagMcp = async (_ctx) => {
|
|
6703
6678
|
const home = os4.homedir();
|
|
6704
|
-
const cacheDir =
|
|
6679
|
+
const cacheDir = path23.join(home, ".cache", "ms-playwright");
|
|
6705
6680
|
let entries = [];
|
|
6706
6681
|
try {
|
|
6707
|
-
entries =
|
|
6682
|
+
entries = fs26.readdirSync(cacheDir);
|
|
6708
6683
|
} catch {
|
|
6709
6684
|
}
|
|
6710
6685
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -6732,13 +6707,13 @@ var init_diagMcp = __esm({
|
|
|
6732
6707
|
});
|
|
6733
6708
|
|
|
6734
6709
|
// src/scripts/frameworkDetectors.ts
|
|
6735
|
-
import * as
|
|
6736
|
-
import * as
|
|
6710
|
+
import * as fs27 from "fs";
|
|
6711
|
+
import * as path24 from "path";
|
|
6737
6712
|
function detectFrameworks(cwd) {
|
|
6738
6713
|
const out = [];
|
|
6739
6714
|
let deps = {};
|
|
6740
6715
|
try {
|
|
6741
|
-
const pkg = JSON.parse(
|
|
6716
|
+
const pkg = JSON.parse(fs27.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
|
|
6742
6717
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6743
6718
|
} catch {
|
|
6744
6719
|
return out;
|
|
@@ -6775,25 +6750,25 @@ function detectFrameworks(cwd) {
|
|
|
6775
6750
|
}
|
|
6776
6751
|
function findFile(cwd, candidates) {
|
|
6777
6752
|
for (const c of candidates) {
|
|
6778
|
-
if (
|
|
6753
|
+
if (fs27.existsSync(path24.join(cwd, c))) return c;
|
|
6779
6754
|
}
|
|
6780
6755
|
return null;
|
|
6781
6756
|
}
|
|
6782
6757
|
function discoverPayloadCollections(cwd) {
|
|
6783
6758
|
const out = [];
|
|
6784
6759
|
for (const dir of COLLECTION_DIRS) {
|
|
6785
|
-
const full =
|
|
6786
|
-
if (!
|
|
6760
|
+
const full = path24.join(cwd, dir);
|
|
6761
|
+
if (!fs27.existsSync(full)) continue;
|
|
6787
6762
|
let files;
|
|
6788
6763
|
try {
|
|
6789
|
-
files =
|
|
6764
|
+
files = fs27.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
6790
6765
|
} catch {
|
|
6791
6766
|
continue;
|
|
6792
6767
|
}
|
|
6793
6768
|
for (const file of files) {
|
|
6794
6769
|
try {
|
|
6795
|
-
const filePath =
|
|
6796
|
-
const content =
|
|
6770
|
+
const filePath = path24.join(full, file);
|
|
6771
|
+
const content = fs27.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
6797
6772
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
6798
6773
|
if (!slugMatch) continue;
|
|
6799
6774
|
const slug = slugMatch[1];
|
|
@@ -6807,7 +6782,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
6807
6782
|
out.push({
|
|
6808
6783
|
name,
|
|
6809
6784
|
slug,
|
|
6810
|
-
filePath:
|
|
6785
|
+
filePath: path24.relative(cwd, filePath),
|
|
6811
6786
|
fields: fields.slice(0, 20),
|
|
6812
6787
|
hasAdmin
|
|
6813
6788
|
});
|
|
@@ -6820,28 +6795,28 @@ function discoverPayloadCollections(cwd) {
|
|
|
6820
6795
|
function discoverAdminComponents(cwd, collections) {
|
|
6821
6796
|
const out = [];
|
|
6822
6797
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
6823
|
-
const full =
|
|
6824
|
-
if (!
|
|
6798
|
+
const full = path24.join(cwd, dir);
|
|
6799
|
+
if (!fs27.existsSync(full)) continue;
|
|
6825
6800
|
let entries;
|
|
6826
6801
|
try {
|
|
6827
|
-
entries =
|
|
6802
|
+
entries = fs27.readdirSync(full, { withFileTypes: true });
|
|
6828
6803
|
} catch {
|
|
6829
6804
|
continue;
|
|
6830
6805
|
}
|
|
6831
6806
|
for (const entry of entries) {
|
|
6832
|
-
const entryPath =
|
|
6807
|
+
const entryPath = path24.join(full, entry.name);
|
|
6833
6808
|
let name;
|
|
6834
6809
|
let filePath;
|
|
6835
6810
|
if (entry.isDirectory()) {
|
|
6836
6811
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
6837
|
-
(f) =>
|
|
6812
|
+
(f) => fs27.existsSync(path24.join(entryPath, f))
|
|
6838
6813
|
);
|
|
6839
6814
|
if (!indexFile) continue;
|
|
6840
6815
|
name = entry.name;
|
|
6841
|
-
filePath =
|
|
6816
|
+
filePath = path24.relative(cwd, path24.join(entryPath, indexFile));
|
|
6842
6817
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
6843
6818
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
6844
|
-
filePath =
|
|
6819
|
+
filePath = path24.relative(cwd, entryPath);
|
|
6845
6820
|
} else {
|
|
6846
6821
|
continue;
|
|
6847
6822
|
}
|
|
@@ -6849,7 +6824,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
6849
6824
|
if (collections) {
|
|
6850
6825
|
for (const col of collections) {
|
|
6851
6826
|
try {
|
|
6852
|
-
const colContent =
|
|
6827
|
+
const colContent = fs27.readFileSync(path24.join(cwd, col.filePath), "utf-8");
|
|
6853
6828
|
if (colContent.includes(name)) {
|
|
6854
6829
|
usedInCollection = col.slug;
|
|
6855
6830
|
break;
|
|
@@ -6867,8 +6842,8 @@ function scanApiRoutes(cwd) {
|
|
|
6867
6842
|
const out = [];
|
|
6868
6843
|
const appDirs = ["src/app", "app"];
|
|
6869
6844
|
for (const appDir of appDirs) {
|
|
6870
|
-
const apiDir =
|
|
6871
|
-
if (!
|
|
6845
|
+
const apiDir = path24.join(cwd, appDir, "api");
|
|
6846
|
+
if (!fs27.existsSync(apiDir)) continue;
|
|
6872
6847
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
6873
6848
|
break;
|
|
6874
6849
|
}
|
|
@@ -6877,14 +6852,14 @@ function scanApiRoutes(cwd) {
|
|
|
6877
6852
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
6878
6853
|
let entries;
|
|
6879
6854
|
try {
|
|
6880
|
-
entries =
|
|
6855
|
+
entries = fs27.readdirSync(dir, { withFileTypes: true });
|
|
6881
6856
|
} catch {
|
|
6882
6857
|
return;
|
|
6883
6858
|
}
|
|
6884
6859
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
6885
6860
|
if (routeFile) {
|
|
6886
6861
|
try {
|
|
6887
|
-
const content =
|
|
6862
|
+
const content = fs27.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
6888
6863
|
const methods = HTTP_METHODS.filter(
|
|
6889
6864
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
6890
6865
|
);
|
|
@@ -6892,7 +6867,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6892
6867
|
out.push({
|
|
6893
6868
|
path: prefix,
|
|
6894
6869
|
methods,
|
|
6895
|
-
filePath:
|
|
6870
|
+
filePath: path24.relative(cwd, path24.join(dir, routeFile.name))
|
|
6896
6871
|
});
|
|
6897
6872
|
}
|
|
6898
6873
|
} catch {
|
|
@@ -6903,7 +6878,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6903
6878
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
6904
6879
|
let segment = entry.name;
|
|
6905
6880
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
6906
|
-
walkApiRoutes(
|
|
6881
|
+
walkApiRoutes(path24.join(dir, entry.name), prefix, cwd, out);
|
|
6907
6882
|
continue;
|
|
6908
6883
|
}
|
|
6909
6884
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -6911,16 +6886,16 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
6911
6886
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
6912
6887
|
segment = `:${segment.slice(1, -1)}`;
|
|
6913
6888
|
}
|
|
6914
|
-
walkApiRoutes(
|
|
6889
|
+
walkApiRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
6915
6890
|
}
|
|
6916
6891
|
}
|
|
6917
6892
|
function scanEnvVars(cwd) {
|
|
6918
6893
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
6919
6894
|
for (const envFile of candidates) {
|
|
6920
|
-
const envPath =
|
|
6921
|
-
if (!
|
|
6895
|
+
const envPath = path24.join(cwd, envFile);
|
|
6896
|
+
if (!fs27.existsSync(envPath)) continue;
|
|
6922
6897
|
try {
|
|
6923
|
-
const content =
|
|
6898
|
+
const content = fs27.readFileSync(envPath, "utf-8");
|
|
6924
6899
|
const vars = [];
|
|
6925
6900
|
for (const line of content.split("\n")) {
|
|
6926
6901
|
const trimmed = line.trim();
|
|
@@ -6965,8 +6940,8 @@ var init_frameworkDetectors = __esm({
|
|
|
6965
6940
|
});
|
|
6966
6941
|
|
|
6967
6942
|
// src/scripts/discoverQaContext.ts
|
|
6968
|
-
import * as
|
|
6969
|
-
import * as
|
|
6943
|
+
import * as fs28 from "fs";
|
|
6944
|
+
import * as path25 from "path";
|
|
6970
6945
|
function runQaDiscovery(cwd) {
|
|
6971
6946
|
const out = {
|
|
6972
6947
|
routes: [],
|
|
@@ -6997,9 +6972,9 @@ function runQaDiscovery(cwd) {
|
|
|
6997
6972
|
}
|
|
6998
6973
|
function detectDevServer(cwd, out) {
|
|
6999
6974
|
try {
|
|
7000
|
-
const pkg = JSON.parse(
|
|
6975
|
+
const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
|
|
7001
6976
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7002
|
-
const pm =
|
|
6977
|
+
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
6978
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
7004
6979
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
7005
6980
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -7009,8 +6984,8 @@ function detectDevServer(cwd, out) {
|
|
|
7009
6984
|
function scanFrontendRoutes(cwd, out) {
|
|
7010
6985
|
const appDirs = ["src/app", "app"];
|
|
7011
6986
|
for (const appDir of appDirs) {
|
|
7012
|
-
const full =
|
|
7013
|
-
if (!
|
|
6987
|
+
const full = path25.join(cwd, appDir);
|
|
6988
|
+
if (!fs28.existsSync(full)) continue;
|
|
7014
6989
|
walkFrontendRoutes(full, "", out);
|
|
7015
6990
|
break;
|
|
7016
6991
|
}
|
|
@@ -7018,7 +6993,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
7018
6993
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
7019
6994
|
let entries;
|
|
7020
6995
|
try {
|
|
7021
|
-
entries =
|
|
6996
|
+
entries = fs28.readdirSync(dir, { withFileTypes: true });
|
|
7022
6997
|
} catch {
|
|
7023
6998
|
return;
|
|
7024
6999
|
}
|
|
@@ -7035,7 +7010,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7035
7010
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7036
7011
|
let segment = entry.name;
|
|
7037
7012
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7038
|
-
walkFrontendRoutes(
|
|
7013
|
+
walkFrontendRoutes(path25.join(dir, entry.name), prefix, out);
|
|
7039
7014
|
continue;
|
|
7040
7015
|
}
|
|
7041
7016
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7043,7 +7018,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7043
7018
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7044
7019
|
segment = `:${segment.slice(1, -1)}`;
|
|
7045
7020
|
}
|
|
7046
|
-
walkFrontendRoutes(
|
|
7021
|
+
walkFrontendRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
7047
7022
|
}
|
|
7048
7023
|
}
|
|
7049
7024
|
function detectAuthFiles(cwd, out) {
|
|
@@ -7060,23 +7035,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
7060
7035
|
"src/app/api/oauth"
|
|
7061
7036
|
];
|
|
7062
7037
|
for (const c of candidates) {
|
|
7063
|
-
if (
|
|
7038
|
+
if (fs28.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
|
|
7064
7039
|
}
|
|
7065
7040
|
}
|
|
7066
7041
|
function detectRoles(cwd, out) {
|
|
7067
7042
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
7068
7043
|
for (const rp of rolePaths) {
|
|
7069
|
-
const dir =
|
|
7070
|
-
if (!
|
|
7044
|
+
const dir = path25.join(cwd, rp);
|
|
7045
|
+
if (!fs28.existsSync(dir)) continue;
|
|
7071
7046
|
let files;
|
|
7072
7047
|
try {
|
|
7073
|
-
files =
|
|
7048
|
+
files = fs28.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7074
7049
|
} catch {
|
|
7075
7050
|
continue;
|
|
7076
7051
|
}
|
|
7077
7052
|
for (const f of files) {
|
|
7078
7053
|
try {
|
|
7079
|
-
const content =
|
|
7054
|
+
const content = fs28.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
|
|
7080
7055
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
7081
7056
|
if (roleMatches) {
|
|
7082
7057
|
for (const m of roleMatches) {
|
|
@@ -7369,6 +7344,23 @@ async function runJob(job, base) {
|
|
|
7369
7344
|
if (executableIdentity !== void 0 && executableIdentity.length > 0)
|
|
7370
7345
|
preloadedData.jobExecutable = executableIdentity;
|
|
7371
7346
|
if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
|
|
7347
|
+
const dutyContext = loadDutyContext(dutyIdentity ?? valid.duty);
|
|
7348
|
+
if (dutyContext) {
|
|
7349
|
+
preloadedData.dutySlug = dutyContext.slug;
|
|
7350
|
+
preloadedData.dutyTitle = dutyContext.title;
|
|
7351
|
+
preloadedData.dutyIntent = dutyContext.body;
|
|
7352
|
+
preloadedData.jobIntent = dutyContext.body;
|
|
7353
|
+
if (preloadedData.jobDuty === void 0) preloadedData.jobDuty = dutyContext.slug;
|
|
7354
|
+
if (dutyContext.config.staff && preloadedData.jobPersona === void 0) {
|
|
7355
|
+
preloadedData.jobPersona = dutyContext.config.staff;
|
|
7356
|
+
}
|
|
7357
|
+
if (dutyContext.config.every && preloadedData.jobSchedule === void 0) {
|
|
7358
|
+
preloadedData.jobSchedule = dutyContext.config.every;
|
|
7359
|
+
}
|
|
7360
|
+
if (dutyContext.config.mentions && dutyContext.config.mentions.length > 0) {
|
|
7361
|
+
preloadedData.mentions = dutyContext.config.mentions.map((login) => `@${login}`).join(" ");
|
|
7362
|
+
}
|
|
7363
|
+
}
|
|
7372
7364
|
if (valid.why !== void 0 && valid.why.length > 0) preloadedData.jobWhy = valid.why;
|
|
7373
7365
|
if (valid.persona !== void 0) preloadedData.jobPersona = valid.persona;
|
|
7374
7366
|
const input = {
|
|
@@ -7384,6 +7376,10 @@ async function runJob(job, base) {
|
|
|
7384
7376
|
const run = base.chain === false ? runExecutable : runExecutableChain;
|
|
7385
7377
|
return run(profileName, input);
|
|
7386
7378
|
}
|
|
7379
|
+
function loadDutyContext(slug) {
|
|
7380
|
+
if (!slug) return null;
|
|
7381
|
+
return readDutyFolder(getProjectDutiesRoot(), slug) ?? readDutyFolder(getBuiltinDutiesRoot(), slug);
|
|
7382
|
+
}
|
|
7387
7383
|
function mintInstantJob(dispatch2, opts) {
|
|
7388
7384
|
return {
|
|
7389
7385
|
action: dispatch2.action,
|
|
@@ -7411,6 +7407,7 @@ var init_job = __esm({
|
|
|
7411
7407
|
"src/job.ts"() {
|
|
7412
7408
|
"use strict";
|
|
7413
7409
|
init_executor();
|
|
7410
|
+
init_dutyFolders();
|
|
7414
7411
|
init_registry();
|
|
7415
7412
|
init_jobIdentity();
|
|
7416
7413
|
init_jobIdentity();
|
|
@@ -7642,8 +7639,8 @@ var init_contentsApiBackend = __esm({
|
|
|
7642
7639
|
});
|
|
7643
7640
|
|
|
7644
7641
|
// src/scripts/jobState/localFileBackend.ts
|
|
7645
|
-
import * as
|
|
7646
|
-
import * as
|
|
7642
|
+
import * as fs29 from "fs";
|
|
7643
|
+
import * as path26 from "path";
|
|
7647
7644
|
function sanitizeKey(s) {
|
|
7648
7645
|
return s.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
7649
7646
|
}
|
|
@@ -7699,7 +7696,7 @@ var init_localFileBackend = __esm({
|
|
|
7699
7696
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
7700
7697
|
this.cwd = opts.cwd;
|
|
7701
7698
|
this.jobsDir = opts.jobsDir;
|
|
7702
|
-
this.absDir =
|
|
7699
|
+
this.absDir = path26.join(opts.cwd, opts.jobsDir);
|
|
7703
7700
|
this.owner = opts.owner;
|
|
7704
7701
|
this.repo = opts.repo;
|
|
7705
7702
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -7714,7 +7711,7 @@ var init_localFileBackend = __esm({
|
|
|
7714
7711
|
`);
|
|
7715
7712
|
return;
|
|
7716
7713
|
}
|
|
7717
|
-
|
|
7714
|
+
fs29.mkdirSync(this.absDir, { recursive: true });
|
|
7718
7715
|
const prefix = this.cacheKeyPrefix();
|
|
7719
7716
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
7720
7717
|
try {
|
|
@@ -7743,7 +7740,7 @@ var init_localFileBackend = __esm({
|
|
|
7743
7740
|
`);
|
|
7744
7741
|
return;
|
|
7745
7742
|
}
|
|
7746
|
-
if (!
|
|
7743
|
+
if (!fs29.existsSync(this.absDir)) {
|
|
7747
7744
|
return;
|
|
7748
7745
|
}
|
|
7749
7746
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -7759,11 +7756,11 @@ var init_localFileBackend = __esm({
|
|
|
7759
7756
|
}
|
|
7760
7757
|
load(slug) {
|
|
7761
7758
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
7762
|
-
const absPath =
|
|
7763
|
-
if (!
|
|
7759
|
+
const absPath = path26.join(this.cwd, relPath);
|
|
7760
|
+
if (!fs29.existsSync(absPath)) {
|
|
7764
7761
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
7765
7762
|
}
|
|
7766
|
-
const raw =
|
|
7763
|
+
const raw = fs29.readFileSync(absPath, "utf-8");
|
|
7767
7764
|
let parsed;
|
|
7768
7765
|
try {
|
|
7769
7766
|
parsed = JSON.parse(raw);
|
|
@@ -7780,13 +7777,13 @@ var init_localFileBackend = __esm({
|
|
|
7780
7777
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
7781
7778
|
return false;
|
|
7782
7779
|
}
|
|
7783
|
-
const absPath =
|
|
7784
|
-
|
|
7780
|
+
const absPath = path26.join(this.cwd, loaded.path);
|
|
7781
|
+
fs29.mkdirSync(path26.dirname(absPath), { recursive: true });
|
|
7785
7782
|
const body = `${JSON.stringify(next, null, 2)}
|
|
7786
7783
|
`;
|
|
7787
7784
|
const tmpPath = `${absPath}.${process.pid}.tmp`;
|
|
7788
|
-
|
|
7789
|
-
|
|
7785
|
+
fs29.writeFileSync(tmpPath, body, "utf-8");
|
|
7786
|
+
fs29.renameSync(tmpPath, absPath);
|
|
7790
7787
|
return true;
|
|
7791
7788
|
}
|
|
7792
7789
|
cacheKeyPrefix() {
|
|
@@ -7938,8 +7935,7 @@ var init_planTaskJobs = __esm({
|
|
|
7938
7935
|
});
|
|
7939
7936
|
|
|
7940
7937
|
// src/scripts/dispatchDutyFileTicks.ts
|
|
7941
|
-
import * as
|
|
7942
|
-
import * as path26 from "path";
|
|
7938
|
+
import * as path27 from "path";
|
|
7943
7939
|
async function decideShouldFire(every, slug, backend, now) {
|
|
7944
7940
|
if (!every) return { skip: false, reason: "no schedule (every cron tick)" };
|
|
7945
7941
|
if (every === "manual") {
|
|
@@ -7980,14 +7976,6 @@ function formatAgo(ms) {
|
|
|
7980
7976
|
const day = Math.round(hr / 24);
|
|
7981
7977
|
return `${day}d`;
|
|
7982
7978
|
}
|
|
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
7979
|
function parseDutyFilter(raw) {
|
|
7992
7980
|
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
7993
7981
|
}
|
|
@@ -7996,21 +7984,21 @@ function filterSlugs(slugs, onlyDuty) {
|
|
|
7996
7984
|
}
|
|
7997
7985
|
function createDutyTaskIssue(opts) {
|
|
7998
7986
|
const title = `Duty ${opts.slug} - multi-executable task`;
|
|
7999
|
-
const body = buildDutyTaskIssueBody(opts.slug, opts.body, opts.
|
|
7987
|
+
const body = buildDutyTaskIssueBody(opts.slug, opts.body, opts.config);
|
|
8000
7988
|
const out = gh(["issue", "create", "--title", title, "--body-file", "-"], { input: body, cwd: opts.cwd });
|
|
8001
7989
|
const url = out.split("\n").map((line) => line.trim()).filter(Boolean).pop() ?? "";
|
|
8002
7990
|
const match = url.match(/\/issues\/(\d+)\b/);
|
|
8003
7991
|
if (!match) throw new Error(`gh issue create returned unexpected output: ${out}`);
|
|
8004
7992
|
return { number: Number(match[1]), url };
|
|
8005
7993
|
}
|
|
8006
|
-
function buildDutyTaskIssueBody(slug, dutyBody,
|
|
8007
|
-
const specs = (
|
|
7994
|
+
function buildDutyTaskIssueBody(slug, dutyBody, config) {
|
|
7995
|
+
const specs = (config.executables ?? []).map((executable) => ({
|
|
8008
7996
|
executable,
|
|
8009
7997
|
duty: slug,
|
|
8010
|
-
...
|
|
7998
|
+
...config.staff ? { staff: config.staff } : {},
|
|
8011
7999
|
reason: `Duty \`${slug}\` slice for \`${executable}\`.`,
|
|
8012
8000
|
flavor: "scheduled",
|
|
8013
|
-
...
|
|
8001
|
+
...config.every ? { schedule: config.every } : {}
|
|
8014
8002
|
}));
|
|
8015
8003
|
return [
|
|
8016
8004
|
`# Duty task: ${slug}`,
|
|
@@ -8023,26 +8011,6 @@ function buildDutyTaskIssueBody(slug, dutyBody, frontmatter) {
|
|
|
8023
8011
|
""
|
|
8024
8012
|
].join("\n");
|
|
8025
8013
|
}
|
|
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
8014
|
async function stampFired(backend, slug, now, task) {
|
|
8047
8015
|
try {
|
|
8048
8016
|
const loaded = await backend.load(slug);
|
|
@@ -8061,10 +8029,10 @@ var dispatchDutyFileTicks;
|
|
|
8061
8029
|
var init_dispatchDutyFileTicks = __esm({
|
|
8062
8030
|
"src/scripts/dispatchDutyFileTicks.ts"() {
|
|
8063
8031
|
"use strict";
|
|
8032
|
+
init_dutyFolders();
|
|
8064
8033
|
init_issue();
|
|
8065
8034
|
init_job();
|
|
8066
|
-
|
|
8067
|
-
init_jobFrontmatter();
|
|
8035
|
+
init_scheduleEvery();
|
|
8068
8036
|
init_jobState();
|
|
8069
8037
|
init_planTaskJobs();
|
|
8070
8038
|
dispatchDutyFileTicks = async (ctx, _profile, args) => {
|
|
@@ -8082,107 +8050,54 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8082
8050
|
}
|
|
8083
8051
|
try {
|
|
8084
8052
|
const onlyDuty = parseDutyFilter(ctx.args.duty);
|
|
8085
|
-
const jobsPath =
|
|
8086
|
-
const slugs = filterSlugs(
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
if (slugs.length === 0 && folderSlugList.length === 0) {
|
|
8053
|
+
const jobsPath = path27.join(ctx.cwd, jobsDir);
|
|
8054
|
+
const slugs = filterSlugs(listDutyFolderSlugs(jobsPath), onlyDuty);
|
|
8055
|
+
ctx.data.jobSlugCount = slugs.length;
|
|
8056
|
+
if (slugs.length === 0) {
|
|
8090
8057
|
const filter = onlyDuty ? ` matching ${onlyDuty}` : "";
|
|
8091
|
-
process.stdout.write(`[jobs] no
|
|
8058
|
+
process.stdout.write(`[jobs] no duty folders${filter} in ${jobsDir}
|
|
8092
8059
|
`);
|
|
8093
8060
|
return;
|
|
8094
8061
|
}
|
|
8095
8062
|
const filtered = onlyDuty ? ` matching ${onlyDuty}` : "";
|
|
8096
|
-
process.stdout.write(
|
|
8097
|
-
|
|
8098
|
-
`
|
|
8099
|
-
);
|
|
8063
|
+
process.stdout.write(`[jobs] ticking ${slugs.length} dut(y/ies)${filtered} via ${targetExecutable}
|
|
8064
|
+
`);
|
|
8100
8065
|
const results = [];
|
|
8101
8066
|
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
8067
|
for (const slug of slugs) {
|
|
8153
|
-
|
|
8154
|
-
|
|
8068
|
+
const duty = readDutyFolder(jobsPath, slug);
|
|
8069
|
+
if (!duty) {
|
|
8070
|
+
process.stderr.write(`[jobs] \u23ED skip ${slug}: duty folder is missing profile.json or duty.md
|
|
8155
8071
|
`);
|
|
8156
|
-
results.push({ slug, exitCode: 0, skipped: true, reason: "
|
|
8072
|
+
results.push({ slug, exitCode: 0, skipped: true, reason: "incomplete duty folder" });
|
|
8157
8073
|
continue;
|
|
8158
8074
|
}
|
|
8159
|
-
const
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
process.stdout.write(`[jobs] \u23ED skip ${slug}: disabled in frontmatter
|
|
8075
|
+
const config = duty.config;
|
|
8076
|
+
if (config.disabled === true) {
|
|
8077
|
+
process.stdout.write(`[jobs] \u23ED skip ${slug}: disabled in profile.json
|
|
8163
8078
|
`);
|
|
8164
8079
|
results.push({ slug, exitCode: 0, skipped: true, reason: "disabled" });
|
|
8165
8080
|
continue;
|
|
8166
8081
|
}
|
|
8167
|
-
if (!
|
|
8168
|
-
process.stderr.write(`[jobs] \u23ED skip ${slug}: no staff assigned (add
|
|
8082
|
+
if (!config.staff || config.staff.trim().length === 0) {
|
|
8083
|
+
process.stderr.write(`[jobs] \u23ED skip ${slug}: no staff assigned (add "staff" to profile.json)
|
|
8169
8084
|
`);
|
|
8170
8085
|
results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
|
|
8171
8086
|
continue;
|
|
8172
8087
|
}
|
|
8173
|
-
const decision = await decideShouldFire(
|
|
8088
|
+
const decision = await decideShouldFire(config.every, slug, backend, now);
|
|
8174
8089
|
if (decision.skip) {
|
|
8175
8090
|
process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
|
|
8176
8091
|
`);
|
|
8177
8092
|
results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
|
|
8178
8093
|
continue;
|
|
8179
8094
|
}
|
|
8180
|
-
if (
|
|
8095
|
+
if (config.executables && config.executables.length > 0) {
|
|
8181
8096
|
try {
|
|
8182
8097
|
const task = createDutyTaskIssue({
|
|
8183
8098
|
slug,
|
|
8184
|
-
body:
|
|
8185
|
-
|
|
8099
|
+
body: duty.body,
|
|
8100
|
+
config,
|
|
8186
8101
|
cwd: ctx.cwd
|
|
8187
8102
|
});
|
|
8188
8103
|
await stampFired(backend, slug, now, task);
|
|
@@ -8192,8 +8107,8 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8192
8107
|
mintScheduledJob({
|
|
8193
8108
|
duty: slug,
|
|
8194
8109
|
executable: "task-jobs",
|
|
8195
|
-
schedule:
|
|
8196
|
-
persona:
|
|
8110
|
+
schedule: config.every,
|
|
8111
|
+
persona: config.staff,
|
|
8197
8112
|
cliArgs: { issue: task.number }
|
|
8198
8113
|
}),
|
|
8199
8114
|
{ cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet }
|
|
@@ -8211,7 +8126,8 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8211
8126
|
}
|
|
8212
8127
|
continue;
|
|
8213
8128
|
}
|
|
8214
|
-
const slugTarget =
|
|
8129
|
+
const slugTarget = config.tickScript ? scriptedExecutable : config.executable ?? targetExecutable;
|
|
8130
|
+
const cliArgs = config.executable ? {} : { [slugArg]: slug };
|
|
8215
8131
|
process.stdout.write(`[jobs] \u2192 tick ${slug} (${slugTarget})
|
|
8216
8132
|
`);
|
|
8217
8133
|
try {
|
|
@@ -8219,8 +8135,9 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8219
8135
|
mintScheduledJob({
|
|
8220
8136
|
duty: slug,
|
|
8221
8137
|
executable: slugTarget,
|
|
8222
|
-
schedule:
|
|
8223
|
-
|
|
8138
|
+
schedule: config.every,
|
|
8139
|
+
persona: config.staff,
|
|
8140
|
+
cliArgs
|
|
8224
8141
|
}),
|
|
8225
8142
|
{ cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false }
|
|
8226
8143
|
);
|
|
@@ -9463,11 +9380,11 @@ var init_handleAbandonedGoal = __esm({
|
|
|
9463
9380
|
// src/scripts/initFlow.ts
|
|
9464
9381
|
import { execFileSync as execFileSync16 } from "child_process";
|
|
9465
9382
|
import * as fs30 from "fs";
|
|
9466
|
-
import * as
|
|
9383
|
+
import * as path28 from "path";
|
|
9467
9384
|
function detectPackageManager(cwd) {
|
|
9468
|
-
if (fs30.existsSync(
|
|
9469
|
-
if (fs30.existsSync(
|
|
9470
|
-
if (fs30.existsSync(
|
|
9385
|
+
if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9386
|
+
if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
|
|
9387
|
+
if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
|
|
9471
9388
|
return "npm";
|
|
9472
9389
|
}
|
|
9473
9390
|
function qualityCommandsFor(pm) {
|
|
@@ -9539,7 +9456,7 @@ function performInit(cwd, force) {
|
|
|
9539
9456
|
const pm = detectPackageManager(cwd);
|
|
9540
9457
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
9541
9458
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
9542
|
-
const configPath =
|
|
9459
|
+
const configPath = path28.join(cwd, "kody.config.json");
|
|
9543
9460
|
if (fs30.existsSync(configPath) && !force) {
|
|
9544
9461
|
skipped.push("kody.config.json");
|
|
9545
9462
|
} else {
|
|
@@ -9548,8 +9465,8 @@ function performInit(cwd, force) {
|
|
|
9548
9465
|
`);
|
|
9549
9466
|
wrote.push("kody.config.json");
|
|
9550
9467
|
}
|
|
9551
|
-
const workflowDir =
|
|
9552
|
-
const workflowPath =
|
|
9468
|
+
const workflowDir = path28.join(cwd, ".github", "workflows");
|
|
9469
|
+
const workflowPath = path28.join(workflowDir, "kody.yml");
|
|
9553
9470
|
if (fs30.existsSync(workflowPath) && !force) {
|
|
9554
9471
|
skipped.push(".github/workflows/kody.yml");
|
|
9555
9472
|
} else {
|
|
@@ -9559,37 +9476,26 @@ function performInit(cwd, force) {
|
|
|
9559
9476
|
}
|
|
9560
9477
|
const builtinJobs = listBuiltinJobs();
|
|
9561
9478
|
if (builtinJobs.length > 0) {
|
|
9562
|
-
const jobsDir =
|
|
9479
|
+
const jobsDir = path28.join(cwd, ".kody", "duties");
|
|
9563
9480
|
fs30.mkdirSync(jobsDir, { recursive: true });
|
|
9564
9481
|
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) {
|
|
9482
|
+
const targetDir = path28.join(jobsDir, job.slug);
|
|
9483
|
+
const relProfile = path28.join(".kody", "duties", job.slug, "profile.json");
|
|
9484
|
+
const relBody = path28.join(".kody", "duties", job.slug, "duty.md");
|
|
9485
|
+
if (fs30.existsSync(targetDir) && fs30.existsSync(path28.join(targetDir, "profile.json")) && !force) {
|
|
9580
9486
|
skipped.push(relProfile);
|
|
9581
|
-
skipped.push(
|
|
9487
|
+
skipped.push(relBody);
|
|
9582
9488
|
continue;
|
|
9583
9489
|
}
|
|
9584
9490
|
fs30.mkdirSync(targetDir, { recursive: true });
|
|
9585
|
-
fs30.writeFileSync(
|
|
9586
|
-
fs30.writeFileSync(
|
|
9491
|
+
fs30.writeFileSync(path28.join(targetDir, "profile.json"), fs30.readFileSync(job.profilePath, "utf-8"));
|
|
9492
|
+
fs30.writeFileSync(path28.join(targetDir, "duty.md"), fs30.readFileSync(job.bodyPath, "utf-8"));
|
|
9587
9493
|
wrote.push(relProfile);
|
|
9588
|
-
wrote.push(
|
|
9494
|
+
wrote.push(relBody);
|
|
9589
9495
|
}
|
|
9590
9496
|
}
|
|
9591
|
-
const staffDir =
|
|
9592
|
-
const staffPath =
|
|
9497
|
+
const staffDir = path28.join(cwd, ".kody", "staff");
|
|
9498
|
+
const staffPath = path28.join(staffDir, "kody.md");
|
|
9593
9499
|
if (fs30.existsSync(staffPath) && !force) {
|
|
9594
9500
|
skipped.push(".kody/staff/kody.md");
|
|
9595
9501
|
} else {
|
|
@@ -9605,7 +9511,7 @@ function performInit(cwd, force) {
|
|
|
9605
9511
|
continue;
|
|
9606
9512
|
}
|
|
9607
9513
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
9608
|
-
const target =
|
|
9514
|
+
const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
|
|
9609
9515
|
if (fs30.existsSync(target) && !force) {
|
|
9610
9516
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9611
9517
|
continue;
|
|
@@ -9931,7 +9837,7 @@ var init_loadIssueStateComment = __esm({
|
|
|
9931
9837
|
|
|
9932
9838
|
// src/scripts/loadJobFromFile.ts
|
|
9933
9839
|
import * as fs31 from "fs";
|
|
9934
|
-
import * as
|
|
9840
|
+
import * as path29 from "path";
|
|
9935
9841
|
function parseJobFile(raw, slug) {
|
|
9936
9842
|
let stripped = raw;
|
|
9937
9843
|
if (stripped.startsWith("---\n")) {
|
|
@@ -9947,17 +9853,17 @@ function parseJobFile(raw, slug) {
|
|
|
9947
9853
|
const rest = trimmed.slice(firstLine2.length).replace(/^\n+/, "");
|
|
9948
9854
|
return { title: h1[1].trim(), body: rest };
|
|
9949
9855
|
}
|
|
9950
|
-
return { title:
|
|
9856
|
+
return { title: humanizeSlug2(slug), body: trimmed };
|
|
9951
9857
|
}
|
|
9952
|
-
function
|
|
9858
|
+
function humanizeSlug2(slug) {
|
|
9953
9859
|
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
9954
9860
|
}
|
|
9955
9861
|
var DUTY_TOOL_PALETTE2, loadJobFromFile;
|
|
9956
9862
|
var init_loadJobFromFile = __esm({
|
|
9957
9863
|
"src/scripts/loadJobFromFile.ts"() {
|
|
9958
9864
|
"use strict";
|
|
9865
|
+
init_dutyFolders();
|
|
9959
9866
|
init_dutyMcp();
|
|
9960
|
-
init_jobFrontmatter();
|
|
9961
9867
|
init_jobState();
|
|
9962
9868
|
DUTY_TOOL_PALETTE2 = new Set(DUTY_MCP_TOOL_NAMES);
|
|
9963
9869
|
loadJobFromFile = async (ctx, profile, args) => {
|
|
@@ -9968,19 +9874,17 @@ var init_loadJobFromFile = __esm({
|
|
|
9968
9874
|
if (!slug) {
|
|
9969
9875
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
9970
9876
|
}
|
|
9971
|
-
const
|
|
9972
|
-
if (!
|
|
9973
|
-
throw new Error(`loadJobFromFile:
|
|
9877
|
+
const duty = readDutyFolder(path29.join(ctx.cwd, jobsDir), slug);
|
|
9878
|
+
if (!duty) {
|
|
9879
|
+
throw new Error(`loadJobFromFile: duty folder not found or incomplete: ${path29.join(ctx.cwd, jobsDir, slug)}`);
|
|
9974
9880
|
}
|
|
9975
|
-
const
|
|
9976
|
-
const
|
|
9977
|
-
const
|
|
9978
|
-
const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
9979
|
-
const workerSlug = (frontmatter.staff ?? "").trim();
|
|
9881
|
+
const { title, body, config } = duty;
|
|
9882
|
+
const mentions = (config.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
9883
|
+
const workerSlug = (config.staff ?? "").trim();
|
|
9980
9884
|
let workerTitle = "";
|
|
9981
9885
|
let workerPersona = "";
|
|
9982
9886
|
if (workerSlug) {
|
|
9983
|
-
const workerPath =
|
|
9887
|
+
const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
9984
9888
|
if (!fs31.existsSync(workerPath)) {
|
|
9985
9889
|
throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
|
|
9986
9890
|
}
|
|
@@ -10005,8 +9909,8 @@ var init_loadJobFromFile = __esm({
|
|
|
10005
9909
|
ctx.data.staffSlug = workerSlug;
|
|
10006
9910
|
ctx.data.staffTitle = workerTitle;
|
|
10007
9911
|
ctx.data.executableSlug = profile.name;
|
|
10008
|
-
ctx.data.dutySchedule = "";
|
|
10009
|
-
const declaredTools =
|
|
9912
|
+
ctx.data.dutySchedule = config.every ?? "";
|
|
9913
|
+
const declaredTools = config.tools ?? [];
|
|
10010
9914
|
if (declaredTools.length > 0) {
|
|
10011
9915
|
const unknown = declaredTools.filter((name) => !DUTY_TOOL_PALETTE2.has(name));
|
|
10012
9916
|
if (unknown.length > 0) {
|
|
@@ -10059,9 +9963,9 @@ ${truncate2(issue.body, FINDING_BODY_MAX_BYTES)}`;
|
|
|
10059
9963
|
|
|
10060
9964
|
// src/scripts/kodyVariables.ts
|
|
10061
9965
|
import * as fs32 from "fs";
|
|
10062
|
-
import * as
|
|
9966
|
+
import * as path30 from "path";
|
|
10063
9967
|
function readKodyVariables(cwd) {
|
|
10064
|
-
const full =
|
|
9968
|
+
const full = path30.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
10065
9969
|
let raw;
|
|
10066
9970
|
try {
|
|
10067
9971
|
raw = fs32.readFileSync(full, "utf-8");
|
|
@@ -10090,7 +9994,7 @@ var init_kodyVariables = __esm({
|
|
|
10090
9994
|
|
|
10091
9995
|
// src/scripts/loadQaContext.ts
|
|
10092
9996
|
import * as fs33 from "fs";
|
|
10093
|
-
import * as
|
|
9997
|
+
import * as path31 from "path";
|
|
10094
9998
|
function parseSlugList(value) {
|
|
10095
9999
|
const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
|
10096
10000
|
return inner.split(",").map(
|
|
@@ -10098,7 +10002,7 @@ function parseSlugList(value) {
|
|
|
10098
10002
|
).filter(Boolean);
|
|
10099
10003
|
}
|
|
10100
10004
|
function readProfileStaff(raw) {
|
|
10101
|
-
const m =
|
|
10005
|
+
const m = FRONTMATTER_RE.exec(raw);
|
|
10102
10006
|
if (!m) return { staff: ["kody"], body: raw };
|
|
10103
10007
|
const body = raw.slice(m[0].length);
|
|
10104
10008
|
let staff = null;
|
|
@@ -10119,7 +10023,7 @@ function readProfileStaff(raw) {
|
|
|
10119
10023
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
10120
10024
|
}
|
|
10121
10025
|
function readProfile(cwd) {
|
|
10122
|
-
const dir =
|
|
10026
|
+
const dir = path31.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
10123
10027
|
if (!fs33.existsSync(dir)) return "";
|
|
10124
10028
|
let entries;
|
|
10125
10029
|
try {
|
|
@@ -10130,7 +10034,7 @@ function readProfile(cwd) {
|
|
|
10130
10034
|
const blocks = [];
|
|
10131
10035
|
for (const file of entries) {
|
|
10132
10036
|
try {
|
|
10133
|
-
const raw = fs33.readFileSync(
|
|
10037
|
+
const raw = fs33.readFileSync(path31.join(dir, file), "utf-8");
|
|
10134
10038
|
const { staff, body } = readProfileStaff(raw);
|
|
10135
10039
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
10136
10040
|
blocks.push(`## ${file}
|
|
@@ -10153,7 +10057,7 @@ function composeAuthBlock(authProfile, login, password) {
|
|
|
10153
10057
|
}
|
|
10154
10058
|
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
10059
|
}
|
|
10156
|
-
var CONTEXT_DIR_REL_PATH, QA_STAFF, ALL_STAFF, LEGACY_AUDIENCE_TO_STAFF,
|
|
10060
|
+
var CONTEXT_DIR_REL_PATH, QA_STAFF, ALL_STAFF, LEGACY_AUDIENCE_TO_STAFF, FRONTMATTER_RE, loadQaContext;
|
|
10157
10061
|
var init_loadQaContext = __esm({
|
|
10158
10062
|
"src/scripts/loadQaContext.ts"() {
|
|
10159
10063
|
"use strict";
|
|
@@ -10162,7 +10066,7 @@ var init_loadQaContext = __esm({
|
|
|
10162
10066
|
QA_STAFF = "qa-engineer";
|
|
10163
10067
|
ALL_STAFF = "*";
|
|
10164
10068
|
LEGACY_AUDIENCE_TO_STAFF = { chat: "kody", qa: QA_STAFF };
|
|
10165
|
-
|
|
10069
|
+
FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
10166
10070
|
loadQaContext = async (ctx) => {
|
|
10167
10071
|
const vars = readKodyVariables(ctx.cwd);
|
|
10168
10072
|
const login = vars.LOGIN_USER ?? "";
|
|
@@ -10177,7 +10081,7 @@ var init_loadQaContext = __esm({
|
|
|
10177
10081
|
|
|
10178
10082
|
// src/taskContext.ts
|
|
10179
10083
|
import * as fs34 from "fs";
|
|
10180
|
-
import * as
|
|
10084
|
+
import * as path32 from "path";
|
|
10181
10085
|
function buildTaskContext(args) {
|
|
10182
10086
|
return {
|
|
10183
10087
|
schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
|
|
@@ -10192,9 +10096,9 @@ function buildTaskContext(args) {
|
|
|
10192
10096
|
}
|
|
10193
10097
|
function persistTaskContext(cwd, ctx) {
|
|
10194
10098
|
try {
|
|
10195
|
-
const dir =
|
|
10099
|
+
const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
|
|
10196
10100
|
fs34.mkdirSync(dir, { recursive: true });
|
|
10197
|
-
const file =
|
|
10101
|
+
const file = path32.join(dir, "task-context.json");
|
|
10198
10102
|
fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
10199
10103
|
`);
|
|
10200
10104
|
return file;
|
|
@@ -10284,7 +10188,7 @@ var init_loadTaskState = __esm({
|
|
|
10284
10188
|
|
|
10285
10189
|
// src/scripts/loadWorkerAdhoc.ts
|
|
10286
10190
|
import * as fs35 from "fs";
|
|
10287
|
-
import * as
|
|
10191
|
+
import * as path33 from "path";
|
|
10288
10192
|
function resolveMessage(messageArg) {
|
|
10289
10193
|
const fromComment = readCommentBody();
|
|
10290
10194
|
if (fromComment) return stripDirective(fromComment);
|
|
@@ -10318,7 +10222,7 @@ function stripDirective(body) {
|
|
|
10318
10222
|
return lines.slice(start).join("\n").trim();
|
|
10319
10223
|
}
|
|
10320
10224
|
function parsePersona(raw, slug) {
|
|
10321
|
-
const stripped =
|
|
10225
|
+
const stripped = stripLeadingFrontmatter(raw);
|
|
10322
10226
|
const trimmed = stripped.trim();
|
|
10323
10227
|
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
10324
10228
|
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
@@ -10326,23 +10230,26 @@ function parsePersona(raw, slug) {
|
|
|
10326
10230
|
const rest = trimmed.slice(firstLine2.length).replace(/^\n+/, "");
|
|
10327
10231
|
return { title: h1[1].trim(), body: rest };
|
|
10328
10232
|
}
|
|
10329
|
-
return { title:
|
|
10233
|
+
return { title: humanizeSlug3(slug), body: trimmed };
|
|
10330
10234
|
}
|
|
10331
|
-
function
|
|
10235
|
+
function stripLeadingFrontmatter(raw) {
|
|
10236
|
+
const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/.exec(raw);
|
|
10237
|
+
return match ? raw.slice(match[0].length) : raw;
|
|
10238
|
+
}
|
|
10239
|
+
function humanizeSlug3(slug) {
|
|
10332
10240
|
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
10333
10241
|
}
|
|
10334
10242
|
var loadWorkerAdhoc;
|
|
10335
10243
|
var init_loadWorkerAdhoc = __esm({
|
|
10336
10244
|
"src/scripts/loadWorkerAdhoc.ts"() {
|
|
10337
10245
|
"use strict";
|
|
10338
|
-
init_jobFrontmatter();
|
|
10339
10246
|
loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
10340
10247
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
10341
10248
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
10342
10249
|
if (!workerSlug) {
|
|
10343
10250
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
10344
10251
|
}
|
|
10345
|
-
const workerPath =
|
|
10252
|
+
const workerPath = path33.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
10346
10253
|
if (!fs35.existsSync(workerPath)) {
|
|
10347
10254
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
10348
10255
|
}
|
|
@@ -12217,12 +12124,12 @@ fi
|
|
|
12217
12124
|
|
|
12218
12125
|
// src/scripts/runPreviewBuild.ts
|
|
12219
12126
|
import { copyFile, writeFile } from "fs/promises";
|
|
12220
|
-
import * as
|
|
12127
|
+
import * as path34 from "path";
|
|
12221
12128
|
import { fileURLToPath } from "url";
|
|
12222
12129
|
function bundledDockerfilePath(mode) {
|
|
12223
|
-
const here =
|
|
12130
|
+
const here = path34.dirname(fileURLToPath(import.meta.url));
|
|
12224
12131
|
const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
|
|
12225
|
-
return
|
|
12132
|
+
return path34.join(here, "preview-build-templates", file);
|
|
12226
12133
|
}
|
|
12227
12134
|
function required(name) {
|
|
12228
12135
|
const v = (process.env[name] ?? "").trim();
|
|
@@ -12481,10 +12388,10 @@ var init_runPreviewBuild = __esm({
|
|
|
12481
12388
|
console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
|
|
12482
12389
|
if (Object.keys(buildEnv).length > 0) {
|
|
12483
12390
|
const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
12484
|
-
await writeFile(
|
|
12391
|
+
await writeFile(path34.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
|
|
12485
12392
|
`, "utf8");
|
|
12486
12393
|
}
|
|
12487
|
-
const consumerDockerfile =
|
|
12394
|
+
const consumerDockerfile = path34.join(ctx.cwd, "Dockerfile.preview");
|
|
12488
12395
|
const { stat } = await import("fs/promises");
|
|
12489
12396
|
let hasConsumerDockerfile = false;
|
|
12490
12397
|
try {
|
|
@@ -12587,7 +12494,7 @@ var init_runPreviewBuild = __esm({
|
|
|
12587
12494
|
// src/scripts/runTickScript.ts
|
|
12588
12495
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
12589
12496
|
import * as fs36 from "fs";
|
|
12590
|
-
import * as
|
|
12497
|
+
import * as path35 from "path";
|
|
12591
12498
|
function buildChildEnv(parent, force) {
|
|
12592
12499
|
const allow = /* @__PURE__ */ new Set([
|
|
12593
12500
|
"PATH",
|
|
@@ -12635,7 +12542,7 @@ var runTickScript;
|
|
|
12635
12542
|
var init_runTickScript = __esm({
|
|
12636
12543
|
"src/scripts/runTickScript.ts"() {
|
|
12637
12544
|
"use strict";
|
|
12638
|
-
|
|
12545
|
+
init_dutyFolders();
|
|
12639
12546
|
init_jobState();
|
|
12640
12547
|
init_parseJobStateFromAgentResult();
|
|
12641
12548
|
runTickScript = async (ctx, _profile, args) => {
|
|
@@ -12649,21 +12556,19 @@ var init_runTickScript = __esm({
|
|
|
12649
12556
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
12650
12557
|
return;
|
|
12651
12558
|
}
|
|
12652
|
-
const
|
|
12653
|
-
if (!
|
|
12559
|
+
const duty = readDutyFolder(path35.join(ctx.cwd, jobsDir), slug);
|
|
12560
|
+
if (!duty) {
|
|
12654
12561
|
ctx.output.exitCode = 99;
|
|
12655
|
-
ctx.output.reason = `runTickScript:
|
|
12562
|
+
ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${path35.join(ctx.cwd, jobsDir, slug)}`;
|
|
12656
12563
|
return;
|
|
12657
12564
|
}
|
|
12658
|
-
const
|
|
12659
|
-
const { frontmatter } = splitFrontmatter(raw);
|
|
12660
|
-
const tickScript = frontmatter.tickScript;
|
|
12565
|
+
const tickScript = duty.config.tickScript;
|
|
12661
12566
|
if (!tickScript) {
|
|
12662
12567
|
ctx.output.exitCode = 99;
|
|
12663
|
-
ctx.output.reason = `runTickScript: duty ${slug} has no \`tickScript
|
|
12568
|
+
ctx.output.reason = `runTickScript: duty ${slug} has no \`tickScript\` in profile.json \u2014 route via duty-tick instead`;
|
|
12664
12569
|
return;
|
|
12665
12570
|
}
|
|
12666
|
-
const scriptPath =
|
|
12571
|
+
const scriptPath = path35.isAbsolute(tickScript) ? tickScript : path35.join(ctx.cwd, tickScript);
|
|
12667
12572
|
if (!fs36.existsSync(scriptPath)) {
|
|
12668
12573
|
ctx.output.exitCode = 99;
|
|
12669
12574
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
@@ -13862,7 +13767,7 @@ var init_scripts = __esm({
|
|
|
13862
13767
|
|
|
13863
13768
|
// src/staff.ts
|
|
13864
13769
|
import * as fs38 from "fs";
|
|
13865
|
-
import * as
|
|
13770
|
+
import * as path36 from "path";
|
|
13866
13771
|
function stripFrontmatter(raw) {
|
|
13867
13772
|
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
13868
13773
|
return (match ? match[1] : raw).trim();
|
|
@@ -13870,7 +13775,7 @@ function stripFrontmatter(raw) {
|
|
|
13870
13775
|
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
13871
13776
|
const trimmed = slug.trim();
|
|
13872
13777
|
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
13873
|
-
const staffPath =
|
|
13778
|
+
const staffPath = path36.join(cwd, staffDir, `${trimmed}.md`);
|
|
13874
13779
|
if (fs38.existsSync(staffPath)) {
|
|
13875
13780
|
const body = stripFrontmatter(fs38.readFileSync(staffPath, "utf-8"));
|
|
13876
13781
|
if (body) return body;
|
|
@@ -13986,7 +13891,7 @@ var init_tools = __esm({
|
|
|
13986
13891
|
// src/executor.ts
|
|
13987
13892
|
import { spawn as spawn7 } from "child_process";
|
|
13988
13893
|
import * as fs39 from "fs";
|
|
13989
|
-
import * as
|
|
13894
|
+
import * as path37 from "path";
|
|
13990
13895
|
function isMutatingPostflight(scriptName) {
|
|
13991
13896
|
return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
|
|
13992
13897
|
}
|
|
@@ -14137,13 +14042,13 @@ async function runExecutable(profileName, input) {
|
|
|
14137
14042
|
})
|
|
14138
14043
|
};
|
|
14139
14044
|
})() : null;
|
|
14140
|
-
const ndjsonDir =
|
|
14045
|
+
const ndjsonDir = path37.join(input.cwd, ".kody");
|
|
14141
14046
|
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
14047
|
const staffPersona = personaSlug ? framePersona(personaSlug, loadStaffPersona(input.cwd, personaSlug)) : null;
|
|
14143
14048
|
const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
|
|
14144
14049
|
const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
|
|
14145
14050
|
const invokeAgent = async (prompt) => {
|
|
14146
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
14051
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path37.isAbsolute(p) ? p : path37.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
14147
14052
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
14148
14053
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
14149
14054
|
const agents = loadSubagents(profile);
|
|
@@ -14184,7 +14089,7 @@ async function runExecutable(profileName, input) {
|
|
|
14184
14089
|
enableVerifyTool: profile.claudeCode.enableVerifyTool,
|
|
14185
14090
|
enableSubmitTool: profile.claudeCode.enableSubmitTool,
|
|
14186
14091
|
// Locked-toolbox duty mode: `loadJobFromFile` flips `ctx.data.dutyTools`
|
|
14187
|
-
// when a duty declares `tools
|
|
14092
|
+
// when a duty declares `tools` in profile.json. The executor doesn't need
|
|
14188
14093
|
// to know the palette — it just forwards the flag so agent.ts can spin
|
|
14189
14094
|
// up the in-process `kody-duty` MCP server with the right context.
|
|
14190
14095
|
enableDutyTool: Array.isArray(ctx.data.dutyTools) && ctx.data.dutyTools.length > 0,
|
|
@@ -14469,13 +14374,13 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
14469
14374
|
function resolveProfilePath(profileName) {
|
|
14470
14375
|
const found = resolveExecutable(profileName);
|
|
14471
14376
|
if (found) return found;
|
|
14472
|
-
const here =
|
|
14377
|
+
const here = path37.dirname(new URL(import.meta.url).pathname);
|
|
14473
14378
|
const candidates = [
|
|
14474
|
-
|
|
14379
|
+
path37.join(here, "executables", profileName, "profile.json"),
|
|
14475
14380
|
// same-dir sibling (dev)
|
|
14476
|
-
|
|
14381
|
+
path37.join(here, "..", "executables", profileName, "profile.json"),
|
|
14477
14382
|
// up one (prod: dist/bin → dist/executables)
|
|
14478
|
-
|
|
14383
|
+
path37.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
14479
14384
|
// fallback
|
|
14480
14385
|
];
|
|
14481
14386
|
for (const c of candidates) {
|
|
@@ -14577,7 +14482,7 @@ function resolveShellTimeoutMs(entry) {
|
|
|
14577
14482
|
}
|
|
14578
14483
|
async function runShellEntry(entry, ctx, profile) {
|
|
14579
14484
|
const shellName = entry.shell;
|
|
14580
|
-
const shellPath =
|
|
14485
|
+
const shellPath = path37.join(profile.dir, shellName);
|
|
14581
14486
|
if (!fs39.existsSync(shellPath)) {
|
|
14582
14487
|
ctx.skipAgent = true;
|
|
14583
14488
|
ctx.output.exitCode = 99;
|
|
@@ -14834,18 +14739,18 @@ function translateOpenAISseToBrain(opts) {
|
|
|
14834
14739
|
// src/servers/brain-serve.ts
|
|
14835
14740
|
import * as fs42 from "fs";
|
|
14836
14741
|
import { createServer } from "http";
|
|
14837
|
-
import * as
|
|
14742
|
+
import * as path40 from "path";
|
|
14838
14743
|
|
|
14839
14744
|
// src/chat/loop.ts
|
|
14840
14745
|
init_agent();
|
|
14841
14746
|
init_registry();
|
|
14842
14747
|
init_task_artifacts();
|
|
14843
|
-
import * as
|
|
14844
|
-
import * as
|
|
14748
|
+
import * as fs12 from "fs";
|
|
14749
|
+
import * as path12 from "path";
|
|
14845
14750
|
|
|
14846
14751
|
// src/chat/attachments.ts
|
|
14847
|
-
import * as
|
|
14848
|
-
import * as
|
|
14752
|
+
import * as fs9 from "fs";
|
|
14753
|
+
import * as path9 from "path";
|
|
14849
14754
|
var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
|
|
14850
14755
|
var EXT_BY_MIME = {
|
|
14851
14756
|
"image/png": "png",
|
|
@@ -14861,7 +14766,7 @@ function extFor(mime) {
|
|
|
14861
14766
|
return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
|
|
14862
14767
|
}
|
|
14863
14768
|
function attachmentsDir(cwd, sessionId) {
|
|
14864
|
-
return
|
|
14769
|
+
return path9.join(cwd, ".kody", "tmp", "attachments", sessionId);
|
|
14865
14770
|
}
|
|
14866
14771
|
function prepareAttachments(turns, cwd, sessionId) {
|
|
14867
14772
|
const imagePaths = [];
|
|
@@ -14878,11 +14783,11 @@ function prepareAttachments(turns, cwd, sessionId) {
|
|
|
14878
14783
|
if (!isImage) return `[File: ${name}]`;
|
|
14879
14784
|
try {
|
|
14880
14785
|
if (!dirEnsured) {
|
|
14881
|
-
|
|
14786
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
14882
14787
|
dirEnsured = true;
|
|
14883
14788
|
}
|
|
14884
|
-
const filePath =
|
|
14885
|
-
|
|
14789
|
+
const filePath = path9.join(dir, `${imageCounter}.${extFor(mime)}`);
|
|
14790
|
+
fs9.writeFileSync(filePath, Buffer.from(data, "base64"));
|
|
14886
14791
|
imageCounter += 1;
|
|
14887
14792
|
imagePaths.push(filePath);
|
|
14888
14793
|
return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it.]`;
|
|
@@ -14898,10 +14803,10 @@ function prepareAttachments(turns, cwd, sessionId) {
|
|
|
14898
14803
|
}
|
|
14899
14804
|
|
|
14900
14805
|
// src/chat/events.ts
|
|
14901
|
-
import * as
|
|
14902
|
-
import * as
|
|
14806
|
+
import * as fs10 from "fs";
|
|
14807
|
+
import * as path10 from "path";
|
|
14903
14808
|
function eventsFilePath(cwd, sessionId) {
|
|
14904
|
-
return
|
|
14809
|
+
return path10.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
|
|
14905
14810
|
}
|
|
14906
14811
|
var FileSink = class {
|
|
14907
14812
|
constructor(file) {
|
|
@@ -14909,8 +14814,8 @@ var FileSink = class {
|
|
|
14909
14814
|
}
|
|
14910
14815
|
file;
|
|
14911
14816
|
async emit(event) {
|
|
14912
|
-
|
|
14913
|
-
|
|
14817
|
+
fs10.mkdirSync(path10.dirname(this.file), { recursive: true });
|
|
14818
|
+
fs10.appendFileSync(this.file, `${JSON.stringify(event)}
|
|
14914
14819
|
`);
|
|
14915
14820
|
}
|
|
14916
14821
|
};
|
|
@@ -14964,14 +14869,14 @@ function makeRunId(sessionId, suffix) {
|
|
|
14964
14869
|
}
|
|
14965
14870
|
|
|
14966
14871
|
// src/chat/session.ts
|
|
14967
|
-
import * as
|
|
14968
|
-
import * as
|
|
14872
|
+
import * as fs11 from "fs";
|
|
14873
|
+
import * as path11 from "path";
|
|
14969
14874
|
function sessionFilePath(cwd, sessionId) {
|
|
14970
|
-
return
|
|
14875
|
+
return path11.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
|
|
14971
14876
|
}
|
|
14972
14877
|
function readMeta(file) {
|
|
14973
|
-
if (!
|
|
14974
|
-
const raw =
|
|
14878
|
+
if (!fs11.existsSync(file)) return null;
|
|
14879
|
+
const raw = fs11.readFileSync(file, "utf-8");
|
|
14975
14880
|
const firstLine2 = raw.split("\n", 1)[0]?.trim();
|
|
14976
14881
|
if (!firstLine2) return null;
|
|
14977
14882
|
try {
|
|
@@ -14984,8 +14889,8 @@ function readMeta(file) {
|
|
|
14984
14889
|
}
|
|
14985
14890
|
}
|
|
14986
14891
|
function readSession(file) {
|
|
14987
|
-
if (!
|
|
14988
|
-
const raw =
|
|
14892
|
+
if (!fs11.existsSync(file)) return [];
|
|
14893
|
+
const raw = fs11.readFileSync(file, "utf-8").trim();
|
|
14989
14894
|
if (!raw) return [];
|
|
14990
14895
|
const turns = [];
|
|
14991
14896
|
for (const line of raw.split("\n")) {
|
|
@@ -15001,14 +14906,14 @@ function readSession(file) {
|
|
|
15001
14906
|
return turns;
|
|
15002
14907
|
}
|
|
15003
14908
|
function appendTurn(file, turn) {
|
|
15004
|
-
|
|
14909
|
+
fs11.mkdirSync(path11.dirname(file), { recursive: true });
|
|
15005
14910
|
const line = JSON.stringify({
|
|
15006
14911
|
role: turn.role,
|
|
15007
14912
|
content: turn.content,
|
|
15008
14913
|
timestamp: turn.timestamp,
|
|
15009
14914
|
toolCalls: turn.toolCalls ?? []
|
|
15010
14915
|
});
|
|
15011
|
-
|
|
14916
|
+
fs11.appendFileSync(file, `${line}
|
|
15012
14917
|
`);
|
|
15013
14918
|
}
|
|
15014
14919
|
function seedInitialMessage(file, message) {
|
|
@@ -15127,7 +15032,7 @@ function buildExecutableCatalog() {
|
|
|
15127
15032
|
const entries = [];
|
|
15128
15033
|
for (const { name, profilePath } of discovered) {
|
|
15129
15034
|
try {
|
|
15130
|
-
const raw = JSON.parse(
|
|
15035
|
+
const raw = JSON.parse(fs12.readFileSync(profilePath, "utf-8"));
|
|
15131
15036
|
const describe = typeof raw.describe === "string" ? raw.describe : "";
|
|
15132
15037
|
const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
|
|
15133
15038
|
entries.push({ name, describe: firstSentence.trim() });
|
|
@@ -15295,10 +15200,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
15295
15200
|
var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
|
|
15296
15201
|
var MAX_INDEX_BYTES = 8e3;
|
|
15297
15202
|
function readMemoryIndexBlock(cwd) {
|
|
15298
|
-
const indexPath =
|
|
15203
|
+
const indexPath = path12.join(cwd, MEMORY_INDEX_REL);
|
|
15299
15204
|
let raw;
|
|
15300
15205
|
try {
|
|
15301
|
-
raw =
|
|
15206
|
+
raw = fs12.readFileSync(indexPath, "utf-8");
|
|
15302
15207
|
} catch {
|
|
15303
15208
|
return "";
|
|
15304
15209
|
}
|
|
@@ -15316,17 +15221,17 @@ function readMemoryIndexBlock(cwd) {
|
|
|
15316
15221
|
var CONTEXT_DIR_REL = ".kody/context";
|
|
15317
15222
|
var MAX_CONTEXT_BYTES = 12e3;
|
|
15318
15223
|
function readContextBlock(cwd) {
|
|
15319
|
-
const dir =
|
|
15224
|
+
const dir = path12.join(cwd, CONTEXT_DIR_REL);
|
|
15320
15225
|
let files;
|
|
15321
15226
|
try {
|
|
15322
|
-
files =
|
|
15227
|
+
files = fs12.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
15323
15228
|
} catch {
|
|
15324
15229
|
return "";
|
|
15325
15230
|
}
|
|
15326
15231
|
const sections = [];
|
|
15327
15232
|
for (const file of files) {
|
|
15328
15233
|
try {
|
|
15329
|
-
const content =
|
|
15234
|
+
const content = fs12.readFileSync(path12.join(dir, file), "utf-8").trim();
|
|
15330
15235
|
if (content) sections.push(`### ${file.replace(/\.md$/, "")}
|
|
15331
15236
|
|
|
15332
15237
|
${content}`);
|
|
@@ -15349,10 +15254,10 @@ _\u2026 (context truncated; see \`.kody/context/\` for the full text)_` : joined
|
|
|
15349
15254
|
var INSTRUCTIONS_REL = ".kody/instructions.md";
|
|
15350
15255
|
var MAX_INSTRUCTIONS_BYTES = 8e3;
|
|
15351
15256
|
function readInstructionsBlock(cwd) {
|
|
15352
|
-
const instructionsPath =
|
|
15257
|
+
const instructionsPath = path12.join(cwd, INSTRUCTIONS_REL);
|
|
15353
15258
|
let raw;
|
|
15354
15259
|
try {
|
|
15355
|
-
raw =
|
|
15260
|
+
raw = fs12.readFileSync(instructionsPath, "utf-8");
|
|
15356
15261
|
} catch {
|
|
15357
15262
|
return "";
|
|
15358
15263
|
}
|
|
@@ -15376,7 +15281,7 @@ init_config();
|
|
|
15376
15281
|
// src/kody-cli.ts
|
|
15377
15282
|
import { execFileSync as execFileSync26 } from "child_process";
|
|
15378
15283
|
import * as fs40 from "fs";
|
|
15379
|
-
import * as
|
|
15284
|
+
import * as path38 from "path";
|
|
15380
15285
|
|
|
15381
15286
|
// src/app-auth.ts
|
|
15382
15287
|
import { createSign } from "crypto";
|
|
@@ -15453,7 +15358,7 @@ init_config();
|
|
|
15453
15358
|
|
|
15454
15359
|
// src/dispatch.ts
|
|
15455
15360
|
init_config();
|
|
15456
|
-
import * as
|
|
15361
|
+
import * as fs13 from "fs";
|
|
15457
15362
|
|
|
15458
15363
|
// src/cron-match.ts
|
|
15459
15364
|
var FIELD_BOUNDS = [
|
|
@@ -15571,10 +15476,10 @@ function autoDispatch(opts) {
|
|
|
15571
15476
|
}
|
|
15572
15477
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15573
15478
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
15574
|
-
if (!eventName || !eventPath || !
|
|
15479
|
+
if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
|
|
15575
15480
|
let event = {};
|
|
15576
15481
|
try {
|
|
15577
|
-
event = JSON.parse(
|
|
15482
|
+
event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
|
|
15578
15483
|
} catch {
|
|
15579
15484
|
return null;
|
|
15580
15485
|
}
|
|
@@ -15685,7 +15590,7 @@ function autoDispatchTyped(opts) {
|
|
|
15685
15590
|
if (legacy) return { kind: "route", ...legacy };
|
|
15686
15591
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15687
15592
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
15688
|
-
if (!eventName || !eventPath || !
|
|
15593
|
+
if (!eventName || !eventPath || !fs13.existsSync(eventPath)) {
|
|
15689
15594
|
return { kind: "silent", reason: "no GHA event context" };
|
|
15690
15595
|
}
|
|
15691
15596
|
if (eventName !== "issue_comment") {
|
|
@@ -15693,7 +15598,7 @@ function autoDispatchTyped(opts) {
|
|
|
15693
15598
|
}
|
|
15694
15599
|
let event = {};
|
|
15695
15600
|
try {
|
|
15696
|
-
event = JSON.parse(
|
|
15601
|
+
event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
|
|
15697
15602
|
} catch {
|
|
15698
15603
|
return { kind: "silent", reason: "GHA event payload unreadable" };
|
|
15699
15604
|
}
|
|
@@ -15737,7 +15642,7 @@ function dispatchScheduledWatches(opts) {
|
|
|
15737
15642
|
for (const exe of listExecutables()) {
|
|
15738
15643
|
let raw;
|
|
15739
15644
|
try {
|
|
15740
|
-
raw =
|
|
15645
|
+
raw = fs13.readFileSync(exe.profilePath, "utf-8");
|
|
15741
15646
|
} catch {
|
|
15742
15647
|
continue;
|
|
15743
15648
|
}
|
|
@@ -15980,9 +15885,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
15980
15885
|
return void 0;
|
|
15981
15886
|
}
|
|
15982
15887
|
function detectPackageManager2(cwd) {
|
|
15983
|
-
if (fs40.existsSync(
|
|
15984
|
-
if (fs40.existsSync(
|
|
15985
|
-
if (fs40.existsSync(
|
|
15888
|
+
if (fs40.existsSync(path38.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
15889
|
+
if (fs40.existsSync(path38.join(cwd, "yarn.lock"))) return "yarn";
|
|
15890
|
+
if (fs40.existsSync(path38.join(cwd, "bun.lockb"))) return "bun";
|
|
15986
15891
|
return "npm";
|
|
15987
15892
|
}
|
|
15988
15893
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -16069,7 +15974,7 @@ function configureGitIdentity(cwd) {
|
|
|
16069
15974
|
}
|
|
16070
15975
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
16071
15976
|
if (!issueNumber) return;
|
|
16072
|
-
const logPath =
|
|
15977
|
+
const logPath = path38.join(cwd, ".kody", "last-run.jsonl");
|
|
16073
15978
|
let tail = "";
|
|
16074
15979
|
try {
|
|
16075
15980
|
if (fs40.existsSync(logPath)) {
|
|
@@ -16098,7 +16003,7 @@ async function runCi(argv) {
|
|
|
16098
16003
|
return 0;
|
|
16099
16004
|
}
|
|
16100
16005
|
const args = parseCiArgs(argv);
|
|
16101
|
-
const cwd = args.cwd ?
|
|
16006
|
+
const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
|
|
16102
16007
|
let earlyConfig;
|
|
16103
16008
|
let earlyConfigError;
|
|
16104
16009
|
try {
|
|
@@ -16409,10 +16314,10 @@ init_repoWorkspace();
|
|
|
16409
16314
|
|
|
16410
16315
|
// src/scripts/brainTurnLog.ts
|
|
16411
16316
|
import * as fs41 from "fs";
|
|
16412
|
-
import * as
|
|
16317
|
+
import * as path39 from "path";
|
|
16413
16318
|
var live = /* @__PURE__ */ new Map();
|
|
16414
16319
|
function eventsPath(dir, chatId) {
|
|
16415
|
-
return
|
|
16320
|
+
return path39.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
16416
16321
|
}
|
|
16417
16322
|
function lastPersistedSeq(dir, chatId) {
|
|
16418
16323
|
const p = eventsPath(dir, chatId);
|
|
@@ -16455,7 +16360,7 @@ function beginTurn(dir, chatId) {
|
|
|
16455
16360
|
};
|
|
16456
16361
|
live.set(chatId, state);
|
|
16457
16362
|
const p = eventsPath(dir, chatId);
|
|
16458
|
-
fs41.mkdirSync(
|
|
16363
|
+
fs41.mkdirSync(path39.dirname(p), { recursive: true });
|
|
16459
16364
|
return (event) => {
|
|
16460
16365
|
state.seq += 1;
|
|
16461
16366
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
@@ -16730,7 +16635,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
16730
16635
|
const repo = strField(body, "repo");
|
|
16731
16636
|
const repoToken = strField(body, "repoToken");
|
|
16732
16637
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
16733
|
-
fs42.mkdirSync(
|
|
16638
|
+
fs42.mkdirSync(path40.dirname(sessionFile), { recursive: true });
|
|
16734
16639
|
appendTurn(sessionFile, {
|
|
16735
16640
|
role: "user",
|
|
16736
16641
|
content: message,
|
|
@@ -16777,7 +16682,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
16777
16682
|
function buildServer(opts) {
|
|
16778
16683
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
16779
16684
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
16780
|
-
const reposRoot = opts.reposRoot ??
|
|
16685
|
+
const reposRoot = opts.reposRoot ?? path40.join(path40.dirname(path40.resolve(opts.cwd)), "repos");
|
|
16781
16686
|
return createServer(async (req, res) => {
|
|
16782
16687
|
if (!req.method || !req.url) {
|
|
16783
16688
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -17371,13 +17276,13 @@ async function loadConfigSafe() {
|
|
|
17371
17276
|
// src/chat-cli.ts
|
|
17372
17277
|
import { execFileSync as execFileSync29 } from "child_process";
|
|
17373
17278
|
import * as fs44 from "fs";
|
|
17374
|
-
import * as
|
|
17279
|
+
import * as path42 from "path";
|
|
17375
17280
|
|
|
17376
17281
|
// src/chat/modes/interactive.ts
|
|
17377
17282
|
init_issue();
|
|
17378
17283
|
import { execFileSync as execFileSync28 } from "child_process";
|
|
17379
17284
|
import * as fs43 from "fs";
|
|
17380
|
-
import * as
|
|
17285
|
+
import * as path41 from "path";
|
|
17381
17286
|
|
|
17382
17287
|
// src/chat/inbox.ts
|
|
17383
17288
|
import { execFileSync as execFileSync27 } from "child_process";
|
|
@@ -17536,9 +17441,9 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
17536
17441
|
return -1;
|
|
17537
17442
|
}
|
|
17538
17443
|
function commitTurn(cwd, sessionId, _verbose) {
|
|
17539
|
-
const sessionRel =
|
|
17540
|
-
const eventsRel =
|
|
17541
|
-
const rels = [sessionRel, eventsRel].filter((p) => fs43.existsSync(
|
|
17444
|
+
const sessionRel = path41.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
17445
|
+
const eventsRel = path41.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
17446
|
+
const rels = [sessionRel, eventsRel].filter((p) => fs43.existsSync(path41.join(cwd, p)));
|
|
17542
17447
|
if (rels.length === 0) return;
|
|
17543
17448
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
17544
17449
|
if (!repository) {
|
|
@@ -17550,8 +17455,8 @@ function commitTurn(cwd, sessionId, _verbose) {
|
|
|
17550
17455
|
}
|
|
17551
17456
|
const branch = defaultBranch(cwd) ?? "main";
|
|
17552
17457
|
for (const rel of rels) {
|
|
17553
|
-
const repoPath = rel.split(
|
|
17554
|
-
const localText = fs43.readFileSync(
|
|
17458
|
+
const repoPath = rel.split(path41.sep).join("/");
|
|
17459
|
+
const localText = fs43.readFileSync(path41.join(cwd, rel), "utf-8");
|
|
17555
17460
|
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
17556
17461
|
}
|
|
17557
17462
|
}
|
|
@@ -17688,12 +17593,12 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
17688
17593
|
return result;
|
|
17689
17594
|
}
|
|
17690
17595
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
17691
|
-
const sessionFile =
|
|
17692
|
-
const eventsFile =
|
|
17596
|
+
const sessionFile = path42.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
17597
|
+
const eventsFile = path42.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
17693
17598
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
17694
|
-
const tasksDir =
|
|
17599
|
+
const tasksDir = path42.join(".kody", "tasks", safeSession);
|
|
17695
17600
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
17696
|
-
const paths = candidatePaths.filter((p) => fs44.existsSync(
|
|
17601
|
+
const paths = candidatePaths.filter((p) => fs44.existsSync(path42.join(cwd, p)));
|
|
17697
17602
|
if (paths.length === 0) return;
|
|
17698
17603
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
17699
17604
|
try {
|
|
@@ -17737,7 +17642,7 @@ async function runChat(argv) {
|
|
|
17737
17642
|
${CHAT_HELP}`);
|
|
17738
17643
|
return 64;
|
|
17739
17644
|
}
|
|
17740
|
-
const cwd = args.cwd ?
|
|
17645
|
+
const cwd = args.cwd ? path42.resolve(args.cwd) : process.cwd();
|
|
17741
17646
|
const sessionId = args.sessionId;
|
|
17742
17647
|
const unpackedSecrets = unpackAllSecrets();
|
|
17743
17648
|
if (unpackedSecrets > 0) {
|
|
@@ -17946,8 +17851,8 @@ var FlyClient = class {
|
|
|
17946
17851
|
get fetch() {
|
|
17947
17852
|
return this.opts.fetchImpl ?? fetch;
|
|
17948
17853
|
}
|
|
17949
|
-
async call(
|
|
17950
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
17854
|
+
async call(path43, init = {}) {
|
|
17855
|
+
const res = await this.fetch(`${FLY_API_BASE}${path43}`, {
|
|
17951
17856
|
method: init.method ?? "GET",
|
|
17952
17857
|
headers: {
|
|
17953
17858
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -17958,7 +17863,7 @@ var FlyClient = class {
|
|
|
17958
17863
|
if (res.status === 404 && init.allow404) return null;
|
|
17959
17864
|
if (!res.ok) {
|
|
17960
17865
|
const text = await res.text().catch(() => "");
|
|
17961
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
17866
|
+
throw new Error(`Fly API ${res.status} on ${path43}: ${text.slice(0, 200) || res.statusText}`);
|
|
17962
17867
|
}
|
|
17963
17868
|
if (res.status === 204) return null;
|
|
17964
17869
|
const raw = await res.text();
|