@kody-ade/kody-engine 0.4.203 → 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 +124 -48
- package/dist/executables/types.ts +9 -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"
|
|
@@ -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.0",
|
|
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";
|
|
@@ -3249,8 +3290,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
3249
3290
|
|
|
3250
3291
|
// src/kody-cli.ts
|
|
3251
3292
|
import { execFileSync as execFileSync28 } from "child_process";
|
|
3252
|
-
import * as
|
|
3253
|
-
import * as
|
|
3293
|
+
import * as fs44 from "fs";
|
|
3294
|
+
import * as path40 from "path";
|
|
3254
3295
|
|
|
3255
3296
|
// src/app-auth.ts
|
|
3256
3297
|
import { createSign } from "crypto";
|
|
@@ -3676,8 +3717,8 @@ function coerceBare(spec, value) {
|
|
|
3676
3717
|
|
|
3677
3718
|
// src/executor.ts
|
|
3678
3719
|
import { spawn as spawn10 } from "child_process";
|
|
3679
|
-
import * as
|
|
3680
|
-
import * as
|
|
3720
|
+
import * as fs43 from "fs";
|
|
3721
|
+
import * as path39 from "path";
|
|
3681
3722
|
|
|
3682
3723
|
// src/container.ts
|
|
3683
3724
|
init_events();
|
|
@@ -3913,6 +3954,8 @@ function loadProfile(profilePath) {
|
|
|
3913
3954
|
const profile = {
|
|
3914
3955
|
name: requireString(profilePath, r, "name"),
|
|
3915
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,
|
|
3916
3959
|
role,
|
|
3917
3960
|
kind,
|
|
3918
3961
|
schedule: typeof r.schedule === "string" ? r.schedule : void 0,
|
|
@@ -6342,10 +6385,10 @@ import * as fs25 from "fs";
|
|
|
6342
6385
|
import * as path22 from "path";
|
|
6343
6386
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
6344
6387
|
var GoalStateError = class extends Error {
|
|
6345
|
-
constructor(
|
|
6346
|
-
super(`Invalid goal state at ${
|
|
6388
|
+
constructor(path42, message) {
|
|
6389
|
+
super(`Invalid goal state at ${path42}:
|
|
6347
6390
|
${message}`);
|
|
6348
|
-
this.path =
|
|
6391
|
+
this.path = path42;
|
|
6349
6392
|
this.name = "GoalStateError";
|
|
6350
6393
|
}
|
|
6351
6394
|
path;
|
|
@@ -10993,8 +11036,8 @@ var FlyClient = class {
|
|
|
10993
11036
|
get fetch() {
|
|
10994
11037
|
return this.opts.fetchImpl ?? fetch;
|
|
10995
11038
|
}
|
|
10996
|
-
async call(
|
|
10997
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
11039
|
+
async call(path42, init = {}) {
|
|
11040
|
+
const res = await this.fetch(`${FLY_API_BASE}${path42}`, {
|
|
10998
11041
|
method: init.method ?? "GET",
|
|
10999
11042
|
headers: {
|
|
11000
11043
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -11005,7 +11048,7 @@ var FlyClient = class {
|
|
|
11005
11048
|
if (res.status === 404 && init.allow404) return null;
|
|
11006
11049
|
if (!res.ok) {
|
|
11007
11050
|
const text = await res.text().catch(() => "");
|
|
11008
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
11051
|
+
throw new Error(`Fly API ${res.status} on ${path42}: ${text.slice(0, 200) || res.statusText}`);
|
|
11009
11052
|
}
|
|
11010
11053
|
if (res.status === 204) return null;
|
|
11011
11054
|
const raw = await res.text();
|
|
@@ -14501,9 +14544,41 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
14501
14544
|
...Object.keys(postflightScripts)
|
|
14502
14545
|
]);
|
|
14503
14546
|
|
|
14504
|
-
// src/
|
|
14547
|
+
// src/staff.ts
|
|
14505
14548
|
import * as fs41 from "fs";
|
|
14506
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";
|
|
14507
14582
|
function splitFrontmatter2(raw) {
|
|
14508
14583
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
14509
14584
|
if (!match) return { fm: {}, body: raw.trim() };
|
|
@@ -14516,10 +14591,10 @@ function splitFrontmatter2(raw) {
|
|
|
14516
14591
|
return { fm, body: (match[2] ?? "").trim() };
|
|
14517
14592
|
}
|
|
14518
14593
|
function resolveAgentFile(profileDir, name) {
|
|
14519
|
-
const local =
|
|
14520
|
-
if (
|
|
14521
|
-
const central =
|
|
14522
|
-
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;
|
|
14523
14598
|
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
14524
14599
|
}
|
|
14525
14600
|
function loadSubagents(profile) {
|
|
@@ -14527,7 +14602,7 @@ function loadSubagents(profile) {
|
|
|
14527
14602
|
if (!names || names.length === 0) return void 0;
|
|
14528
14603
|
const agents = {};
|
|
14529
14604
|
for (const name of names) {
|
|
14530
|
-
const { fm, body } = splitFrontmatter2(
|
|
14605
|
+
const { fm, body } = splitFrontmatter2(fs42.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
|
|
14531
14606
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
14532
14607
|
const def = {
|
|
14533
14608
|
description: fm.description ?? `Subagent ${name}`,
|
|
@@ -14715,9 +14790,10 @@ async function runExecutable(profileName, input) {
|
|
|
14715
14790
|
})
|
|
14716
14791
|
};
|
|
14717
14792
|
})() : null;
|
|
14718
|
-
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;
|
|
14719
14795
|
const invokeAgent = async (prompt) => {
|
|
14720
|
-
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);
|
|
14721
14797
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
14722
14798
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
14723
14799
|
const agents = loadSubagents(profile);
|
|
@@ -14746,7 +14822,7 @@ async function runExecutable(profileName, input) {
|
|
|
14746
14822
|
maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
|
|
14747
14823
|
// DISCIPLINE leads so the stable, role-agnostic block sits at the front
|
|
14748
14824
|
// 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,
|
|
14825
|
+
systemPromptAppend: [DISCIPLINE, staffPersona, profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
|
|
14750
14826
|
cacheable: profile.claudeCode.cacheable,
|
|
14751
14827
|
enableVerifyTool: profile.claudeCode.enableVerifyTool,
|
|
14752
14828
|
enableSubmitTool: profile.claudeCode.enableSubmitTool,
|
|
@@ -14986,17 +15062,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
14986
15062
|
function resolveProfilePath(profileName) {
|
|
14987
15063
|
const found = resolveExecutable(profileName);
|
|
14988
15064
|
if (found) return found;
|
|
14989
|
-
const here =
|
|
15065
|
+
const here = path39.dirname(new URL(import.meta.url).pathname);
|
|
14990
15066
|
const candidates = [
|
|
14991
|
-
|
|
15067
|
+
path39.join(here, "executables", profileName, "profile.json"),
|
|
14992
15068
|
// same-dir sibling (dev)
|
|
14993
|
-
|
|
15069
|
+
path39.join(here, "..", "executables", profileName, "profile.json"),
|
|
14994
15070
|
// up one (prod: dist/bin → dist/executables)
|
|
14995
|
-
|
|
15071
|
+
path39.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
14996
15072
|
// fallback
|
|
14997
15073
|
];
|
|
14998
15074
|
for (const c of candidates) {
|
|
14999
|
-
if (
|
|
15075
|
+
if (fs43.existsSync(c)) return c;
|
|
15000
15076
|
}
|
|
15001
15077
|
return candidates[0];
|
|
15002
15078
|
}
|
|
@@ -15096,8 +15172,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
15096
15172
|
var SIGKILL_GRACE_MS = 5e3;
|
|
15097
15173
|
async function runShellEntry(entry, ctx, profile) {
|
|
15098
15174
|
const shellName = entry.shell;
|
|
15099
|
-
const shellPath =
|
|
15100
|
-
if (!
|
|
15175
|
+
const shellPath = path39.join(profile.dir, shellName);
|
|
15176
|
+
if (!fs43.existsSync(shellPath)) {
|
|
15101
15177
|
ctx.skipAgent = true;
|
|
15102
15178
|
ctx.output.exitCode = 99;
|
|
15103
15179
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -15332,9 +15408,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
15332
15408
|
return void 0;
|
|
15333
15409
|
}
|
|
15334
15410
|
function detectPackageManager2(cwd) {
|
|
15335
|
-
if (
|
|
15336
|
-
if (
|
|
15337
|
-
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";
|
|
15338
15414
|
return "npm";
|
|
15339
15415
|
}
|
|
15340
15416
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -15421,11 +15497,11 @@ function configureGitIdentity(cwd) {
|
|
|
15421
15497
|
}
|
|
15422
15498
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
15423
15499
|
if (!issueNumber) return;
|
|
15424
|
-
const logPath =
|
|
15500
|
+
const logPath = path40.join(cwd, ".kody", "last-run.jsonl");
|
|
15425
15501
|
let tail = "";
|
|
15426
15502
|
try {
|
|
15427
|
-
if (
|
|
15428
|
-
const content =
|
|
15503
|
+
if (fs44.existsSync(logPath)) {
|
|
15504
|
+
const content = fs44.readFileSync(logPath, "utf-8");
|
|
15429
15505
|
tail = content.slice(-3e3);
|
|
15430
15506
|
}
|
|
15431
15507
|
} catch {
|
|
@@ -15450,7 +15526,7 @@ async function runCi(argv) {
|
|
|
15450
15526
|
return 0;
|
|
15451
15527
|
}
|
|
15452
15528
|
const args = parseCiArgs(argv);
|
|
15453
|
-
const cwd = args.cwd ?
|
|
15529
|
+
const cwd = args.cwd ? path40.resolve(args.cwd) : process.cwd();
|
|
15454
15530
|
let earlyConfig;
|
|
15455
15531
|
try {
|
|
15456
15532
|
earlyConfig = loadConfig(cwd);
|
|
@@ -15460,9 +15536,9 @@ async function runCi(argv) {
|
|
|
15460
15536
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15461
15537
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
15462
15538
|
let manualWorkflowDispatch = false;
|
|
15463
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
15539
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs44.existsSync(dispatchEventPath)) {
|
|
15464
15540
|
try {
|
|
15465
|
-
const evt = JSON.parse(
|
|
15541
|
+
const evt = JSON.parse(fs44.readFileSync(dispatchEventPath, "utf-8"));
|
|
15466
15542
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
15467
15543
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
15468
15544
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -15728,12 +15804,12 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
15728
15804
|
return result;
|
|
15729
15805
|
}
|
|
15730
15806
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
15731
|
-
const sessionFile =
|
|
15732
|
-
const eventsFile =
|
|
15807
|
+
const sessionFile = path41.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
15808
|
+
const eventsFile = path41.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
15733
15809
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
15734
|
-
const tasksDir =
|
|
15810
|
+
const tasksDir = path41.join(".kody", "tasks", safeSession);
|
|
15735
15811
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
15736
|
-
const paths = candidatePaths.filter((p) =>
|
|
15812
|
+
const paths = candidatePaths.filter((p) => fs45.existsSync(path41.join(cwd, p)));
|
|
15737
15813
|
if (paths.length === 0) return;
|
|
15738
15814
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
15739
15815
|
try {
|
|
@@ -15777,7 +15853,7 @@ async function runChat(argv) {
|
|
|
15777
15853
|
${CHAT_HELP}`);
|
|
15778
15854
|
return 64;
|
|
15779
15855
|
}
|
|
15780
|
-
const cwd = args.cwd ?
|
|
15856
|
+
const cwd = args.cwd ? path41.resolve(args.cwd) : process.cwd();
|
|
15781
15857
|
const sessionId = args.sessionId;
|
|
15782
15858
|
const unpackedSecrets = unpackAllSecrets();
|
|
15783
15859
|
if (unpackedSecrets > 0) {
|
|
@@ -15829,7 +15905,7 @@ ${CHAT_HELP}`);
|
|
|
15829
15905
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
15830
15906
|
const meta = readMeta(sessionFile);
|
|
15831
15907
|
process.stdout.write(
|
|
15832
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
15908
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs45.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
15833
15909
|
`
|
|
15834
15910
|
);
|
|
15835
15911
|
try {
|
|
@@ -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",
|