@kody-ade/kody-engine-lite 0.1.25 → 0.1.27
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 +74 -224
- package/dist/agent-runner.d.ts +4 -0
- package/dist/agent-runner.js +122 -0
- package/dist/bin/cli.js +365 -100
- 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/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,269 +1,119 @@
|
|
|
1
1
|
# Kody Engine Lite
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## How it works
|
|
3
|
+
**Issue → PR in one command.** Comment `@kody` on a GitHub issue and Kody autonomously classifies, plans, builds, tests, reviews, fixes, and ships a pull request.
|
|
6
4
|
|
|
7
5
|
```
|
|
8
|
-
@kody
|
|
9
|
-
↓
|
|
10
|
-
GitHub Actions workflow
|
|
11
|
-
↓
|
|
12
|
-
7-stage pipeline:
|
|
13
|
-
1. taskify — classify task, detect complexity, ask questions if unclear
|
|
14
|
-
2. plan — TDD implementation plan (opus — deep reasoning)
|
|
15
|
-
3. build — implement code changes (sonnet — tool use via Claude Code)
|
|
16
|
-
4. verify — typecheck + tests + lint (auto-fix on failure)
|
|
17
|
-
5. review — code review with severity levels (opus)
|
|
18
|
-
6. review-fix — fix Critical/Major findings (sonnet)
|
|
19
|
-
7. ship — push branch + create PR with Closes #N
|
|
20
|
-
↓
|
|
21
|
-
PR created with What/Scope/Changes description
|
|
6
|
+
@kody → taskify → plan → build → verify → review → fix → ship → PR created
|
|
22
7
|
```
|
|
23
8
|
|
|
9
|
+
Kody is a 7-stage autonomous SDLC pipeline that runs in GitHub Actions. It uses Claude Code (or any LLM via LiteLLM) to turn issues into production-ready PRs — with quality gates, AI-powered failure diagnosis, risk-based human approval, and self-improving memory.
|
|
10
|
+
|
|
11
|
+
## Why Kody?
|
|
12
|
+
|
|
13
|
+
Most AI coding tools are **autocomplete** (Copilot) or **chat-based** (Cursor, Cline). You still drive. Kody is different: it's an **autonomous pipeline** that takes an issue and delivers a tested, reviewed PR.
|
|
14
|
+
|
|
15
|
+
| | Kody | Copilot Workspace | Devin | Cursor Agent |
|
|
16
|
+
|---|---|---|---|---|
|
|
17
|
+
| **Runs in CI** | GitHub Actions | GitHub Cloud | Devin Cloud | Local IDE |
|
|
18
|
+
| **Fire and forget** | Comment `@kody`, walk away | Must interact | Must interact | Must be open |
|
|
19
|
+
| **Quality gates** | typecheck + tests + lint + AI diagnosis + auto-retry | Basic | Runs tests | Runs tests |
|
|
20
|
+
| **Risk gate** | Pauses HIGH-risk tasks for human approval | No | No | No |
|
|
21
|
+
| **Model flexible** | Any LLM via LiteLLM | GitHub models only | Proprietary | Cursor models |
|
|
22
|
+
| **Open source** | MIT | Proprietary | Proprietary | Proprietary |
|
|
23
|
+
| **Cost** | Your API costs only | $10-39/month | $20-500/month | Subscription |
|
|
24
|
+
|
|
25
|
+
[Full comparison →](docs/COMPARISON.md)
|
|
26
|
+
|
|
24
27
|
## Quick Start
|
|
25
28
|
|
|
26
29
|
```bash
|
|
27
|
-
# Install
|
|
30
|
+
# 1. Install
|
|
28
31
|
npm install -g @kody-ade/kody-engine-lite
|
|
29
32
|
|
|
30
|
-
#
|
|
33
|
+
# 2. Initialize (auto-detects everything)
|
|
31
34
|
cd your-project
|
|
32
35
|
kody-engine-lite init
|
|
33
36
|
|
|
34
|
-
#
|
|
37
|
+
# 3. Set up GitHub
|
|
38
|
+
gh secret set ANTHROPIC_API_KEY --repo owner/repo
|
|
39
|
+
# Settings → Actions → "Allow GitHub Actions to create and approve pull requests"
|
|
40
|
+
|
|
41
|
+
# 4. Push
|
|
35
42
|
git add .github/workflows/kody.yml kody.config.json .kody/
|
|
36
43
|
git commit -m "chore: add kody engine"
|
|
37
44
|
git push
|
|
38
45
|
|
|
39
|
-
# Comment on any issue
|
|
46
|
+
# 5. Comment on any issue
|
|
40
47
|
@kody
|
|
41
48
|
```
|
|
42
49
|
|
|
43
|
-
|
|
50
|
+
`init` spawns Claude Code to analyze your project and generates: workflow file, config with auto-detected quality commands, project memory (architecture + conventions), and 14 GitHub labels.
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
- `.github/workflows/kody.yml` — full CI/CD workflow
|
|
47
|
-
- `kody.config.json` — quality commands auto-detected from `package.json`
|
|
48
|
-
- `.kody/memory/architecture.md` — project-specific tech stack and structure
|
|
49
|
-
- `.kody/memory/conventions.md` — coding patterns, references to existing docs
|
|
50
|
-
- 14 GitHub labels — lifecycle, complexity, and type labels
|
|
51
|
-
- Health checks — CLI tools, GitHub auth, secrets, config validation
|
|
52
|
+
**Prerequisites:** Node.js >= 22, [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code), [GitHub CLI](https://cli.github.com/), git
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
## Pipeline
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
```
|
|
57
|
+
@kody on issue
|
|
58
|
+
↓
|
|
59
|
+
1. taskify — classify task, detect complexity, ask questions → task.json
|
|
60
|
+
2. plan — TDD implementation plan (deep reasoning) → plan.md
|
|
61
|
+
↓ HIGH risk? pause for human approval
|
|
62
|
+
3. build — implement code via Claude Code tools → code changes
|
|
63
|
+
4. verify — typecheck + tests + lint (AI diagnosis + autofix) → verify.md
|
|
64
|
+
5. review — code review: PASS/FAIL + Critical/Major/Minor → review.md
|
|
65
|
+
6. review-fix — fix Critical and Major findings → code changes
|
|
66
|
+
7. ship — push branch + create PR with Closes #N → ship.md
|
|
67
|
+
↓
|
|
68
|
+
PR created
|
|
58
69
|
```
|
|
59
70
|
|
|
60
|
-
**
|
|
61
|
-
|
|
71
|
+
Complexity auto-detected: **low** skips plan/review (4 stages), **medium** skips review-fix (6 stages), **high** runs all 7.
|
|
72
|
+
|
|
73
|
+
[Pipeline details →](docs/PIPELINE.md)
|
|
62
74
|
|
|
63
75
|
## Commands
|
|
64
76
|
|
|
65
77
|
### GitHub Comments
|
|
66
78
|
|
|
67
|
-
```
|
|
68
|
-
@kody
|
|
69
|
-
@kody approve
|
|
70
|
-
@kody fix
|
|
71
|
-
@kody
|
|
72
|
-
fix the auth middleware
|
|
73
|
-
to return 401 not 500
|
|
74
|
-
@kody rerun Resume from failed/paused stage
|
|
75
|
-
@kody rerun --from <stage> Resume from specific stage
|
|
79
|
+
```bash
|
|
80
|
+
@kody # Full pipeline
|
|
81
|
+
@kody approve # Resume after questions or risk gate
|
|
82
|
+
@kody fix # Re-build (comment body = feedback)
|
|
83
|
+
@kody rerun --from <stage> # Resume from specific stage
|
|
76
84
|
```
|
|
77
85
|
|
|
78
86
|
### CLI
|
|
79
87
|
|
|
80
88
|
```bash
|
|
81
|
-
kody-engine-lite run --
|
|
82
|
-
kody-engine-lite run --
|
|
89
|
+
kody-engine-lite run --issue-number 42 --local --cwd ./project
|
|
90
|
+
kody-engine-lite run --task "Add retry utility" --local
|
|
83
91
|
kody-engine-lite fix --issue-number 42 --feedback "Use middleware pattern"
|
|
84
|
-
kody-engine-lite rerun --issue-number 42
|
|
85
|
-
kody-engine-lite status --task-id
|
|
92
|
+
kody-engine-lite rerun --issue-number 42 --from verify
|
|
93
|
+
kody-engine-lite status --task-id 42-260327-102254
|
|
86
94
|
kody-engine-lite init [--force]
|
|
87
95
|
```
|
|
88
96
|
|
|
89
|
-
## Pipeline Stages
|
|
90
|
-
|
|
91
|
-
| Stage | Model | What it does |
|
|
92
|
-
|-------|-------|-------------|
|
|
93
|
-
| taskify | haiku | Classify task → `task.json` (type, scope, risk, questions) |
|
|
94
|
-
| plan | opus | Create TDD plan → `plan.md` (deep reasoning) |
|
|
95
|
-
| build | sonnet | Implement code using Claude Code tools (Read/Write/Edit/Bash) |
|
|
96
|
-
| verify | — | Run typecheck + tests + lint from `kody.config.json` |
|
|
97
|
-
| review | opus | Code review → `review.md` (PASS/FAIL + Critical/Major/Minor) |
|
|
98
|
-
| review-fix | sonnet | Fix Critical and Major findings |
|
|
99
|
-
| ship | — | Push branch, create PR, comment on issue |
|
|
100
|
-
|
|
101
97
|
## Key Features
|
|
102
98
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- **
|
|
108
|
-
- **
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
|
117
|
-
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
122
|
-
### Branch Syncing
|
|
123
|
-
|
|
124
|
-
Every run merges latest from the default branch into the feature branch before building.
|
|
125
|
-
|
|
126
|
-
### Verify + Autofix Loop
|
|
127
|
-
|
|
128
|
-
If verify fails: runs lint-fix → format-fix → autofix agent → retries (up to 2 attempts).
|
|
129
|
-
|
|
130
|
-
### Review + Fix Loop
|
|
131
|
-
|
|
132
|
-
If review verdict is FAIL: runs review-fix agent → re-reviews.
|
|
133
|
-
|
|
134
|
-
### Rich PR Description
|
|
135
|
-
|
|
136
|
-
```markdown
|
|
137
|
-
## What
|
|
138
|
-
Add authentication middleware to 8 unprotected API routes
|
|
139
|
-
|
|
140
|
-
## Scope
|
|
141
|
-
- `src/middleware/auth.ts`
|
|
142
|
-
- `src/app/api/cron/route.ts`
|
|
143
|
-
|
|
144
|
-
**Type:** bugfix | **Risk:** high
|
|
145
|
-
|
|
146
|
-
## Changes
|
|
147
|
-
Added auth middleware to all cron routes and copilotkit endpoint.
|
|
148
|
-
|
|
149
|
-
**Review:** ✅ PASS
|
|
150
|
-
**Verify:** ✅ typecheck + tests + lint passed
|
|
151
|
-
|
|
152
|
-
<details><summary>📋 Implementation plan</summary>
|
|
153
|
-
...
|
|
154
|
-
</details>
|
|
155
|
-
|
|
156
|
-
Closes #42
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Labels
|
|
160
|
-
|
|
161
|
-
`init` creates 14 labels:
|
|
162
|
-
- **Lifecycle:** `kody:planning`, `kody:building`, `kody:review`, `kody:done`, `kody:failed`, `kody:waiting`
|
|
163
|
-
- **Complexity:** `kody:low`, `kody:medium`, `kody:high`
|
|
164
|
-
- **Type:** `kody:feature`, `kody:bugfix`, `kody:refactor`, `kody:docs`, `kody:chore`
|
|
165
|
-
|
|
166
|
-
### Memory System
|
|
167
|
-
|
|
168
|
-
`.kody/memory/` files are prepended to every agent prompt:
|
|
169
|
-
- `architecture.md` — auto-generated by `init`
|
|
170
|
-
- `conventions.md` — auto-learned after each successful run
|
|
171
|
-
|
|
172
|
-
## Using Non-Anthropic Models (LiteLLM)
|
|
173
|
-
|
|
174
|
-
Claude Code can use **any model** through a LiteLLM proxy. Tested with MiniMax (full tool use: Write, Read, Edit, Bash, Grep).
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
# Start LiteLLM proxy
|
|
178
|
-
docker run -d -p 4000:4000 \
|
|
179
|
-
-e MINIMAX_API_KEY=your-key \
|
|
180
|
-
-v config.yaml:/app/config.yaml \
|
|
181
|
-
ghcr.io/berriai/litellm:main-latest --config /app/config.yaml
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
```yaml
|
|
185
|
-
# config.yaml
|
|
186
|
-
model_list:
|
|
187
|
-
- model_name: minimax
|
|
188
|
-
litellm_params:
|
|
189
|
-
model: minimax/MiniMax-M2.7-highspeed
|
|
190
|
-
api_key: os.environ/MINIMAX_API_KEY
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
```json
|
|
194
|
-
// kody.config.json
|
|
195
|
-
{
|
|
196
|
-
"agent": {
|
|
197
|
-
"litellmUrl": "http://localhost:4000",
|
|
198
|
-
"modelMap": { "cheap": "minimax", "mid": "minimax", "strong": "minimax" }
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
LiteLLM translates Anthropic's tool-use protocol to the target provider's format. No code changes needed.
|
|
204
|
-
|
|
205
|
-
## Configuration
|
|
206
|
-
|
|
207
|
-
### `kody.config.json`
|
|
208
|
-
|
|
209
|
-
| Field | Description | Default |
|
|
210
|
-
|-------|-------------|---------|
|
|
211
|
-
| `quality.typecheck` | Typecheck command | `pnpm -s tsc --noEmit` |
|
|
212
|
-
| `quality.lint` | Lint command | `""` |
|
|
213
|
-
| `quality.lintFix` | Auto-fix lint | `""` |
|
|
214
|
-
| `quality.testUnit` | Unit test command | `pnpm -s test` |
|
|
215
|
-
| `git.defaultBranch` | Default branch | `dev` |
|
|
216
|
-
| `github.owner` | GitHub org/user | auto-detected |
|
|
217
|
-
| `github.repo` | Repository name | auto-detected |
|
|
218
|
-
| `agent.modelMap.cheap` | Taskify model | `haiku` |
|
|
219
|
-
| `agent.modelMap.mid` | Build/fix model | `sonnet` |
|
|
220
|
-
| `agent.modelMap.strong` | Plan/review model | `opus` |
|
|
221
|
-
| `agent.litellmUrl` | LiteLLM proxy URL | — |
|
|
222
|
-
|
|
223
|
-
### Environment Variables
|
|
224
|
-
|
|
225
|
-
| Variable | Required | Description |
|
|
226
|
-
|----------|----------|-------------|
|
|
227
|
-
| `ANTHROPIC_API_KEY` | Yes | Claude API key (repo secret) |
|
|
228
|
-
| `GH_TOKEN` | Auto | GitHub token (provided by Actions) |
|
|
229
|
-
| `LOG_LEVEL` | No | `debug`, `info`, `warn`, `error` |
|
|
230
|
-
|
|
231
|
-
## Architecture
|
|
232
|
-
|
|
233
|
-
```
|
|
234
|
-
src/
|
|
235
|
-
├── entry.ts — CLI: run/rerun/fix/status, arg parsing, CI mode
|
|
236
|
-
├── state-machine.ts — Pipeline loop, stage dispatch, question gates, complexity
|
|
237
|
-
├── agent-runner.ts — Claude Code subprocess wrapper (thin — spawn, pipe, timeout)
|
|
238
|
-
├── context.ts — Prompt assembly + memory + task context injection
|
|
239
|
-
├── definitions.ts — 7-stage pipeline config
|
|
240
|
-
├── types.ts — TypeScript interfaces
|
|
241
|
-
├── config.ts — kody.config.json loader + constants
|
|
242
|
-
├── git-utils.ts — Branch, commit, push, sync, diff
|
|
243
|
-
├── github-api.ts — Issues, labels, PRs, comments via gh CLI
|
|
244
|
-
├── verify-runner.ts — Quality gate execution
|
|
245
|
-
├── validators.ts — Output validation (task.json, plan.md, review.md)
|
|
246
|
-
├── memory.ts — .kody/memory/ reader
|
|
247
|
-
├── logger.ts — Structured logging with CI groups
|
|
248
|
-
├── preflight.ts — Startup health checks
|
|
249
|
-
├── kody-utils.ts — Task directory utilities
|
|
250
|
-
└── bin/cli.ts — Package CLI: init (LLM-powered), run, version
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## Development
|
|
254
|
-
|
|
255
|
-
```bash
|
|
256
|
-
pnpm install # Install deps
|
|
257
|
-
pnpm typecheck # Type check
|
|
258
|
-
pnpm test # 125 tests (16 files)
|
|
259
|
-
pnpm build # Build npm package
|
|
260
|
-
pnpm kody run ... # Dev mode (tsx)
|
|
261
|
-
npm publish --access public # Publish
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
## Security
|
|
265
|
-
|
|
266
|
-
Only GitHub collaborators (COLLABORATOR, MEMBER, OWNER) can trigger `@kody`. External contributors cannot.
|
|
99
|
+
- **Risk Gate** — HIGH-risk tasks pause for human plan approval before building ([details](docs/FEATURES.md#risk-gate))
|
|
100
|
+
- **AI Failure Diagnosis** — classifies errors as fixable/infrastructure/pre-existing/abort before retry ([details](docs/FEATURES.md#ai-powered-failure-diagnosis))
|
|
101
|
+
- **Question Gates** — asks product/architecture questions when the task is unclear ([details](docs/FEATURES.md#question-gates))
|
|
102
|
+
- **Retrospective** — analyzes each run, identifies patterns, suggests improvements ([details](docs/FEATURES.md#retrospective-system))
|
|
103
|
+
- **Auto-Learning** — extracts coding conventions from each successful run ([details](docs/FEATURES.md#auto-learning-memory))
|
|
104
|
+
- **Any LLM** — route through LiteLLM to use MiniMax, GPT, Gemini, local models ([setup guide](docs/LITELLM.md))
|
|
105
|
+
|
|
106
|
+
## Documentation
|
|
107
|
+
|
|
108
|
+
| Doc | What's in it |
|
|
109
|
+
|-----|-------------|
|
|
110
|
+
| [Pipeline](docs/PIPELINE.md) | Stage details, complexity skipping, artifacts, state machine |
|
|
111
|
+
| [Features](docs/FEATURES.md) | Risk gate, diagnosis, retrospective, auto-learn, labels |
|
|
112
|
+
| [LiteLLM](docs/LITELLM.md) | Non-Anthropic model setup, auto-start, tested providers |
|
|
113
|
+
| [Configuration](docs/CONFIGURATION.md) | Full config reference, env vars, workflow setup |
|
|
114
|
+
| [Comparison](docs/COMPARISON.md) | vs Copilot, Devin, Cursor, Cline, SWE-agent, OpenHands |
|
|
115
|
+
| [Architecture](docs/ARCHITECTURE.md) | Source tree, state machine diagram, development guide |
|
|
116
|
+
| [FAQ](docs/FAQ.md) | Common questions about usage, models, security, cost |
|
|
267
117
|
|
|
268
118
|
## License
|
|
269
119
|
|
|
@@ -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
|
+
}
|