@kody-ade/kody-engine 0.4.204-next.2 → 0.4.204-next.4
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 +447 -358
- package/dist/executables/types.ts +16 -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.4",
|
|
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",
|
|
@@ -3726,11 +3726,11 @@ import * as path39 from "path";
|
|
|
3726
3726
|
// src/container.ts
|
|
3727
3727
|
init_events();
|
|
3728
3728
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
3729
|
-
import * as
|
|
3729
|
+
import * as fs19 from "fs";
|
|
3730
3730
|
|
|
3731
3731
|
// src/profile.ts
|
|
3732
|
-
import * as
|
|
3733
|
-
import * as
|
|
3732
|
+
import * as fs16 from "fs";
|
|
3733
|
+
import * as path15 from "path";
|
|
3734
3734
|
|
|
3735
3735
|
// src/profile-error.ts
|
|
3736
3736
|
var ProfileError = class extends Error {
|
|
@@ -3870,6 +3870,150 @@ function applyLifecycle(profile, profilePath) {
|
|
|
3870
3870
|
expander(profile, profilePath);
|
|
3871
3871
|
}
|
|
3872
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
|
+
|
|
3873
4017
|
// src/profile.ts
|
|
3874
4018
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
3875
4019
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
@@ -3879,6 +4023,7 @@ var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementin
|
|
|
3879
4023
|
var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
3880
4024
|
"name",
|
|
3881
4025
|
"staff",
|
|
4026
|
+
"every",
|
|
3882
4027
|
"describe",
|
|
3883
4028
|
"role",
|
|
3884
4029
|
"kind",
|
|
@@ -3902,12 +4047,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
|
3902
4047
|
"preloadContext"
|
|
3903
4048
|
]);
|
|
3904
4049
|
function loadProfile(profilePath) {
|
|
3905
|
-
if (!
|
|
4050
|
+
if (!fs16.existsSync(profilePath)) {
|
|
3906
4051
|
throw new ProfileError(profilePath, "file not found");
|
|
3907
4052
|
}
|
|
3908
4053
|
let raw;
|
|
3909
4054
|
try {
|
|
3910
|
-
raw = JSON.parse(
|
|
4055
|
+
raw = JSON.parse(fs16.readFileSync(profilePath, "utf-8"));
|
|
3911
4056
|
} catch (err) {
|
|
3912
4057
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
3913
4058
|
}
|
|
@@ -3918,7 +4063,7 @@ function loadProfile(profilePath) {
|
|
|
3918
4063
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
3919
4064
|
if (unknownKeys.length > 0) {
|
|
3920
4065
|
process.stderr.write(
|
|
3921
|
-
`[kody profile] ${
|
|
4066
|
+
`[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
3922
4067
|
`
|
|
3923
4068
|
);
|
|
3924
4069
|
}
|
|
@@ -3960,6 +4105,8 @@ function loadProfile(profilePath) {
|
|
|
3960
4105
|
describe: typeof r.describe === "string" ? r.describe : "",
|
|
3961
4106
|
// Optional persona to run as. Empty/blank string → undefined (no persona).
|
|
3962
4107
|
staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
|
|
4108
|
+
// Optional recurrence cadence (scheduled duty). Blank → undefined (on-demand).
|
|
4109
|
+
every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
|
|
3963
4110
|
role,
|
|
3964
4111
|
kind,
|
|
3965
4112
|
schedule: typeof r.schedule === "string" ? r.schedule : void 0,
|
|
@@ -3981,27 +4128,28 @@ function loadProfile(profilePath) {
|
|
|
3981
4128
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
3982
4129
|
// flip to true after end-to-end verification.
|
|
3983
4130
|
preloadContext: r.preloadContext === true,
|
|
3984
|
-
dir:
|
|
3985
|
-
promptTemplates: readPromptTemplates(
|
|
4131
|
+
dir: path15.dirname(profilePath),
|
|
4132
|
+
promptTemplates: readPromptTemplates(path15.dirname(profilePath))
|
|
3986
4133
|
};
|
|
3987
4134
|
if (lifecycle) {
|
|
3988
4135
|
applyLifecycle(profile, profilePath);
|
|
3989
4136
|
}
|
|
4137
|
+
profile.subagentTemplates = captureSubagentTemplates(profile);
|
|
3990
4138
|
return profile;
|
|
3991
4139
|
}
|
|
3992
4140
|
function readPromptTemplates(dir) {
|
|
3993
4141
|
const out = {};
|
|
3994
4142
|
const read = (p) => {
|
|
3995
4143
|
try {
|
|
3996
|
-
out[p] =
|
|
4144
|
+
out[p] = fs16.readFileSync(p, "utf-8");
|
|
3997
4145
|
} catch {
|
|
3998
4146
|
}
|
|
3999
4147
|
};
|
|
4000
|
-
read(
|
|
4148
|
+
read(path15.join(dir, "prompt.md"));
|
|
4001
4149
|
try {
|
|
4002
|
-
const promptsDir =
|
|
4003
|
-
for (const ent of
|
|
4004
|
-
if (ent.endsWith(".md")) read(
|
|
4150
|
+
const promptsDir = path15.join(dir, "prompts");
|
|
4151
|
+
for (const ent of fs16.readdirSync(promptsDir)) {
|
|
4152
|
+
if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
|
|
4005
4153
|
}
|
|
4006
4154
|
} catch {
|
|
4007
4155
|
}
|
|
@@ -4482,7 +4630,7 @@ var CONTAINER_MAX_ITERATIONS = 50;
|
|
|
4482
4630
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
4483
4631
|
try {
|
|
4484
4632
|
const profilePath = resolveProfilePath(profileName);
|
|
4485
|
-
if (!
|
|
4633
|
+
if (!fs19.existsSync(profilePath)) return null;
|
|
4486
4634
|
return loadProfile(profilePath).inputs;
|
|
4487
4635
|
} catch {
|
|
4488
4636
|
return null;
|
|
@@ -4926,9 +5074,9 @@ function errMsg(err) {
|
|
|
4926
5074
|
|
|
4927
5075
|
// src/litellm.ts
|
|
4928
5076
|
import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
|
|
4929
|
-
import * as
|
|
4930
|
-
import * as
|
|
4931
|
-
import * as
|
|
5077
|
+
import * as fs20 from "fs";
|
|
5078
|
+
import * as os3 from "os";
|
|
5079
|
+
import * as path18 from "path";
|
|
4932
5080
|
async function checkLitellmHealth(url) {
|
|
4933
5081
|
try {
|
|
4934
5082
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -4980,13 +5128,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4980
5128
|
let child;
|
|
4981
5129
|
let logPath;
|
|
4982
5130
|
const spawnProxy = () => {
|
|
4983
|
-
const configPath =
|
|
4984
|
-
|
|
5131
|
+
const configPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
5132
|
+
fs20.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
4985
5133
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
4986
|
-
const nextLogPath =
|
|
4987
|
-
const outFd =
|
|
5134
|
+
const nextLogPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
5135
|
+
const outFd = fs20.openSync(nextLogPath, "w");
|
|
4988
5136
|
child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
4989
|
-
|
|
5137
|
+
fs20.closeSync(outFd);
|
|
4990
5138
|
logPath = nextLogPath;
|
|
4991
5139
|
};
|
|
4992
5140
|
const waitForHealth = async () => {
|
|
@@ -5000,7 +5148,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
5000
5148
|
const readLogTail = () => {
|
|
5001
5149
|
if (!logPath) return "";
|
|
5002
5150
|
try {
|
|
5003
|
-
return
|
|
5151
|
+
return fs20.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
5004
5152
|
} catch {
|
|
5005
5153
|
return "";
|
|
5006
5154
|
}
|
|
@@ -5052,10 +5200,10 @@ ${tail}`
|
|
|
5052
5200
|
return { url, kill: killChild, isHealthy, ensureHealthy };
|
|
5053
5201
|
}
|
|
5054
5202
|
function readDotenvApiKeys(projectDir) {
|
|
5055
|
-
const dotenvPath =
|
|
5056
|
-
if (!
|
|
5203
|
+
const dotenvPath = path18.join(projectDir, ".env");
|
|
5204
|
+
if (!fs20.existsSync(dotenvPath)) return {};
|
|
5057
5205
|
const result = {};
|
|
5058
|
-
for (const rawLine of
|
|
5206
|
+
for (const rawLine of fs20.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
5059
5207
|
const line = rawLine.trim();
|
|
5060
5208
|
if (!line || line.startsWith("#")) continue;
|
|
5061
5209
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -5157,8 +5305,8 @@ function pushWithRetry(opts = {}) {
|
|
|
5157
5305
|
}
|
|
5158
5306
|
|
|
5159
5307
|
// src/commit.ts
|
|
5160
|
-
import * as
|
|
5161
|
-
import * as
|
|
5308
|
+
import * as fs21 from "fs";
|
|
5309
|
+
import * as path19 from "path";
|
|
5162
5310
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
5163
5311
|
".kody/",
|
|
5164
5312
|
".kody-engine/",
|
|
@@ -5219,18 +5367,18 @@ function tryGit(args, cwd) {
|
|
|
5219
5367
|
}
|
|
5220
5368
|
function abortUnfinishedGitOps(cwd) {
|
|
5221
5369
|
const aborted = [];
|
|
5222
|
-
const gitDir =
|
|
5223
|
-
if (!
|
|
5224
|
-
if (
|
|
5370
|
+
const gitDir = path19.join(cwd ?? process.cwd(), ".git");
|
|
5371
|
+
if (!fs21.existsSync(gitDir)) return aborted;
|
|
5372
|
+
if (fs21.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
|
|
5225
5373
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
5226
5374
|
}
|
|
5227
|
-
if (
|
|
5375
|
+
if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
5228
5376
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
5229
5377
|
}
|
|
5230
|
-
if (
|
|
5378
|
+
if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
|
|
5231
5379
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
5232
5380
|
}
|
|
5233
|
-
if (
|
|
5381
|
+
if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
|
|
5234
5382
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
5235
5383
|
}
|
|
5236
5384
|
try {
|
|
@@ -5286,7 +5434,7 @@ function normalizeCommitMessage(raw) {
|
|
|
5286
5434
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
5287
5435
|
const allChanged = listChangedFiles(cwd);
|
|
5288
5436
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
5289
|
-
const mergeHeadExists =
|
|
5437
|
+
const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
5290
5438
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
5291
5439
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
5292
5440
|
}
|
|
@@ -5418,7 +5566,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
5418
5566
|
|
|
5419
5567
|
// src/gha.ts
|
|
5420
5568
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
5421
|
-
import * as
|
|
5569
|
+
import * as fs22 from "fs";
|
|
5422
5570
|
function getRunUrl() {
|
|
5423
5571
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5424
5572
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5429,10 +5577,10 @@ function getRunUrl() {
|
|
|
5429
5577
|
function reactToTriggerComment(cwd) {
|
|
5430
5578
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5431
5579
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5432
|
-
if (!eventPath || !
|
|
5580
|
+
if (!eventPath || !fs22.existsSync(eventPath)) return;
|
|
5433
5581
|
let event = null;
|
|
5434
5582
|
try {
|
|
5435
|
-
event = JSON.parse(
|
|
5583
|
+
event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
|
|
5436
5584
|
} catch {
|
|
5437
5585
|
return;
|
|
5438
5586
|
}
|
|
@@ -5584,22 +5732,22 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
|
|
|
5584
5732
|
};
|
|
5585
5733
|
|
|
5586
5734
|
// src/scripts/brainServe.ts
|
|
5587
|
-
import * as
|
|
5735
|
+
import * as fs24 from "fs";
|
|
5588
5736
|
import { createServer } from "http";
|
|
5589
|
-
import * as
|
|
5737
|
+
import * as path21 from "path";
|
|
5590
5738
|
init_repoWorkspace();
|
|
5591
5739
|
|
|
5592
5740
|
// src/scripts/brainTurnLog.ts
|
|
5593
|
-
import * as
|
|
5594
|
-
import * as
|
|
5741
|
+
import * as fs23 from "fs";
|
|
5742
|
+
import * as path20 from "path";
|
|
5595
5743
|
var live = /* @__PURE__ */ new Map();
|
|
5596
5744
|
function eventsPath(dir, chatId) {
|
|
5597
|
-
return
|
|
5745
|
+
return path20.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
5598
5746
|
}
|
|
5599
5747
|
function lastPersistedSeq(dir, chatId) {
|
|
5600
5748
|
const p = eventsPath(dir, chatId);
|
|
5601
|
-
if (!
|
|
5602
|
-
const lines =
|
|
5749
|
+
if (!fs23.existsSync(p)) return 0;
|
|
5750
|
+
const lines = fs23.readFileSync(p, "utf-8").split("\n").filter(Boolean);
|
|
5603
5751
|
if (lines.length === 0) return 0;
|
|
5604
5752
|
try {
|
|
5605
5753
|
return JSON.parse(lines[lines.length - 1]).seq || 0;
|
|
@@ -5609,9 +5757,9 @@ function lastPersistedSeq(dir, chatId) {
|
|
|
5609
5757
|
}
|
|
5610
5758
|
function readSince(dir, chatId, since) {
|
|
5611
5759
|
const p = eventsPath(dir, chatId);
|
|
5612
|
-
if (!
|
|
5760
|
+
if (!fs23.existsSync(p)) return [];
|
|
5613
5761
|
const out = [];
|
|
5614
|
-
for (const line of
|
|
5762
|
+
for (const line of fs23.readFileSync(p, "utf-8").split("\n")) {
|
|
5615
5763
|
if (!line) continue;
|
|
5616
5764
|
try {
|
|
5617
5765
|
const rec = JSON.parse(line);
|
|
@@ -5637,12 +5785,12 @@ function beginTurn(dir, chatId) {
|
|
|
5637
5785
|
};
|
|
5638
5786
|
live.set(chatId, state);
|
|
5639
5787
|
const p = eventsPath(dir, chatId);
|
|
5640
|
-
|
|
5788
|
+
fs23.mkdirSync(path20.dirname(p), { recursive: true });
|
|
5641
5789
|
return (event) => {
|
|
5642
5790
|
state.seq += 1;
|
|
5643
5791
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
5644
5792
|
try {
|
|
5645
|
-
|
|
5793
|
+
fs23.appendFileSync(p, `${JSON.stringify(rec)}
|
|
5646
5794
|
`);
|
|
5647
5795
|
} catch (err) {
|
|
5648
5796
|
process.stderr.write(
|
|
@@ -5681,7 +5829,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
|
|
|
5681
5829
|
event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
|
|
5682
5830
|
};
|
|
5683
5831
|
try {
|
|
5684
|
-
|
|
5832
|
+
fs23.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
|
|
5685
5833
|
`);
|
|
5686
5834
|
} catch {
|
|
5687
5835
|
}
|
|
@@ -5912,7 +6060,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
5912
6060
|
const repo = strField(body, "repo");
|
|
5913
6061
|
const repoToken = strField(body, "repoToken");
|
|
5914
6062
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
5915
|
-
|
|
6063
|
+
fs24.mkdirSync(path21.dirname(sessionFile), { recursive: true });
|
|
5916
6064
|
appendTurn(sessionFile, {
|
|
5917
6065
|
role: "user",
|
|
5918
6066
|
content: message,
|
|
@@ -5959,7 +6107,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
5959
6107
|
function buildServer(opts) {
|
|
5960
6108
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
5961
6109
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
5962
|
-
const reposRoot = opts.reposRoot ??
|
|
6110
|
+
const reposRoot = opts.reposRoot ?? path21.join(path21.dirname(path21.resolve(opts.cwd)), "repos");
|
|
5963
6111
|
return createServer(async (req, res) => {
|
|
5964
6112
|
if (!req.method || !req.url) {
|
|
5965
6113
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -6060,93 +6208,6 @@ var brainServe = async (ctx) => {
|
|
|
6060
6208
|
});
|
|
6061
6209
|
};
|
|
6062
6210
|
|
|
6063
|
-
// src/scripts/buildSyntheticPlugin.ts
|
|
6064
|
-
import * as fs23 from "fs";
|
|
6065
|
-
import * as os3 from "os";
|
|
6066
|
-
import * as path20 from "path";
|
|
6067
|
-
function getPluginsCatalogRoot() {
|
|
6068
|
-
const here = path20.dirname(new URL(import.meta.url).pathname);
|
|
6069
|
-
const candidates = [
|
|
6070
|
-
path20.join(here, "..", "plugins"),
|
|
6071
|
-
// dev: src/scripts → src/plugins
|
|
6072
|
-
path20.join(here, "..", "..", "plugins"),
|
|
6073
|
-
// built: dist/scripts → dist/plugins
|
|
6074
|
-
path20.join(here, "..", "..", "src", "plugins")
|
|
6075
|
-
// fallback
|
|
6076
|
-
];
|
|
6077
|
-
for (const c of candidates) {
|
|
6078
|
-
if (fs23.existsSync(c) && fs23.statSync(c).isDirectory()) return c;
|
|
6079
|
-
}
|
|
6080
|
-
return candidates[0];
|
|
6081
|
-
}
|
|
6082
|
-
var buildSyntheticPlugin = async (ctx, profile) => {
|
|
6083
|
-
const cc = profile.claudeCode;
|
|
6084
|
-
const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
|
|
6085
|
-
if (!needsSynthetic) return;
|
|
6086
|
-
const catalog = getPluginsCatalogRoot();
|
|
6087
|
-
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6088
|
-
const root = path20.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
6089
|
-
fs23.mkdirSync(path20.join(root, ".claude-plugin"), { recursive: true });
|
|
6090
|
-
const resolvePart = (bucket, entry) => {
|
|
6091
|
-
const local = path20.join(profile.dir, bucket, entry);
|
|
6092
|
-
if (fs23.existsSync(local)) return local;
|
|
6093
|
-
const central = path20.join(catalog, bucket, entry);
|
|
6094
|
-
if (fs23.existsSync(central)) return central;
|
|
6095
|
-
throw new Error(
|
|
6096
|
-
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
6097
|
-
);
|
|
6098
|
-
};
|
|
6099
|
-
if (cc.skills.length > 0) {
|
|
6100
|
-
const dst = path20.join(root, "skills");
|
|
6101
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6102
|
-
for (const name of cc.skills) {
|
|
6103
|
-
copyDir(resolvePart("skills", name), path20.join(dst, name));
|
|
6104
|
-
}
|
|
6105
|
-
}
|
|
6106
|
-
if (cc.commands.length > 0) {
|
|
6107
|
-
const dst = path20.join(root, "commands");
|
|
6108
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6109
|
-
for (const name of cc.commands) {
|
|
6110
|
-
fs23.copyFileSync(resolvePart("commands", `${name}.md`), path20.join(dst, `${name}.md`));
|
|
6111
|
-
}
|
|
6112
|
-
}
|
|
6113
|
-
if (cc.hooks.length > 0) {
|
|
6114
|
-
const dst = path20.join(root, "hooks");
|
|
6115
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6116
|
-
const merged = { hooks: {} };
|
|
6117
|
-
for (const name of cc.hooks) {
|
|
6118
|
-
const src = resolvePart("hooks", `${name}.json`);
|
|
6119
|
-
const parsed = JSON.parse(fs23.readFileSync(src, "utf-8"));
|
|
6120
|
-
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
6121
|
-
if (!Array.isArray(entries)) continue;
|
|
6122
|
-
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
6123
|
-
merged.hooks[event].push(...entries);
|
|
6124
|
-
}
|
|
6125
|
-
}
|
|
6126
|
-
fs23.writeFileSync(path20.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
6127
|
-
`);
|
|
6128
|
-
}
|
|
6129
|
-
const manifest = {
|
|
6130
|
-
name: `kody-synth-${profile.name}`,
|
|
6131
|
-
version: "1.0.0",
|
|
6132
|
-
description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
|
|
6133
|
-
};
|
|
6134
|
-
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
6135
|
-
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
6136
|
-
fs23.writeFileSync(path20.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
6137
|
-
`);
|
|
6138
|
-
ctx.data.syntheticPluginPath = root;
|
|
6139
|
-
};
|
|
6140
|
-
function copyDir(src, dst) {
|
|
6141
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6142
|
-
for (const ent of fs23.readdirSync(src, { withFileTypes: true })) {
|
|
6143
|
-
const s = path20.join(src, ent.name);
|
|
6144
|
-
const d = path20.join(dst, ent.name);
|
|
6145
|
-
if (ent.isDirectory()) copyDir(s, d);
|
|
6146
|
-
else if (ent.isFile()) fs23.copyFileSync(s, d);
|
|
6147
|
-
}
|
|
6148
|
-
}
|
|
6149
|
-
|
|
6150
6211
|
// src/coverage.ts
|
|
6151
6212
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
6152
6213
|
function patternToRegex(pattern) {
|
|
@@ -6295,13 +6356,13 @@ function defaultLabelMap() {
|
|
|
6295
6356
|
}
|
|
6296
6357
|
|
|
6297
6358
|
// src/scripts/commitAndPush.ts
|
|
6298
|
-
import * as
|
|
6299
|
-
import * as
|
|
6359
|
+
import * as fs25 from "fs";
|
|
6360
|
+
import * as path22 from "path";
|
|
6300
6361
|
init_events();
|
|
6301
6362
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
6302
6363
|
function sentinelPathForStage(cwd, profileName) {
|
|
6303
6364
|
const runId = resolveRunId();
|
|
6304
|
-
return
|
|
6365
|
+
return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
6305
6366
|
}
|
|
6306
6367
|
var commitAndPush2 = async (ctx, profile) => {
|
|
6307
6368
|
const branch = ctx.data.branch;
|
|
@@ -6311,9 +6372,9 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6311
6372
|
}
|
|
6312
6373
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
6313
6374
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
6314
|
-
if (sentinel &&
|
|
6375
|
+
if (sentinel && fs25.existsSync(sentinel)) {
|
|
6315
6376
|
try {
|
|
6316
|
-
const replay = JSON.parse(
|
|
6377
|
+
const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
|
|
6317
6378
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
6318
6379
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
6319
6380
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -6366,8 +6427,8 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6366
6427
|
const result = ctx.data.commitResult;
|
|
6367
6428
|
if (sentinel && result?.committed) {
|
|
6368
6429
|
try {
|
|
6369
|
-
|
|
6370
|
-
|
|
6430
|
+
fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
|
|
6431
|
+
fs25.writeFileSync(
|
|
6371
6432
|
sentinel,
|
|
6372
6433
|
JSON.stringify(
|
|
6373
6434
|
{
|
|
@@ -6390,8 +6451,8 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6390
6451
|
init_issue();
|
|
6391
6452
|
|
|
6392
6453
|
// src/goal/state.ts
|
|
6393
|
-
import * as
|
|
6394
|
-
import * as
|
|
6454
|
+
import * as fs26 from "fs";
|
|
6455
|
+
import * as path23 from "path";
|
|
6395
6456
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
6396
6457
|
var GoalStateError = class extends Error {
|
|
6397
6458
|
constructor(path42, message) {
|
|
@@ -6537,16 +6598,16 @@ function describeCommitMessage(goal) {
|
|
|
6537
6598
|
}
|
|
6538
6599
|
|
|
6539
6600
|
// src/scripts/composePrompt.ts
|
|
6540
|
-
import * as
|
|
6541
|
-
import * as
|
|
6601
|
+
import * as fs27 from "fs";
|
|
6602
|
+
import * as path24 from "path";
|
|
6542
6603
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
6543
6604
|
var composePrompt = async (ctx, profile) => {
|
|
6544
6605
|
const explicit = ctx.data.promptTemplate;
|
|
6545
6606
|
const mode = ctx.args.mode;
|
|
6546
6607
|
const candidates = [
|
|
6547
|
-
explicit ?
|
|
6548
|
-
mode ?
|
|
6549
|
-
|
|
6608
|
+
explicit ? path24.join(profile.dir, explicit) : null,
|
|
6609
|
+
mode ? path24.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
6610
|
+
path24.join(profile.dir, "prompt.md")
|
|
6550
6611
|
].filter(Boolean);
|
|
6551
6612
|
let templatePath = "";
|
|
6552
6613
|
let template = "";
|
|
@@ -6559,7 +6620,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
6559
6620
|
break;
|
|
6560
6621
|
}
|
|
6561
6622
|
try {
|
|
6562
|
-
template =
|
|
6623
|
+
template = fs27.readFileSync(c, "utf-8");
|
|
6563
6624
|
templatePath = c;
|
|
6564
6625
|
break;
|
|
6565
6626
|
} catch (err) {
|
|
@@ -6570,7 +6631,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
6570
6631
|
if (!templatePath) {
|
|
6571
6632
|
let dirState;
|
|
6572
6633
|
try {
|
|
6573
|
-
dirState = `dir contents: [${
|
|
6634
|
+
dirState = `dir contents: [${fs27.readdirSync(profile.dir).join(", ")}]`;
|
|
6574
6635
|
} catch (err) {
|
|
6575
6636
|
dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
|
|
6576
6637
|
}
|
|
@@ -7395,15 +7456,15 @@ var deriveQaScopeFromIssue = async (ctx) => {
|
|
|
7395
7456
|
|
|
7396
7457
|
// src/scripts/diagMcp.ts
|
|
7397
7458
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
7398
|
-
import * as
|
|
7459
|
+
import * as fs28 from "fs";
|
|
7399
7460
|
import * as os4 from "os";
|
|
7400
|
-
import * as
|
|
7461
|
+
import * as path25 from "path";
|
|
7401
7462
|
var diagMcp = async (_ctx) => {
|
|
7402
7463
|
const home = os4.homedir();
|
|
7403
|
-
const cacheDir =
|
|
7464
|
+
const cacheDir = path25.join(home, ".cache", "ms-playwright");
|
|
7404
7465
|
let entries = [];
|
|
7405
7466
|
try {
|
|
7406
|
-
entries =
|
|
7467
|
+
entries = fs28.readdirSync(cacheDir);
|
|
7407
7468
|
} catch {
|
|
7408
7469
|
}
|
|
7409
7470
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -7429,17 +7490,17 @@ var diagMcp = async (_ctx) => {
|
|
|
7429
7490
|
};
|
|
7430
7491
|
|
|
7431
7492
|
// src/scripts/discoverQaContext.ts
|
|
7432
|
-
import * as
|
|
7433
|
-
import * as
|
|
7493
|
+
import * as fs30 from "fs";
|
|
7494
|
+
import * as path27 from "path";
|
|
7434
7495
|
|
|
7435
7496
|
// src/scripts/frameworkDetectors.ts
|
|
7436
|
-
import * as
|
|
7437
|
-
import * as
|
|
7497
|
+
import * as fs29 from "fs";
|
|
7498
|
+
import * as path26 from "path";
|
|
7438
7499
|
function detectFrameworks(cwd) {
|
|
7439
7500
|
const out = [];
|
|
7440
7501
|
let deps = {};
|
|
7441
7502
|
try {
|
|
7442
|
-
const pkg = JSON.parse(
|
|
7503
|
+
const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
|
|
7443
7504
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7444
7505
|
} catch {
|
|
7445
7506
|
return out;
|
|
@@ -7476,7 +7537,7 @@ function detectFrameworks(cwd) {
|
|
|
7476
7537
|
}
|
|
7477
7538
|
function findFile(cwd, candidates) {
|
|
7478
7539
|
for (const c of candidates) {
|
|
7479
|
-
if (
|
|
7540
|
+
if (fs29.existsSync(path26.join(cwd, c))) return c;
|
|
7480
7541
|
}
|
|
7481
7542
|
return null;
|
|
7482
7543
|
}
|
|
@@ -7489,18 +7550,18 @@ var COLLECTION_DIRS = [
|
|
|
7489
7550
|
function discoverPayloadCollections(cwd) {
|
|
7490
7551
|
const out = [];
|
|
7491
7552
|
for (const dir of COLLECTION_DIRS) {
|
|
7492
|
-
const full =
|
|
7493
|
-
if (!
|
|
7553
|
+
const full = path26.join(cwd, dir);
|
|
7554
|
+
if (!fs29.existsSync(full)) continue;
|
|
7494
7555
|
let files;
|
|
7495
7556
|
try {
|
|
7496
|
-
files =
|
|
7557
|
+
files = fs29.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7497
7558
|
} catch {
|
|
7498
7559
|
continue;
|
|
7499
7560
|
}
|
|
7500
7561
|
for (const file of files) {
|
|
7501
7562
|
try {
|
|
7502
|
-
const filePath =
|
|
7503
|
-
const content =
|
|
7563
|
+
const filePath = path26.join(full, file);
|
|
7564
|
+
const content = fs29.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
7504
7565
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
7505
7566
|
if (!slugMatch) continue;
|
|
7506
7567
|
const slug = slugMatch[1];
|
|
@@ -7514,7 +7575,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
7514
7575
|
out.push({
|
|
7515
7576
|
name,
|
|
7516
7577
|
slug,
|
|
7517
|
-
filePath:
|
|
7578
|
+
filePath: path26.relative(cwd, filePath),
|
|
7518
7579
|
fields: fields.slice(0, 20),
|
|
7519
7580
|
hasAdmin
|
|
7520
7581
|
});
|
|
@@ -7528,28 +7589,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
7528
7589
|
function discoverAdminComponents(cwd, collections) {
|
|
7529
7590
|
const out = [];
|
|
7530
7591
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
7531
|
-
const full =
|
|
7532
|
-
if (!
|
|
7592
|
+
const full = path26.join(cwd, dir);
|
|
7593
|
+
if (!fs29.existsSync(full)) continue;
|
|
7533
7594
|
let entries;
|
|
7534
7595
|
try {
|
|
7535
|
-
entries =
|
|
7596
|
+
entries = fs29.readdirSync(full, { withFileTypes: true });
|
|
7536
7597
|
} catch {
|
|
7537
7598
|
continue;
|
|
7538
7599
|
}
|
|
7539
7600
|
for (const entry of entries) {
|
|
7540
|
-
const entryPath =
|
|
7601
|
+
const entryPath = path26.join(full, entry.name);
|
|
7541
7602
|
let name;
|
|
7542
7603
|
let filePath;
|
|
7543
7604
|
if (entry.isDirectory()) {
|
|
7544
7605
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
7545
|
-
(f) =>
|
|
7606
|
+
(f) => fs29.existsSync(path26.join(entryPath, f))
|
|
7546
7607
|
);
|
|
7547
7608
|
if (!indexFile) continue;
|
|
7548
7609
|
name = entry.name;
|
|
7549
|
-
filePath =
|
|
7610
|
+
filePath = path26.relative(cwd, path26.join(entryPath, indexFile));
|
|
7550
7611
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
7551
7612
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
7552
|
-
filePath =
|
|
7613
|
+
filePath = path26.relative(cwd, entryPath);
|
|
7553
7614
|
} else {
|
|
7554
7615
|
continue;
|
|
7555
7616
|
}
|
|
@@ -7557,7 +7618,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
7557
7618
|
if (collections) {
|
|
7558
7619
|
for (const col of collections) {
|
|
7559
7620
|
try {
|
|
7560
|
-
const colContent =
|
|
7621
|
+
const colContent = fs29.readFileSync(path26.join(cwd, col.filePath), "utf-8");
|
|
7561
7622
|
if (colContent.includes(name)) {
|
|
7562
7623
|
usedInCollection = col.slug;
|
|
7563
7624
|
break;
|
|
@@ -7576,8 +7637,8 @@ function scanApiRoutes(cwd) {
|
|
|
7576
7637
|
const out = [];
|
|
7577
7638
|
const appDirs = ["src/app", "app"];
|
|
7578
7639
|
for (const appDir of appDirs) {
|
|
7579
|
-
const apiDir =
|
|
7580
|
-
if (!
|
|
7640
|
+
const apiDir = path26.join(cwd, appDir, "api");
|
|
7641
|
+
if (!fs29.existsSync(apiDir)) continue;
|
|
7581
7642
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
7582
7643
|
break;
|
|
7583
7644
|
}
|
|
@@ -7586,14 +7647,14 @@ function scanApiRoutes(cwd) {
|
|
|
7586
7647
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
7587
7648
|
let entries;
|
|
7588
7649
|
try {
|
|
7589
|
-
entries =
|
|
7650
|
+
entries = fs29.readdirSync(dir, { withFileTypes: true });
|
|
7590
7651
|
} catch {
|
|
7591
7652
|
return;
|
|
7592
7653
|
}
|
|
7593
7654
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
7594
7655
|
if (routeFile) {
|
|
7595
7656
|
try {
|
|
7596
|
-
const content =
|
|
7657
|
+
const content = fs29.readFileSync(path26.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
7597
7658
|
const methods = HTTP_METHODS.filter(
|
|
7598
7659
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
7599
7660
|
);
|
|
@@ -7601,7 +7662,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7601
7662
|
out.push({
|
|
7602
7663
|
path: prefix,
|
|
7603
7664
|
methods,
|
|
7604
|
-
filePath:
|
|
7665
|
+
filePath: path26.relative(cwd, path26.join(dir, routeFile.name))
|
|
7605
7666
|
});
|
|
7606
7667
|
}
|
|
7607
7668
|
} catch {
|
|
@@ -7612,7 +7673,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7612
7673
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7613
7674
|
let segment = entry.name;
|
|
7614
7675
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7615
|
-
walkApiRoutes(
|
|
7676
|
+
walkApiRoutes(path26.join(dir, entry.name), prefix, cwd, out);
|
|
7616
7677
|
continue;
|
|
7617
7678
|
}
|
|
7618
7679
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7620,7 +7681,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7620
7681
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7621
7682
|
segment = `:${segment.slice(1, -1)}`;
|
|
7622
7683
|
}
|
|
7623
|
-
walkApiRoutes(
|
|
7684
|
+
walkApiRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
7624
7685
|
}
|
|
7625
7686
|
}
|
|
7626
7687
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -7640,10 +7701,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
7640
7701
|
function scanEnvVars(cwd) {
|
|
7641
7702
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
7642
7703
|
for (const envFile of candidates) {
|
|
7643
|
-
const envPath =
|
|
7644
|
-
if (!
|
|
7704
|
+
const envPath = path26.join(cwd, envFile);
|
|
7705
|
+
if (!fs29.existsSync(envPath)) continue;
|
|
7645
7706
|
try {
|
|
7646
|
-
const content =
|
|
7707
|
+
const content = fs29.readFileSync(envPath, "utf-8");
|
|
7647
7708
|
const vars = [];
|
|
7648
7709
|
for (const line of content.split("\n")) {
|
|
7649
7710
|
const trimmed = line.trim();
|
|
@@ -7691,9 +7752,9 @@ function runQaDiscovery(cwd) {
|
|
|
7691
7752
|
}
|
|
7692
7753
|
function detectDevServer(cwd, out) {
|
|
7693
7754
|
try {
|
|
7694
|
-
const pkg = JSON.parse(
|
|
7755
|
+
const pkg = JSON.parse(fs30.readFileSync(path27.join(cwd, "package.json"), "utf-8"));
|
|
7695
7756
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7696
|
-
const pm =
|
|
7757
|
+
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";
|
|
7697
7758
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
7698
7759
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
7699
7760
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -7703,8 +7764,8 @@ function detectDevServer(cwd, out) {
|
|
|
7703
7764
|
function scanFrontendRoutes(cwd, out) {
|
|
7704
7765
|
const appDirs = ["src/app", "app"];
|
|
7705
7766
|
for (const appDir of appDirs) {
|
|
7706
|
-
const full =
|
|
7707
|
-
if (!
|
|
7767
|
+
const full = path27.join(cwd, appDir);
|
|
7768
|
+
if (!fs30.existsSync(full)) continue;
|
|
7708
7769
|
walkFrontendRoutes(full, "", out);
|
|
7709
7770
|
break;
|
|
7710
7771
|
}
|
|
@@ -7712,7 +7773,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
7712
7773
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
7713
7774
|
let entries;
|
|
7714
7775
|
try {
|
|
7715
|
-
entries =
|
|
7776
|
+
entries = fs30.readdirSync(dir, { withFileTypes: true });
|
|
7716
7777
|
} catch {
|
|
7717
7778
|
return;
|
|
7718
7779
|
}
|
|
@@ -7729,7 +7790,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7729
7790
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7730
7791
|
let segment = entry.name;
|
|
7731
7792
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7732
|
-
walkFrontendRoutes(
|
|
7793
|
+
walkFrontendRoutes(path27.join(dir, entry.name), prefix, out);
|
|
7733
7794
|
continue;
|
|
7734
7795
|
}
|
|
7735
7796
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7737,7 +7798,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7737
7798
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7738
7799
|
segment = `:${segment.slice(1, -1)}`;
|
|
7739
7800
|
}
|
|
7740
|
-
walkFrontendRoutes(
|
|
7801
|
+
walkFrontendRoutes(path27.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
7741
7802
|
}
|
|
7742
7803
|
}
|
|
7743
7804
|
function detectAuthFiles(cwd, out) {
|
|
@@ -7754,23 +7815,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
7754
7815
|
"src/app/api/oauth"
|
|
7755
7816
|
];
|
|
7756
7817
|
for (const c of candidates) {
|
|
7757
|
-
if (
|
|
7818
|
+
if (fs30.existsSync(path27.join(cwd, c))) out.authFiles.push(c);
|
|
7758
7819
|
}
|
|
7759
7820
|
}
|
|
7760
7821
|
function detectRoles(cwd, out) {
|
|
7761
7822
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
7762
7823
|
for (const rp of rolePaths) {
|
|
7763
|
-
const dir =
|
|
7764
|
-
if (!
|
|
7824
|
+
const dir = path27.join(cwd, rp);
|
|
7825
|
+
if (!fs30.existsSync(dir)) continue;
|
|
7765
7826
|
let files;
|
|
7766
7827
|
try {
|
|
7767
|
-
files =
|
|
7828
|
+
files = fs30.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7768
7829
|
} catch {
|
|
7769
7830
|
continue;
|
|
7770
7831
|
}
|
|
7771
7832
|
for (const f of files) {
|
|
7772
7833
|
try {
|
|
7773
|
-
const content =
|
|
7834
|
+
const content = fs30.readFileSync(path27.join(dir, f), "utf-8").slice(0, 5e3);
|
|
7774
7835
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
7775
7836
|
if (roleMatches) {
|
|
7776
7837
|
for (const m of roleMatches) {
|
|
@@ -7953,8 +8014,8 @@ ${stateBody}`;
|
|
|
7953
8014
|
};
|
|
7954
8015
|
|
|
7955
8016
|
// src/scripts/dispatchJobFileTicks.ts
|
|
7956
|
-
import * as
|
|
7957
|
-
import * as
|
|
8017
|
+
import * as fs32 from "fs";
|
|
8018
|
+
import * as path29 from "path";
|
|
7958
8019
|
|
|
7959
8020
|
// src/scripts/jobFrontmatter.ts
|
|
7960
8021
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -7970,7 +8031,7 @@ var SCHEDULE_EVERY_VALUES = [
|
|
|
7970
8031
|
"manual"
|
|
7971
8032
|
];
|
|
7972
8033
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
7973
|
-
function
|
|
8034
|
+
function splitFrontmatter2(raw) {
|
|
7974
8035
|
const match = FRONTMATTER_RE.exec(raw);
|
|
7975
8036
|
if (!match) return { frontmatter: {}, body: raw };
|
|
7976
8037
|
const inner = match[1] ?? "";
|
|
@@ -8247,8 +8308,8 @@ function isShaConflict(err) {
|
|
|
8247
8308
|
}
|
|
8248
8309
|
|
|
8249
8310
|
// src/scripts/jobState/localFileBackend.ts
|
|
8250
|
-
import * as
|
|
8251
|
-
import * as
|
|
8311
|
+
import * as fs31 from "fs";
|
|
8312
|
+
import * as path28 from "path";
|
|
8252
8313
|
var LocalFileBackend = class {
|
|
8253
8314
|
name = "local-file";
|
|
8254
8315
|
cwd;
|
|
@@ -8263,7 +8324,7 @@ var LocalFileBackend = class {
|
|
|
8263
8324
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
8264
8325
|
this.cwd = opts.cwd;
|
|
8265
8326
|
this.jobsDir = opts.jobsDir;
|
|
8266
|
-
this.absDir =
|
|
8327
|
+
this.absDir = path28.join(opts.cwd, opts.jobsDir);
|
|
8267
8328
|
this.owner = opts.owner;
|
|
8268
8329
|
this.repo = opts.repo;
|
|
8269
8330
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -8278,7 +8339,7 @@ var LocalFileBackend = class {
|
|
|
8278
8339
|
`);
|
|
8279
8340
|
return;
|
|
8280
8341
|
}
|
|
8281
|
-
|
|
8342
|
+
fs31.mkdirSync(this.absDir, { recursive: true });
|
|
8282
8343
|
const prefix = this.cacheKeyPrefix();
|
|
8283
8344
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
8284
8345
|
try {
|
|
@@ -8307,7 +8368,7 @@ var LocalFileBackend = class {
|
|
|
8307
8368
|
`);
|
|
8308
8369
|
return;
|
|
8309
8370
|
}
|
|
8310
|
-
if (!
|
|
8371
|
+
if (!fs31.existsSync(this.absDir)) {
|
|
8311
8372
|
return;
|
|
8312
8373
|
}
|
|
8313
8374
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -8323,11 +8384,11 @@ var LocalFileBackend = class {
|
|
|
8323
8384
|
}
|
|
8324
8385
|
load(slug) {
|
|
8325
8386
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
8326
|
-
const absPath =
|
|
8327
|
-
if (!
|
|
8387
|
+
const absPath = path28.join(this.cwd, relPath);
|
|
8388
|
+
if (!fs31.existsSync(absPath)) {
|
|
8328
8389
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
8329
8390
|
}
|
|
8330
|
-
const raw =
|
|
8391
|
+
const raw = fs31.readFileSync(absPath, "utf-8");
|
|
8331
8392
|
let parsed;
|
|
8332
8393
|
try {
|
|
8333
8394
|
parsed = JSON.parse(raw);
|
|
@@ -8344,13 +8405,13 @@ var LocalFileBackend = class {
|
|
|
8344
8405
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
8345
8406
|
return false;
|
|
8346
8407
|
}
|
|
8347
|
-
const absPath =
|
|
8348
|
-
|
|
8408
|
+
const absPath = path28.join(this.cwd, loaded.path);
|
|
8409
|
+
fs31.mkdirSync(path28.dirname(absPath), { recursive: true });
|
|
8349
8410
|
const body = `${JSON.stringify(next, null, 2)}
|
|
8350
8411
|
`;
|
|
8351
8412
|
const tmpPath = `${absPath}.${process.pid}.tmp`;
|
|
8352
|
-
|
|
8353
|
-
|
|
8413
|
+
fs31.writeFileSync(tmpPath, body, "utf-8");
|
|
8414
|
+
fs31.renameSync(tmpPath, absPath);
|
|
8354
8415
|
return true;
|
|
8355
8416
|
}
|
|
8356
8417
|
cacheKeyPrefix() {
|
|
@@ -8428,7 +8489,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
8428
8489
|
await backend.hydrate();
|
|
8429
8490
|
}
|
|
8430
8491
|
try {
|
|
8431
|
-
const slugs = listJobSlugs(
|
|
8492
|
+
const slugs = listJobSlugs(path29.join(ctx.cwd, jobsDir));
|
|
8432
8493
|
ctx.data.jobSlugCount = slugs.length;
|
|
8433
8494
|
if (slugs.length === 0) {
|
|
8434
8495
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -8479,6 +8540,56 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
8479
8540
|
} catch (err) {
|
|
8480
8541
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8481
8542
|
process.stderr.write(`[jobs] tick ${slug} crashed: ${msg}
|
|
8543
|
+
`);
|
|
8544
|
+
results.push({ slug, exitCode: 99, reason: msg });
|
|
8545
|
+
}
|
|
8546
|
+
}
|
|
8547
|
+
const folderSlugs = listFolderDutySlugs(path29.join(ctx.cwd, jobsDir));
|
|
8548
|
+
for (const slug of folderSlugs) {
|
|
8549
|
+
let every;
|
|
8550
|
+
let staff;
|
|
8551
|
+
try {
|
|
8552
|
+
const profile = loadProfile(path29.join(ctx.cwd, jobsDir, slug, "profile.json"));
|
|
8553
|
+
every = profile.every;
|
|
8554
|
+
staff = profile.staff;
|
|
8555
|
+
} catch (err) {
|
|
8556
|
+
process.stderr.write(`[jobs] \u23ED skip folder-duty ${slug}: profile load failed: ${String(err)}
|
|
8557
|
+
`);
|
|
8558
|
+
continue;
|
|
8559
|
+
}
|
|
8560
|
+
if (!every) continue;
|
|
8561
|
+
if (!staff || staff.trim().length === 0) {
|
|
8562
|
+
process.stderr.write(`[jobs] \u23ED skip ${slug}: scheduled duty has no staff
|
|
8563
|
+
`);
|
|
8564
|
+
results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
|
|
8565
|
+
continue;
|
|
8566
|
+
}
|
|
8567
|
+
const decision = await decideShouldFire(every, slug, backend, now);
|
|
8568
|
+
if (decision.skip) {
|
|
8569
|
+
process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
|
|
8570
|
+
`);
|
|
8571
|
+
results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
|
|
8572
|
+
continue;
|
|
8573
|
+
}
|
|
8574
|
+
await stampFired(backend, slug, now);
|
|
8575
|
+
process.stdout.write(`[jobs] \u2192 run scheduled duty ${slug} (one-shot, as ${staff})
|
|
8576
|
+
`);
|
|
8577
|
+
try {
|
|
8578
|
+
const out = await runExecutable(slug, {
|
|
8579
|
+
cliArgs: {},
|
|
8580
|
+
cwd: ctx.cwd,
|
|
8581
|
+
config: ctx.config,
|
|
8582
|
+
verbose: ctx.verbose,
|
|
8583
|
+
quiet: ctx.quiet
|
|
8584
|
+
});
|
|
8585
|
+
results.push({ slug, exitCode: out.exitCode, reason: out.reason });
|
|
8586
|
+
if (out.exitCode !== 0) {
|
|
8587
|
+
process.stderr.write(`[jobs] scheduled duty ${slug} failed (exit ${out.exitCode}): ${out.reason ?? ""}
|
|
8588
|
+
`);
|
|
8589
|
+
}
|
|
8590
|
+
} catch (err) {
|
|
8591
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8592
|
+
process.stderr.write(`[jobs] scheduled duty ${slug} crashed: ${msg}
|
|
8482
8593
|
`);
|
|
8483
8594
|
results.push({ slug, exitCode: 99, reason: msg });
|
|
8484
8595
|
}
|
|
@@ -8539,22 +8650,42 @@ function formatAgo(ms) {
|
|
|
8539
8650
|
}
|
|
8540
8651
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
8541
8652
|
try {
|
|
8542
|
-
const raw =
|
|
8543
|
-
return
|
|
8653
|
+
const raw = fs32.readFileSync(path29.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
8654
|
+
return splitFrontmatter2(raw).frontmatter;
|
|
8544
8655
|
} catch {
|
|
8545
8656
|
return {};
|
|
8546
8657
|
}
|
|
8547
8658
|
}
|
|
8548
8659
|
function listJobSlugs(absDir) {
|
|
8549
|
-
if (!
|
|
8660
|
+
if (!fs32.existsSync(absDir)) return [];
|
|
8550
8661
|
let entries;
|
|
8551
8662
|
try {
|
|
8552
|
-
entries =
|
|
8663
|
+
entries = fs32.readdirSync(absDir, { withFileTypes: true });
|
|
8553
8664
|
} catch {
|
|
8554
8665
|
return [];
|
|
8555
8666
|
}
|
|
8556
8667
|
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();
|
|
8557
8668
|
}
|
|
8669
|
+
function listFolderDutySlugs(absDir) {
|
|
8670
|
+
if (!fs32.existsSync(absDir)) return [];
|
|
8671
|
+
let entries;
|
|
8672
|
+
try {
|
|
8673
|
+
entries = fs32.readdirSync(absDir, { withFileTypes: true });
|
|
8674
|
+
} catch {
|
|
8675
|
+
return [];
|
|
8676
|
+
}
|
|
8677
|
+
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();
|
|
8678
|
+
}
|
|
8679
|
+
async function stampFired(backend, slug, now) {
|
|
8680
|
+
try {
|
|
8681
|
+
const loaded = await backend.load(slug);
|
|
8682
|
+
const nextData = { ...loaded.state.data ?? {}, lastFiredAt: new Date(now).toISOString() };
|
|
8683
|
+
await backend.save(loaded, { ...loaded.state, data: nextData });
|
|
8684
|
+
} catch (err) {
|
|
8685
|
+
process.stderr.write(`[jobs] failed to stamp lastFiredAt for ${slug}: ${String(err)}
|
|
8686
|
+
`);
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8558
8689
|
|
|
8559
8690
|
// src/scripts/dispatchJobTicks.ts
|
|
8560
8691
|
init_issue();
|
|
@@ -9608,12 +9739,12 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
9608
9739
|
|
|
9609
9740
|
// src/scripts/initFlow.ts
|
|
9610
9741
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
9611
|
-
import * as
|
|
9612
|
-
import * as
|
|
9742
|
+
import * as fs33 from "fs";
|
|
9743
|
+
import * as path30 from "path";
|
|
9613
9744
|
function detectPackageManager(cwd) {
|
|
9614
|
-
if (
|
|
9615
|
-
if (
|
|
9616
|
-
if (
|
|
9745
|
+
if (fs33.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9746
|
+
if (fs33.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
|
|
9747
|
+
if (fs33.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
|
|
9617
9748
|
return "npm";
|
|
9618
9749
|
}
|
|
9619
9750
|
function qualityCommandsFor(pm) {
|
|
@@ -9742,36 +9873,36 @@ function performInit(cwd, force) {
|
|
|
9742
9873
|
const pm = detectPackageManager(cwd);
|
|
9743
9874
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
9744
9875
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
9745
|
-
const configPath =
|
|
9746
|
-
if (
|
|
9876
|
+
const configPath = path30.join(cwd, "kody.config.json");
|
|
9877
|
+
if (fs33.existsSync(configPath) && !force) {
|
|
9747
9878
|
skipped.push("kody.config.json");
|
|
9748
9879
|
} else {
|
|
9749
9880
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
|
|
9750
|
-
|
|
9881
|
+
fs33.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
9751
9882
|
`);
|
|
9752
9883
|
wrote.push("kody.config.json");
|
|
9753
9884
|
}
|
|
9754
|
-
const workflowDir =
|
|
9755
|
-
const workflowPath =
|
|
9756
|
-
if (
|
|
9885
|
+
const workflowDir = path30.join(cwd, ".github", "workflows");
|
|
9886
|
+
const workflowPath = path30.join(workflowDir, "kody.yml");
|
|
9887
|
+
if (fs33.existsSync(workflowPath) && !force) {
|
|
9757
9888
|
skipped.push(".github/workflows/kody.yml");
|
|
9758
9889
|
} else {
|
|
9759
|
-
|
|
9760
|
-
|
|
9890
|
+
fs33.mkdirSync(workflowDir, { recursive: true });
|
|
9891
|
+
fs33.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
9761
9892
|
wrote.push(".github/workflows/kody.yml");
|
|
9762
9893
|
}
|
|
9763
9894
|
const builtinJobs = listBuiltinJobs();
|
|
9764
9895
|
if (builtinJobs.length > 0) {
|
|
9765
|
-
const jobsDir =
|
|
9766
|
-
|
|
9896
|
+
const jobsDir = path30.join(cwd, ".kody", "duties");
|
|
9897
|
+
fs33.mkdirSync(jobsDir, { recursive: true });
|
|
9767
9898
|
for (const job of builtinJobs) {
|
|
9768
|
-
const rel =
|
|
9769
|
-
const target =
|
|
9770
|
-
if (
|
|
9899
|
+
const rel = path30.join(".kody", "duties", `${job.slug}.md`);
|
|
9900
|
+
const target = path30.join(cwd, rel);
|
|
9901
|
+
if (fs33.existsSync(target) && !force) {
|
|
9771
9902
|
skipped.push(rel);
|
|
9772
9903
|
continue;
|
|
9773
9904
|
}
|
|
9774
|
-
|
|
9905
|
+
fs33.writeFileSync(target, fs33.readFileSync(job.filePath, "utf-8"));
|
|
9775
9906
|
wrote.push(rel);
|
|
9776
9907
|
}
|
|
9777
9908
|
}
|
|
@@ -9783,12 +9914,12 @@ function performInit(cwd, force) {
|
|
|
9783
9914
|
continue;
|
|
9784
9915
|
}
|
|
9785
9916
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
9786
|
-
const target =
|
|
9787
|
-
if (
|
|
9917
|
+
const target = path30.join(workflowDir, `kody-${exe.name}.yml`);
|
|
9918
|
+
if (fs33.existsSync(target) && !force) {
|
|
9788
9919
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9789
9920
|
continue;
|
|
9790
9921
|
}
|
|
9791
|
-
|
|
9922
|
+
fs33.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
9792
9923
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9793
9924
|
}
|
|
9794
9925
|
let labels;
|
|
@@ -9973,8 +10104,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
9973
10104
|
|
|
9974
10105
|
// src/scripts/loadJobFromFile.ts
|
|
9975
10106
|
init_dutyMcp();
|
|
9976
|
-
import * as
|
|
9977
|
-
import * as
|
|
10107
|
+
import * as fs34 from "fs";
|
|
10108
|
+
import * as path31 from "path";
|
|
9978
10109
|
var DUTY_TOOL_PALETTE = new Set(DUTY_MCP_TOOL_NAMES);
|
|
9979
10110
|
var loadJobFromFile = async (ctx, profile, args) => {
|
|
9980
10111
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -9984,23 +10115,23 @@ var loadJobFromFile = async (ctx, profile, args) => {
|
|
|
9984
10115
|
if (!slug) {
|
|
9985
10116
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
9986
10117
|
}
|
|
9987
|
-
const absPath =
|
|
9988
|
-
if (!
|
|
10118
|
+
const absPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
10119
|
+
if (!fs34.existsSync(absPath)) {
|
|
9989
10120
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
9990
10121
|
}
|
|
9991
|
-
const raw =
|
|
10122
|
+
const raw = fs34.readFileSync(absPath, "utf-8");
|
|
9992
10123
|
const { title, body } = parseJobFile(raw, slug);
|
|
9993
|
-
const frontmatter =
|
|
10124
|
+
const frontmatter = splitFrontmatter2(raw).frontmatter;
|
|
9994
10125
|
const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
9995
10126
|
const workerSlug = (frontmatter.staff ?? "").trim();
|
|
9996
10127
|
let workerTitle = "";
|
|
9997
10128
|
let workerPersona = "";
|
|
9998
10129
|
if (workerSlug) {
|
|
9999
|
-
const workerPath =
|
|
10000
|
-
if (!
|
|
10130
|
+
const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
10131
|
+
if (!fs34.existsSync(workerPath)) {
|
|
10001
10132
|
throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
|
|
10002
10133
|
}
|
|
10003
|
-
const workerRaw =
|
|
10134
|
+
const workerRaw = fs34.readFileSync(workerPath, "utf-8");
|
|
10004
10135
|
const parsed = parseJobFile(workerRaw, workerSlug);
|
|
10005
10136
|
workerTitle = parsed.title;
|
|
10006
10137
|
workerPersona = parsed.body;
|
|
@@ -10084,18 +10215,18 @@ init_loadMemoryContext();
|
|
|
10084
10215
|
init_loadPriorArt();
|
|
10085
10216
|
|
|
10086
10217
|
// src/scripts/loadQaContext.ts
|
|
10087
|
-
import * as
|
|
10088
|
-
import * as
|
|
10218
|
+
import * as fs36 from "fs";
|
|
10219
|
+
import * as path33 from "path";
|
|
10089
10220
|
|
|
10090
10221
|
// src/scripts/kodyVariables.ts
|
|
10091
|
-
import * as
|
|
10092
|
-
import * as
|
|
10222
|
+
import * as fs35 from "fs";
|
|
10223
|
+
import * as path32 from "path";
|
|
10093
10224
|
var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
|
|
10094
10225
|
function readKodyVariables(cwd) {
|
|
10095
|
-
const full =
|
|
10226
|
+
const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
10096
10227
|
let raw;
|
|
10097
10228
|
try {
|
|
10098
|
-
raw =
|
|
10229
|
+
raw = fs35.readFileSync(full, "utf-8");
|
|
10099
10230
|
} catch {
|
|
10100
10231
|
return {};
|
|
10101
10232
|
}
|
|
@@ -10146,18 +10277,18 @@ function readProfileStaff(raw) {
|
|
|
10146
10277
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
10147
10278
|
}
|
|
10148
10279
|
function readProfile(cwd) {
|
|
10149
|
-
const dir =
|
|
10150
|
-
if (!
|
|
10280
|
+
const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
10281
|
+
if (!fs36.existsSync(dir)) return "";
|
|
10151
10282
|
let entries;
|
|
10152
10283
|
try {
|
|
10153
|
-
entries =
|
|
10284
|
+
entries = fs36.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
10154
10285
|
} catch {
|
|
10155
10286
|
return "";
|
|
10156
10287
|
}
|
|
10157
10288
|
const blocks = [];
|
|
10158
10289
|
for (const file of entries) {
|
|
10159
10290
|
try {
|
|
10160
|
-
const raw =
|
|
10291
|
+
const raw = fs36.readFileSync(path33.join(dir, file), "utf-8");
|
|
10161
10292
|
const { staff, body } = readProfileStaff(raw);
|
|
10162
10293
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
10163
10294
|
blocks.push(`## ${file}
|
|
@@ -10194,8 +10325,8 @@ var loadQaContext = async (ctx) => {
|
|
|
10194
10325
|
init_events();
|
|
10195
10326
|
|
|
10196
10327
|
// src/taskContext.ts
|
|
10197
|
-
import * as
|
|
10198
|
-
import * as
|
|
10328
|
+
import * as fs37 from "fs";
|
|
10329
|
+
import * as path34 from "path";
|
|
10199
10330
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
10200
10331
|
function buildTaskContext(args) {
|
|
10201
10332
|
return {
|
|
@@ -10211,10 +10342,10 @@ function buildTaskContext(args) {
|
|
|
10211
10342
|
}
|
|
10212
10343
|
function persistTaskContext(cwd, ctx) {
|
|
10213
10344
|
try {
|
|
10214
|
-
const dir =
|
|
10215
|
-
|
|
10216
|
-
const file =
|
|
10217
|
-
|
|
10345
|
+
const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
|
|
10346
|
+
fs37.mkdirSync(dir, { recursive: true });
|
|
10347
|
+
const file = path34.join(dir, "task-context.json");
|
|
10348
|
+
fs37.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
10218
10349
|
`);
|
|
10219
10350
|
return file;
|
|
10220
10351
|
} catch (err) {
|
|
@@ -10280,19 +10411,19 @@ var loadTaskState = async (ctx) => {
|
|
|
10280
10411
|
};
|
|
10281
10412
|
|
|
10282
10413
|
// src/scripts/loadWorkerAdhoc.ts
|
|
10283
|
-
import * as
|
|
10284
|
-
import * as
|
|
10414
|
+
import * as fs38 from "fs";
|
|
10415
|
+
import * as path35 from "path";
|
|
10285
10416
|
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
10286
10417
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
10287
10418
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
10288
10419
|
if (!workerSlug) {
|
|
10289
10420
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
10290
10421
|
}
|
|
10291
|
-
const workerPath =
|
|
10292
|
-
if (!
|
|
10422
|
+
const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
10423
|
+
if (!fs38.existsSync(workerPath)) {
|
|
10293
10424
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
10294
10425
|
}
|
|
10295
|
-
const { title, body } = parsePersona(
|
|
10426
|
+
const { title, body } = parsePersona(fs38.readFileSync(workerPath, "utf-8"), workerSlug);
|
|
10296
10427
|
const message = resolveMessage(ctx.args.message);
|
|
10297
10428
|
if (!message) {
|
|
10298
10429
|
throw new Error(
|
|
@@ -10312,9 +10443,9 @@ function resolveMessage(messageArg) {
|
|
|
10312
10443
|
}
|
|
10313
10444
|
function readCommentBody() {
|
|
10314
10445
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
10315
|
-
if (!eventPath || !
|
|
10446
|
+
if (!eventPath || !fs38.existsSync(eventPath)) return "";
|
|
10316
10447
|
try {
|
|
10317
|
-
const event = JSON.parse(
|
|
10448
|
+
const event = JSON.parse(fs38.readFileSync(eventPath, "utf-8"));
|
|
10318
10449
|
return String(event.comment?.body ?? "");
|
|
10319
10450
|
} catch {
|
|
10320
10451
|
return "";
|
|
@@ -10338,7 +10469,7 @@ function stripDirective(body) {
|
|
|
10338
10469
|
return lines.slice(start).join("\n").trim();
|
|
10339
10470
|
}
|
|
10340
10471
|
function parsePersona(raw, slug) {
|
|
10341
|
-
const stripped =
|
|
10472
|
+
const stripped = splitFrontmatter2(raw).body;
|
|
10342
10473
|
const trimmed = stripped.trim();
|
|
10343
10474
|
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
10344
10475
|
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
@@ -12633,7 +12764,7 @@ function resolveBaseOverride(value) {
|
|
|
12633
12764
|
|
|
12634
12765
|
// src/scripts/runnerServe.ts
|
|
12635
12766
|
import { spawn as spawn5 } from "child_process";
|
|
12636
|
-
import * as
|
|
12767
|
+
import * as fs39 from "fs";
|
|
12637
12768
|
import { createServer as createServer3 } from "http";
|
|
12638
12769
|
var DEFAULT_PORT2 = 8080;
|
|
12639
12770
|
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
@@ -12713,8 +12844,8 @@ async function defaultRunJob(job) {
|
|
|
12713
12844
|
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
12714
12845
|
const branch = job.ref ?? "main";
|
|
12715
12846
|
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
12716
|
-
|
|
12717
|
-
|
|
12847
|
+
fs39.rmSync(workdir, { recursive: true, force: true });
|
|
12848
|
+
fs39.mkdirSync(workdir, { recursive: true });
|
|
12718
12849
|
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
12719
12850
|
const interactive = job.mode === "interactive";
|
|
12720
12851
|
const scheduled = job.mode === "scheduled";
|
|
@@ -12847,7 +12978,7 @@ var runnerServe = async (ctx) => {
|
|
|
12847
12978
|
|
|
12848
12979
|
// src/scripts/runPreviewBuild.ts
|
|
12849
12980
|
import { copyFile, writeFile } from "fs/promises";
|
|
12850
|
-
import * as
|
|
12981
|
+
import * as path36 from "path";
|
|
12851
12982
|
import { fileURLToPath } from "url";
|
|
12852
12983
|
|
|
12853
12984
|
// src/scripts/previewBuildHelpers.ts
|
|
@@ -12996,9 +13127,9 @@ var FLY_MACHINES = "https://api.machines.dev/v1";
|
|
|
12996
13127
|
var FLY_GRAPHQL = "https://api.fly.io/graphql";
|
|
12997
13128
|
var REQ_TIMEOUT_MS2 = 3e4;
|
|
12998
13129
|
function bundledDockerfilePath(mode) {
|
|
12999
|
-
const here =
|
|
13130
|
+
const here = path36.dirname(fileURLToPath(import.meta.url));
|
|
13000
13131
|
const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
|
|
13001
|
-
return
|
|
13132
|
+
return path36.join(here, "preview-build-templates", file);
|
|
13002
13133
|
}
|
|
13003
13134
|
function required(name) {
|
|
13004
13135
|
const v = (process.env[name] ?? "").trim();
|
|
@@ -13247,10 +13378,10 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
|
|
|
13247
13378
|
console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
|
|
13248
13379
|
if (Object.keys(buildEnv).length > 0) {
|
|
13249
13380
|
const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
13250
|
-
await writeFile(
|
|
13381
|
+
await writeFile(path36.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
|
|
13251
13382
|
`, "utf8");
|
|
13252
13383
|
}
|
|
13253
|
-
const consumerDockerfile =
|
|
13384
|
+
const consumerDockerfile = path36.join(ctx.cwd, "Dockerfile.preview");
|
|
13254
13385
|
const { stat } = await import("fs/promises");
|
|
13255
13386
|
let hasConsumerDockerfile = false;
|
|
13256
13387
|
try {
|
|
@@ -13350,8 +13481,8 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
|
|
|
13350
13481
|
|
|
13351
13482
|
// src/scripts/runTickScript.ts
|
|
13352
13483
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13353
|
-
import * as
|
|
13354
|
-
import * as
|
|
13484
|
+
import * as fs40 from "fs";
|
|
13485
|
+
import * as path37 from "path";
|
|
13355
13486
|
var runTickScript = async (ctx, _profile, args) => {
|
|
13356
13487
|
ctx.skipAgent = true;
|
|
13357
13488
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -13363,22 +13494,22 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
13363
13494
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
13364
13495
|
return;
|
|
13365
13496
|
}
|
|
13366
|
-
const jobPath =
|
|
13367
|
-
if (!
|
|
13497
|
+
const jobPath = path37.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
13498
|
+
if (!fs40.existsSync(jobPath)) {
|
|
13368
13499
|
ctx.output.exitCode = 99;
|
|
13369
13500
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
13370
13501
|
return;
|
|
13371
13502
|
}
|
|
13372
|
-
const raw =
|
|
13373
|
-
const { frontmatter } =
|
|
13503
|
+
const raw = fs40.readFileSync(jobPath, "utf-8");
|
|
13504
|
+
const { frontmatter } = splitFrontmatter2(raw);
|
|
13374
13505
|
const tickScript = frontmatter.tickScript;
|
|
13375
13506
|
if (!tickScript) {
|
|
13376
13507
|
ctx.output.exitCode = 99;
|
|
13377
13508
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
13378
13509
|
return;
|
|
13379
13510
|
}
|
|
13380
|
-
const scriptPath =
|
|
13381
|
-
if (!
|
|
13511
|
+
const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
|
|
13512
|
+
if (!fs40.existsSync(scriptPath)) {
|
|
13382
13513
|
ctx.output.exitCode = 99;
|
|
13383
13514
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
13384
13515
|
return;
|
|
@@ -14424,7 +14555,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
|
|
|
14424
14555
|
};
|
|
14425
14556
|
|
|
14426
14557
|
// src/scripts/writeRunSummary.ts
|
|
14427
|
-
import * as
|
|
14558
|
+
import * as fs41 from "fs";
|
|
14428
14559
|
var writeRunSummary = async (ctx, profile) => {
|
|
14429
14560
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
14430
14561
|
if (!summaryPath) return;
|
|
@@ -14446,7 +14577,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
14446
14577
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
14447
14578
|
lines.push("");
|
|
14448
14579
|
try {
|
|
14449
|
-
|
|
14580
|
+
fs41.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
14450
14581
|
`);
|
|
14451
14582
|
} catch {
|
|
14452
14583
|
}
|
|
@@ -14554,8 +14685,8 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
14554
14685
|
]);
|
|
14555
14686
|
|
|
14556
14687
|
// src/staff.ts
|
|
14557
|
-
import * as
|
|
14558
|
-
import * as
|
|
14688
|
+
import * as fs42 from "fs";
|
|
14689
|
+
import * as path38 from "path";
|
|
14559
14690
|
var DEFAULT_STAFF_DIR = ".kody/staff";
|
|
14560
14691
|
function stripFrontmatter(raw) {
|
|
14561
14692
|
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
@@ -14564,11 +14695,11 @@ function stripFrontmatter(raw) {
|
|
|
14564
14695
|
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
14565
14696
|
const trimmed = slug.trim();
|
|
14566
14697
|
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
14567
|
-
const staffPath =
|
|
14568
|
-
if (!
|
|
14698
|
+
const staffPath = path38.join(cwd, staffDir, `${trimmed}.md`);
|
|
14699
|
+
if (!fs42.existsSync(staffPath)) {
|
|
14569
14700
|
throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
|
|
14570
14701
|
}
|
|
14571
|
-
const body = stripFrontmatter(
|
|
14702
|
+
const body = stripFrontmatter(fs42.readFileSync(staffPath, "utf-8"));
|
|
14572
14703
|
if (!body) throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
|
|
14573
14704
|
return body;
|
|
14574
14705
|
}
|
|
@@ -14585,48 +14716,6 @@ function framePersona(slug, persona) {
|
|
|
14585
14716
|
].join("\n");
|
|
14586
14717
|
}
|
|
14587
14718
|
|
|
14588
|
-
// src/subagents.ts
|
|
14589
|
-
import * as fs42 from "fs";
|
|
14590
|
-
import * as path38 from "path";
|
|
14591
|
-
function splitFrontmatter2(raw) {
|
|
14592
|
-
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
14593
|
-
if (!match) return { fm: {}, body: raw.trim() };
|
|
14594
|
-
const fm = {};
|
|
14595
|
-
for (const line of match[1].split("\n")) {
|
|
14596
|
-
const idx = line.indexOf(":");
|
|
14597
|
-
if (idx === -1) continue;
|
|
14598
|
-
fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
14599
|
-
}
|
|
14600
|
-
return { fm, body: (match[2] ?? "").trim() };
|
|
14601
|
-
}
|
|
14602
|
-
function resolveAgentFile(profileDir, name) {
|
|
14603
|
-
const local = path38.join(profileDir, "agents", `${name}.md`);
|
|
14604
|
-
if (fs42.existsSync(local)) return local;
|
|
14605
|
-
const central = path38.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
14606
|
-
if (fs42.existsSync(central)) return central;
|
|
14607
|
-
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
14608
|
-
}
|
|
14609
|
-
function loadSubagents(profile) {
|
|
14610
|
-
const names = profile.claudeCode.subagents;
|
|
14611
|
-
if (!names || names.length === 0) return void 0;
|
|
14612
|
-
const agents = {};
|
|
14613
|
-
for (const name of names) {
|
|
14614
|
-
const { fm, body } = splitFrontmatter2(fs42.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
|
|
14615
|
-
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
14616
|
-
const def = {
|
|
14617
|
-
description: fm.description ?? `Subagent ${name}`,
|
|
14618
|
-
prompt: body
|
|
14619
|
-
};
|
|
14620
|
-
if (fm.tools) {
|
|
14621
|
-
const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
|
|
14622
|
-
if (tools.length > 0) def.tools = tools;
|
|
14623
|
-
}
|
|
14624
|
-
if (fm.model) def.model = fm.model;
|
|
14625
|
-
agents[fm.name || name] = def;
|
|
14626
|
-
}
|
|
14627
|
-
return agents;
|
|
14628
|
-
}
|
|
14629
|
-
|
|
14630
14719
|
// src/tools.ts
|
|
14631
14720
|
import { execFileSync as execFileSync27 } from "child_process";
|
|
14632
14721
|
function verifyCliTools(tools, cwd) {
|