@kody-ade/kody-engine 0.4.203 → 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 +654 -398
- package/dist/executables/types.ts +39 -0
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -551,9 +551,11 @@ __export(dutyMcp_exports, {
|
|
|
551
551
|
dispatchWorkflow: () => dispatchWorkflow,
|
|
552
552
|
ensureComment: () => ensureComment,
|
|
553
553
|
ensureIssue: () => ensureIssue,
|
|
554
|
+
isDispatchGated: () => isDispatchGated,
|
|
554
555
|
parseDutyTrustMode: () => parseDutyTrustMode,
|
|
555
556
|
readCheckRuns: () => readCheckRuns,
|
|
556
|
-
readDutyTrustMode: () => readDutyTrustMode
|
|
557
|
+
readDutyTrustMode: () => readDutyTrustMode,
|
|
558
|
+
readThread: () => readThread
|
|
557
559
|
});
|
|
558
560
|
import { createSdkMcpServer as createSdkMcpServer3, tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
|
|
559
561
|
import { z as z3 } from "zod";
|
|
@@ -665,6 +667,24 @@ function readDutyTrustMode(repoSlug, dutySlug) {
|
|
|
665
667
|
return "ask";
|
|
666
668
|
}
|
|
667
669
|
}
|
|
670
|
+
function readThread(repoSlug, number, limit = 10) {
|
|
671
|
+
const meta = JSON.parse(gh(["api", `repos/${repoSlug}/issues/${number}`]));
|
|
672
|
+
const rawComments = JSON.parse(
|
|
673
|
+
gh(["api", `repos/${repoSlug}/issues/${number}/comments?per_page=100`])
|
|
674
|
+
);
|
|
675
|
+
const comments = rawComments.slice(-Math.max(1, limit)).map((c) => ({
|
|
676
|
+
author: c.user?.login ?? "?",
|
|
677
|
+
createdAt: c.created_at ?? "",
|
|
678
|
+
body: (c.body ?? "").slice(0, THREAD_BODY_MAX)
|
|
679
|
+
}));
|
|
680
|
+
return {
|
|
681
|
+
number,
|
|
682
|
+
title: meta.title ?? "",
|
|
683
|
+
state: meta.state ?? "",
|
|
684
|
+
labels: (meta.labels ?? []).map((l) => l.name ?? "").filter(Boolean),
|
|
685
|
+
comments
|
|
686
|
+
};
|
|
687
|
+
}
|
|
668
688
|
function readCheckRuns(repoSlug, ref, ignoreNames) {
|
|
669
689
|
const sha = gh(["api", `repos/${repoSlug}/commits/${ref}`, "--jq", ".sha"]).trim();
|
|
670
690
|
const raw = gh([
|
|
@@ -723,6 +743,11 @@ function dispatchWorkflow(workflowFile, executable, issueNumber) {
|
|
|
723
743
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
724
744
|
}
|
|
725
745
|
}
|
|
746
|
+
function isDispatchGated(executable, mode) {
|
|
747
|
+
if (mode === "auto") return false;
|
|
748
|
+
if (executable && GATE_EXEMPT_EXECUTABLES.has(executable)) return false;
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
726
751
|
function trustRefusal(dutySlug) {
|
|
727
752
|
return `Not dispatched: duty \`${dutySlug ?? "?"}\` is in ASK mode (not trusted for autonomy). Do NOT retry the dispatch. Instead notify the operator (use recommend_to_operator, or rely on the tracking issue that already @-mentions them), then submit_state. To let this duty act on its own, grant it Auto on the dashboard Trust page.`;
|
|
728
753
|
}
|
|
@@ -751,7 +776,7 @@ function buildDutyMcpServer(opts) {
|
|
|
751
776
|
pr: z3.number().int().positive().describe("PR number to repair.")
|
|
752
777
|
},
|
|
753
778
|
async (args) => {
|
|
754
|
-
if (readDutyTrustMode(opts.repoSlug, opts.dutySlug)
|
|
779
|
+
if (isDispatchGated(verb, readDutyTrustMode(opts.repoSlug, opts.dutySlug))) {
|
|
755
780
|
return { content: [{ type: "text", text: trustRefusal(opts.dutySlug) }] };
|
|
756
781
|
}
|
|
757
782
|
const result = dispatchVerb(workflowFile, verb, args.pr);
|
|
@@ -818,6 +843,18 @@ function buildDutyMcpServer(opts) {
|
|
|
818
843
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
819
844
|
}
|
|
820
845
|
);
|
|
846
|
+
const readThreadTool = tool3(
|
|
847
|
+
"read_thread",
|
|
848
|
+
"Read an issue or PR's recent comments + labels + title/state. Returns {number, title, state, labels:[...], comments:[{author, createdAt, body}]} (newest last, body truncated). Use this to read a verdict a dispatched check posted back \u2014 e.g. qa-engineer's report or ui-review's PASS/CONCERNS/FAIL \u2014 on a later tick. Read-only; works for both issues and PRs.",
|
|
849
|
+
{
|
|
850
|
+
number: z3.number().int().positive().describe("Issue or PR number to read."),
|
|
851
|
+
limit: z3.number().int().positive().optional().describe("Max recent comments to return (default 10).")
|
|
852
|
+
},
|
|
853
|
+
async (args) => {
|
|
854
|
+
const result = readThread(opts.repoSlug, args.number, args.limit ?? 10);
|
|
855
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
856
|
+
}
|
|
857
|
+
);
|
|
821
858
|
const ensureIssueTool = tool3(
|
|
822
859
|
"ensure_issue",
|
|
823
860
|
"Idempotently ensure ONE open tracking issue exists for `key`. Searches OPEN issues (issues API, not the laggy search index) for `key`'s hidden marker; if found, returns {created:false, number} and creates NOTHING; otherwise creates the issue (title + body, marker appended) and returns {created:true, number}. This is the anti-duplication primitive: use one stable `key` per recurring finding so re-ticks reuse the same issue. Only take follow-up actions (dispatch/comment) when created===true.",
|
|
@@ -856,7 +893,7 @@ function buildDutyMcpServer(opts) {
|
|
|
856
893
|
issueNumber: z3.number().int().positive().describe("Issue (or PR) number forwarded as issue_number.")
|
|
857
894
|
},
|
|
858
895
|
async (args) => {
|
|
859
|
-
if (readDutyTrustMode(opts.repoSlug, opts.dutySlug)
|
|
896
|
+
if (isDispatchGated(args.executable, readDutyTrustMode(opts.repoSlug, opts.dutySlug))) {
|
|
860
897
|
return { content: [{ type: "text", text: trustRefusal(opts.dutySlug) }] };
|
|
861
898
|
}
|
|
862
899
|
const result = dispatchWorkflow(workflowFile, args.executable, args.issueNumber);
|
|
@@ -875,6 +912,7 @@ function buildDutyMcpServer(opts) {
|
|
|
875
912
|
recommendTool,
|
|
876
913
|
ledgerTool,
|
|
877
914
|
checkRunsTool,
|
|
915
|
+
readThreadTool,
|
|
878
916
|
ensureIssueTool,
|
|
879
917
|
ensureCommentTool,
|
|
880
918
|
dispatchTool
|
|
@@ -882,7 +920,7 @@ function buildDutyMcpServer(opts) {
|
|
|
882
920
|
});
|
|
883
921
|
return { server };
|
|
884
922
|
}
|
|
885
|
-
var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, TRUST_STATE_BRANCH, CHECK_FAIL_CONCLUSIONS, DEFAULT_IGNORE_CHECKS, trackMarker, commentMarker, DUTY_MCP_TOOL_NAMES;
|
|
923
|
+
var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, TRUST_STATE_BRANCH, THREAD_BODY_MAX, CHECK_FAIL_CONCLUSIONS, DEFAULT_IGNORE_CHECKS, trackMarker, commentMarker, GATE_EXEMPT_EXECUTABLES, DUTY_MCP_TOOL_NAMES;
|
|
886
924
|
var init_dutyMcp = __esm({
|
|
887
925
|
"src/dutyMcp.ts"() {
|
|
888
926
|
"use strict";
|
|
@@ -891,10 +929,12 @@ var init_dutyMcp = __esm({
|
|
|
891
929
|
RUNNING_STATUSES = /* @__PURE__ */ new Set(["IN_PROGRESS", "QUEUED", "PENDING", "WAITING", "REQUESTED"]);
|
|
892
930
|
TRUST_FILE_PATH = ".kody/state/trust.json";
|
|
893
931
|
TRUST_STATE_BRANCH = "kody-state";
|
|
932
|
+
THREAD_BODY_MAX = 4e3;
|
|
894
933
|
CHECK_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "STARTUP_FAILURE", "ACTION_REQUIRED"]);
|
|
895
934
|
DEFAULT_IGNORE_CHECKS = ["run", "kody", "job-tick", "goal-tick", "worker-ask", "chat"];
|
|
896
935
|
trackMarker = (key) => `<!-- kody-track:${key} -->`;
|
|
897
936
|
commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
|
|
937
|
+
GATE_EXEMPT_EXECUTABLES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
|
|
898
938
|
DUTY_MCP_TOOL_NAMES = [
|
|
899
939
|
"list_prs_to_repair",
|
|
900
940
|
"sync_pr",
|
|
@@ -903,6 +943,7 @@ var init_dutyMcp = __esm({
|
|
|
903
943
|
"recommend_to_operator",
|
|
904
944
|
"read_ledger",
|
|
905
945
|
"read_check_runs",
|
|
946
|
+
"read_thread",
|
|
906
947
|
"ensure_issue",
|
|
907
948
|
"ensure_comment",
|
|
908
949
|
"dispatch_workflow"
|
|
@@ -1031,16 +1072,16 @@ var init_fetchRepoMcp = __esm({
|
|
|
1031
1072
|
});
|
|
1032
1073
|
|
|
1033
1074
|
// src/prompt.ts
|
|
1034
|
-
import * as
|
|
1035
|
-
import * as
|
|
1075
|
+
import * as fs17 from "fs";
|
|
1076
|
+
import * as path16 from "path";
|
|
1036
1077
|
function loadProjectConventions(projectDir) {
|
|
1037
1078
|
const out = [];
|
|
1038
1079
|
for (const rel of CONVENTION_FILES) {
|
|
1039
|
-
const abs =
|
|
1040
|
-
if (!
|
|
1080
|
+
const abs = path16.join(projectDir, rel);
|
|
1081
|
+
if (!fs17.existsSync(abs)) continue;
|
|
1041
1082
|
let content;
|
|
1042
1083
|
try {
|
|
1043
|
-
content =
|
|
1084
|
+
content = fs17.readFileSync(abs, "utf-8");
|
|
1044
1085
|
} catch {
|
|
1045
1086
|
continue;
|
|
1046
1087
|
}
|
|
@@ -1275,28 +1316,28 @@ var loadMemoryContext_exports = {};
|
|
|
1275
1316
|
__export(loadMemoryContext_exports, {
|
|
1276
1317
|
loadMemoryContext: () => loadMemoryContext
|
|
1277
1318
|
});
|
|
1278
|
-
import * as
|
|
1279
|
-
import * as
|
|
1319
|
+
import * as fs18 from "fs";
|
|
1320
|
+
import * as path17 from "path";
|
|
1280
1321
|
function collectPages(memoryAbs) {
|
|
1281
1322
|
const out = [];
|
|
1282
1323
|
walkMd(memoryAbs, (file) => {
|
|
1283
1324
|
let stat;
|
|
1284
1325
|
try {
|
|
1285
|
-
stat =
|
|
1326
|
+
stat = fs18.statSync(file);
|
|
1286
1327
|
} catch {
|
|
1287
1328
|
return;
|
|
1288
1329
|
}
|
|
1289
1330
|
let raw;
|
|
1290
1331
|
try {
|
|
1291
|
-
raw =
|
|
1332
|
+
raw = fs18.readFileSync(file, "utf-8");
|
|
1292
1333
|
} catch {
|
|
1293
1334
|
return;
|
|
1294
1335
|
}
|
|
1295
1336
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1296
|
-
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");
|
|
1297
1338
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
1298
1339
|
out.push({
|
|
1299
|
-
relPath:
|
|
1340
|
+
relPath: path17.relative(memoryAbs, file),
|
|
1300
1341
|
title,
|
|
1301
1342
|
updated,
|
|
1302
1343
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
|
|
@@ -1364,16 +1405,16 @@ function walkMd(root, visit) {
|
|
|
1364
1405
|
const dir = stack.pop();
|
|
1365
1406
|
let names;
|
|
1366
1407
|
try {
|
|
1367
|
-
names =
|
|
1408
|
+
names = fs18.readdirSync(dir);
|
|
1368
1409
|
} catch {
|
|
1369
1410
|
continue;
|
|
1370
1411
|
}
|
|
1371
1412
|
for (const name of names) {
|
|
1372
1413
|
if (name.startsWith(".")) continue;
|
|
1373
|
-
const full =
|
|
1414
|
+
const full = path17.join(dir, name);
|
|
1374
1415
|
let stat;
|
|
1375
1416
|
try {
|
|
1376
|
-
stat =
|
|
1417
|
+
stat = fs18.statSync(full);
|
|
1377
1418
|
} catch {
|
|
1378
1419
|
continue;
|
|
1379
1420
|
}
|
|
@@ -1396,8 +1437,8 @@ var init_loadMemoryContext = __esm({
|
|
|
1396
1437
|
TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
|
|
1397
1438
|
loadMemoryContext = async (ctx) => {
|
|
1398
1439
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
1399
|
-
const memoryAbs =
|
|
1400
|
-
if (!
|
|
1440
|
+
const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
1441
|
+
if (!fs18.existsSync(memoryAbs)) {
|
|
1401
1442
|
ctx.data.memoryContext = "";
|
|
1402
1443
|
return;
|
|
1403
1444
|
}
|
|
@@ -1442,7 +1483,7 @@ var init_loadCoverageRules = __esm({
|
|
|
1442
1483
|
// package.json
|
|
1443
1484
|
var package_default = {
|
|
1444
1485
|
name: "@kody-ade/kody-engine",
|
|
1445
|
-
version: "0.4.
|
|
1486
|
+
version: "0.4.204-next.10",
|
|
1446
1487
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
1447
1488
|
license: "MIT",
|
|
1448
1489
|
type: "module",
|
|
@@ -1500,8 +1541,8 @@ var package_default = {
|
|
|
1500
1541
|
|
|
1501
1542
|
// src/chat-cli.ts
|
|
1502
1543
|
import { execFileSync as execFileSync29 } from "child_process";
|
|
1503
|
-
import * as
|
|
1504
|
-
import * as
|
|
1544
|
+
import * as fs45 from "fs";
|
|
1545
|
+
import * as path41 from "path";
|
|
1505
1546
|
|
|
1506
1547
|
// src/chat/events.ts
|
|
1507
1548
|
import * as fs from "fs";
|
|
@@ -2334,6 +2375,9 @@ function getExecutablesRoot() {
|
|
|
2334
2375
|
function getProjectExecutablesRoot() {
|
|
2335
2376
|
return path7.join(process.cwd(), ".kody", "executables");
|
|
2336
2377
|
}
|
|
2378
|
+
function getProjectDutiesRoot() {
|
|
2379
|
+
return path7.join(process.cwd(), ".kody", "duties");
|
|
2380
|
+
}
|
|
2337
2381
|
function getBuiltinJobsRoot() {
|
|
2338
2382
|
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2339
2383
|
const candidates = [
|
|
@@ -2361,7 +2405,7 @@ function listBuiltinJobs(root = getBuiltinJobsRoot()) {
|
|
|
2361
2405
|
return out;
|
|
2362
2406
|
}
|
|
2363
2407
|
function getExecutableRoots() {
|
|
2364
|
-
return [getProjectExecutablesRoot(), getExecutablesRoot()];
|
|
2408
|
+
return [getProjectDutiesRoot(), getProjectExecutablesRoot(), getExecutablesRoot()];
|
|
2365
2409
|
}
|
|
2366
2410
|
function listExecutables(roots = getExecutableRoots()) {
|
|
2367
2411
|
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
@@ -3249,8 +3293,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
3249
3293
|
|
|
3250
3294
|
// src/kody-cli.ts
|
|
3251
3295
|
import { execFileSync as execFileSync28 } from "child_process";
|
|
3252
|
-
import * as
|
|
3253
|
-
import * as
|
|
3296
|
+
import * as fs44 from "fs";
|
|
3297
|
+
import * as path40 from "path";
|
|
3254
3298
|
|
|
3255
3299
|
// src/app-auth.ts
|
|
3256
3300
|
import { createSign } from "crypto";
|
|
@@ -3676,17 +3720,17 @@ function coerceBare(spec, value) {
|
|
|
3676
3720
|
|
|
3677
3721
|
// src/executor.ts
|
|
3678
3722
|
import { spawn as spawn10 } from "child_process";
|
|
3679
|
-
import * as
|
|
3680
|
-
import * as
|
|
3723
|
+
import * as fs43 from "fs";
|
|
3724
|
+
import * as path39 from "path";
|
|
3681
3725
|
|
|
3682
3726
|
// src/container.ts
|
|
3683
3727
|
init_events();
|
|
3684
3728
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
3685
|
-
import * as
|
|
3729
|
+
import * as fs19 from "fs";
|
|
3686
3730
|
|
|
3687
3731
|
// src/profile.ts
|
|
3688
|
-
import * as
|
|
3689
|
-
import * as
|
|
3732
|
+
import * as fs16 from "fs";
|
|
3733
|
+
import * as path15 from "path";
|
|
3690
3734
|
|
|
3691
3735
|
// src/profile-error.ts
|
|
3692
3736
|
var ProfileError = class extends Error {
|
|
@@ -3826,6 +3870,150 @@ function applyLifecycle(profile, profilePath) {
|
|
|
3826
3870
|
expander(profile, profilePath);
|
|
3827
3871
|
}
|
|
3828
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
|
+
|
|
3829
4017
|
// src/profile.ts
|
|
3830
4018
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
3831
4019
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
@@ -3834,6 +4022,10 @@ var VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
|
|
|
3834
4022
|
var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
|
|
3835
4023
|
var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
3836
4024
|
"name",
|
|
4025
|
+
"staff",
|
|
4026
|
+
"every",
|
|
4027
|
+
"dutyTools",
|
|
4028
|
+
"mentions",
|
|
3837
4029
|
"describe",
|
|
3838
4030
|
"role",
|
|
3839
4031
|
"kind",
|
|
@@ -3857,12 +4049,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
|
3857
4049
|
"preloadContext"
|
|
3858
4050
|
]);
|
|
3859
4051
|
function loadProfile(profilePath) {
|
|
3860
|
-
if (!
|
|
4052
|
+
if (!fs16.existsSync(profilePath)) {
|
|
3861
4053
|
throw new ProfileError(profilePath, "file not found");
|
|
3862
4054
|
}
|
|
3863
4055
|
let raw;
|
|
3864
4056
|
try {
|
|
3865
|
-
raw = JSON.parse(
|
|
4057
|
+
raw = JSON.parse(fs16.readFileSync(profilePath, "utf-8"));
|
|
3866
4058
|
} catch (err) {
|
|
3867
4059
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
3868
4060
|
}
|
|
@@ -3873,7 +4065,7 @@ function loadProfile(profilePath) {
|
|
|
3873
4065
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
3874
4066
|
if (unknownKeys.length > 0) {
|
|
3875
4067
|
process.stderr.write(
|
|
3876
|
-
`[kody profile] ${
|
|
4068
|
+
`[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
3877
4069
|
`
|
|
3878
4070
|
);
|
|
3879
4071
|
}
|
|
@@ -3913,6 +4105,13 @@ function loadProfile(profilePath) {
|
|
|
3913
4105
|
const profile = {
|
|
3914
4106
|
name: requireString(profilePath, r, "name"),
|
|
3915
4107
|
describe: typeof r.describe === "string" ? r.describe : "",
|
|
4108
|
+
// Optional persona to run as. Empty/blank string → undefined (no persona).
|
|
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,
|
|
3916
4115
|
role,
|
|
3917
4116
|
kind,
|
|
3918
4117
|
schedule: typeof r.schedule === "string" ? r.schedule : void 0,
|
|
@@ -3934,27 +4133,28 @@ function loadProfile(profilePath) {
|
|
|
3934
4133
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
3935
4134
|
// flip to true after end-to-end verification.
|
|
3936
4135
|
preloadContext: r.preloadContext === true,
|
|
3937
|
-
dir:
|
|
3938
|
-
promptTemplates: readPromptTemplates(
|
|
4136
|
+
dir: path15.dirname(profilePath),
|
|
4137
|
+
promptTemplates: readPromptTemplates(path15.dirname(profilePath))
|
|
3939
4138
|
};
|
|
3940
4139
|
if (lifecycle) {
|
|
3941
4140
|
applyLifecycle(profile, profilePath);
|
|
3942
4141
|
}
|
|
4142
|
+
profile.subagentTemplates = captureSubagentTemplates(profile);
|
|
3943
4143
|
return profile;
|
|
3944
4144
|
}
|
|
3945
4145
|
function readPromptTemplates(dir) {
|
|
3946
4146
|
const out = {};
|
|
3947
4147
|
const read = (p) => {
|
|
3948
4148
|
try {
|
|
3949
|
-
out[p] =
|
|
4149
|
+
out[p] = fs16.readFileSync(p, "utf-8");
|
|
3950
4150
|
} catch {
|
|
3951
4151
|
}
|
|
3952
4152
|
};
|
|
3953
|
-
read(
|
|
4153
|
+
read(path15.join(dir, "prompt.md"));
|
|
3954
4154
|
try {
|
|
3955
|
-
const promptsDir =
|
|
3956
|
-
for (const ent of
|
|
3957
|
-
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));
|
|
3958
4158
|
}
|
|
3959
4159
|
} catch {
|
|
3960
4160
|
}
|
|
@@ -4294,16 +4494,17 @@ function parseStateComment(body) {
|
|
|
4294
4494
|
flow: parsed.flow
|
|
4295
4495
|
};
|
|
4296
4496
|
}
|
|
4297
|
-
function reduce(state, executable, action, phase) {
|
|
4497
|
+
function reduce(state, executable, action, phase, staff) {
|
|
4298
4498
|
if (!action) return state;
|
|
4299
4499
|
const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
|
|
4300
4500
|
const newExecutables = {
|
|
4301
4501
|
...state.executables,
|
|
4302
4502
|
[executable]: { ...state.executables[executable] ?? { lastAction: null }, lastAction: action }
|
|
4303
4503
|
};
|
|
4504
|
+
const ranAsStaff = typeof staff === "string" && staff.length > 0 ? staff : void 0;
|
|
4304
4505
|
const newHistory = [
|
|
4305
4506
|
...state.history,
|
|
4306
|
-
{ timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action) }
|
|
4507
|
+
{ timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action), staff: ranAsStaff }
|
|
4307
4508
|
].slice(-HISTORY_MAX_ENTRIES);
|
|
4308
4509
|
return {
|
|
4309
4510
|
schemaVersion: 1,
|
|
@@ -4312,6 +4513,7 @@ function reduce(state, executable, action, phase) {
|
|
|
4312
4513
|
attempts: newAttempts,
|
|
4313
4514
|
lastOutcome: action,
|
|
4314
4515
|
currentExecutable: executable,
|
|
4516
|
+
ranAsStaff: ranAsStaff ?? null,
|
|
4315
4517
|
status: statusFromAction(action),
|
|
4316
4518
|
phase: phaseFromAction(action, phase)
|
|
4317
4519
|
},
|
|
@@ -4348,6 +4550,9 @@ function renderStateComment(state) {
|
|
|
4348
4550
|
if (state.core.currentExecutable) {
|
|
4349
4551
|
lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
|
|
4350
4552
|
}
|
|
4553
|
+
if (state.core.ranAsStaff) {
|
|
4554
|
+
lines.push(`- **Ran as:** \`${state.core.ranAsStaff}\``);
|
|
4555
|
+
}
|
|
4351
4556
|
if (state.core.lastOutcome) {
|
|
4352
4557
|
lines.push(`- **Last action:** \`${state.core.lastOutcome.type}\``);
|
|
4353
4558
|
}
|
|
@@ -4430,7 +4635,7 @@ var CONTAINER_MAX_ITERATIONS = 50;
|
|
|
4430
4635
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
4431
4636
|
try {
|
|
4432
4637
|
const profilePath = resolveProfilePath(profileName);
|
|
4433
|
-
if (!
|
|
4638
|
+
if (!fs19.existsSync(profilePath)) return null;
|
|
4434
4639
|
return loadProfile(profilePath).inputs;
|
|
4435
4640
|
} catch {
|
|
4436
4641
|
return null;
|
|
@@ -4874,9 +5079,9 @@ function errMsg(err) {
|
|
|
4874
5079
|
|
|
4875
5080
|
// src/litellm.ts
|
|
4876
5081
|
import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
|
|
4877
|
-
import * as
|
|
4878
|
-
import * as
|
|
4879
|
-
import * as
|
|
5082
|
+
import * as fs20 from "fs";
|
|
5083
|
+
import * as os3 from "os";
|
|
5084
|
+
import * as path18 from "path";
|
|
4880
5085
|
async function checkLitellmHealth(url) {
|
|
4881
5086
|
try {
|
|
4882
5087
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -4928,13 +5133,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4928
5133
|
let child;
|
|
4929
5134
|
let logPath;
|
|
4930
5135
|
const spawnProxy = () => {
|
|
4931
|
-
const configPath =
|
|
4932
|
-
|
|
5136
|
+
const configPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
5137
|
+
fs20.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
4933
5138
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
4934
|
-
const nextLogPath =
|
|
4935
|
-
const outFd =
|
|
5139
|
+
const nextLogPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
5140
|
+
const outFd = fs20.openSync(nextLogPath, "w");
|
|
4936
5141
|
child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
4937
|
-
|
|
5142
|
+
fs20.closeSync(outFd);
|
|
4938
5143
|
logPath = nextLogPath;
|
|
4939
5144
|
};
|
|
4940
5145
|
const waitForHealth = async () => {
|
|
@@ -4948,7 +5153,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4948
5153
|
const readLogTail = () => {
|
|
4949
5154
|
if (!logPath) return "";
|
|
4950
5155
|
try {
|
|
4951
|
-
return
|
|
5156
|
+
return fs20.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
4952
5157
|
} catch {
|
|
4953
5158
|
return "";
|
|
4954
5159
|
}
|
|
@@ -5000,10 +5205,10 @@ ${tail}`
|
|
|
5000
5205
|
return { url, kill: killChild, isHealthy, ensureHealthy };
|
|
5001
5206
|
}
|
|
5002
5207
|
function readDotenvApiKeys(projectDir) {
|
|
5003
|
-
const dotenvPath =
|
|
5004
|
-
if (!
|
|
5208
|
+
const dotenvPath = path18.join(projectDir, ".env");
|
|
5209
|
+
if (!fs20.existsSync(dotenvPath)) return {};
|
|
5005
5210
|
const result = {};
|
|
5006
|
-
for (const rawLine of
|
|
5211
|
+
for (const rawLine of fs20.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
5007
5212
|
const line = rawLine.trim();
|
|
5008
5213
|
if (!line || line.startsWith("#")) continue;
|
|
5009
5214
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -5105,8 +5310,8 @@ function pushWithRetry(opts = {}) {
|
|
|
5105
5310
|
}
|
|
5106
5311
|
|
|
5107
5312
|
// src/commit.ts
|
|
5108
|
-
import * as
|
|
5109
|
-
import * as
|
|
5313
|
+
import * as fs21 from "fs";
|
|
5314
|
+
import * as path19 from "path";
|
|
5110
5315
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
5111
5316
|
".kody/",
|
|
5112
5317
|
".kody-engine/",
|
|
@@ -5167,18 +5372,18 @@ function tryGit(args, cwd) {
|
|
|
5167
5372
|
}
|
|
5168
5373
|
function abortUnfinishedGitOps(cwd) {
|
|
5169
5374
|
const aborted = [];
|
|
5170
|
-
const gitDir =
|
|
5171
|
-
if (!
|
|
5172
|
-
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"))) {
|
|
5173
5378
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
5174
5379
|
}
|
|
5175
|
-
if (
|
|
5380
|
+
if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
5176
5381
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
5177
5382
|
}
|
|
5178
|
-
if (
|
|
5383
|
+
if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
|
|
5179
5384
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
5180
5385
|
}
|
|
5181
|
-
if (
|
|
5386
|
+
if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
|
|
5182
5387
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
5183
5388
|
}
|
|
5184
5389
|
try {
|
|
@@ -5234,7 +5439,7 @@ function normalizeCommitMessage(raw) {
|
|
|
5234
5439
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
5235
5440
|
const allChanged = listChangedFiles(cwd);
|
|
5236
5441
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
5237
|
-
const mergeHeadExists =
|
|
5442
|
+
const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
5238
5443
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
5239
5444
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
5240
5445
|
}
|
|
@@ -5325,7 +5530,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
5325
5530
|
const action = ctx.data.action;
|
|
5326
5531
|
let nextIssueState = issueState;
|
|
5327
5532
|
if (targetType === "pr" && action) {
|
|
5328
|
-
nextIssueState = reduce(issueState, profile.name, action, profile.phase);
|
|
5533
|
+
nextIssueState = reduce(issueState, profile.name, action, profile.phase, profile.staff);
|
|
5329
5534
|
if (state?.core.prUrl && !nextIssueState.core.prUrl) nextIssueState.core.prUrl = state.core.prUrl;
|
|
5330
5535
|
}
|
|
5331
5536
|
const prevHops = issueState.flow?.hops ?? flow.hops ?? 0;
|
|
@@ -5366,7 +5571,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
5366
5571
|
|
|
5367
5572
|
// src/gha.ts
|
|
5368
5573
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
5369
|
-
import * as
|
|
5574
|
+
import * as fs22 from "fs";
|
|
5370
5575
|
function getRunUrl() {
|
|
5371
5576
|
const server = process.env.GITHUB_SERVER_URL;
|
|
5372
5577
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -5377,10 +5582,10 @@ function getRunUrl() {
|
|
|
5377
5582
|
function reactToTriggerComment(cwd) {
|
|
5378
5583
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5379
5584
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5380
|
-
if (!eventPath || !
|
|
5585
|
+
if (!eventPath || !fs22.existsSync(eventPath)) return;
|
|
5381
5586
|
let event = null;
|
|
5382
5587
|
try {
|
|
5383
|
-
event = JSON.parse(
|
|
5588
|
+
event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
|
|
5384
5589
|
} catch {
|
|
5385
5590
|
return;
|
|
5386
5591
|
}
|
|
@@ -5532,22 +5737,22 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
|
|
|
5532
5737
|
};
|
|
5533
5738
|
|
|
5534
5739
|
// src/scripts/brainServe.ts
|
|
5535
|
-
import * as
|
|
5740
|
+
import * as fs24 from "fs";
|
|
5536
5741
|
import { createServer } from "http";
|
|
5537
|
-
import * as
|
|
5742
|
+
import * as path21 from "path";
|
|
5538
5743
|
init_repoWorkspace();
|
|
5539
5744
|
|
|
5540
5745
|
// src/scripts/brainTurnLog.ts
|
|
5541
|
-
import * as
|
|
5542
|
-
import * as
|
|
5746
|
+
import * as fs23 from "fs";
|
|
5747
|
+
import * as path20 from "path";
|
|
5543
5748
|
var live = /* @__PURE__ */ new Map();
|
|
5544
5749
|
function eventsPath(dir, chatId) {
|
|
5545
|
-
return
|
|
5750
|
+
return path20.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
5546
5751
|
}
|
|
5547
5752
|
function lastPersistedSeq(dir, chatId) {
|
|
5548
5753
|
const p = eventsPath(dir, chatId);
|
|
5549
|
-
if (!
|
|
5550
|
-
const lines =
|
|
5754
|
+
if (!fs23.existsSync(p)) return 0;
|
|
5755
|
+
const lines = fs23.readFileSync(p, "utf-8").split("\n").filter(Boolean);
|
|
5551
5756
|
if (lines.length === 0) return 0;
|
|
5552
5757
|
try {
|
|
5553
5758
|
return JSON.parse(lines[lines.length - 1]).seq || 0;
|
|
@@ -5557,9 +5762,9 @@ function lastPersistedSeq(dir, chatId) {
|
|
|
5557
5762
|
}
|
|
5558
5763
|
function readSince(dir, chatId, since) {
|
|
5559
5764
|
const p = eventsPath(dir, chatId);
|
|
5560
|
-
if (!
|
|
5765
|
+
if (!fs23.existsSync(p)) return [];
|
|
5561
5766
|
const out = [];
|
|
5562
|
-
for (const line of
|
|
5767
|
+
for (const line of fs23.readFileSync(p, "utf-8").split("\n")) {
|
|
5563
5768
|
if (!line) continue;
|
|
5564
5769
|
try {
|
|
5565
5770
|
const rec = JSON.parse(line);
|
|
@@ -5585,12 +5790,12 @@ function beginTurn(dir, chatId) {
|
|
|
5585
5790
|
};
|
|
5586
5791
|
live.set(chatId, state);
|
|
5587
5792
|
const p = eventsPath(dir, chatId);
|
|
5588
|
-
|
|
5793
|
+
fs23.mkdirSync(path20.dirname(p), { recursive: true });
|
|
5589
5794
|
return (event) => {
|
|
5590
5795
|
state.seq += 1;
|
|
5591
5796
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
5592
5797
|
try {
|
|
5593
|
-
|
|
5798
|
+
fs23.appendFileSync(p, `${JSON.stringify(rec)}
|
|
5594
5799
|
`);
|
|
5595
5800
|
} catch (err) {
|
|
5596
5801
|
process.stderr.write(
|
|
@@ -5629,7 +5834,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
|
|
|
5629
5834
|
event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
|
|
5630
5835
|
};
|
|
5631
5836
|
try {
|
|
5632
|
-
|
|
5837
|
+
fs23.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
|
|
5633
5838
|
`);
|
|
5634
5839
|
} catch {
|
|
5635
5840
|
}
|
|
@@ -5860,7 +6065,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
5860
6065
|
const repo = strField(body, "repo");
|
|
5861
6066
|
const repoToken = strField(body, "repoToken");
|
|
5862
6067
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
5863
|
-
|
|
6068
|
+
fs24.mkdirSync(path21.dirname(sessionFile), { recursive: true });
|
|
5864
6069
|
appendTurn(sessionFile, {
|
|
5865
6070
|
role: "user",
|
|
5866
6071
|
content: message,
|
|
@@ -5907,7 +6112,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
5907
6112
|
function buildServer(opts) {
|
|
5908
6113
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
5909
6114
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
5910
|
-
const reposRoot = opts.reposRoot ??
|
|
6115
|
+
const reposRoot = opts.reposRoot ?? path21.join(path21.dirname(path21.resolve(opts.cwd)), "repos");
|
|
5911
6116
|
return createServer(async (req, res) => {
|
|
5912
6117
|
if (!req.method || !req.url) {
|
|
5913
6118
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -6008,93 +6213,6 @@ var brainServe = async (ctx) => {
|
|
|
6008
6213
|
});
|
|
6009
6214
|
};
|
|
6010
6215
|
|
|
6011
|
-
// src/scripts/buildSyntheticPlugin.ts
|
|
6012
|
-
import * as fs23 from "fs";
|
|
6013
|
-
import * as os3 from "os";
|
|
6014
|
-
import * as path20 from "path";
|
|
6015
|
-
function getPluginsCatalogRoot() {
|
|
6016
|
-
const here = path20.dirname(new URL(import.meta.url).pathname);
|
|
6017
|
-
const candidates = [
|
|
6018
|
-
path20.join(here, "..", "plugins"),
|
|
6019
|
-
// dev: src/scripts → src/plugins
|
|
6020
|
-
path20.join(here, "..", "..", "plugins"),
|
|
6021
|
-
// built: dist/scripts → dist/plugins
|
|
6022
|
-
path20.join(here, "..", "..", "src", "plugins")
|
|
6023
|
-
// fallback
|
|
6024
|
-
];
|
|
6025
|
-
for (const c of candidates) {
|
|
6026
|
-
if (fs23.existsSync(c) && fs23.statSync(c).isDirectory()) return c;
|
|
6027
|
-
}
|
|
6028
|
-
return candidates[0];
|
|
6029
|
-
}
|
|
6030
|
-
var buildSyntheticPlugin = async (ctx, profile) => {
|
|
6031
|
-
const cc = profile.claudeCode;
|
|
6032
|
-
const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
|
|
6033
|
-
if (!needsSynthetic) return;
|
|
6034
|
-
const catalog = getPluginsCatalogRoot();
|
|
6035
|
-
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6036
|
-
const root = path20.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
6037
|
-
fs23.mkdirSync(path20.join(root, ".claude-plugin"), { recursive: true });
|
|
6038
|
-
const resolvePart = (bucket, entry) => {
|
|
6039
|
-
const local = path20.join(profile.dir, bucket, entry);
|
|
6040
|
-
if (fs23.existsSync(local)) return local;
|
|
6041
|
-
const central = path20.join(catalog, bucket, entry);
|
|
6042
|
-
if (fs23.existsSync(central)) return central;
|
|
6043
|
-
throw new Error(
|
|
6044
|
-
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
6045
|
-
);
|
|
6046
|
-
};
|
|
6047
|
-
if (cc.skills.length > 0) {
|
|
6048
|
-
const dst = path20.join(root, "skills");
|
|
6049
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6050
|
-
for (const name of cc.skills) {
|
|
6051
|
-
copyDir(resolvePart("skills", name), path20.join(dst, name));
|
|
6052
|
-
}
|
|
6053
|
-
}
|
|
6054
|
-
if (cc.commands.length > 0) {
|
|
6055
|
-
const dst = path20.join(root, "commands");
|
|
6056
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6057
|
-
for (const name of cc.commands) {
|
|
6058
|
-
fs23.copyFileSync(resolvePart("commands", `${name}.md`), path20.join(dst, `${name}.md`));
|
|
6059
|
-
}
|
|
6060
|
-
}
|
|
6061
|
-
if (cc.hooks.length > 0) {
|
|
6062
|
-
const dst = path20.join(root, "hooks");
|
|
6063
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6064
|
-
const merged = { hooks: {} };
|
|
6065
|
-
for (const name of cc.hooks) {
|
|
6066
|
-
const src = resolvePart("hooks", `${name}.json`);
|
|
6067
|
-
const parsed = JSON.parse(fs23.readFileSync(src, "utf-8"));
|
|
6068
|
-
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
6069
|
-
if (!Array.isArray(entries)) continue;
|
|
6070
|
-
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
6071
|
-
merged.hooks[event].push(...entries);
|
|
6072
|
-
}
|
|
6073
|
-
}
|
|
6074
|
-
fs23.writeFileSync(path20.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
6075
|
-
`);
|
|
6076
|
-
}
|
|
6077
|
-
const manifest = {
|
|
6078
|
-
name: `kody-synth-${profile.name}`,
|
|
6079
|
-
version: "1.0.0",
|
|
6080
|
-
description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
|
|
6081
|
-
};
|
|
6082
|
-
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
6083
|
-
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
6084
|
-
fs23.writeFileSync(path20.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
6085
|
-
`);
|
|
6086
|
-
ctx.data.syntheticPluginPath = root;
|
|
6087
|
-
};
|
|
6088
|
-
function copyDir(src, dst) {
|
|
6089
|
-
fs23.mkdirSync(dst, { recursive: true });
|
|
6090
|
-
for (const ent of fs23.readdirSync(src, { withFileTypes: true })) {
|
|
6091
|
-
const s = path20.join(src, ent.name);
|
|
6092
|
-
const d = path20.join(dst, ent.name);
|
|
6093
|
-
if (ent.isDirectory()) copyDir(s, d);
|
|
6094
|
-
else if (ent.isFile()) fs23.copyFileSync(s, d);
|
|
6095
|
-
}
|
|
6096
|
-
}
|
|
6097
|
-
|
|
6098
6216
|
// src/coverage.ts
|
|
6099
6217
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
6100
6218
|
function patternToRegex(pattern) {
|
|
@@ -6243,13 +6361,13 @@ function defaultLabelMap() {
|
|
|
6243
6361
|
}
|
|
6244
6362
|
|
|
6245
6363
|
// src/scripts/commitAndPush.ts
|
|
6246
|
-
import * as
|
|
6247
|
-
import * as
|
|
6364
|
+
import * as fs25 from "fs";
|
|
6365
|
+
import * as path22 from "path";
|
|
6248
6366
|
init_events();
|
|
6249
6367
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
6250
6368
|
function sentinelPathForStage(cwd, profileName) {
|
|
6251
6369
|
const runId = resolveRunId();
|
|
6252
|
-
return
|
|
6370
|
+
return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
6253
6371
|
}
|
|
6254
6372
|
var commitAndPush2 = async (ctx, profile) => {
|
|
6255
6373
|
const branch = ctx.data.branch;
|
|
@@ -6259,9 +6377,9 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6259
6377
|
}
|
|
6260
6378
|
const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
|
|
6261
6379
|
const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
|
|
6262
|
-
if (sentinel &&
|
|
6380
|
+
if (sentinel && fs25.existsSync(sentinel)) {
|
|
6263
6381
|
try {
|
|
6264
|
-
const replay = JSON.parse(
|
|
6382
|
+
const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
|
|
6265
6383
|
ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
|
|
6266
6384
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
6267
6385
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
@@ -6314,8 +6432,8 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6314
6432
|
const result = ctx.data.commitResult;
|
|
6315
6433
|
if (sentinel && result?.committed) {
|
|
6316
6434
|
try {
|
|
6317
|
-
|
|
6318
|
-
|
|
6435
|
+
fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
|
|
6436
|
+
fs25.writeFileSync(
|
|
6319
6437
|
sentinel,
|
|
6320
6438
|
JSON.stringify(
|
|
6321
6439
|
{
|
|
@@ -6338,14 +6456,14 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
6338
6456
|
init_issue();
|
|
6339
6457
|
|
|
6340
6458
|
// src/goal/state.ts
|
|
6341
|
-
import * as
|
|
6342
|
-
import * as
|
|
6459
|
+
import * as fs26 from "fs";
|
|
6460
|
+
import * as path23 from "path";
|
|
6343
6461
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
6344
6462
|
var GoalStateError = class extends Error {
|
|
6345
|
-
constructor(
|
|
6346
|
-
super(`Invalid goal state at ${
|
|
6463
|
+
constructor(path42, message) {
|
|
6464
|
+
super(`Invalid goal state at ${path42}:
|
|
6347
6465
|
${message}`);
|
|
6348
|
-
this.path =
|
|
6466
|
+
this.path = path42;
|
|
6349
6467
|
this.name = "GoalStateError";
|
|
6350
6468
|
}
|
|
6351
6469
|
path;
|
|
@@ -6485,16 +6603,16 @@ function describeCommitMessage(goal) {
|
|
|
6485
6603
|
}
|
|
6486
6604
|
|
|
6487
6605
|
// src/scripts/composePrompt.ts
|
|
6488
|
-
import * as
|
|
6489
|
-
import * as
|
|
6606
|
+
import * as fs27 from "fs";
|
|
6607
|
+
import * as path24 from "path";
|
|
6490
6608
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
6491
6609
|
var composePrompt = async (ctx, profile) => {
|
|
6492
6610
|
const explicit = ctx.data.promptTemplate;
|
|
6493
6611
|
const mode = ctx.args.mode;
|
|
6494
6612
|
const candidates = [
|
|
6495
|
-
explicit ?
|
|
6496
|
-
mode ?
|
|
6497
|
-
|
|
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")
|
|
6498
6616
|
].filter(Boolean);
|
|
6499
6617
|
let templatePath = "";
|
|
6500
6618
|
let template = "";
|
|
@@ -6507,7 +6625,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
6507
6625
|
break;
|
|
6508
6626
|
}
|
|
6509
6627
|
try {
|
|
6510
|
-
template =
|
|
6628
|
+
template = fs27.readFileSync(c, "utf-8");
|
|
6511
6629
|
templatePath = c;
|
|
6512
6630
|
break;
|
|
6513
6631
|
} catch (err) {
|
|
@@ -6518,7 +6636,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
6518
6636
|
if (!templatePath) {
|
|
6519
6637
|
let dirState;
|
|
6520
6638
|
try {
|
|
6521
|
-
dirState = `dir contents: [${
|
|
6639
|
+
dirState = `dir contents: [${fs27.readdirSync(profile.dir).join(", ")}]`;
|
|
6522
6640
|
} catch (err) {
|
|
6523
6641
|
dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
|
|
6524
6642
|
}
|
|
@@ -7343,15 +7461,15 @@ var deriveQaScopeFromIssue = async (ctx) => {
|
|
|
7343
7461
|
|
|
7344
7462
|
// src/scripts/diagMcp.ts
|
|
7345
7463
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
7346
|
-
import * as
|
|
7464
|
+
import * as fs28 from "fs";
|
|
7347
7465
|
import * as os4 from "os";
|
|
7348
|
-
import * as
|
|
7466
|
+
import * as path25 from "path";
|
|
7349
7467
|
var diagMcp = async (_ctx) => {
|
|
7350
7468
|
const home = os4.homedir();
|
|
7351
|
-
const cacheDir =
|
|
7469
|
+
const cacheDir = path25.join(home, ".cache", "ms-playwright");
|
|
7352
7470
|
let entries = [];
|
|
7353
7471
|
try {
|
|
7354
|
-
entries =
|
|
7472
|
+
entries = fs28.readdirSync(cacheDir);
|
|
7355
7473
|
} catch {
|
|
7356
7474
|
}
|
|
7357
7475
|
const hasChromium = entries.some((e) => e.startsWith("chromium"));
|
|
@@ -7377,17 +7495,17 @@ var diagMcp = async (_ctx) => {
|
|
|
7377
7495
|
};
|
|
7378
7496
|
|
|
7379
7497
|
// src/scripts/discoverQaContext.ts
|
|
7380
|
-
import * as
|
|
7381
|
-
import * as
|
|
7498
|
+
import * as fs30 from "fs";
|
|
7499
|
+
import * as path27 from "path";
|
|
7382
7500
|
|
|
7383
7501
|
// src/scripts/frameworkDetectors.ts
|
|
7384
|
-
import * as
|
|
7385
|
-
import * as
|
|
7502
|
+
import * as fs29 from "fs";
|
|
7503
|
+
import * as path26 from "path";
|
|
7386
7504
|
function detectFrameworks(cwd) {
|
|
7387
7505
|
const out = [];
|
|
7388
7506
|
let deps = {};
|
|
7389
7507
|
try {
|
|
7390
|
-
const pkg = JSON.parse(
|
|
7508
|
+
const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
|
|
7391
7509
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7392
7510
|
} catch {
|
|
7393
7511
|
return out;
|
|
@@ -7424,7 +7542,7 @@ function detectFrameworks(cwd) {
|
|
|
7424
7542
|
}
|
|
7425
7543
|
function findFile(cwd, candidates) {
|
|
7426
7544
|
for (const c of candidates) {
|
|
7427
|
-
if (
|
|
7545
|
+
if (fs29.existsSync(path26.join(cwd, c))) return c;
|
|
7428
7546
|
}
|
|
7429
7547
|
return null;
|
|
7430
7548
|
}
|
|
@@ -7437,18 +7555,18 @@ var COLLECTION_DIRS = [
|
|
|
7437
7555
|
function discoverPayloadCollections(cwd) {
|
|
7438
7556
|
const out = [];
|
|
7439
7557
|
for (const dir of COLLECTION_DIRS) {
|
|
7440
|
-
const full =
|
|
7441
|
-
if (!
|
|
7558
|
+
const full = path26.join(cwd, dir);
|
|
7559
|
+
if (!fs29.existsSync(full)) continue;
|
|
7442
7560
|
let files;
|
|
7443
7561
|
try {
|
|
7444
|
-
files =
|
|
7562
|
+
files = fs29.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7445
7563
|
} catch {
|
|
7446
7564
|
continue;
|
|
7447
7565
|
}
|
|
7448
7566
|
for (const file of files) {
|
|
7449
7567
|
try {
|
|
7450
|
-
const filePath =
|
|
7451
|
-
const content =
|
|
7568
|
+
const filePath = path26.join(full, file);
|
|
7569
|
+
const content = fs29.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
7452
7570
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
7453
7571
|
if (!slugMatch) continue;
|
|
7454
7572
|
const slug = slugMatch[1];
|
|
@@ -7462,7 +7580,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
7462
7580
|
out.push({
|
|
7463
7581
|
name,
|
|
7464
7582
|
slug,
|
|
7465
|
-
filePath:
|
|
7583
|
+
filePath: path26.relative(cwd, filePath),
|
|
7466
7584
|
fields: fields.slice(0, 20),
|
|
7467
7585
|
hasAdmin
|
|
7468
7586
|
});
|
|
@@ -7476,28 +7594,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
7476
7594
|
function discoverAdminComponents(cwd, collections) {
|
|
7477
7595
|
const out = [];
|
|
7478
7596
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
7479
|
-
const full =
|
|
7480
|
-
if (!
|
|
7597
|
+
const full = path26.join(cwd, dir);
|
|
7598
|
+
if (!fs29.existsSync(full)) continue;
|
|
7481
7599
|
let entries;
|
|
7482
7600
|
try {
|
|
7483
|
-
entries =
|
|
7601
|
+
entries = fs29.readdirSync(full, { withFileTypes: true });
|
|
7484
7602
|
} catch {
|
|
7485
7603
|
continue;
|
|
7486
7604
|
}
|
|
7487
7605
|
for (const entry of entries) {
|
|
7488
|
-
const entryPath =
|
|
7606
|
+
const entryPath = path26.join(full, entry.name);
|
|
7489
7607
|
let name;
|
|
7490
7608
|
let filePath;
|
|
7491
7609
|
if (entry.isDirectory()) {
|
|
7492
7610
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
7493
|
-
(f) =>
|
|
7611
|
+
(f) => fs29.existsSync(path26.join(entryPath, f))
|
|
7494
7612
|
);
|
|
7495
7613
|
if (!indexFile) continue;
|
|
7496
7614
|
name = entry.name;
|
|
7497
|
-
filePath =
|
|
7615
|
+
filePath = path26.relative(cwd, path26.join(entryPath, indexFile));
|
|
7498
7616
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
7499
7617
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
7500
|
-
filePath =
|
|
7618
|
+
filePath = path26.relative(cwd, entryPath);
|
|
7501
7619
|
} else {
|
|
7502
7620
|
continue;
|
|
7503
7621
|
}
|
|
@@ -7505,7 +7623,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
7505
7623
|
if (collections) {
|
|
7506
7624
|
for (const col of collections) {
|
|
7507
7625
|
try {
|
|
7508
|
-
const colContent =
|
|
7626
|
+
const colContent = fs29.readFileSync(path26.join(cwd, col.filePath), "utf-8");
|
|
7509
7627
|
if (colContent.includes(name)) {
|
|
7510
7628
|
usedInCollection = col.slug;
|
|
7511
7629
|
break;
|
|
@@ -7524,8 +7642,8 @@ function scanApiRoutes(cwd) {
|
|
|
7524
7642
|
const out = [];
|
|
7525
7643
|
const appDirs = ["src/app", "app"];
|
|
7526
7644
|
for (const appDir of appDirs) {
|
|
7527
|
-
const apiDir =
|
|
7528
|
-
if (!
|
|
7645
|
+
const apiDir = path26.join(cwd, appDir, "api");
|
|
7646
|
+
if (!fs29.existsSync(apiDir)) continue;
|
|
7529
7647
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
7530
7648
|
break;
|
|
7531
7649
|
}
|
|
@@ -7534,14 +7652,14 @@ function scanApiRoutes(cwd) {
|
|
|
7534
7652
|
function walkApiRoutes(dir, prefix, cwd, out) {
|
|
7535
7653
|
let entries;
|
|
7536
7654
|
try {
|
|
7537
|
-
entries =
|
|
7655
|
+
entries = fs29.readdirSync(dir, { withFileTypes: true });
|
|
7538
7656
|
} catch {
|
|
7539
7657
|
return;
|
|
7540
7658
|
}
|
|
7541
7659
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
7542
7660
|
if (routeFile) {
|
|
7543
7661
|
try {
|
|
7544
|
-
const content =
|
|
7662
|
+
const content = fs29.readFileSync(path26.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
7545
7663
|
const methods = HTTP_METHODS.filter(
|
|
7546
7664
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
7547
7665
|
);
|
|
@@ -7549,7 +7667,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7549
7667
|
out.push({
|
|
7550
7668
|
path: prefix,
|
|
7551
7669
|
methods,
|
|
7552
|
-
filePath:
|
|
7670
|
+
filePath: path26.relative(cwd, path26.join(dir, routeFile.name))
|
|
7553
7671
|
});
|
|
7554
7672
|
}
|
|
7555
7673
|
} catch {
|
|
@@ -7560,7 +7678,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7560
7678
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7561
7679
|
let segment = entry.name;
|
|
7562
7680
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7563
|
-
walkApiRoutes(
|
|
7681
|
+
walkApiRoutes(path26.join(dir, entry.name), prefix, cwd, out);
|
|
7564
7682
|
continue;
|
|
7565
7683
|
}
|
|
7566
7684
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7568,7 +7686,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
7568
7686
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7569
7687
|
segment = `:${segment.slice(1, -1)}`;
|
|
7570
7688
|
}
|
|
7571
|
-
walkApiRoutes(
|
|
7689
|
+
walkApiRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
7572
7690
|
}
|
|
7573
7691
|
}
|
|
7574
7692
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -7588,10 +7706,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
7588
7706
|
function scanEnvVars(cwd) {
|
|
7589
7707
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
7590
7708
|
for (const envFile of candidates) {
|
|
7591
|
-
const envPath =
|
|
7592
|
-
if (!
|
|
7709
|
+
const envPath = path26.join(cwd, envFile);
|
|
7710
|
+
if (!fs29.existsSync(envPath)) continue;
|
|
7593
7711
|
try {
|
|
7594
|
-
const content =
|
|
7712
|
+
const content = fs29.readFileSync(envPath, "utf-8");
|
|
7595
7713
|
const vars = [];
|
|
7596
7714
|
for (const line of content.split("\n")) {
|
|
7597
7715
|
const trimmed = line.trim();
|
|
@@ -7639,9 +7757,9 @@ function runQaDiscovery(cwd) {
|
|
|
7639
7757
|
}
|
|
7640
7758
|
function detectDevServer(cwd, out) {
|
|
7641
7759
|
try {
|
|
7642
|
-
const pkg = JSON.parse(
|
|
7760
|
+
const pkg = JSON.parse(fs30.readFileSync(path27.join(cwd, "package.json"), "utf-8"));
|
|
7643
7761
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7644
|
-
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";
|
|
7645
7763
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
7646
7764
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
7647
7765
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -7651,8 +7769,8 @@ function detectDevServer(cwd, out) {
|
|
|
7651
7769
|
function scanFrontendRoutes(cwd, out) {
|
|
7652
7770
|
const appDirs = ["src/app", "app"];
|
|
7653
7771
|
for (const appDir of appDirs) {
|
|
7654
|
-
const full =
|
|
7655
|
-
if (!
|
|
7772
|
+
const full = path27.join(cwd, appDir);
|
|
7773
|
+
if (!fs30.existsSync(full)) continue;
|
|
7656
7774
|
walkFrontendRoutes(full, "", out);
|
|
7657
7775
|
break;
|
|
7658
7776
|
}
|
|
@@ -7660,7 +7778,7 @@ function scanFrontendRoutes(cwd, out) {
|
|
|
7660
7778
|
function walkFrontendRoutes(dir, prefix, out) {
|
|
7661
7779
|
let entries;
|
|
7662
7780
|
try {
|
|
7663
|
-
entries =
|
|
7781
|
+
entries = fs30.readdirSync(dir, { withFileTypes: true });
|
|
7664
7782
|
} catch {
|
|
7665
7783
|
return;
|
|
7666
7784
|
}
|
|
@@ -7677,7 +7795,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7677
7795
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7678
7796
|
let segment = entry.name;
|
|
7679
7797
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7680
|
-
walkFrontendRoutes(
|
|
7798
|
+
walkFrontendRoutes(path27.join(dir, entry.name), prefix, out);
|
|
7681
7799
|
continue;
|
|
7682
7800
|
}
|
|
7683
7801
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -7685,7 +7803,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
7685
7803
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7686
7804
|
segment = `:${segment.slice(1, -1)}`;
|
|
7687
7805
|
}
|
|
7688
|
-
walkFrontendRoutes(
|
|
7806
|
+
walkFrontendRoutes(path27.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
7689
7807
|
}
|
|
7690
7808
|
}
|
|
7691
7809
|
function detectAuthFiles(cwd, out) {
|
|
@@ -7702,23 +7820,23 @@ function detectAuthFiles(cwd, out) {
|
|
|
7702
7820
|
"src/app/api/oauth"
|
|
7703
7821
|
];
|
|
7704
7822
|
for (const c of candidates) {
|
|
7705
|
-
if (
|
|
7823
|
+
if (fs30.existsSync(path27.join(cwd, c))) out.authFiles.push(c);
|
|
7706
7824
|
}
|
|
7707
7825
|
}
|
|
7708
7826
|
function detectRoles(cwd, out) {
|
|
7709
7827
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
7710
7828
|
for (const rp of rolePaths) {
|
|
7711
|
-
const dir =
|
|
7712
|
-
if (!
|
|
7829
|
+
const dir = path27.join(cwd, rp);
|
|
7830
|
+
if (!fs30.existsSync(dir)) continue;
|
|
7713
7831
|
let files;
|
|
7714
7832
|
try {
|
|
7715
|
-
files =
|
|
7833
|
+
files = fs30.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7716
7834
|
} catch {
|
|
7717
7835
|
continue;
|
|
7718
7836
|
}
|
|
7719
7837
|
for (const f of files) {
|
|
7720
7838
|
try {
|
|
7721
|
-
const content =
|
|
7839
|
+
const content = fs30.readFileSync(path27.join(dir, f), "utf-8").slice(0, 5e3);
|
|
7722
7840
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
7723
7841
|
if (roleMatches) {
|
|
7724
7842
|
for (const m of roleMatches) {
|
|
@@ -7901,8 +8019,8 @@ ${stateBody}`;
|
|
|
7901
8019
|
};
|
|
7902
8020
|
|
|
7903
8021
|
// src/scripts/dispatchJobFileTicks.ts
|
|
7904
|
-
import * as
|
|
7905
|
-
import * as
|
|
8022
|
+
import * as fs32 from "fs";
|
|
8023
|
+
import * as path29 from "path";
|
|
7906
8024
|
|
|
7907
8025
|
// src/scripts/jobFrontmatter.ts
|
|
7908
8026
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -7918,7 +8036,7 @@ var SCHEDULE_EVERY_VALUES = [
|
|
|
7918
8036
|
"manual"
|
|
7919
8037
|
];
|
|
7920
8038
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
7921
|
-
function
|
|
8039
|
+
function splitFrontmatter2(raw) {
|
|
7922
8040
|
const match = FRONTMATTER_RE.exec(raw);
|
|
7923
8041
|
if (!match) return { frontmatter: {}, body: raw };
|
|
7924
8042
|
const inner = match[1] ?? "";
|
|
@@ -8195,8 +8313,8 @@ function isShaConflict(err) {
|
|
|
8195
8313
|
}
|
|
8196
8314
|
|
|
8197
8315
|
// src/scripts/jobState/localFileBackend.ts
|
|
8198
|
-
import * as
|
|
8199
|
-
import * as
|
|
8316
|
+
import * as fs31 from "fs";
|
|
8317
|
+
import * as path28 from "path";
|
|
8200
8318
|
var LocalFileBackend = class {
|
|
8201
8319
|
name = "local-file";
|
|
8202
8320
|
cwd;
|
|
@@ -8211,7 +8329,7 @@ var LocalFileBackend = class {
|
|
|
8211
8329
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
8212
8330
|
this.cwd = opts.cwd;
|
|
8213
8331
|
this.jobsDir = opts.jobsDir;
|
|
8214
|
-
this.absDir =
|
|
8332
|
+
this.absDir = path28.join(opts.cwd, opts.jobsDir);
|
|
8215
8333
|
this.owner = opts.owner;
|
|
8216
8334
|
this.repo = opts.repo;
|
|
8217
8335
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -8226,7 +8344,7 @@ var LocalFileBackend = class {
|
|
|
8226
8344
|
`);
|
|
8227
8345
|
return;
|
|
8228
8346
|
}
|
|
8229
|
-
|
|
8347
|
+
fs31.mkdirSync(this.absDir, { recursive: true });
|
|
8230
8348
|
const prefix = this.cacheKeyPrefix();
|
|
8231
8349
|
const probeKey = `${prefix}probe-${Date.now()}`;
|
|
8232
8350
|
try {
|
|
@@ -8255,7 +8373,7 @@ var LocalFileBackend = class {
|
|
|
8255
8373
|
`);
|
|
8256
8374
|
return;
|
|
8257
8375
|
}
|
|
8258
|
-
if (!
|
|
8376
|
+
if (!fs31.existsSync(this.absDir)) {
|
|
8259
8377
|
return;
|
|
8260
8378
|
}
|
|
8261
8379
|
const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
|
|
@@ -8271,11 +8389,11 @@ var LocalFileBackend = class {
|
|
|
8271
8389
|
}
|
|
8272
8390
|
load(slug) {
|
|
8273
8391
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
8274
|
-
const absPath =
|
|
8275
|
-
if (!
|
|
8392
|
+
const absPath = path28.join(this.cwd, relPath);
|
|
8393
|
+
if (!fs31.existsSync(absPath)) {
|
|
8276
8394
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
8277
8395
|
}
|
|
8278
|
-
const raw =
|
|
8396
|
+
const raw = fs31.readFileSync(absPath, "utf-8");
|
|
8279
8397
|
let parsed;
|
|
8280
8398
|
try {
|
|
8281
8399
|
parsed = JSON.parse(raw);
|
|
@@ -8292,13 +8410,13 @@ var LocalFileBackend = class {
|
|
|
8292
8410
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
8293
8411
|
return false;
|
|
8294
8412
|
}
|
|
8295
|
-
const absPath =
|
|
8296
|
-
|
|
8413
|
+
const absPath = path28.join(this.cwd, loaded.path);
|
|
8414
|
+
fs31.mkdirSync(path28.dirname(absPath), { recursive: true });
|
|
8297
8415
|
const body = `${JSON.stringify(next, null, 2)}
|
|
8298
8416
|
`;
|
|
8299
8417
|
const tmpPath = `${absPath}.${process.pid}.tmp`;
|
|
8300
|
-
|
|
8301
|
-
|
|
8418
|
+
fs31.writeFileSync(tmpPath, body, "utf-8");
|
|
8419
|
+
fs31.renameSync(tmpPath, absPath);
|
|
8302
8420
|
return true;
|
|
8303
8421
|
}
|
|
8304
8422
|
cacheKeyPrefix() {
|
|
@@ -8376,7 +8494,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
8376
8494
|
await backend.hydrate();
|
|
8377
8495
|
}
|
|
8378
8496
|
try {
|
|
8379
|
-
const slugs = listJobSlugs(
|
|
8497
|
+
const slugs = listJobSlugs(path29.join(ctx.cwd, jobsDir));
|
|
8380
8498
|
ctx.data.jobSlugCount = slugs.length;
|
|
8381
8499
|
if (slugs.length === 0) {
|
|
8382
8500
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -8387,6 +8505,55 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
8387
8505
|
`);
|
|
8388
8506
|
const results = [];
|
|
8389
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
|
+
}
|
|
8390
8557
|
for (const slug of slugs) {
|
|
8391
8558
|
const frontmatter = readJobFrontmatter(ctx.cwd, jobsDir, slug);
|
|
8392
8559
|
if (frontmatter.disabled === true) {
|
|
@@ -8487,22 +8654,42 @@ function formatAgo(ms) {
|
|
|
8487
8654
|
}
|
|
8488
8655
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
8489
8656
|
try {
|
|
8490
|
-
const raw =
|
|
8491
|
-
return
|
|
8657
|
+
const raw = fs32.readFileSync(path29.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
8658
|
+
return splitFrontmatter2(raw).frontmatter;
|
|
8492
8659
|
} catch {
|
|
8493
8660
|
return {};
|
|
8494
8661
|
}
|
|
8495
8662
|
}
|
|
8496
8663
|
function listJobSlugs(absDir) {
|
|
8497
|
-
if (!
|
|
8664
|
+
if (!fs32.existsSync(absDir)) return [];
|
|
8498
8665
|
let entries;
|
|
8499
8666
|
try {
|
|
8500
|
-
entries =
|
|
8667
|
+
entries = fs32.readdirSync(absDir, { withFileTypes: true });
|
|
8501
8668
|
} catch {
|
|
8502
8669
|
return [];
|
|
8503
8670
|
}
|
|
8504
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();
|
|
8505
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
|
+
}
|
|
8506
8693
|
|
|
8507
8694
|
// src/scripts/dispatchJobTicks.ts
|
|
8508
8695
|
init_issue();
|
|
@@ -9556,12 +9743,12 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
9556
9743
|
|
|
9557
9744
|
// src/scripts/initFlow.ts
|
|
9558
9745
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
9559
|
-
import * as
|
|
9560
|
-
import * as
|
|
9746
|
+
import * as fs33 from "fs";
|
|
9747
|
+
import * as path30 from "path";
|
|
9561
9748
|
function detectPackageManager(cwd) {
|
|
9562
|
-
if (
|
|
9563
|
-
if (
|
|
9564
|
-
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";
|
|
9565
9752
|
return "npm";
|
|
9566
9753
|
}
|
|
9567
9754
|
function qualityCommandsFor(pm) {
|
|
@@ -9690,36 +9877,36 @@ function performInit(cwd, force) {
|
|
|
9690
9877
|
const pm = detectPackageManager(cwd);
|
|
9691
9878
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
9692
9879
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
9693
|
-
const configPath =
|
|
9694
|
-
if (
|
|
9880
|
+
const configPath = path30.join(cwd, "kody.config.json");
|
|
9881
|
+
if (fs33.existsSync(configPath) && !force) {
|
|
9695
9882
|
skipped.push("kody.config.json");
|
|
9696
9883
|
} else {
|
|
9697
9884
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
|
|
9698
|
-
|
|
9885
|
+
fs33.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
9699
9886
|
`);
|
|
9700
9887
|
wrote.push("kody.config.json");
|
|
9701
9888
|
}
|
|
9702
|
-
const workflowDir =
|
|
9703
|
-
const workflowPath =
|
|
9704
|
-
if (
|
|
9889
|
+
const workflowDir = path30.join(cwd, ".github", "workflows");
|
|
9890
|
+
const workflowPath = path30.join(workflowDir, "kody.yml");
|
|
9891
|
+
if (fs33.existsSync(workflowPath) && !force) {
|
|
9705
9892
|
skipped.push(".github/workflows/kody.yml");
|
|
9706
9893
|
} else {
|
|
9707
|
-
|
|
9708
|
-
|
|
9894
|
+
fs33.mkdirSync(workflowDir, { recursive: true });
|
|
9895
|
+
fs33.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
9709
9896
|
wrote.push(".github/workflows/kody.yml");
|
|
9710
9897
|
}
|
|
9711
9898
|
const builtinJobs = listBuiltinJobs();
|
|
9712
9899
|
if (builtinJobs.length > 0) {
|
|
9713
|
-
const jobsDir =
|
|
9714
|
-
|
|
9900
|
+
const jobsDir = path30.join(cwd, ".kody", "duties");
|
|
9901
|
+
fs33.mkdirSync(jobsDir, { recursive: true });
|
|
9715
9902
|
for (const job of builtinJobs) {
|
|
9716
|
-
const rel =
|
|
9717
|
-
const target =
|
|
9718
|
-
if (
|
|
9903
|
+
const rel = path30.join(".kody", "duties", `${job.slug}.md`);
|
|
9904
|
+
const target = path30.join(cwd, rel);
|
|
9905
|
+
if (fs33.existsSync(target) && !force) {
|
|
9719
9906
|
skipped.push(rel);
|
|
9720
9907
|
continue;
|
|
9721
9908
|
}
|
|
9722
|
-
|
|
9909
|
+
fs33.writeFileSync(target, fs33.readFileSync(job.filePath, "utf-8"));
|
|
9723
9910
|
wrote.push(rel);
|
|
9724
9911
|
}
|
|
9725
9912
|
}
|
|
@@ -9731,12 +9918,12 @@ function performInit(cwd, force) {
|
|
|
9731
9918
|
continue;
|
|
9732
9919
|
}
|
|
9733
9920
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
9734
|
-
const target =
|
|
9735
|
-
if (
|
|
9921
|
+
const target = path30.join(workflowDir, `kody-${exe.name}.yml`);
|
|
9922
|
+
if (fs33.existsSync(target) && !force) {
|
|
9736
9923
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9737
9924
|
continue;
|
|
9738
9925
|
}
|
|
9739
|
-
|
|
9926
|
+
fs33.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
9740
9927
|
wrote.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
9741
9928
|
}
|
|
9742
9929
|
let labels;
|
|
@@ -9921,8 +10108,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
9921
10108
|
|
|
9922
10109
|
// src/scripts/loadJobFromFile.ts
|
|
9923
10110
|
init_dutyMcp();
|
|
9924
|
-
import * as
|
|
9925
|
-
import * as
|
|
10111
|
+
import * as fs34 from "fs";
|
|
10112
|
+
import * as path31 from "path";
|
|
9926
10113
|
var DUTY_TOOL_PALETTE = new Set(DUTY_MCP_TOOL_NAMES);
|
|
9927
10114
|
var loadJobFromFile = async (ctx, profile, args) => {
|
|
9928
10115
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -9932,23 +10119,23 @@ var loadJobFromFile = async (ctx, profile, args) => {
|
|
|
9932
10119
|
if (!slug) {
|
|
9933
10120
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
9934
10121
|
}
|
|
9935
|
-
const absPath =
|
|
9936
|
-
if (!
|
|
10122
|
+
const absPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
10123
|
+
if (!fs34.existsSync(absPath)) {
|
|
9937
10124
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
9938
10125
|
}
|
|
9939
|
-
const raw =
|
|
10126
|
+
const raw = fs34.readFileSync(absPath, "utf-8");
|
|
9940
10127
|
const { title, body } = parseJobFile(raw, slug);
|
|
9941
|
-
const frontmatter =
|
|
10128
|
+
const frontmatter = splitFrontmatter2(raw).frontmatter;
|
|
9942
10129
|
const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
|
|
9943
10130
|
const workerSlug = (frontmatter.staff ?? "").trim();
|
|
9944
10131
|
let workerTitle = "";
|
|
9945
10132
|
let workerPersona = "";
|
|
9946
10133
|
if (workerSlug) {
|
|
9947
|
-
const workerPath =
|
|
9948
|
-
if (!
|
|
10134
|
+
const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
10135
|
+
if (!fs34.existsSync(workerPath)) {
|
|
9949
10136
|
throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
|
|
9950
10137
|
}
|
|
9951
|
-
const workerRaw =
|
|
10138
|
+
const workerRaw = fs34.readFileSync(workerPath, "utf-8");
|
|
9952
10139
|
const parsed = parseJobFile(workerRaw, workerSlug);
|
|
9953
10140
|
workerTitle = parsed.title;
|
|
9954
10141
|
workerPersona = parsed.body;
|
|
@@ -10001,6 +10188,36 @@ function humanizeSlug(slug) {
|
|
|
10001
10188
|
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
10002
10189
|
}
|
|
10003
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
|
+
|
|
10004
10221
|
// src/scripts/loadLinkedFinding.ts
|
|
10005
10222
|
init_issue();
|
|
10006
10223
|
var FINDING_BODY_MAX_BYTES = 4e3;
|
|
@@ -10032,18 +10249,18 @@ init_loadMemoryContext();
|
|
|
10032
10249
|
init_loadPriorArt();
|
|
10033
10250
|
|
|
10034
10251
|
// src/scripts/loadQaContext.ts
|
|
10035
|
-
import * as
|
|
10036
|
-
import * as
|
|
10252
|
+
import * as fs36 from "fs";
|
|
10253
|
+
import * as path33 from "path";
|
|
10037
10254
|
|
|
10038
10255
|
// src/scripts/kodyVariables.ts
|
|
10039
|
-
import * as
|
|
10040
|
-
import * as
|
|
10256
|
+
import * as fs35 from "fs";
|
|
10257
|
+
import * as path32 from "path";
|
|
10041
10258
|
var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
|
|
10042
10259
|
function readKodyVariables(cwd) {
|
|
10043
|
-
const full =
|
|
10260
|
+
const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
|
|
10044
10261
|
let raw;
|
|
10045
10262
|
try {
|
|
10046
|
-
raw =
|
|
10263
|
+
raw = fs35.readFileSync(full, "utf-8");
|
|
10047
10264
|
} catch {
|
|
10048
10265
|
return {};
|
|
10049
10266
|
}
|
|
@@ -10094,18 +10311,18 @@ function readProfileStaff(raw) {
|
|
|
10094
10311
|
return { staff: staff ?? legacy ?? ["kody"], body };
|
|
10095
10312
|
}
|
|
10096
10313
|
function readProfile(cwd) {
|
|
10097
|
-
const dir =
|
|
10098
|
-
if (!
|
|
10314
|
+
const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
|
|
10315
|
+
if (!fs36.existsSync(dir)) return "";
|
|
10099
10316
|
let entries;
|
|
10100
10317
|
try {
|
|
10101
|
-
entries =
|
|
10318
|
+
entries = fs36.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
10102
10319
|
} catch {
|
|
10103
10320
|
return "";
|
|
10104
10321
|
}
|
|
10105
10322
|
const blocks = [];
|
|
10106
10323
|
for (const file of entries) {
|
|
10107
10324
|
try {
|
|
10108
|
-
const raw =
|
|
10325
|
+
const raw = fs36.readFileSync(path33.join(dir, file), "utf-8");
|
|
10109
10326
|
const { staff, body } = readProfileStaff(raw);
|
|
10110
10327
|
if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
|
|
10111
10328
|
blocks.push(`## ${file}
|
|
@@ -10142,8 +10359,8 @@ var loadQaContext = async (ctx) => {
|
|
|
10142
10359
|
init_events();
|
|
10143
10360
|
|
|
10144
10361
|
// src/taskContext.ts
|
|
10145
|
-
import * as
|
|
10146
|
-
import * as
|
|
10362
|
+
import * as fs37 from "fs";
|
|
10363
|
+
import * as path34 from "path";
|
|
10147
10364
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
10148
10365
|
function buildTaskContext(args) {
|
|
10149
10366
|
return {
|
|
@@ -10159,10 +10376,10 @@ function buildTaskContext(args) {
|
|
|
10159
10376
|
}
|
|
10160
10377
|
function persistTaskContext(cwd, ctx) {
|
|
10161
10378
|
try {
|
|
10162
|
-
const dir =
|
|
10163
|
-
|
|
10164
|
-
const file =
|
|
10165
|
-
|
|
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)}
|
|
10166
10383
|
`);
|
|
10167
10384
|
return file;
|
|
10168
10385
|
} catch (err) {
|
|
@@ -10228,19 +10445,19 @@ var loadTaskState = async (ctx) => {
|
|
|
10228
10445
|
};
|
|
10229
10446
|
|
|
10230
10447
|
// src/scripts/loadWorkerAdhoc.ts
|
|
10231
|
-
import * as
|
|
10232
|
-
import * as
|
|
10448
|
+
import * as fs38 from "fs";
|
|
10449
|
+
import * as path35 from "path";
|
|
10233
10450
|
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
10234
10451
|
const workersDir = String(args?.workersDir ?? ".kody/staff");
|
|
10235
10452
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
10236
10453
|
if (!workerSlug) {
|
|
10237
10454
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
10238
10455
|
}
|
|
10239
|
-
const workerPath =
|
|
10240
|
-
if (!
|
|
10456
|
+
const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
10457
|
+
if (!fs38.existsSync(workerPath)) {
|
|
10241
10458
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
10242
10459
|
}
|
|
10243
|
-
const { title, body } = parsePersona(
|
|
10460
|
+
const { title, body } = parsePersona(fs38.readFileSync(workerPath, "utf-8"), workerSlug);
|
|
10244
10461
|
const message = resolveMessage(ctx.args.message);
|
|
10245
10462
|
if (!message) {
|
|
10246
10463
|
throw new Error(
|
|
@@ -10260,9 +10477,9 @@ function resolveMessage(messageArg) {
|
|
|
10260
10477
|
}
|
|
10261
10478
|
function readCommentBody() {
|
|
10262
10479
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
10263
|
-
if (!eventPath || !
|
|
10480
|
+
if (!eventPath || !fs38.existsSync(eventPath)) return "";
|
|
10264
10481
|
try {
|
|
10265
|
-
const event = JSON.parse(
|
|
10482
|
+
const event = JSON.parse(fs38.readFileSync(eventPath, "utf-8"));
|
|
10266
10483
|
return String(event.comment?.body ?? "");
|
|
10267
10484
|
} catch {
|
|
10268
10485
|
return "";
|
|
@@ -10286,7 +10503,7 @@ function stripDirective(body) {
|
|
|
10286
10503
|
return lines.slice(start).join("\n").trim();
|
|
10287
10504
|
}
|
|
10288
10505
|
function parsePersona(raw, slug) {
|
|
10289
|
-
const stripped =
|
|
10506
|
+
const stripped = splitFrontmatter2(raw).body;
|
|
10290
10507
|
const trimmed = stripped.trim();
|
|
10291
10508
|
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
10292
10509
|
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
@@ -10993,8 +11210,8 @@ var FlyClient = class {
|
|
|
10993
11210
|
get fetch() {
|
|
10994
11211
|
return this.opts.fetchImpl ?? fetch;
|
|
10995
11212
|
}
|
|
10996
|
-
async call(
|
|
10997
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
11213
|
+
async call(path42, init = {}) {
|
|
11214
|
+
const res = await this.fetch(`${FLY_API_BASE}${path42}`, {
|
|
10998
11215
|
method: init.method ?? "GET",
|
|
10999
11216
|
headers: {
|
|
11000
11217
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -11005,7 +11222,7 @@ var FlyClient = class {
|
|
|
11005
11222
|
if (res.status === 404 && init.allow404) return null;
|
|
11006
11223
|
if (!res.ok) {
|
|
11007
11224
|
const text = await res.text().catch(() => "");
|
|
11008
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
11225
|
+
throw new Error(`Fly API ${res.status} on ${path42}: ${text.slice(0, 200) || res.statusText}`);
|
|
11009
11226
|
}
|
|
11010
11227
|
if (res.status === 204) return null;
|
|
11011
11228
|
const raw = await res.text();
|
|
@@ -12581,7 +12798,7 @@ function resolveBaseOverride(value) {
|
|
|
12581
12798
|
|
|
12582
12799
|
// src/scripts/runnerServe.ts
|
|
12583
12800
|
import { spawn as spawn5 } from "child_process";
|
|
12584
|
-
import * as
|
|
12801
|
+
import * as fs39 from "fs";
|
|
12585
12802
|
import { createServer as createServer3 } from "http";
|
|
12586
12803
|
var DEFAULT_PORT2 = 8080;
|
|
12587
12804
|
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
@@ -12661,8 +12878,8 @@ async function defaultRunJob(job) {
|
|
|
12661
12878
|
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
12662
12879
|
const branch = job.ref ?? "main";
|
|
12663
12880
|
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
12664
|
-
|
|
12665
|
-
|
|
12881
|
+
fs39.rmSync(workdir, { recursive: true, force: true });
|
|
12882
|
+
fs39.mkdirSync(workdir, { recursive: true });
|
|
12666
12883
|
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
12667
12884
|
const interactive = job.mode === "interactive";
|
|
12668
12885
|
const scheduled = job.mode === "scheduled";
|
|
@@ -12795,7 +13012,7 @@ var runnerServe = async (ctx) => {
|
|
|
12795
13012
|
|
|
12796
13013
|
// src/scripts/runPreviewBuild.ts
|
|
12797
13014
|
import { copyFile, writeFile } from "fs/promises";
|
|
12798
|
-
import * as
|
|
13015
|
+
import * as path36 from "path";
|
|
12799
13016
|
import { fileURLToPath } from "url";
|
|
12800
13017
|
|
|
12801
13018
|
// src/scripts/previewBuildHelpers.ts
|
|
@@ -12944,9 +13161,9 @@ var FLY_MACHINES = "https://api.machines.dev/v1";
|
|
|
12944
13161
|
var FLY_GRAPHQL = "https://api.fly.io/graphql";
|
|
12945
13162
|
var REQ_TIMEOUT_MS2 = 3e4;
|
|
12946
13163
|
function bundledDockerfilePath(mode) {
|
|
12947
|
-
const here =
|
|
13164
|
+
const here = path36.dirname(fileURLToPath(import.meta.url));
|
|
12948
13165
|
const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
|
|
12949
|
-
return
|
|
13166
|
+
return path36.join(here, "preview-build-templates", file);
|
|
12950
13167
|
}
|
|
12951
13168
|
function required(name) {
|
|
12952
13169
|
const v = (process.env[name] ?? "").trim();
|
|
@@ -13195,10 +13412,10 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
|
|
|
13195
13412
|
console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
|
|
13196
13413
|
if (Object.keys(buildEnv).length > 0) {
|
|
13197
13414
|
const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
13198
|
-
await writeFile(
|
|
13415
|
+
await writeFile(path36.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
|
|
13199
13416
|
`, "utf8");
|
|
13200
13417
|
}
|
|
13201
|
-
const consumerDockerfile =
|
|
13418
|
+
const consumerDockerfile = path36.join(ctx.cwd, "Dockerfile.preview");
|
|
13202
13419
|
const { stat } = await import("fs/promises");
|
|
13203
13420
|
let hasConsumerDockerfile = false;
|
|
13204
13421
|
try {
|
|
@@ -13298,8 +13515,8 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
|
|
|
13298
13515
|
|
|
13299
13516
|
// src/scripts/runTickScript.ts
|
|
13300
13517
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13301
|
-
import * as
|
|
13302
|
-
import * as
|
|
13518
|
+
import * as fs40 from "fs";
|
|
13519
|
+
import * as path37 from "path";
|
|
13303
13520
|
var runTickScript = async (ctx, _profile, args) => {
|
|
13304
13521
|
ctx.skipAgent = true;
|
|
13305
13522
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -13311,22 +13528,22 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
13311
13528
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
13312
13529
|
return;
|
|
13313
13530
|
}
|
|
13314
|
-
const jobPath =
|
|
13315
|
-
if (!
|
|
13531
|
+
const jobPath = path37.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
13532
|
+
if (!fs40.existsSync(jobPath)) {
|
|
13316
13533
|
ctx.output.exitCode = 99;
|
|
13317
13534
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
13318
13535
|
return;
|
|
13319
13536
|
}
|
|
13320
|
-
const raw =
|
|
13321
|
-
const { frontmatter } =
|
|
13537
|
+
const raw = fs40.readFileSync(jobPath, "utf-8");
|
|
13538
|
+
const { frontmatter } = splitFrontmatter2(raw);
|
|
13322
13539
|
const tickScript = frontmatter.tickScript;
|
|
13323
13540
|
if (!tickScript) {
|
|
13324
13541
|
ctx.output.exitCode = 99;
|
|
13325
13542
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
13326
13543
|
return;
|
|
13327
13544
|
}
|
|
13328
|
-
const scriptPath =
|
|
13329
|
-
if (!
|
|
13545
|
+
const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
|
|
13546
|
+
if (!fs40.existsSync(scriptPath)) {
|
|
13330
13547
|
ctx.output.exitCode = 99;
|
|
13331
13548
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
13332
13549
|
return;
|
|
@@ -13455,7 +13672,7 @@ var saveTaskState = async (ctx, profile) => {
|
|
|
13455
13672
|
if (!target || !number || !state) return;
|
|
13456
13673
|
const executable = profile.name;
|
|
13457
13674
|
const action = ctx.data.action ?? synthesizeAction(ctx);
|
|
13458
|
-
const next = reduce(state, executable, action, profile.phase);
|
|
13675
|
+
const next = reduce(state, executable, action, profile.phase, profile.staff);
|
|
13459
13676
|
if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
|
|
13460
13677
|
if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
|
|
13461
13678
|
writeTaskState(target, number, next, ctx.cwd);
|
|
@@ -14372,7 +14589,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
|
|
|
14372
14589
|
};
|
|
14373
14590
|
|
|
14374
14591
|
// src/scripts/writeRunSummary.ts
|
|
14375
|
-
import * as
|
|
14592
|
+
import * as fs41 from "fs";
|
|
14376
14593
|
var writeRunSummary = async (ctx, profile) => {
|
|
14377
14594
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
14378
14595
|
if (!summaryPath) return;
|
|
@@ -14394,7 +14611,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
14394
14611
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
14395
14612
|
lines.push("");
|
|
14396
14613
|
try {
|
|
14397
|
-
|
|
14614
|
+
fs41.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
14398
14615
|
`);
|
|
14399
14616
|
} catch {
|
|
14400
14617
|
}
|
|
@@ -14416,6 +14633,7 @@ var preflightScripts = {
|
|
|
14416
14633
|
loadIssueContext,
|
|
14417
14634
|
loadIssueStateComment,
|
|
14418
14635
|
loadJobFromFile,
|
|
14636
|
+
loadDutyState,
|
|
14419
14637
|
loadWorkerAdhoc,
|
|
14420
14638
|
loadConventions,
|
|
14421
14639
|
loadCoverageRules,
|
|
@@ -14501,46 +14719,36 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
14501
14719
|
...Object.keys(postflightScripts)
|
|
14502
14720
|
]);
|
|
14503
14721
|
|
|
14504
|
-
// src/
|
|
14505
|
-
import * as
|
|
14506
|
-
import * as
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
}
|
|
14516
|
-
|
|
14517
|
-
}
|
|
14518
|
-
|
|
14519
|
-
const
|
|
14520
|
-
if (
|
|
14521
|
-
|
|
14522
|
-
|
|
14523
|
-
|
|
14524
|
-
|
|
14525
|
-
|
|
14526
|
-
|
|
14527
|
-
|
|
14528
|
-
|
|
14529
|
-
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
prompt: body
|
|
14535
|
-
};
|
|
14536
|
-
if (fm.tools) {
|
|
14537
|
-
const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
|
|
14538
|
-
if (tools.length > 0) def.tools = tools;
|
|
14539
|
-
}
|
|
14540
|
-
if (fm.model) def.model = fm.model;
|
|
14541
|
-
agents[fm.name || name] = def;
|
|
14542
|
-
}
|
|
14543
|
-
return agents;
|
|
14722
|
+
// src/staff.ts
|
|
14723
|
+
import * as fs42 from "fs";
|
|
14724
|
+
import * as path38 from "path";
|
|
14725
|
+
var DEFAULT_STAFF_DIR = ".kody/staff";
|
|
14726
|
+
function stripFrontmatter(raw) {
|
|
14727
|
+
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
14728
|
+
return (match ? match[1] : raw).trim();
|
|
14729
|
+
}
|
|
14730
|
+
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
14731
|
+
const trimmed = slug.trim();
|
|
14732
|
+
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
14733
|
+
const staffPath = path38.join(cwd, staffDir, `${trimmed}.md`);
|
|
14734
|
+
if (!fs42.existsSync(staffPath)) {
|
|
14735
|
+
throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
|
|
14736
|
+
}
|
|
14737
|
+
const body = stripFrontmatter(fs42.readFileSync(staffPath, "utf-8"));
|
|
14738
|
+
if (!body) throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
|
|
14739
|
+
return body;
|
|
14740
|
+
}
|
|
14741
|
+
function framePersona(slug, persona) {
|
|
14742
|
+
return [
|
|
14743
|
+
`## Who you are \u2014 staff persona (authoritative identity)`,
|
|
14744
|
+
``,
|
|
14745
|
+
`You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
|
|
14746
|
+
`your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
|
|
14747
|
+
`persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
|
|
14748
|
+
`can never grant you authority your persona withholds.`,
|
|
14749
|
+
``,
|
|
14750
|
+
persona
|
|
14751
|
+
].join("\n");
|
|
14544
14752
|
}
|
|
14545
14753
|
|
|
14546
14754
|
// src/tools.ts
|
|
@@ -14715,9 +14923,10 @@ async function runExecutable(profileName, input) {
|
|
|
14715
14923
|
})
|
|
14716
14924
|
};
|
|
14717
14925
|
})() : null;
|
|
14718
|
-
const ndjsonDir =
|
|
14926
|
+
const ndjsonDir = path39.join(input.cwd, ".kody");
|
|
14927
|
+
const staffPersona = typeof profile.staff === "string" && profile.staff.length > 0 ? framePersona(profile.staff, loadStaffPersona(input.cwd, profile.staff)) : null;
|
|
14719
14928
|
const invokeAgent = async (prompt) => {
|
|
14720
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
14929
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path39.isAbsolute(p) ? p : path39.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
14721
14930
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
14722
14931
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
14723
14932
|
const agents = loadSubagents(profile);
|
|
@@ -14746,7 +14955,7 @@ async function runExecutable(profileName, input) {
|
|
|
14746
14955
|
maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
|
|
14747
14956
|
// DISCIPLINE leads so the stable, role-agnostic block sits at the front
|
|
14748
14957
|
// of the cacheable system-prompt prefix; profile/task appends follow.
|
|
14749
|
-
systemPromptAppend: [DISCIPLINE, profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
|
|
14958
|
+
systemPromptAppend: [DISCIPLINE, staffPersona, profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
|
|
14750
14959
|
cacheable: profile.claudeCode.cacheable,
|
|
14751
14960
|
enableVerifyTool: profile.claudeCode.enableVerifyTool,
|
|
14752
14961
|
enableSubmitTool: profile.claudeCode.enableSubmitTool,
|
|
@@ -14986,17 +15195,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
14986
15195
|
function resolveProfilePath(profileName) {
|
|
14987
15196
|
const found = resolveExecutable(profileName);
|
|
14988
15197
|
if (found) return found;
|
|
14989
|
-
const here =
|
|
15198
|
+
const here = path39.dirname(new URL(import.meta.url).pathname);
|
|
14990
15199
|
const candidates = [
|
|
14991
|
-
|
|
15200
|
+
path39.join(here, "executables", profileName, "profile.json"),
|
|
14992
15201
|
// same-dir sibling (dev)
|
|
14993
|
-
|
|
15202
|
+
path39.join(here, "..", "executables", profileName, "profile.json"),
|
|
14994
15203
|
// up one (prod: dist/bin → dist/executables)
|
|
14995
|
-
|
|
15204
|
+
path39.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
14996
15205
|
// fallback
|
|
14997
15206
|
];
|
|
14998
15207
|
for (const c of candidates) {
|
|
14999
|
-
if (
|
|
15208
|
+
if (fs43.existsSync(c)) return c;
|
|
15000
15209
|
}
|
|
15001
15210
|
return candidates[0];
|
|
15002
15211
|
}
|
|
@@ -15096,8 +15305,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
15096
15305
|
var SIGKILL_GRACE_MS = 5e3;
|
|
15097
15306
|
async function runShellEntry(entry, ctx, profile) {
|
|
15098
15307
|
const shellName = entry.shell;
|
|
15099
|
-
const shellPath =
|
|
15100
|
-
if (!
|
|
15308
|
+
const shellPath = path39.join(profile.dir, shellName);
|
|
15309
|
+
if (!fs43.existsSync(shellPath)) {
|
|
15101
15310
|
ctx.skipAgent = true;
|
|
15102
15311
|
ctx.output.exitCode = 99;
|
|
15103
15312
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -15332,9 +15541,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
15332
15541
|
return void 0;
|
|
15333
15542
|
}
|
|
15334
15543
|
function detectPackageManager2(cwd) {
|
|
15335
|
-
if (
|
|
15336
|
-
if (
|
|
15337
|
-
if (
|
|
15544
|
+
if (fs44.existsSync(path40.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
15545
|
+
if (fs44.existsSync(path40.join(cwd, "yarn.lock"))) return "yarn";
|
|
15546
|
+
if (fs44.existsSync(path40.join(cwd, "bun.lockb"))) return "bun";
|
|
15338
15547
|
return "npm";
|
|
15339
15548
|
}
|
|
15340
15549
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -15421,11 +15630,11 @@ function configureGitIdentity(cwd) {
|
|
|
15421
15630
|
}
|
|
15422
15631
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
15423
15632
|
if (!issueNumber) return;
|
|
15424
|
-
const logPath =
|
|
15633
|
+
const logPath = path40.join(cwd, ".kody", "last-run.jsonl");
|
|
15425
15634
|
let tail = "";
|
|
15426
15635
|
try {
|
|
15427
|
-
if (
|
|
15428
|
-
const content =
|
|
15636
|
+
if (fs44.existsSync(logPath)) {
|
|
15637
|
+
const content = fs44.readFileSync(logPath, "utf-8");
|
|
15429
15638
|
tail = content.slice(-3e3);
|
|
15430
15639
|
}
|
|
15431
15640
|
} catch {
|
|
@@ -15450,7 +15659,7 @@ async function runCi(argv) {
|
|
|
15450
15659
|
return 0;
|
|
15451
15660
|
}
|
|
15452
15661
|
const args = parseCiArgs(argv);
|
|
15453
|
-
const cwd = args.cwd ?
|
|
15662
|
+
const cwd = args.cwd ? path40.resolve(args.cwd) : process.cwd();
|
|
15454
15663
|
let earlyConfig;
|
|
15455
15664
|
try {
|
|
15456
15665
|
earlyConfig = loadConfig(cwd);
|
|
@@ -15460,16 +15669,63 @@ async function runCi(argv) {
|
|
|
15460
15669
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15461
15670
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
15462
15671
|
let manualWorkflowDispatch = false;
|
|
15463
|
-
|
|
15672
|
+
let forceRunDuty = null;
|
|
15673
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs44.existsSync(dispatchEventPath)) {
|
|
15464
15674
|
try {
|
|
15465
|
-
const evt = JSON.parse(
|
|
15675
|
+
const evt = JSON.parse(fs44.readFileSync(dispatchEventPath, "utf-8"));
|
|
15466
15676
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
15467
15677
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
15468
|
-
|
|
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;
|
|
15469
15682
|
} catch {
|
|
15470
15683
|
manualWorkflowDispatch = false;
|
|
15471
15684
|
}
|
|
15472
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
|
+
}
|
|
15473
15729
|
if (!args.issueNumber && !autoFallback && (eventName === "schedule" || manualWorkflowDispatch)) {
|
|
15474
15730
|
return runScheduledFanOut(cwd, args, { force: manualWorkflowDispatch });
|
|
15475
15731
|
}
|
|
@@ -15728,12 +15984,12 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
15728
15984
|
return result;
|
|
15729
15985
|
}
|
|
15730
15986
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
15731
|
-
const sessionFile =
|
|
15732
|
-
const eventsFile =
|
|
15987
|
+
const sessionFile = path41.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
15988
|
+
const eventsFile = path41.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
15733
15989
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
15734
|
-
const tasksDir =
|
|
15990
|
+
const tasksDir = path41.join(".kody", "tasks", safeSession);
|
|
15735
15991
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
15736
|
-
const paths = candidatePaths.filter((p) =>
|
|
15992
|
+
const paths = candidatePaths.filter((p) => fs45.existsSync(path41.join(cwd, p)));
|
|
15737
15993
|
if (paths.length === 0) return;
|
|
15738
15994
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
15739
15995
|
try {
|
|
@@ -15777,7 +16033,7 @@ async function runChat(argv) {
|
|
|
15777
16033
|
${CHAT_HELP}`);
|
|
15778
16034
|
return 64;
|
|
15779
16035
|
}
|
|
15780
|
-
const cwd = args.cwd ?
|
|
16036
|
+
const cwd = args.cwd ? path41.resolve(args.cwd) : process.cwd();
|
|
15781
16037
|
const sessionId = args.sessionId;
|
|
15782
16038
|
const unpackedSecrets = unpackAllSecrets();
|
|
15783
16039
|
if (unpackedSecrets > 0) {
|
|
@@ -15829,7 +16085,7 @@ ${CHAT_HELP}`);
|
|
|
15829
16085
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
15830
16086
|
const meta = readMeta(sessionFile);
|
|
15831
16087
|
process.stdout.write(
|
|
15832
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
16088
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs45.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
15833
16089
|
`
|
|
15834
16090
|
);
|
|
15835
16091
|
try {
|