@kody-ade/kody-engine-lite 0.1.3 → 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 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
@@ -279,7 +279,7 @@ function readPromptFile(stageName) {
279
279
  }
280
280
  throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
281
281
  }
282
- function injectTaskContext(prompt, taskId, taskDir) {
282
+ function injectTaskContext(prompt, taskId, taskDir, feedback) {
283
283
  let context = `## Task Context
284
284
  `;
285
285
  context += `Task ID: ${taskId}
@@ -326,14 +326,20 @@ ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
326
326
  context += `
327
327
  ## Plan Summary
328
328
  ${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
329
+ `;
330
+ }
331
+ if (feedback) {
332
+ context += `
333
+ ## Human Feedback
334
+ ${feedback}
329
335
  `;
330
336
  }
331
337
  return prompt.replace("{{TASK_CONTEXT}}", context);
332
338
  }
333
- function buildFullPrompt(stageName, taskId, taskDir, projectDir) {
339
+ function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
334
340
  const memory = readProjectMemory(projectDir);
335
341
  const promptTemplate = readPromptFile(stageName);
336
- const prompt = injectTaskContext(promptTemplate, taskId, taskDir);
342
+ const prompt = injectTaskContext(promptTemplate, taskId, taskDir, feedback);
337
343
  return memory ? `${memory}
338
344
  ---
339
345
 
@@ -780,7 +786,7 @@ async function executeAgentStage(ctx, def) {
780
786
  logger.info(` [dry-run] skipping ${def.name}`);
781
787
  return { outcome: "completed", retries: 0 };
782
788
  }
783
- 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);
784
790
  const model = resolveModel(def.modelTier, def.name);
785
791
  logger.info(` model=${model} timeout=${def.timeout / 1e3}s`);
786
792
  const config = getProjectConfig();
@@ -798,6 +804,21 @@ async function executeAgentStage(ctx, def) {
798
804
  if (def.outputFile && result.output) {
799
805
  fs4.writeFileSync(path4.join(ctx.taskDir, def.outputFile), result.output);
800
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
+ }
801
822
  if (def.outputFile) {
802
823
  const outputPath = path4.join(ctx.taskDir, def.outputFile);
803
824
  if (fs4.existsSync(outputPath)) {
@@ -947,7 +968,13 @@ function executeShipStage(ctx, _def) {
947
968
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
948
969
  title = (lines[0] ?? "Update").slice(0, 72);
949
970
  }
950
- 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`;
951
978
  const pr = createPR(head, base, title, body);
952
979
  if (pr) {
953
980
  if (ctx.input.issueNumber && !ctx.input.local) {
@@ -1479,6 +1506,20 @@ Artifacts in ${taskDir}:`);
1479
1506
  console.log(` ${f}`);
1480
1507
  }
1481
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
+ }
1482
1523
  process.exit(1);
1483
1524
  }
1484
1525
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",