@kody-ade/kody-engine-lite 0.1.67 → 0.1.68
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 +78 -6
- package/package.json +9 -8
- package/templates/kody.yml +82 -3
package/dist/bin/cli.js
CHANGED
|
@@ -679,6 +679,45 @@ function submitPRReview(prNumber, body, event) {
|
|
|
679
679
|
logger.warn(` Failed to submit PR review: ${err}`);
|
|
680
680
|
}
|
|
681
681
|
}
|
|
682
|
+
function getCIFailureLogs(runId, maxLength = 8e3) {
|
|
683
|
+
try {
|
|
684
|
+
const logsOutput = gh([
|
|
685
|
+
"run",
|
|
686
|
+
"view",
|
|
687
|
+
String(runId),
|
|
688
|
+
"--log-failed"
|
|
689
|
+
]);
|
|
690
|
+
if (!logsOutput) return null;
|
|
691
|
+
const truncated = logsOutput.slice(-maxLength);
|
|
692
|
+
const prefix = logsOutput.length > maxLength ? "...(earlier output truncated)\n" : "";
|
|
693
|
+
return `${prefix}${truncated}`;
|
|
694
|
+
} catch (err) {
|
|
695
|
+
logger.warn(` Failed to get CI failure logs for run ${runId}: ${err}`);
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function getLatestFailedRunForBranch(branch) {
|
|
700
|
+
try {
|
|
701
|
+
const output = gh([
|
|
702
|
+
"run",
|
|
703
|
+
"list",
|
|
704
|
+
"--branch",
|
|
705
|
+
branch,
|
|
706
|
+
"--status",
|
|
707
|
+
"failure",
|
|
708
|
+
"--limit",
|
|
709
|
+
"1",
|
|
710
|
+
"--json",
|
|
711
|
+
"databaseId",
|
|
712
|
+
"--jq",
|
|
713
|
+
".[0].databaseId"
|
|
714
|
+
]);
|
|
715
|
+
return output.trim() || null;
|
|
716
|
+
} catch (err) {
|
|
717
|
+
logger.warn(` Failed to get latest failed run for branch ${branch}: ${err}`);
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
682
721
|
function getLatestKodyReviewComment(prNumber) {
|
|
683
722
|
try {
|
|
684
723
|
const output = gh([
|
|
@@ -3203,13 +3242,14 @@ function parseArgs() {
|
|
|
3203
3242
|
kody run --task-id <id> [--task "<desc>"] [--cwd <path>] [--issue-number <n>] [--complexity low|medium|high] [--feedback "<text>"] [--local] [--dry-run]
|
|
3204
3243
|
kody rerun --task-id <id> --from <stage> [--cwd <path>] [--issue-number <n>]
|
|
3205
3244
|
kody fix --task-id <id> [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
|
|
3245
|
+
kody fix-ci [--pr-number <n>] [--ci-run-id <id>] [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
|
|
3206
3246
|
kody review [--pr-number <n>] [--issue-number <n>] [--cwd <path>] [--local]
|
|
3207
3247
|
kody status --task-id <id> [--cwd <path>]
|
|
3208
3248
|
kody --help`);
|
|
3209
3249
|
process.exit(0);
|
|
3210
3250
|
}
|
|
3211
3251
|
const command2 = args2[0];
|
|
3212
|
-
if (!["run", "rerun", "fix", "status", "review"].includes(command2)) {
|
|
3252
|
+
if (!["run", "rerun", "fix", "fix-ci", "status", "review"].includes(command2)) {
|
|
3213
3253
|
console.error(`Unknown command: ${command2}`);
|
|
3214
3254
|
process.exit(1);
|
|
3215
3255
|
}
|
|
@@ -3227,7 +3267,8 @@ function parseArgs() {
|
|
|
3227
3267
|
prNumber: prStr ? parseInt(prStr, 10) : void 0,
|
|
3228
3268
|
feedback: getArg(args2, "--feedback") ?? process.env.FEEDBACK,
|
|
3229
3269
|
local: localFlag || !isCI2 && !hasFlag(args2, "--no-local"),
|
|
3230
|
-
complexity: getArg(args2, "--complexity") ?? process.env.COMPLEXITY
|
|
3270
|
+
complexity: getArg(args2, "--complexity") ?? process.env.COMPLEXITY,
|
|
3271
|
+
ciRunId: getArg(args2, "--ci-run-id") ?? process.env.CI_RUN_ID
|
|
3231
3272
|
};
|
|
3232
3273
|
}
|
|
3233
3274
|
var isCI2;
|
|
@@ -3463,7 +3504,7 @@ async function main() {
|
|
|
3463
3504
|
setGhCwd(projectDir);
|
|
3464
3505
|
logger.info(`Working directory: ${projectDir}`);
|
|
3465
3506
|
}
|
|
3466
|
-
const isPRFix = input.command === "fix" && !!input.prNumber;
|
|
3507
|
+
const isPRFix = (input.command === "fix" || input.command === "fix-ci") && !!input.prNumber;
|
|
3467
3508
|
if (input.issueNumber && input.command !== "review" && !isPRFix) {
|
|
3468
3509
|
const taskAction = resolveForIssue(input.issueNumber, projectDir);
|
|
3469
3510
|
logger.info(`Task action: ${taskAction.action}`);
|
|
@@ -3497,7 +3538,7 @@ async function main() {
|
|
|
3497
3538
|
let taskId = input.taskId;
|
|
3498
3539
|
if (!taskId) {
|
|
3499
3540
|
if (isPRFix) {
|
|
3500
|
-
taskId =
|
|
3541
|
+
taskId = `${input.command === "fix-ci" ? "fixci" : "fix"}-pr-${input.prNumber}-${generateTaskId()}`;
|
|
3501
3542
|
} else if (input.issueNumber) {
|
|
3502
3543
|
taskId = `${input.issueNumber}-${generateTaskId()}`;
|
|
3503
3544
|
} else if (input.command === "run" && input.task) {
|
|
@@ -3615,9 +3656,40 @@ ${issue.body ?? ""}`;
|
|
|
3615
3656
|
console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
|
|
3616
3657
|
process.exit(1);
|
|
3617
3658
|
}
|
|
3618
|
-
if (input.command === "fix" && !input.fromStage) {
|
|
3659
|
+
if ((input.command === "fix" || input.command === "fix-ci") && !input.fromStage) {
|
|
3619
3660
|
input.fromStage = "build";
|
|
3620
3661
|
}
|
|
3662
|
+
if (input.command === "fix-ci" && input.prNumber) {
|
|
3663
|
+
let ciRunId = input.ciRunId;
|
|
3664
|
+
if (!ciRunId && input.feedback) {
|
|
3665
|
+
const match = input.feedback.match(/Run ID:\s*(\d+)/);
|
|
3666
|
+
ciRunId = match?.[1];
|
|
3667
|
+
}
|
|
3668
|
+
if (!ciRunId) {
|
|
3669
|
+
const prDetails = getPRDetails(input.prNumber);
|
|
3670
|
+
if (prDetails) {
|
|
3671
|
+
ciRunId = getLatestFailedRunForBranch(prDetails.headBranch) ?? void 0;
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
if (ciRunId) {
|
|
3675
|
+
const ciLogs = getCIFailureLogs(ciRunId);
|
|
3676
|
+
if (ciLogs) {
|
|
3677
|
+
logger.info(` Found CI failure logs for run ${ciRunId}, injecting as feedback`);
|
|
3678
|
+
const ciContext = `## CI Failure Logs (run ${ciRunId})
|
|
3679
|
+
|
|
3680
|
+
The CI pipeline failed. Fix the code to make CI pass.
|
|
3681
|
+
|
|
3682
|
+
\`\`\`
|
|
3683
|
+
${ciLogs}
|
|
3684
|
+
\`\`\``;
|
|
3685
|
+
input.feedback = input.feedback ? `${ciContext}
|
|
3686
|
+
|
|
3687
|
+
## Additional context
|
|
3688
|
+
|
|
3689
|
+
${input.feedback}` : ciContext;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3621
3693
|
if (input.command === "fix" && input.prNumber) {
|
|
3622
3694
|
const reviewComment = getLatestKodyReviewComment(input.prNumber);
|
|
3623
3695
|
if (reviewComment) {
|
|
@@ -3667,7 +3739,7 @@ ${input.feedback}` : reviewContext;
|
|
|
3667
3739
|
projectDir,
|
|
3668
3740
|
runners,
|
|
3669
3741
|
input: {
|
|
3670
|
-
mode: input.command === "rerun" || input.command === "fix" ? "rerun" : "full",
|
|
3742
|
+
mode: input.command === "rerun" || input.command === "fix" || input.command === "fix-ci" ? "rerun" : "full",
|
|
3671
3743
|
fromStage: input.fromStage,
|
|
3672
3744
|
dryRun: input.dryRun,
|
|
3673
3745
|
issueNumber: input.issueNumber,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine-lite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.68",
|
|
4
4
|
"description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
"templates",
|
|
14
14
|
"kody.config.schema.json"
|
|
15
15
|
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"kody": "tsx src/entry.ts",
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"prepublishOnly": "pnpm build"
|
|
22
|
+
},
|
|
16
23
|
"dependencies": {
|
|
17
24
|
"dotenv": "^16.4.7"
|
|
18
25
|
},
|
|
@@ -25,11 +32,5 @@
|
|
|
25
32
|
},
|
|
26
33
|
"engines": {
|
|
27
34
|
"node": ">=22"
|
|
28
|
-
},
|
|
29
|
-
"scripts": {
|
|
30
|
-
"kody": "tsx src/entry.ts",
|
|
31
|
-
"build": "tsup",
|
|
32
|
-
"test": "vitest run",
|
|
33
|
-
"typecheck": "tsc --noEmit"
|
|
34
35
|
}
|
|
35
|
-
}
|
|
36
|
+
}
|
package/templates/kody.yml
CHANGED
|
@@ -28,12 +28,16 @@ on:
|
|
|
28
28
|
pull_request_review:
|
|
29
29
|
types: [submitted]
|
|
30
30
|
|
|
31
|
+
workflow_run:
|
|
32
|
+
workflows: ["CI"]
|
|
33
|
+
types: [completed]
|
|
34
|
+
|
|
31
35
|
push:
|
|
32
36
|
branches: [main, dev]
|
|
33
37
|
paths: ["src/**", "kody.config.json", "package.json"]
|
|
34
38
|
|
|
35
39
|
concurrency:
|
|
36
|
-
group: kody-${{ github.event.inputs.task_id || github.event.issue.number || github.event.pull_request.number || github.sha }}
|
|
40
|
+
group: kody-${{ github.event.inputs.task_id || github.event.issue.number || github.event.pull_request.number || github.event.workflow_run.id || github.sha }}
|
|
37
41
|
cancel-in-progress: false
|
|
38
42
|
|
|
39
43
|
permissions:
|
|
@@ -56,6 +60,7 @@ jobs:
|
|
|
56
60
|
issue_number: ${{ steps.parse.outputs.issue_number }}
|
|
57
61
|
pr_number: ${{ steps.parse.outputs.pr_number }}
|
|
58
62
|
feedback: ${{ steps.parse.outputs.feedback }}
|
|
63
|
+
ci_run_id: ${{ steps.parse.outputs.ci_run_id }}
|
|
59
64
|
valid: ${{ steps.parse.outputs.valid }}
|
|
60
65
|
steps:
|
|
61
66
|
- uses: actions/setup-node@v4
|
|
@@ -109,7 +114,7 @@ jobs:
|
|
|
109
114
|
|
|
110
115
|
# Validate mode
|
|
111
116
|
case "$MODE" in
|
|
112
|
-
full|rerun|fix|status|approve|review|bootstrap) ;;
|
|
117
|
+
full|rerun|fix|fix-ci|status|approve|review|bootstrap) ;;
|
|
113
118
|
*)
|
|
114
119
|
# If first arg isn't a mode, it might be a task-id or nothing
|
|
115
120
|
if [ -n "$MODE" ] && [ "$MODE" != "" ]; then
|
|
@@ -139,6 +144,15 @@ jobs:
|
|
|
139
144
|
# Leave TASK_ID empty — entry.ts finds latest task for issue
|
|
140
145
|
fi
|
|
141
146
|
|
|
147
|
+
# fix-ci: extract body as feedback + CI run ID
|
|
148
|
+
if [ "$MODE" = "fix-ci" ]; then
|
|
149
|
+
FIX_CI_BODY=$(echo "$BODY" | sed -n '/\(@kody\|\/kody\)\s*fix-ci/,$p' | tail -n +2)
|
|
150
|
+
if [ -n "$FIX_CI_BODY" ]; then
|
|
151
|
+
FEEDBACK="$FIX_CI_BODY"
|
|
152
|
+
fi
|
|
153
|
+
CI_RUN_ID=$(echo "$FIX_CI_BODY" | grep -oP 'Run ID:\s*\K\d+' || echo "")
|
|
154
|
+
fi
|
|
155
|
+
|
|
142
156
|
# Bootstrap mode: set task-id and skip normal pipeline
|
|
143
157
|
if [ "$MODE" = "bootstrap" ]; then
|
|
144
158
|
TASK_ID="bootstrap-$(date +%y%m%d-%H%M%S)"
|
|
@@ -170,6 +184,7 @@ jobs:
|
|
|
170
184
|
echo "$FEEDBACK"
|
|
171
185
|
echo "KODY_EOF"
|
|
172
186
|
} >> $GITHUB_OUTPUT
|
|
187
|
+
echo "ci_run_id=${CI_RUN_ID:-}" >> $GITHUB_OUTPUT
|
|
173
188
|
echo "valid=true" >> $GITHUB_OUTPUT
|
|
174
189
|
|
|
175
190
|
# ─── Orchestrate ─────────────────────────────────────────────────────────────
|
|
@@ -198,7 +213,7 @@ jobs:
|
|
|
198
213
|
token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
199
214
|
|
|
200
215
|
- name: Checkout PR branch (for fix/rerun/review on PRs)
|
|
201
|
-
if: github.event.issue.pull_request && (needs.parse.outputs.mode == 'fix' || needs.parse.outputs.mode == 'rerun' || needs.parse.outputs.mode == 'review')
|
|
216
|
+
if: github.event.issue.pull_request && (needs.parse.outputs.mode == 'fix' || needs.parse.outputs.mode == 'fix-ci' || needs.parse.outputs.mode == 'rerun' || needs.parse.outputs.mode == 'review')
|
|
202
217
|
env:
|
|
203
218
|
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
204
219
|
run: |
|
|
@@ -243,6 +258,7 @@ jobs:
|
|
|
243
258
|
ISSUE_NUMBER: ${{ github.event.inputs.issue_number || needs.parse.outputs.issue_number }}
|
|
244
259
|
PR_NUMBER: ${{ needs.parse.outputs.pr_number }}
|
|
245
260
|
FEEDBACK: ${{ github.event.inputs.feedback || needs.parse.outputs.feedback }}
|
|
261
|
+
CI_RUN_ID: ${{ needs.parse.outputs.ci_run_id }}
|
|
246
262
|
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
|
|
247
263
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
248
264
|
run: |
|
|
@@ -253,6 +269,7 @@ jobs:
|
|
|
253
269
|
CMD="run"
|
|
254
270
|
[ "$MODE" = "rerun" ] && CMD="rerun"
|
|
255
271
|
[ "$MODE" = "fix" ] && CMD="fix"
|
|
272
|
+
[ "$MODE" = "fix-ci" ] && CMD="fix-ci"
|
|
256
273
|
[ "$MODE" = "review" ] && CMD="review"
|
|
257
274
|
ARGS="--issue-number $ISSUE_NUMBER"
|
|
258
275
|
[ -n "$TASK_ID" ] && ARGS="$ARGS --task-id $TASK_ID"
|
|
@@ -338,6 +355,68 @@ jobs:
|
|
|
338
355
|
});
|
|
339
356
|
}
|
|
340
357
|
|
|
358
|
+
# ─── Fix-CI Auto-trigger (workflow_run trigger) ─────────────────────────────
|
|
359
|
+
fix-ci-trigger:
|
|
360
|
+
if: >-
|
|
361
|
+
github.event_name == 'workflow_run' &&
|
|
362
|
+
github.event.workflow_run.conclusion == 'failure' &&
|
|
363
|
+
github.event.workflow_run.event == 'pull_request' &&
|
|
364
|
+
github.event.workflow_run.pull_requests[0]
|
|
365
|
+
runs-on: ubuntu-latest
|
|
366
|
+
timeout-minutes: 5
|
|
367
|
+
permissions:
|
|
368
|
+
issues: write
|
|
369
|
+
pull-requests: write
|
|
370
|
+
steps:
|
|
371
|
+
- name: Check loop guard and post fix-ci comment
|
|
372
|
+
uses: actions/github-script@v7
|
|
373
|
+
with:
|
|
374
|
+
script: |
|
|
375
|
+
const pr = context.payload.workflow_run.pull_requests[0];
|
|
376
|
+
const prNumber = pr.number;
|
|
377
|
+
|
|
378
|
+
// Check recent comments for existing fix-ci attempt (last 24h)
|
|
379
|
+
const comments = await github.rest.issues.listComments({
|
|
380
|
+
owner: context.repo.owner,
|
|
381
|
+
repo: context.repo.repo,
|
|
382
|
+
issue_number: prNumber,
|
|
383
|
+
per_page: 30,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
387
|
+
const recentFixCi = comments.data.filter(
|
|
388
|
+
(c) => c.body.includes('@kody fix-ci') && new Date(c.created_at) > oneDayAgo
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
if (recentFixCi.length >= 1) {
|
|
392
|
+
core.info('Loop guard: @kody fix-ci already commented in last 24h, skipping');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Check if last commit was from a bot (kody's previous fix attempt)
|
|
397
|
+
const commits = await github.rest.pulls.listCommits({
|
|
398
|
+
owner: context.repo.owner,
|
|
399
|
+
repo: context.repo.repo,
|
|
400
|
+
pull_number: prNumber,
|
|
401
|
+
per_page: 1,
|
|
402
|
+
});
|
|
403
|
+
const lastAuthor = commits.data[commits.data.length - 1]?.commit?.author?.name;
|
|
404
|
+
if (lastAuthor === 'github-actions[bot]' || lastAuthor === 'kody[bot]') {
|
|
405
|
+
core.info('Loop guard: last commit from bot, skipping');
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Post fix-ci comment
|
|
410
|
+
const runId = context.payload.workflow_run.id;
|
|
411
|
+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
|
|
412
|
+
await github.rest.issues.createComment({
|
|
413
|
+
owner: context.repo.owner,
|
|
414
|
+
repo: context.repo.repo,
|
|
415
|
+
issue_number: prNumber,
|
|
416
|
+
body: `@kody fix-ci\nCI failed: [View logs](${runUrl})\nRun ID: ${runId}`,
|
|
417
|
+
});
|
|
418
|
+
core.info(`Posted @kody fix-ci on PR #${prNumber} for run ${runId}`);
|
|
419
|
+
|
|
341
420
|
# ─── Smoke Test (push trigger) ──────────────────────────────────────────────
|
|
342
421
|
smoke-test:
|
|
343
422
|
if: github.event_name == 'push'
|