@kody-ade/kody-engine-lite 0.1.71 → 0.1.73
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 +32 -92
- package/dist/bin/cli.js +54 -9
- package/package.json +1 -1
- package/dist/agent-runner.d.ts +0 -4
- package/dist/agent-runner.js +0 -122
- package/dist/ci/parse-inputs.d.ts +0 -6
- package/dist/ci/parse-inputs.js +0 -76
- package/dist/ci/parse-safety.d.ts +0 -6
- package/dist/ci/parse-safety.js +0 -22
- package/dist/cli/args.d.ts +0 -13
- package/dist/cli/args.js +0 -42
- package/dist/cli/litellm.d.ts +0 -2
- package/dist/cli/litellm.js +0 -85
- package/dist/cli/task-resolution.d.ts +0 -2
- package/dist/cli/task-resolution.js +0 -41
- package/dist/config.d.ts +0 -49
- package/dist/config.js +0 -72
- package/dist/context.d.ts +0 -4
- package/dist/context.js +0 -83
- package/dist/definitions.d.ts +0 -3
- package/dist/definitions.js +0 -59
- package/dist/entry.d.ts +0 -1
- package/dist/entry.js +0 -236
- package/dist/git-utils.d.ts +0 -13
- package/dist/git-utils.js +0 -174
- package/dist/github-api.d.ts +0 -14
- package/dist/github-api.js +0 -114
- package/dist/kody-utils.d.ts +0 -1
- package/dist/kody-utils.js +0 -9
- package/dist/learning/auto-learn.d.ts +0 -2
- package/dist/learning/auto-learn.js +0 -169
- package/dist/logger.d.ts +0 -14
- package/dist/logger.js +0 -51
- package/dist/memory.d.ts +0 -1
- package/dist/memory.js +0 -20
- package/dist/observer.d.ts +0 -9
- package/dist/observer.js +0 -80
- package/dist/pipeline/complexity.d.ts +0 -3
- package/dist/pipeline/complexity.js +0 -12
- package/dist/pipeline/executor-registry.d.ts +0 -3
- package/dist/pipeline/executor-registry.js +0 -20
- package/dist/pipeline/hooks.d.ts +0 -17
- package/dist/pipeline/hooks.js +0 -110
- package/dist/pipeline/questions.d.ts +0 -2
- package/dist/pipeline/questions.js +0 -44
- package/dist/pipeline/runner-selection.d.ts +0 -2
- package/dist/pipeline/runner-selection.js +0 -13
- package/dist/pipeline/state.d.ts +0 -4
- package/dist/pipeline/state.js +0 -37
- package/dist/pipeline.d.ts +0 -3
- package/dist/pipeline.js +0 -213
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.js +0 -69
- package/dist/retrospective.d.ts +0 -26
- package/dist/retrospective.js +0 -211
- package/dist/stages/agent.d.ts +0 -2
- package/dist/stages/agent.js +0 -94
- package/dist/stages/gate.d.ts +0 -2
- package/dist/stages/gate.js +0 -32
- package/dist/stages/review.d.ts +0 -2
- package/dist/stages/review.js +0 -32
- package/dist/stages/ship.d.ts +0 -3
- package/dist/stages/ship.js +0 -154
- package/dist/stages/verify.d.ts +0 -2
- package/dist/stages/verify.js +0 -94
- package/dist/types.d.ts +0 -61
- package/dist/types.js +0 -1
- package/dist/validators.d.ts +0 -8
- package/dist/validators.js +0 -42
- package/dist/verify-runner.d.ts +0 -11
- package/dist/verify-runner.js +0 -110
package/README.md
CHANGED
|
@@ -7,25 +7,19 @@
|
|
|
7
7
|
|
|
8
8
|
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 shared context between stages.
|
|
9
9
|
|
|
10
|
+
> **Kody is the only AI coding tool that generates repo-customized prompts.** Every other tool sends the same generic instructions regardless of your codebase. Kody analyzes your repo's patterns, conventions, and gaps — then generates tailored instruction files for every pipeline stage. The AI writes code that looks like it belongs in your project because it was taught *from* your project. [Learn more →](docs/FEATURES.md#repo-aware-step-files-kodysteps)
|
|
11
|
+
|
|
10
12
|
## Why Kody?
|
|
11
13
|
|
|
12
14
|
Most AI coding tools are **autocomplete** (Copilot) or **chat-based** (Cursor, Cline). You still drive. Kody is an **autonomous pipeline** — comment `@kody`, walk away, come back to a PR.
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| **Risk gate** | Pauses HIGH-risk for human approval | No | No | No |
|
|
22
|
-
| **AI failure diagnosis** | Classifies errors before retry (fixable/infra/abort) | No | No | No |
|
|
23
|
-
| **Incremental codebase improvement** | Step files encode gaps to fix — every task raises quality | No | No | No |
|
|
24
|
-
| **Model flexible** | Any LLM via LiteLLM | GitHub models only | Proprietary | Cursor models |
|
|
25
|
-
| **Open source** | MIT | Proprietary | Proprietary | Proprietary |
|
|
26
|
-
| **Cost** | **Free** with free-tier models (Gemini, etc.) or pay-per-use with any LLM | $10-39/month | $20-500/month | Subscription |
|
|
27
|
-
|
|
28
|
-
[Full comparison →](docs/COMPARISON.md)
|
|
16
|
+
- **Repo-aware prompts** — auto-generated step files with your repo's patterns, gaps, and acceptance criteria
|
|
17
|
+
- **7 stages with quality gates** — not a single agent conversation
|
|
18
|
+
- **Fire and forget** — runs in GitHub Actions, no IDE required
|
|
19
|
+
- **Any LLM** — route through LiteLLM to use MiniMax, GPT, Gemini, or local models
|
|
20
|
+
- **Free** with free-tier models — no subscriptions, no per-seat pricing
|
|
21
|
+
|
|
22
|
+
[How Kody compares to Copilot, Devin, Cursor, OpenHands, and others →](docs/COMPARISON.md)
|
|
29
23
|
|
|
30
24
|
## Pipeline
|
|
31
25
|
|
|
@@ -147,99 +141,44 @@ Set the `provider` field in `kody.config.json` — Kody auto-generates the LiteL
|
|
|
147
141
|
|
|
148
142
|
Add the provider's API key to `.env`:
|
|
149
143
|
```
|
|
150
|
-
|
|
144
|
+
ANTHROPIC_COMPATIBLE_API_KEY=your-key-here
|
|
151
145
|
```
|
|
152
146
|
|
|
153
|
-
That's it. Kody auto-starts the LiteLLM proxy and loads API keys from `.env`. For
|
|
147
|
+
That's it. Kody auto-starts the LiteLLM proxy and loads API keys from `.env`. For per-tier model control, configure `modelMap` in `kody.config.json`. [Full LiteLLM guide →](docs/LITELLM.md)
|
|
154
148
|
|
|
155
149
|
## Commands
|
|
156
150
|
|
|
157
|
-
### GitHub Comments
|
|
158
|
-
|
|
159
151
|
| Command | What it does |
|
|
160
152
|
|---------|-------------|
|
|
161
|
-
| `@kody` | Run full pipeline |
|
|
153
|
+
| `@kody` | Run full pipeline on an issue |
|
|
162
154
|
| `@kody approve` | Resume after questions or risk gate |
|
|
163
|
-
| `@kody fix` | Re-run from build
|
|
155
|
+
| `@kody fix` | Re-run from build. Reads PR review comments as context |
|
|
156
|
+
| `@kody fix-ci` | Fix failing CI checks (auto-triggered on Kody PRs) |
|
|
164
157
|
| `@kody rerun` | Resume from the failed or paused stage |
|
|
165
|
-
| `@kody rerun --from <stage>` | Resume from a specific stage |
|
|
166
158
|
| `@kody bootstrap` | Regenerate project memory and step files |
|
|
167
159
|
|
|
168
|
-
### CLI
|
|
169
|
-
|
|
170
160
|
```bash
|
|
171
|
-
kody-engine-lite init [--force]
|
|
172
|
-
kody-engine-lite
|
|
173
|
-
kody-engine-lite run --issue-number 42 --local --cwd ./project
|
|
174
|
-
kody-engine-lite run --task "Add retry utility" --local
|
|
161
|
+
kody-engine-lite init [--force] # Setup repo
|
|
162
|
+
kody-engine-lite run --issue-number 42 --local
|
|
175
163
|
kody-engine-lite fix --issue-number 42 --feedback "Use middleware pattern"
|
|
164
|
+
kody-engine-lite fix-ci --pr-number 42
|
|
176
165
|
kody-engine-lite rerun --issue-number 42 --from verify
|
|
177
|
-
kody-engine-lite status --task-id 42-260327-102254
|
|
178
166
|
```
|
|
179
167
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
### Repo-Aware Step Files (`.kody/steps/`)
|
|
183
|
-
|
|
184
|
-
**This is what separates Kody from every other AI coding tool.**
|
|
185
|
-
|
|
186
|
-
Most AI tools send the same generic prompt regardless of whether they're working on a Next.js app, a Go microservice, or a Python ML pipeline. Kody generates **customized instruction files for every pipeline stage**, tailored to your specific repository.
|
|
168
|
+
[Full CLI reference with all flags and options →](docs/CLI.md)
|
|
187
169
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
| Step File | What It Controls |
|
|
191
|
-
|-----------|-----------------|
|
|
192
|
-
| `taskify.md` | How tasks are classified and scoped for this repo |
|
|
193
|
-
| `plan.md` | Planning guidelines with your repo's architecture in mind |
|
|
194
|
-
| `build.md` | Coding instructions with your actual patterns as examples |
|
|
195
|
-
| `autofix.md` | How to fix verification failures using your toolchain |
|
|
196
|
-
| `review.md` | Review checklist calibrated to your repo's quality bar |
|
|
197
|
-
| `review-fix.md` | How to address review findings in your codebase |
|
|
198
|
-
|
|
199
|
-
Each step file contains three repo-specific sections:
|
|
200
|
-
- **Repo Patterns** — real code examples from your codebase (file paths, function signatures, snippets) showing what "good" looks like in your project
|
|
201
|
-
- **Improvement Areas** — gaps, anti-patterns, and inconsistencies the AI should fix when it touches related code (e.g., "collections missing access control", "services not using DI")
|
|
202
|
-
- **Acceptance Criteria** — a concrete checklist that defines "done" for each stage in your specific repo
|
|
203
|
-
|
|
204
|
-
**Why this matters:**
|
|
205
|
-
- Code produced by Kody **matches your existing patterns** — same naming, same abstractions, same file organization
|
|
206
|
-
- Known gaps get **incrementally fixed** — every task that touches a weak area leaves it better
|
|
207
|
-
- Quality bar is **explicit and auditable** — acceptance criteria are version-controlled in your repo, not hidden in a prompt
|
|
208
|
-
- **You own and customize** the instructions — edit `.kody/steps/build.md` to change how Kody writes code, no engine changes needed
|
|
209
|
-
- Re-run `kody init --force` after major refactors to **regenerate** step files from current state
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
# Example: .kody/steps/build.md (auto-generated, then customizable)
|
|
213
|
-
---
|
|
214
|
-
name: build
|
|
215
|
-
...
|
|
216
|
-
---
|
|
217
|
-
<original engine prompt preserved>
|
|
218
|
-
|
|
219
|
-
## Repo Patterns
|
|
220
|
-
**Collection pattern** (`src/collections/certificates.ts`):
|
|
221
|
-
- Always cast `relationTo` with `as CollectionSlug`
|
|
222
|
-
- Register every new collection in `src/payload.config.ts`
|
|
223
|
-
|
|
224
|
-
## Improvement Areas
|
|
225
|
-
- Collections missing access control — add `access` guards when touching
|
|
226
|
-
- Run `pnpm payload generate:types` after any schema change
|
|
227
|
-
|
|
228
|
-
## Acceptance Criteria
|
|
229
|
-
- [ ] `pnpm tsc --noEmit` exits with zero errors
|
|
230
|
-
- [ ] All new collections include explicit access control
|
|
231
|
-
- [ ] User-supplied strings sanitized via `src/security/sanitizers.ts`
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### Other Features
|
|
170
|
+
## Key Features
|
|
235
171
|
|
|
236
|
-
- **
|
|
237
|
-
- **
|
|
238
|
-
- **
|
|
239
|
-
- **
|
|
240
|
-
- **
|
|
241
|
-
- **
|
|
242
|
-
- **
|
|
172
|
+
- **[Repo-Aware Step Files](docs/FEATURES.md#repo-aware-step-files-kodysteps)** — auto-generated per-stage instructions grounded in your actual code patterns, gaps, and acceptance criteria. Edit `.kody/steps/*.md` to customize how Kody works in your repo.
|
|
173
|
+
- **[Shared Sessions](docs/FEATURES.md#shared-sessions)** — stages in the same group share a Claude Code session, eliminating cold-start re-exploration
|
|
174
|
+
- **[Risk Gate](docs/FEATURES.md#risk-gate)** — HIGH-risk tasks pause for human approval before building
|
|
175
|
+
- **[AI Failure Diagnosis](docs/FEATURES.md#ai-powered-failure-diagnosis)** — classifies errors (fixable/infrastructure/pre-existing/abort) before retry
|
|
176
|
+
- **[Question Gates](docs/FEATURES.md#question-gates)** — asks product/architecture questions when the task is unclear
|
|
177
|
+
- **[Any LLM](docs/LITELLM.md)** — route through LiteLLM to use MiniMax, GPT, Gemini, local models
|
|
178
|
+
- **[Retrospective](docs/FEATURES.md#retrospective-system)** — analyzes each run, identifies patterns, suggests improvements
|
|
179
|
+
- **[Auto-Learning](docs/FEATURES.md#auto-learning-memory)** — extracts coding conventions from each successful run
|
|
180
|
+
- **[Pattern Discovery](docs/FEATURES.md#pattern-discovery)** — plan stage searches for existing patterns before proposing new approaches
|
|
181
|
+
- **[Decision Memory](docs/FEATURES.md#decision-memory)** — architectural decisions from code reviews are saved and enforced in future tasks
|
|
243
182
|
|
|
244
183
|
## Documentation
|
|
245
184
|
|
|
@@ -247,9 +186,10 @@ name: build
|
|
|
247
186
|
|-----|-------------|
|
|
248
187
|
| [Pipeline](docs/PIPELINE.md) | Stage details, shared sessions, complexity skipping, artifacts |
|
|
249
188
|
| [Bootstrap](docs/BOOTSTRAP.md) | Project memory, step files, labels — what bootstrap generates and when to run it |
|
|
250
|
-
| [Features](docs/FEATURES.md) | Risk gate, diagnosis, sessions, retrospective, auto-learn,
|
|
189
|
+
| [Features](docs/FEATURES.md) | Risk gate, diagnosis, sessions, retrospective, auto-learn, pattern discovery, decision memory, PR feedback |
|
|
251
190
|
| [LiteLLM](docs/LITELLM.md) | Non-Anthropic model setup, auto-start, tested providers |
|
|
252
|
-
| [
|
|
191
|
+
| [CLI](docs/CLI.md) | Full command reference — all flags, env vars, and examples |
|
|
192
|
+
| [Configuration](docs/CONFIGURATION.md) | Config file reference, env vars, workflow setup |
|
|
253
193
|
| [Comparison](docs/COMPARISON.md) | vs Copilot, Devin, Cursor, Cline, OpenHands, SWE-agent |
|
|
254
194
|
| [Architecture](docs/ARCHITECTURE.md) | Source tree, state machine diagram, development guide |
|
|
255
195
|
| [FAQ](docs/FAQ.md) | Common questions about usage, models, security, cost |
|
package/dist/bin/cli.js
CHANGED
|
@@ -3400,6 +3400,35 @@ async function checkLitellmHealth(url) {
|
|
|
3400
3400
|
return false;
|
|
3401
3401
|
}
|
|
3402
3402
|
}
|
|
3403
|
+
async function checkModelHealth(baseUrl, apiKey, model = "claude-haiku-4-5") {
|
|
3404
|
+
try {
|
|
3405
|
+
const res = await fetch(`${baseUrl}/v1/messages`, {
|
|
3406
|
+
method: "POST",
|
|
3407
|
+
headers: {
|
|
3408
|
+
"Content-Type": "application/json",
|
|
3409
|
+
"x-api-key": apiKey,
|
|
3410
|
+
"anthropic-version": "2023-06-01"
|
|
3411
|
+
},
|
|
3412
|
+
body: JSON.stringify({
|
|
3413
|
+
model,
|
|
3414
|
+
max_tokens: 4,
|
|
3415
|
+
messages: [{ role: "user", content: "Reply with: ok" }]
|
|
3416
|
+
}),
|
|
3417
|
+
signal: AbortSignal.timeout(3e4)
|
|
3418
|
+
});
|
|
3419
|
+
if (!res.ok) {
|
|
3420
|
+
const body2 = await res.text().catch(() => "");
|
|
3421
|
+
return { ok: false, error: `HTTP ${res.status}: ${body2.slice(0, 200)}` };
|
|
3422
|
+
}
|
|
3423
|
+
const body = await res.json();
|
|
3424
|
+
if (!body.content?.[0]?.text) {
|
|
3425
|
+
return { ok: false, error: "Empty response from model" };
|
|
3426
|
+
}
|
|
3427
|
+
return { ok: true };
|
|
3428
|
+
} catch (err) {
|
|
3429
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3403
3432
|
function generateLitellmConfig(provider, modelMap) {
|
|
3404
3433
|
const apiKeyVar = providerApiKeyEnvVar(provider);
|
|
3405
3434
|
const entries = ["model_list:"];
|
|
@@ -3416,17 +3445,12 @@ function generateLitellmConfig(provider, modelMap) {
|
|
|
3416
3445
|
return entries.join("\n") + "\n";
|
|
3417
3446
|
}
|
|
3418
3447
|
async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
if (fs19.existsSync(manualConfigPath)) {
|
|
3422
|
-
configPath = manualConfigPath;
|
|
3423
|
-
} else if (generatedConfig) {
|
|
3424
|
-
configPath = path18.join(os.tmpdir(), "kody-litellm-config.yaml");
|
|
3425
|
-
fs19.writeFileSync(configPath, generatedConfig);
|
|
3426
|
-
} else {
|
|
3427
|
-
logger.warn("litellm-config.yaml not found and no provider configured \u2014 cannot start proxy");
|
|
3448
|
+
if (!generatedConfig) {
|
|
3449
|
+
logger.warn("No provider configured in kody.config.json \u2014 cannot start LiteLLM proxy");
|
|
3428
3450
|
return null;
|
|
3429
3451
|
}
|
|
3452
|
+
const configPath = path18.join(os.tmpdir(), "kody-litellm-config.yaml");
|
|
3453
|
+
fs19.writeFileSync(configPath, generatedConfig);
|
|
3430
3454
|
const portMatch = url.match(/:(\d+)/);
|
|
3431
3455
|
const port = portMatch ? portMatch[1] : "4000";
|
|
3432
3456
|
let litellmFound = false;
|
|
@@ -3600,6 +3624,25 @@ async function ensureLitellmProxy(config, projectDir) {
|
|
|
3600
3624
|
}
|
|
3601
3625
|
return litellmProcess;
|
|
3602
3626
|
}
|
|
3627
|
+
async function runModelHealthCheck(config) {
|
|
3628
|
+
const usesProxy = needsLitellmProxy(config);
|
|
3629
|
+
const baseUrl = usesProxy ? getLitellmUrl() : "https://api.anthropic.com";
|
|
3630
|
+
const apiKey = usesProxy ? process.env.ANTHROPIC_COMPATIBLE_API_KEY : process.env.ANTHROPIC_API_KEY;
|
|
3631
|
+
if (!apiKey) {
|
|
3632
|
+
const keyName = usesProxy ? "ANTHROPIC_COMPATIBLE_API_KEY" : "ANTHROPIC_API_KEY";
|
|
3633
|
+
logger.warn(`Skipping model health check \u2014 ${keyName} not set`);
|
|
3634
|
+
return;
|
|
3635
|
+
}
|
|
3636
|
+
const model = config.agent.modelMap.cheap;
|
|
3637
|
+
logger.info(`Model health check (${model} via ${usesProxy ? "LiteLLM" : "Anthropic"})...`);
|
|
3638
|
+
const result = await checkModelHealth(baseUrl, apiKey, model);
|
|
3639
|
+
if (result.ok) {
|
|
3640
|
+
logger.info(" \u2713 Model responded");
|
|
3641
|
+
} else {
|
|
3642
|
+
logger.error(` \u2717 Model health check failed: ${result.error}`);
|
|
3643
|
+
process.exit(1);
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3603
3646
|
async function main() {
|
|
3604
3647
|
const input = parseArgs();
|
|
3605
3648
|
const projectDir = input.cwd ? path20.resolve(input.cwd) : process.cwd();
|
|
@@ -3693,6 +3736,7 @@ async function main() {
|
|
|
3693
3736
|
}
|
|
3694
3737
|
const config2 = getProjectConfig();
|
|
3695
3738
|
const litellmProcess2 = await ensureLitellmProxy(config2, projectDir);
|
|
3739
|
+
await runModelHealthCheck(config2);
|
|
3696
3740
|
const runners2 = createRunners(config2);
|
|
3697
3741
|
const defaultRunnerName2 = config2.agent.defaultRunner ?? Object.keys(runners2)[0] ?? "claude";
|
|
3698
3742
|
const defaultRunner2 = runners2[defaultRunnerName2];
|
|
@@ -3825,6 +3869,7 @@ ${input.feedback}`);
|
|
|
3825
3869
|
}
|
|
3826
3870
|
const config = getProjectConfig();
|
|
3827
3871
|
let litellmProcess = await ensureLitellmProxy(config, projectDir);
|
|
3872
|
+
await runModelHealthCheck(config);
|
|
3828
3873
|
const cleanupLitellm = () => {
|
|
3829
3874
|
if (litellmProcess) {
|
|
3830
3875
|
litellmProcess.kill?.();
|
package/package.json
CHANGED
package/dist/agent-runner.d.ts
DELETED
package/dist/agent-runner.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
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/ci/parse-inputs.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
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");
|
package/dist/ci/parse-safety.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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", "");
|
package/dist/cli/args.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/cli/litellm.d.ts
DELETED