@kody-ade/kody-engine-lite 0.1.2 → 0.1.4
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 +305 -0
- package/dist/bin/cli.js +56 -14
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Kody Engine Lite
|
|
2
|
+
|
|
3
|
+
Autonomous SDLC pipeline — runs a 7-stage pipeline (taskify → plan → build → verify → review → review-fix → ship) on your codebase using Claude Code as the execution engine.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
@kody full <task-id> (comment on a GitHub issue)
|
|
9
|
+
↓
|
|
10
|
+
GitHub Actions workflow triggers
|
|
11
|
+
↓
|
|
12
|
+
Kody Engine Lite (npm package)
|
|
13
|
+
↓
|
|
14
|
+
7-stage pipeline:
|
|
15
|
+
1. taskify — classify the task (haiku)
|
|
16
|
+
2. plan — create implementation plan (sonnet)
|
|
17
|
+
3. build — implement code changes (opus)
|
|
18
|
+
4. verify — run typecheck + tests + lint
|
|
19
|
+
5. review — code review (sonnet)
|
|
20
|
+
6. review-fix — fix review issues (opus)
|
|
21
|
+
7. ship — push branch + create PR
|
|
22
|
+
↓
|
|
23
|
+
PR created on your repo
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g @kody-ade/kody-engine-lite
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Init (run in your project root)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd your-project
|
|
38
|
+
kody-engine-lite init
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This will:
|
|
42
|
+
- Copy `.github/workflows/kody.yml` to your repo
|
|
43
|
+
- Create `kody.config.json` (edit this)
|
|
44
|
+
- Create `.kody/memory/architecture.md` (auto-detected)
|
|
45
|
+
- Create `.kody/memory/conventions.md` (seed)
|
|
46
|
+
- Add `.tasks/` to `.gitignore`
|
|
47
|
+
- Run health checks (prerequisites, GitHub auth, secrets)
|
|
48
|
+
|
|
49
|
+
### 3. Configure
|
|
50
|
+
|
|
51
|
+
Edit `kody.config.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"quality": {
|
|
56
|
+
"typecheck": "pnpm tsc --noEmit",
|
|
57
|
+
"lint": "pnpm lint",
|
|
58
|
+
"lintFix": "pnpm lint:fix",
|
|
59
|
+
"format": "",
|
|
60
|
+
"formatFix": "",
|
|
61
|
+
"testUnit": "pnpm test"
|
|
62
|
+
},
|
|
63
|
+
"git": {
|
|
64
|
+
"defaultBranch": "main"
|
|
65
|
+
},
|
|
66
|
+
"github": {
|
|
67
|
+
"owner": "your-org",
|
|
68
|
+
"repo": "your-repo"
|
|
69
|
+
},
|
|
70
|
+
"paths": {
|
|
71
|
+
"taskDir": ".tasks"
|
|
72
|
+
},
|
|
73
|
+
"agent": {
|
|
74
|
+
"runner": "claude-code",
|
|
75
|
+
"modelMap": {
|
|
76
|
+
"cheap": "haiku",
|
|
77
|
+
"mid": "sonnet",
|
|
78
|
+
"strong": "opus"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 4. GitHub Setup
|
|
85
|
+
|
|
86
|
+
#### Required Secret
|
|
87
|
+
|
|
88
|
+
Add `ANTHROPIC_API_KEY` to your repo secrets:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
gh secret set ANTHROPIC_API_KEY --repo owner/repo
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Required Permissions
|
|
95
|
+
|
|
96
|
+
Go to **Settings → Actions → General → Workflow permissions**:
|
|
97
|
+
|
|
98
|
+
1. Select **"Read and write permissions"**
|
|
99
|
+
2. Check **"Allow GitHub Actions to create and approve pull requests"**
|
|
100
|
+
3. Save
|
|
101
|
+
|
|
102
|
+
Without this, the ship stage cannot create PRs.
|
|
103
|
+
|
|
104
|
+
### 5. Push and Use
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git add .github/workflows/kody.yml kody.config.json .kody/
|
|
108
|
+
git commit -m "chore: add kody engine"
|
|
109
|
+
git push
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Then comment on any issue:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
@kody full my-task-id
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## CLI Usage
|
|
119
|
+
|
|
120
|
+
### Run locally
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Run against current directory
|
|
124
|
+
kody-engine-lite run --task-id my-task --task "Add a sum function to src/math.ts with tests"
|
|
125
|
+
|
|
126
|
+
# Run against a different project
|
|
127
|
+
kody-engine-lite run --task-id my-task --task "Add feature X" --cwd /path/to/project
|
|
128
|
+
|
|
129
|
+
# Run from a GitHub issue (fetches issue body as task)
|
|
130
|
+
kody-engine-lite run --task-id my-task --issue-number 1 --cwd /path/to/project
|
|
131
|
+
|
|
132
|
+
# Dry run (no agent calls)
|
|
133
|
+
kody-engine-lite run --task-id my-task --task "Test" --dry-run
|
|
134
|
+
|
|
135
|
+
# Resume from a failed stage
|
|
136
|
+
kody-engine-lite rerun --task-id my-task --from review
|
|
137
|
+
|
|
138
|
+
# Check pipeline status
|
|
139
|
+
kody-engine-lite status --task-id my-task
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Init a new project
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
cd your-project
|
|
146
|
+
kody-engine-lite init # setup workflow, config, memory
|
|
147
|
+
kody-engine-lite init --force # overwrite existing workflow
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### GitHub Comment Triggers
|
|
151
|
+
|
|
152
|
+
Comment on any issue:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
@kody full <task-id> # Run full pipeline
|
|
156
|
+
@kody rerun <task-id> --from <stage> # Resume from stage
|
|
157
|
+
@kody status <task-id> # Check status
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Pipeline Stages
|
|
161
|
+
|
|
162
|
+
| Stage | Model | What it does |
|
|
163
|
+
|-------|-------|-------------|
|
|
164
|
+
| taskify | haiku | Classifies task from issue body → `task.json` |
|
|
165
|
+
| plan | sonnet | Creates TDD implementation plan → `plan.md` |
|
|
166
|
+
| build | opus | Implements code changes (uses Claude Code tools) |
|
|
167
|
+
| verify | — | Runs typecheck + tests + lint (from `kody.config.json`) |
|
|
168
|
+
| review | sonnet | Code review → `review.md` (PASS/FAIL + findings) |
|
|
169
|
+
| review-fix | opus | Fixes Critical/Major review findings |
|
|
170
|
+
| ship | — | Pushes branch + creates PR + comments on issue |
|
|
171
|
+
|
|
172
|
+
### Automatic Loops
|
|
173
|
+
|
|
174
|
+
- **Verify + autofix**: If verify fails, runs lint-fix + format-fix + autofix agent, retries up to 2 times
|
|
175
|
+
- **Review + fix**: If review verdict is FAIL, runs review-fix agent then re-reviews
|
|
176
|
+
|
|
177
|
+
## Memory System
|
|
178
|
+
|
|
179
|
+
Kody maintains project memory in `.kody/memory/`:
|
|
180
|
+
|
|
181
|
+
- **`architecture.md`** — auto-detected on `init` (framework, language, testing, directory structure)
|
|
182
|
+
- **`conventions.md`** — auto-learned after each successful pipeline run
|
|
183
|
+
|
|
184
|
+
Memory is prepended to every agent prompt, giving Claude Code project context.
|
|
185
|
+
|
|
186
|
+
## Configuration Reference
|
|
187
|
+
|
|
188
|
+
### `kody.config.json`
|
|
189
|
+
|
|
190
|
+
| Field | Description | Default |
|
|
191
|
+
|-------|-------------|---------|
|
|
192
|
+
| `quality.typecheck` | Typecheck command | `pnpm -s tsc --noEmit` |
|
|
193
|
+
| `quality.lint` | Lint command | `pnpm -s lint` |
|
|
194
|
+
| `quality.lintFix` | Lint fix command | `pnpm lint:fix` |
|
|
195
|
+
| `quality.format` | Format check command | `""` |
|
|
196
|
+
| `quality.formatFix` | Format fix command | `""` |
|
|
197
|
+
| `quality.testUnit` | Test command | `pnpm -s test` |
|
|
198
|
+
| `git.defaultBranch` | Default branch | `dev` |
|
|
199
|
+
| `github.owner` | GitHub org/user | `""` |
|
|
200
|
+
| `github.repo` | GitHub repo name | `""` |
|
|
201
|
+
| `paths.taskDir` | Task artifacts directory | `.tasks` |
|
|
202
|
+
| `agent.modelMap.cheap` | Model for taskify | `haiku` |
|
|
203
|
+
| `agent.modelMap.mid` | Model for plan/review | `sonnet` |
|
|
204
|
+
| `agent.modelMap.strong` | Model for build/review-fix | `opus` |
|
|
205
|
+
|
|
206
|
+
### Environment Variables
|
|
207
|
+
|
|
208
|
+
| Variable | Required | Description |
|
|
209
|
+
|----------|----------|-------------|
|
|
210
|
+
| `ANTHROPIC_API_KEY` | Yes | Claude API key (set as repo secret) |
|
|
211
|
+
| `GH_TOKEN` | Auto | GitHub token (provided by Actions) |
|
|
212
|
+
| `GH_PAT` | No | Personal access token (preferred over GH_TOKEN) |
|
|
213
|
+
| `LOG_LEVEL` | No | `debug`, `info`, `warn`, `error` (default: `info`) |
|
|
214
|
+
| `LITELLM_BASE_URL` | No | LiteLLM proxy URL for model routing |
|
|
215
|
+
|
|
216
|
+
## LiteLLM (Optional)
|
|
217
|
+
|
|
218
|
+
For multi-provider model routing with fallback:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
pip install litellm[proxy]
|
|
222
|
+
litellm --config litellm-config.yaml --port 4000
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Then set in `kody.config.json`:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"agent": {
|
|
230
|
+
"litellmUrl": "http://localhost:4000",
|
|
231
|
+
"modelMap": { "cheap": "cheap", "mid": "mid", "strong": "strong" }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Task Artifacts
|
|
237
|
+
|
|
238
|
+
Each pipeline run creates artifacts in `.tasks/<task-id>/`:
|
|
239
|
+
|
|
240
|
+
| File | Created by | Content |
|
|
241
|
+
|------|-----------|---------|
|
|
242
|
+
| `task.md` | entry / issue fetch | Task description |
|
|
243
|
+
| `task.json` | taskify stage | Structured classification |
|
|
244
|
+
| `plan.md` | plan stage | Implementation steps |
|
|
245
|
+
| `verify.md` | verify stage | Quality gate results |
|
|
246
|
+
| `review.md` | review stage | Code review + verdict |
|
|
247
|
+
| `ship.md` | ship stage | PR URL |
|
|
248
|
+
| `status.json` | state machine | Pipeline state (per-stage) |
|
|
249
|
+
|
|
250
|
+
## Architecture
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
@kody-ade/kody-engine-lite (npm package)
|
|
254
|
+
├── dist/bin/cli.js — CLI entry point
|
|
255
|
+
├── prompts/ — Stage prompt templates
|
|
256
|
+
│ ├── taskify.md
|
|
257
|
+
│ ├── plan.md
|
|
258
|
+
│ ├── build.md
|
|
259
|
+
│ ├── review.md
|
|
260
|
+
│ ├── review-fix.md
|
|
261
|
+
│ └── autofix.md
|
|
262
|
+
└── templates/
|
|
263
|
+
└── kody.yml — GitHub Actions workflow template
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Source files (in this repo):
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
src/
|
|
270
|
+
├── entry.ts — CLI argument parsing, pipeline dispatch
|
|
271
|
+
├── types.ts — TypeScript interfaces
|
|
272
|
+
├── definitions.ts — 7-stage pipeline configuration
|
|
273
|
+
├── state-machine.ts — Pipeline orchestration loop
|
|
274
|
+
├── agent-runner.ts — Claude Code subprocess wrapper
|
|
275
|
+
├── context.ts — Prompt assembly + task context injection
|
|
276
|
+
├── memory.ts — Project memory reader
|
|
277
|
+
├── config.ts — Config loading (kody.config.json)
|
|
278
|
+
├── logger.ts — Structured logging
|
|
279
|
+
├── preflight.ts — Startup checks
|
|
280
|
+
├── validators.ts — Output validation
|
|
281
|
+
├── verify-runner.ts — Quality gate execution
|
|
282
|
+
├── kody-utils.ts — Task directory utilities
|
|
283
|
+
├── git-utils.ts — Git operations
|
|
284
|
+
├── github-api.ts — GitHub API via gh CLI
|
|
285
|
+
└── bin/cli.ts — Package CLI (init, run, version)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Development
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# Install deps
|
|
292
|
+
pnpm install
|
|
293
|
+
|
|
294
|
+
# Type check
|
|
295
|
+
pnpm typecheck
|
|
296
|
+
|
|
297
|
+
# Run locally (dev mode)
|
|
298
|
+
pnpm kody run --task-id test --task "Add feature" --cwd /path/to/project
|
|
299
|
+
|
|
300
|
+
# Build package
|
|
301
|
+
pnpm build
|
|
302
|
+
|
|
303
|
+
# Publish
|
|
304
|
+
npm publish --access public
|
|
305
|
+
```
|
package/dist/bin/cli.js
CHANGED
|
@@ -267,18 +267,19 @@ var init_config = __esm({
|
|
|
267
267
|
import * as fs3 from "fs";
|
|
268
268
|
import * as path3 from "path";
|
|
269
269
|
function readPromptFile(stageName) {
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
"..",
|
|
273
|
-
"prompts",
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
270
|
+
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
271
|
+
const candidates = [
|
|
272
|
+
path3.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
|
|
273
|
+
path3.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
|
|
274
|
+
];
|
|
275
|
+
for (const candidate of candidates) {
|
|
276
|
+
if (fs3.existsSync(candidate)) {
|
|
277
|
+
return fs3.readFileSync(candidate, "utf-8");
|
|
278
|
+
}
|
|
278
279
|
}
|
|
279
|
-
|
|
280
|
+
throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
|
|
280
281
|
}
|
|
281
|
-
function injectTaskContext(prompt, taskId, taskDir) {
|
|
282
|
+
function injectTaskContext(prompt, taskId, taskDir, feedback) {
|
|
282
283
|
let context = `## Task Context
|
|
283
284
|
`;
|
|
284
285
|
context += `Task ID: ${taskId}
|
|
@@ -325,14 +326,20 @@ ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
|
|
|
325
326
|
context += `
|
|
326
327
|
## Plan Summary
|
|
327
328
|
${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
if (feedback) {
|
|
332
|
+
context += `
|
|
333
|
+
## Human Feedback
|
|
334
|
+
${feedback}
|
|
328
335
|
`;
|
|
329
336
|
}
|
|
330
337
|
return prompt.replace("{{TASK_CONTEXT}}", context);
|
|
331
338
|
}
|
|
332
|
-
function buildFullPrompt(stageName, taskId, taskDir, projectDir) {
|
|
339
|
+
function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
|
|
333
340
|
const memory = readProjectMemory(projectDir);
|
|
334
341
|
const promptTemplate = readPromptFile(stageName);
|
|
335
|
-
const prompt = injectTaskContext(promptTemplate, taskId, taskDir);
|
|
342
|
+
const prompt = injectTaskContext(promptTemplate, taskId, taskDir, feedback);
|
|
336
343
|
return memory ? `${memory}
|
|
337
344
|
---
|
|
338
345
|
|
|
@@ -779,7 +786,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
779
786
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
780
787
|
return { outcome: "completed", retries: 0 };
|
|
781
788
|
}
|
|
782
|
-
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir);
|
|
789
|
+
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
|
|
783
790
|
const model = resolveModel(def.modelTier, def.name);
|
|
784
791
|
logger.info(` model=${model} timeout=${def.timeout / 1e3}s`);
|
|
785
792
|
const config = getProjectConfig();
|
|
@@ -797,6 +804,21 @@ async function executeAgentStage(ctx, def) {
|
|
|
797
804
|
if (def.outputFile && result.output) {
|
|
798
805
|
fs4.writeFileSync(path4.join(ctx.taskDir, def.outputFile), result.output);
|
|
799
806
|
}
|
|
807
|
+
if (def.outputFile) {
|
|
808
|
+
const outputPath = path4.join(ctx.taskDir, def.outputFile);
|
|
809
|
+
if (!fs4.existsSync(outputPath)) {
|
|
810
|
+
const ext = path4.extname(def.outputFile);
|
|
811
|
+
const base = path4.basename(def.outputFile, ext);
|
|
812
|
+
const files = fs4.readdirSync(ctx.taskDir);
|
|
813
|
+
const variant = files.find(
|
|
814
|
+
(f) => f.startsWith(base + "-") && f.endsWith(ext)
|
|
815
|
+
);
|
|
816
|
+
if (variant) {
|
|
817
|
+
fs4.renameSync(path4.join(ctx.taskDir, variant), outputPath);
|
|
818
|
+
logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
800
822
|
if (def.outputFile) {
|
|
801
823
|
const outputPath = path4.join(ctx.taskDir, def.outputFile);
|
|
802
824
|
if (fs4.existsSync(outputPath)) {
|
|
@@ -946,7 +968,13 @@ function executeShipStage(ctx, _def) {
|
|
|
946
968
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
947
969
|
title = (lines[0] ?? "Update").slice(0, 72);
|
|
948
970
|
}
|
|
949
|
-
const
|
|
971
|
+
const closesLine = ctx.input.issueNumber ? `
|
|
972
|
+
|
|
973
|
+
Closes #${ctx.input.issueNumber}` : "";
|
|
974
|
+
const body = `Generated by Kody pipeline${closesLine}
|
|
975
|
+
|
|
976
|
+
---
|
|
977
|
+
\u{1F916} Generated by Kody`;
|
|
950
978
|
const pr = createPR(head, base, title, body);
|
|
951
979
|
if (pr) {
|
|
952
980
|
if (ctx.input.issueNumber && !ctx.input.local) {
|
|
@@ -1478,6 +1506,20 @@ Artifacts in ${taskDir}:`);
|
|
|
1478
1506
|
console.log(` ${f}`);
|
|
1479
1507
|
}
|
|
1480
1508
|
if (state.state === "failed") {
|
|
1509
|
+
if (ctx.input.issueNumber && !ctx.input.local) {
|
|
1510
|
+
const failedStage = Object.entries(state.stages).find(
|
|
1511
|
+
([, s]) => s.state === "failed" || s.state === "timeout"
|
|
1512
|
+
);
|
|
1513
|
+
const stageName = failedStage ? failedStage[0] : "unknown";
|
|
1514
|
+
const error = failedStage ? failedStage[1].error ?? "" : "";
|
|
1515
|
+
try {
|
|
1516
|
+
postComment(
|
|
1517
|
+
ctx.input.issueNumber,
|
|
1518
|
+
`\u274C Pipeline failed at **${stageName}**${error ? `: ${error.slice(0, 200)}` : ""}`
|
|
1519
|
+
);
|
|
1520
|
+
} catch {
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1481
1523
|
process.exit(1);
|
|
1482
1524
|
}
|
|
1483
1525
|
}
|