@kody-ade/kody-engine-lite 0.1.104 → 0.1.106
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/dist/bin/cli.js +626 -171
- package/kody.config.schema.json +5 -0
- package/package.json +1 -1
- package/prompts/taskify.md +0 -5
- package/templates/kody.yml +30 -103
- 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/kody.config.schema.json
CHANGED
|
@@ -201,6 +201,11 @@
|
|
|
201
201
|
"readyTimeout": {
|
|
202
202
|
"type": "number",
|
|
203
203
|
"description": "Seconds to wait for the server to be ready. Default: 30"
|
|
204
|
+
},
|
|
205
|
+
"env": {
|
|
206
|
+
"type": "array",
|
|
207
|
+
"items": { "type": "string" },
|
|
208
|
+
"description": "List of GitHub secret names to forward as environment variables for the dev server process (e.g., ['BLOB_READ_WRITE_TOKEN', 'DATABASE_URL']). These are injected into the workflow env block during 'kody init'."
|
|
204
209
|
}
|
|
205
210
|
},
|
|
206
211
|
"required": ["command", "url"]
|
package/package.json
CHANGED
package/prompts/taskify.md
CHANGED
|
@@ -26,15 +26,10 @@ Required JSON format:
|
|
|
26
26
|
"description": "Clear description of what the task requires",
|
|
27
27
|
"scope": ["list", "of", "exact/file/paths", "affected"],
|
|
28
28
|
"risk_level": "low | medium | high",
|
|
29
|
-
"hasUI": true,
|
|
30
29
|
"existing_patterns": ["list of existing patterns found that the implementation should reuse"],
|
|
31
30
|
"questions": []
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
hasUI heuristics:
|
|
35
|
-
- true: task touches frontend files (.tsx, .jsx, .vue, .svelte, .css, .scss, .html), UI components, pages, layouts, or styles
|
|
36
|
-
- false: task is purely backend, CLI, API, database, config, docs, or infrastructure
|
|
37
|
-
|
|
38
33
|
Risk level heuristics:
|
|
39
34
|
- low: single file change, no breaking changes, docs, config, isolated scripts, test additions, style changes
|
|
40
35
|
- medium: multiple files, possible side effects, API changes, new dependencies, refactoring existing logic
|
package/templates/kody.yml
CHANGED
|
@@ -103,107 +103,11 @@ jobs:
|
|
|
103
103
|
if: steps.safety.outputs.valid == 'true'
|
|
104
104
|
id: parse
|
|
105
105
|
env:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# Extract: @kody [mode] [task-id] [--from stage]
|
|
112
|
-
KODY_ARGS=$(echo "$BODY" | grep -oP '(?:@kody|/kody)\s+\K.*' || echo "")
|
|
113
|
-
MODE=$(echo "$KODY_ARGS" | awk '{print $1}')
|
|
114
|
-
RAW_TASK_ID=$(echo "$KODY_ARGS" | awk '{print $2}')
|
|
115
|
-
# Don't treat flags (--from, --feedback) as task IDs
|
|
116
|
-
case "$RAW_TASK_ID" in
|
|
117
|
-
--*) TASK_ID="" ;;
|
|
118
|
-
*) TASK_ID="$RAW_TASK_ID" ;;
|
|
119
|
-
esac
|
|
120
|
-
FROM_STAGE=$(echo "$KODY_ARGS" | grep -oP '(?<=--from )\S+' || echo "")
|
|
121
|
-
FEEDBACK=$(echo "$KODY_ARGS" | grep -oP '(?<=--feedback ")[^"]*' || echo "")
|
|
122
|
-
COMPLEXITY=""
|
|
123
|
-
if echo "$KODY_ARGS" | grep -q -- '--complexity'; then
|
|
124
|
-
COMPLEXITY=$(echo "$KODY_ARGS" | tr ' ' '\n' | grep -A1 -- '--complexity' | tail -1)
|
|
125
|
-
fi
|
|
126
|
-
DRY_RUN="false"
|
|
127
|
-
if echo "$KODY_ARGS" | grep -q -- '--dry-run'; then
|
|
128
|
-
DRY_RUN="true"
|
|
129
|
-
fi
|
|
130
|
-
|
|
131
|
-
# Validate mode
|
|
132
|
-
case "$MODE" in
|
|
133
|
-
full|rerun|fix|fix-ci|status|approve|review|resolve|bootstrap) ;;
|
|
134
|
-
*)
|
|
135
|
-
# If first arg isn't a mode, it might be a task-id or nothing
|
|
136
|
-
if [ -n "$MODE" ] && [ "$MODE" != "" ]; then
|
|
137
|
-
TASK_ID="$MODE"
|
|
138
|
-
fi
|
|
139
|
-
MODE="full"
|
|
140
|
-
;;
|
|
141
|
-
esac
|
|
142
|
-
|
|
143
|
-
ISSUE_NUM="${{ github.event.issue.number }}"
|
|
144
|
-
|
|
145
|
-
# For approve mode: extract answer body and convert to rerun
|
|
146
|
-
# Must run BEFORE task-id generation so we don't create a new task
|
|
147
|
-
# approve: extract answers, convert to rerun
|
|
148
|
-
if [ "$MODE" = "approve" ]; then
|
|
149
|
-
APPROVE_BODY=$(echo "$BODY" | sed -n '/\(@kody\|\/kody\)\s*approve/,$p' | tail -n +2)
|
|
150
|
-
FEEDBACK="$APPROVE_BODY"
|
|
151
|
-
MODE="rerun"
|
|
152
|
-
fi
|
|
153
|
-
|
|
154
|
-
# fix: extract description as feedback, convert to fix command
|
|
155
|
-
if [ "$MODE" = "fix" ]; then
|
|
156
|
-
FIX_BODY=$(echo "$BODY" | sed -n '/\(@kody\|\/kody\)\s*fix/,$p' | tail -n +2)
|
|
157
|
-
if [ -n "$FIX_BODY" ]; then
|
|
158
|
-
FEEDBACK="$FIX_BODY"
|
|
159
|
-
fi
|
|
160
|
-
# Leave TASK_ID empty — entry.ts finds latest task for issue
|
|
161
|
-
fi
|
|
162
|
-
|
|
163
|
-
# fix-ci: extract body as feedback + CI run ID
|
|
164
|
-
if [ "$MODE" = "fix-ci" ]; then
|
|
165
|
-
FIX_CI_BODY=$(echo "$BODY" | sed -n '/\(@kody\|\/kody\)\s*fix-ci/,$p' | tail -n +2)
|
|
166
|
-
if [ -n "$FIX_CI_BODY" ]; then
|
|
167
|
-
FEEDBACK="$FIX_CI_BODY"
|
|
168
|
-
fi
|
|
169
|
-
CI_RUN_ID=$(echo "$FIX_CI_BODY" | grep -oP 'Run ID:\s*\K\d+' || echo "")
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
# Bootstrap mode: set task-id and skip normal pipeline
|
|
173
|
-
if [ "$MODE" = "bootstrap" ]; then
|
|
174
|
-
TASK_ID="bootstrap-$(date +%y%m%d-%H%M%S)"
|
|
175
|
-
fi
|
|
176
|
-
|
|
177
|
-
# Auto-generate task-id if not provided (only for full mode)
|
|
178
|
-
if [ -z "$TASK_ID" ] && [ "$MODE" = "full" ]; then
|
|
179
|
-
TASK_ID="${ISSUE_NUM}-$(date +%y%m%d-%H%M%S)"
|
|
180
|
-
fi
|
|
181
|
-
|
|
182
|
-
# Detect if this comment is on a PR (PRs are issues in GitHub API)
|
|
183
|
-
PR_NUMBER=""
|
|
184
|
-
if [ -n "${{ github.event.issue.pull_request }}" ]; then
|
|
185
|
-
PR_NUMBER="$ISSUE_NUM"
|
|
186
|
-
fi
|
|
187
|
-
|
|
188
|
-
# For review mode on a PR comment: use the PR number directly
|
|
189
|
-
if [ "$MODE" = "review" ] && [ -n "$PR_NUMBER" ]; then
|
|
190
|
-
TASK_ID="review-pr-${PR_NUMBER}-$(date +%y%m%d-%H%M%S)"
|
|
191
|
-
fi
|
|
192
|
-
|
|
193
|
-
echo "task_id=$TASK_ID" >> $GITHUB_OUTPUT
|
|
194
|
-
echo "mode=$MODE" >> $GITHUB_OUTPUT
|
|
195
|
-
echo "from_stage=$FROM_STAGE" >> $GITHUB_OUTPUT
|
|
196
|
-
echo "issue_number=$ISSUE_NUM" >> $GITHUB_OUTPUT
|
|
197
|
-
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
|
198
|
-
{
|
|
199
|
-
echo "feedback<<KODY_EOF"
|
|
200
|
-
echo "$FEEDBACK"
|
|
201
|
-
echo "KODY_EOF"
|
|
202
|
-
} >> $GITHUB_OUTPUT
|
|
203
|
-
echo "complexity=$COMPLEXITY" >> $GITHUB_OUTPUT
|
|
204
|
-
echo "ci_run_id=${CI_RUN_ID:-}" >> $GITHUB_OUTPUT
|
|
205
|
-
echo "dry_run=$DRY_RUN" >> $GITHUB_OUTPUT
|
|
206
|
-
echo "valid=true" >> $GITHUB_OUTPUT
|
|
106
|
+
COMMENT_BODY: ${{ github.event.comment.body }}
|
|
107
|
+
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
108
|
+
ISSUE_IS_PR: ${{ github.event.issue.pull_request }}
|
|
109
|
+
TRIGGER_TYPE: comment
|
|
110
|
+
run: kody-engine-lite ci-parse
|
|
207
111
|
|
|
208
112
|
# ─── Orchestrate ─────────────────────────────────────────────────────────────
|
|
209
113
|
orchestrate:
|
|
@@ -262,10 +166,21 @@ jobs:
|
|
|
262
166
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
263
167
|
git config user.name "github-actions[bot]"
|
|
264
168
|
|
|
169
|
+
- name: Export project secrets
|
|
170
|
+
env:
|
|
171
|
+
ALL_SECRETS: ${{ toJSON(secrets) }}
|
|
172
|
+
run: |
|
|
173
|
+
echo "$ALL_SECRETS" | jq -r 'to_entries[] | select(.key | test("^(GITHUB_TOKEN)$") | not) | @json' | while IFS= read -r entry; do
|
|
174
|
+
KEY=$(echo "$entry" | jq -r '.key')
|
|
175
|
+
VALUE=$(echo "$entry" | jq -r '.value')
|
|
176
|
+
DELIM="KODY_EOF_${KEY}"
|
|
177
|
+
echo "${KEY}<<${DELIM}" >> $GITHUB_ENV
|
|
178
|
+
echo "${VALUE}" >> $GITHUB_ENV
|
|
179
|
+
echo "${DELIM}" >> $GITHUB_ENV
|
|
180
|
+
done
|
|
181
|
+
|
|
265
182
|
- name: Run Kody pipeline
|
|
266
183
|
env:
|
|
267
|
-
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
268
|
-
ANTHROPIC_COMPATIBLE_API_KEY: ${{ secrets.ANTHROPIC_COMPATIBLE_API_KEY }}
|
|
269
184
|
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
270
185
|
TASK_ID: ${{ github.event.inputs.task_id || needs.parse.outputs.task_id }}
|
|
271
186
|
MODE: ${{ github.event.inputs.mode || needs.parse.outputs.mode || 'full' }}
|
|
@@ -342,10 +257,22 @@ jobs:
|
|
|
342
257
|
github.event_name == 'pull_request' &&
|
|
343
258
|
github.event.pull_request.merged == true
|
|
344
259
|
runs-on: ubuntu-latest
|
|
260
|
+
permissions:
|
|
261
|
+
issues: write
|
|
345
262
|
steps:
|
|
263
|
+
- name: Generate App token
|
|
264
|
+
id: app-token
|
|
265
|
+
if: vars.KODY_APP_ID != ''
|
|
266
|
+
uses: actions/create-github-app-token@v1
|
|
267
|
+
with:
|
|
268
|
+
app-id: ${{ vars.KODY_APP_ID }}
|
|
269
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
270
|
+
repositories: ${{ github.event.repository.name }}
|
|
271
|
+
|
|
346
272
|
- name: Close linked issue
|
|
347
273
|
uses: actions/github-script@v7
|
|
348
274
|
with:
|
|
275
|
+
github-token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
349
276
|
script: |
|
|
350
277
|
// Extract issue number from branch name (e.g. "42--feature-name")
|
|
351
278
|
const branch = context.payload.pull_request.head.ref;
|
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
package/dist/cli/litellm.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { execFileSync } from "child_process";
|
|
4
|
-
import { logger } from "../logger.js";
|
|
5
|
-
export async function checkLitellmHealth(url) {
|
|
6
|
-
try {
|
|
7
|
-
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3000) });
|
|
8
|
-
return response.ok;
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
export async function tryStartLitellm(url, projectDir) {
|
|
15
|
-
const configPath = path.join(projectDir, "litellm-config.yaml");
|
|
16
|
-
if (!fs.existsSync(configPath)) {
|
|
17
|
-
logger.warn("litellm-config.yaml not found — cannot start proxy");
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
// Extract port from URL
|
|
21
|
-
const portMatch = url.match(/:(\d+)/);
|
|
22
|
-
const port = portMatch ? portMatch[1] : "4000";
|
|
23
|
-
// Check if litellm is installed
|
|
24
|
-
try {
|
|
25
|
-
execFileSync("litellm", ["--version"], { timeout: 5000, stdio: "pipe" });
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
try {
|
|
29
|
-
execFileSync("python3", ["-m", "litellm", "--version"], { timeout: 5000, stdio: "pipe" });
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
logger.warn("litellm not installed (pip install 'litellm[proxy]')");
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
logger.info(`Starting LiteLLM proxy on port ${port}...`);
|
|
37
|
-
// Determine command
|
|
38
|
-
let cmd;
|
|
39
|
-
let args;
|
|
40
|
-
try {
|
|
41
|
-
execFileSync("litellm", ["--version"], { timeout: 5000, stdio: "pipe" });
|
|
42
|
-
cmd = "litellm";
|
|
43
|
-
args = ["--config", configPath, "--port", port];
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
cmd = "python3";
|
|
47
|
-
args = ["-m", "litellm", "--config", configPath, "--port", port];
|
|
48
|
-
}
|
|
49
|
-
// Load API key env vars from project .env (only *_API_KEY patterns)
|
|
50
|
-
const dotenvPath = path.join(projectDir, ".env");
|
|
51
|
-
const dotenvVars = {};
|
|
52
|
-
if (fs.existsSync(dotenvPath)) {
|
|
53
|
-
for (const line of fs.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
54
|
-
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
55
|
-
if (match)
|
|
56
|
-
dotenvVars[match[1]] = match[2];
|
|
57
|
-
}
|
|
58
|
-
if (Object.keys(dotenvVars).length > 0) {
|
|
59
|
-
logger.info(` Loaded API keys: ${Object.keys(dotenvVars).join(", ")}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const { spawn } = await import("child_process");
|
|
63
|
-
const child = spawn(cmd, args, {
|
|
64
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
65
|
-
detached: true,
|
|
66
|
-
env: { ...process.env, ...dotenvVars },
|
|
67
|
-
});
|
|
68
|
-
// Capture stderr for debugging
|
|
69
|
-
let proxyStderr = "";
|
|
70
|
-
child.stderr?.on("data", (chunk) => { proxyStderr += chunk.toString(); });
|
|
71
|
-
// Wait for health
|
|
72
|
-
for (let i = 0; i < 30; i++) {
|
|
73
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
74
|
-
if (await checkLitellmHealth(url)) {
|
|
75
|
-
logger.info(`LiteLLM proxy ready at ${url}`);
|
|
76
|
-
return child;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (proxyStderr) {
|
|
80
|
-
logger.warn(`LiteLLM stderr: ${proxyStderr.slice(-1000)}`);
|
|
81
|
-
}
|
|
82
|
-
logger.warn("LiteLLM proxy failed to start within 60s");
|
|
83
|
-
child.kill();
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { execFileSync } from "child_process";
|
|
4
|
-
export function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
5
|
-
const tasksDir = path.join(projectDir, ".tasks");
|
|
6
|
-
if (!fs.existsSync(tasksDir))
|
|
7
|
-
return null;
|
|
8
|
-
// Only consider directories (not files)
|
|
9
|
-
const allDirs = fs.readdirSync(tasksDir, { withFileTypes: true })
|
|
10
|
-
.filter((d) => d.isDirectory())
|
|
11
|
-
.map((d) => d.name)
|
|
12
|
-
.sort()
|
|
13
|
-
.reverse();
|
|
14
|
-
// Direct match: tasks starting with issue number
|
|
15
|
-
const prefix = `${issueNumber}-`;
|
|
16
|
-
const direct = allDirs.find((d) => d.startsWith(prefix));
|
|
17
|
-
if (direct)
|
|
18
|
-
return direct;
|
|
19
|
-
// Fallback for PR comments: extract issue number from current git branch
|
|
20
|
-
// Branch format: <issueNum>--<slug> (e.g., 1031--security-8x-route)
|
|
21
|
-
try {
|
|
22
|
-
const branch = execFileSync("git", ["branch", "--show-current"], {
|
|
23
|
-
encoding: "utf-8", cwd: projectDir, timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
24
|
-
}).trim();
|
|
25
|
-
const branchIssueMatch = branch.match(/^(\d+)-/);
|
|
26
|
-
if (branchIssueMatch) {
|
|
27
|
-
const branchIssueNum = branchIssueMatch[1];
|
|
28
|
-
const branchPrefix = `${branchIssueNum}-`;
|
|
29
|
-
const fromBranch = allDirs.find((d) => d.startsWith(branchPrefix));
|
|
30
|
-
if (fromBranch)
|
|
31
|
-
return fromBranch;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
catch { /* ignore */ }
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
export function generateTaskId() {
|
|
38
|
-
const now = new Date();
|
|
39
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
40
|
-
return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
41
|
-
}
|