@kody-ade/kody-engine 0.3.46 → 0.3.48
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/README.md +13 -0
- package/dist/bin/kody.js +169 -159
- package/dist/executables/classify/profile.json +3 -2
- package/dist/executables/release-deploy/deploy.sh +0 -0
- package/dist/executables/release-prepare/prepare.sh +0 -0
- package/dist/executables/release-publish/publish.sh +0 -0
- package/dist/executables/resolve/apply-prefer.sh +0 -0
- package/dist/executables/run/prompt.md +2 -1
- package/package.json +15 -14
- package/templates/kody.yml +7 -3
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ kody chore --issue <N> # run → review (→ fix
|
|
|
62
62
|
kody mission-scheduler # fans out to per-issue mission-tick
|
|
63
63
|
kody mission-tick --issue <N> # one tick of a kody:mission issue
|
|
64
64
|
kody watch-stale-prs # weekly stale-PR report
|
|
65
|
+
kody memorize # daily vault wiki update from recent PRs
|
|
65
66
|
|
|
66
67
|
# deterministic (no agent)
|
|
67
68
|
kody sync --pr <N> # merge default into PR branch
|
|
@@ -91,6 +92,18 @@ Drives the running preview deployment via the Playwright MCP server alongside th
|
|
|
91
92
|
- Credentials: `.kody/qa-guide.md` (committed, scaffolded by `kody init` with `CHANGE_ME` placeholders).
|
|
92
93
|
- Auto-discovery: routes, roles, login/admin paths, Payload CMS collections, API routes, env vars — fed to the agent as context.
|
|
93
94
|
|
|
95
|
+
### `memorize` — vault wiki
|
|
96
|
+
|
|
97
|
+
A scheduled watch (cron `0 3 * * *`) that synthesizes recently merged PRs into a markdown knowledge base at `.kody/vault/` and opens a PR with the changes. Pages are entity-centric (`architecture/`, `conventions/`, `decisions/`, `components/`), not per-PR logs. Future kody runs see the relevant pages via the `loadVaultContext` preflight, which is wired into `run` / `fix` / `resolve` and exposes them as `{{vaultContext}}` in the prompt.
|
|
98
|
+
|
|
99
|
+
To enable in a consumer repo: ensure `.gitignore` un-ignores the vault if `.kody/*` is otherwise ignored:
|
|
100
|
+
|
|
101
|
+
```gitignore
|
|
102
|
+
.kody/*
|
|
103
|
+
!.kody/vault/
|
|
104
|
+
!.kody/vault/**
|
|
105
|
+
```
|
|
106
|
+
|
|
94
107
|
### `release`
|
|
95
108
|
|
|
96
109
|
- `--mode prepare` — bumps `package.json`, updates `CHANGELOG.md`, opens a `release/vX.Y.Z` PR. `--bump patch|minor|major` (default `patch`).
|
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.48",
|
|
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",
|
|
@@ -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 execFileSync25 } from "child_process";
|
|
54
54
|
import * as fs26 from "fs";
|
|
55
55
|
import * as path23 from "path";
|
|
56
56
|
|
|
@@ -605,7 +605,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
605
605
|
}
|
|
606
606
|
|
|
607
607
|
// src/kody-cli.ts
|
|
608
|
-
import { execFileSync as
|
|
608
|
+
import { execFileSync as execFileSync24 } from "child_process";
|
|
609
609
|
import * as fs25 from "fs";
|
|
610
610
|
import * as path22 from "path";
|
|
611
611
|
|
|
@@ -2774,6 +2774,35 @@ function parsePr(url) {
|
|
|
2774
2774
|
return Number.isFinite(n) ? n : null;
|
|
2775
2775
|
}
|
|
2776
2776
|
|
|
2777
|
+
// src/scripts/dispatchClassified.ts
|
|
2778
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
2779
|
+
var API_TIMEOUT_MS4 = 3e4;
|
|
2780
|
+
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
2781
|
+
var dispatchClassified = async (ctx) => {
|
|
2782
|
+
const issueNumber = ctx.args.issue;
|
|
2783
|
+
if (!issueNumber) return;
|
|
2784
|
+
const classification = ctx.data.classification;
|
|
2785
|
+
if (!classification || !VALID_CLASSES2.has(classification)) return;
|
|
2786
|
+
try {
|
|
2787
|
+
execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
2788
|
+
cwd: ctx.cwd,
|
|
2789
|
+
timeout: API_TIMEOUT_MS4,
|
|
2790
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2791
|
+
});
|
|
2792
|
+
} catch (err) {
|
|
2793
|
+
process.stderr.write(
|
|
2794
|
+
`[kody dispatchClassified] failed to dispatch @kody ${classification}: ${err instanceof Error ? err.message : String(err)}
|
|
2795
|
+
`
|
|
2796
|
+
);
|
|
2797
|
+
ctx.data.action = failedAction("dispatch post failed");
|
|
2798
|
+
ctx.output.exitCode = 1;
|
|
2799
|
+
ctx.output.reason = "classify: dispatch failed";
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
function failedAction(reason) {
|
|
2803
|
+
return { type: "CLASSIFY_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2777
2806
|
// src/scripts/dispatchMissionFileTicks.ts
|
|
2778
2807
|
import * as fs16 from "fs";
|
|
2779
2808
|
import * as path15 from "path";
|
|
@@ -2833,17 +2862,17 @@ function listMissionSlugs(absDir) {
|
|
|
2833
2862
|
}
|
|
2834
2863
|
|
|
2835
2864
|
// src/issue.ts
|
|
2836
|
-
import { execFileSync as
|
|
2837
|
-
var
|
|
2865
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
2866
|
+
var API_TIMEOUT_MS5 = 3e4;
|
|
2838
2867
|
function ghToken2() {
|
|
2839
2868
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
2840
2869
|
}
|
|
2841
2870
|
function gh2(args, options) {
|
|
2842
2871
|
const token = ghToken2();
|
|
2843
2872
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
2844
|
-
return
|
|
2873
|
+
return execFileSync9("gh", args, {
|
|
2845
2874
|
encoding: "utf-8",
|
|
2846
|
-
timeout:
|
|
2875
|
+
timeout: API_TIMEOUT_MS5,
|
|
2847
2876
|
cwd: options?.cwd,
|
|
2848
2877
|
env,
|
|
2849
2878
|
input: options?.input,
|
|
@@ -3214,18 +3243,7 @@ var ensureMemorizePr = async (ctx) => {
|
|
|
3214
3243
|
}
|
|
3215
3244
|
try {
|
|
3216
3245
|
const output = gh2(
|
|
3217
|
-
[
|
|
3218
|
-
"pr",
|
|
3219
|
-
"create",
|
|
3220
|
-
"--head",
|
|
3221
|
-
branch,
|
|
3222
|
-
"--base",
|
|
3223
|
-
ctx.config.git.defaultBranch,
|
|
3224
|
-
"--title",
|
|
3225
|
-
title,
|
|
3226
|
-
"--body-file",
|
|
3227
|
-
"-"
|
|
3228
|
-
],
|
|
3246
|
+
["pr", "create", "--head", branch, "--base", ctx.config.git.defaultBranch, "--title", title, "--body-file", "-"],
|
|
3229
3247
|
{ input: body, cwd: ctx.cwd }
|
|
3230
3248
|
);
|
|
3231
3249
|
const url = output.trim();
|
|
@@ -3321,7 +3339,7 @@ function computeFailureReason(ctx) {
|
|
|
3321
3339
|
}
|
|
3322
3340
|
|
|
3323
3341
|
// src/scripts/finishFlow.ts
|
|
3324
|
-
import { execFileSync as
|
|
3342
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3325
3343
|
|
|
3326
3344
|
// src/lifecycleLabels.ts
|
|
3327
3345
|
var KODY_NAMESPACE = "kody";
|
|
@@ -3441,7 +3459,7 @@ function errMsg(err) {
|
|
|
3441
3459
|
}
|
|
3442
3460
|
|
|
3443
3461
|
// src/scripts/finishFlow.ts
|
|
3444
|
-
var
|
|
3462
|
+
var API_TIMEOUT_MS6 = 3e4;
|
|
3445
3463
|
var STATUS_ICON = {
|
|
3446
3464
|
"review-passed": "\u2705",
|
|
3447
3465
|
"fix-applied": "\u2705",
|
|
@@ -3474,8 +3492,8 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
3474
3492
|
**PR:** ${state.core.prUrl}` : "";
|
|
3475
3493
|
const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
|
|
3476
3494
|
try {
|
|
3477
|
-
|
|
3478
|
-
timeout:
|
|
3495
|
+
execFileSync10("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
3496
|
+
timeout: API_TIMEOUT_MS6,
|
|
3479
3497
|
cwd: ctx.cwd,
|
|
3480
3498
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3481
3499
|
});
|
|
@@ -3488,7 +3506,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
|
|
|
3488
3506
|
};
|
|
3489
3507
|
|
|
3490
3508
|
// src/branch.ts
|
|
3491
|
-
import { execFileSync as
|
|
3509
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
3492
3510
|
var UncommittedChangesError = class extends Error {
|
|
3493
3511
|
constructor(branch) {
|
|
3494
3512
|
super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
|
|
@@ -3498,7 +3516,7 @@ var UncommittedChangesError = class extends Error {
|
|
|
3498
3516
|
branch;
|
|
3499
3517
|
};
|
|
3500
3518
|
function git2(args, cwd) {
|
|
3501
|
-
return
|
|
3519
|
+
return execFileSync11("git", args, {
|
|
3502
3520
|
encoding: "utf-8",
|
|
3503
3521
|
timeout: 3e4,
|
|
3504
3522
|
cwd,
|
|
@@ -3523,7 +3541,7 @@ function checkoutPrBranch(prNumber, cwd) {
|
|
|
3523
3541
|
SKIP_HOOKS: "1",
|
|
3524
3542
|
GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
|
|
3525
3543
|
};
|
|
3526
|
-
|
|
3544
|
+
execFileSync11("gh", ["pr", "checkout", String(prNumber)], {
|
|
3527
3545
|
cwd,
|
|
3528
3546
|
env,
|
|
3529
3547
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3590,7 +3608,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
3590
3608
|
}
|
|
3591
3609
|
|
|
3592
3610
|
// src/gha.ts
|
|
3593
|
-
import { execFileSync as
|
|
3611
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
3594
3612
|
import * as fs17 from "fs";
|
|
3595
3613
|
function getRunUrl() {
|
|
3596
3614
|
const server = process.env.GITHUB_SERVER_URL;
|
|
@@ -3633,7 +3651,7 @@ function reactToTriggerComment(cwd) {
|
|
|
3633
3651
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
3634
3652
|
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
3635
3653
|
try {
|
|
3636
|
-
|
|
3654
|
+
execFileSync12("gh", args, opts);
|
|
3637
3655
|
return;
|
|
3638
3656
|
} catch (err) {
|
|
3639
3657
|
lastErr = err;
|
|
@@ -3646,13 +3664,13 @@ function reactToTriggerComment(cwd) {
|
|
|
3646
3664
|
}
|
|
3647
3665
|
function sleepMs(ms) {
|
|
3648
3666
|
try {
|
|
3649
|
-
|
|
3667
|
+
execFileSync12("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
3650
3668
|
} catch {
|
|
3651
3669
|
}
|
|
3652
3670
|
}
|
|
3653
3671
|
|
|
3654
3672
|
// src/workflow.ts
|
|
3655
|
-
import { execFileSync as
|
|
3673
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
3656
3674
|
var GH_TIMEOUT_MS = 3e4;
|
|
3657
3675
|
function ghToken3() {
|
|
3658
3676
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
@@ -3660,7 +3678,7 @@ function ghToken3() {
|
|
|
3660
3678
|
function gh3(args, cwd) {
|
|
3661
3679
|
const token = ghToken3();
|
|
3662
3680
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
3663
|
-
return
|
|
3681
|
+
return execFileSync13("gh", args, {
|
|
3664
3682
|
encoding: "utf-8",
|
|
3665
3683
|
timeout: GH_TIMEOUT_MS,
|
|
3666
3684
|
cwd,
|
|
@@ -3844,7 +3862,7 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
3844
3862
|
}
|
|
3845
3863
|
|
|
3846
3864
|
// src/scripts/initFlow.ts
|
|
3847
|
-
import { execFileSync as
|
|
3865
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
3848
3866
|
import * as fs19 from "fs";
|
|
3849
3867
|
import * as path17 from "path";
|
|
3850
3868
|
|
|
@@ -3885,7 +3903,7 @@ function qualityCommandsFor(pm) {
|
|
|
3885
3903
|
function detectOwnerRepo(cwd) {
|
|
3886
3904
|
let url;
|
|
3887
3905
|
try {
|
|
3888
|
-
url =
|
|
3906
|
+
url = execFileSync14("git", ["remote", "get-url", "origin"], {
|
|
3889
3907
|
cwd,
|
|
3890
3908
|
encoding: "utf-8",
|
|
3891
3909
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3970,7 +3988,7 @@ jobs:
|
|
|
3970
3988
|
`;
|
|
3971
3989
|
function defaultBranchFromGit(cwd) {
|
|
3972
3990
|
try {
|
|
3973
|
-
const ref =
|
|
3991
|
+
const ref = execFileSync14("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
3974
3992
|
cwd,
|
|
3975
3993
|
encoding: "utf-8",
|
|
3976
3994
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3978,7 +3996,7 @@ function defaultBranchFromGit(cwd) {
|
|
|
3978
3996
|
return ref.replace("refs/remotes/origin/", "");
|
|
3979
3997
|
} catch {
|
|
3980
3998
|
try {
|
|
3981
|
-
return
|
|
3999
|
+
return execFileSync14("git", ["branch", "--show-current"], {
|
|
3982
4000
|
cwd,
|
|
3983
4001
|
encoding: "utf-8",
|
|
3984
4002
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4567,7 +4585,12 @@ function sortByRecency(pages) {
|
|
|
4567
4585
|
}
|
|
4568
4586
|
function formatBlock(pages) {
|
|
4569
4587
|
if (pages.length === 0) return "";
|
|
4570
|
-
const lines = [
|
|
4588
|
+
const lines = [
|
|
4589
|
+
"# Project memory (`.kody/vault/`)",
|
|
4590
|
+
"",
|
|
4591
|
+
"Pages from prior memorize ticks. Treat as advisory context \u2014 confirm against the codebase before acting.",
|
|
4592
|
+
""
|
|
4593
|
+
];
|
|
4571
4594
|
let total = 0;
|
|
4572
4595
|
for (const p of pages) {
|
|
4573
4596
|
const block = [`## ${p.title} \u2014 \`${p.relPath}\``, "", p.content].join("\n");
|
|
@@ -4582,7 +4605,7 @@ function formatBlock(pages) {
|
|
|
4582
4605
|
return lines.join("\n");
|
|
4583
4606
|
}
|
|
4584
4607
|
function walkMd(root, visit) {
|
|
4585
|
-
|
|
4608
|
+
const stack = [root];
|
|
4586
4609
|
while (stack.length > 0) {
|
|
4587
4610
|
const dir = stack.pop();
|
|
4588
4611
|
let names;
|
|
@@ -4610,7 +4633,7 @@ function walkMd(root, visit) {
|
|
|
4610
4633
|
}
|
|
4611
4634
|
|
|
4612
4635
|
// src/scripts/memorizeFlow.ts
|
|
4613
|
-
import { execFileSync as
|
|
4636
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
4614
4637
|
import * as fs22 from "fs";
|
|
4615
4638
|
import * as path20 from "path";
|
|
4616
4639
|
var VAULT_DIR_RELATIVE2 = ".kody/vault";
|
|
@@ -4637,8 +4660,10 @@ var memorizeFlow = async (ctx) => {
|
|
|
4637
4660
|
process.stdout.write(`[kody memorize] no merged PRs since ${sinceIso}; agent may emit no changes
|
|
4638
4661
|
`);
|
|
4639
4662
|
} else {
|
|
4640
|
-
process.stdout.write(
|
|
4641
|
-
`)
|
|
4663
|
+
process.stdout.write(
|
|
4664
|
+
`[kody memorize] ${recent.length} merged PR(s) since ${sinceIso} \u2192 branch ${ctx.data.branch}
|
|
4665
|
+
`
|
|
4666
|
+
);
|
|
4642
4667
|
}
|
|
4643
4668
|
};
|
|
4644
4669
|
function ensureBranch(ctx, vaultAbs) {
|
|
@@ -4764,7 +4789,7 @@ function formatVaultIndex(vaultAbs) {
|
|
|
4764
4789
|
}
|
|
4765
4790
|
function walkMd2(root, visit) {
|
|
4766
4791
|
if (!fs22.existsSync(root)) return;
|
|
4767
|
-
|
|
4792
|
+
const stack = [root];
|
|
4768
4793
|
while (stack.length > 0) {
|
|
4769
4794
|
const dir = stack.pop();
|
|
4770
4795
|
let names;
|
|
@@ -4791,7 +4816,7 @@ function walkMd2(root, visit) {
|
|
|
4791
4816
|
}
|
|
4792
4817
|
}
|
|
4793
4818
|
function git3(args, cwd) {
|
|
4794
|
-
return
|
|
4819
|
+
return execFileSync15("git", args, {
|
|
4795
4820
|
encoding: "utf-8",
|
|
4796
4821
|
timeout: 3e4,
|
|
4797
4822
|
cwd,
|
|
@@ -4801,8 +4826,8 @@ function git3(args, cwd) {
|
|
|
4801
4826
|
}
|
|
4802
4827
|
|
|
4803
4828
|
// src/scripts/mergeReleasePr.ts
|
|
4804
|
-
import { execFileSync as
|
|
4805
|
-
var
|
|
4829
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
4830
|
+
var API_TIMEOUT_MS7 = 6e4;
|
|
4806
4831
|
var mergeReleasePr = async (ctx) => {
|
|
4807
4832
|
const state = ctx.data.taskState;
|
|
4808
4833
|
const prUrl = state?.core.prUrl;
|
|
@@ -4818,8 +4843,8 @@ var mergeReleasePr = async (ctx) => {
|
|
|
4818
4843
|
return;
|
|
4819
4844
|
}
|
|
4820
4845
|
try {
|
|
4821
|
-
|
|
4822
|
-
timeout:
|
|
4846
|
+
execFileSync16("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
4847
|
+
timeout: API_TIMEOUT_MS7,
|
|
4823
4848
|
cwd: ctx.cwd,
|
|
4824
4849
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4825
4850
|
});
|
|
@@ -5082,86 +5107,6 @@ var persistFlowState = async (ctx) => {
|
|
|
5082
5107
|
}
|
|
5083
5108
|
};
|
|
5084
5109
|
|
|
5085
|
-
// src/scripts/postClassification.ts
|
|
5086
|
-
import { execFileSync as execFileSync16 } from "child_process";
|
|
5087
|
-
var API_TIMEOUT_MS7 = 3e4;
|
|
5088
|
-
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
5089
|
-
var postClassification = async (ctx) => {
|
|
5090
|
-
const issueNumber = ctx.args.issue;
|
|
5091
|
-
if (!issueNumber) return;
|
|
5092
|
-
const presetClassification = ctx.data.classification;
|
|
5093
|
-
let classification = null;
|
|
5094
|
-
let reason = null;
|
|
5095
|
-
if (presetClassification && VALID_CLASSES2.has(presetClassification)) {
|
|
5096
|
-
classification = presetClassification;
|
|
5097
|
-
reason = ctx.data.classificationReason ?? "label-based match";
|
|
5098
|
-
} else {
|
|
5099
|
-
const parsed = parseClassification(ctx.data.prSummary ?? "");
|
|
5100
|
-
classification = parsed?.classification ?? null;
|
|
5101
|
-
reason = parsed?.reason ?? null;
|
|
5102
|
-
}
|
|
5103
|
-
if (!classification) {
|
|
5104
|
-
ctx.data.action = failedAction("classification missing or invalid");
|
|
5105
|
-
tryAuditComment(
|
|
5106
|
-
issueNumber,
|
|
5107
|
-
"\u26A0\uFE0F kody classifier could not decide \u2014 please re-run with an explicit `@kody <type>`.",
|
|
5108
|
-
ctx.cwd
|
|
5109
|
-
);
|
|
5110
|
-
ctx.output.exitCode = 1;
|
|
5111
|
-
ctx.output.reason = "classify: no decision";
|
|
5112
|
-
return;
|
|
5113
|
-
}
|
|
5114
|
-
tryAuditComment(issueNumber, `\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`, ctx.cwd);
|
|
5115
|
-
try {
|
|
5116
|
-
execFileSync16("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
5117
|
-
cwd: ctx.cwd,
|
|
5118
|
-
timeout: API_TIMEOUT_MS7,
|
|
5119
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
5120
|
-
});
|
|
5121
|
-
} catch (err) {
|
|
5122
|
-
process.stderr.write(
|
|
5123
|
-
`[kody postClassification] failed to dispatch @kody ${classification}: ${err instanceof Error ? err.message : String(err)}
|
|
5124
|
-
`
|
|
5125
|
-
);
|
|
5126
|
-
ctx.data.action = failedAction("dispatch post failed");
|
|
5127
|
-
ctx.output.exitCode = 1;
|
|
5128
|
-
ctx.output.reason = "classify: dispatch failed";
|
|
5129
|
-
return;
|
|
5130
|
-
}
|
|
5131
|
-
ctx.data.action = makeAction3(`CLASSIFIED_AS_${classification.toUpperCase()}`, {
|
|
5132
|
-
classification,
|
|
5133
|
-
reason: reason ?? "",
|
|
5134
|
-
source: ctx.data.classificationSource ?? "agent"
|
|
5135
|
-
});
|
|
5136
|
-
ctx.data.classification = classification;
|
|
5137
|
-
ctx.data.classificationReason = reason ?? "";
|
|
5138
|
-
};
|
|
5139
|
-
function parseClassification(prSummary) {
|
|
5140
|
-
if (!prSummary) return null;
|
|
5141
|
-
const classMatch = prSummary.match(/classification:\s*(feature|bug|spec|chore)\b/i);
|
|
5142
|
-
if (!classMatch) return null;
|
|
5143
|
-
const classification = classMatch[1].toLowerCase();
|
|
5144
|
-
const reasonMatch = prSummary.match(/reason:\s*(.+)$/im);
|
|
5145
|
-
const reason = reasonMatch ? reasonMatch[1].trim() : "";
|
|
5146
|
-
return { classification, reason };
|
|
5147
|
-
}
|
|
5148
|
-
function tryAuditComment(issueNumber, body, cwd) {
|
|
5149
|
-
try {
|
|
5150
|
-
execFileSync16("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
5151
|
-
cwd,
|
|
5152
|
-
timeout: API_TIMEOUT_MS7,
|
|
5153
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
5154
|
-
});
|
|
5155
|
-
} catch {
|
|
5156
|
-
}
|
|
5157
|
-
}
|
|
5158
|
-
function makeAction3(type, payload) {
|
|
5159
|
-
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5160
|
-
}
|
|
5161
|
-
function failedAction(reason) {
|
|
5162
|
-
return { type: "CLASSIFY_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5163
|
-
}
|
|
5164
|
-
|
|
5165
5110
|
// src/scripts/postIssueComment.ts
|
|
5166
5111
|
var postIssueComment2 = async (ctx) => {
|
|
5167
5112
|
if (ctx.skipAgent && ctx.output.exitCode !== void 0) return;
|
|
@@ -5330,6 +5275,70 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
|
|
|
5330
5275
|
);
|
|
5331
5276
|
};
|
|
5332
5277
|
|
|
5278
|
+
// src/scripts/recordClassification.ts
|
|
5279
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
5280
|
+
var API_TIMEOUT_MS8 = 3e4;
|
|
5281
|
+
var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
5282
|
+
var recordClassification = async (ctx) => {
|
|
5283
|
+
const issueNumber = ctx.args.issue;
|
|
5284
|
+
if (!issueNumber) return;
|
|
5285
|
+
const presetClassification = ctx.data.classification;
|
|
5286
|
+
let classification = null;
|
|
5287
|
+
let reason = null;
|
|
5288
|
+
if (presetClassification && VALID_CLASSES3.has(presetClassification)) {
|
|
5289
|
+
classification = presetClassification;
|
|
5290
|
+
reason = ctx.data.classificationReason ?? "label-based match";
|
|
5291
|
+
} else {
|
|
5292
|
+
const parsed = parseClassification(ctx.data.prSummary ?? "");
|
|
5293
|
+
classification = parsed?.classification ?? null;
|
|
5294
|
+
reason = parsed?.reason ?? null;
|
|
5295
|
+
}
|
|
5296
|
+
if (!classification) {
|
|
5297
|
+
ctx.data.action = failedAction3("classification missing or invalid");
|
|
5298
|
+
tryAuditComment(
|
|
5299
|
+
issueNumber,
|
|
5300
|
+
"\u26A0\uFE0F kody classifier could not decide \u2014 please re-run with an explicit `@kody <type>`.",
|
|
5301
|
+
ctx.cwd
|
|
5302
|
+
);
|
|
5303
|
+
ctx.output.exitCode = 1;
|
|
5304
|
+
ctx.output.reason = "classify: no decision";
|
|
5305
|
+
return;
|
|
5306
|
+
}
|
|
5307
|
+
tryAuditComment(issueNumber, `\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`, ctx.cwd);
|
|
5308
|
+
ctx.data.action = makeAction3(`CLASSIFIED_AS_${classification.toUpperCase()}`, {
|
|
5309
|
+
classification,
|
|
5310
|
+
reason: reason ?? "",
|
|
5311
|
+
source: ctx.data.classificationSource ?? "agent"
|
|
5312
|
+
});
|
|
5313
|
+
ctx.data.classification = classification;
|
|
5314
|
+
ctx.data.classificationReason = reason ?? "";
|
|
5315
|
+
};
|
|
5316
|
+
function parseClassification(prSummary) {
|
|
5317
|
+
if (!prSummary) return null;
|
|
5318
|
+
const classMatch = prSummary.match(/classification:\s*(feature|bug|spec|chore)\b/i);
|
|
5319
|
+
if (!classMatch) return null;
|
|
5320
|
+
const classification = classMatch[1].toLowerCase();
|
|
5321
|
+
const reasonMatch = prSummary.match(/reason:\s*(.+)$/im);
|
|
5322
|
+
const reason = reasonMatch ? reasonMatch[1].trim() : "";
|
|
5323
|
+
return { classification, reason };
|
|
5324
|
+
}
|
|
5325
|
+
function tryAuditComment(issueNumber, body, cwd) {
|
|
5326
|
+
try {
|
|
5327
|
+
execFileSync17("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
5328
|
+
cwd,
|
|
5329
|
+
timeout: API_TIMEOUT_MS8,
|
|
5330
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5331
|
+
});
|
|
5332
|
+
} catch {
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
function makeAction3(type, payload) {
|
|
5336
|
+
return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5337
|
+
}
|
|
5338
|
+
function failedAction3(reason) {
|
|
5339
|
+
return { type: "CLASSIFY_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5340
|
+
}
|
|
5341
|
+
|
|
5333
5342
|
// src/scripts/recordOutcome.ts
|
|
5334
5343
|
var recordOutcome = async (ctx, profile) => {
|
|
5335
5344
|
const seg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
@@ -5365,12 +5374,12 @@ function fail(ctx, profile, reason) {
|
|
|
5365
5374
|
ctx.data.agentDone = false;
|
|
5366
5375
|
ctx.data.agentFailureReason = reason;
|
|
5367
5376
|
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
5368
|
-
const
|
|
5377
|
+
const failedAction4 = {
|
|
5369
5378
|
type: `${modeSeg}_FAILED`,
|
|
5370
5379
|
payload: { reason },
|
|
5371
5380
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5372
5381
|
};
|
|
5373
|
-
ctx.data.action =
|
|
5382
|
+
ctx.data.action = failedAction4;
|
|
5374
5383
|
}
|
|
5375
5384
|
function countActionItems(block) {
|
|
5376
5385
|
if (!block.trim()) return 0;
|
|
@@ -5411,12 +5420,12 @@ function fail2(ctx, profile, reason) {
|
|
|
5411
5420
|
ctx.data.agentDone = false;
|
|
5412
5421
|
ctx.data.agentFailureReason = reason;
|
|
5413
5422
|
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
5414
|
-
const
|
|
5423
|
+
const failedAction4 = {
|
|
5415
5424
|
type: `${modeSeg}_FAILED`,
|
|
5416
5425
|
payload: { reason },
|
|
5417
5426
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5418
5427
|
};
|
|
5419
|
-
ctx.data.action =
|
|
5428
|
+
ctx.data.action = failedAction4;
|
|
5420
5429
|
}
|
|
5421
5430
|
|
|
5422
5431
|
// src/scripts/resolveArtifacts.ts
|
|
@@ -5443,7 +5452,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
5443
5452
|
};
|
|
5444
5453
|
|
|
5445
5454
|
// src/scripts/resolveFlow.ts
|
|
5446
|
-
import { execFileSync as
|
|
5455
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
5447
5456
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
5448
5457
|
var resolveFlow = async (ctx) => {
|
|
5449
5458
|
const prNumber = ctx.args.pr;
|
|
@@ -5513,7 +5522,7 @@ function buildPreferBlock(prefer, baseBranch) {
|
|
|
5513
5522
|
}
|
|
5514
5523
|
function getConflictedFiles(cwd) {
|
|
5515
5524
|
try {
|
|
5516
|
-
const out =
|
|
5525
|
+
const out = execFileSync18("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
5517
5526
|
encoding: "utf-8",
|
|
5518
5527
|
cwd,
|
|
5519
5528
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -5528,7 +5537,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
5528
5537
|
let total = 0;
|
|
5529
5538
|
for (const f of files) {
|
|
5530
5539
|
try {
|
|
5531
|
-
const content =
|
|
5540
|
+
const content = execFileSync18("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
5532
5541
|
const snippet = `### ${f}
|
|
5533
5542
|
|
|
5534
5543
|
\`\`\`
|
|
@@ -5702,11 +5711,11 @@ var skipAgent = async (ctx) => {
|
|
|
5702
5711
|
};
|
|
5703
5712
|
|
|
5704
5713
|
// src/scripts/stageMergeConflicts.ts
|
|
5705
|
-
import { execFileSync as
|
|
5714
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
5706
5715
|
var stageMergeConflicts = async (ctx) => {
|
|
5707
5716
|
if (ctx.data.agentDone === false) return;
|
|
5708
5717
|
try {
|
|
5709
|
-
|
|
5718
|
+
execFileSync19("git", ["add", "-A"], {
|
|
5710
5719
|
cwd: ctx.cwd,
|
|
5711
5720
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
5712
5721
|
stdio: "pipe"
|
|
@@ -5716,8 +5725,8 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
5716
5725
|
};
|
|
5717
5726
|
|
|
5718
5727
|
// src/scripts/startFlow.ts
|
|
5719
|
-
import { execFileSync as
|
|
5720
|
-
var
|
|
5728
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
5729
|
+
var API_TIMEOUT_MS9 = 3e4;
|
|
5721
5730
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
5722
5731
|
const entry = args?.entry;
|
|
5723
5732
|
if (!entry) {
|
|
@@ -5750,8 +5759,8 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
5750
5759
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
5751
5760
|
const body = `@kody ${next}`;
|
|
5752
5761
|
try {
|
|
5753
|
-
|
|
5754
|
-
timeout:
|
|
5762
|
+
execFileSync20("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
5763
|
+
timeout: API_TIMEOUT_MS9,
|
|
5755
5764
|
cwd,
|
|
5756
5765
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5757
5766
|
});
|
|
@@ -5764,7 +5773,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
5764
5773
|
}
|
|
5765
5774
|
|
|
5766
5775
|
// src/scripts/syncFlow.ts
|
|
5767
|
-
import { execFileSync as
|
|
5776
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
5768
5777
|
var syncFlow = async (ctx, _profile, args) => {
|
|
5769
5778
|
const announceOnSuccess = Boolean(args?.announceOnSuccess);
|
|
5770
5779
|
const prNumber = ctx.args.pr;
|
|
@@ -5836,7 +5845,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
5836
5845
|
}
|
|
5837
5846
|
function revParseHead(cwd) {
|
|
5838
5847
|
try {
|
|
5839
|
-
return
|
|
5848
|
+
return execFileSync21("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
5840
5849
|
} catch {
|
|
5841
5850
|
return "";
|
|
5842
5851
|
}
|
|
@@ -5844,9 +5853,9 @@ function revParseHead(cwd) {
|
|
|
5844
5853
|
function pushBranch(branch, cwd) {
|
|
5845
5854
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
5846
5855
|
try {
|
|
5847
|
-
|
|
5856
|
+
execFileSync21("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
5848
5857
|
} catch {
|
|
5849
|
-
|
|
5858
|
+
execFileSync21("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
5850
5859
|
cwd,
|
|
5851
5860
|
env,
|
|
5852
5861
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5957,8 +5966,8 @@ var verify = async (ctx) => {
|
|
|
5957
5966
|
};
|
|
5958
5967
|
|
|
5959
5968
|
// src/scripts/waitForCi.ts
|
|
5960
|
-
import { execFileSync as
|
|
5961
|
-
var
|
|
5969
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
5970
|
+
var API_TIMEOUT_MS10 = 3e4;
|
|
5962
5971
|
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
5963
5972
|
const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
|
|
5964
5973
|
const pollSeconds = numArg(args, "pollSeconds", 30);
|
|
@@ -6035,9 +6044,9 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
6035
6044
|
};
|
|
6036
6045
|
function fetchChecks(prNumber, cwd) {
|
|
6037
6046
|
try {
|
|
6038
|
-
const raw =
|
|
6047
|
+
const raw = execFileSync22("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
|
|
6039
6048
|
encoding: "utf-8",
|
|
6040
|
-
timeout:
|
|
6049
|
+
timeout: API_TIMEOUT_MS10,
|
|
6041
6050
|
cwd,
|
|
6042
6051
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6043
6052
|
});
|
|
@@ -6299,7 +6308,8 @@ var postflightScripts = {
|
|
|
6299
6308
|
finishFlow,
|
|
6300
6309
|
advanceFlow,
|
|
6301
6310
|
persistFlowState,
|
|
6302
|
-
|
|
6311
|
+
recordClassification,
|
|
6312
|
+
dispatchClassified,
|
|
6303
6313
|
notifyTerminal,
|
|
6304
6314
|
recordOutcome,
|
|
6305
6315
|
mergeReleasePr,
|
|
@@ -6311,7 +6321,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
6311
6321
|
]);
|
|
6312
6322
|
|
|
6313
6323
|
// src/tools.ts
|
|
6314
|
-
import { execFileSync as
|
|
6324
|
+
import { execFileSync as execFileSync23 } from "child_process";
|
|
6315
6325
|
function verifyCliTools(tools, cwd) {
|
|
6316
6326
|
const out = [];
|
|
6317
6327
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -6344,7 +6354,7 @@ function verifyOne(tool, cwd) {
|
|
|
6344
6354
|
}
|
|
6345
6355
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
6346
6356
|
try {
|
|
6347
|
-
|
|
6357
|
+
execFileSync23("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
6348
6358
|
return true;
|
|
6349
6359
|
} catch {
|
|
6350
6360
|
return false;
|
|
@@ -6753,7 +6763,7 @@ function detectPackageManager2(cwd) {
|
|
|
6753
6763
|
}
|
|
6754
6764
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
6755
6765
|
try {
|
|
6756
|
-
|
|
6766
|
+
execFileSync24(cmd, args, {
|
|
6757
6767
|
cwd,
|
|
6758
6768
|
stdio: stream ? "inherit" : "pipe",
|
|
6759
6769
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -6766,7 +6776,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
6766
6776
|
}
|
|
6767
6777
|
function isOnPath(bin) {
|
|
6768
6778
|
try {
|
|
6769
|
-
|
|
6779
|
+
execFileSync24("which", [bin], { stdio: "pipe" });
|
|
6770
6780
|
return true;
|
|
6771
6781
|
} catch {
|
|
6772
6782
|
return false;
|
|
@@ -6800,7 +6810,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
6800
6810
|
} catch {
|
|
6801
6811
|
}
|
|
6802
6812
|
try {
|
|
6803
|
-
|
|
6813
|
+
execFileSync24("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
6804
6814
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
6805
6815
|
return 0;
|
|
6806
6816
|
} catch {
|
|
@@ -6810,16 +6820,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
6810
6820
|
}
|
|
6811
6821
|
function configureGitIdentity(cwd) {
|
|
6812
6822
|
try {
|
|
6813
|
-
const name =
|
|
6823
|
+
const name = execFileSync24("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
6814
6824
|
if (name) return;
|
|
6815
6825
|
} catch {
|
|
6816
6826
|
}
|
|
6817
6827
|
try {
|
|
6818
|
-
|
|
6828
|
+
execFileSync24("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
6819
6829
|
} catch {
|
|
6820
6830
|
}
|
|
6821
6831
|
try {
|
|
6822
|
-
|
|
6832
|
+
execFileSync24("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
6823
6833
|
cwd,
|
|
6824
6834
|
stdio: "pipe"
|
|
6825
6835
|
});
|
|
@@ -7090,9 +7100,9 @@ function commitChatFiles(cwd, sessionId, verbose) {
|
|
|
7090
7100
|
if (paths.length === 0) return;
|
|
7091
7101
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
7092
7102
|
try {
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7103
|
+
execFileSync25("git", ["add", ...paths], opts);
|
|
7104
|
+
execFileSync25("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
7105
|
+
execFileSync25("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
7096
7106
|
} catch (err) {
|
|
7097
7107
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7098
7108
|
process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
|
|
@@ -47,9 +47,10 @@
|
|
|
47
47
|
],
|
|
48
48
|
"postflight": [
|
|
49
49
|
{ "script": "parseAgentResult" },
|
|
50
|
-
{ "script": "
|
|
50
|
+
{ "script": "recordClassification" },
|
|
51
51
|
{ "script": "writeRunSummary" },
|
|
52
|
-
{ "script": "saveTaskState" }
|
|
52
|
+
{ "script": "saveTaskState" },
|
|
53
|
+
{ "script": "dispatchClassified" }
|
|
53
54
|
]
|
|
54
55
|
},
|
|
55
56
|
"output": {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -26,10 +26,11 @@ If a prior-art block is present above, READ THE DIFFS — those are failed or su
|
|
|
26
26
|
- Read the **full** contents of every file you intend to change (not just a grep hit).
|
|
27
27
|
- Read the tests for each of those files, if tests exist for the module.
|
|
28
28
|
- Read at least one sibling module that already implements the same pattern you're about to follow — your edits should mirror an existing convention unless you can name why a new one is needed.
|
|
29
|
+
- If your change requires writing or modifying a test, also check for repo-level testing guidance: `tests/README.md`, `TESTING.md`, or a "Testing"/"Tests" section in `AGENTS.md`/`CLAUDE.md`. If one exists, treat its patterns (auth setup, fixture creation, what NOT to do) as authoritative — they override anything you might infer from grepping individual files.
|
|
29
30
|
- If a file you need to read does not exist, say so explicitly in your plan (step 2). Do not guess at its contents.
|
|
30
31
|
2. **Plan** — before any Edit/Write, output a short plan (5–10 lines): what files you'll change, the approach, what could go wrong. No fluff.
|
|
31
32
|
3. **Build** — Edit/Write to implement the change. Stay within the plan; if you discover the plan was wrong, briefly say so and adjust.
|
|
32
|
-
4. **Test** — for every new module you added and every behavior you changed, write or update tests. If the plan above contains a "Test plan" section, treat it as authoritative: every item there must produce a corresponding test.
|
|
33
|
+
4. **Test** — for every new module you added and every behavior you changed, write or update tests. If the plan above contains a "Test plan" section, treat it as authoritative: every item there must produce a corresponding test. Before writing a test, open the newest existing file in the same test directory (`tests/int/`, `tests/unit/`, `tests/e2e/`, or sibling `*.test.ts`) and copy its imports, setup hooks, and auth pattern **verbatim**. Do NOT introduce a new test infrastructure (own testcontainers, `fetch` against relative URLs, alternate auth headers) when a working pattern already exists in that directory — divergence from the established pattern is a hard failure even if the test passes locally. Cover at least one happy path and one failure path per change. Skipping tests is a hard failure. A change may only be declared untestable if you can name the specific blocker (e.g., "no fake exists for the X SDK and stubbing it would mock the entire call surface"); vague "this is just config" claims are rejected. Untestable changes go in `PLAN_DEVIATIONS:` with the named blocker.
|
|
33
34
|
5. **Verify** — run each quality command with Bash. On failure, fix the root cause and re-run. When reporting that a command passed, you MUST have just run it and seen exit code 0 in this session — do not paraphrase prior output.
|
|
34
35
|
6. Your FINAL message must use this exact format (or a single `FAILED: <reason>` line on failure). The `PLAN_DEVIATIONS:` block is REQUIRED whenever a plan was provided.
|
|
35
36
|
|
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.48",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,18 @@
|
|
|
12
12
|
"templates",
|
|
13
13
|
"kody.config.schema.json"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"kody": "tsx bin/kody.ts",
|
|
17
|
+
"build": "tsup && node scripts/copy-assets.cjs",
|
|
18
|
+
"test": "vitest run tests/unit tests/int --no-coverage",
|
|
19
|
+
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
20
|
+
"test:all": "vitest run tests --no-coverage",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "biome check",
|
|
23
|
+
"lint:fix": "biome check --write",
|
|
24
|
+
"format": "biome format --write",
|
|
25
|
+
"prepublishOnly": "pnpm build"
|
|
26
|
+
},
|
|
15
27
|
"dependencies": {
|
|
16
28
|
"@anthropic-ai/claude-agent-sdk": "0.2.119"
|
|
17
29
|
},
|
|
@@ -31,16 +43,5 @@
|
|
|
31
43
|
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
32
44
|
},
|
|
33
45
|
"homepage": "https://github.com/aharonyaircohen/kody-engine",
|
|
34
|
-
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
35
|
-
|
|
36
|
-
"kody": "tsx bin/kody.ts",
|
|
37
|
-
"build": "tsup && node scripts/copy-assets.cjs",
|
|
38
|
-
"test": "vitest run tests/unit tests/int --no-coverage",
|
|
39
|
-
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
40
|
-
"test:all": "vitest run tests --no-coverage",
|
|
41
|
-
"typecheck": "tsc --noEmit",
|
|
42
|
-
"lint": "biome check",
|
|
43
|
-
"lint:fix": "biome check --write",
|
|
44
|
-
"format": "biome format --write"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
46
|
+
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
47
|
+
}
|
package/templates/kody.yml
CHANGED
|
@@ -48,9 +48,13 @@ on:
|
|
|
48
48
|
pull_request:
|
|
49
49
|
types: [closed]
|
|
50
50
|
schedule:
|
|
51
|
-
# Wakes every
|
|
52
|
-
# (mission-scheduler, memorize, watch-stale-prs, …) match this
|
|
53
|
-
|
|
51
|
+
# Wakes every 30 minutes; kody fans out to whichever scheduled executables
|
|
52
|
+
# (mission-scheduler, memorize, watch-stale-prs, …) match this tick.
|
|
53
|
+
#
|
|
54
|
+
# `memorize` writes to `.kody/vault/` and opens a daily PR. If your
|
|
55
|
+
# `.gitignore` ignores `.kody/*`, add `!.kody/vault/` and `!.kody/vault/**`
|
|
56
|
+
# so memorize's pages are tracked.
|
|
57
|
+
- cron: "*/30 * * * *"
|
|
54
58
|
|
|
55
59
|
jobs:
|
|
56
60
|
run:
|