@kody-ade/kody-engine-lite 0.1.60 → 0.1.62
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 +12 -14
- package/dist/agent-runner.d.ts +4 -0
- package/dist/agent-runner.js +122 -0
- package/dist/bin/cli.js +93 -80
- package/dist/ci/parse-inputs.d.ts +6 -0
- package/dist/ci/parse-inputs.js +76 -0
- package/dist/ci/parse-safety.d.ts +6 -0
- package/dist/ci/parse-safety.js +22 -0
- package/dist/cli/args.d.ts +13 -0
- package/dist/cli/args.js +42 -0
- package/dist/cli/litellm.d.ts +2 -0
- package/dist/cli/litellm.js +85 -0
- package/dist/cli/task-resolution.d.ts +2 -0
- package/dist/cli/task-resolution.js +41 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +72 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.js +83 -0
- package/dist/definitions.d.ts +3 -0
- package/dist/definitions.js +59 -0
- package/dist/entry.d.ts +1 -0
- package/dist/entry.js +236 -0
- package/dist/git-utils.d.ts +13 -0
- package/dist/git-utils.js +174 -0
- package/dist/github-api.d.ts +14 -0
- package/dist/github-api.js +114 -0
- package/dist/kody-utils.d.ts +1 -0
- package/dist/kody-utils.js +9 -0
- package/dist/learning/auto-learn.d.ts +2 -0
- package/dist/learning/auto-learn.js +169 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +51 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.js +20 -0
- package/dist/observer.d.ts +9 -0
- package/dist/observer.js +80 -0
- package/dist/pipeline/complexity.d.ts +3 -0
- package/dist/pipeline/complexity.js +12 -0
- package/dist/pipeline/executor-registry.d.ts +3 -0
- package/dist/pipeline/executor-registry.js +20 -0
- package/dist/pipeline/hooks.d.ts +17 -0
- package/dist/pipeline/hooks.js +110 -0
- package/dist/pipeline/questions.d.ts +2 -0
- package/dist/pipeline/questions.js +44 -0
- package/dist/pipeline/runner-selection.d.ts +2 -0
- package/dist/pipeline/runner-selection.js +13 -0
- package/dist/pipeline/state.d.ts +4 -0
- package/dist/pipeline/state.js +37 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.js +213 -0
- package/dist/preflight.d.ts +1 -0
- package/dist/preflight.js +69 -0
- package/dist/retrospective.d.ts +26 -0
- package/dist/retrospective.js +211 -0
- package/dist/stages/agent.d.ts +2 -0
- package/dist/stages/agent.js +94 -0
- package/dist/stages/gate.d.ts +2 -0
- package/dist/stages/gate.js +32 -0
- package/dist/stages/review.d.ts +2 -0
- package/dist/stages/review.js +32 -0
- package/dist/stages/ship.d.ts +3 -0
- package/dist/stages/ship.js +154 -0
- package/dist/stages/verify.d.ts +2 -0
- package/dist/stages/verify.js +94 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +1 -0
- package/dist/validators.d.ts +8 -0
- package/dist/validators.js +42 -0
- package/dist/verify-runner.d.ts +11 -0
- package/dist/verify-runner.js +110 -0
- package/kody.config.schema.json +7 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,23 +138,19 @@ Comment on any GitHub issue:
|
|
|
138
138
|
|
|
139
139
|
### Switch to a different model (optional)
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```yaml
|
|
144
|
-
# litellm-config.yaml
|
|
145
|
-
model_list:
|
|
146
|
-
- model_name: claude-haiku-4-5-20251001
|
|
147
|
-
litellm_params:
|
|
148
|
-
model: minimax/MiniMax-M2.7-highspeed
|
|
149
|
-
api_key: os.environ/MINIMAX_API_KEY
|
|
150
|
-
```
|
|
141
|
+
Set the `provider` field in `kody.config.json` — Kody auto-generates the LiteLLM config, starts the proxy, and routes all stages through your provider:
|
|
151
142
|
|
|
152
143
|
```json
|
|
153
|
-
// kody.config.json —
|
|
154
|
-
{ "agent": { "
|
|
144
|
+
// kody.config.json — use MiniMax (or any LLM)
|
|
145
|
+
{ "agent": { "provider": "minimax" } }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Add the provider's API key to `.env`:
|
|
149
|
+
```
|
|
150
|
+
MINIMAX_API_KEY=your-key-here
|
|
155
151
|
```
|
|
156
152
|
|
|
157
|
-
Kody auto-starts the proxy and loads API keys from `.env`. [Full LiteLLM guide →](docs/LITELLM.md)
|
|
153
|
+
That's it. Kody auto-starts the LiteLLM proxy and loads API keys from `.env`. For advanced routing (different models per tier, custom config), add a `litellm-config.yaml`. [Full LiteLLM guide →](docs/LITELLM.md)
|
|
158
154
|
|
|
159
155
|
## Commands
|
|
160
156
|
|
|
@@ -167,16 +163,18 @@ Kody auto-starts the proxy and loads API keys from `.env`. [Full LiteLLM guide
|
|
|
167
163
|
| `@kody fix` | Re-run from build stage. Write feedback in the comment body — it gets injected into the build prompt |
|
|
168
164
|
| `@kody rerun` | Resume from the failed or paused stage |
|
|
169
165
|
| `@kody rerun --from <stage>` | Resume from a specific stage |
|
|
166
|
+
| `@kody bootstrap` | Regenerate project memory and step files |
|
|
170
167
|
|
|
171
168
|
### CLI
|
|
172
169
|
|
|
173
170
|
```bash
|
|
171
|
+
kody-engine-lite init [--force] # Setup repo: workflow, config, memory, step files
|
|
172
|
+
kody-engine-lite bootstrap # Regenerate memory + step files (runs in GH Actions)
|
|
174
173
|
kody-engine-lite run --issue-number 42 --local --cwd ./project
|
|
175
174
|
kody-engine-lite run --task "Add retry utility" --local
|
|
176
175
|
kody-engine-lite fix --issue-number 42 --feedback "Use middleware pattern"
|
|
177
176
|
kody-engine-lite rerun --issue-number 42 --from verify
|
|
178
177
|
kody-engine-lite status --task-id 42-260327-102254
|
|
179
|
-
kody-engine-lite init [--force]
|
|
180
178
|
```
|
|
181
179
|
|
|
182
180
|
## Key Features
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { spawn, execFileSync } from "child_process";
|
|
2
|
+
const SIGKILL_GRACE_MS = 5000;
|
|
3
|
+
const STDERR_TAIL_CHARS = 500;
|
|
4
|
+
function writeStdin(child, prompt) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
if (!child.stdin) {
|
|
7
|
+
resolve();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
child.stdin.write(prompt, (err) => {
|
|
11
|
+
if (err)
|
|
12
|
+
reject(err);
|
|
13
|
+
else {
|
|
14
|
+
child.stdin.end();
|
|
15
|
+
resolve();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function waitForProcess(child, timeout) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const stdoutChunks = [];
|
|
23
|
+
const stderrChunks = [];
|
|
24
|
+
child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
25
|
+
child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
|
|
26
|
+
const timer = setTimeout(() => {
|
|
27
|
+
child.kill("SIGTERM");
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
if (!child.killed)
|
|
30
|
+
child.kill("SIGKILL");
|
|
31
|
+
}, SIGKILL_GRACE_MS);
|
|
32
|
+
}, timeout);
|
|
33
|
+
child.on("exit", (code) => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
resolve({
|
|
36
|
+
code,
|
|
37
|
+
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
38
|
+
stderr: Buffer.concat(stderrChunks).toString(),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
child.on("error", (err) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
resolve({ code: -1, stdout: "", stderr: err.message });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function runSubprocess(command, args, prompt, timeout, options) {
|
|
48
|
+
const child = spawn(command, args, {
|
|
49
|
+
cwd: options?.cwd ?? process.cwd(),
|
|
50
|
+
env: {
|
|
51
|
+
...process.env,
|
|
52
|
+
SKIP_BUILD: "1",
|
|
53
|
+
SKIP_HOOKS: "1",
|
|
54
|
+
...options?.env,
|
|
55
|
+
},
|
|
56
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
await writeStdin(child, prompt);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
return {
|
|
63
|
+
outcome: "failed",
|
|
64
|
+
error: `Failed to send prompt: ${err instanceof Error ? err.message : String(err)}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const { code, stdout, stderr } = await waitForProcess(child, timeout);
|
|
68
|
+
if (code === 0) {
|
|
69
|
+
return { outcome: "completed", output: stdout };
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
outcome: code === null ? "timed_out" : "failed",
|
|
73
|
+
error: `Exit code ${code}\n${stderr.slice(-STDERR_TAIL_CHARS)}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function checkCommand(command, args) {
|
|
77
|
+
try {
|
|
78
|
+
execFileSync(command, args, { timeout: 10_000, stdio: "pipe" });
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ─── Claude Code Runner ──────────────────────────────────────────────────────
|
|
86
|
+
export function createClaudeCodeRunner() {
|
|
87
|
+
return {
|
|
88
|
+
async run(_stageName, prompt, model, timeout, _taskDir, options) {
|
|
89
|
+
return runSubprocess("claude", [
|
|
90
|
+
"--print",
|
|
91
|
+
"--model", model,
|
|
92
|
+
"--dangerously-skip-permissions",
|
|
93
|
+
"--allowedTools", "Bash,Edit,Read,Write,Glob,Grep",
|
|
94
|
+
], prompt, timeout, options);
|
|
95
|
+
},
|
|
96
|
+
async healthCheck() {
|
|
97
|
+
return checkCommand("claude", ["--version"]);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// ─── Runner Factory ──────────────────────────────────────────────────────────
|
|
102
|
+
const RUNNER_FACTORIES = {
|
|
103
|
+
"claude-code": createClaudeCodeRunner,
|
|
104
|
+
};
|
|
105
|
+
export function createRunners(config) {
|
|
106
|
+
// New multi-runner config
|
|
107
|
+
if (config.agent.runners && Object.keys(config.agent.runners).length > 0) {
|
|
108
|
+
const runners = {};
|
|
109
|
+
for (const [name, runnerConfig] of Object.entries(config.agent.runners)) {
|
|
110
|
+
const factory = RUNNER_FACTORIES[runnerConfig.type];
|
|
111
|
+
if (factory) {
|
|
112
|
+
runners[name] = factory();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return runners;
|
|
116
|
+
}
|
|
117
|
+
// Legacy single-runner fallback
|
|
118
|
+
const runnerType = config.agent.runner ?? "claude-code";
|
|
119
|
+
const factory = RUNNER_FACTORIES[runnerType];
|
|
120
|
+
const defaultName = config.agent.defaultRunner ?? "claude";
|
|
121
|
+
return { [defaultName]: factory ? factory() : createClaudeCodeRunner() };
|
|
122
|
+
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -114,17 +114,15 @@ function createRunners(config) {
|
|
|
114
114
|
if (config.agent.runners && Object.keys(config.agent.runners).length > 0) {
|
|
115
115
|
const runners = {};
|
|
116
116
|
for (const [name, runnerConfig] of Object.entries(config.agent.runners)) {
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
runners[name] =
|
|
117
|
+
const factory = RUNNER_FACTORIES[runnerConfig.type];
|
|
118
|
+
if (factory) {
|
|
119
|
+
runners[name] = factory();
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
return runners;
|
|
123
123
|
}
|
|
124
|
-
const runnerType = config.agent.runner ?? "claude-code";
|
|
125
|
-
const factory = RUNNER_FACTORIES[runnerType];
|
|
126
124
|
const defaultName = config.agent.defaultRunner ?? "claude";
|
|
127
|
-
return { [defaultName]:
|
|
125
|
+
return { [defaultName]: createClaudeCodeRunner() };
|
|
128
126
|
}
|
|
129
127
|
var SIGKILL_GRACE_MS, STDERR_TAIL_CHARS, RUNNER_FACTORIES;
|
|
130
128
|
var init_agent_runner = __esm({
|
|
@@ -253,12 +251,10 @@ var init_logger = __esm({
|
|
|
253
251
|
import * as fs from "fs";
|
|
254
252
|
import * as path from "path";
|
|
255
253
|
function needsLitellmProxy(config) {
|
|
256
|
-
|
|
257
|
-
if (config.agent.provider && config.agent.provider !== "anthropic") return true;
|
|
258
|
-
return false;
|
|
254
|
+
return !!(config.agent.provider && config.agent.provider !== "anthropic");
|
|
259
255
|
}
|
|
260
|
-
function getLitellmUrl(
|
|
261
|
-
return
|
|
256
|
+
function getLitellmUrl() {
|
|
257
|
+
return LITELLM_DEFAULT_URL;
|
|
262
258
|
}
|
|
263
259
|
function providerApiKeyEnvVar(provider) {
|
|
264
260
|
return `${provider.toUpperCase()}_API_KEY`;
|
|
@@ -277,7 +273,6 @@ function getProjectConfig() {
|
|
|
277
273
|
quality: { ...DEFAULT_CONFIG.quality, ...raw.quality },
|
|
278
274
|
git: { ...DEFAULT_CONFIG.git, ...raw.git },
|
|
279
275
|
github: { ...DEFAULT_CONFIG.github, ...raw.github },
|
|
280
|
-
paths: { ...DEFAULT_CONFIG.paths, ...raw.paths },
|
|
281
276
|
agent: { ...DEFAULT_CONFIG.agent, ...raw.agent },
|
|
282
277
|
contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers
|
|
283
278
|
};
|
|
@@ -300,7 +295,6 @@ var init_config = __esm({
|
|
|
300
295
|
typecheck: "pnpm -s tsc --noEmit",
|
|
301
296
|
lint: "pnpm -s lint",
|
|
302
297
|
lintFix: "pnpm lint:fix",
|
|
303
|
-
format: "pnpm -s format:check",
|
|
304
298
|
formatFix: "pnpm format:fix",
|
|
305
299
|
testUnit: "pnpm -s test"
|
|
306
300
|
},
|
|
@@ -311,12 +305,7 @@ var init_config = __esm({
|
|
|
311
305
|
owner: "",
|
|
312
306
|
repo: ""
|
|
313
307
|
},
|
|
314
|
-
paths: {
|
|
315
|
-
taskDir: ".kody/tasks"
|
|
316
|
-
},
|
|
317
308
|
agent: {
|
|
318
|
-
runner: "claude-code",
|
|
319
|
-
defaultRunner: "claude",
|
|
320
309
|
modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
|
|
321
310
|
},
|
|
322
311
|
contextTiers: {
|
|
@@ -1238,9 +1227,6 @@ ${prompt}` : prompt;
|
|
|
1238
1227
|
}
|
|
1239
1228
|
function resolveModel(modelTier, stageName) {
|
|
1240
1229
|
const config = getProjectConfig();
|
|
1241
|
-
if (config.agent.usePerStageRouting && stageName) {
|
|
1242
|
-
return stageName;
|
|
1243
|
-
}
|
|
1244
1230
|
if (config.agent.provider && config.agent.provider !== "anthropic") {
|
|
1245
1231
|
return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
|
|
1246
1232
|
}
|
|
@@ -1372,7 +1358,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
1372
1358
|
logger.info(` runner=${runnerName} model=${model} timeout=${def.timeout / 1e3}s`);
|
|
1373
1359
|
const extraEnv = {};
|
|
1374
1360
|
if (needsLitellmProxy(config)) {
|
|
1375
|
-
extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl(
|
|
1361
|
+
extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
|
|
1376
1362
|
}
|
|
1377
1363
|
const sessions = ctx.sessions ?? {};
|
|
1378
1364
|
const sessionInfo = getSessionInfo(def.name, sessions);
|
|
@@ -1751,8 +1737,8 @@ async function executeVerifyWithAutofix(ctx, def) {
|
|
|
1751
1737
|
const defaultRunner = getRunnerForStage(ctx, "taskify");
|
|
1752
1738
|
const diagConfig = getProjectConfig();
|
|
1753
1739
|
const diagEnv = {};
|
|
1754
|
-
if (diagConfig
|
|
1755
|
-
diagEnv.ANTHROPIC_BASE_URL =
|
|
1740
|
+
if (needsLitellmProxy(diagConfig)) {
|
|
1741
|
+
diagEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
|
|
1756
1742
|
}
|
|
1757
1743
|
const diagnosis = await diagnoseFailure(
|
|
1758
1744
|
"verify",
|
|
@@ -2047,7 +2033,14 @@ Updated existing PR: ${existingPr.url}
|
|
|
2047
2033
|
PR #${existingPr.number}
|
|
2048
2034
|
`);
|
|
2049
2035
|
} else {
|
|
2050
|
-
|
|
2036
|
+
let pr = createPR(head, base, title, body);
|
|
2037
|
+
if (!pr) {
|
|
2038
|
+
const recovered = getPRForBranch(head);
|
|
2039
|
+
if (recovered) {
|
|
2040
|
+
logger.info(` PR recovered after create error: ${recovered.url}`);
|
|
2041
|
+
pr = recovered;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2051
2044
|
if (pr) {
|
|
2052
2045
|
if (ctx.input.issueNumber && !ctx.input.local) {
|
|
2053
2046
|
try {
|
|
@@ -2537,8 +2530,8 @@ ${previousText}
|
|
|
2537
2530
|
const model = resolveModel("cheap");
|
|
2538
2531
|
const config = getProjectConfig();
|
|
2539
2532
|
const extraEnv = {};
|
|
2540
|
-
if (config
|
|
2541
|
-
extraEnv.ANTHROPIC_BASE_URL =
|
|
2533
|
+
if (needsLitellmProxy(config)) {
|
|
2534
|
+
extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
|
|
2542
2535
|
}
|
|
2543
2536
|
const result = await runner.run("retrospective", prompt, model, 3e4, "", {
|
|
2544
2537
|
cwd: ctx.projectDir,
|
|
@@ -3274,7 +3267,7 @@ import * as fs21 from "fs";
|
|
|
3274
3267
|
import * as path20 from "path";
|
|
3275
3268
|
async function ensureLitellmProxy(config, projectDir) {
|
|
3276
3269
|
if (!needsLitellmProxy(config)) return null;
|
|
3277
|
-
const litellmUrl = getLitellmUrl(
|
|
3270
|
+
const litellmUrl = getLitellmUrl();
|
|
3278
3271
|
const proxyRunning = await checkLitellmHealth(litellmUrl);
|
|
3279
3272
|
let litellmProcess = null;
|
|
3280
3273
|
if (!proxyRunning) {
|
|
@@ -3770,16 +3763,13 @@ function buildConfig(cwd, basic) {
|
|
|
3770
3763
|
typecheck: find("typecheck", "type-check") || (pkg.devDependencies?.typescript ? `${basic.pm} tsc --noEmit` : ""),
|
|
3771
3764
|
lint: find("lint"),
|
|
3772
3765
|
lintFix: find("lint:fix", "lint-fix"),
|
|
3773
|
-
format: find("format:check"),
|
|
3774
3766
|
formatFix: find("format", "format:fix"),
|
|
3775
3767
|
testUnit: find("test:unit", "test", "test:ci")
|
|
3776
3768
|
},
|
|
3777
3769
|
git: { defaultBranch: basic.defaultBranch },
|
|
3778
3770
|
github: { owner: basic.owner, repo: basic.repo },
|
|
3779
|
-
paths: { taskDir: ".kody/tasks" },
|
|
3780
3771
|
agent: {
|
|
3781
|
-
|
|
3782
|
-
defaultRunner: "claude",
|
|
3772
|
+
provider: "anthropic",
|
|
3783
3773
|
modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
|
|
3784
3774
|
}
|
|
3785
3775
|
};
|
|
@@ -3858,55 +3848,8 @@ function initCommand(opts) {
|
|
|
3858
3848
|
console.log(` \u2717 ${c.name} \u2014 ${c.fix}`);
|
|
3859
3849
|
}
|
|
3860
3850
|
}
|
|
3861
|
-
const labels = [
|
|
3862
|
-
{ name: "kody:planning", color: "c5def5", description: "Kody is analyzing and planning" },
|
|
3863
|
-
{ name: "kody:building", color: "0e8a16", description: "Kody is building code" },
|
|
3864
|
-
{ name: "kody:review", color: "fbca04", description: "Kody is reviewing code" },
|
|
3865
|
-
{ name: "kody:done", color: "0e8a16", description: "Kody completed successfully" },
|
|
3866
|
-
{ name: "kody:failed", color: "d93f0b", description: "Kody pipeline failed" },
|
|
3867
|
-
{ name: "kody:waiting", color: "fef2c0", description: "Kody is waiting for answers" },
|
|
3868
|
-
{ name: "kody:low", color: "bfdadc", description: "Low complexity \u2014 skip plan/review" },
|
|
3869
|
-
{ name: "kody:medium", color: "c5def5", description: "Medium complexity \u2014 skip review-fix" },
|
|
3870
|
-
{ name: "kody:high", color: "d4c5f9", description: "High complexity \u2014 full pipeline" },
|
|
3871
|
-
{ name: "kody:feature", color: "0e8a16", description: "New feature" },
|
|
3872
|
-
{ name: "kody:bugfix", color: "d93f0b", description: "Bug fix" },
|
|
3873
|
-
{ name: "kody:refactor", color: "fbca04", description: "Code refactoring" },
|
|
3874
|
-
{ name: "kody:docs", color: "0075ca", description: "Documentation" },
|
|
3875
|
-
{ name: "kody:chore", color: "e4e669", description: "Maintenance task" }
|
|
3876
|
-
];
|
|
3877
3851
|
console.log("\n\u2500\u2500 Labels \u2500\u2500");
|
|
3878
|
-
|
|
3879
|
-
try {
|
|
3880
|
-
execFileSync11("gh", [
|
|
3881
|
-
"label",
|
|
3882
|
-
"create",
|
|
3883
|
-
label.name,
|
|
3884
|
-
"--repo",
|
|
3885
|
-
repoSlug,
|
|
3886
|
-
"--color",
|
|
3887
|
-
label.color,
|
|
3888
|
-
"--description",
|
|
3889
|
-
label.description,
|
|
3890
|
-
"--force"
|
|
3891
|
-
], {
|
|
3892
|
-
encoding: "utf-8",
|
|
3893
|
-
timeout: 1e4,
|
|
3894
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3895
|
-
});
|
|
3896
|
-
console.log(` \u2713 ${label.name}`);
|
|
3897
|
-
} catch {
|
|
3898
|
-
try {
|
|
3899
|
-
execFileSync11("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
|
|
3900
|
-
encoding: "utf-8",
|
|
3901
|
-
timeout: 1e4,
|
|
3902
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3903
|
-
});
|
|
3904
|
-
console.log(` \u25CB ${label.name} (exists)`);
|
|
3905
|
-
} catch {
|
|
3906
|
-
console.log(` \u2717 ${label.name} \u2014 failed to create`);
|
|
3907
|
-
}
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3852
|
+
console.log(" \u25CB Labels will be created automatically during bootstrap");
|
|
3910
3853
|
}
|
|
3911
3854
|
console.log("\n\u2500\u2500 Config \u2500\u2500");
|
|
3912
3855
|
if (fs22.existsSync(configDest)) {
|
|
@@ -4295,6 +4238,76 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
4295
4238
|
}
|
|
4296
4239
|
}
|
|
4297
4240
|
console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
|
|
4241
|
+
console.log("\n\u2500\u2500 Labels \u2500\u2500");
|
|
4242
|
+
try {
|
|
4243
|
+
let repoSlug = "";
|
|
4244
|
+
try {
|
|
4245
|
+
const configPath = path21.join(cwd, "kody.config.json");
|
|
4246
|
+
if (fs22.existsSync(configPath)) {
|
|
4247
|
+
const config = JSON.parse(fs22.readFileSync(configPath, "utf-8"));
|
|
4248
|
+
if (config.github?.owner && config.github?.repo) {
|
|
4249
|
+
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
} catch {
|
|
4253
|
+
}
|
|
4254
|
+
if (repoSlug) {
|
|
4255
|
+
const labels = [
|
|
4256
|
+
{ name: "kody:planning", color: "c5def5", description: "Kody is analyzing and planning" },
|
|
4257
|
+
{ name: "kody:building", color: "0e8a16", description: "Kody is building code" },
|
|
4258
|
+
{ name: "kody:review", color: "fbca04", description: "Kody is reviewing code" },
|
|
4259
|
+
{ name: "kody:done", color: "0e8a16", description: "Kody completed successfully" },
|
|
4260
|
+
{ name: "kody:failed", color: "d93f0b", description: "Kody pipeline failed" },
|
|
4261
|
+
{ name: "kody:waiting", color: "fef2c0", description: "Kody is waiting for answers" },
|
|
4262
|
+
{ name: "kody:low", color: "bfdadc", description: "Low complexity \u2014 skip plan/review" },
|
|
4263
|
+
{ name: "kody:medium", color: "c5def5", description: "Medium complexity \u2014 skip review-fix" },
|
|
4264
|
+
{ name: "kody:high", color: "d4c5f9", description: "High complexity \u2014 full pipeline" },
|
|
4265
|
+
{ name: "kody:feature", color: "0e8a16", description: "New feature" },
|
|
4266
|
+
{ name: "kody:bugfix", color: "d93f0b", description: "Bug fix" },
|
|
4267
|
+
{ name: "kody:refactor", color: "fbca04", description: "Code refactoring" },
|
|
4268
|
+
{ name: "kody:docs", color: "0075ca", description: "Documentation" },
|
|
4269
|
+
{ name: "kody:chore", color: "e4e669", description: "Maintenance task" }
|
|
4270
|
+
];
|
|
4271
|
+
for (const label of labels) {
|
|
4272
|
+
try {
|
|
4273
|
+
execFileSync11("gh", [
|
|
4274
|
+
"label",
|
|
4275
|
+
"create",
|
|
4276
|
+
label.name,
|
|
4277
|
+
"--repo",
|
|
4278
|
+
repoSlug,
|
|
4279
|
+
"--color",
|
|
4280
|
+
label.color,
|
|
4281
|
+
"--description",
|
|
4282
|
+
label.description,
|
|
4283
|
+
"--force"
|
|
4284
|
+
], {
|
|
4285
|
+
cwd,
|
|
4286
|
+
encoding: "utf-8",
|
|
4287
|
+
timeout: 1e4,
|
|
4288
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4289
|
+
});
|
|
4290
|
+
console.log(` \u2713 ${label.name}`);
|
|
4291
|
+
} catch {
|
|
4292
|
+
try {
|
|
4293
|
+
execFileSync11("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
|
|
4294
|
+
cwd,
|
|
4295
|
+
encoding: "utf-8",
|
|
4296
|
+
timeout: 1e4,
|
|
4297
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4298
|
+
});
|
|
4299
|
+
console.log(` \u25CB ${label.name} (exists)`);
|
|
4300
|
+
} catch {
|
|
4301
|
+
console.log(` \u2717 ${label.name} \u2014 failed to create`);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
} else {
|
|
4306
|
+
console.log(" \u25CB Skipped \u2014 could not determine repo from kody.config.json");
|
|
4307
|
+
}
|
|
4308
|
+
} catch {
|
|
4309
|
+
console.log(" \u25CB Label creation skipped");
|
|
4310
|
+
}
|
|
4298
4311
|
console.log("\n\u2500\u2500 Git \u2500\u2500");
|
|
4299
4312
|
const filesToCommit = [
|
|
4300
4313
|
".kody/memory/architecture.md",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses @kody / /kody comment body into structured inputs.
|
|
3
|
+
* Run by the parse job in GitHub Actions.
|
|
4
|
+
* Reads from env, writes to $GITHUB_OUTPUT.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
const outputFile = process.env.GITHUB_OUTPUT;
|
|
8
|
+
const triggerType = process.env.TRIGGER_TYPE ?? "dispatch";
|
|
9
|
+
function output(key, value) {
|
|
10
|
+
if (outputFile) {
|
|
11
|
+
fs.appendFileSync(outputFile, `${key}=${value}\n`);
|
|
12
|
+
}
|
|
13
|
+
console.log(`${key}=${value}`);
|
|
14
|
+
}
|
|
15
|
+
// For workflow_dispatch, pass through inputs
|
|
16
|
+
if (triggerType === "dispatch") {
|
|
17
|
+
output("task_id", process.env.INPUT_TASK_ID ?? "");
|
|
18
|
+
output("mode", process.env.INPUT_MODE ?? "full");
|
|
19
|
+
output("from_stage", process.env.INPUT_FROM_STAGE ?? "");
|
|
20
|
+
output("issue_number", process.env.INPUT_ISSUE_NUMBER ?? "");
|
|
21
|
+
output("feedback", process.env.INPUT_FEEDBACK ?? "");
|
|
22
|
+
output("valid", process.env.INPUT_TASK_ID ? "true" : "false");
|
|
23
|
+
output("trigger_type", "dispatch");
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
// For issue_comment, parse the comment body
|
|
27
|
+
const commentBody = process.env.COMMENT_BODY ?? "";
|
|
28
|
+
const issueNumber = process.env.ISSUE_NUMBER ?? "";
|
|
29
|
+
// Match: @kody [mode] [task-id] [--from stage] [--feedback "text"]
|
|
30
|
+
const kodyMatch = commentBody.match(/(?:@kody|\/kody)\s*(.*)/i);
|
|
31
|
+
if (!kodyMatch) {
|
|
32
|
+
output("valid", "false");
|
|
33
|
+
output("trigger_type", "comment");
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
const parts = kodyMatch[1].trim().split(/\s+/);
|
|
37
|
+
const validModes = ["full", "rerun", "status"];
|
|
38
|
+
let mode = "full";
|
|
39
|
+
let taskId = "";
|
|
40
|
+
let fromStage = "";
|
|
41
|
+
let feedback = "";
|
|
42
|
+
let i = 0;
|
|
43
|
+
// First arg: mode or task-id
|
|
44
|
+
if (parts[i] && validModes.includes(parts[i])) {
|
|
45
|
+
mode = parts[i];
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
// Second arg: task-id
|
|
49
|
+
if (parts[i] && !parts[i].startsWith("--")) {
|
|
50
|
+
taskId = parts[i];
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
// Named args
|
|
54
|
+
while (i < parts.length) {
|
|
55
|
+
if (parts[i] === "--from" && parts[i + 1]) {
|
|
56
|
+
fromStage = parts[i + 1];
|
|
57
|
+
i += 2;
|
|
58
|
+
}
|
|
59
|
+
else if (parts[i] === "--feedback" && parts[i + 1]) {
|
|
60
|
+
// Collect quoted feedback
|
|
61
|
+
const rest = parts.slice(i + 1).join(" ");
|
|
62
|
+
const quoted = rest.match(/^"([^"]*)"/);
|
|
63
|
+
feedback = quoted ? quoted[1] : parts[i + 1];
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
output("task_id", taskId);
|
|
71
|
+
output("mode", mode);
|
|
72
|
+
output("from_stage", fromStage);
|
|
73
|
+
output("issue_number", issueNumber);
|
|
74
|
+
output("feedback", feedback);
|
|
75
|
+
output("valid", taskId ? "true" : "false");
|
|
76
|
+
output("trigger_type", "comment");
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates that a comment trigger is safe to execute.
|
|
3
|
+
* Run by the parse job in GitHub Actions.
|
|
4
|
+
* Reads from env, writes to $GITHUB_OUTPUT.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
const ALLOWED_ASSOCIATIONS = ["COLLABORATOR", "MEMBER", "OWNER"];
|
|
8
|
+
const association = process.env.COMMENT_AUTHOR_ASSOCIATION ?? "";
|
|
9
|
+
const outputFile = process.env.GITHUB_OUTPUT;
|
|
10
|
+
function output(key, value) {
|
|
11
|
+
if (outputFile) {
|
|
12
|
+
fs.appendFileSync(outputFile, `${key}=${value}\n`);
|
|
13
|
+
}
|
|
14
|
+
console.log(`${key}=${value}`);
|
|
15
|
+
}
|
|
16
|
+
if (!ALLOWED_ASSOCIATIONS.includes(association)) {
|
|
17
|
+
output("valid", "false");
|
|
18
|
+
output("reason", `Author association '${association}' not in allowlist: ${ALLOWED_ASSOCIATIONS.join(", ")}`);
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
output("valid", "true");
|
|
22
|
+
output("reason", "");
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface CliInput {
|
|
2
|
+
command: "run" | "rerun" | "fix" | "status";
|
|
3
|
+
taskId?: string;
|
|
4
|
+
task?: string;
|
|
5
|
+
fromStage?: string;
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
issueNumber?: number;
|
|
9
|
+
feedback?: string;
|
|
10
|
+
local?: boolean;
|
|
11
|
+
complexity?: "low" | "medium" | "high";
|
|
12
|
+
}
|
|
13
|
+
export declare function parseArgs(): CliInput;
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const isCI = !!process.env.GITHUB_ACTIONS;
|
|
2
|
+
function getArg(args, flag) {
|
|
3
|
+
const idx = args.indexOf(flag);
|
|
4
|
+
if (idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith("--")) {
|
|
5
|
+
return args[idx + 1];
|
|
6
|
+
}
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
function hasFlag(args, flag) {
|
|
10
|
+
return args.includes(flag);
|
|
11
|
+
}
|
|
12
|
+
export function parseArgs() {
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h") || args.length === 0) {
|
|
15
|
+
console.log(`Usage:
|
|
16
|
+
kody run --task-id <id> [--task "<desc>"] [--cwd <path>] [--issue-number <n>] [--complexity low|medium|high] [--feedback "<text>"] [--local] [--dry-run]
|
|
17
|
+
kody rerun --task-id <id> --from <stage> [--cwd <path>] [--issue-number <n>]
|
|
18
|
+
kody fix --task-id <id> [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
|
|
19
|
+
kody status --task-id <id> [--cwd <path>]
|
|
20
|
+
kody --help`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
const command = args[0];
|
|
24
|
+
if (!["run", "rerun", "fix", "status"].includes(command)) {
|
|
25
|
+
console.error(`Unknown command: ${command}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const issueStr = getArg(args, "--issue-number") ?? process.env.ISSUE_NUMBER;
|
|
29
|
+
const localFlag = hasFlag(args, "--local");
|
|
30
|
+
return {
|
|
31
|
+
command,
|
|
32
|
+
taskId: getArg(args, "--task-id") ?? process.env.TASK_ID,
|
|
33
|
+
task: getArg(args, "--task"),
|
|
34
|
+
fromStage: getArg(args, "--from") ?? process.env.FROM_STAGE,
|
|
35
|
+
dryRun: hasFlag(args, "--dry-run") || process.env.DRY_RUN === "true",
|
|
36
|
+
cwd: getArg(args, "--cwd"),
|
|
37
|
+
issueNumber: issueStr ? parseInt(issueStr, 10) : undefined,
|
|
38
|
+
feedback: getArg(args, "--feedback") ?? process.env.FEEDBACK,
|
|
39
|
+
local: localFlag || (!isCI && !hasFlag(args, "--no-local")),
|
|
40
|
+
complexity: (getArg(args, "--complexity") ?? process.env.COMPLEXITY),
|
|
41
|
+
};
|
|
42
|
+
}
|