@kody-ade/kody-engine 0.4.202 → 0.4.204-next.0
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 +153 -46
- package/dist/executables/goal-scheduler/scheduler.sh +0 -0
- package/dist/executables/release-deploy/deploy.sh +0 -0
- package/dist/executables/release-prepare/prepare.sh +0 -0
- package/dist/executables/release-publish/publish.sh +0 -0
- package/dist/executables/resolve/apply-prefer.sh +0 -0
- package/dist/executables/revert/revert.sh +0 -0
- package/dist/executables/types.ts +9 -0
- package/package.json +22 -21
package/dist/bin/kody.js
CHANGED
|
@@ -551,7 +551,11 @@ __export(dutyMcp_exports, {
|
|
|
551
551
|
dispatchWorkflow: () => dispatchWorkflow,
|
|
552
552
|
ensureComment: () => ensureComment,
|
|
553
553
|
ensureIssue: () => ensureIssue,
|
|
554
|
-
|
|
554
|
+
isDispatchGated: () => isDispatchGated,
|
|
555
|
+
parseDutyTrustMode: () => parseDutyTrustMode,
|
|
556
|
+
readCheckRuns: () => readCheckRuns,
|
|
557
|
+
readDutyTrustMode: () => readDutyTrustMode,
|
|
558
|
+
readThread: () => readThread
|
|
555
559
|
});
|
|
556
560
|
import { createSdkMcpServer as createSdkMcpServer3, tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
|
|
557
561
|
import { z as z3 } from "zod";
|
|
@@ -645,6 +649,42 @@ function readLedger(label) {
|
|
|
645
649
|
return { found: false, payload: { error: err instanceof Error ? err.message : String(err) } };
|
|
646
650
|
}
|
|
647
651
|
}
|
|
652
|
+
function parseDutyTrustMode(rawJson, dutySlug) {
|
|
653
|
+
try {
|
|
654
|
+
const parsed = JSON.parse(rawJson);
|
|
655
|
+
return parsed?.duties?.[dutySlug]?.mode === "auto" ? "auto" : "ask";
|
|
656
|
+
} catch {
|
|
657
|
+
return "ask";
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function readDutyTrustMode(repoSlug, dutySlug) {
|
|
661
|
+
if (!dutySlug) return "ask";
|
|
662
|
+
try {
|
|
663
|
+
const b64 = gh(["api", `repos/${repoSlug}/contents/${TRUST_FILE_PATH}?ref=${TRUST_STATE_BRANCH}`, "--jq", ".content"]);
|
|
664
|
+
const json = Buffer.from(b64.trim(), "base64").toString("utf-8");
|
|
665
|
+
return parseDutyTrustMode(json, dutySlug);
|
|
666
|
+
} catch {
|
|
667
|
+
return "ask";
|
|
668
|
+
}
|
|
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
|
+
}
|
|
648
688
|
function readCheckRuns(repoSlug, ref, ignoreNames) {
|
|
649
689
|
const sha = gh(["api", `repos/${repoSlug}/commits/${ref}`, "--jq", ".sha"]).trim();
|
|
650
690
|
const raw = gh([
|
|
@@ -703,6 +743,14 @@ function dispatchWorkflow(workflowFile, executable, issueNumber) {
|
|
|
703
743
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
704
744
|
}
|
|
705
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
|
+
}
|
|
751
|
+
function trustRefusal(dutySlug) {
|
|
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.`;
|
|
753
|
+
}
|
|
706
754
|
function buildDutyMcpServer(opts) {
|
|
707
755
|
const workflowFile = opts.workflowFile ?? "kody.yml";
|
|
708
756
|
const listTool = tool3(
|
|
@@ -728,6 +776,9 @@ function buildDutyMcpServer(opts) {
|
|
|
728
776
|
pr: z3.number().int().positive().describe("PR number to repair.")
|
|
729
777
|
},
|
|
730
778
|
async (args) => {
|
|
779
|
+
if (isDispatchGated(verb, readDutyTrustMode(opts.repoSlug, opts.dutySlug))) {
|
|
780
|
+
return { content: [{ type: "text", text: trustRefusal(opts.dutySlug) }] };
|
|
781
|
+
}
|
|
731
782
|
const result = dispatchVerb(workflowFile, verb, args.pr);
|
|
732
783
|
const text = result.ok ? `Dispatched \`${verb}\` on PR #${args.pr}. The repair runs in its own workflow_dispatch \u2014 wait for the next tick to see the new headSha.` : `Dispatch failed for \`${verb}\` on PR #${args.pr}: ${result.error}`;
|
|
733
784
|
return {
|
|
@@ -792,6 +843,18 @@ function buildDutyMcpServer(opts) {
|
|
|
792
843
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
793
844
|
}
|
|
794
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
|
+
);
|
|
795
858
|
const ensureIssueTool = tool3(
|
|
796
859
|
"ensure_issue",
|
|
797
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.",
|
|
@@ -830,6 +893,9 @@ function buildDutyMcpServer(opts) {
|
|
|
830
893
|
issueNumber: z3.number().int().positive().describe("Issue (or PR) number forwarded as issue_number.")
|
|
831
894
|
},
|
|
832
895
|
async (args) => {
|
|
896
|
+
if (isDispatchGated(args.executable, readDutyTrustMode(opts.repoSlug, opts.dutySlug))) {
|
|
897
|
+
return { content: [{ type: "text", text: trustRefusal(opts.dutySlug) }] };
|
|
898
|
+
}
|
|
833
899
|
const result = dispatchWorkflow(workflowFile, args.executable, args.issueNumber);
|
|
834
900
|
const text = result.ok ? `Dispatched \`${args.executable}\` on #${args.issueNumber} via workflow_dispatch.` : `Dispatch failed for \`${args.executable}\` on #${args.issueNumber}: ${result.error}`;
|
|
835
901
|
return { content: [{ type: "text", text }] };
|
|
@@ -846,6 +912,7 @@ function buildDutyMcpServer(opts) {
|
|
|
846
912
|
recommendTool,
|
|
847
913
|
ledgerTool,
|
|
848
914
|
checkRunsTool,
|
|
915
|
+
readThreadTool,
|
|
849
916
|
ensureIssueTool,
|
|
850
917
|
ensureCommentTool,
|
|
851
918
|
dispatchTool
|
|
@@ -853,17 +920,21 @@ function buildDutyMcpServer(opts) {
|
|
|
853
920
|
});
|
|
854
921
|
return { server };
|
|
855
922
|
}
|
|
856
|
-
var FAIL_CONCLUSIONS, RUNNING_STATUSES, 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;
|
|
857
924
|
var init_dutyMcp = __esm({
|
|
858
925
|
"src/dutyMcp.ts"() {
|
|
859
926
|
"use strict";
|
|
860
927
|
init_issue();
|
|
861
928
|
FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE", "CANCELLED"]);
|
|
862
929
|
RUNNING_STATUSES = /* @__PURE__ */ new Set(["IN_PROGRESS", "QUEUED", "PENDING", "WAITING", "REQUESTED"]);
|
|
930
|
+
TRUST_FILE_PATH = ".kody/state/trust.json";
|
|
931
|
+
TRUST_STATE_BRANCH = "kody-state";
|
|
932
|
+
THREAD_BODY_MAX = 4e3;
|
|
863
933
|
CHECK_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "STARTUP_FAILURE", "ACTION_REQUIRED"]);
|
|
864
934
|
DEFAULT_IGNORE_CHECKS = ["run", "kody", "job-tick", "goal-tick", "worker-ask", "chat"];
|
|
865
935
|
trackMarker = (key) => `<!-- kody-track:${key} -->`;
|
|
866
936
|
commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
|
|
937
|
+
GATE_EXEMPT_EXECUTABLES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
|
|
867
938
|
DUTY_MCP_TOOL_NAMES = [
|
|
868
939
|
"list_prs_to_repair",
|
|
869
940
|
"sync_pr",
|
|
@@ -872,6 +943,7 @@ var init_dutyMcp = __esm({
|
|
|
872
943
|
"recommend_to_operator",
|
|
873
944
|
"read_ledger",
|
|
874
945
|
"read_check_runs",
|
|
946
|
+
"read_thread",
|
|
875
947
|
"ensure_issue",
|
|
876
948
|
"ensure_comment",
|
|
877
949
|
"dispatch_workflow"
|
|
@@ -1411,7 +1483,7 @@ var init_loadCoverageRules = __esm({
|
|
|
1411
1483
|
// package.json
|
|
1412
1484
|
var package_default = {
|
|
1413
1485
|
name: "@kody-ade/kody-engine",
|
|
1414
|
-
version: "0.4.
|
|
1486
|
+
version: "0.4.204-next.0",
|
|
1415
1487
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
1416
1488
|
license: "MIT",
|
|
1417
1489
|
type: "module",
|
|
@@ -1469,8 +1541,8 @@ var package_default = {
|
|
|
1469
1541
|
|
|
1470
1542
|
// src/chat-cli.ts
|
|
1471
1543
|
import { execFileSync as execFileSync29 } from "child_process";
|
|
1472
|
-
import * as
|
|
1473
|
-
import * as
|
|
1544
|
+
import * as fs45 from "fs";
|
|
1545
|
+
import * as path41 from "path";
|
|
1474
1546
|
|
|
1475
1547
|
// src/chat/events.ts
|
|
1476
1548
|
import * as fs from "fs";
|
|
@@ -3218,8 +3290,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
3218
3290
|
|
|
3219
3291
|
// src/kody-cli.ts
|
|
3220
3292
|
import { execFileSync as execFileSync28 } from "child_process";
|
|
3221
|
-
import * as
|
|
3222
|
-
import * as
|
|
3293
|
+
import * as fs44 from "fs";
|
|
3294
|
+
import * as path40 from "path";
|
|
3223
3295
|
|
|
3224
3296
|
// src/app-auth.ts
|
|
3225
3297
|
import { createSign } from "crypto";
|
|
@@ -3645,8 +3717,8 @@ function coerceBare(spec, value) {
|
|
|
3645
3717
|
|
|
3646
3718
|
// src/executor.ts
|
|
3647
3719
|
import { spawn as spawn10 } from "child_process";
|
|
3648
|
-
import * as
|
|
3649
|
-
import * as
|
|
3720
|
+
import * as fs43 from "fs";
|
|
3721
|
+
import * as path39 from "path";
|
|
3650
3722
|
|
|
3651
3723
|
// src/container.ts
|
|
3652
3724
|
init_events();
|
|
@@ -3882,6 +3954,8 @@ function loadProfile(profilePath) {
|
|
|
3882
3954
|
const profile = {
|
|
3883
3955
|
name: requireString(profilePath, r, "name"),
|
|
3884
3956
|
describe: typeof r.describe === "string" ? r.describe : "",
|
|
3957
|
+
// Optional persona to run as. Empty/blank string → undefined (no persona).
|
|
3958
|
+
staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
|
|
3885
3959
|
role,
|
|
3886
3960
|
kind,
|
|
3887
3961
|
schedule: typeof r.schedule === "string" ? r.schedule : void 0,
|
|
@@ -6311,10 +6385,10 @@ import * as fs25 from "fs";
|
|
|
6311
6385
|
import * as path22 from "path";
|
|
6312
6386
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
6313
6387
|
var GoalStateError = class extends Error {
|
|
6314
|
-
constructor(
|
|
6315
|
-
super(`Invalid goal state at ${
|
|
6388
|
+
constructor(path42, message) {
|
|
6389
|
+
super(`Invalid goal state at ${path42}:
|
|
6316
6390
|
${message}`);
|
|
6317
|
-
this.path =
|
|
6391
|
+
this.path = path42;
|
|
6318
6392
|
this.name = "GoalStateError";
|
|
6319
6393
|
}
|
|
6320
6394
|
path;
|
|
@@ -10962,8 +11036,8 @@ var FlyClient = class {
|
|
|
10962
11036
|
get fetch() {
|
|
10963
11037
|
return this.opts.fetchImpl ?? fetch;
|
|
10964
11038
|
}
|
|
10965
|
-
async call(
|
|
10966
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
11039
|
+
async call(path42, init = {}) {
|
|
11040
|
+
const res = await this.fetch(`${FLY_API_BASE}${path42}`, {
|
|
10967
11041
|
method: init.method ?? "GET",
|
|
10968
11042
|
headers: {
|
|
10969
11043
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -10974,7 +11048,7 @@ var FlyClient = class {
|
|
|
10974
11048
|
if (res.status === 404 && init.allow404) return null;
|
|
10975
11049
|
if (!res.ok) {
|
|
10976
11050
|
const text = await res.text().catch(() => "");
|
|
10977
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
11051
|
+
throw new Error(`Fly API ${res.status} on ${path42}: ${text.slice(0, 200) || res.statusText}`);
|
|
10978
11052
|
}
|
|
10979
11053
|
if (res.status === 204) return null;
|
|
10980
11054
|
const raw = await res.text();
|
|
@@ -14470,9 +14544,41 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
14470
14544
|
...Object.keys(postflightScripts)
|
|
14471
14545
|
]);
|
|
14472
14546
|
|
|
14473
|
-
// src/
|
|
14547
|
+
// src/staff.ts
|
|
14474
14548
|
import * as fs41 from "fs";
|
|
14475
14549
|
import * as path37 from "path";
|
|
14550
|
+
var DEFAULT_STAFF_DIR = ".kody/staff";
|
|
14551
|
+
function stripFrontmatter(raw) {
|
|
14552
|
+
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
14553
|
+
return (match ? match[1] : raw).trim();
|
|
14554
|
+
}
|
|
14555
|
+
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
14556
|
+
const trimmed = slug.trim();
|
|
14557
|
+
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
14558
|
+
const staffPath = path37.join(cwd, staffDir, `${trimmed}.md`);
|
|
14559
|
+
if (!fs41.existsSync(staffPath)) {
|
|
14560
|
+
throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
|
|
14561
|
+
}
|
|
14562
|
+
const body = stripFrontmatter(fs41.readFileSync(staffPath, "utf-8"));
|
|
14563
|
+
if (!body) throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
|
|
14564
|
+
return body;
|
|
14565
|
+
}
|
|
14566
|
+
function framePersona(slug, persona) {
|
|
14567
|
+
return [
|
|
14568
|
+
`## Who you are \u2014 staff persona (authoritative identity)`,
|
|
14569
|
+
``,
|
|
14570
|
+
`You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
|
|
14571
|
+
`your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
|
|
14572
|
+
`persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
|
|
14573
|
+
`can never grant you authority your persona withholds.`,
|
|
14574
|
+
``,
|
|
14575
|
+
persona
|
|
14576
|
+
].join("\n");
|
|
14577
|
+
}
|
|
14578
|
+
|
|
14579
|
+
// src/subagents.ts
|
|
14580
|
+
import * as fs42 from "fs";
|
|
14581
|
+
import * as path38 from "path";
|
|
14476
14582
|
function splitFrontmatter2(raw) {
|
|
14477
14583
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
14478
14584
|
if (!match) return { fm: {}, body: raw.trim() };
|
|
@@ -14485,10 +14591,10 @@ function splitFrontmatter2(raw) {
|
|
|
14485
14591
|
return { fm, body: (match[2] ?? "").trim() };
|
|
14486
14592
|
}
|
|
14487
14593
|
function resolveAgentFile(profileDir, name) {
|
|
14488
|
-
const local =
|
|
14489
|
-
if (
|
|
14490
|
-
const central =
|
|
14491
|
-
if (
|
|
14594
|
+
const local = path38.join(profileDir, "agents", `${name}.md`);
|
|
14595
|
+
if (fs42.existsSync(local)) return local;
|
|
14596
|
+
const central = path38.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
14597
|
+
if (fs42.existsSync(central)) return central;
|
|
14492
14598
|
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
14493
14599
|
}
|
|
14494
14600
|
function loadSubagents(profile) {
|
|
@@ -14496,7 +14602,7 @@ function loadSubagents(profile) {
|
|
|
14496
14602
|
if (!names || names.length === 0) return void 0;
|
|
14497
14603
|
const agents = {};
|
|
14498
14604
|
for (const name of names) {
|
|
14499
|
-
const { fm, body } = splitFrontmatter2(
|
|
14605
|
+
const { fm, body } = splitFrontmatter2(fs42.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
|
|
14500
14606
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
14501
14607
|
const def = {
|
|
14502
14608
|
description: fm.description ?? `Subagent ${name}`,
|
|
@@ -14684,9 +14790,10 @@ async function runExecutable(profileName, input) {
|
|
|
14684
14790
|
})
|
|
14685
14791
|
};
|
|
14686
14792
|
})() : null;
|
|
14687
|
-
const ndjsonDir =
|
|
14793
|
+
const ndjsonDir = path39.join(input.cwd, ".kody");
|
|
14794
|
+
const staffPersona = typeof profile.staff === "string" && profile.staff.length > 0 ? framePersona(profile.staff, loadStaffPersona(input.cwd, profile.staff)) : null;
|
|
14688
14795
|
const invokeAgent = async (prompt) => {
|
|
14689
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
14796
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path39.isAbsolute(p) ? p : path39.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
14690
14797
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
14691
14798
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
14692
14799
|
const agents = loadSubagents(profile);
|
|
@@ -14715,7 +14822,7 @@ async function runExecutable(profileName, input) {
|
|
|
14715
14822
|
maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
|
|
14716
14823
|
// DISCIPLINE leads so the stable, role-agnostic block sits at the front
|
|
14717
14824
|
// of the cacheable system-prompt prefix; profile/task appends follow.
|
|
14718
|
-
systemPromptAppend: [DISCIPLINE, profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
|
|
14825
|
+
systemPromptAppend: [DISCIPLINE, staffPersona, profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
|
|
14719
14826
|
cacheable: profile.claudeCode.cacheable,
|
|
14720
14827
|
enableVerifyTool: profile.claudeCode.enableVerifyTool,
|
|
14721
14828
|
enableSubmitTool: profile.claudeCode.enableSubmitTool,
|
|
@@ -14955,17 +15062,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
14955
15062
|
function resolveProfilePath(profileName) {
|
|
14956
15063
|
const found = resolveExecutable(profileName);
|
|
14957
15064
|
if (found) return found;
|
|
14958
|
-
const here =
|
|
15065
|
+
const here = path39.dirname(new URL(import.meta.url).pathname);
|
|
14959
15066
|
const candidates = [
|
|
14960
|
-
|
|
15067
|
+
path39.join(here, "executables", profileName, "profile.json"),
|
|
14961
15068
|
// same-dir sibling (dev)
|
|
14962
|
-
|
|
15069
|
+
path39.join(here, "..", "executables", profileName, "profile.json"),
|
|
14963
15070
|
// up one (prod: dist/bin → dist/executables)
|
|
14964
|
-
|
|
15071
|
+
path39.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
14965
15072
|
// fallback
|
|
14966
15073
|
];
|
|
14967
15074
|
for (const c of candidates) {
|
|
14968
|
-
if (
|
|
15075
|
+
if (fs43.existsSync(c)) return c;
|
|
14969
15076
|
}
|
|
14970
15077
|
return candidates[0];
|
|
14971
15078
|
}
|
|
@@ -15065,8 +15172,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
15065
15172
|
var SIGKILL_GRACE_MS = 5e3;
|
|
15066
15173
|
async function runShellEntry(entry, ctx, profile) {
|
|
15067
15174
|
const shellName = entry.shell;
|
|
15068
|
-
const shellPath =
|
|
15069
|
-
if (!
|
|
15175
|
+
const shellPath = path39.join(profile.dir, shellName);
|
|
15176
|
+
if (!fs43.existsSync(shellPath)) {
|
|
15070
15177
|
ctx.skipAgent = true;
|
|
15071
15178
|
ctx.output.exitCode = 99;
|
|
15072
15179
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -15301,9 +15408,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
15301
15408
|
return void 0;
|
|
15302
15409
|
}
|
|
15303
15410
|
function detectPackageManager2(cwd) {
|
|
15304
|
-
if (
|
|
15305
|
-
if (
|
|
15306
|
-
if (
|
|
15411
|
+
if (fs44.existsSync(path40.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
15412
|
+
if (fs44.existsSync(path40.join(cwd, "yarn.lock"))) return "yarn";
|
|
15413
|
+
if (fs44.existsSync(path40.join(cwd, "bun.lockb"))) return "bun";
|
|
15307
15414
|
return "npm";
|
|
15308
15415
|
}
|
|
15309
15416
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -15390,11 +15497,11 @@ function configureGitIdentity(cwd) {
|
|
|
15390
15497
|
}
|
|
15391
15498
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
15392
15499
|
if (!issueNumber) return;
|
|
15393
|
-
const logPath =
|
|
15500
|
+
const logPath = path40.join(cwd, ".kody", "last-run.jsonl");
|
|
15394
15501
|
let tail = "";
|
|
15395
15502
|
try {
|
|
15396
|
-
if (
|
|
15397
|
-
const content =
|
|
15503
|
+
if (fs44.existsSync(logPath)) {
|
|
15504
|
+
const content = fs44.readFileSync(logPath, "utf-8");
|
|
15398
15505
|
tail = content.slice(-3e3);
|
|
15399
15506
|
}
|
|
15400
15507
|
} catch {
|
|
@@ -15419,7 +15526,7 @@ async function runCi(argv) {
|
|
|
15419
15526
|
return 0;
|
|
15420
15527
|
}
|
|
15421
15528
|
const args = parseCiArgs(argv);
|
|
15422
|
-
const cwd = args.cwd ?
|
|
15529
|
+
const cwd = args.cwd ? path40.resolve(args.cwd) : process.cwd();
|
|
15423
15530
|
let earlyConfig;
|
|
15424
15531
|
try {
|
|
15425
15532
|
earlyConfig = loadConfig(cwd);
|
|
@@ -15429,9 +15536,9 @@ async function runCi(argv) {
|
|
|
15429
15536
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15430
15537
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
15431
15538
|
let manualWorkflowDispatch = false;
|
|
15432
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
15539
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs44.existsSync(dispatchEventPath)) {
|
|
15433
15540
|
try {
|
|
15434
|
-
const evt = JSON.parse(
|
|
15541
|
+
const evt = JSON.parse(fs44.readFileSync(dispatchEventPath, "utf-8"));
|
|
15435
15542
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
15436
15543
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
15437
15544
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -15697,12 +15804,12 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
15697
15804
|
return result;
|
|
15698
15805
|
}
|
|
15699
15806
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
15700
|
-
const sessionFile =
|
|
15701
|
-
const eventsFile =
|
|
15807
|
+
const sessionFile = path41.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
15808
|
+
const eventsFile = path41.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
15702
15809
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
15703
|
-
const tasksDir =
|
|
15810
|
+
const tasksDir = path41.join(".kody", "tasks", safeSession);
|
|
15704
15811
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
15705
|
-
const paths = candidatePaths.filter((p) =>
|
|
15812
|
+
const paths = candidatePaths.filter((p) => fs45.existsSync(path41.join(cwd, p)));
|
|
15706
15813
|
if (paths.length === 0) return;
|
|
15707
15814
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
15708
15815
|
try {
|
|
@@ -15746,7 +15853,7 @@ async function runChat(argv) {
|
|
|
15746
15853
|
${CHAT_HELP}`);
|
|
15747
15854
|
return 64;
|
|
15748
15855
|
}
|
|
15749
|
-
const cwd = args.cwd ?
|
|
15856
|
+
const cwd = args.cwd ? path41.resolve(args.cwd) : process.cwd();
|
|
15750
15857
|
const sessionId = args.sessionId;
|
|
15751
15858
|
const unpackedSecrets = unpackAllSecrets();
|
|
15752
15859
|
if (unpackedSecrets > 0) {
|
|
@@ -15798,7 +15905,7 @@ ${CHAT_HELP}`);
|
|
|
15798
15905
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
15799
15906
|
const meta = readMeta(sessionFile);
|
|
15800
15907
|
process.stdout.write(
|
|
15801
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
15908
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs45.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
15802
15909
|
`
|
|
15803
15910
|
);
|
|
15804
15911
|
try {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -17,6 +17,15 @@ import type { Phase } from "../state.js"
|
|
|
17
17
|
|
|
18
18
|
export interface Profile {
|
|
19
19
|
name: string
|
|
20
|
+
/**
|
|
21
|
+
* Optional staff member this executable runs *as*. When set, the executor
|
|
22
|
+
* loads `.kody/staff/<staff>.md` and injects that persona (authoritative
|
|
23
|
+
* identity) ahead of the executable's own system-prompt append. This is the
|
|
24
|
+
* unification hook: a "duty" is just an executable + a staff member. Absent →
|
|
25
|
+
* runs with no persona (unchanged legacy behaviour). A declared-but-missing
|
|
26
|
+
* staff file is fatal at run time (see src/staff.ts).
|
|
27
|
+
*/
|
|
28
|
+
staff?: string
|
|
20
29
|
describe: string
|
|
21
30
|
/**
|
|
22
31
|
* Semantic role — what this executable IS, not when it runs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.204-next.0",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,25 @@
|
|
|
12
12
|
"templates",
|
|
13
13
|
"kody.config.schema.json"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"kody:run": "tsx bin/kody.ts",
|
|
17
|
+
"serve": "tsx bin/kody.ts serve",
|
|
18
|
+
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
19
|
+
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
20
|
+
"build": "tsup && node scripts/copy-assets.cjs",
|
|
21
|
+
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
22
|
+
"pretest": "pnpm check:modularity",
|
|
23
|
+
"test": "vitest run tests/unit tests/int --coverage",
|
|
24
|
+
"test:smoke": "vitest run tests/smoke --no-coverage",
|
|
25
|
+
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
26
|
+
"test:all": "vitest run tests --no-coverage",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"lint": "biome check",
|
|
29
|
+
"lint:fix": "biome check --write",
|
|
30
|
+
"format": "biome format --write",
|
|
31
|
+
"brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
|
|
32
|
+
"prepublishOnly": "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm build"
|
|
33
|
+
},
|
|
15
34
|
"dependencies": {
|
|
16
35
|
"@actions/cache": "^6.0.0",
|
|
17
36
|
"@anthropic-ai/claude-agent-sdk": "0.2.119",
|
|
@@ -34,23 +53,5 @@
|
|
|
34
53
|
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
35
54
|
},
|
|
36
55
|
"homepage": "https://github.com/aharonyaircohen/kody-engine",
|
|
37
|
-
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
38
|
-
|
|
39
|
-
"kody:run": "tsx bin/kody.ts",
|
|
40
|
-
"serve": "tsx bin/kody.ts serve",
|
|
41
|
-
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
42
|
-
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
43
|
-
"build": "tsup && node scripts/copy-assets.cjs",
|
|
44
|
-
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
45
|
-
"pretest": "pnpm check:modularity",
|
|
46
|
-
"test": "vitest run tests/unit tests/int --coverage",
|
|
47
|
-
"test:smoke": "vitest run tests/smoke --no-coverage",
|
|
48
|
-
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
49
|
-
"test:all": "vitest run tests --no-coverage",
|
|
50
|
-
"typecheck": "tsc --noEmit",
|
|
51
|
-
"lint": "biome check",
|
|
52
|
-
"lint:fix": "biome check --write",
|
|
53
|
-
"format": "biome format --write",
|
|
54
|
-
"brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
56
|
+
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
57
|
+
}
|