@kody-ade/kody-engine 0.3.13 → 0.3.15
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
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.3.
|
|
6
|
+
version: "0.3.15",
|
|
7
7
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -677,6 +677,9 @@ function autoDispatch(opts) {
|
|
|
677
677
|
}
|
|
678
678
|
return null;
|
|
679
679
|
}
|
|
680
|
+
if (eventName === "schedule") {
|
|
681
|
+
return { executable: "manager", cliArgs: {}, target: 0 };
|
|
682
|
+
}
|
|
680
683
|
if (eventName !== "issue_comment") return null;
|
|
681
684
|
const body = String(event.comment?.body ?? "").toLowerCase();
|
|
682
685
|
const targetNum = Number(event.issue?.number ?? 0);
|
|
@@ -2732,6 +2735,70 @@ function postPrReviewComment(prNumber, body, cwd) {
|
|
|
2732
2735
|
}
|
|
2733
2736
|
}
|
|
2734
2737
|
|
|
2738
|
+
// src/scripts/dispatchManagerTicks.ts
|
|
2739
|
+
var dispatchManagerTicks = async (ctx, _profile, args) => {
|
|
2740
|
+
ctx.skipAgent = true;
|
|
2741
|
+
const label = String(args?.label ?? "");
|
|
2742
|
+
const targetExecutable = String(args?.targetExecutable ?? "");
|
|
2743
|
+
if (!label) throw new Error("dispatchManagerTicks: `with.label` is required");
|
|
2744
|
+
if (!targetExecutable) throw new Error("dispatchManagerTicks: `with.targetExecutable` is required");
|
|
2745
|
+
const issueArg = String(args?.issueArg ?? "issue");
|
|
2746
|
+
const issues = listIssuesByLabel(label, ctx.cwd);
|
|
2747
|
+
ctx.data.managerIssueCount = issues.length;
|
|
2748
|
+
if (issues.length === 0) {
|
|
2749
|
+
process.stdout.write(`[manager] no open issues with label "${label}"
|
|
2750
|
+
`);
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
process.stdout.write(`[manager] ticking ${issues.length} issue(s) via ${targetExecutable}
|
|
2754
|
+
`);
|
|
2755
|
+
const results = [];
|
|
2756
|
+
for (const issue of issues) {
|
|
2757
|
+
process.stdout.write(`[manager] \u2192 tick #${issue.number}: ${issue.title}
|
|
2758
|
+
`);
|
|
2759
|
+
try {
|
|
2760
|
+
const out = await runExecutable(targetExecutable, {
|
|
2761
|
+
cliArgs: { [issueArg]: issue.number },
|
|
2762
|
+
cwd: ctx.cwd,
|
|
2763
|
+
config: ctx.config,
|
|
2764
|
+
verbose: ctx.verbose,
|
|
2765
|
+
quiet: ctx.quiet
|
|
2766
|
+
});
|
|
2767
|
+
results.push({ issue: issue.number, exitCode: out.exitCode, reason: out.reason });
|
|
2768
|
+
if (out.exitCode !== 0) {
|
|
2769
|
+
process.stderr.write(`[manager] tick #${issue.number} failed (exit ${out.exitCode}): ${out.reason ?? ""}
|
|
2770
|
+
`);
|
|
2771
|
+
}
|
|
2772
|
+
} catch (err) {
|
|
2773
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2774
|
+
process.stderr.write(`[manager] tick #${issue.number} crashed: ${msg}
|
|
2775
|
+
`);
|
|
2776
|
+
results.push({ issue: issue.number, exitCode: 99, reason: msg });
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
ctx.data.managerTickResults = results;
|
|
2780
|
+
ctx.output.exitCode = 0;
|
|
2781
|
+
};
|
|
2782
|
+
function listIssuesByLabel(label, cwd) {
|
|
2783
|
+
let raw = "";
|
|
2784
|
+
try {
|
|
2785
|
+
raw = gh2(
|
|
2786
|
+
["issue", "list", "--state", "open", "--label", label, "--limit", "100", "--json", "number,title"],
|
|
2787
|
+
{ cwd }
|
|
2788
|
+
);
|
|
2789
|
+
} catch {
|
|
2790
|
+
return [];
|
|
2791
|
+
}
|
|
2792
|
+
let list;
|
|
2793
|
+
try {
|
|
2794
|
+
list = JSON.parse(raw);
|
|
2795
|
+
} catch {
|
|
2796
|
+
return [];
|
|
2797
|
+
}
|
|
2798
|
+
if (!Array.isArray(list)) return [];
|
|
2799
|
+
return list.filter((x) => typeof x.number === "number" && typeof x.title === "string").map((x) => ({ number: x.number, title: x.title }));
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2735
2802
|
// src/pr.ts
|
|
2736
2803
|
var TITLE_MAX = 72;
|
|
2737
2804
|
function stripTitlePrefixes(raw) {
|
|
@@ -3744,6 +3811,112 @@ var loadIssueContext = async (ctx) => {
|
|
|
3744
3811
|
ctx.data.commentTargetNumber = issueNumber;
|
|
3745
3812
|
};
|
|
3746
3813
|
|
|
3814
|
+
// src/scripts/issueStateComment.ts
|
|
3815
|
+
function isStateEnvelope(x) {
|
|
3816
|
+
if (x === null || typeof x !== "object") return false;
|
|
3817
|
+
const o = x;
|
|
3818
|
+
return o.version === 1 && typeof o.rev === "number" && Number.isInteger(o.rev) && o.rev >= 0 && typeof o.cursor === "string" && typeof o.done === "boolean" && o.data !== null && typeof o.data === "object" && !Array.isArray(o.data);
|
|
3819
|
+
}
|
|
3820
|
+
function formatStateCommentBody(marker, state) {
|
|
3821
|
+
return `<!-- ${marker} -->
|
|
3822
|
+
|
|
3823
|
+
\`\`\`json
|
|
3824
|
+
${JSON.stringify(state, null, 2)}
|
|
3825
|
+
\`\`\`
|
|
3826
|
+
`;
|
|
3827
|
+
}
|
|
3828
|
+
function parseStateCommentBody(marker, body) {
|
|
3829
|
+
const markerLine = `<!-- ${marker} -->`;
|
|
3830
|
+
if (!body.trimStart().startsWith(markerLine)) return null;
|
|
3831
|
+
const fenceOpen = body.indexOf("```json");
|
|
3832
|
+
if (fenceOpen === -1) return null;
|
|
3833
|
+
const after = body.slice(fenceOpen + "```json".length);
|
|
3834
|
+
const fenceClose = after.indexOf("```");
|
|
3835
|
+
if (fenceClose === -1) return null;
|
|
3836
|
+
const jsonText = after.slice(0, fenceClose).trim();
|
|
3837
|
+
let parsed;
|
|
3838
|
+
try {
|
|
3839
|
+
parsed = JSON.parse(jsonText);
|
|
3840
|
+
} catch {
|
|
3841
|
+
return null;
|
|
3842
|
+
}
|
|
3843
|
+
return isStateEnvelope(parsed) ? parsed : null;
|
|
3844
|
+
}
|
|
3845
|
+
function listIssueComments(owner, repo, issueNumber, cwd) {
|
|
3846
|
+
const raw = gh2(["api", "--paginate", `repos/${owner}/${repo}/issues/${issueNumber}/comments`], { cwd });
|
|
3847
|
+
let parsed;
|
|
3848
|
+
try {
|
|
3849
|
+
parsed = JSON.parse(raw);
|
|
3850
|
+
} catch {
|
|
3851
|
+
return [];
|
|
3852
|
+
}
|
|
3853
|
+
if (!Array.isArray(parsed)) return [];
|
|
3854
|
+
return parsed.filter((c) => typeof c.id === "number" && typeof c.node_id === "string" && typeof c.body === "string").map((c) => ({ id: c.id, node_id: c.node_id, body: c.body }));
|
|
3855
|
+
}
|
|
3856
|
+
function findStateComment2(owner, repo, issueNumber, marker, cwd) {
|
|
3857
|
+
const comments = listIssueComments(owner, repo, issueNumber, cwd);
|
|
3858
|
+
for (const c of comments) {
|
|
3859
|
+
const state = parseStateCommentBody(marker, c.body);
|
|
3860
|
+
if (!state) continue;
|
|
3861
|
+
return { commentId: c.id, commentNodeId: c.node_id, state };
|
|
3862
|
+
}
|
|
3863
|
+
return null;
|
|
3864
|
+
}
|
|
3865
|
+
function createStateComment(owner, repo, issueNumber, marker, state, cwd) {
|
|
3866
|
+
const body = formatStateCommentBody(marker, state);
|
|
3867
|
+
const raw = gh2(
|
|
3868
|
+
["api", "--method", "POST", `repos/${owner}/${repo}/issues/${issueNumber}/comments`, "--input", "-"],
|
|
3869
|
+
{ cwd, input: JSON.stringify({ body }) }
|
|
3870
|
+
);
|
|
3871
|
+
const parsed = JSON.parse(raw);
|
|
3872
|
+
try {
|
|
3873
|
+
minimizeComment(parsed.node_id, cwd);
|
|
3874
|
+
} catch {
|
|
3875
|
+
}
|
|
3876
|
+
return { commentId: parsed.id, commentNodeId: parsed.node_id, state };
|
|
3877
|
+
}
|
|
3878
|
+
function updateStateComment(owner, repo, commentId, commentNodeId, marker, state, cwd) {
|
|
3879
|
+
const body = formatStateCommentBody(marker, state);
|
|
3880
|
+
gh2(
|
|
3881
|
+
["api", "--method", "PATCH", `repos/${owner}/${repo}/issues/comments/${commentId}`, "--input", "-"],
|
|
3882
|
+
{ cwd, input: JSON.stringify({ body }) }
|
|
3883
|
+
);
|
|
3884
|
+
try {
|
|
3885
|
+
minimizeComment(commentNodeId, cwd);
|
|
3886
|
+
} catch {
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
function minimizeComment(nodeId, cwd) {
|
|
3890
|
+
const mutation = "mutation($id: ID!) { minimizeComment(input: { classifier: OUTDATED, subjectId: $id }) { minimizedComment { isMinimized } } }";
|
|
3891
|
+
gh2(["api", "graphql", "-f", `query=${mutation}`, "-f", `id=${nodeId}`], { cwd });
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
// src/scripts/loadIssueStateComment.ts
|
|
3895
|
+
var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
3896
|
+
const marker = String(args?.marker ?? "");
|
|
3897
|
+
if (!marker) {
|
|
3898
|
+
throw new Error("loadIssueStateComment: `with.marker` is required");
|
|
3899
|
+
}
|
|
3900
|
+
const issueArg = String(args?.issueArg ?? "issue");
|
|
3901
|
+
const issueNumber = Number(ctx.args[issueArg]);
|
|
3902
|
+
if (!Number.isFinite(issueNumber) || issueNumber <= 0) {
|
|
3903
|
+
throw new Error(`loadIssueStateComment: ctx.args.${issueArg} must be a positive integer`);
|
|
3904
|
+
}
|
|
3905
|
+
const owner = ctx.config.github.owner;
|
|
3906
|
+
const repo = ctx.config.github.repo;
|
|
3907
|
+
if (!owner || !repo) {
|
|
3908
|
+
throw new Error("loadIssueStateComment: ctx.config.github.owner/repo must be set");
|
|
3909
|
+
}
|
|
3910
|
+
const issue = getIssue(issueNumber, ctx.cwd);
|
|
3911
|
+
const loaded = findStateComment2(owner, repo, issueNumber, marker, ctx.cwd);
|
|
3912
|
+
ctx.data.stateMarker = marker;
|
|
3913
|
+
ctx.data.issueIntent = issue.body;
|
|
3914
|
+
ctx.data.issueTitle = issue.title;
|
|
3915
|
+
ctx.data.issueNumber = String(issueNumber);
|
|
3916
|
+
ctx.data.issueStateComment = loaded;
|
|
3917
|
+
ctx.data.issueStateJson = loaded ? JSON.stringify(loaded.state, null, 2) : "null";
|
|
3918
|
+
};
|
|
3919
|
+
|
|
3747
3920
|
// src/scripts/loadPriorArt.ts
|
|
3748
3921
|
var PER_PR_DIFF_MAX_BYTES = 8e3;
|
|
3749
3922
|
var TOTAL_MAX_BYTES = 3e4;
|
|
@@ -3911,6 +4084,53 @@ function makeAction(type, payload) {
|
|
|
3911
4084
|
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3912
4085
|
}
|
|
3913
4086
|
|
|
4087
|
+
// src/scripts/parseIssueStateFromAgentResult.ts
|
|
4088
|
+
function isPartialEnvelope(x) {
|
|
4089
|
+
if (x === null || typeof x !== "object") return false;
|
|
4090
|
+
const o = x;
|
|
4091
|
+
return typeof o.cursor === "string" && o.cursor.length > 0 && typeof o.done === "boolean" && o.data !== null && typeof o.data === "object" && !Array.isArray(o.data);
|
|
4092
|
+
}
|
|
4093
|
+
var parseIssueStateFromAgentResult = async (ctx, _profile, agentResult, args) => {
|
|
4094
|
+
const fenceLabel = String(args?.fenceLabel ?? "");
|
|
4095
|
+
if (!fenceLabel) {
|
|
4096
|
+
throw new Error("parseIssueStateFromAgentResult: `with.fenceLabel` is required");
|
|
4097
|
+
}
|
|
4098
|
+
if (!agentResult) {
|
|
4099
|
+
ctx.data.nextStateParseError = "agent did not run";
|
|
4100
|
+
return;
|
|
4101
|
+
}
|
|
4102
|
+
const fenceRegex = new RegExp("```" + escapeRegex(fenceLabel) + "\\s*\\n([\\s\\S]*?)\\n```", "m");
|
|
4103
|
+
const match = fenceRegex.exec(agentResult.finalText);
|
|
4104
|
+
if (!match) {
|
|
4105
|
+
ctx.data.nextStateParseError = `agent did not emit a \`${fenceLabel}\` fenced block`;
|
|
4106
|
+
return;
|
|
4107
|
+
}
|
|
4108
|
+
let parsed;
|
|
4109
|
+
try {
|
|
4110
|
+
parsed = JSON.parse(match[1].trim());
|
|
4111
|
+
} catch (err) {
|
|
4112
|
+
ctx.data.nextStateParseError = `state JSON parse error: ${err instanceof Error ? err.message : String(err)}`;
|
|
4113
|
+
return;
|
|
4114
|
+
}
|
|
4115
|
+
if (!isPartialEnvelope(parsed)) {
|
|
4116
|
+
ctx.data.nextStateParseError = "state must be an object with string `cursor`, object `data`, and boolean `done`";
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
const loaded = ctx.data.issueStateComment;
|
|
4120
|
+
const prevRev = loaded?.state.rev ?? 0;
|
|
4121
|
+
const next = {
|
|
4122
|
+
version: 1,
|
|
4123
|
+
rev: prevRev + 1,
|
|
4124
|
+
cursor: parsed.cursor,
|
|
4125
|
+
data: parsed.data,
|
|
4126
|
+
done: parsed.done
|
|
4127
|
+
};
|
|
4128
|
+
ctx.data.nextIssueState = next;
|
|
4129
|
+
};
|
|
4130
|
+
function escapeRegex(s) {
|
|
4131
|
+
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
4132
|
+
}
|
|
4133
|
+
|
|
3914
4134
|
// src/scripts/persistArtifacts.ts
|
|
3915
4135
|
var persistArtifacts = async (ctx, profile) => {
|
|
3916
4136
|
if (profile.outputArtifacts.length === 0) return;
|
|
@@ -5232,6 +5452,42 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
5232
5452
|
ctx.data.staleCount = stale.length;
|
|
5233
5453
|
};
|
|
5234
5454
|
|
|
5455
|
+
// src/scripts/writeIssueStateComment.ts
|
|
5456
|
+
var writeIssueStateComment = async (ctx, _profile, _agentResult, args) => {
|
|
5457
|
+
const marker = String(args?.marker ?? "");
|
|
5458
|
+
if (!marker) {
|
|
5459
|
+
throw new Error("writeIssueStateComment: `with.marker` is required");
|
|
5460
|
+
}
|
|
5461
|
+
const issueArg = String(args?.issueArg ?? "issue");
|
|
5462
|
+
const issueNumber = Number(ctx.args[issueArg]);
|
|
5463
|
+
if (!Number.isFinite(issueNumber) || issueNumber <= 0) {
|
|
5464
|
+
throw new Error(`writeIssueStateComment: ctx.args.${issueArg} must be a positive integer`);
|
|
5465
|
+
}
|
|
5466
|
+
const parseError = ctx.data.nextStateParseError;
|
|
5467
|
+
if (parseError) {
|
|
5468
|
+
process.stderr.write(`[kody] state write skipped: ${parseError}
|
|
5469
|
+
`);
|
|
5470
|
+
if (ctx.output.exitCode === 0) ctx.output.exitCode = 1;
|
|
5471
|
+
if (!ctx.output.reason) ctx.output.reason = `next-state parse failed: ${parseError}`;
|
|
5472
|
+
return;
|
|
5473
|
+
}
|
|
5474
|
+
const next = ctx.data.nextIssueState;
|
|
5475
|
+
if (!next) {
|
|
5476
|
+
return;
|
|
5477
|
+
}
|
|
5478
|
+
const owner = ctx.config.github.owner;
|
|
5479
|
+
const repo = ctx.config.github.repo;
|
|
5480
|
+
if (!owner || !repo) {
|
|
5481
|
+
throw new Error("writeIssueStateComment: ctx.config.github.owner/repo must be set");
|
|
5482
|
+
}
|
|
5483
|
+
const loaded = ctx.data.issueStateComment;
|
|
5484
|
+
if (loaded) {
|
|
5485
|
+
updateStateComment(owner, repo, loaded.commentId, loaded.commentNodeId, marker, next, ctx.cwd);
|
|
5486
|
+
} else {
|
|
5487
|
+
createStateComment(owner, repo, issueNumber, marker, next, ctx.cwd);
|
|
5488
|
+
}
|
|
5489
|
+
};
|
|
5490
|
+
|
|
5235
5491
|
// src/scripts/writeRunSummary.ts
|
|
5236
5492
|
import * as fs20 from "fs";
|
|
5237
5493
|
var writeRunSummary = async (ctx, profile) => {
|
|
@@ -5274,6 +5530,7 @@ var preflightScripts = {
|
|
|
5274
5530
|
watchStalePrsFlow,
|
|
5275
5531
|
loadTaskState,
|
|
5276
5532
|
loadIssueContext,
|
|
5533
|
+
loadIssueStateComment,
|
|
5277
5534
|
loadConventions,
|
|
5278
5535
|
loadCoverageRules,
|
|
5279
5536
|
loadPriorArt,
|
|
@@ -5286,10 +5543,13 @@ var preflightScripts = {
|
|
|
5286
5543
|
setLifecycleLabel,
|
|
5287
5544
|
skipAgent,
|
|
5288
5545
|
classifyByLabel,
|
|
5289
|
-
diagMcp
|
|
5546
|
+
diagMcp,
|
|
5547
|
+
dispatchManagerTicks
|
|
5290
5548
|
};
|
|
5291
5549
|
var postflightScripts = {
|
|
5292
5550
|
parseAgentResult: parseAgentResult2,
|
|
5551
|
+
parseIssueStateFromAgentResult,
|
|
5552
|
+
writeIssueStateComment,
|
|
5293
5553
|
requireFeedbackActions,
|
|
5294
5554
|
requirePlanDeviations,
|
|
5295
5555
|
verify,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "manager",
|
|
3
|
+
"role": "watch",
|
|
4
|
+
"describe": "Scheduled: for every open issue labeled kody:manager, invoke manager-tick once. No agent on the scheduler itself.",
|
|
5
|
+
"kind": "scheduled",
|
|
6
|
+
"schedule": "*/5 * * * *",
|
|
7
|
+
"inputs": [],
|
|
8
|
+
"claudeCode": {
|
|
9
|
+
"model": "inherit",
|
|
10
|
+
"permissionMode": "default",
|
|
11
|
+
"maxTurns": null,
|
|
12
|
+
"maxThinkingTokens": null,
|
|
13
|
+
"systemPromptAppend": null,
|
|
14
|
+
"tools": [],
|
|
15
|
+
"hooks": [],
|
|
16
|
+
"skills": [],
|
|
17
|
+
"commands": [],
|
|
18
|
+
"subagents": [],
|
|
19
|
+
"plugins": [],
|
|
20
|
+
"mcpServers": []
|
|
21
|
+
},
|
|
22
|
+
"cliTools": [
|
|
23
|
+
{
|
|
24
|
+
"name": "gh",
|
|
25
|
+
"install": {
|
|
26
|
+
"required": true,
|
|
27
|
+
"checkCommand": "command -v gh"
|
|
28
|
+
},
|
|
29
|
+
"verify": "gh auth status",
|
|
30
|
+
"usage": "",
|
|
31
|
+
"allowedUses": ["issue"]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"inputArtifacts": [],
|
|
35
|
+
"outputArtifacts": [],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"preflight": [
|
|
38
|
+
{
|
|
39
|
+
"script": "dispatchManagerTicks",
|
|
40
|
+
"with": {
|
|
41
|
+
"label": "kody:manager",
|
|
42
|
+
"color": "1d76db",
|
|
43
|
+
"description": "kody: issue is a manager mission — scheduler will tick it on every cron wake",
|
|
44
|
+
"targetExecutable": "manager-tick",
|
|
45
|
+
"issueArg": "issue"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"postflight": []
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "manager-tick",
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"describe": "One classifier tick for one manager issue: read intent + state, decide and execute via gh, emit next state.",
|
|
5
|
+
"kind": "oneshot",
|
|
6
|
+
"inputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "issue",
|
|
9
|
+
"flag": "--issue",
|
|
10
|
+
"type": "int",
|
|
11
|
+
"required": true,
|
|
12
|
+
"describe": "GitHub issue number that owns this manager mission."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"claudeCode": {
|
|
16
|
+
"model": "inherit",
|
|
17
|
+
"permissionMode": "default",
|
|
18
|
+
"maxTurns": 20,
|
|
19
|
+
"maxThinkingTokens": null,
|
|
20
|
+
"systemPromptAppend": null,
|
|
21
|
+
"tools": ["Bash", "Read"],
|
|
22
|
+
"hooks": [],
|
|
23
|
+
"skills": [],
|
|
24
|
+
"commands": [],
|
|
25
|
+
"subagents": [],
|
|
26
|
+
"plugins": [],
|
|
27
|
+
"mcpServers": []
|
|
28
|
+
},
|
|
29
|
+
"cliTools": [
|
|
30
|
+
{
|
|
31
|
+
"name": "gh",
|
|
32
|
+
"install": {
|
|
33
|
+
"required": true,
|
|
34
|
+
"checkCommand": "command -v gh"
|
|
35
|
+
},
|
|
36
|
+
"verify": "gh auth status",
|
|
37
|
+
"usage": "Use `gh` for all GitHub actions: `gh workflow run <workflow> -f <key>=<val>` to spawn a child run, `gh run view <id> --json status,conclusion` to check status, `gh issue comment <n> --body \"...\"` to post narration. NEVER edit files in the working tree.",
|
|
38
|
+
"allowedUses": ["workflow", "run", "issue", "pr", "api"]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"inputArtifacts": [],
|
|
42
|
+
"outputArtifacts": [],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"preflight": [
|
|
45
|
+
{
|
|
46
|
+
"script": "loadIssueStateComment",
|
|
47
|
+
"with": {
|
|
48
|
+
"marker": "kody-manager-state",
|
|
49
|
+
"issueArg": "issue"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"script": "composePrompt"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"postflight": [
|
|
57
|
+
{
|
|
58
|
+
"script": "parseIssueStateFromAgentResult",
|
|
59
|
+
"with": {
|
|
60
|
+
"fenceLabel": "kody-manager-next-state"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"script": "writeIssueStateComment",
|
|
65
|
+
"with": {
|
|
66
|
+
"marker": "kody-manager-state",
|
|
67
|
+
"issueArg": "issue"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
You are **kody manager-tick**, the coordinator for one GitHub-issue-scoped mission. You do **not** touch code, do **not** commit, and do **not** edit files. You coordinate other kody executables by dispatching their workflows, observing their runs, and writing back state.
|
|
2
|
+
|
|
3
|
+
## The mission
|
|
4
|
+
|
|
5
|
+
Issue **#{{issueNumber}}** — *{{issueTitle}}* — owns this mission. The issue description below is authoritative: it states what success looks like, constraints, deadlines, budget, or anything else a human operator has written. Re-read it every tick — the human may have edited it to steer you.
|
|
6
|
+
|
|
7
|
+
### Mission (issue description)
|
|
8
|
+
|
|
9
|
+
{{issueIntent}}
|
|
10
|
+
|
|
11
|
+
## Current state
|
|
12
|
+
|
|
13
|
+
This is the state you wrote at the end of the previous tick (or `null` if this is the first tick):
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{{issueStateJson}}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`cursor` is *your* enum — pick whatever labels map cleanly to your mission's phases (e.g. `seed`, `spawn-release`, `waiting-release`, `merge-to-dev`, `finalize`, `done`). `data` is where you stash anything you need on the next tick (run IDs, SHAs, child issue numbers, budget counters). `done: true` tells the scheduler to stop calling you.
|
|
20
|
+
|
|
21
|
+
## What to do on this tick
|
|
22
|
+
|
|
23
|
+
1. **Re-read the mission.** If the human has edited the description in a way that changes what "on track" means, adapt.
|
|
24
|
+
2. **Decide the single next step** based on (cursor, data, mission).
|
|
25
|
+
- If `cursor` is `null`/first-run, initialize: plan the pipeline, pick an initial cursor, record any baseline info in `data`.
|
|
26
|
+
- If you're waiting on a child run, check its status via `gh run view <id> --json status,conclusion`. If still running, just update cursor/data minimally and exit — the next cron wake will check again. If succeeded, advance. If failed, record the failure and either spawn a remediation child or mark `done: true` with an error.
|
|
27
|
+
- If it's time to spawn a child executable, use `gh workflow run kody.yml -f issue_number=<N>` (or the appropriate workflow + inputs for the consumer repo). Capture the dispatched run's ID via `gh run list --workflow=kody.yml --limit 1 --json databaseId --jq '.[0].databaseId'` and stash it in `data`.
|
|
28
|
+
- If the mission is complete, set `done: true` and a terminal cursor like `done`.
|
|
29
|
+
3. **Optionally post a human-readable narration comment** on the issue summarizing what you just did (spawned run #12345, waiting on CI, etc.). Keep it short. Use `gh issue comment {{issueNumber}} --body "..."`.
|
|
30
|
+
4. **Emit the new state** at the very end of your response using the fenced block below. Do not include `version` or `rev` — the postflight script manages those.
|
|
31
|
+
|
|
32
|
+
## Output contract (MANDATORY, exactly once, at the end)
|
|
33
|
+
|
|
34
|
+
End your response with a single fenced block using the `kody-manager-next-state` language tag:
|
|
35
|
+
|
|
36
|
+
````
|
|
37
|
+
```kody-manager-next-state
|
|
38
|
+
{
|
|
39
|
+
"cursor": "<your-next-cursor>",
|
|
40
|
+
"data": { ... },
|
|
41
|
+
"done": <true|false>
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
````
|
|
45
|
+
|
|
46
|
+
If you fail to emit this block, or the JSON is invalid, the tick fails and the state comment is NOT updated. On the next wake you'll see the same prior state and can retry.
|
|
47
|
+
|
|
48
|
+
## Rules
|
|
49
|
+
|
|
50
|
+
- Never edit, create, or delete files in the working tree.
|
|
51
|
+
- Never commit or push.
|
|
52
|
+
- Only shell calls allowed: `gh` (for workflows, runs, issues, PRs, API). Everything must go through it.
|
|
53
|
+
- Keep each tick focused: do one thing per wake. The cron will call you again.
|
|
54
|
+
- If the state says you're waiting on something, just check and re-emit — don't spawn a duplicate.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
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",
|