@kody-ade/kody-engine 0.4.204-next.0 → 0.4.204-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/kody.js +544 -364
- package/dist/executables/types.ts +30 -0
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -1072,16 +1072,16 @@ var init_fetchRepoMcp = __esm({
|
|
|
1072
1072
|
});
|
|
1073
1073
|
|
|
1074
1074
|
// src/prompt.ts
|
|
1075
|
-
import * as
|
|
1076
|
-
import * as
|
|
1075
|
+
import * as fs17 from "fs";
|
|
1076
|
+
import * as path16 from "path";
|
|
1077
1077
|
function loadProjectConventions(projectDir) {
|
|
1078
1078
|
const out = [];
|
|
1079
1079
|
for (const rel of CONVENTION_FILES) {
|
|
1080
|
-
const abs =
|
|
1081
|
-
if (!
|
|
1080
|
+
const abs = path16.join(projectDir, rel);
|
|
1081
|
+
if (!fs17.existsSync(abs)) continue;
|
|
1082
1082
|
let content;
|
|
1083
1083
|
try {
|
|
1084
|
-
content =
|
|
1084
|
+
content = fs17.readFileSync(abs, "utf-8");
|
|
1085
1085
|
} catch {
|
|
1086
1086
|
continue;
|
|
1087
1087
|
}
|
|
@@ -1316,28 +1316,28 @@ var loadMemoryContext_exports = {};
|
|
|
1316
1316
|
__export(loadMemoryContext_exports, {
|
|
1317
1317
|
loadMemoryContext: () => loadMemoryContext
|
|
1318
1318
|
});
|
|
1319
|
-
import * as
|
|
1320
|
-
import * as
|
|
1319
|
+
import * as fs18 from "fs";
|
|
1320
|
+
import * as path17 from "path";
|
|
1321
1321
|
function collectPages(memoryAbs) {
|
|
1322
1322
|
const out = [];
|
|
1323
1323
|
walkMd(memoryAbs, (file) => {
|
|
1324
1324
|
let stat;
|
|
1325
1325
|
try {
|
|
1326
|
-
stat =
|
|
1326
|
+
stat = fs18.statSync(file);
|
|
1327
1327
|
} catch {
|
|
1328
1328
|
return;
|
|
1329
1329
|
}
|
|
1330
1330
|
let raw;
|
|
1331
1331
|
try {
|
|
1332
|
-
raw =
|
|
1332
|
+
raw = fs18.readFileSync(file, "utf-8");
|
|
1333
1333
|
} catch {
|
|
1334
1334
|
return;
|
|
1335
1335
|
}
|
|
1336
1336
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1337
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
1337
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path17.basename(file, ".md");
|
|
1338
1338
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
1339
1339
|
out.push({
|
|
1340
|
-
relPath:
|
|
1340
|
+
relPath: path17.relative(memoryAbs, file),
|
|
1341
1341
|
title,
|
|
1342
1342
|
updated,
|
|
1343
1343
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
|
|
@@ -1405,16 +1405,16 @@ function walkMd(root, visit) {
|
|
|
1405
1405
|
const dir = stack.pop();
|
|
1406
1406
|
let names;
|
|
1407
1407
|
try {
|
|
1408
|
-
names =
|
|
1408
|
+
names = fs18.readdirSync(dir);
|
|
1409
1409
|
} catch {
|
|
1410
1410
|
continue;
|
|
1411
1411
|
}
|
|
1412
1412
|
for (const name of names) {
|
|
1413
1413
|
if (name.startsWith(".")) continue;
|
|
1414
|
-
const full =
|
|
1414
|
+
const full = path17.join(dir, name);
|
|
1415
1415
|
let stat;
|
|
1416
1416
|
try {
|
|
1417
|
-
stat =
|
|
1417
|
+
stat = fs18.statSync(full);
|
|
1418
1418
|
} catch {
|
|
1419
1419
|
continue;
|
|
1420
1420
|
}
|
|
@@ -1437,8 +1437,8 @@ var init_loadMemoryContext = __esm({
|
|
|
1437
1437
|
TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
|
|
1438
1438
|
loadMemoryContext = async (ctx) => {
|
|
1439
1439
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
1440
|
-
const memoryAbs =
|
|
1441
|
-
if (!
|
|
1440
|
+
const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
1441
|
+
if (!fs18.existsSync(memoryAbs)) {
|
|
1442
1442
|
ctx.data.memoryContext = "";
|
|
1443
1443
|
return;
|
|
1444
1444
|
}
|
|
@@ -1483,7 +1483,7 @@ var init_loadCoverageRules = __esm({
|
|
|
1483
1483
|
// package.json
|
|
1484
1484
|
var package_default = {
|
|
1485
1485
|
name: "@kody-ade/kody-engine",
|
|
1486
|
-
version: "0.4.204-next.
|
|
1486
|
+
version: "0.4.204-next.10",
|
|
1487
1487
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
1488
1488
|
license: "MIT",
|
|
1489
1489
|
type: "module",
|
|
@@ -2375,6 +2375,9 @@ function getExecutablesRoot() {
|
|
|
2375
2375
|
function getProjectExecutablesRoot() {
|
|
2376
2376
|
return path7.join(process.cwd(), ".kody", "executables");
|
|
2377
2377
|
}
|
|
2378
|
+
function getProjectDutiesRoot() {
|
|
2379
|
+
return path7.join(process.cwd(), ".kody", "duties");
|
|
2380
|
+
}
|
|
2378
2381
|
function getBuiltinJobsRoot() {
|
|
2379
2382
|
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2380
2383
|
const candidates = [
|
|
@@ -2402,7 +2405,7 @@ function listBuiltinJobs(root = getBuiltinJobsRoot()) {
|
|
|
2402
2405
|
return out;
|
|
2403
2406
|
}
|
|
2404
2407
|
function getExecutableRoots() {
|
|
2405
|
-
return [getProjectExecutablesRoot(), getExecutablesRoot()];
|
|
2408
|
+
return [getProjectDutiesRoot(), getProjectExecutablesRoot(), getExecutablesRoot()];
|
|
2406
2409
|
}
|
|
2407
2410
|
function listExecutables(roots = getExecutableRoots()) {
|
|
2408
2411
|
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
@@ -3723,11 +3726,11 @@ import * as path39 from "path";
|
|
|
3723
3726
|
// src/container.ts
|
|
3724
3727
|
init_events();
|
|
3725
3728
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
3726
|
-
import * as
|
|
3729
|
+
import * as fs19 from "fs";
|
|
3727
3730
|
|
|
3728
3731
|
// src/profile.ts
|
|
3729
|
-
import * as
|
|
3730
|
-
import * as
|
|
3732
|
+
import * as fs16 from "fs";
|
|
3733
|
+
import * as path15 from "path";
|
|
3731
3734
|
|
|
3732
3735
|
// src/profile-error.ts
|
|
3733
3736
|
var ProfileError = class extends Error {
|
|
@@ -3867,6 +3870,150 @@ function applyLifecycle(profile, profilePath) {
|
|
|
3867
3870
|
expander(profile, profilePath);
|
|
3868
3871
|
}
|
|
3869
3872
|
|
|
3873
|
+
// src/subagents.ts
|
|
3874
|
+
import * as fs15 from "fs";
|
|
3875
|
+
import * as path14 from "path";
|
|
3876
|
+
|
|
3877
|
+
// src/scripts/buildSyntheticPlugin.ts
|
|
3878
|
+
import * as fs14 from "fs";
|
|
3879
|
+
import * as os2 from "os";
|
|
3880
|
+
import * as path13 from "path";
|
|
3881
|
+
function getPluginsCatalogRoot() {
|
|
3882
|
+
const here = path13.dirname(new URL(import.meta.url).pathname);
|
|
3883
|
+
const candidates = [
|
|
3884
|
+
path13.join(here, "..", "plugins"),
|
|
3885
|
+
// dev: src/scripts → src/plugins
|
|
3886
|
+
path13.join(here, "..", "..", "plugins"),
|
|
3887
|
+
// built: dist/scripts → dist/plugins
|
|
3888
|
+
path13.join(here, "..", "..", "src", "plugins")
|
|
3889
|
+
// fallback
|
|
3890
|
+
];
|
|
3891
|
+
for (const c of candidates) {
|
|
3892
|
+
if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
|
|
3893
|
+
}
|
|
3894
|
+
return candidates[0];
|
|
3895
|
+
}
|
|
3896
|
+
var buildSyntheticPlugin = async (ctx, profile) => {
|
|
3897
|
+
const cc = profile.claudeCode;
|
|
3898
|
+
const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
|
|
3899
|
+
if (!needsSynthetic) return;
|
|
3900
|
+
const catalog = getPluginsCatalogRoot();
|
|
3901
|
+
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3902
|
+
const root = path13.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
3903
|
+
fs14.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
|
|
3904
|
+
const resolvePart = (bucket, entry) => {
|
|
3905
|
+
const local = path13.join(profile.dir, bucket, entry);
|
|
3906
|
+
if (fs14.existsSync(local)) return local;
|
|
3907
|
+
const central = path13.join(catalog, bucket, entry);
|
|
3908
|
+
if (fs14.existsSync(central)) return central;
|
|
3909
|
+
throw new Error(
|
|
3910
|
+
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
3911
|
+
);
|
|
3912
|
+
};
|
|
3913
|
+
if (cc.skills.length > 0) {
|
|
3914
|
+
const dst = path13.join(root, "skills");
|
|
3915
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
3916
|
+
for (const name of cc.skills) {
|
|
3917
|
+
copyDir(resolvePart("skills", name), path13.join(dst, name));
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
if (cc.commands.length > 0) {
|
|
3921
|
+
const dst = path13.join(root, "commands");
|
|
3922
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
3923
|
+
for (const name of cc.commands) {
|
|
3924
|
+
fs14.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
if (cc.hooks.length > 0) {
|
|
3928
|
+
const dst = path13.join(root, "hooks");
|
|
3929
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
3930
|
+
const merged = { hooks: {} };
|
|
3931
|
+
for (const name of cc.hooks) {
|
|
3932
|
+
const src = resolvePart("hooks", `${name}.json`);
|
|
3933
|
+
const parsed = JSON.parse(fs14.readFileSync(src, "utf-8"));
|
|
3934
|
+
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
3935
|
+
if (!Array.isArray(entries)) continue;
|
|
3936
|
+
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
3937
|
+
merged.hooks[event].push(...entries);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
fs14.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
3941
|
+
`);
|
|
3942
|
+
}
|
|
3943
|
+
const manifest = {
|
|
3944
|
+
name: `kody-synth-${profile.name}`,
|
|
3945
|
+
version: "1.0.0",
|
|
3946
|
+
description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
|
|
3947
|
+
};
|
|
3948
|
+
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
3949
|
+
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
3950
|
+
fs14.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
3951
|
+
`);
|
|
3952
|
+
ctx.data.syntheticPluginPath = root;
|
|
3953
|
+
};
|
|
3954
|
+
function copyDir(src, dst) {
|
|
3955
|
+
fs14.mkdirSync(dst, { recursive: true });
|
|
3956
|
+
for (const ent of fs14.readdirSync(src, { withFileTypes: true })) {
|
|
3957
|
+
const s = path13.join(src, ent.name);
|
|
3958
|
+
const d = path13.join(dst, ent.name);
|
|
3959
|
+
if (ent.isDirectory()) copyDir(s, d);
|
|
3960
|
+
else if (ent.isFile()) fs14.copyFileSync(s, d);
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
// src/subagents.ts
|
|
3965
|
+
function splitFrontmatter(raw) {
|
|
3966
|
+
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
3967
|
+
if (!match) return { fm: {}, body: raw.trim() };
|
|
3968
|
+
const fm = {};
|
|
3969
|
+
for (const line of match[1].split("\n")) {
|
|
3970
|
+
const idx = line.indexOf(":");
|
|
3971
|
+
if (idx === -1) continue;
|
|
3972
|
+
fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
3973
|
+
}
|
|
3974
|
+
return { fm, body: (match[2] ?? "").trim() };
|
|
3975
|
+
}
|
|
3976
|
+
function resolveAgentFile(profileDir, name) {
|
|
3977
|
+
const local = path14.join(profileDir, "agents", `${name}.md`);
|
|
3978
|
+
if (fs15.existsSync(local)) return local;
|
|
3979
|
+
const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
3980
|
+
if (fs15.existsSync(central)) return central;
|
|
3981
|
+
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
3982
|
+
}
|
|
3983
|
+
function captureSubagentTemplates(profile) {
|
|
3984
|
+
const names = profile.claudeCode.subagents;
|
|
3985
|
+
if (!names || names.length === 0) return {};
|
|
3986
|
+
const out = {};
|
|
3987
|
+
for (const name of names) {
|
|
3988
|
+
try {
|
|
3989
|
+
out[name] = fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
3990
|
+
} catch {
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
return out;
|
|
3994
|
+
}
|
|
3995
|
+
function loadSubagents(profile) {
|
|
3996
|
+
const names = profile.claudeCode.subagents;
|
|
3997
|
+
if (!names || names.length === 0) return void 0;
|
|
3998
|
+
const agents = {};
|
|
3999
|
+
for (const name of names) {
|
|
4000
|
+
const raw = profile.subagentTemplates?.[name] ?? fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
4001
|
+
const { fm, body } = splitFrontmatter(raw);
|
|
4002
|
+
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
4003
|
+
const def = {
|
|
4004
|
+
description: fm.description ?? `Subagent ${name}`,
|
|
4005
|
+
prompt: body
|
|
4006
|
+
};
|
|
4007
|
+
if (fm.tools) {
|
|
4008
|
+
const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
|
|
4009
|
+
if (tools.length > 0) def.tools = tools;
|
|
4010
|
+
}
|
|
4011
|
+
if (fm.model) def.model = fm.model;
|
|
4012
|
+
agents[fm.name || name] = def;
|
|
4013
|
+
}
|
|
4014
|
+
return agents;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
3870
4017
|
// src/profile.ts
|
|
3871
4018
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
3872
4019
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
@@ -3875,6 +4022,10 @@ var VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
|
|
|
3875
4022
|
var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
|
|
3876
4023
|
var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
3877
4024
|
"name",
|
|
4025
|
+
"staff",
|
|
4026
|
+
"every",
|
|
4027
|
+
"dutyTools",
|
|
4028
|
+
"mentions",
|
|
3878
4029
|
"describe",
|
|
3879
4030
|
"role",
|
|
3880
4031
|
"kind",
|
|
@@ -3898,12 +4049,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
|
3898
4049
|
"preloadContext"
|
|
3899
4050
|
]);
|
|
3900
4051
|
function loadProfile(profilePath) {
|
|
3901
|
-
if (!
|
|
4052
|
+
if (!fs16.existsSync(profilePath)) {
|
|
3902
4053
|
throw new ProfileError(profilePath, "file not found");
|
|
3903
4054
|
}
|
|
3904
4055
|
let raw;
|
|
3905
4056
|
try {
|
|
3906
|
-
raw = JSON.parse(
|
|
4057
|
+
raw = JSON.parse(fs16.readFileSync(profilePath, "utf-8"));
|
|
3907
4058
|
} catch (err) {
|
|
3908
4059
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
3909
4060
|
}
|
|
@@ -3914,7 +4065,7 @@ function loadProfile(profilePath) {
|
|
|
3914
4065
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
3915
4066
|
if (unknownKeys.length > 0) {
|
|
3916
4067
|
process.stderr.write(
|
|
3917
|
-
`[kody profile] ${
|
|
4068
|
+
`[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
3918
4069
|
`
|
|
3919
4070
|
);
|
|
3920
4071
|
}
|
|
@@ -3956,6 +4107,11 @@ function loadProfile(profilePath) {
|
|
|
3956
4107
|
describe: typeof r.describe === "string" ? r.describe : "",
|
|
3957
4108
|
// Optional persona to run as. Empty/blank string → undefined (no persona).
|
|
3958
4109
|
staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
|
|
4110
|
+
// Optional recurrence cadence (scheduled duty). Blank → undefined (on-demand).
|
|
4111
|
+
every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
|
|
4112
|
+
// Locked-toolbox palette + mentions (folder-duty successors to frontmatter).
|
|
4113
|
+
dutyTools: Array.isArray(r.dutyTools) ? r.dutyTools.map((t) => String(t).trim()).filter(Boolean) : void 0,
|
|
4114
|
+
mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : void 0,
|
|
3959
4115
|
role,
|
|
3960
4116
|
kind,
|
|
3961
4117
|
schedule: typeof r.schedule === "string" ? r.schedule : void 0,
|
|
@@ -3977,27 +4133,28 @@ function loadProfile(profilePath) {
|
|
|
3977
4133
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
3978
4134
|
// flip to true after end-to-end verification.
|
|
3979
4135
|
preloadContext: r.preloadContext === true,
|
|
3980
|
-
dir:
|
|
3981
|
-
promptTemplates: readPromptTemplates(
|
|
4136
|
+
dir: path15.dirname(profilePath),
|
|
4137
|
+
promptTemplates: readPromptTemplates(path15.dirname(profilePath))
|
|
3982
4138
|
};
|
|
3983
4139
|
if (lifecycle) {
|
|
3984
4140
|
applyLifecycle(profile, profilePath);
|
|
3985
4141
|
}
|
|
4142
|
+
profile.subagentTemplates = captureSubagentTemplates(profile);
|
|
3986
4143
|
return profile;
|
|
3987
4144
|
}
|
|
3988
4145
|
function readPromptTemplates(dir) {
|
|
3989
4146
|
const out = {};
|
|
3990
4147
|
const read = (p) => {
|
|
3991
4148
|
try {
|
|
3992
|
-
out[p] =
|
|
4149
|
+
out[p] = fs16.readFileSync(p, "utf-8");
|
|
3993
4150
|
} catch {
|
|
3994
4151
|
}
|
|
3995
4152
|
};
|
|
3996
|
-
read(
|
|
4153
|
+
read(path15.join(dir, "prompt.md"));
|
|
3997
4154
|
try {
|
|
3998
|
-
const promptsDir =
|
|
3999
|
-
for (const ent of
|
|
4000
|
-
if (ent.endsWith(".md")) read(
|
|
4155
|
+
const promptsDir = path15.join(dir, "prompts");
|
|
4156
|
+
for (const ent of fs16.readdirSync(promptsDir)) {
|
|
4157
|
+
if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
|
|
4001
4158
|
}
|
|
4002
4159
|
} catch {
|
|
4003
4160
|
}
|
|
@@ -4337,16 +4494,17 @@ function parseStateComment(body) {
|
|
|
4337
4494
|
flow: parsed.flow
|
|
4338
4495
|
};
|
|
4339
4496
|
}
|
|
4340
|
-
function reduce(state, executable, action, phase) {
|
|
4497
|
+
function reduce(state, executable, action, phase, staff) {
|
|
4341
4498
|
if (!action) return state;
|
|
4342
4499
|
const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
|
|
4343
4500
|
const newExecutables = {
|
|
4344
4501
|
...state.executables,
|
|
4345
4502
|
[executable]: { ...state.executables[executable] ?? { lastAction: null }, lastAction: action }
|
|
4346
4503
|
};
|
|
4504
|
+
const ranAsStaff = typeof staff === "string" && staff.length > 0 ? staff : void 0;
|
|
4347
4505
|
const newHistory = [
|
|
4348
4506
|
...state.history,
|
|
4349
|
-
{ timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action) }
|
|
4507
|
+
{ timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action), staff: ranAsStaff }
|
|
4350
4508
|
].slice(-HISTORY_MAX_ENTRIES);
|
|
4351
4509
|
return {
|
|
4352
4510
|
schemaVersion: 1,
|
|
@@ -4355,6 +4513,7 @@ function reduce(state, executable, action, phase) {
|
|
|
4355
4513
|
attempts: newAttempts,
|
|
4356
4514
|
lastOutcome: action,
|
|
4357
4515
|
currentExecutable: executable,
|
|
4516
|
+
ranAsStaff: ranAsStaff ?? null,
|
|
4358
4517
|
status: statusFromAction(action),
|
|
4359
4518
|
phase: phaseFromAction(action, phase)
|
|
4360
4519
|
},
|
|
@@ -4391,6 +4550,9 @@ function renderStateComment(state) {
|
|
|
4391
4550
|
if (state.core.currentExecutable) {
|
|
4392
4551
|
lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
|
|
4393
4552
|
}
|
|
4553
|
+
if (state.core.ranAsStaff) {
|
|
4554
|
+
lines.push(`- **Ran as:** \`${state.core.ranAsStaff}\``);
|
|
4555
|
+
}
|
|
4394
4556
|
if (state.core.lastOutcome) {
|
|
4395
4557
|
lines.push(`- **Last action:** \`${state.core.lastOutcome.type}\``);
|
|
4396
4558
|
}
|
|
@@ -4473,7 +4635,7 @@ var CONTAINER_MAX_ITERATIONS = 50;
|
|
|
4473
4635
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
4474
4636
|
try {
|
|
4475
4637
|
const profilePath = resolveProfilePath(profileName);
|
|
4476
|
-
if (!
|
|
4638
|
+
if (!fs19.existsSync(profilePath)) return null;
|
|
4477
4639
|
return loadProfile(profilePath).inputs;
|
|
4478
4640
|
} catch {
|
|
4479
4641
|
return null;
|
|
@@ -4917,9 +5079,9 @@ function errMsg(err) {
|
|
|
4917
5079
|
|
|
4918
5080
|
// src/litellm.ts
|
|
4919
5081
|
import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
|
|
4920
|
-
import * as
|
|
4921
|
-
import * as
|
|
4922
|
-
import * as
|
|
5082
|
+
import * as fs20 from "fs";
|
|
5083
|
+
import * as os3 from "os";
|
|
5084
|
+
import * as path18 from "path";
|
|
4923
5085
|
async function checkLitellmHealth(url) {
|
|
4924
5086
|
try {
|
|
4925
5087
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -4971,13 +5133,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4971
5133
|
let child;
|
|
4972
5134
|
let logPath;
|
|
4973
5135
|
const spawnProxy = () => {
|
|
4974
|
-
const configPath =
|
|
4975
|
-
|
|
5136
|
+
const configPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
5137
|
+
fs20.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
4976
5138
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
4977
|
-
const nextLogPath =
|
|
4978
|
-
const outFd =
|
|
5139
|
+
const nextLogPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
5140
|
+
const outFd = fs20.openSync(nextLogPath, "w");
|
|
4979
5141
|
child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
4980
|
-
|
|
5142
|
+
fs20.closeSync(outFd);
|
|
4981
5143
|
logPath = nextLogPath;
|
|
4982
5144
|
};
|
|
4983
5145
|
const waitForHealth = async () => {
|
|
@@ -4991,7 +5153,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4991
5153
|
const readLogTail = () => {
|
|
4992
5154
|
if (!logPath) return "";
|
|
4993
5155
|
try {
|
|
4994
|
-
return
|
|
5156
|
+
return fs20.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
4995
5157
|
} catch {
|
|
4996
5158
|
return "";
|
|
4997
5159
|
}
|
|
@@ -5043,10 +5205,10 @@ ${tail}`
|
|
|
5043
5205
|
return { url, kill: killChild, isHealthy, ensureHealthy };
|
|
5044
5206
|
}
|
|
5045
5207
|
function readDotenvApiKeys(projectDir) {
|
|
5046
|
-
const dotenvPath =
|
|
5047
|
-
if (!
|
|
5208
|
+
const dotenvPath = path18.join(projectDir, ".env");
|
|
5209
|
+
if (!fs20.existsSync(dotenvPath)) return {};
|
|
5048
5210
|
const result = {};
|
|
5049
|
-
for (const rawLine of
|
|
5211
|
+
for (const rawLine of fs20.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
5050
5212
|
const line = rawLine.trim();
|
|
5051
5213
|
if (!line || line.startsWith("#")) continue;
|
|
5052
5214
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -5148,8 +5310,8 @@ function pushWithRetry(opts = {}) {
|
|
|
5148
5310
|
}
|
|
5149
5311
|
|
|
5150
5312
|
// src/commit.ts
|
|
5151
|
-
import * as
|
|
5152
|
-
import * as
|
|
5313
|
+
import * as fs21 from "fs";
|
|
5314
|
+
import * as path19 from "path";
|
|
5153
5315
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
5154
5316
|
".kody/",
|
|
5155
5317
|
".kody-engine/",
|
|
@@ -5210,18 +5372,18 @@ function tryGit(args, cwd) {
|
|
|
5210
5372
|
}
|
|
5211
5373
|
function abortUnfinishedGitOps(cwd) {
|
|
5212
5374
|
const aborted = [];
|
|
5213
|
-
const gitDir =
|
|
5214
|
-
if (!
|
|
5215
|
-
if (
|
|
5375
|
+
const gitDir = path19.join(cwd ?? process.cwd(), ".git");
|
|
5376
|
+
if (!fs21.existsSync(gitDir)) return aborted;
|
|
5377
|
+
if (fs21.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
|
|
5216
5378
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
5217
5379
|
}
|
|
5218
|
-
if (
|
|
5380
|
+
if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
5219
5381
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
5220
5382
|
}
|
|
5221
|
-
if (
|
|
5383
|
+
if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
|
|
5222
5384
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
5223
5385
|
}
|
|
5224
|
-
if (
|
|
5386
|
+
if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
|
|
5225
5387
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
5226
5388
|
}
|
|
5227
5389
|
try {
|
|
@@ -5277,7 +5439,7 @@ function normalizeCommitMessage(raw) {
|
|
|
5277
5439
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
5278
5440
|
const allChanged = listChangedFiles(cwd);
|
|
5279
5441
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
5280
|
-
const mergeHeadExists =
|
|
5442
|
+
const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
5281
5443
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
5282
5444
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
5283
5445
|
}
|
|
@@ -5368,7 +5530,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
5368
5530
|
const action = ctx.data.action;
|
|
5369
5531
|
let nextIssueState = issueState;
|
|
5370
5532
|
if (targetType === "pr" && action) {
|
|
5371
|
-
nextIssueState = reduce(issueState, profile.name, action, profile.phase);
|
|
5533
|
+
nextIssueState = reduce(issueState, profile.name, action, profile.phase, profile.staff);
|
|
5372
5534
|
if (state?.core.prUrl && !nextIssueState.core.prUrl) nextIssueState.core.prUrl = state.core.prUrl;
|
|
5373
5535
|
}
|
|
5374
5536
|
const prevHops = issueState.flow?.hops ?? flow.hops ?? 0;
|
|
@@ -5409,7 +5571,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
5409
5571
|
|
|
5410
5572
|
// src/gha.ts
|
|
5411
5573
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
5412
|
-
import * as
|
|
5574
|
+
import * as fs22 from "fs";
|
|
5413
5575
|
function getRunUrl() {
|
|
5414
5576
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5415
5577
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5420,10 +5582,10 @@ function getRunUrl() {
|
|
|
5420
5582
|
function reactToTriggerComment(cwd) {
|
|
5421
5583
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5422
5584
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5423
|
-
if (!eventPath || !
|
|
5585
|
+
if (!eventPath || !fs22.existsSync(eventPath)) return;
|
|
5424
5586
|
let event = null;
|
|
5425
5587
|
try {
|
|
5426
|
-
event = JSON.parse(
|
|
5588
|
+
event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
|
|
5427
5589
|
} catch {
|
|
5428
5590
|
return;
|
|
5429
5591
|
}
|
|
@@ -5575,22 +5737,22 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
|
|
|
5575
5737
|
};
|
|
5576
5738
|
|
|
5577
5739
|
// src/scripts/brainServe.ts
|
|
5578
|
-
import * as
|
|
5740
|
+
import * as fs24 from "fs";
|
|
5579
5741
|
import { createServer } from "http";
|
|
5580
|
-
import * as
|
|
5742
|
+
import * as path21 from "path";
|
|
5581
5743
|
init_repoWorkspace();
|
|
5582
5744
|
|
|
5583
5745
|
// src/scripts/brainTurnLog.ts
|
|
5584
|
-
import * as
|
|
5585
|
-
import * as
|
|
5746
|
+
import * as fs23 from "fs";
|
|
5747
|
+
import * as path20 from "path";
|
|
5586
5748
|
var live = /* @__PURE__ */ new Map();
|
|
5587
5749
|
function eventsPath(dir, chatId) {
|
|
5588
|
-
return
|
|
5750
|
+
return path20.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
5589
5751
|
}
|
|
5590
5752
|
function lastPersistedSeq(dir, chatId) {
|
|
5591
5753
|
const p = eventsPath(dir, chatId);
|
|
5592
|
-
if (!
|
|
5593
|
-
const lines =
|
|
5754
|
+
if (!fs23.existsSync(p)) return 0;
|
|
5755
|
+
const lines = fs23.readFileSync(p, "utf-8").split("\n").filter(Boolean);
|
|
5594
5756
|
if (lines.length === 0) return 0;
|
|
5595
5757
|
try {
|
|
5596
5758
|
return JSON.parse(lines[lines.length - 1]).seq || 0;
|
|
@@ -5600,9 +5762,9 @@ function lastPersistedSeq(dir, chatId) {
|
|
|
5600
5762
|
}
|
|
5601
5763
|
function readSince(dir, chatId, since) {
|
|
5602
5764
|
const p = eventsPath(dir, chatId);
|
|
5603
|
-
if (!
|
|
5765
|
+
if (!fs23.existsSync(p)) return [];
|
|
5604
5766
|
const out = [];
|
|
5605
|
-
for (const line of
|
|
5767
|
+
for (const line of fs23.readFileSync(p, "utf-8").split("\n")) {
|
|
5606
5768
|
if (!line) continue;
|
|
5607
5769
|
try {
|
|
5608
5770
|
const rec = JSON.parse(line);
|
|
@@ -5628,12 +5790,12 @@ function beginTurn(dir, chatId) {
|
|
|
5628
5790
|
};
|
|
5629
5791
|
live.set(chatId, state);
|
|
5630
5792
|
const p = eventsPath(dir, chatId);
|
|
5631
|
-
|
|
5793
|
+
fs23.mkdirSync(path20.dirname(p), { recursive: true });
|
|
5632
5794
|
return (event) => {
|
|
5633
5795
|
state.seq += 1;
|
|
5634
5796
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
5635
5797
|
try {
|
|
5636
|
-
|
|
5798
|
+
fs23.appendFileSync(p, `${JSON.stringify(rec)}
|
|
5637
5799
|
`);
|
|
5638
5800
|
} catch (err) {
|
|
5639
5801
|
process.stderr.write(
|
|
@@ -5672,7 +5834,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
|
|
|
5672
5834
|
event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
|
|
5673
5835
|
};
|
|
5674
5836
|
try {
|
|
5675
|
-
|
|
5837
|
+
fs23.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
|
|
5676
5838
|
`);
|
|
5677
5839
|
} catch {
|
|
5678
5840
|
}
|
|
@@ -5903,7 +6065,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
5903
6065
|
const repo = strField(body, "repo");
|
|
5904
6066
|
const repoToken = strField(body, "repoToken");
|
|
5905
6067
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
5906
|
-
|
|
6068
|
+
fs24.mkdirSync(path21.dirname(sessionFile), { recursive: true });
|
|
5907
6069
|
appendTurn(sessionFile, {
|
|
5908
6070
|
role: "user",
|
|
5909
6071
|
content: message,
|
|
@@ -5950,7 +6112,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
5950
6112
|
function buildServer(opts) {
|
|
5951
6113
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
5952
6114
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
5953
|
-
const reposRoot = opts.reposRoot ??
|
|
6115
|
+
const reposRoot = opts.reposRoot ?? path21.join(path21.dirname(path21.resolve(opts.cwd)), "repos");
|
|
5954
6116
|
return createServer(async (req, res) => {
|
|
5955
6117
|
if (!req.method || !req.url) {
|
|
5956
6118
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -6051,93 +6213,6 @@ var brainServe = async (ctx) => {
|
|
|
6051
6213
|
});
|
|
6052
6214
|
};
|
|
6053
6215
|
|
|
6054
|
-
// src/scripts/buildSyntheticPlugin.ts
|
|
6055
|
-
import * as fs23 from "fs";
|
|
6056
|
-
import * as os3 from "os";
|
|
6057
|
-
import * as path20 from "path";
|
|
6058
|
-
function getPluginsCatalogRoot() {
|
|
6059
|
-
const here = path20.dirname(new URL(import.meta.url).pathname);
|
|
6060
|
-
const candidates = [
|
|
6061
|
-
path20.join(here, "..", "plugins"),
|
|
6062
|
-
// dev: src/scripts → src/plugins
|
|
6063
|
-
path20.join(here, "..", "..", "plugins"),
|
|
6064
|
-
// built: dist/scripts → dist/plugins
|
|
6065
|
-
path20.join(here, "..", "..", "src", "plugins")
|
|
6066
|
-
// fallback
|
|
6067
|
-
];
|
|
6068
|
-
for (const c of candidates) {
|
|
6069
|
-
if (fs23.existsSync(c) && fs23.statSync(c).isDirectory()) return c;
|
|
6070
|
-
}
|
|
6071
|
-
return candidates[0];
|
|
6072
|
-
}
|
|
6073
|
-
var buildSyntheticPlugin = async (ctx, profile) => {
|
|
6074
|
-
const cc = profile.claudeCode;
|
|
6075
|
-
const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
|
|
6076
|
-
if (!needsSynthetic) return;
|
|
6077
|
-
const catalog = getPluginsCatalogRoot();
|
|
6078
|
-
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6079
|
-
const root = path20.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
6080
|
-
fs23.mkdirSync(path20.join(root, ".claude-plugin"), { recursive: true });
|
|
6081
|
-
const resolvePart = (bucket, entry) => {
|
|
6082
|
-
const local = path20.join(profile.dir, bucket, entry);
|
|
6083
|
-
if (fs23.existsSync(local)) return local;
|
|
6084
|
-
const central = path20.join(catalog, bucket, entry);
|
|
6085
|
-
if (fs23.existsSync(central)) return central;
|
|
6086
|
-
throw new Error(
|
|
6087
|
-
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
6088
|
-
);
|
|
6089
|
-
};
|
|
6090
|
-
if (cc.skills.length > 0) {
|
|
6091
|
-
const dst = path20.join(root, "skills");
|
|
6092
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6093
|
-
for (const name of cc.skills) {
|
|
6094
|
-
copyDir(resolvePart("skills", name), path20.join(dst, name));
|
|
6095
|
-
}
|
|
6096
|
-
}
|
|
6097
|
-
if (cc.commands.length > 0) {
|
|
6098
|
-
const dst = path20.join(root, "commands");
|
|
6099
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6100
|
-
for (const name of cc.commands) {
|
|
6101
|
-
fs23.copyFileSync(resolvePart("commands", `${name}.md`), path20.join(dst, `${name}.md`));
|
|
6102
|
-
}
|
|
6103
|
-
}
|
|
6104
|
-
if (cc.hooks.length > 0) {
|
|
6105
|
-
const dst = path20.join(root, "hooks");
|
|
6106
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6107
|
-
const merged = { hooks: {} };
|
|
6108
|
-
for (const name of cc.hooks) {
|
|
6109
|
-
const src = resolvePart("hooks", `${name}.json`);
|
|
6110
|
-
const parsed = JSON.parse(fs23.readFileSync(src, "utf-8"));
|
|
6111
|
-
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
6112
|
-
if (!Array.isArray(entries)) continue;
|
|
6113
|
-
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
6114
|
-
merged.hooks[event].push(...entries);
|
|
6115
|
-
}
|
|
6116
|
-
}
|
|
6117
|
-
fs23.writeFileSync(path20.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
6118
|
-
`);
|
|
6119
|
-
}
|
|
6120
|
-
const manifest = {
|
|
6121
|
-
name: `kody-synth-${profile.name}`,
|
|
6122
|
-
version: "1.0.0",
|
|
6123
|
-
description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
|
|
6124
|
-
};
|
|
6125
|
-
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
6126
|
-
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
6127
|
-
fs23.writeFileSync(path20.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
6128
|
-
`);
|
|
6129
|
-
ctx.data.syntheticPluginPath = root;
|
|
6130
|
-
};
|
|
6131
|
-
function copyDir(src, dst) {
|
|
6132
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6133
|
-
for (const ent of fs23.readdirSync(src, { withFileTypes: true })) {
|
|
6134
|
-
const s = path20.join(src, ent.name);
|
|
6135
|
-
const d = path20.join(dst, ent.name);
|
|
6136
|
-
if (ent.isDirectory()) copyDir(s, d);
|
|
6137
|
-
else if (ent.isFile()) fs23.copyFileSync(s, d);
|
|
6138
|
-
}
|
|
6139
|
-
}
|
|
6140
|
-
|
|
6141
6216
|
// src/coverage.ts
|
|
6142
6217
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
6143
6218
|
function patternToRegex(pattern) {
|
|
@@ -6286,13 +6361,13 @@ function defaultLabelMap() {
|
|
|
6286
6361
|
}
|
|
6287
6362
|
|
|
6288
6363
|
// src/scripts/commitAndPush.ts
|
|
6289
|
-
import * as
|
|
6290
|
-
import * as
|
|
6364
|
+
import * as fs25 from "fs";
|
|
6365
|
+
import * as path22 from "path";
|
|
6291
6366
|
init_events();
|
|
6292
6367
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
6293
6368
|
function sentinelPathForStage(cwd, profileName) {
|
|
6294
6369
|
const runId = resolveRunId();
|
|
6295
|
-
return
|
|
6370
|
+
return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
6296
6371
|
}
|
|
6297
6372
|
var commitAndPush2 = async (ctx, profile) => {
|
|
6298
6373
|
const branch = ctx.data.branch;
|
|
@@ -6302,9 +6377,9 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6302
6377
|
}
|
|
6303
6378
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
6304
6379
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
6305
|
-
if (sentinel &&
|
|
6380
|
+
if (sentinel && fs25.existsSync(sentinel)) {
|
|
6306
6381
|
try {
|
|
6307
|
-
const replay = JSON.parse(
|
|
6382
|
+
const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
|
|
6308
6383
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
6309
6384
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
6310
6385
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -6357,8 +6432,8 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6357
6432
|
const result = ctx.data.commitResult;
|
|
6358
6433
|
if (sentinel && result?.committed) {
|
|
6359
6434
|
try {
|
|
6360
|
-
|
|
6361
|
-
|
|
6435
|
+
fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
|
|
6436
|
+
fs25.writeFileSync(
|
|
6362
6437
|
sentinel,
|
|
6363
6438
|
JSON.stringify(
|
|
6364
6439
|
{
|
|
@@ -6381,8 +6456,8 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6381
6456
|
init_issue();
|
|
6382
6457
|
|
|
6383
6458
|
// src/goal/state.ts
|
|
6384
|
-
import * as
|
|
6385
|
-
import * as
|
|
6459
|
+
import * as fs26 from "fs";
|
|
6460
|
+
import * as path23 from "path";
|
|
6386
6461
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
6387
6462
|
var GoalStateError = class extends Error {
|
|
6388
6463
|
constructor(path42, message) {
|
|
@@ -6528,16 +6603,16 @@ function describeCommitMessage(goal) {
|
|
|
6528
6603
|
}
|
|
6529
6604
|
|
|
6530
6605
|
// src/scripts/composePrompt.ts
|
|
6531
|
-
import * as
|
|
6532
|
-
import * as
|
|
6606
|
+
import * as fs27 from "fs";
|
|
6607
|
+
import * as path24 from "path";
|
|
6533
6608
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
6534
6609
|
var composePrompt = async (ctx, profile) => {
|
|
6535
6610
|
const explicit = ctx.data.promptTemplate;
|
|
6536
6611
|
const mode = ctx.args.mode;
|
|
6537
6612
|
const candidates = [
|
|
6538
|
-
explicit ?
|
|
6539
|
-
mode ?
|
|
6540
|
-
|
|
6613
|
+
explicit ? path24.join(profile.dir, explicit) : null,
|
|
6614
|
+
mode ? path24.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
6615
|
+
path24.join(profile.dir, "prompt.md")
|
|
6541
6616
|
].filter(Boolean);
|
|
6542
6617
|
let templatePath = "";
|
|
6543
6618
|
let template = "";
|
|
@@ -6550,7 +6625,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
6550
6625
|
break;
|
|
6551
6626
|
}
|
|
6552
6627
|
try {
|
|
6553
|
-
template =
|
|
6628
|
+
template = fs27.readFileSync(c, "utf-8");
|
|
6554
6629
|
templatePath = c;
|
|
6555
6630
|
break;
|
|
6556
6631
|
} catch (err) {
|
|
@@ -6561,7 +6636,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
6561
6636
|
if (!templatePath) {
|
|
6562
6637
|
let dirState;
|
|
6563
6638
|
try {
|
|
6564
|
-
dirState = `dir contents: [${
|
|
6639
|
+
dirState = `dir contents: [${fs27.readdirSync(profile.dir).join(", ")}]`;
|
|
6565
6640
|
} catch (err) {
|
|
6566
6641
|
dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
|
|
6567
6642
|
}
|
|
@@ -7386,15 +7461,15 @@ var deriveQaScopeFromIssue = async (ctx) => {
|
|
|
7386
7461
|
|
|
7387
7462
|
// src/scripts/diagMcp.ts
|
|
7388
7463
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
7389
|
-
import * as
|
|
7464
|
+
import * as fs28 from "fs";
|
|
7390
7465
|
import * as os4 from "os";
|
|
7391
|
-
import * as
|
|
7466
|
+
import * as path25 from "path";
|
|
7392
7467
|
var diagMcp = async (_ctx) => {
|
|
7393
7468
|
const home = os4.homedir();
|
|
7394
|
-
const cacheDir =
|
|
7469
|
+
const cacheDir = path25.join(home, ".cache", "ms-playwright");
|
|
7395
7470
|
let entries = [];
|
|
7396
7471
|
try {
|
|
7397
|
-
entries =
|
|
7472
|
+
entries = fs28.readdirSync(cacheDir);
|
|
7398
7473
|
} catch {
|
|
7399
7474
|
}
|
|
7400
7475
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -7420,17 +7495,17 @@ var diagMcp = async (_ctx) => {
|
|
|
7420
7495
|
};
|
|
7421
7496
|
|
|
7422
7497
|
// src/scripts/discoverQaContext.ts
|
|
7423
|
-
import * as
|
|
7424
|
-
import * as
|
|
7498
|
+
import * as fs30 from "fs";
|
|
7499
|
+
import * as path27 from "path";
|
|
7425
7500
|
|
|
7426
7501
|
// src/scripts/frameworkDetectors.ts
|
|
7427
|
-
import * as
|
|
7428
|
-
import * as
|
|
7502
|
+
import * as fs29 from "fs";
|
|
7503
|
+
import * as path26 from "path";
|
|
7429
7504
|
function detectFrameworks(cwd) {
|
|
7430
7505
|
const out = [];
|
|
7431
7506
|
let deps = {};
|
|
7432
7507
|
try {
|
|
7433
|
-
const pkg = JSON.parse(
|
|
7508
|
+
const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
|
|
7434
7509
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7435
7510
|
} catch {
|
|
7436
7511
|
return out;
|
|
@@ -7467,7 +7542,7 @@ function detectFrameworks(cwd) {
|
|
|
7467
7542
|
}
|
|
7468
7543
|
function findFile(cwd, candidates) {
|
|
7469
7544
|
for (const c of candidates) {
|
|
7470
|
-
if (
|
|
7545
|
+
if (fs29.existsSync(path26.join(cwd, c))) return c;
|
|
7471
7546
|
}
|
|
7472
7547
|
return null;
|
|
7473
7548
|
}
|
|
@@ -7480,18 +7555,18 @@ var COLLECTION_DIRS = [
|
|
|
7480
7555
|
function discoverPayloadCollections(cwd) {
|
|
7481
7556
|
const out = [];
|
|
7482
7557
|
for (const dir of COLLECTION_DIRS) {
|
|
7483
|
-
const full =
|
|
7484
|
-
if (!
|
|
7558
|
+
const full = path26.join(cwd, dir);
|
|
7559
|
+
if (!fs29.existsSync(full)) continue;
|
|
7485
7560
|
let files;
|
|
7486
7561
|
try {
|
|
7487
|
-
files =
|
|
7562
|
+
files = fs29.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7488
7563
|
} catch {
|
|
7489
7564
|
continue;
|
|
7490
7565
|
}
|
|
7491
7566
|
for (const file of files) {
|
|
7492
7567
|
try {
|
|
7493
|
-
const filePath =
|
|
7494
|
-
const content =
|
|
7568
|
+
const filePath = path26.join(full, file);
|
|
7569
|
+
const content = fs29.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
7495
7570
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
7496
7571
|
if (!slugMatch) continue;
|
|
7497
7572
|
const slug = slugMatch[1];
|
|
@@ -7505,7 +7580,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
7505
7580
|
out.push({
|
|
7506
7581
|
name,
|
|
7507
7582
|
slug,
|
|
7508
|
-
filePath:
|
|
7583
|
+
filePath: path26.relative(cwd, filePath),
|
|
7509
7584
|
fields: fields.slice(0, 20),
|
|
7510
7585
|
hasAdmin
|
|
7511
7586
|
});
|
|
@@ -7519,28 +7594,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
7519
7594
|
function discoverAdminComponents(cwd, collections) {
|
|
7520
7595
|
const out = [];
|
|
7521
7596
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
7522
|
-
const full =
|
|
7523
|
-
if (!
|
|
7597
|
+
const full = path26.join(cwd, dir);
|
|
7598
|
+
if (!fs29.existsSync(full)) continue;
|
|
7524
7599
|
let entries;
|
|
7525
7600
|
try {
|
|
7526
|
-
entries =
|
|
7601
|
+
entries = fs29.readdirSync(full, { withFileTypes: true });
|
|
7527
7602
|
} catch {
|
|
7528
7603
|
continue;
|
|
7529
7604
|
}
|
|
7530
7605
|
for (const entry of entries) {
|
|
7531
|
-
const entryPath =
|
|
7606
|
+
const entryPath = path26.join(full, entry.name);
|
|
7532
7607
|
let name;
|
|
7533
7608
|
let filePath;
|
|
7534
7609
|
if (entry.isDirectory()) {
|
|
7535
7610
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
7536
|
-
(f) =>
|
|
7611
|
+
(f) => fs29.existsSync(path26.join(entryPath, f))
|
|
7537
7612
|
);
|
|
7538
7613
|
if (!indexFile) continue;
|
|
7539
7614
|
name = entry.name;
|
|
7540
|
-
filePath =
|
|
7615
|
+
filePath = path26.relative(cwd, path26.join(entryPath, indexFile));
|
|
7541
7616
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
7542
7617
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
7543
|
-
filePath =
|
|
7618
|
+
filePath = path26.relative(cwd, entryPath);
|
|
7544
7619
|
} else {
|
|
7545
7620
|
continue;
|
|
7546
7621
|
}
|
|
@@ -7548,7 +7623,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
7548
7623
|
if (collections) {
|
|
7549
7624
|
for (const col of collections) {
|
|
7550
7625
|
try {
|
|
7551
|
-
const colContent =
|
|
7626
|
+
const colContent = fs29.readFileSync(path26.join(cwd, col.filePath), "utf-8");
|
|
7552
7627
|
if (colContent.includes(name)) {
|
|
7553
7628
|
usedInCollection = col.slug;
|
|
7554
7629
|
break;
|
|
@@ -7567,8 +7642,8 @@ function scanApiRoutes(cwd) {
|
|
|
7567
7642
|
const out = [];
|
|
7568
7643
|
const appDirs = ["src/app", "app"];
|
|
7569
7644
|
for (const appDir of appDirs) {
|
|
7570
|
-
const apiDir =
|
|
7571
|
-
if (!
|
|
7645
|
+
const apiDir = path26.join(cwd, appDir, "api");
|
|
7646
|
+
if (!fs29.existsSync(apiDir)) continue;
|
|
7572
7647
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
7573
7648
|
break;
|
|
7574
7649
|
}
|
|
@@ -7577,14 +7652,14 @@ function scanApiRoutes(cwd) {
|
|
|
7577
7652
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
7578
7653
|
let entries;
|
|
7579
7654
|
try {
|
|
7580
|
-
entries =
|
|
7655
|
+
entries = fs29.readdirSync(dir, { withFileTypes: true });
|
|
7581
7656
|
} catch {
|
|
7582
7657
|
return;
|
|
7583
7658
|
}
|
|
7584
7659
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
7585
7660
|
if (routeFile) {
|
|
7586
7661
|
try {
|
|
7587
|
-
const content =
|
|
7662
|
+
const content = fs29.readFileSync(path26.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
7588
7663
|
const methods = HTTP_METHODS.filter(
|
|
7589
7664
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
7590
7665
|
);
|
|
@@ -7592,7 +7667,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7592
7667
|
out.push({
|
|
7593
7668
|
path: prefix,
|
|
7594
7669
|
methods,
|
|
7595
|
-
filePath:
|
|
7670
|
+
filePath: path26.relative(cwd, path26.join(dir, routeFile.name))
|
|
7596
7671
|
});
|
|
7597
7672
|
}
|
|
7598
7673
|
} catch {
|
|
@@ -7603,7 +7678,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7603
7678
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7604
7679
|
let segment = entry.name;
|
|
7605
7680
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7606
|
-
walkApiRoutes(
|
|
7681
|
+
walkApiRoutes(path26.join(dir, entry.name), prefix, cwd, out);
|
|
7607
7682
|
continue;
|
|
7608
7683
|
}
|
|
7609
7684
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7611,7 +7686,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7611
7686
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7612
7687
|
segment = `:${segment.slice(1, -1)}`;
|
|
7613
7688
|
}
|
|
7614
|
-
walkApiRoutes(
|
|
7689
|
+
walkApiRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
7615
7690
|
}
|
|
7616
7691
|
}
|
|
7617
7692
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -7631,10 +7706,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
7631
7706
|
function scanEnvVars(cwd) {
|
|
7632
7707
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
7633
7708
|
for (const envFile of candidates) {
|
|
7634
|
-
const envPath =
|
|
7635
|
-
if (!
|
|
7709
|
+
const envPath = path26.join(cwd, envFile);
|
|
7710
|
+
if (!fs29.existsSync(envPath)) continue;
|
|
7636
7711
|
try {
|
|
7637
|
-
const content =
|
|
7712
|
+
const content = fs29.readFileSync(envPath, "utf-8");
|
|
7638
7713
|
const vars = [];
|
|
7639
7714
|
for (const line of content.split("\n")) {
|
|
7640
7715
|
const trimmed = line.trim();
|
|
@@ -7682,9 +7757,9 @@ function runQaDiscovery(cwd) {
|
|
|
7682
7757
|
}
|
|
7683
7758
|
function detectDevServer(cwd, out) {
|
|
7684
7759
|
try {
|
|
7685
|
-
const pkg = JSON.parse(
|
|
7760
|
+
const pkg = JSON.parse(fs30.readFileSync(path27.join(cwd, "package.json"), "utf-8"));
|
|
7686
7761
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7687
|
-
const pm =
|
|
7762
|
+
const pm = fs30.existsSync(path27.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs30.existsSync(path27.join(cwd, "yarn.lock")) ? "yarn" : fs30.existsSync(path27.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
7688
7763
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
7689
7764
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
7690
7765
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -7694,8 +7769,8 @@ function detectDevServer(cwd, out) {
|
|
|
7694
7769
|
function scanFrontendRoutes(cwd, out) {
|
|
7695
7770
|
const appDirs = ["src/app", "app"];
|
|
7696
7771
|
for (const appDir of appDirs) {
|
|
7697
|
-
const full =
|
|
7698
|
-
if (!
|
|
7772
|
+
const full = path27.join(cwd, appDir);
|
|
7773
|
+
if (!fs30.existsSync(full)) continue;
|
|
7699
7774
|
walkFrontendRoutes(full, "", out);
|
|
7700
7775
|
break;
|
|
7701
7776
|
}
|
|
@@ -7703,7 +7778,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
7703
7778
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
7704
7779
|
let entries;
|
|
7705
7780
|
try {
|
|
7706
|
-
entries =
|
|
7781
|
+
entries = fs30.readdirSync(dir, { withFileTypes: true });
|
|
7707
7782
|
} catch {
|
|
7708
7783
|
return;
|
|
7709
7784
|
}
|
|
@@ -7720,7 +7795,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7720
7795
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7721
7796
|
let segment = entry.name;
|
|
7722
7797
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7723
|
-
walkFrontendRoutes(
|
|
7798
|
+
walkFrontendRoutes(path27.join(dir, entry.name), prefix, out);
|
|
7724
7799
|
continue;
|
|
7725
7800
|
}
|
|
7726
7801
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7728,7 +7803,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7728
7803
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7729
7804
|
segment = `:${segment.slice(1, -1)}`;
|
|
7730
7805
|
}
|
|
7731
|
-
walkFrontendRoutes(
|
|
7806
|
+
walkFrontendRoutes(path27.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
7732
7807
|
}
|
|
7733
7808
|
}
|
|
7734
7809
|
function detectAuthFiles(cwd, out) {
|
|
@@ -7745,23 +7820,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
7745
7820
|
"src/app/api/oauth"
|
|
7746
7821
|
];
|
|
7747
7822
|
for (const c of candidates) {
|
|
7748
|
-
if (
|
|
7823
|
+
if (fs30.existsSync(path27.join(cwd, c))) out.authFiles.push(c);
|
|
7749
7824
|
}
|
|
7750
7825
|
}
|
|
7751
7826
|
function detectRoles(cwd, out) {
|
|
7752
7827
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
7753
7828
|
for (const rp of rolePaths) {
|
|
7754
|
-
const dir =
|
|
7755
|
-
if (!
|
|
7829
|
+
const dir = path27.join(cwd, rp);
|
|
7830
|
+
if (!fs30.existsSync(dir)) continue;
|
|
7756
7831
|
let files;
|
|
7757
7832
|
try {
|
|
7758
|
-
files =
|
|
7833
|
+
files = fs30.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7759
7834
|
} catch {
|
|
7760
7835
|
continue;
|
|
7761
7836
|
}
|
|
7762
7837
|
for (const f of files) {
|
|
7763
7838
|
try {
|
|
7764
|
-
const content =
|
|
7839
|
+
const content = fs30.readFileSync(path27.join(dir, f), "utf-8").slice(0, 5e3);
|
|
7765
7840
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
7766
7841
|
if (roleMatches) {
|
|
7767
7842
|
for (const m of roleMatches) {
|
|
@@ -7944,8 +8019,8 @@ ${stateBody}`;
|
|
|
7944
8019
|
};
|
|
7945
8020
|
|
|
7946
8021
|
// src/scripts/dispatchJobFileTicks.ts
|
|
7947
|
-
import * as
|
|
7948
|
-
import * as
|
|
8022
|
+
import * as fs32 from "fs";
|
|
8023
|
+
import * as path29 from "path";
|
|
7949
8024
|
|
|
7950
8025
|
// src/scripts/jobFrontmatter.ts
|
|
7951
8026
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -7961,7 +8036,7 @@ var SCHEDULE_EVERY_VALUES = [
|
|
|
7961
8036
|
"manual"
|
|
7962
8037
|
];
|
|
7963
8038
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
7964
|
-
function
|
|
8039
|
+
function splitFrontmatter2(raw) {
|
|
7965
8040
|
const match = FRONTMATTER_RE.exec(raw);
|
|
7966
8041
|
if (!match) return { frontmatter: {}, body: raw };
|
|
7967
8042
|
const inner = match[1] ?? "";
|
|
@@ -8238,8 +8313,8 @@ function isShaConflict(err) {
|
|
|
8238
8313
|
}
|
|
8239
8314
|
|
|
8240
8315
|
// src/scripts/jobState/localFileBackend.ts
|
|
8241
|
-
import * as
|
|
8242
|
-
import * as
|
|
8316
|
+
import * as fs31 from "fs";
|
|
8317
|
+
import * as path28 from "path";
|
|
8243
8318
|
var LocalFileBackend = class {
|
|
8244
8319
|
name = "local-file";
|
|
8245
8320
|
cwd;
|
|
@@ -8254,7 +8329,7 @@ var LocalFileBackend = class {
|
|
|
8254
8329
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
8255
8330
|
this.cwd = opts.cwd;
|
|
8256
8331
|
this.jobsDir = opts.jobsDir;
|
|
8257
|
-
this.absDir =
|
|
8332
|
+
this.absDir = path28.join(opts.cwd, opts.jobsDir);
|
|
8258
8333
|
this.owner = opts.owner;
|
|
8259
8334
|
this.repo = opts.repo;
|
|
8260
8335
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -8269,7 +8344,7 @@ var LocalFileBackend = class {
|
|
|
8269
8344
|
`);
|
|
8270
8345
|
return;
|
|
8271
8346
|
}
|
|
8272
|
-
|
|
8347
|
+
fs31.mkdirSync(this.absDir, { recursive: true });
|
|
8273
8348
|
const prefix = this.cacheKeyPrefix();
|
|
8274
8349
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
8275
8350
|
try {
|
|
@@ -8298,7 +8373,7 @@ var LocalFileBackend = class {
|
|
|
8298
8373
|
`);
|
|
8299
8374
|
return;
|
|
8300
8375
|
}
|
|
8301
|
-
if (!
|
|
8376
|
+
if (!fs31.existsSync(this.absDir)) {
|
|
8302
8377
|
return;
|
|
8303
8378
|
}
|
|
8304
8379
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -8314,11 +8389,11 @@ var LocalFileBackend = class {
|
|
|
8314
8389
|
}
|
|
8315
8390
|
load(slug) {
|
|
8316
8391
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
8317
|
-
const absPath =
|
|
8318
|
-
if (!
|
|
8392
|
+
const absPath = path28.join(this.cwd, relPath);
|
|
8393
|
+
if (!fs31.existsSync(absPath)) {
|
|
8319
8394
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
8320
8395
|
}
|
|
8321
|
-
const raw =
|
|
8396
|
+
const raw = fs31.readFileSync(absPath, "utf-8");
|
|
8322
8397
|
let parsed;
|
|
8323
8398
|
try {
|
|
8324
8399
|
parsed = JSON.parse(raw);
|
|
@@ -8335,13 +8410,13 @@ var LocalFileBackend = class {
|
|
|
8335
8410
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
8336
8411
|
return false;
|
|
8337
8412
|
}
|
|
8338
|
-
const absPath =
|
|
8339
|
-
|
|
8413
|
+
const absPath = path28.join(this.cwd, loaded.path);
|
|
8414
|
+
fs31.mkdirSync(path28.dirname(absPath), { recursive: true });
|
|
8340
8415
|
const body = `${JSON.stringify(next, null, 2)}
|
|
8341
8416
|
`;
|
|
8342
8417
|
const tmpPath = `${absPath}.${process.pid}.tmp`;
|
|
8343
|
-
|
|
8344
|
-
|
|
8418
|
+
fs31.writeFileSync(tmpPath, body, "utf-8");
|
|
8419
|
+
fs31.renameSync(tmpPath, absPath);
|
|
8345
8420
|
return true;
|
|
8346
8421
|
}
|
|
8347
8422
|
cacheKeyPrefix() {
|
|
@@ -8419,7 +8494,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
8419
8494
|
await backend.hydrate();
|
|
8420
8495
|
}
|
|
8421
8496
|
try {
|
|
8422
|
-
const slugs = listJobSlugs(
|
|
8497
|
+
const slugs = listJobSlugs(path29.join(ctx.cwd, jobsDir));
|
|
8423
8498
|
ctx.data.jobSlugCount = slugs.length;
|
|
8424
8499
|
if (slugs.length === 0) {
|
|
8425
8500
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -8430,6 +8505,55 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
8430
8505
|
`);
|
|
8431
8506
|
const results = [];
|
|
8432
8507
|
const now = Date.now();
|
|
8508
|
+
const scheduledDuties = listFolderDutySlugs(path29.join(ctx.cwd, jobsDir)).map((slug) => {
|
|
8509
|
+
try {
|
|
8510
|
+
const p = loadProfile(path29.join(ctx.cwd, jobsDir, slug, "profile.json"));
|
|
8511
|
+
return { slug, every: p.every, staff: p.staff };
|
|
8512
|
+
} catch (err) {
|
|
8513
|
+
process.stderr.write(`[jobs] \u23ED skip folder-duty ${slug}: profile load failed: ${String(err)}
|
|
8514
|
+
`);
|
|
8515
|
+
return null;
|
|
8516
|
+
}
|
|
8517
|
+
}).filter((d) => d !== null && Boolean(d.every));
|
|
8518
|
+
process.stdout.write(`[jobs] ${scheduledDuties.length} scheduled folder-dut(y/ies) to consider
|
|
8519
|
+
`);
|
|
8520
|
+
for (const { slug, every, staff } of scheduledDuties) {
|
|
8521
|
+
if (!staff || staff.trim().length === 0) {
|
|
8522
|
+
process.stderr.write(`[jobs] \u23ED skip ${slug}: scheduled duty has no staff
|
|
8523
|
+
`);
|
|
8524
|
+
results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
|
|
8525
|
+
continue;
|
|
8526
|
+
}
|
|
8527
|
+
const decision = await decideShouldFire(every, slug, backend, now);
|
|
8528
|
+
if (decision.skip) {
|
|
8529
|
+
process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
|
|
8530
|
+
`);
|
|
8531
|
+
results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
|
|
8532
|
+
continue;
|
|
8533
|
+
}
|
|
8534
|
+
await stampFired(backend, slug, now);
|
|
8535
|
+
process.stdout.write(`[jobs] \u2192 run scheduled duty ${slug} (one-shot, as ${staff})
|
|
8536
|
+
`);
|
|
8537
|
+
try {
|
|
8538
|
+
const out = await runExecutable(slug, {
|
|
8539
|
+
cliArgs: {},
|
|
8540
|
+
cwd: ctx.cwd,
|
|
8541
|
+
config: ctx.config,
|
|
8542
|
+
verbose: ctx.verbose,
|
|
8543
|
+
quiet: ctx.quiet
|
|
8544
|
+
});
|
|
8545
|
+
results.push({ slug, exitCode: out.exitCode, reason: out.reason });
|
|
8546
|
+
if (out.exitCode !== 0) {
|
|
8547
|
+
process.stderr.write(`[jobs] scheduled duty ${slug} failed (exit ${out.exitCode}): ${out.reason ?? ""}
|
|
8548
|
+
`);
|
|
8549
|
+
}
|
|
8550
|
+
} catch (err) {
|
|
8551
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8552
|
+
process.stderr.write(`[jobs] scheduled duty ${slug} crashed: ${msg}
|
|
8553
|
+
`);
|
|
8554
|
+
results.push({ slug, exitCode: 99, reason: msg });
|
|
8555
|
+
}
|
|
8556
|
+
}
|
|
8433
8557
|
for (const slug of slugs) {
|
|
8434
8558
|
const frontmatter = readJobFrontmatter(ctx.cwd, jobsDir, slug);
|
|
8435
8559
|
if (frontmatter.disabled === true) {
|
|
@@ -8530,22 +8654,42 @@ function formatAgo(ms) {
|
|
|
8530
8654
|
}
|
|
8531
8655
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
8532
8656
|
try {
|
|
8533
|
-
const raw =
|
|
8534
|
-
return
|
|
8657
|
+
const raw = fs32.readFileSync(path29.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
8658
|
+
return splitFrontmatter2(raw).frontmatter;
|
|
8535
8659
|
} catch {
|
|
8536
8660
|
return {};
|
|
8537
8661
|
}
|
|
8538
8662
|
}
|
|
8539
8663
|
function listJobSlugs(absDir) {
|
|
8540
|
-
if (!
|
|
8664
|
+
if (!fs32.existsSync(absDir)) return [];
|
|
8541
8665
|
let entries;
|
|
8542
8666
|
try {
|
|
8543
|
-
entries =
|
|
8667
|
+
entries = fs32.readdirSync(absDir, { withFileTypes: true });
|
|
8544
8668
|
} catch {
|
|
8545
8669
|
return [];
|
|
8546
8670
|
}
|
|
8547
8671
|
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();
|
|
8548
8672
|
}
|
|
8673
|
+
function listFolderDutySlugs(absDir) {
|
|
8674
|
+
if (!fs32.existsSync(absDir)) return [];
|
|
8675
|
+
let entries;
|
|
8676
|
+
try {
|
|
8677
|
+
entries = fs32.readdirSync(absDir, { withFileTypes: true });
|
|
8678
|
+
} catch {
|
|
8679
|
+
return [];
|
|
8680
|
+
}
|
|
8681
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => fs32.existsSync(path29.join(absDir, e.name, "profile.json"))).map((e) => e.name).sort();
|
|
8682
|
+
}
|
|
8683
|
+
async function stampFired(backend, slug, now) {
|
|
8684
|
+
try {
|
|
8685
|
+
const loaded = await backend.load(slug);
|
|
8686
|
+
const nextData = { ...loaded.state.data ?? {}, lastFiredAt: new Date(now).toISOString() };
|
|
8687
|
+
await backend.save(loaded, { ...loaded.state, data: nextData });
|
|
8688
|
+
} catch (err) {
|
|
8689
|
+
process.stderr.write(`[jobs] failed to stamp lastFiredAt for ${slug}: ${String(err)}
|
|
8690
|
+
`);
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8549
8693
|
|
|
8550
8694
|
// src/scripts/dispatchJobTicks.ts
|
|
8551
8695
|
init_issue();
|
|
@@ -9599,12 +9743,12 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
9599
9743
|
|
|
9600
9744
|
// src/scripts/initFlow.ts
|
|
9601
9745
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
9602
|
-
import * as
|
|
9603
|
-
import * as
|
|
9746
|
+
import * as fs33 from "fs";
|
|
9747
|
+
import * as path30 from "path";
|
|
9604
9748
|
function detectPackageManager(cwd) {
|
|
9605
|
-
if (
|
|
9606
|
-
if (
|
|
9607
|
-
if (
|
|
9749
|
+
if (fs33.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9750
|
+
if (fs33.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
|
|
9751
|
+
if (fs33.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
|
|
9608
9752
|
return "npm";
|
|
9609
9753
|
}
|
|
9610
9754
|
function qualityCommandsFor(pm) {
|
|
@@ -9733,36 +9877,36 @@ function performInit(cwd, force) {
|
|
|
9733
9877
|
const pm = detectPackageManager(cwd);
|
|
9734
9878
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
9735
9879
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
9736
|
-
const configPath =
|
|
9737
|
-
if (
|
|
9880
|
+
const configPath = path30.join(cwd, "kody.config.json");
|
|
9881
|
+
if (fs33.existsSync(configPath) && !force) {
|
|
9738
9882
|
skipped.push("kody.config.json");
|
|
9739
9883
|
} else {
|
|
9740
9884
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
|
|
9741
|
-
|
|
9885
|
+
fs33.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
9742
9886
|
`);
|
|
9743
9887
|
wrote.push("kody.config.json");
|
|
9744
9888
|
}
|
|
9745
|
-
const workflowDir =
|
|
9746
|
-
const workflowPath =
|
|
9747
|
-
if (
|
|
9889
|
+
const workflowDir = path30.join(cwd, ".github", "workflows");
|
|
9890
|
+
const workflowPath = path30.join(workflowDir, "kody.yml");
|
|
9891
|
+
if (fs33.existsSync(workflowPath) && !force) {
|
|
9748
9892
|
skipped.push(".github/workflows/kody.yml");
|
|
9749
9893
|
} else {
|
|
9750
|
-
|
|
9751
|
-
|
|
9894
|
+
fs33.mkdirSync(workflowDir, { recursive: true });
|
|
9895
|
+
fs33.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
9752
9896
|
wrote.push(".github/workflows/kody.yml");
|
|
9753
9897
|
}
|
|
9754
9898
|
const builtinJobs = listBuiltinJobs();
|
|
9755
9899
|
if (builtinJobs.length > 0) {
|
|
9756
|
-
const jobsDir =
|
|
9757
|
-
|
|
9900
|
+
const jobsDir = path30.join(cwd, ".kody", "duties");
|
|
9901
|
+
fs33.mkdirSync(jobsDir, { recursive: true });
|
|
9758
9902
|
for (const job of builtinJobs) {
|
|
9759
|
-
const rel =
|
|
9760
|
-
const target =
|
|
9761
|
-
if (
|
|
9903
|
+
const rel = path30.join(".kody", "duties", `${job.slug}.md`);
|
|
9904
|
+
const target = path30.join(cwd, rel);
|
|
9905
|
+
if (fs33.existsSync(target) && !force) {
|
|
9762
9906
|
skipped.push(rel);
|
|
9763
9907
|
continue;
|
|
9764
9908
|
}
|
|
9765
|
-
|
|
9909
|
+
fs33.writeFileSync(target, fs33.readFileSync(job.filePath, "utf-8"));
|
|
9766
9910
|
wrote.push(rel);
|
|
9767
9911
|
}
|
|
9768
9912
|
}
|
|
@@ -9774,12 +9918,12 @@ function performInit(cwd, force) {
|
|
|
9774
9918
|
continue;
|
|
9775
9919
|
}
|
|
9776
9920
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
9777
|
-
const target =
|
|
9778
|
-
if (
|
|
9921
|
+
const target = path30.join(workflowDir, `kody-${exe.name}.yml`);
|
|
9922
|
+
if (fs33.existsSync(target) && !force) {
|
|
9779
9923
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9780
9924
|
continue;
|
|
9781
9925
|
}
|
|
9782
|
-
|
|
9926
|
+
fs33.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
9783
9927
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9784
9928
|
}
|
|
9785
9929
|
let labels;
|
|
@@ -9964,8 +10108,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
9964
10108
|
|
|
9965
10109
|
// src/scripts/loadJobFromFile.ts
|
|
9966
10110
|
init_dutyMcp();
|
|
9967
|
-
import * as
|
|
9968
|
-
import * as
|
|
10111
|
+
import * as fs34 from "fs";
|
|
10112
|
+
import * as path31 from "path";
|
|
9969
10113
|
var DUTY_TOOL_PALETTE = new Set(DUTY_MCP_TOOL_NAMES);
|
|
9970
10114
|
var loadJobFromFile = async (ctx, profile, args) => {
|
|
9971
10115
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -9975,23 +10119,23 @@ var loadJobFromFile = async (ctx, profile, args) => {
|
|
|
9975
10119
|
if (!slug) {
|
|
9976
10120
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
9977
10121
|
}
|
|
9978
|
-
const absPath =
|
|
9979
|
-
if (!
|
|
10122
|
+
const absPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
10123
|
+
if (!fs34.existsSync(absPath)) {
|
|
9980
10124
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
9981
10125
|
}
|
|
9982
|
-
const raw =
|
|
10126
|
+
const raw = fs34.readFileSync(absPath, "utf-8");
|
|
9983
10127
|
const { title, body } = parseJobFile(raw, slug);
|
|
9984
|
-
const frontmatter =
|
|
10128
|
+
const frontmatter = splitFrontmatter2(raw).frontmatter;
|
|
9985
10129
|
const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
9986
10130
|
const workerSlug = (frontmatter.staff ?? "").trim();
|
|
9987
10131
|
let workerTitle = "";
|
|
9988
10132
|
let workerPersona = "";
|
|
9989
10133
|
if (workerSlug) {
|
|
9990
|
-
const workerPath =
|
|
9991
|
-
if (!
|
|
10134
|
+
const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
10135
|
+
if (!fs34.existsSync(workerPath)) {
|
|
9992
10136
|
throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
|
|
9993
10137
|
}
|
|
9994
|
-
const workerRaw =
|
|
10138
|
+
const workerRaw = fs34.readFileSync(workerPath, "utf-8");
|
|
9995
10139
|
const parsed = parseJobFile(workerRaw, workerSlug);
|
|
9996
10140
|
workerTitle = parsed.title;
|
|
9997
10141
|
workerPersona = parsed.body;
|
|
@@ -10044,6 +10188,36 @@ function humanizeSlug(slug) {
|
|
|
10044
10188
|
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
10045
10189
|
}
|
|
10046
10190
|
|
|
10191
|
+
// src/scripts/loadDutyState.ts
|
|
10192
|
+
init_dutyMcp();
|
|
10193
|
+
var DUTY_TOOL_PALETTE2 = new Set(DUTY_MCP_TOOL_NAMES);
|
|
10194
|
+
var loadDutyState = async (ctx, profile, args) => {
|
|
10195
|
+
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
10196
|
+
const slug = profile.name;
|
|
10197
|
+
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
10198
|
+
if (backend.hydrate) await backend.hydrate();
|
|
10199
|
+
const loaded = await backend.load(slug);
|
|
10200
|
+
ctx.data.jobSlug = slug;
|
|
10201
|
+
ctx.data.jobState = loaded;
|
|
10202
|
+
ctx.data.jobStateJson = JSON.stringify(loaded.state, null, 2);
|
|
10203
|
+
const mentions = (profile.mentions ?? []).map((l) => `@${l}`).join(" ");
|
|
10204
|
+
ctx.data.mentions = mentions;
|
|
10205
|
+
const declaredTools = profile.dutyTools ?? [];
|
|
10206
|
+
if (declaredTools.length > 0) {
|
|
10207
|
+
const unknown = declaredTools.filter((name) => !DUTY_TOOL_PALETTE2.has(name));
|
|
10208
|
+
if (unknown.length > 0) {
|
|
10209
|
+
throw new Error(
|
|
10210
|
+
`loadDutyState: duty '${slug}' declared dutyTools not in the kody-duty palette: ${unknown.join(", ")}. Available: ${[...DUTY_MCP_TOOL_NAMES].join(", ")}`
|
|
10211
|
+
);
|
|
10212
|
+
}
|
|
10213
|
+
ctx.data.dutyTools = declaredTools;
|
|
10214
|
+
ctx.data.dutyToolsList = declaredTools.map((name) => `- \`${name}\``).join("\n");
|
|
10215
|
+
ctx.data.dutyOperatorMention = mentions;
|
|
10216
|
+
const mcpToolNames = declaredTools.map((name) => `mcp__kody-duty__${name}`);
|
|
10217
|
+
profile.claudeCode.tools = [...mcpToolNames, "mcp__kody-submit__submit_state"];
|
|
10218
|
+
}
|
|
10219
|
+
};
|
|
10220
|
+
|
|
10047
10221
|
// src/scripts/loadLinkedFinding.ts
|
|
10048
10222
|
init_issue();
|
|
10049
10223
|
var FINDING_BODY_MAX_BYTES = 4e3;
|
|
@@ -10075,18 +10249,18 @@ init_loadMemoryContext();
|
|
|
10075
10249
|
init_loadPriorArt();
|
|
10076
10250
|
|
|
10077
10251
|
// src/scripts/loadQaContext.ts
|
|
10078
|
-
import * as
|
|
10079
|
-
import * as
|
|
10252
|
+
import * as fs36 from "fs";
|
|
10253
|
+
import * as path33 from "path";
|
|
10080
10254
|
|
|
10081
10255
|
// src/scripts/kodyVariables.ts
|
|
10082
|
-
import * as
|
|
10083
|
-
import * as
|
|
10256
|
+
import * as fs35 from "fs";
|
|
10257
|
+
import * as path32 from "path";
|
|
10084
10258
|
var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
|
|
10085
10259
|
function readKodyVariables(cwd) {
|
|
10086
|
-
const full =
|
|
10260
|
+
const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
10087
10261
|
let raw;
|
|
10088
10262
|
try {
|
|
10089
|
-
raw =
|
|
10263
|
+
raw = fs35.readFileSync(full, "utf-8");
|
|
10090
10264
|
} catch {
|
|
10091
10265
|
return {};
|
|
10092
10266
|
}
|
|
@@ -10137,18 +10311,18 @@ function readProfileStaff(raw) {
|
|
|
10137
10311
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
10138
10312
|
}
|
|
10139
10313
|
function readProfile(cwd) {
|
|
10140
|
-
const dir =
|
|
10141
|
-
if (!
|
|
10314
|
+
const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
10315
|
+
if (!fs36.existsSync(dir)) return "";
|
|
10142
10316
|
let entries;
|
|
10143
10317
|
try {
|
|
10144
|
-
entries =
|
|
10318
|
+
entries = fs36.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
10145
10319
|
} catch {
|
|
10146
10320
|
return "";
|
|
10147
10321
|
}
|
|
10148
10322
|
const blocks = [];
|
|
10149
10323
|
for (const file of entries) {
|
|
10150
10324
|
try {
|
|
10151
|
-
const raw =
|
|
10325
|
+
const raw = fs36.readFileSync(path33.join(dir, file), "utf-8");
|
|
10152
10326
|
const { staff, body } = readProfileStaff(raw);
|
|
10153
10327
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
10154
10328
|
blocks.push(`## ${file}
|
|
@@ -10185,8 +10359,8 @@ var loadQaContext = async (ctx) => {
|
|
|
10185
10359
|
init_events();
|
|
10186
10360
|
|
|
10187
10361
|
// src/taskContext.ts
|
|
10188
|
-
import * as
|
|
10189
|
-
import * as
|
|
10362
|
+
import * as fs37 from "fs";
|
|
10363
|
+
import * as path34 from "path";
|
|
10190
10364
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
10191
10365
|
function buildTaskContext(args) {
|
|
10192
10366
|
return {
|
|
@@ -10202,10 +10376,10 @@ function buildTaskContext(args) {
|
|
|
10202
10376
|
}
|
|
10203
10377
|
function persistTaskContext(cwd, ctx) {
|
|
10204
10378
|
try {
|
|
10205
|
-
const dir =
|
|
10206
|
-
|
|
10207
|
-
const file =
|
|
10208
|
-
|
|
10379
|
+
const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
|
|
10380
|
+
fs37.mkdirSync(dir, { recursive: true });
|
|
10381
|
+
const file = path34.join(dir, "task-context.json");
|
|
10382
|
+
fs37.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
10209
10383
|
`);
|
|
10210
10384
|
return file;
|
|
10211
10385
|
} catch (err) {
|
|
@@ -10271,19 +10445,19 @@ var loadTaskState = async (ctx) => {
|
|
|
10271
10445
|
};
|
|
10272
10446
|
|
|
10273
10447
|
// src/scripts/loadWorkerAdhoc.ts
|
|
10274
|
-
import * as
|
|
10275
|
-
import * as
|
|
10448
|
+
import * as fs38 from "fs";
|
|
10449
|
+
import * as path35 from "path";
|
|
10276
10450
|
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
10277
10451
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
10278
10452
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
10279
10453
|
if (!workerSlug) {
|
|
10280
10454
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
10281
10455
|
}
|
|
10282
|
-
const workerPath =
|
|
10283
|
-
if (!
|
|
10456
|
+
const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
10457
|
+
if (!fs38.existsSync(workerPath)) {
|
|
10284
10458
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
10285
10459
|
}
|
|
10286
|
-
const { title, body } = parsePersona(
|
|
10460
|
+
const { title, body } = parsePersona(fs38.readFileSync(workerPath, "utf-8"), workerSlug);
|
|
10287
10461
|
const message = resolveMessage(ctx.args.message);
|
|
10288
10462
|
if (!message) {
|
|
10289
10463
|
throw new Error(
|
|
@@ -10303,9 +10477,9 @@ function resolveMessage(messageArg) {
|
|
|
10303
10477
|
}
|
|
10304
10478
|
function readCommentBody() {
|
|
10305
10479
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
10306
|
-
if (!eventPath || !
|
|
10480
|
+
if (!eventPath || !fs38.existsSync(eventPath)) return "";
|
|
10307
10481
|
try {
|
|
10308
|
-
const event = JSON.parse(
|
|
10482
|
+
const event = JSON.parse(fs38.readFileSync(eventPath, "utf-8"));
|
|
10309
10483
|
return String(event.comment?.body ?? "");
|
|
10310
10484
|
} catch {
|
|
10311
10485
|
return "";
|
|
@@ -10329,7 +10503,7 @@ function stripDirective(body) {
|
|
|
10329
10503
|
return lines.slice(start).join("\n").trim();
|
|
10330
10504
|
}
|
|
10331
10505
|
function parsePersona(raw, slug) {
|
|
10332
|
-
const stripped =
|
|
10506
|
+
const stripped = splitFrontmatter2(raw).body;
|
|
10333
10507
|
const trimmed = stripped.trim();
|
|
10334
10508
|
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
10335
10509
|
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
@@ -12624,7 +12798,7 @@ function resolveBaseOverride(value) {
|
|
|
12624
12798
|
|
|
12625
12799
|
// src/scripts/runnerServe.ts
|
|
12626
12800
|
import { spawn as spawn5 } from "child_process";
|
|
12627
|
-
import * as
|
|
12801
|
+
import * as fs39 from "fs";
|
|
12628
12802
|
import { createServer as createServer3 } from "http";
|
|
12629
12803
|
var DEFAULT_PORT2 = 8080;
|
|
12630
12804
|
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
@@ -12704,8 +12878,8 @@ async function defaultRunJob(job) {
|
|
|
12704
12878
|
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
12705
12879
|
const branch = job.ref ?? "main";
|
|
12706
12880
|
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
12707
|
-
|
|
12708
|
-
|
|
12881
|
+
fs39.rmSync(workdir, { recursive: true, force: true });
|
|
12882
|
+
fs39.mkdirSync(workdir, { recursive: true });
|
|
12709
12883
|
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
12710
12884
|
const interactive = job.mode === "interactive";
|
|
12711
12885
|
const scheduled = job.mode === "scheduled";
|
|
@@ -12838,7 +13012,7 @@ var runnerServe = async (ctx) => {
|
|
|
12838
13012
|
|
|
12839
13013
|
// src/scripts/runPreviewBuild.ts
|
|
12840
13014
|
import { copyFile, writeFile } from "fs/promises";
|
|
12841
|
-
import * as
|
|
13015
|
+
import * as path36 from "path";
|
|
12842
13016
|
import { fileURLToPath } from "url";
|
|
12843
13017
|
|
|
12844
13018
|
// src/scripts/previewBuildHelpers.ts
|
|
@@ -12987,9 +13161,9 @@ var FLY_MACHINES = "https://api.machines.dev/v1";
|
|
|
12987
13161
|
var FLY_GRAPHQL = "https://api.fly.io/graphql";
|
|
12988
13162
|
var REQ_TIMEOUT_MS2 = 3e4;
|
|
12989
13163
|
function bundledDockerfilePath(mode) {
|
|
12990
|
-
const here =
|
|
13164
|
+
const here = path36.dirname(fileURLToPath(import.meta.url));
|
|
12991
13165
|
const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
|
|
12992
|
-
return
|
|
13166
|
+
return path36.join(here, "preview-build-templates", file);
|
|
12993
13167
|
}
|
|
12994
13168
|
function required(name) {
|
|
12995
13169
|
const v = (process.env[name] ?? "").trim();
|
|
@@ -13238,10 +13412,10 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
|
|
|
13238
13412
|
console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
|
|
13239
13413
|
if (Object.keys(buildEnv).length > 0) {
|
|
13240
13414
|
const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
13241
|
-
await writeFile(
|
|
13415
|
+
await writeFile(path36.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
|
|
13242
13416
|
`, "utf8");
|
|
13243
13417
|
}
|
|
13244
|
-
const consumerDockerfile =
|
|
13418
|
+
const consumerDockerfile = path36.join(ctx.cwd, "Dockerfile.preview");
|
|
13245
13419
|
const { stat } = await import("fs/promises");
|
|
13246
13420
|
let hasConsumerDockerfile = false;
|
|
13247
13421
|
try {
|
|
@@ -13341,8 +13515,8 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
|
|
|
13341
13515
|
|
|
13342
13516
|
// src/scripts/runTickScript.ts
|
|
13343
13517
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13344
|
-
import * as
|
|
13345
|
-
import * as
|
|
13518
|
+
import * as fs40 from "fs";
|
|
13519
|
+
import * as path37 from "path";
|
|
13346
13520
|
var runTickScript = async (ctx, _profile, args) => {
|
|
13347
13521
|
ctx.skipAgent = true;
|
|
13348
13522
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -13354,22 +13528,22 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
13354
13528
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
13355
13529
|
return;
|
|
13356
13530
|
}
|
|
13357
|
-
const jobPath =
|
|
13358
|
-
if (!
|
|
13531
|
+
const jobPath = path37.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
13532
|
+
if (!fs40.existsSync(jobPath)) {
|
|
13359
13533
|
ctx.output.exitCode = 99;
|
|
13360
13534
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
13361
13535
|
return;
|
|
13362
13536
|
}
|
|
13363
|
-
const raw =
|
|
13364
|
-
const { frontmatter } =
|
|
13537
|
+
const raw = fs40.readFileSync(jobPath, "utf-8");
|
|
13538
|
+
const { frontmatter } = splitFrontmatter2(raw);
|
|
13365
13539
|
const tickScript = frontmatter.tickScript;
|
|
13366
13540
|
if (!tickScript) {
|
|
13367
13541
|
ctx.output.exitCode = 99;
|
|
13368
13542
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
13369
13543
|
return;
|
|
13370
13544
|
}
|
|
13371
|
-
const scriptPath =
|
|
13372
|
-
if (!
|
|
13545
|
+
const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
|
|
13546
|
+
if (!fs40.existsSync(scriptPath)) {
|
|
13373
13547
|
ctx.output.exitCode = 99;
|
|
13374
13548
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
13375
13549
|
return;
|
|
@@ -13498,7 +13672,7 @@ var saveTaskState = async (ctx, profile) => {
|
|
|
13498
13672
|
if (!target || !number || !state) return;
|
|
13499
13673
|
const executable = profile.name;
|
|
13500
13674
|
const action = ctx.data.action ?? synthesizeAction(ctx);
|
|
13501
|
-
const next = reduce(state, executable, action, profile.phase);
|
|
13675
|
+
const next = reduce(state, executable, action, profile.phase, profile.staff);
|
|
13502
13676
|
if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
|
|
13503
13677
|
if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
|
|
13504
13678
|
writeTaskState(target, number, next, ctx.cwd);
|
|
@@ -14415,7 +14589,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
|
|
|
14415
14589
|
};
|
|
14416
14590
|
|
|
14417
14591
|
// src/scripts/writeRunSummary.ts
|
|
14418
|
-
import * as
|
|
14592
|
+
import * as fs41 from "fs";
|
|
14419
14593
|
var writeRunSummary = async (ctx, profile) => {
|
|
14420
14594
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
14421
14595
|
if (!summaryPath) return;
|
|
@@ -14437,7 +14611,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
14437
14611
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
14438
14612
|
lines.push("");
|
|
14439
14613
|
try {
|
|
14440
|
-
|
|
14614
|
+
fs41.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
14441
14615
|
`);
|
|
14442
14616
|
} catch {
|
|
14443
14617
|
}
|
|
@@ -14459,6 +14633,7 @@ var preflightScripts = {
|
|
|
14459
14633
|
loadIssueContext,
|
|
14460
14634
|
loadIssueStateComment,
|
|
14461
14635
|
loadJobFromFile,
|
|
14636
|
+
loadDutyState,
|
|
14462
14637
|
loadWorkerAdhoc,
|
|
14463
14638
|
loadConventions,
|
|
14464
14639
|
loadCoverageRules,
|
|
@@ -14545,8 +14720,8 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
14545
14720
|
]);
|
|
14546
14721
|
|
|
14547
14722
|
// src/staff.ts
|
|
14548
|
-
import * as
|
|
14549
|
-
import * as
|
|
14723
|
+
import * as fs42 from "fs";
|
|
14724
|
+
import * as path38 from "path";
|
|
14550
14725
|
var DEFAULT_STAFF_DIR = ".kody/staff";
|
|
14551
14726
|
function stripFrontmatter(raw) {
|
|
14552
14727
|
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
@@ -14555,11 +14730,11 @@ function stripFrontmatter(raw) {
|
|
|
14555
14730
|
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
14556
14731
|
const trimmed = slug.trim();
|
|
14557
14732
|
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
14558
|
-
const staffPath =
|
|
14559
|
-
if (!
|
|
14733
|
+
const staffPath = path38.join(cwd, staffDir, `${trimmed}.md`);
|
|
14734
|
+
if (!fs42.existsSync(staffPath)) {
|
|
14560
14735
|
throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
|
|
14561
14736
|
}
|
|
14562
|
-
const body = stripFrontmatter(
|
|
14737
|
+
const body = stripFrontmatter(fs42.readFileSync(staffPath, "utf-8"));
|
|
14563
14738
|
if (!body) throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
|
|
14564
14739
|
return body;
|
|
14565
14740
|
}
|
|
@@ -14576,48 +14751,6 @@ function framePersona(slug, persona) {
|
|
|
14576
14751
|
].join("\n");
|
|
14577
14752
|
}
|
|
14578
14753
|
|
|
14579
|
-
// src/subagents.ts
|
|
14580
|
-
import * as fs42 from "fs";
|
|
14581
|
-
import * as path38 from "path";
|
|
14582
|
-
function splitFrontmatter2(raw) {
|
|
14583
|
-
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
14584
|
-
if (!match) return { fm: {}, body: raw.trim() };
|
|
14585
|
-
const fm = {};
|
|
14586
|
-
for (const line of match[1].split("\n")) {
|
|
14587
|
-
const idx = line.indexOf(":");
|
|
14588
|
-
if (idx === -1) continue;
|
|
14589
|
-
fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
14590
|
-
}
|
|
14591
|
-
return { fm, body: (match[2] ?? "").trim() };
|
|
14592
|
-
}
|
|
14593
|
-
function resolveAgentFile(profileDir, name) {
|
|
14594
|
-
const local = path38.join(profileDir, "agents", `${name}.md`);
|
|
14595
|
-
if (fs42.existsSync(local)) return local;
|
|
14596
|
-
const central = path38.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
14597
|
-
if (fs42.existsSync(central)) return central;
|
|
14598
|
-
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
14599
|
-
}
|
|
14600
|
-
function loadSubagents(profile) {
|
|
14601
|
-
const names = profile.claudeCode.subagents;
|
|
14602
|
-
if (!names || names.length === 0) return void 0;
|
|
14603
|
-
const agents = {};
|
|
14604
|
-
for (const name of names) {
|
|
14605
|
-
const { fm, body } = splitFrontmatter2(fs42.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
|
|
14606
|
-
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
14607
|
-
const def = {
|
|
14608
|
-
description: fm.description ?? `Subagent ${name}`,
|
|
14609
|
-
prompt: body
|
|
14610
|
-
};
|
|
14611
|
-
if (fm.tools) {
|
|
14612
|
-
const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
|
|
14613
|
-
if (tools.length > 0) def.tools = tools;
|
|
14614
|
-
}
|
|
14615
|
-
if (fm.model) def.model = fm.model;
|
|
14616
|
-
agents[fm.name || name] = def;
|
|
14617
|
-
}
|
|
14618
|
-
return agents;
|
|
14619
|
-
}
|
|
14620
|
-
|
|
14621
14754
|
// src/tools.ts
|
|
14622
14755
|
import { execFileSync as execFileSync27 } from "child_process";
|
|
14623
14756
|
function verifyCliTools(tools, cwd) {
|
|
@@ -15536,16 +15669,63 @@ async function runCi(argv) {
|
|
|
15536
15669
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15537
15670
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
15538
15671
|
let manualWorkflowDispatch = false;
|
|
15672
|
+
let forceRunDuty = null;
|
|
15539
15673
|
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs44.existsSync(dispatchEventPath)) {
|
|
15540
15674
|
try {
|
|
15541
15675
|
const evt = JSON.parse(fs44.readFileSync(dispatchEventPath, "utf-8"));
|
|
15542
15676
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
15543
15677
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
15544
|
-
|
|
15678
|
+
const exeInput = String(evt?.inputs?.executable ?? "").trim();
|
|
15679
|
+
const noTarget = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
15680
|
+
if (noTarget && exeInput) forceRunDuty = exeInput;
|
|
15681
|
+
else manualWorkflowDispatch = noTarget;
|
|
15545
15682
|
} catch {
|
|
15546
15683
|
manualWorkflowDispatch = false;
|
|
15547
15684
|
}
|
|
15548
15685
|
}
|
|
15686
|
+
if (forceRunDuty) {
|
|
15687
|
+
const config = earlyConfig ?? loadConfig(cwd);
|
|
15688
|
+
process.stdout.write(`\u2192 kody: manual one-shot run of duty ${forceRunDuty}
|
|
15689
|
+
|
|
15690
|
+
`);
|
|
15691
|
+
try {
|
|
15692
|
+
const n = unpackAllSecrets();
|
|
15693
|
+
if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s)
|
|
15694
|
+
`);
|
|
15695
|
+
await resolveAuthToken();
|
|
15696
|
+
const pm = args.packageManager ?? detectPackageManager2(cwd);
|
|
15697
|
+
if (!args.skipInstall) {
|
|
15698
|
+
const code = installDeps(pm, cwd);
|
|
15699
|
+
if (code !== 0) {
|
|
15700
|
+
process.stderr.write(`[kody] dependency install failed (exit ${code})
|
|
15701
|
+
`);
|
|
15702
|
+
return 99;
|
|
15703
|
+
}
|
|
15704
|
+
}
|
|
15705
|
+
if (!args.skipLitellm) {
|
|
15706
|
+
const code = installLitellmIfNeeded(cwd);
|
|
15707
|
+
if (code !== 0) {
|
|
15708
|
+
process.stderr.write(`[kody] litellm install failed (exit ${code})
|
|
15709
|
+
`);
|
|
15710
|
+
return 99;
|
|
15711
|
+
}
|
|
15712
|
+
}
|
|
15713
|
+
configureGitIdentity(cwd);
|
|
15714
|
+
} catch (err) {
|
|
15715
|
+
process.stderr.write(`[kody] manual duty preflight crashed: ${String(err)}
|
|
15716
|
+
`);
|
|
15717
|
+
return 99;
|
|
15718
|
+
}
|
|
15719
|
+
const result = await runExecutableChain(forceRunDuty, {
|
|
15720
|
+
cliArgs: {},
|
|
15721
|
+
cwd,
|
|
15722
|
+
config,
|
|
15723
|
+
verbose: args.verbose,
|
|
15724
|
+
quiet: args.quiet
|
|
15725
|
+
});
|
|
15726
|
+
const ec = result.exitCode;
|
|
15727
|
+
return ec === 0 || ec === 1 || ec === 2 ? ec : 99;
|
|
15728
|
+
}
|
|
15549
15729
|
if (!args.issueNumber && !autoFallback && (eventName === "schedule" || manualWorkflowDispatch)) {
|
|
15550
15730
|
return runScheduledFanOut(cwd, args, { force: manualWorkflowDispatch });
|
|
15551
15731
|
}
|