@kody-ade/kody-engine 0.2.54 → 0.2.56
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/kody2.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.2.
|
|
6
|
+
version: "0.2.56",
|
|
7
7
|
description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -50,7 +50,7 @@ var package_default = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
// src/chat-cli.ts
|
|
53
|
-
import { execFileSync as
|
|
53
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
54
54
|
import * as fs22 from "fs";
|
|
55
55
|
import * as path19 from "path";
|
|
56
56
|
|
|
@@ -182,9 +182,24 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
182
182
|
issueContext: parseIssueContext(raw.issueContext),
|
|
183
183
|
testRequirements: parseTestRequirements(raw.testRequirements),
|
|
184
184
|
defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable : void 0,
|
|
185
|
+
classify: parseClassifyConfig(raw.classify),
|
|
185
186
|
release: parseReleaseConfig(raw.release)
|
|
186
187
|
};
|
|
187
188
|
}
|
|
189
|
+
function parseClassifyConfig(raw) {
|
|
190
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
191
|
+
const r = raw;
|
|
192
|
+
const out = {};
|
|
193
|
+
if (r.labelMap && typeof r.labelMap === "object") {
|
|
194
|
+
const entries = Object.entries(r.labelMap).filter(
|
|
195
|
+
([, v]) => typeof v === "string" && v.length > 0
|
|
196
|
+
);
|
|
197
|
+
if (entries.length > 0) {
|
|
198
|
+
out.labelMap = Object.fromEntries(entries.map(([k, v]) => [k.toLowerCase(), String(v)]));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
202
|
+
}
|
|
188
203
|
function parseReleaseConfig(raw) {
|
|
189
204
|
if (!raw || typeof raw !== "object") return void 0;
|
|
190
205
|
const r = raw;
|
|
@@ -528,7 +543,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
528
543
|
}
|
|
529
544
|
|
|
530
545
|
// src/kody2-cli.ts
|
|
531
|
-
import { execFileSync as
|
|
546
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
532
547
|
import * as fs21 from "fs";
|
|
533
548
|
import * as path18 from "path";
|
|
534
549
|
|
|
@@ -1499,6 +1514,41 @@ ${formatMissesForFeedback(misses)}`;
|
|
|
1499
1514
|
ctx.data.coverageMisses = finalMisses;
|
|
1500
1515
|
};
|
|
1501
1516
|
|
|
1517
|
+
// src/scripts/classifyByLabel.ts
|
|
1518
|
+
var VALID_CLASSES = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
1519
|
+
var classifyByLabel = async (ctx) => {
|
|
1520
|
+
const issue = ctx.data.issue;
|
|
1521
|
+
const labels = issue?.labels;
|
|
1522
|
+
if (!labels || labels.length === 0) return;
|
|
1523
|
+
const cfgMap = ctx.config.classify?.labelMap;
|
|
1524
|
+
const map = cfgMap ?? defaultLabelMap();
|
|
1525
|
+
for (const label of labels) {
|
|
1526
|
+
const candidate = map[label.toLowerCase()];
|
|
1527
|
+
if (candidate && VALID_CLASSES.has(candidate)) {
|
|
1528
|
+
ctx.data.classification = candidate;
|
|
1529
|
+
ctx.data.classificationSource = "label";
|
|
1530
|
+
ctx.data.classificationReason = `label \`${label}\` \u2192 ${candidate}`;
|
|
1531
|
+
ctx.skipAgent = true;
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
function defaultLabelMap() {
|
|
1537
|
+
return {
|
|
1538
|
+
bug: "bug",
|
|
1539
|
+
enhancement: "bug",
|
|
1540
|
+
refactor: "feature",
|
|
1541
|
+
feature: "feature",
|
|
1542
|
+
performance: "feature",
|
|
1543
|
+
rfc: "spec",
|
|
1544
|
+
design: "spec",
|
|
1545
|
+
spec: "spec",
|
|
1546
|
+
docs: "chore",
|
|
1547
|
+
chore: "chore",
|
|
1548
|
+
dependencies: "chore"
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1502
1552
|
// src/scripts/commitAndPush.ts
|
|
1503
1553
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
1504
1554
|
|
|
@@ -2366,7 +2416,7 @@ function gh2(args, options) {
|
|
|
2366
2416
|
}).trim();
|
|
2367
2417
|
}
|
|
2368
2418
|
function getIssue(issueNumber, cwd) {
|
|
2369
|
-
const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments"], { cwd });
|
|
2419
|
+
const output = gh2(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
|
|
2370
2420
|
const parsed = JSON.parse(output);
|
|
2371
2421
|
if (typeof parsed?.title !== "string") {
|
|
2372
2422
|
throw new Error(`Issue #${issueNumber}: unexpected response shape`);
|
|
@@ -2379,7 +2429,8 @@ function getIssue(issueNumber, cwd) {
|
|
|
2379
2429
|
body: c.body ?? "",
|
|
2380
2430
|
author: c.author?.login ?? "unknown",
|
|
2381
2431
|
createdAt: c.createdAt ?? ""
|
|
2382
|
-
}))
|
|
2432
|
+
})),
|
|
2433
|
+
labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
|
|
2383
2434
|
};
|
|
2384
2435
|
}
|
|
2385
2436
|
function stripKody2Mentions(body) {
|
|
@@ -3534,7 +3585,9 @@ var loadIssueContext = async (ctx) => {
|
|
|
3534
3585
|
const kept = sorted.slice(0, limit);
|
|
3535
3586
|
const commentsFormatted = kept.length === 0 ? "(no comments yet)" : kept.map((c) => `- **${c.author}** (${c.createdAt}):
|
|
3536
3587
|
${truncate2(c.body, maxBytes).replace(/\n/g, "\n ")}`).join("\n\n");
|
|
3537
|
-
|
|
3588
|
+
const labels = issue.labels ?? [];
|
|
3589
|
+
const labelsFormatted = labels.length === 0 ? "(no labels)" : labels.map((l) => `\`${l}\``).join(", ");
|
|
3590
|
+
ctx.data.issue = { ...issue, commentsFormatted, labelsFormatted };
|
|
3538
3591
|
ctx.data.commentTargetType = "issue";
|
|
3539
3592
|
ctx.data.commentTargetNumber = issueNumber;
|
|
3540
3593
|
};
|
|
@@ -3659,6 +3712,86 @@ var persistFlowState = async (ctx) => {
|
|
|
3659
3712
|
}
|
|
3660
3713
|
};
|
|
3661
3714
|
|
|
3715
|
+
// src/scripts/postClassification.ts
|
|
3716
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
3717
|
+
var API_TIMEOUT_MS6 = 3e4;
|
|
3718
|
+
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
3719
|
+
var postClassification = async (ctx) => {
|
|
3720
|
+
const issueNumber = ctx.args.issue;
|
|
3721
|
+
if (!issueNumber) return;
|
|
3722
|
+
const presetClassification = ctx.data.classification;
|
|
3723
|
+
let classification = null;
|
|
3724
|
+
let reason = null;
|
|
3725
|
+
if (presetClassification && VALID_CLASSES2.has(presetClassification)) {
|
|
3726
|
+
classification = presetClassification;
|
|
3727
|
+
reason = ctx.data.classificationReason ?? "label-based match";
|
|
3728
|
+
} else {
|
|
3729
|
+
const parsed = parseClassification(ctx.data.prSummary ?? "");
|
|
3730
|
+
classification = parsed?.classification ?? null;
|
|
3731
|
+
reason = parsed?.reason ?? null;
|
|
3732
|
+
}
|
|
3733
|
+
if (!classification) {
|
|
3734
|
+
ctx.data.action = failedAction("classification missing or invalid");
|
|
3735
|
+
tryAuditComment(issueNumber, "\u26A0\uFE0F kody2 classifier could not decide \u2014 please re-run with an explicit `@kody2 <type>`.", ctx.cwd);
|
|
3736
|
+
ctx.output.exitCode = 1;
|
|
3737
|
+
ctx.output.reason = "classify: no decision";
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
tryAuditComment(
|
|
3741
|
+
issueNumber,
|
|
3742
|
+
`\u{1F50E} kody2 classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`,
|
|
3743
|
+
ctx.cwd
|
|
3744
|
+
);
|
|
3745
|
+
try {
|
|
3746
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", `@kody2 ${classification}`], {
|
|
3747
|
+
cwd: ctx.cwd,
|
|
3748
|
+
timeout: API_TIMEOUT_MS6,
|
|
3749
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3750
|
+
});
|
|
3751
|
+
} catch (err) {
|
|
3752
|
+
process.stderr.write(
|
|
3753
|
+
`[kody2 postClassification] failed to dispatch @kody2 ${classification}: ${err instanceof Error ? err.message : String(err)}
|
|
3754
|
+
`
|
|
3755
|
+
);
|
|
3756
|
+
ctx.data.action = failedAction("dispatch post failed");
|
|
3757
|
+
ctx.output.exitCode = 1;
|
|
3758
|
+
ctx.output.reason = "classify: dispatch failed";
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
ctx.data.action = makeAction2(`CLASSIFIED_AS_${classification.toUpperCase()}`, {
|
|
3762
|
+
classification,
|
|
3763
|
+
reason: reason ?? "",
|
|
3764
|
+
source: ctx.data.classificationSource ?? "agent"
|
|
3765
|
+
});
|
|
3766
|
+
ctx.data.classification = classification;
|
|
3767
|
+
ctx.data.classificationReason = reason ?? "";
|
|
3768
|
+
};
|
|
3769
|
+
function parseClassification(prSummary) {
|
|
3770
|
+
if (!prSummary) return null;
|
|
3771
|
+
const classMatch = prSummary.match(/classification:\s*(feature|bug|spec|chore)\b/i);
|
|
3772
|
+
if (!classMatch) return null;
|
|
3773
|
+
const classification = classMatch[1].toLowerCase();
|
|
3774
|
+
const reasonMatch = prSummary.match(/reason:\s*(.+)$/im);
|
|
3775
|
+
const reason = reasonMatch ? reasonMatch[1].trim() : "";
|
|
3776
|
+
return { classification, reason };
|
|
3777
|
+
}
|
|
3778
|
+
function tryAuditComment(issueNumber, body, cwd) {
|
|
3779
|
+
try {
|
|
3780
|
+
execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
3781
|
+
cwd,
|
|
3782
|
+
timeout: API_TIMEOUT_MS6,
|
|
3783
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3784
|
+
});
|
|
3785
|
+
} catch {
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
function makeAction2(type, payload) {
|
|
3789
|
+
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3790
|
+
}
|
|
3791
|
+
function failedAction(reason) {
|
|
3792
|
+
return { type: "CLASSIFY_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3662
3795
|
// src/scripts/postIssueComment.ts
|
|
3663
3796
|
var postIssueComment2 = async (ctx) => {
|
|
3664
3797
|
if (ctx.skipAgent && ctx.output.exitCode !== void 0) return;
|
|
@@ -3773,7 +3906,7 @@ function reviewAction(verdict, payload) {
|
|
|
3773
3906
|
const type = verdict === "PASS" ? "REVIEW_PASS" : verdict === "CONCERNS" ? "REVIEW_CONCERNS" : verdict === "FAIL" ? "REVIEW_FAIL" : "REVIEW_COMPLETED";
|
|
3774
3907
|
return { type, payload: { verdict, ...payload }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3775
3908
|
}
|
|
3776
|
-
function
|
|
3909
|
+
function failedAction2(reason) {
|
|
3777
3910
|
return { type: "REVIEW_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3778
3911
|
}
|
|
3779
3912
|
var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
@@ -3781,7 +3914,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
|
3781
3914
|
if (!prNumber) {
|
|
3782
3915
|
ctx.output.exitCode = 99;
|
|
3783
3916
|
ctx.output.reason = "review postflight: no PR number in context";
|
|
3784
|
-
ctx.data.action =
|
|
3917
|
+
ctx.data.action = failedAction2(ctx.output.reason);
|
|
3785
3918
|
return;
|
|
3786
3919
|
}
|
|
3787
3920
|
if (!agentResult || agentResult.outcome !== "completed") {
|
|
@@ -3792,7 +3925,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
|
3792
3925
|
}
|
|
3793
3926
|
ctx.output.exitCode = 1;
|
|
3794
3927
|
ctx.output.reason = reason;
|
|
3795
|
-
ctx.data.action =
|
|
3928
|
+
ctx.data.action = failedAction2(reason);
|
|
3796
3929
|
return;
|
|
3797
3930
|
}
|
|
3798
3931
|
const reviewBody = agentResult.finalText.trim();
|
|
@@ -3803,7 +3936,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
|
3803
3936
|
}
|
|
3804
3937
|
ctx.output.exitCode = 1;
|
|
3805
3938
|
ctx.output.reason = "empty review body";
|
|
3806
|
-
ctx.data.action =
|
|
3939
|
+
ctx.data.action = failedAction2("empty review body");
|
|
3807
3940
|
return;
|
|
3808
3941
|
}
|
|
3809
3942
|
try {
|
|
@@ -3812,7 +3945,7 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
|
3812
3945
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3813
3946
|
ctx.output.exitCode = 4;
|
|
3814
3947
|
ctx.output.reason = `failed to post review comment: ${msg}`;
|
|
3815
|
-
ctx.data.action =
|
|
3948
|
+
ctx.data.action = failedAction2(ctx.output.reason);
|
|
3816
3949
|
return;
|
|
3817
3950
|
}
|
|
3818
3951
|
const verdict = detectVerdict(reviewBody);
|
|
@@ -3828,7 +3961,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
3828
3961
|
};
|
|
3829
3962
|
|
|
3830
3963
|
// src/scripts/releaseFlow.ts
|
|
3831
|
-
import { execFileSync as
|
|
3964
|
+
import { execFileSync as execFileSync15, spawnSync } from "child_process";
|
|
3832
3965
|
import * as fs18 from "fs";
|
|
3833
3966
|
import * as path16 from "path";
|
|
3834
3967
|
function bumpVersion(current, bump) {
|
|
@@ -3858,7 +3991,7 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
3858
3991
|
const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
|
|
3859
3992
|
let log = "";
|
|
3860
3993
|
try {
|
|
3861
|
-
log =
|
|
3994
|
+
log = execFileSync15("git", ["log", range, "--pretty=format:%s||%h", "--no-merges"], {
|
|
3862
3995
|
cwd,
|
|
3863
3996
|
encoding: "utf-8",
|
|
3864
3997
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3915,7 +4048,7 @@ ${entry}${prior.slice(idx + 1)}`);
|
|
|
3915
4048
|
}
|
|
3916
4049
|
}
|
|
3917
4050
|
function git3(args, cwd, timeout = 6e4) {
|
|
3918
|
-
return
|
|
4051
|
+
return execFileSync15("git", args, {
|
|
3919
4052
|
encoding: "utf-8",
|
|
3920
4053
|
timeout,
|
|
3921
4054
|
cwd,
|
|
@@ -4142,12 +4275,12 @@ function fail(ctx, profile, reason) {
|
|
|
4142
4275
|
ctx.data.agentDone = false;
|
|
4143
4276
|
ctx.data.agentFailureReason = reason;
|
|
4144
4277
|
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
4145
|
-
const
|
|
4278
|
+
const failedAction3 = {
|
|
4146
4279
|
type: `${modeSeg}_FAILED`,
|
|
4147
4280
|
payload: { reason },
|
|
4148
4281
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4149
4282
|
};
|
|
4150
|
-
ctx.data.action =
|
|
4283
|
+
ctx.data.action = failedAction3;
|
|
4151
4284
|
}
|
|
4152
4285
|
function countActionItems(block) {
|
|
4153
4286
|
if (!block.trim()) return 0;
|
|
@@ -4186,12 +4319,12 @@ function fail2(ctx, profile, reason) {
|
|
|
4186
4319
|
ctx.data.agentDone = false;
|
|
4187
4320
|
ctx.data.agentFailureReason = reason;
|
|
4188
4321
|
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
4189
|
-
const
|
|
4322
|
+
const failedAction3 = {
|
|
4190
4323
|
type: `${modeSeg}_FAILED`,
|
|
4191
4324
|
payload: { reason },
|
|
4192
4325
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4193
4326
|
};
|
|
4194
|
-
ctx.data.action =
|
|
4327
|
+
ctx.data.action = failedAction3;
|
|
4195
4328
|
}
|
|
4196
4329
|
|
|
4197
4330
|
// src/scripts/resolveArtifacts.ts
|
|
@@ -4218,7 +4351,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
4218
4351
|
};
|
|
4219
4352
|
|
|
4220
4353
|
// src/scripts/resolveFlow.ts
|
|
4221
|
-
import { execFileSync as
|
|
4354
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
4222
4355
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
4223
4356
|
var resolveFlow = async (ctx) => {
|
|
4224
4357
|
const prNumber = ctx.args.pr;
|
|
@@ -4270,7 +4403,7 @@ var resolveFlow = async (ctx) => {
|
|
|
4270
4403
|
};
|
|
4271
4404
|
function getConflictedFiles(cwd) {
|
|
4272
4405
|
try {
|
|
4273
|
-
const out =
|
|
4406
|
+
const out = execFileSync16("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
4274
4407
|
encoding: "utf-8",
|
|
4275
4408
|
cwd,
|
|
4276
4409
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -4285,7 +4418,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
4285
4418
|
let total = 0;
|
|
4286
4419
|
for (const f of files) {
|
|
4287
4420
|
try {
|
|
4288
|
-
const content =
|
|
4421
|
+
const content = execFileSync16("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
4289
4422
|
const snippet = `### ${f}
|
|
4290
4423
|
|
|
4291
4424
|
\`\`\`
|
|
@@ -4353,6 +4486,253 @@ function tryPostPr4(prNumber, body, cwd) {
|
|
|
4353
4486
|
}
|
|
4354
4487
|
}
|
|
4355
4488
|
|
|
4489
|
+
// src/scripts/riskGate.ts
|
|
4490
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
4491
|
+
var ALL_GATES = ["secrets", "workflow-edit", "large-diff", "dep-change", "test-deletion"];
|
|
4492
|
+
var HARD_GATES = /* @__PURE__ */ new Set(["secrets"]);
|
|
4493
|
+
var APPROVE_ALL = "kody-approve:all";
|
|
4494
|
+
var GATED_LABEL = "kody:gated";
|
|
4495
|
+
var DEFAULT_MAX_FILES = 20;
|
|
4496
|
+
var DEFAULT_MAX_DELETIONS = 500;
|
|
4497
|
+
var riskGate = async (ctx, profile, _agent, args) => {
|
|
4498
|
+
const changedFiles = ctx.data.changedFiles ?? [];
|
|
4499
|
+
const gatesToRun = parseGates(args?.gates);
|
|
4500
|
+
const violations = evaluateGates(ctx, profile.name, changedFiles, gatesToRun, args);
|
|
4501
|
+
if (violations.length === 0) {
|
|
4502
|
+
ctx.data.riskGate = { violations: [], pending: [], decision: "allow" };
|
|
4503
|
+
return;
|
|
4504
|
+
}
|
|
4505
|
+
const targetType = ctx.data.commentTargetType;
|
|
4506
|
+
const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
|
|
4507
|
+
const labels = targetNumber > 0 ? getIssueLabels(targetNumber, ctx.cwd) : [];
|
|
4508
|
+
const approveAll = labels.includes(APPROVE_ALL);
|
|
4509
|
+
const pending = violations.filter((v) => !isApproved(v, labels, approveAll));
|
|
4510
|
+
ctx.data.riskGate = {
|
|
4511
|
+
violations,
|
|
4512
|
+
pending,
|
|
4513
|
+
decision: pending.length === 0 ? "allow" : "halt"
|
|
4514
|
+
};
|
|
4515
|
+
if (pending.length === 0 || !targetType || targetNumber <= 0) return;
|
|
4516
|
+
for (const v of pending) {
|
|
4517
|
+
ensureApproveLabel(v.name, ctx.cwd);
|
|
4518
|
+
}
|
|
4519
|
+
try {
|
|
4520
|
+
setKodyLabel(
|
|
4521
|
+
targetNumber,
|
|
4522
|
+
{
|
|
4523
|
+
label: GATED_LABEL,
|
|
4524
|
+
color: "fbca04",
|
|
4525
|
+
description: "kody2: awaiting human approval of risk gate(s)"
|
|
4526
|
+
},
|
|
4527
|
+
ctx.cwd
|
|
4528
|
+
);
|
|
4529
|
+
} catch {
|
|
4530
|
+
}
|
|
4531
|
+
const body = formatAdvisory(pending);
|
|
4532
|
+
try {
|
|
4533
|
+
if (targetType === "issue") postIssueComment(targetNumber, body, ctx.cwd);
|
|
4534
|
+
else postPrReviewComment(targetNumber, body, ctx.cwd);
|
|
4535
|
+
} catch {
|
|
4536
|
+
}
|
|
4537
|
+
if (!ctx.output.reason) {
|
|
4538
|
+
ctx.output.reason = `risk gate halt: ${pending.map((p) => p.name).join(", ")}`;
|
|
4539
|
+
}
|
|
4540
|
+
};
|
|
4541
|
+
function evaluateGates(ctx, profileName, changedFiles, gatesToRun, args) {
|
|
4542
|
+
const violations = [];
|
|
4543
|
+
if (gatesToRun.includes("secrets")) {
|
|
4544
|
+
const hits = changedFiles.filter(isSecretPath);
|
|
4545
|
+
if (hits.length > 0) {
|
|
4546
|
+
violations.push({
|
|
4547
|
+
name: "secrets",
|
|
4548
|
+
severity: "hard",
|
|
4549
|
+
reason: `secret/credential files touched: ${preview(hits)}`
|
|
4550
|
+
});
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
if (gatesToRun.includes("workflow-edit")) {
|
|
4554
|
+
const hits = changedFiles.filter((f) => f.startsWith(".github/workflows/"));
|
|
4555
|
+
if (hits.length > 0) {
|
|
4556
|
+
violations.push({
|
|
4557
|
+
name: "workflow-edit",
|
|
4558
|
+
severity: "soft",
|
|
4559
|
+
reason: `CI workflow files modified: ${preview(hits)}`
|
|
4560
|
+
});
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
if (gatesToRun.includes("large-diff")) {
|
|
4564
|
+
const maxFiles = toPositiveInt(args?.maxFiles, DEFAULT_MAX_FILES);
|
|
4565
|
+
const maxDeletions = toPositiveInt(args?.maxDeletions, DEFAULT_MAX_DELETIONS);
|
|
4566
|
+
if (changedFiles.length > maxFiles) {
|
|
4567
|
+
violations.push({
|
|
4568
|
+
name: "large-diff",
|
|
4569
|
+
severity: "soft",
|
|
4570
|
+
reason: `${changedFiles.length} files changed (threshold: ${maxFiles})`
|
|
4571
|
+
});
|
|
4572
|
+
} else {
|
|
4573
|
+
const stats = computeDiffStats(ctx);
|
|
4574
|
+
if (stats && stats.deletions > maxDeletions) {
|
|
4575
|
+
violations.push({
|
|
4576
|
+
name: "large-diff",
|
|
4577
|
+
severity: "soft",
|
|
4578
|
+
reason: `${stats.deletions} lines deleted (threshold: ${maxDeletions})`
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
if (gatesToRun.includes("dep-change") && profileName !== "chore") {
|
|
4584
|
+
const hits = changedFiles.filter(isDepFile);
|
|
4585
|
+
if (hits.length > 0) {
|
|
4586
|
+
violations.push({
|
|
4587
|
+
name: "dep-change",
|
|
4588
|
+
severity: "soft",
|
|
4589
|
+
reason: `dependency/lockfile changes outside a chore flow: ${preview(hits)}`
|
|
4590
|
+
});
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
if (gatesToRun.includes("test-deletion")) {
|
|
4594
|
+
const deleted = listDeletedFilesInHeadCommit(ctx.cwd).filter(isTestFile);
|
|
4595
|
+
if (deleted.length > 0) {
|
|
4596
|
+
violations.push({
|
|
4597
|
+
name: "test-deletion",
|
|
4598
|
+
severity: "soft",
|
|
4599
|
+
reason: `test files deleted: ${preview(deleted)}`
|
|
4600
|
+
});
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
return violations;
|
|
4604
|
+
}
|
|
4605
|
+
function isApproved(v, labels, approveAll) {
|
|
4606
|
+
if (labels.includes(`kody-approve:${v.name}`)) return true;
|
|
4607
|
+
if (!HARD_GATES.has(v.name) && approveAll) return true;
|
|
4608
|
+
return false;
|
|
4609
|
+
}
|
|
4610
|
+
function parseGates(spec) {
|
|
4611
|
+
if (spec === void 0 || spec === null || spec === "") return ALL_GATES;
|
|
4612
|
+
const list = String(spec).split(",").map((s) => s.trim()).filter(Boolean);
|
|
4613
|
+
const valid = ALL_GATES;
|
|
4614
|
+
const matched = list.filter((n) => valid.includes(n));
|
|
4615
|
+
return matched.length > 0 ? matched : ALL_GATES;
|
|
4616
|
+
}
|
|
4617
|
+
function toPositiveInt(v, fallback) {
|
|
4618
|
+
const n = typeof v === "number" ? v : parseInt(String(v ?? ""), 10);
|
|
4619
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
4620
|
+
}
|
|
4621
|
+
function preview(list, max = 5) {
|
|
4622
|
+
if (list.length <= max) return list.join(", ");
|
|
4623
|
+
return `${list.slice(0, max).join(", ")} (+${list.length - max} more)`;
|
|
4624
|
+
}
|
|
4625
|
+
var SECRET_PATTERNS = [
|
|
4626
|
+
/(^|\/)\.env(\.|$)/i,
|
|
4627
|
+
/\.pem$/i,
|
|
4628
|
+
/\.key$/i,
|
|
4629
|
+
/(^|\/)(id_rsa|id_ed25519|id_ecdsa)(\.|$)/i,
|
|
4630
|
+
/credentials?(\.|\/|$)/i,
|
|
4631
|
+
/(^|\/)(private|secret)[^/]*\.json$/i,
|
|
4632
|
+
/(^|\/)\.netrc$/i,
|
|
4633
|
+
/(^|\/)\.npmrc$/i
|
|
4634
|
+
];
|
|
4635
|
+
function isSecretPath(p) {
|
|
4636
|
+
return SECRET_PATTERNS.some((r) => r.test(p));
|
|
4637
|
+
}
|
|
4638
|
+
var DEP_FILES = /* @__PURE__ */ new Set([
|
|
4639
|
+
"package.json",
|
|
4640
|
+
"pnpm-lock.yaml",
|
|
4641
|
+
"package-lock.json",
|
|
4642
|
+
"yarn.lock",
|
|
4643
|
+
"requirements.txt",
|
|
4644
|
+
"Pipfile",
|
|
4645
|
+
"Pipfile.lock",
|
|
4646
|
+
"poetry.lock",
|
|
4647
|
+
"go.mod",
|
|
4648
|
+
"go.sum",
|
|
4649
|
+
"Cargo.toml",
|
|
4650
|
+
"Cargo.lock",
|
|
4651
|
+
"Gemfile",
|
|
4652
|
+
"Gemfile.lock"
|
|
4653
|
+
]);
|
|
4654
|
+
function isDepFile(p) {
|
|
4655
|
+
return DEP_FILES.has(p.split("/").pop() ?? "");
|
|
4656
|
+
}
|
|
4657
|
+
function isTestFile(p) {
|
|
4658
|
+
return /(^|\/)(tests?|__tests__|spec)\//i.test(p) || /\.(test|spec)\.[a-z0-9]+$/i.test(p);
|
|
4659
|
+
}
|
|
4660
|
+
function computeDiffStats(ctx) {
|
|
4661
|
+
const base = ctx.config.git.defaultBranch;
|
|
4662
|
+
for (const ref of [`origin/${base}...HEAD`, `${base}...HEAD`]) {
|
|
4663
|
+
try {
|
|
4664
|
+
const out = execFileSync17("git", ["diff", "--shortstat", ref], {
|
|
4665
|
+
cwd: ctx.cwd,
|
|
4666
|
+
encoding: "utf-8",
|
|
4667
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4668
|
+
}).trim();
|
|
4669
|
+
if (out) return parseShortstat(out);
|
|
4670
|
+
} catch {
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
return null;
|
|
4674
|
+
}
|
|
4675
|
+
function parseShortstat(s) {
|
|
4676
|
+
const ins = /(\d+)\s+insertions?/.exec(s);
|
|
4677
|
+
const del = /(\d+)\s+deletions?/.exec(s);
|
|
4678
|
+
return {
|
|
4679
|
+
insertions: ins ? parseInt(ins[1], 10) : 0,
|
|
4680
|
+
deletions: del ? parseInt(del[1], 10) : 0
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
function listDeletedFilesInHeadCommit(cwd) {
|
|
4684
|
+
try {
|
|
4685
|
+
const out = execFileSync17("git", ["show", "--name-status", "--pretty=format:", "HEAD"], {
|
|
4686
|
+
cwd,
|
|
4687
|
+
encoding: "utf-8",
|
|
4688
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4689
|
+
});
|
|
4690
|
+
return out.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("D ")).map((l) => l.slice(2).trim()).filter(Boolean);
|
|
4691
|
+
} catch {
|
|
4692
|
+
return [];
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
function ensureApproveLabel(gate, cwd) {
|
|
4696
|
+
try {
|
|
4697
|
+
gh2(
|
|
4698
|
+
[
|
|
4699
|
+
"label",
|
|
4700
|
+
"create",
|
|
4701
|
+
`kody-approve:${gate}`,
|
|
4702
|
+
"--force",
|
|
4703
|
+
"--color",
|
|
4704
|
+
"0e8a16",
|
|
4705
|
+
"--description",
|
|
4706
|
+
`kody2: approve the ${gate} risk gate and resume the flow`
|
|
4707
|
+
],
|
|
4708
|
+
{ cwd }
|
|
4709
|
+
);
|
|
4710
|
+
} catch {
|
|
4711
|
+
}
|
|
4712
|
+
}
|
|
4713
|
+
function formatAdvisory(pending) {
|
|
4714
|
+
const lines = [];
|
|
4715
|
+
lines.push("\u23F8\uFE0F **kody2 risk gate halted the flow.**");
|
|
4716
|
+
lines.push("");
|
|
4717
|
+
lines.push("The changes were committed and pushed, but the flow will **not progress** until a human approves:");
|
|
4718
|
+
lines.push("");
|
|
4719
|
+
for (const v of pending) {
|
|
4720
|
+
lines.push(`- **\`${v.name}\`** _(${v.severity})_ \u2014 ${v.reason}`);
|
|
4721
|
+
}
|
|
4722
|
+
lines.push("");
|
|
4723
|
+
lines.push("**To approve and resume**, add one of these labels to this issue/PR:");
|
|
4724
|
+
const perGate = pending.map((v) => `\`kody-approve:${v.name}\``).join(", ");
|
|
4725
|
+
lines.push(`- ${perGate}`);
|
|
4726
|
+
if (pending.some((v) => v.severity === "soft")) {
|
|
4727
|
+
lines.push("- `kody-approve:all` \u2014 bypass **soft** gates at once (hard gates still require their specific label)");
|
|
4728
|
+
}
|
|
4729
|
+
lines.push("");
|
|
4730
|
+
lines.push(
|
|
4731
|
+
"Then re-trigger kody2 (e.g. post a new `@kody2 \u2026` comment). The branch already holds the committed work, so the re-run resumes from the same point."
|
|
4732
|
+
);
|
|
4733
|
+
return lines.join("\n");
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4356
4736
|
// src/scripts/runFlow.ts
|
|
4357
4737
|
var runFlow = async (ctx) => {
|
|
4358
4738
|
const issueNumber = ctx.args.issue;
|
|
@@ -4449,8 +4829,8 @@ var skipAgent = async (ctx) => {
|
|
|
4449
4829
|
};
|
|
4450
4830
|
|
|
4451
4831
|
// src/scripts/startFlow.ts
|
|
4452
|
-
import { execFileSync as
|
|
4453
|
-
var
|
|
4832
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
4833
|
+
var API_TIMEOUT_MS7 = 3e4;
|
|
4454
4834
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
4455
4835
|
const entry = args?.entry;
|
|
4456
4836
|
if (!entry) {
|
|
@@ -4483,8 +4863,8 @@ function postKody2Comment(target, issueNumber, state, next, cwd) {
|
|
|
4483
4863
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
4484
4864
|
const body = `@kody2 ${next}`;
|
|
4485
4865
|
try {
|
|
4486
|
-
|
|
4487
|
-
timeout:
|
|
4866
|
+
execFileSync18("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
4867
|
+
timeout: API_TIMEOUT_MS7,
|
|
4488
4868
|
cwd,
|
|
4489
4869
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4490
4870
|
});
|
|
@@ -4503,7 +4883,7 @@ function parsePr2(url) {
|
|
|
4503
4883
|
}
|
|
4504
4884
|
|
|
4505
4885
|
// src/scripts/syncFlow.ts
|
|
4506
|
-
import { execFileSync as
|
|
4886
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
4507
4887
|
var syncFlow = async (ctx) => {
|
|
4508
4888
|
ctx.skipAgent = true;
|
|
4509
4889
|
const prNumber = ctx.args.pr;
|
|
@@ -4562,7 +4942,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
4562
4942
|
}
|
|
4563
4943
|
function revParseHead(cwd) {
|
|
4564
4944
|
try {
|
|
4565
|
-
return
|
|
4945
|
+
return execFileSync19("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
4566
4946
|
} catch {
|
|
4567
4947
|
return "";
|
|
4568
4948
|
}
|
|
@@ -4570,9 +4950,9 @@ function revParseHead(cwd) {
|
|
|
4570
4950
|
function pushBranch(branch, cwd) {
|
|
4571
4951
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
4572
4952
|
try {
|
|
4573
|
-
|
|
4953
|
+
execFileSync19("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
4574
4954
|
} catch {
|
|
4575
|
-
|
|
4955
|
+
execFileSync19("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
4576
4956
|
cwd,
|
|
4577
4957
|
env,
|
|
4578
4958
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4790,7 +5170,8 @@ var preflightScripts = {
|
|
|
4790
5170
|
resolvePreviewUrl,
|
|
4791
5171
|
composePrompt,
|
|
4792
5172
|
setLifecycleLabel,
|
|
4793
|
-
skipAgent
|
|
5173
|
+
skipAgent,
|
|
5174
|
+
classifyByLabel
|
|
4794
5175
|
};
|
|
4795
5176
|
var postflightScripts = {
|
|
4796
5177
|
parseAgentResult: parseAgentResult2,
|
|
@@ -4812,7 +5193,9 @@ var postflightScripts = {
|
|
|
4812
5193
|
dispatch,
|
|
4813
5194
|
finishFlow,
|
|
4814
5195
|
advanceFlow,
|
|
4815
|
-
persistFlowState
|
|
5196
|
+
persistFlowState,
|
|
5197
|
+
postClassification,
|
|
5198
|
+
riskGate
|
|
4816
5199
|
};
|
|
4817
5200
|
var allScriptNames = /* @__PURE__ */ new Set([
|
|
4818
5201
|
...Object.keys(preflightScripts),
|
|
@@ -4820,7 +5203,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
4820
5203
|
]);
|
|
4821
5204
|
|
|
4822
5205
|
// src/tools.ts
|
|
4823
|
-
import { execFileSync as
|
|
5206
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
4824
5207
|
function verifyCliTools(tools, cwd) {
|
|
4825
5208
|
const out = [];
|
|
4826
5209
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -4853,7 +5236,7 @@ function verifyOne(tool, cwd) {
|
|
|
4853
5236
|
}
|
|
4854
5237
|
function runShell2(cmd, cwd, timeoutMs = 3e4) {
|
|
4855
5238
|
try {
|
|
4856
|
-
|
|
5239
|
+
execFileSync20("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
4857
5240
|
return true;
|
|
4858
5241
|
} catch {
|
|
4859
5242
|
return false;
|
|
@@ -5183,7 +5566,7 @@ function detectPackageManager2(cwd) {
|
|
|
5183
5566
|
}
|
|
5184
5567
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
5185
5568
|
try {
|
|
5186
|
-
|
|
5569
|
+
execFileSync21(cmd, args, {
|
|
5187
5570
|
cwd,
|
|
5188
5571
|
stdio: stream ? "inherit" : "pipe",
|
|
5189
5572
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -5196,7 +5579,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
5196
5579
|
}
|
|
5197
5580
|
function isOnPath(bin) {
|
|
5198
5581
|
try {
|
|
5199
|
-
|
|
5582
|
+
execFileSync21("which", [bin], { stdio: "pipe" });
|
|
5200
5583
|
return true;
|
|
5201
5584
|
} catch {
|
|
5202
5585
|
return false;
|
|
@@ -5230,7 +5613,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5230
5613
|
} catch {
|
|
5231
5614
|
}
|
|
5232
5615
|
try {
|
|
5233
|
-
|
|
5616
|
+
execFileSync21("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
5234
5617
|
process.stdout.write("\u2192 kody2: litellm already installed\n");
|
|
5235
5618
|
return 0;
|
|
5236
5619
|
} catch {
|
|
@@ -5240,16 +5623,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
5240
5623
|
}
|
|
5241
5624
|
function configureGitIdentity(cwd) {
|
|
5242
5625
|
try {
|
|
5243
|
-
const name =
|
|
5626
|
+
const name = execFileSync21("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
5244
5627
|
if (name) return;
|
|
5245
5628
|
} catch {
|
|
5246
5629
|
}
|
|
5247
5630
|
try {
|
|
5248
|
-
|
|
5631
|
+
execFileSync21("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
5249
5632
|
} catch {
|
|
5250
5633
|
}
|
|
5251
5634
|
try {
|
|
5252
|
-
|
|
5635
|
+
execFileSync21("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
5253
5636
|
cwd,
|
|
5254
5637
|
stdio: "pipe"
|
|
5255
5638
|
});
|
|
@@ -5426,9 +5809,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
5426
5809
|
if (paths.length === 0) return;
|
|
5427
5810
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
5428
5811
|
try {
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5812
|
+
execFileSync22("git", ["add", ...paths], opts);
|
|
5813
|
+
execFileSync22("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
5814
|
+
execFileSync22("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
5432
5815
|
} catch (err) {
|
|
5433
5816
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5434
5817
|
process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "classify",
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"describe": "Classify an issue into one of {feature, bug, spec, chore} and dispatch the matching sub-orchestrator. Label-first fast path; LLM fallback when labels don't decide.",
|
|
5
|
+
"inputs": [
|
|
6
|
+
{
|
|
7
|
+
"name": "issue",
|
|
8
|
+
"flag": "--issue",
|
|
9
|
+
"type": "int",
|
|
10
|
+
"required": true,
|
|
11
|
+
"describe": "GitHub issue number to classify."
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"claudeCode": {
|
|
15
|
+
"model": "inherit",
|
|
16
|
+
"permissionMode": "default",
|
|
17
|
+
"maxTurns": null,
|
|
18
|
+
"maxThinkingTokens": null,
|
|
19
|
+
"systemPromptAppend": null,
|
|
20
|
+
"tools": [
|
|
21
|
+
"Read",
|
|
22
|
+
"Bash"
|
|
23
|
+
],
|
|
24
|
+
"hooks": [],
|
|
25
|
+
"skills": [],
|
|
26
|
+
"commands": [],
|
|
27
|
+
"subagents": [],
|
|
28
|
+
"plugins": [],
|
|
29
|
+
"mcpServers": []
|
|
30
|
+
},
|
|
31
|
+
"cliTools": [],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"preflight": [
|
|
34
|
+
{
|
|
35
|
+
"script": "setLifecycleLabel",
|
|
36
|
+
"with": {
|
|
37
|
+
"label": "kody:classifying",
|
|
38
|
+
"color": "0e8a16",
|
|
39
|
+
"description": "kody2: classifying the issue"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{ "script": "loadIssueContext" },
|
|
43
|
+
{ "script": "loadTaskState" },
|
|
44
|
+
{ "script": "classifyByLabel" },
|
|
45
|
+
{ "script": "loadConventions" },
|
|
46
|
+
{ "script": "composePrompt" }
|
|
47
|
+
],
|
|
48
|
+
"postflight": [
|
|
49
|
+
{ "script": "parseAgentResult" },
|
|
50
|
+
{ "script": "postClassification" },
|
|
51
|
+
{ "script": "writeRunSummary" },
|
|
52
|
+
{ "script": "saveTaskState" }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"output": {
|
|
56
|
+
"actionTypes": [
|
|
57
|
+
"CLASSIFIED_AS_FEATURE",
|
|
58
|
+
"CLASSIFIED_AS_BUG",
|
|
59
|
+
"CLASSIFIED_AS_SPEC",
|
|
60
|
+
"CLASSIFIED_AS_CHORE",
|
|
61
|
+
"CLASSIFY_FAILED"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
You are Kody's issue-triage classifier. Your only job: read the issue below and pick ONE of the four flow types.
|
|
2
|
+
|
|
3
|
+
# Repo
|
|
4
|
+
{{repoOwner}}/{{repoName}}, default branch `{{defaultBranch}}`
|
|
5
|
+
|
|
6
|
+
# Issue #{{issue.number}}: {{issue.title}}
|
|
7
|
+
|
|
8
|
+
Labels: {{issue.labelsFormatted}}
|
|
9
|
+
|
|
10
|
+
{{issue.body}}
|
|
11
|
+
|
|
12
|
+
Recent comments (most recent first, truncated):
|
|
13
|
+
{{issue.commentsFormatted}}
|
|
14
|
+
|
|
15
|
+
{{conventionsBlock}}
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Classification rubric
|
|
20
|
+
|
|
21
|
+
Pick **exactly one** of:
|
|
22
|
+
|
|
23
|
+
- **feature** — new user-facing capability, refactor, performance work, or anything where scope is not fully known up front. Multi-file change likely. Use when the issue opens a design space (even if small).
|
|
24
|
+
- **bug** — fix broken behavior, enhancement to existing feature, or any targeted change where the scope is localized and well understood. Skip research; go straight to plan.
|
|
25
|
+
- **spec** — produce a design doc, RFC, architecture proposal, or exploration artifact. No code changes. Terminates at the plan artifact.
|
|
26
|
+
- **chore** — trivial maintenance: docs tweak, dep bump, lint fix, README update. No planning needed.
|
|
27
|
+
|
|
28
|
+
**If the issue ASKS for an RFC / design doc / spec / analysis with no implementation → `spec`.** Beats everything else.
|
|
29
|
+
**If the issue is plainly "fix X" or "add tiny Y to existing Z" with clear boundaries → `bug`.**
|
|
30
|
+
**If the issue is "tweak config / bump dep / fix typo" with no real design choice → `chore`.**
|
|
31
|
+
**Otherwise → `feature`.**
|
|
32
|
+
|
|
33
|
+
# Required output
|
|
34
|
+
|
|
35
|
+
Your FINAL message must be exactly this shape (no extra text before or after):
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
DONE
|
|
39
|
+
COMMIT_MSG: classify: <classification>
|
|
40
|
+
PR_SUMMARY:
|
|
41
|
+
classification: <feature|bug|spec|chore>
|
|
42
|
+
reason: <one sentence explaining the pick, grounded in the issue text>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
# Rules
|
|
46
|
+
|
|
47
|
+
- Read-only. Do NOT modify any file. Do NOT run git or gh.
|
|
48
|
+
- Output `FAILED: <reason>` if the issue is incoherent or ambiguous beyond the rubric.
|
|
49
|
+
- Do not over-think. This is triage, not analysis.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.56",
|
|
4
4
|
"description": "kody2 — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|