@kody-ade/kody-engine 0.4.203 → 0.4.204-next.2
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 +138 -53
- 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.2",
|
|
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,8 +3720,8 @@ 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();
|
|
@@ -3834,6 +3878,7 @@ var VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
|
|
|
3834
3878
|
var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
|
|
3835
3879
|
var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
3836
3880
|
"name",
|
|
3881
|
+
"staff",
|
|
3837
3882
|
"describe",
|
|
3838
3883
|
"role",
|
|
3839
3884
|
"kind",
|
|
@@ -3913,6 +3958,8 @@ function loadProfile(profilePath) {
|
|
|
3913
3958
|
const profile = {
|
|
3914
3959
|
name: requireString(profilePath, r, "name"),
|
|
3915
3960
|
describe: typeof r.describe === "string" ? r.describe : "",
|
|
3961
|
+
// Optional persona to run as. Empty/blank string → undefined (no persona).
|
|
3962
|
+
staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
|
|
3916
3963
|
role,
|
|
3917
3964
|
kind,
|
|
3918
3965
|
schedule: typeof r.schedule === "string" ? r.schedule : void 0,
|
|
@@ -4294,16 +4341,17 @@ function parseStateComment(body) {
|
|
|
4294
4341
|
flow: parsed.flow
|
|
4295
4342
|
};
|
|
4296
4343
|
}
|
|
4297
|
-
function reduce(state, executable, action, phase) {
|
|
4344
|
+
function reduce(state, executable, action, phase, staff) {
|
|
4298
4345
|
if (!action) return state;
|
|
4299
4346
|
const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
|
|
4300
4347
|
const newExecutables = {
|
|
4301
4348
|
...state.executables,
|
|
4302
4349
|
[executable]: { ...state.executables[executable] ?? { lastAction: null }, lastAction: action }
|
|
4303
4350
|
};
|
|
4351
|
+
const ranAsStaff = typeof staff === "string" && staff.length > 0 ? staff : void 0;
|
|
4304
4352
|
const newHistory = [
|
|
4305
4353
|
...state.history,
|
|
4306
|
-
{ timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action) }
|
|
4354
|
+
{ timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action), staff: ranAsStaff }
|
|
4307
4355
|
].slice(-HISTORY_MAX_ENTRIES);
|
|
4308
4356
|
return {
|
|
4309
4357
|
schemaVersion: 1,
|
|
@@ -4312,6 +4360,7 @@ function reduce(state, executable, action, phase) {
|
|
|
4312
4360
|
attempts: newAttempts,
|
|
4313
4361
|
lastOutcome: action,
|
|
4314
4362
|
currentExecutable: executable,
|
|
4363
|
+
ranAsStaff: ranAsStaff ?? null,
|
|
4315
4364
|
status: statusFromAction(action),
|
|
4316
4365
|
phase: phaseFromAction(action, phase)
|
|
4317
4366
|
},
|
|
@@ -4348,6 +4397,9 @@ function renderStateComment(state) {
|
|
|
4348
4397
|
if (state.core.currentExecutable) {
|
|
4349
4398
|
lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
|
|
4350
4399
|
}
|
|
4400
|
+
if (state.core.ranAsStaff) {
|
|
4401
|
+
lines.push(`- **Ran as:** \`${state.core.ranAsStaff}\``);
|
|
4402
|
+
}
|
|
4351
4403
|
if (state.core.lastOutcome) {
|
|
4352
4404
|
lines.push(`- **Last action:** \`${state.core.lastOutcome.type}\``);
|
|
4353
4405
|
}
|
|
@@ -5325,7 +5377,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
5325
5377
|
const action = ctx.data.action;
|
|
5326
5378
|
let nextIssueState = issueState;
|
|
5327
5379
|
if (targetType === "pr" && action) {
|
|
5328
|
-
nextIssueState = reduce(issueState, profile.name, action, profile.phase);
|
|
5380
|
+
nextIssueState = reduce(issueState, profile.name, action, profile.phase, profile.staff);
|
|
5329
5381
|
if (state?.core.prUrl && !nextIssueState.core.prUrl) nextIssueState.core.prUrl = state.core.prUrl;
|
|
5330
5382
|
}
|
|
5331
5383
|
const prevHops = issueState.flow?.hops ?? flow.hops ?? 0;
|
|
@@ -6342,10 +6394,10 @@ import * as fs25 from "fs";
|
|
|
6342
6394
|
import * as path22 from "path";
|
|
6343
6395
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
6344
6396
|
var GoalStateError = class extends Error {
|
|
6345
|
-
constructor(
|
|
6346
|
-
super(`Invalid goal state at ${
|
|
6397
|
+
constructor(path42, message) {
|
|
6398
|
+
super(`Invalid goal state at ${path42}:
|
|
6347
6399
|
${message}`);
|
|
6348
|
-
this.path =
|
|
6400
|
+
this.path = path42;
|
|
6349
6401
|
this.name = "GoalStateError";
|
|
6350
6402
|
}
|
|
6351
6403
|
path;
|
|
@@ -10993,8 +11045,8 @@ var FlyClient = class {
|
|
|
10993
11045
|
get fetch() {
|
|
10994
11046
|
return this.opts.fetchImpl ?? fetch;
|
|
10995
11047
|
}
|
|
10996
|
-
async call(
|
|
10997
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
11048
|
+
async call(path42, init = {}) {
|
|
11049
|
+
const res = await this.fetch(`${FLY_API_BASE}${path42}`, {
|
|
10998
11050
|
method: init.method ?? "GET",
|
|
10999
11051
|
headers: {
|
|
11000
11052
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -11005,7 +11057,7 @@ var FlyClient = class {
|
|
|
11005
11057
|
if (res.status === 404 && init.allow404) return null;
|
|
11006
11058
|
if (!res.ok) {
|
|
11007
11059
|
const text = await res.text().catch(() => "");
|
|
11008
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
11060
|
+
throw new Error(`Fly API ${res.status} on ${path42}: ${text.slice(0, 200) || res.statusText}`);
|
|
11009
11061
|
}
|
|
11010
11062
|
if (res.status === 204) return null;
|
|
11011
11063
|
const raw = await res.text();
|
|
@@ -13455,7 +13507,7 @@ var saveTaskState = async (ctx, profile) => {
|
|
|
13455
13507
|
if (!target || !number || !state) return;
|
|
13456
13508
|
const executable = profile.name;
|
|
13457
13509
|
const action = ctx.data.action ?? synthesizeAction(ctx);
|
|
13458
|
-
const next = reduce(state, executable, action, profile.phase);
|
|
13510
|
+
const next = reduce(state, executable, action, profile.phase, profile.staff);
|
|
13459
13511
|
if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
|
|
13460
13512
|
if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
|
|
13461
13513
|
writeTaskState(target, number, next, ctx.cwd);
|
|
@@ -14501,9 +14553,41 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
14501
14553
|
...Object.keys(postflightScripts)
|
|
14502
14554
|
]);
|
|
14503
14555
|
|
|
14504
|
-
// src/
|
|
14556
|
+
// src/staff.ts
|
|
14505
14557
|
import * as fs41 from "fs";
|
|
14506
14558
|
import * as path37 from "path";
|
|
14559
|
+
var DEFAULT_STAFF_DIR = ".kody/staff";
|
|
14560
|
+
function stripFrontmatter(raw) {
|
|
14561
|
+
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
14562
|
+
return (match ? match[1] : raw).trim();
|
|
14563
|
+
}
|
|
14564
|
+
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
14565
|
+
const trimmed = slug.trim();
|
|
14566
|
+
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
14567
|
+
const staffPath = path37.join(cwd, staffDir, `${trimmed}.md`);
|
|
14568
|
+
if (!fs41.existsSync(staffPath)) {
|
|
14569
|
+
throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
|
|
14570
|
+
}
|
|
14571
|
+
const body = stripFrontmatter(fs41.readFileSync(staffPath, "utf-8"));
|
|
14572
|
+
if (!body) throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
|
|
14573
|
+
return body;
|
|
14574
|
+
}
|
|
14575
|
+
function framePersona(slug, persona) {
|
|
14576
|
+
return [
|
|
14577
|
+
`## Who you are \u2014 staff persona (authoritative identity)`,
|
|
14578
|
+
``,
|
|
14579
|
+
`You are operating as staff member \`${slug}\`. This persona defines *who* you are:`,
|
|
14580
|
+
`your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
|
|
14581
|
+
`persona's restrictions are stricter than the task, **the persona wins** \u2014 a task`,
|
|
14582
|
+
`can never grant you authority your persona withholds.`,
|
|
14583
|
+
``,
|
|
14584
|
+
persona
|
|
14585
|
+
].join("\n");
|
|
14586
|
+
}
|
|
14587
|
+
|
|
14588
|
+
// src/subagents.ts
|
|
14589
|
+
import * as fs42 from "fs";
|
|
14590
|
+
import * as path38 from "path";
|
|
14507
14591
|
function splitFrontmatter2(raw) {
|
|
14508
14592
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
14509
14593
|
if (!match) return { fm: {}, body: raw.trim() };
|
|
@@ -14516,10 +14600,10 @@ function splitFrontmatter2(raw) {
|
|
|
14516
14600
|
return { fm, body: (match[2] ?? "").trim() };
|
|
14517
14601
|
}
|
|
14518
14602
|
function resolveAgentFile(profileDir, name) {
|
|
14519
|
-
const local =
|
|
14520
|
-
if (
|
|
14521
|
-
const central =
|
|
14522
|
-
if (
|
|
14603
|
+
const local = path38.join(profileDir, "agents", `${name}.md`);
|
|
14604
|
+
if (fs42.existsSync(local)) return local;
|
|
14605
|
+
const central = path38.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
14606
|
+
if (fs42.existsSync(central)) return central;
|
|
14523
14607
|
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
14524
14608
|
}
|
|
14525
14609
|
function loadSubagents(profile) {
|
|
@@ -14527,7 +14611,7 @@ function loadSubagents(profile) {
|
|
|
14527
14611
|
if (!names || names.length === 0) return void 0;
|
|
14528
14612
|
const agents = {};
|
|
14529
14613
|
for (const name of names) {
|
|
14530
|
-
const { fm, body } = splitFrontmatter2(
|
|
14614
|
+
const { fm, body } = splitFrontmatter2(fs42.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
|
|
14531
14615
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
14532
14616
|
const def = {
|
|
14533
14617
|
description: fm.description ?? `Subagent ${name}`,
|
|
@@ -14715,9 +14799,10 @@ async function runExecutable(profileName, input) {
|
|
|
14715
14799
|
})
|
|
14716
14800
|
};
|
|
14717
14801
|
})() : null;
|
|
14718
|
-
const ndjsonDir =
|
|
14802
|
+
const ndjsonDir = path39.join(input.cwd, ".kody");
|
|
14803
|
+
const staffPersona = typeof profile.staff === "string" && profile.staff.length > 0 ? framePersona(profile.staff, loadStaffPersona(input.cwd, profile.staff)) : null;
|
|
14719
14804
|
const invokeAgent = async (prompt) => {
|
|
14720
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
14805
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path39.isAbsolute(p) ? p : path39.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
14721
14806
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
14722
14807
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
14723
14808
|
const agents = loadSubagents(profile);
|
|
@@ -14746,7 +14831,7 @@ async function runExecutable(profileName, input) {
|
|
|
14746
14831
|
maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
|
|
14747
14832
|
// DISCIPLINE leads so the stable, role-agnostic block sits at the front
|
|
14748
14833
|
// 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,
|
|
14834
|
+
systemPromptAppend: [DISCIPLINE, staffPersona, profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
|
|
14750
14835
|
cacheable: profile.claudeCode.cacheable,
|
|
14751
14836
|
enableVerifyTool: profile.claudeCode.enableVerifyTool,
|
|
14752
14837
|
enableSubmitTool: profile.claudeCode.enableSubmitTool,
|
|
@@ -14986,17 +15071,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
14986
15071
|
function resolveProfilePath(profileName) {
|
|
14987
15072
|
const found = resolveExecutable(profileName);
|
|
14988
15073
|
if (found) return found;
|
|
14989
|
-
const here =
|
|
15074
|
+
const here = path39.dirname(new URL(import.meta.url).pathname);
|
|
14990
15075
|
const candidates = [
|
|
14991
|
-
|
|
15076
|
+
path39.join(here, "executables", profileName, "profile.json"),
|
|
14992
15077
|
// same-dir sibling (dev)
|
|
14993
|
-
|
|
15078
|
+
path39.join(here, "..", "executables", profileName, "profile.json"),
|
|
14994
15079
|
// up one (prod: dist/bin → dist/executables)
|
|
14995
|
-
|
|
15080
|
+
path39.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
14996
15081
|
// fallback
|
|
14997
15082
|
];
|
|
14998
15083
|
for (const c of candidates) {
|
|
14999
|
-
if (
|
|
15084
|
+
if (fs43.existsSync(c)) return c;
|
|
15000
15085
|
}
|
|
15001
15086
|
return candidates[0];
|
|
15002
15087
|
}
|
|
@@ -15096,8 +15181,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
15096
15181
|
var SIGKILL_GRACE_MS = 5e3;
|
|
15097
15182
|
async function runShellEntry(entry, ctx, profile) {
|
|
15098
15183
|
const shellName = entry.shell;
|
|
15099
|
-
const shellPath =
|
|
15100
|
-
if (!
|
|
15184
|
+
const shellPath = path39.join(profile.dir, shellName);
|
|
15185
|
+
if (!fs43.existsSync(shellPath)) {
|
|
15101
15186
|
ctx.skipAgent = true;
|
|
15102
15187
|
ctx.output.exitCode = 99;
|
|
15103
15188
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -15332,9 +15417,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
15332
15417
|
return void 0;
|
|
15333
15418
|
}
|
|
15334
15419
|
function detectPackageManager2(cwd) {
|
|
15335
|
-
if (
|
|
15336
|
-
if (
|
|
15337
|
-
if (
|
|
15420
|
+
if (fs44.existsSync(path40.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
15421
|
+
if (fs44.existsSync(path40.join(cwd, "yarn.lock"))) return "yarn";
|
|
15422
|
+
if (fs44.existsSync(path40.join(cwd, "bun.lockb"))) return "bun";
|
|
15338
15423
|
return "npm";
|
|
15339
15424
|
}
|
|
15340
15425
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -15421,11 +15506,11 @@ function configureGitIdentity(cwd) {
|
|
|
15421
15506
|
}
|
|
15422
15507
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
15423
15508
|
if (!issueNumber) return;
|
|
15424
|
-
const logPath =
|
|
15509
|
+
const logPath = path40.join(cwd, ".kody", "last-run.jsonl");
|
|
15425
15510
|
let tail = "";
|
|
15426
15511
|
try {
|
|
15427
|
-
if (
|
|
15428
|
-
const content =
|
|
15512
|
+
if (fs44.existsSync(logPath)) {
|
|
15513
|
+
const content = fs44.readFileSync(logPath, "utf-8");
|
|
15429
15514
|
tail = content.slice(-3e3);
|
|
15430
15515
|
}
|
|
15431
15516
|
} catch {
|
|
@@ -15450,7 +15535,7 @@ async function runCi(argv) {
|
|
|
15450
15535
|
return 0;
|
|
15451
15536
|
}
|
|
15452
15537
|
const args = parseCiArgs(argv);
|
|
15453
|
-
const cwd = args.cwd ?
|
|
15538
|
+
const cwd = args.cwd ? path40.resolve(args.cwd) : process.cwd();
|
|
15454
15539
|
let earlyConfig;
|
|
15455
15540
|
try {
|
|
15456
15541
|
earlyConfig = loadConfig(cwd);
|
|
@@ -15460,9 +15545,9 @@ async function runCi(argv) {
|
|
|
15460
15545
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
15461
15546
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
15462
15547
|
let manualWorkflowDispatch = false;
|
|
15463
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
15548
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs44.existsSync(dispatchEventPath)) {
|
|
15464
15549
|
try {
|
|
15465
|
-
const evt = JSON.parse(
|
|
15550
|
+
const evt = JSON.parse(fs44.readFileSync(dispatchEventPath, "utf-8"));
|
|
15466
15551
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
15467
15552
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
15468
15553
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -15728,12 +15813,12 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
15728
15813
|
return result;
|
|
15729
15814
|
}
|
|
15730
15815
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
15731
|
-
const sessionFile =
|
|
15732
|
-
const eventsFile =
|
|
15816
|
+
const sessionFile = path41.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
15817
|
+
const eventsFile = path41.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
15733
15818
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
15734
|
-
const tasksDir =
|
|
15819
|
+
const tasksDir = path41.join(".kody", "tasks", safeSession);
|
|
15735
15820
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
15736
|
-
const paths = candidatePaths.filter((p) =>
|
|
15821
|
+
const paths = candidatePaths.filter((p) => fs45.existsSync(path41.join(cwd, p)));
|
|
15737
15822
|
if (paths.length === 0) return;
|
|
15738
15823
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
15739
15824
|
try {
|
|
@@ -15777,7 +15862,7 @@ async function runChat(argv) {
|
|
|
15777
15862
|
${CHAT_HELP}`);
|
|
15778
15863
|
return 64;
|
|
15779
15864
|
}
|
|
15780
|
-
const cwd = args.cwd ?
|
|
15865
|
+
const cwd = args.cwd ? path41.resolve(args.cwd) : process.cwd();
|
|
15781
15866
|
const sessionId = args.sessionId;
|
|
15782
15867
|
const unpackedSecrets = unpackAllSecrets();
|
|
15783
15868
|
if (unpackedSecrets > 0) {
|
|
@@ -15829,7 +15914,7 @@ ${CHAT_HELP}`);
|
|
|
15829
15914
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
15830
15915
|
const meta = readMeta(sessionFile);
|
|
15831
15916
|
process.stdout.write(
|
|
15832
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
15917
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs45.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
15833
15918
|
`
|
|
15834
15919
|
);
|
|
15835
15920
|
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.2",
|
|
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",
|