@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.
Files changed (3) hide show
  1. package/README.md +305 -0
  2. package/dist/bin/cli.js +56 -14
  3. 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 promptPath = path3.resolve(
271
- new URL(".", import.meta.url).pathname,
272
- "..",
273
- "prompts",
274
- `${stageName}.md`
275
- );
276
- if (!fs3.existsSync(promptPath)) {
277
- throw new Error(`Prompt file not found: ${promptPath}`);
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
- return fs3.readFileSync(promptPath, "utf-8");
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 body = "Generated by Kody pipeline\n\n---\n\u{1F916} Generated by Kody";
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",