@kody-ade/kody-engine-lite 0.1.56 → 0.1.57
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 +93 -22
- package/kody.config.schema.json +2 -2
- package/package.json +1 -1
- package/prompts/autofix.md +27 -9
- package/prompts/review.md +83 -16
- package/templates/kody.yml +8 -8
- 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/dist/bin/cli.js
CHANGED
|
@@ -300,7 +300,7 @@ var init_config = __esm({
|
|
|
300
300
|
repo: ""
|
|
301
301
|
},
|
|
302
302
|
paths: {
|
|
303
|
-
taskDir: ".tasks"
|
|
303
|
+
taskDir: ".kody/tasks"
|
|
304
304
|
},
|
|
305
305
|
agent: {
|
|
306
306
|
runner: "claude-code",
|
|
@@ -1101,7 +1101,6 @@ var init_agent = __esm({
|
|
|
1101
1101
|
taskify: "explore",
|
|
1102
1102
|
plan: "explore",
|
|
1103
1103
|
build: "build",
|
|
1104
|
-
autofix: "build",
|
|
1105
1104
|
"review-fix": "build",
|
|
1106
1105
|
review: "review"
|
|
1107
1106
|
};
|
|
@@ -1601,6 +1600,20 @@ function executeShipStage(ctx, _def) {
|
|
|
1601
1600
|
try {
|
|
1602
1601
|
const head = getCurrentBranch(ctx.projectDir);
|
|
1603
1602
|
const base = getDefaultBranch(ctx.projectDir);
|
|
1603
|
+
try {
|
|
1604
|
+
execFileSync7("git", ["add", ctx.taskDir], {
|
|
1605
|
+
cwd: ctx.projectDir,
|
|
1606
|
+
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
1607
|
+
stdio: "pipe"
|
|
1608
|
+
});
|
|
1609
|
+
execFileSync7("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
|
|
1610
|
+
cwd: ctx.projectDir,
|
|
1611
|
+
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
1612
|
+
stdio: "pipe"
|
|
1613
|
+
});
|
|
1614
|
+
logger.info(" Committed task artifacts");
|
|
1615
|
+
} catch {
|
|
1616
|
+
}
|
|
1604
1617
|
pushBranch(ctx.projectDir);
|
|
1605
1618
|
const config = getProjectConfig();
|
|
1606
1619
|
let owner = config.github?.owner;
|
|
@@ -1698,6 +1711,7 @@ Failed: ${msg}
|
|
|
1698
1711
|
var init_ship = __esm({
|
|
1699
1712
|
"src/stages/ship.ts"() {
|
|
1700
1713
|
"use strict";
|
|
1714
|
+
init_logger();
|
|
1701
1715
|
init_git_utils();
|
|
1702
1716
|
init_github_api();
|
|
1703
1717
|
init_config();
|
|
@@ -2526,7 +2540,7 @@ import * as fs16 from "fs";
|
|
|
2526
2540
|
import * as path15 from "path";
|
|
2527
2541
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
2528
2542
|
function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
2529
|
-
const tasksDir = path15.join(projectDir, ".tasks");
|
|
2543
|
+
const tasksDir = path15.join(projectDir, ".kody", "tasks");
|
|
2530
2544
|
if (!fs16.existsSync(tasksDir)) return null;
|
|
2531
2545
|
const allDirs = fs16.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
2532
2546
|
const prefix = `${issueNumber}-`;
|
|
@@ -2587,7 +2601,7 @@ Or comment on the specific PR: \`@kody review\``
|
|
|
2587
2601
|
}
|
|
2588
2602
|
async function runStandaloneReview(input) {
|
|
2589
2603
|
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
2590
|
-
const taskDir = path16.join(input.projectDir, ".tasks", taskId);
|
|
2604
|
+
const taskDir = path16.join(input.projectDir, ".kody", "tasks", taskId);
|
|
2591
2605
|
fs17.mkdirSync(taskDir, { recursive: true });
|
|
2592
2606
|
const taskContent = `# ${input.prTitle}
|
|
2593
2607
|
|
|
@@ -2830,7 +2844,7 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
|
2830
2844
|
function resolveForIssue(issueNumber, projectDir) {
|
|
2831
2845
|
const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
|
|
2832
2846
|
if (existingTaskId) {
|
|
2833
|
-
const statusPath = path18.join(projectDir, ".tasks", existingTaskId, "status.json");
|
|
2847
|
+
const statusPath = path18.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
2834
2848
|
let existingState = null;
|
|
2835
2849
|
if (fs19.existsSync(statusPath)) {
|
|
2836
2850
|
try {
|
|
@@ -2924,7 +2938,7 @@ async function main() {
|
|
|
2924
2938
|
process.exit(1);
|
|
2925
2939
|
}
|
|
2926
2940
|
}
|
|
2927
|
-
const taskDir = path19.join(projectDir, ".tasks", taskId);
|
|
2941
|
+
const taskDir = path19.join(projectDir, ".kody", "tasks", taskId);
|
|
2928
2942
|
fs20.mkdirSync(taskDir, { recursive: true });
|
|
2929
2943
|
if (input.command === "status") {
|
|
2930
2944
|
printStatus(taskId, taskDir);
|
|
@@ -2963,10 +2977,12 @@ async function main() {
|
|
|
2963
2977
|
const proxyRunning = await checkLitellmHealth(config2.agent.litellmUrl);
|
|
2964
2978
|
if (!proxyRunning) {
|
|
2965
2979
|
litellmProcess2 = await tryStartLitellm(config2.agent.litellmUrl, projectDir);
|
|
2980
|
+
if (!litellmProcess2) {
|
|
2981
|
+
logger.error("LiteLLM is configured (litellmUrl) but could not be started. Install it with: pip install 'litellm[proxy]'");
|
|
2982
|
+
process.exit(1);
|
|
2983
|
+
}
|
|
2966
2984
|
}
|
|
2967
|
-
|
|
2968
|
-
process.env.ANTHROPIC_BASE_URL = config2.agent.litellmUrl;
|
|
2969
|
-
}
|
|
2985
|
+
process.env.ANTHROPIC_BASE_URL = config2.agent.litellmUrl;
|
|
2970
2986
|
}
|
|
2971
2987
|
const runners2 = createRunners(config2);
|
|
2972
2988
|
const defaultRunnerName2 = config2.agent.defaultRunner ?? Object.keys(runners2)[0] ?? "claude";
|
|
@@ -3036,7 +3052,7 @@ ${issue.body ?? ""}`;
|
|
|
3036
3052
|
}
|
|
3037
3053
|
}
|
|
3038
3054
|
if (!fs20.existsSync(taskMdPath)) {
|
|
3039
|
-
console.error("No task.md found. Provide --task, --issue-number, or ensure .tasks/<id>/task.md exists.");
|
|
3055
|
+
console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
|
|
3040
3056
|
process.exit(1);
|
|
3041
3057
|
}
|
|
3042
3058
|
if (input.command === "fix" && !input.fromStage) {
|
|
@@ -3078,16 +3094,14 @@ ${input.feedback}` : reviewContext;
|
|
|
3078
3094
|
if (!proxyRunning) {
|
|
3079
3095
|
litellmProcess = await tryStartLitellm(config.agent.litellmUrl, projectDir);
|
|
3080
3096
|
if (!litellmProcess) {
|
|
3081
|
-
logger.
|
|
3082
|
-
|
|
3097
|
+
logger.error("LiteLLM is configured (litellmUrl) but could not be started. Install it with: pip install 'litellm[proxy]'");
|
|
3098
|
+
process.exit(1);
|
|
3083
3099
|
}
|
|
3084
3100
|
} else {
|
|
3085
3101
|
logger.info(`LiteLLM proxy already running at ${config.agent.litellmUrl}`);
|
|
3086
3102
|
}
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
logger.info(`ANTHROPIC_BASE_URL set to ${config.agent.litellmUrl}`);
|
|
3090
|
-
}
|
|
3103
|
+
process.env.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
|
|
3104
|
+
logger.info(`ANTHROPIC_BASE_URL set to ${config.agent.litellmUrl}`);
|
|
3091
3105
|
}
|
|
3092
3106
|
const runners = createRunners(config);
|
|
3093
3107
|
const defaultRunnerName = config.agent.defaultRunner ?? Object.keys(runners)[0] ?? "claude";
|
|
@@ -3362,7 +3376,7 @@ function buildConfig(cwd, basic) {
|
|
|
3362
3376
|
},
|
|
3363
3377
|
git: { defaultBranch: basic.defaultBranch },
|
|
3364
3378
|
github: { owner: basic.owner, repo: basic.repo },
|
|
3365
|
-
paths: { taskDir: ".tasks" },
|
|
3379
|
+
paths: { taskDir: ".kody/tasks" },
|
|
3366
3380
|
agent: {
|
|
3367
3381
|
runner: "claude-code",
|
|
3368
3382
|
defaultRunner: "claude",
|
|
@@ -3404,11 +3418,12 @@ function initCommand(opts) {
|
|
|
3404
3418
|
const gitignorePath = path20.join(cwd, ".gitignore");
|
|
3405
3419
|
if (fs21.existsSync(gitignorePath)) {
|
|
3406
3420
|
const content = fs21.readFileSync(gitignorePath, "utf-8");
|
|
3407
|
-
if (
|
|
3408
|
-
|
|
3409
|
-
|
|
3421
|
+
if (content.includes(".tasks/")) {
|
|
3422
|
+
const updated = content.replace(/\n?\.tasks\/\n?/g, "\n");
|
|
3423
|
+
fs21.writeFileSync(gitignorePath, updated);
|
|
3424
|
+
console.log(" \u2713 .gitignore (removed legacy .tasks/ \u2014 tasks now committed in .kody/tasks/)");
|
|
3410
3425
|
} else {
|
|
3411
|
-
console.log(" \u25CB .gitignore (
|
|
3426
|
+
console.log(" \u25CB .gitignore (ok)");
|
|
3412
3427
|
}
|
|
3413
3428
|
}
|
|
3414
3429
|
console.log("\n\u2500\u2500 Prerequisites \u2500\u2500");
|
|
@@ -3589,7 +3604,7 @@ function initCommand(opts) {
|
|
|
3589
3604
|
console.log("");
|
|
3590
3605
|
}
|
|
3591
3606
|
}
|
|
3592
|
-
var STEP_STAGES = ["taskify", "plan", "build", "
|
|
3607
|
+
var STEP_STAGES = ["taskify", "plan", "build", "review", "review-fix"];
|
|
3593
3608
|
function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
3594
3609
|
const srcDir = path20.join(cwd, "src");
|
|
3595
3610
|
const baseDir = fs21.existsSync(srcDir) ? srcDir : cwd;
|
|
@@ -3627,11 +3642,46 @@ ${content}
|
|
|
3627
3642
|
}
|
|
3628
3643
|
return results.join("\n\n");
|
|
3629
3644
|
}
|
|
3645
|
+
function ghComment(issueNumber, body, cwd) {
|
|
3646
|
+
try {
|
|
3647
|
+
let repoSlug = "";
|
|
3648
|
+
try {
|
|
3649
|
+
const configPath = path20.join(cwd, "kody.config.json");
|
|
3650
|
+
if (fs21.existsSync(configPath)) {
|
|
3651
|
+
const config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
|
|
3652
|
+
if (config.github?.owner && config.github?.repo) {
|
|
3653
|
+
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
} catch {
|
|
3657
|
+
}
|
|
3658
|
+
if (!repoSlug) return;
|
|
3659
|
+
execFileSync11("gh", [
|
|
3660
|
+
"issue",
|
|
3661
|
+
"comment",
|
|
3662
|
+
String(issueNumber),
|
|
3663
|
+
"--repo",
|
|
3664
|
+
repoSlug,
|
|
3665
|
+
"--body",
|
|
3666
|
+
body
|
|
3667
|
+
], {
|
|
3668
|
+
cwd,
|
|
3669
|
+
encoding: "utf-8",
|
|
3670
|
+
timeout: 15e3,
|
|
3671
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3672
|
+
});
|
|
3673
|
+
} catch {
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3630
3676
|
function bootstrapCommand() {
|
|
3631
3677
|
const cwd = process.cwd();
|
|
3678
|
+
const issueNumber = parseInt(process.env.ISSUE_NUMBER ?? "", 10) || 0;
|
|
3632
3679
|
console.log(`
|
|
3633
3680
|
\u{1F527} Kody Bootstrap \u2014 Generating project memory + step files
|
|
3634
3681
|
`);
|
|
3682
|
+
if (issueNumber) {
|
|
3683
|
+
ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
|
|
3684
|
+
}
|
|
3635
3685
|
const readIfExists = (rel, maxChars = 3e3) => {
|
|
3636
3686
|
const p = path20.join(cwd, rel);
|
|
3637
3687
|
if (fs21.existsSync(p)) return fs21.readFileSync(p, "utf-8").slice(0, maxChars);
|
|
@@ -3911,8 +3961,16 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
3911
3961
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3912
3962
|
}).trim();
|
|
3913
3963
|
console.log(` \u2713 Created PR: ${prUrl}`);
|
|
3964
|
+
if (issueNumber) {
|
|
3965
|
+
ghComment(issueNumber, `\u2705 **Bootstrap complete** \u2014 PR created: ${prUrl}
|
|
3966
|
+
|
|
3967
|
+
Review and merge to activate project-specific pipeline configuration.`, cwd);
|
|
3968
|
+
}
|
|
3914
3969
|
} catch (prErr) {
|
|
3915
3970
|
console.log(` \u25CB PR creation failed: ${prErr instanceof Error ? prErr.message : prErr}`);
|
|
3971
|
+
if (issueNumber) {
|
|
3972
|
+
ghComment(issueNumber, `\u26A0\uFE0F **Bootstrap complete** \u2014 files generated and pushed to branch \`${branchName}\`, but PR creation failed. Create it manually.`, cwd);
|
|
3973
|
+
}
|
|
3916
3974
|
}
|
|
3917
3975
|
} else {
|
|
3918
3976
|
console.log(" \u25CB No new changes to commit");
|
|
@@ -3935,6 +3993,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
3935
3993
|
}
|
|
3936
3994
|
} catch (err) {
|
|
3937
3995
|
console.log(` \u25CB Git commit skipped: ${err instanceof Error ? err.message : err}`);
|
|
3996
|
+
if (issueNumber) {
|
|
3997
|
+
ghComment(issueNumber, `\u274C **Bootstrap failed** \u2014 git operation error: ${err instanceof Error ? err.message : err}`, cwd);
|
|
3998
|
+
}
|
|
3938
3999
|
}
|
|
3939
4000
|
}
|
|
3940
4001
|
console.log("\n\u2500\u2500 Done \u2500\u2500");
|
|
@@ -3982,3 +4043,13 @@ if (command === "init") {
|
|
|
3982
4043
|
} else {
|
|
3983
4044
|
Promise.resolve().then(() => init_entry());
|
|
3984
4045
|
}
|
|
4046
|
+
export {
|
|
4047
|
+
buildConfig,
|
|
4048
|
+
checkCommand2 as checkCommand,
|
|
4049
|
+
checkFile,
|
|
4050
|
+
checkGhAuth,
|
|
4051
|
+
checkGhRepoAccess,
|
|
4052
|
+
checkGhSecret,
|
|
4053
|
+
detectArchitectureBasic,
|
|
4054
|
+
detectBasicConfig
|
|
4055
|
+
};
|
package/kody.config.schema.json
CHANGED
|
@@ -82,8 +82,8 @@
|
|
|
82
82
|
"properties": {
|
|
83
83
|
"taskDir": {
|
|
84
84
|
"type": "string",
|
|
85
|
-
"description": "Directory for pipeline artifacts and state (
|
|
86
|
-
"default": ".tasks"
|
|
85
|
+
"description": "Directory for pipeline artifacts and state (committed to branch)",
|
|
86
|
+
"default": ".kody/tasks"
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
89
|
"additionalProperties": false
|
package/package.json
CHANGED
package/prompts/autofix.md
CHANGED
|
@@ -1,21 +1,39 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: autofix
|
|
3
|
-
description:
|
|
3
|
+
description: Investigate root cause then fix verification errors (typecheck, lint, test failures)
|
|
4
4
|
mode: primary
|
|
5
5
|
tools: [read, write, edit, bash, glob, grep]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are an autofix agent. The verification stage failed. Fix the errors below.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
IRON LAW: NO FIXES WITHOUT INVESTIGATION FIRST. Do not jump to changing code. Understand the failure first.
|
|
11
|
+
|
|
12
|
+
## Phase 1 — Investigate (do this BEFORE any edits)
|
|
13
|
+
1. Read the full error output — what exactly failed?
|
|
14
|
+
2. Identify the affected files — Read them to understand context
|
|
15
|
+
3. Check recent changes: run `git diff HEAD~1` to see what changed
|
|
16
|
+
4. Classify the failure pattern:
|
|
17
|
+
- **Type error**: mismatched types, missing properties, wrong generics
|
|
18
|
+
- **Test failure**: assertion mismatch, missing mock, changed behavior
|
|
19
|
+
- **Lint error**: style violation, unused import, naming convention
|
|
20
|
+
- **Runtime error**: null reference, missing dependency, config issue
|
|
21
|
+
- **Integration failure**: API contract mismatch, schema drift
|
|
22
|
+
5. Identify root cause — is this a direct error in new code, or a side effect of a change elsewhere?
|
|
23
|
+
|
|
24
|
+
## Phase 2 — Fix (only after root cause is clear)
|
|
25
|
+
1. Try quick wins first: run configured lintFix and formatFix commands via Bash
|
|
26
|
+
2. For type errors: fix the type mismatch at its source, not by adding type assertions
|
|
27
|
+
3. For test failures: fix the root cause (implementation or test), not both — determine which is correct
|
|
28
|
+
4. For lint errors: apply the specific fix the linter suggests
|
|
29
|
+
5. For integration failures: trace the contract back to its definition, fix the mismatch at source
|
|
16
30
|
6. After EACH fix, re-run the failing command to verify it passes
|
|
17
|
-
7.
|
|
31
|
+
7. If a fix introduces new failures, REVERT and try a different approach
|
|
32
|
+
8. Do NOT commit or push — the orchestrator handles git
|
|
18
33
|
|
|
19
|
-
|
|
34
|
+
## Rules
|
|
35
|
+
- Fix ONLY the reported errors. Do NOT make unrelated changes.
|
|
36
|
+
- Minimal diff — use Edit for surgical changes, not Write for rewrites
|
|
37
|
+
- If the failure is pre-existing (not caused by this PR's changes), document it and move on
|
|
20
38
|
|
|
21
39
|
{{TASK_CONTEXT}}
|
package/prompts/review.md
CHANGED
|
@@ -5,9 +5,10 @@ mode: primary
|
|
|
5
5
|
tools: [read, glob, grep, bash]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
You are a code review agent
|
|
8
|
+
You are a code review agent following the Superpowers Structured Review methodology.
|
|
9
9
|
|
|
10
10
|
Use Bash to run `git diff` to see what changed. Use Read to examine modified files in full context.
|
|
11
|
+
When the diff introduces new enum values, status strings, or type constants — use Grep to trace ALL consumers outside the diff.
|
|
11
12
|
|
|
12
13
|
CRITICAL: You MUST output a structured review in the EXACT format below. Do NOT output conversational text, status updates, or summaries. Your entire output must be the structured review markdown.
|
|
13
14
|
|
|
@@ -21,28 +22,94 @@ Output markdown with this EXACT structure:
|
|
|
21
22
|
## Findings
|
|
22
23
|
|
|
23
24
|
### Critical
|
|
24
|
-
<Security vulnerabilities, data loss risks, crashes, broken authentication>
|
|
25
25
|
<If none: "None.">
|
|
26
26
|
|
|
27
27
|
### Major
|
|
28
|
-
<Logic errors, missing edge cases, broken tests, significant performance issues, missing error handling>
|
|
29
28
|
<If none: "None.">
|
|
30
29
|
|
|
31
30
|
### Minor
|
|
32
|
-
<Style issues, naming improvements, readability, trivial performance, minor refactoring opportunities>
|
|
33
31
|
<If none: "None.">
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
33
|
+
For each finding use: `file:line` — problem description. Suggested fix.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Two-Pass Review
|
|
38
|
+
|
|
39
|
+
**Pass 1 — CRITICAL (must fix before merge):**
|
|
40
|
+
|
|
41
|
+
### SQL & Data Safety
|
|
42
|
+
- String interpolation in SQL — use parameterized queries even for `.to_i`/`.to_f` values
|
|
43
|
+
- TOCTOU races: check-then-set patterns that should be atomic `WHERE` + update
|
|
44
|
+
- Bypassing model validations via direct DB writes (e.g., `update_column`, raw queries)
|
|
45
|
+
- N+1 queries: missing eager loading for associations used in loops/views
|
|
46
|
+
|
|
47
|
+
### Race Conditions & Concurrency
|
|
48
|
+
- Read-check-write without uniqueness constraint or duplicate key handling
|
|
49
|
+
- find-or-create without unique DB index — concurrent calls create duplicates
|
|
50
|
+
- Status transitions without atomic `WHERE old_status = ? UPDATE SET new_status`
|
|
51
|
+
- Unsafe HTML rendering (`dangerouslySetInnerHTML`, `v-html`, `.html_safe`) on user-controlled data (XSS)
|
|
52
|
+
|
|
53
|
+
### LLM Output Trust Boundary
|
|
54
|
+
- LLM-generated values (emails, URLs, names) written to DB without format validation
|
|
55
|
+
- Structured tool output accepted without type/shape checks before DB writes
|
|
56
|
+
- LLM-generated URLs fetched without allowlist — SSRF risk
|
|
57
|
+
- LLM output stored in vector DBs without sanitization — stored prompt injection risk
|
|
58
|
+
|
|
59
|
+
### Shell Injection
|
|
60
|
+
- `subprocess.run()` / `os.system()` with `shell=True` AND string interpolation — use argument arrays
|
|
61
|
+
- `eval()` / `exec()` on LLM-generated code without sandboxing
|
|
62
|
+
|
|
63
|
+
### Enum & Value Completeness
|
|
64
|
+
When the diff introduces a new enum value, status string, tier name, or type constant:
|
|
65
|
+
- Trace it through every consumer (READ each file that switches/filters on that value)
|
|
66
|
+
- Check allowlists/filter arrays containing sibling values
|
|
67
|
+
- Check `case`/`if-elsif` chains — does the new value fall through to a wrong default?
|
|
68
|
+
|
|
69
|
+
**Pass 2 — INFORMATIONAL (should review, may auto-fix):**
|
|
70
|
+
|
|
71
|
+
### Conditional Side Effects
|
|
72
|
+
- Code paths that branch but forget a side effect on one branch (e.g., promoted but URL only attached conditionally)
|
|
73
|
+
- Log messages claiming an action happened when it was conditionally skipped
|
|
74
|
+
|
|
75
|
+
### Test Gaps
|
|
76
|
+
- Negative-path tests asserting type/status but not side effects
|
|
77
|
+
- Security enforcement features (blocking, rate limiting, auth) without integration tests
|
|
78
|
+
- Missing `.expects(:something).never` when a path should NOT call an external service
|
|
79
|
+
|
|
80
|
+
### Dead Code & Consistency
|
|
81
|
+
- Variables assigned but never read
|
|
82
|
+
- Comments/docstrings describing old behavior after code changed
|
|
83
|
+
- Version mismatch between PR title and VERSION/CHANGELOG
|
|
84
|
+
|
|
85
|
+
### Crypto & Entropy
|
|
86
|
+
- Truncation instead of hashing — less entropy, easier collisions
|
|
87
|
+
- `rand()` / `Math.random()` for security-sensitive values — use crypto-secure alternatives
|
|
88
|
+
- Non-constant-time comparisons (`==`) on secrets or tokens — timing attack risk
|
|
89
|
+
|
|
90
|
+
### Performance & Bundle Impact
|
|
91
|
+
- Known-heavy dependencies added: moment.js (→ date-fns), full lodash (→ lodash-es), jquery
|
|
92
|
+
- Images without `loading="lazy"` or explicit dimensions (CLS)
|
|
93
|
+
- `useEffect` fetch waterfalls — combine or parallelize
|
|
94
|
+
- Synchronous `<script>` without async/defer
|
|
95
|
+
|
|
96
|
+
### Type Coercion at Boundaries
|
|
97
|
+
- Values crossing language/serialization boundaries where type could change (numeric vs string)
|
|
98
|
+
- Hash/digest inputs without `.toString()` normalization before serialization
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Severity Definitions
|
|
103
|
+
|
|
104
|
+
- **Critical**: Security vulnerability, data loss, application crash, broken authentication, injection risk, race condition. MUST fix before merge.
|
|
105
|
+
- **Major**: Logic error, missing edge case, broken test, significant performance issue, missing input validation, enum completeness gap. SHOULD fix before merge.
|
|
106
|
+
- **Minor**: Style issue, naming improvement, readability, micro-optimization, stale comments. NICE to fix, not blocking.
|
|
107
|
+
|
|
108
|
+
## Suppressions — do NOT flag these:
|
|
109
|
+
- Redundancy that aids readability
|
|
110
|
+
- "Add a comment explaining this threshold" — thresholds change, comments rot
|
|
111
|
+
- Consistency-only changes with no behavioral impact
|
|
112
|
+
- Issues already addressed in the diff you are reviewing — read the FULL diff first
|
|
113
|
+
- devDependencies additions (no production impact)
|
|
47
114
|
|
|
48
115
|
{{TASK_CONTEXT}}
|
package/templates/kody.yml
CHANGED
|
@@ -254,7 +254,7 @@ jobs:
|
|
|
254
254
|
[ -n "$TASK_ID" ] && ARGS="$ARGS --task-id $TASK_ID"
|
|
255
255
|
[ -n "$PR_NUMBER" ] && ARGS="$ARGS --pr-number $PR_NUMBER"
|
|
256
256
|
[ -n "$FROM_STAGE" ] && ARGS="$ARGS --from $FROM_STAGE"
|
|
257
|
-
|
|
257
|
+
# FEEDBACK is passed via env var, not CLI arg (avoids shell escaping issues)
|
|
258
258
|
[ "$DRY_RUN" = "true" ] && ARGS="$ARGS --dry-run"
|
|
259
259
|
kody-engine-lite $CMD $ARGS
|
|
260
260
|
fi
|
|
@@ -263,7 +263,7 @@ jobs:
|
|
|
263
263
|
if: always()
|
|
264
264
|
run: |
|
|
265
265
|
TASK_ID="${{ github.event.inputs.task_id || needs.parse.outputs.task_id }}"
|
|
266
|
-
STATUS_FILE=".tasks/${TASK_ID}/status.json"
|
|
266
|
+
STATUS_FILE=".kody/tasks/${TASK_ID}/status.json"
|
|
267
267
|
if [ -f "$STATUS_FILE" ]; then
|
|
268
268
|
STATE=$(jq -r '.state' "$STATUS_FILE")
|
|
269
269
|
ICON="❌"
|
|
@@ -292,7 +292,7 @@ jobs:
|
|
|
292
292
|
uses: actions/upload-artifact@v4
|
|
293
293
|
with:
|
|
294
294
|
name: kody-tasks-${{ github.event.inputs.task_id || needs.parse.outputs.task_id }}
|
|
295
|
-
path: .tasks/
|
|
295
|
+
path: .kody/tasks/
|
|
296
296
|
retention-days: 7
|
|
297
297
|
|
|
298
298
|
# ─── Error Notifications ─────────────────────────────────────────────────────
|
|
@@ -355,11 +355,11 @@ jobs:
|
|
|
355
355
|
run: kody-engine-lite --help
|
|
356
356
|
- name: Dry run
|
|
357
357
|
run: |
|
|
358
|
-
mkdir -p .tasks/smoke-test
|
|
359
|
-
echo "Smoke test task" > .tasks/smoke-test/task.md
|
|
358
|
+
mkdir -p .kody/tasks/smoke-test
|
|
359
|
+
echo "Smoke test task" > .kody/tasks/smoke-test/task.md
|
|
360
360
|
kody-engine-lite run --task-id smoke-test --dry-run || true
|
|
361
|
-
if [ -f ".tasks/smoke-test/status.json" ]; then
|
|
361
|
+
if [ -f ".kody/tasks/smoke-test/status.json" ]; then
|
|
362
362
|
echo "✓ status.json created"
|
|
363
|
-
cat .tasks/smoke-test/status.json
|
|
363
|
+
cat .kody/tasks/smoke-test/status.json
|
|
364
364
|
fi
|
|
365
|
-
rm -rf .tasks/smoke-test
|
|
365
|
+
rm -rf .kody/tasks/smoke-test
|
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
|
-
}
|