@kody-ade/kody-engine-lite 0.1.60 → 0.1.62
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 +12 -14
- package/dist/agent-runner.d.ts +4 -0
- package/dist/agent-runner.js +122 -0
- package/dist/bin/cli.js +93 -80
- package/dist/ci/parse-inputs.d.ts +6 -0
- package/dist/ci/parse-inputs.js +76 -0
- package/dist/ci/parse-safety.d.ts +6 -0
- package/dist/ci/parse-safety.js +22 -0
- package/dist/cli/args.d.ts +13 -0
- package/dist/cli/args.js +42 -0
- package/dist/cli/litellm.d.ts +2 -0
- package/dist/cli/litellm.js +85 -0
- package/dist/cli/task-resolution.d.ts +2 -0
- package/dist/cli/task-resolution.js +41 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +72 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.js +83 -0
- package/dist/definitions.d.ts +3 -0
- package/dist/definitions.js +59 -0
- package/dist/entry.d.ts +1 -0
- package/dist/entry.js +236 -0
- package/dist/git-utils.d.ts +13 -0
- package/dist/git-utils.js +174 -0
- package/dist/github-api.d.ts +14 -0
- package/dist/github-api.js +114 -0
- package/dist/kody-utils.d.ts +1 -0
- package/dist/kody-utils.js +9 -0
- package/dist/learning/auto-learn.d.ts +2 -0
- package/dist/learning/auto-learn.js +169 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +51 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.js +20 -0
- package/dist/observer.d.ts +9 -0
- package/dist/observer.js +80 -0
- package/dist/pipeline/complexity.d.ts +3 -0
- package/dist/pipeline/complexity.js +12 -0
- package/dist/pipeline/executor-registry.d.ts +3 -0
- package/dist/pipeline/executor-registry.js +20 -0
- package/dist/pipeline/hooks.d.ts +17 -0
- package/dist/pipeline/hooks.js +110 -0
- package/dist/pipeline/questions.d.ts +2 -0
- package/dist/pipeline/questions.js +44 -0
- package/dist/pipeline/runner-selection.d.ts +2 -0
- package/dist/pipeline/runner-selection.js +13 -0
- package/dist/pipeline/state.d.ts +4 -0
- package/dist/pipeline/state.js +37 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.js +213 -0
- package/dist/preflight.d.ts +1 -0
- package/dist/preflight.js +69 -0
- package/dist/retrospective.d.ts +26 -0
- package/dist/retrospective.js +211 -0
- package/dist/stages/agent.d.ts +2 -0
- package/dist/stages/agent.js +94 -0
- package/dist/stages/gate.d.ts +2 -0
- package/dist/stages/gate.js +32 -0
- package/dist/stages/review.d.ts +2 -0
- package/dist/stages/review.js +32 -0
- package/dist/stages/ship.d.ts +3 -0
- package/dist/stages/ship.js +154 -0
- package/dist/stages/verify.d.ts +2 -0
- package/dist/stages/verify.js +94 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +1 -0
- package/dist/validators.d.ts +8 -0
- package/dist/validators.js +42 -0
- package/dist/verify-runner.d.ts +11 -0
- package/dist/verify-runner.js +110 -0
- package/kody.config.schema.json +7 -48
- package/package.json +1 -1
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type StageName = "taskify" | "plan" | "build" | "verify" | "review" | "review-fix" | "ship";
|
|
2
|
+
export type StageType = "agent" | "gate" | "deterministic";
|
|
3
|
+
export type PipelineState = "pending" | "running" | "completed" | "failed" | "timeout";
|
|
4
|
+
export interface StageDefinition {
|
|
5
|
+
name: StageName;
|
|
6
|
+
type: StageType;
|
|
7
|
+
modelTier: "cheap" | "mid" | "strong";
|
|
8
|
+
timeout: number;
|
|
9
|
+
maxRetries: number;
|
|
10
|
+
outputFile?: string;
|
|
11
|
+
retryWithAgent?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface StageState {
|
|
14
|
+
state: PipelineState;
|
|
15
|
+
startedAt?: string;
|
|
16
|
+
completedAt?: string;
|
|
17
|
+
retries: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
outputFile?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PipelineStatus {
|
|
22
|
+
taskId: string;
|
|
23
|
+
state: "running" | "completed" | "failed";
|
|
24
|
+
stages: Record<StageName, StageState>;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}
|
|
28
|
+
export interface StageResult {
|
|
29
|
+
outcome: "completed" | "failed" | "timed_out";
|
|
30
|
+
outputFile?: string;
|
|
31
|
+
error?: string;
|
|
32
|
+
retries: number;
|
|
33
|
+
}
|
|
34
|
+
export interface AgentResult {
|
|
35
|
+
outcome: "completed" | "failed" | "timed_out";
|
|
36
|
+
output?: string;
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface AgentRunnerOptions {
|
|
40
|
+
cwd?: string;
|
|
41
|
+
env?: Record<string, string>;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentRunner {
|
|
44
|
+
run(stageName: string, prompt: string, model: string, timeout: number, taskDir: string, options?: AgentRunnerOptions): Promise<AgentResult>;
|
|
45
|
+
healthCheck(): Promise<boolean>;
|
|
46
|
+
}
|
|
47
|
+
export interface PipelineContext {
|
|
48
|
+
taskId: string;
|
|
49
|
+
taskDir: string;
|
|
50
|
+
projectDir: string;
|
|
51
|
+
runners: Record<string, AgentRunner>;
|
|
52
|
+
input: {
|
|
53
|
+
mode: "full" | "rerun" | "status";
|
|
54
|
+
fromStage?: string;
|
|
55
|
+
dryRun?: boolean;
|
|
56
|
+
issueNumber?: number;
|
|
57
|
+
feedback?: string;
|
|
58
|
+
local?: boolean;
|
|
59
|
+
complexity?: "low" | "medium" | "high";
|
|
60
|
+
};
|
|
61
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ValidationResult {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
error?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function stripFences(content: string): string;
|
|
6
|
+
export declare function validateTaskJson(content: string): ValidationResult;
|
|
7
|
+
export declare function validatePlanMd(content: string): ValidationResult;
|
|
8
|
+
export declare function validateReviewMd(content: string): ValidationResult;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const REQUIRED_TASK_FIELDS = [
|
|
2
|
+
"task_type",
|
|
3
|
+
"title",
|
|
4
|
+
"description",
|
|
5
|
+
"scope",
|
|
6
|
+
"risk_level",
|
|
7
|
+
];
|
|
8
|
+
export function stripFences(content) {
|
|
9
|
+
return content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
10
|
+
}
|
|
11
|
+
export function validateTaskJson(content) {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(stripFences(content));
|
|
14
|
+
for (const field of REQUIRED_TASK_FIELDS) {
|
|
15
|
+
if (!(field in parsed)) {
|
|
16
|
+
return { valid: false, error: `Missing field: ${field}` };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { valid: true };
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return {
|
|
23
|
+
valid: false,
|
|
24
|
+
error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function validatePlanMd(content) {
|
|
29
|
+
if (content.length < 10) {
|
|
30
|
+
return { valid: false, error: "Plan is too short (< 10 chars)" };
|
|
31
|
+
}
|
|
32
|
+
if (!/^##\s+\w+/m.test(content)) {
|
|
33
|
+
return { valid: false, error: "Plan has no markdown h2 sections" };
|
|
34
|
+
}
|
|
35
|
+
return { valid: true };
|
|
36
|
+
}
|
|
37
|
+
export function validateReviewMd(content) {
|
|
38
|
+
if (/pass/i.test(content) || /fail/i.test(content)) {
|
|
39
|
+
return { valid: true };
|
|
40
|
+
}
|
|
41
|
+
return { valid: false, error: "Review must contain 'pass' or 'fail'" };
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface VerifyResult {
|
|
2
|
+
pass: boolean;
|
|
3
|
+
errors: string[];
|
|
4
|
+
summary: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Parse a command string into [executable, ...args], respecting quoted arguments.
|
|
8
|
+
* e.g., 'pnpm -s "test:unit"' → ["pnpm", "-s", "test:unit"]
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseCommand(cmd: string): string[];
|
|
11
|
+
export declare function runQualityGates(taskDir: string, projectRoot?: string): VerifyResult;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
|
+
import { getProjectConfig, VERIFY_COMMAND_TIMEOUT_MS } from "./config.js";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
function isExecError(err) {
|
|
5
|
+
return typeof err === "object" && err !== null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parse a command string into [executable, ...args], respecting quoted arguments.
|
|
9
|
+
* e.g., 'pnpm -s "test:unit"' → ["pnpm", "-s", "test:unit"]
|
|
10
|
+
*/
|
|
11
|
+
export function parseCommand(cmd) {
|
|
12
|
+
const parts = [];
|
|
13
|
+
let current = "";
|
|
14
|
+
let inQuote = null;
|
|
15
|
+
for (const ch of cmd) {
|
|
16
|
+
if (inQuote) {
|
|
17
|
+
if (ch === inQuote) {
|
|
18
|
+
inQuote = null;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
current += ch;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (ch === '"' || ch === "'") {
|
|
25
|
+
inQuote = ch;
|
|
26
|
+
}
|
|
27
|
+
else if (/\s/.test(ch)) {
|
|
28
|
+
if (current) {
|
|
29
|
+
parts.push(current);
|
|
30
|
+
current = "";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
current += ch;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (current)
|
|
38
|
+
parts.push(current);
|
|
39
|
+
if (inQuote)
|
|
40
|
+
logger.warn(`Unclosed quote in command: ${cmd}`);
|
|
41
|
+
return parts;
|
|
42
|
+
}
|
|
43
|
+
function runCommand(cmd, cwd, timeout) {
|
|
44
|
+
const parts = parseCommand(cmd);
|
|
45
|
+
if (parts.length === 0) {
|
|
46
|
+
return { success: true, output: "", timedOut: false };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const output = execFileSync(parts[0], parts.slice(1), {
|
|
50
|
+
cwd,
|
|
51
|
+
timeout,
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
54
|
+
env: { ...process.env, FORCE_COLOR: "0" },
|
|
55
|
+
});
|
|
56
|
+
return { success: true, output: output ?? "", timedOut: false };
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const stdout = isExecError(err) ? err.stdout ?? "" : "";
|
|
60
|
+
const stderr = isExecError(err) ? err.stderr ?? "" : "";
|
|
61
|
+
const killed = isExecError(err) ? !!err.killed : false;
|
|
62
|
+
return { success: false, output: `${stdout}${stderr}`, timedOut: killed };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function parseErrors(output) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
for (const line of output.split("\n")) {
|
|
68
|
+
if (/error|Error|ERROR|failed|Failed|FAIL|warning:|Warning:|WARN/i.test(line)) {
|
|
69
|
+
errors.push(line.slice(0, 500));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return errors;
|
|
73
|
+
}
|
|
74
|
+
function extractSummary(output, cmdName) {
|
|
75
|
+
const summaryPatterns = /Test Suites|Tests|Coverage|ERRORS|FAILURES|success|completed/i;
|
|
76
|
+
const lines = output.split("\n").filter((l) => summaryPatterns.test(l));
|
|
77
|
+
return lines.slice(-3).map((l) => `[${cmdName}] ${l.trim()}`);
|
|
78
|
+
}
|
|
79
|
+
export function runQualityGates(taskDir, projectRoot) {
|
|
80
|
+
const config = getProjectConfig();
|
|
81
|
+
const cwd = projectRoot ?? process.cwd();
|
|
82
|
+
const allErrors = [];
|
|
83
|
+
const allSummary = [];
|
|
84
|
+
let allPass = true;
|
|
85
|
+
const commands = [
|
|
86
|
+
{ name: "typecheck", cmd: config.quality.typecheck },
|
|
87
|
+
{ name: "test", cmd: config.quality.testUnit },
|
|
88
|
+
];
|
|
89
|
+
if (config.quality.lint) {
|
|
90
|
+
commands.push({ name: "lint", cmd: config.quality.lint });
|
|
91
|
+
}
|
|
92
|
+
for (const { name, cmd } of commands) {
|
|
93
|
+
if (!cmd)
|
|
94
|
+
continue;
|
|
95
|
+
logger.info(` Running ${name}: ${cmd}`);
|
|
96
|
+
const result = runCommand(cmd, cwd, VERIFY_COMMAND_TIMEOUT_MS);
|
|
97
|
+
if (result.timedOut) {
|
|
98
|
+
allErrors.push(`${name}: timed out after ${VERIFY_COMMAND_TIMEOUT_MS / 1000}s`);
|
|
99
|
+
allPass = false;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!result.success) {
|
|
103
|
+
allPass = false;
|
|
104
|
+
const errors = parseErrors(result.output);
|
|
105
|
+
allErrors.push(...errors.map((e) => `[${name}] ${e}`));
|
|
106
|
+
}
|
|
107
|
+
allSummary.push(...extractSummary(result.output, name));
|
|
108
|
+
}
|
|
109
|
+
return { pass: allPass, errors: allErrors, summary: allSummary };
|
|
110
|
+
}
|
package/kody.config.schema.json
CHANGED
|
@@ -23,11 +23,6 @@
|
|
|
23
23
|
"description": "Auto-fix lint command, run when verify fails (e.g., 'pnpm lint:fix')",
|
|
24
24
|
"default": ""
|
|
25
25
|
},
|
|
26
|
-
"format": {
|
|
27
|
-
"type": "string",
|
|
28
|
-
"description": "Format check command (e.g., 'pnpm format:check'). Empty to skip.",
|
|
29
|
-
"default": ""
|
|
30
|
-
},
|
|
31
26
|
"formatFix": {
|
|
32
27
|
"type": "string",
|
|
33
28
|
"description": "Auto-fix format command, run when verify fails (e.g., 'pnpm format')",
|
|
@@ -49,14 +44,6 @@
|
|
|
49
44
|
"type": "string",
|
|
50
45
|
"description": "Default branch for PR base and branch syncing (e.g., 'main', 'dev')",
|
|
51
46
|
"default": "dev"
|
|
52
|
-
},
|
|
53
|
-
"userEmail": {
|
|
54
|
-
"type": "string",
|
|
55
|
-
"description": "Git user email for CI commits (optional, defaults to github-actions bot)"
|
|
56
|
-
},
|
|
57
|
-
"userName": {
|
|
58
|
-
"type": "string",
|
|
59
|
-
"description": "Git user name for CI commits (optional, defaults to github-actions bot)"
|
|
60
47
|
}
|
|
61
48
|
},
|
|
62
49
|
"additionalProperties": false
|
|
@@ -76,50 +63,27 @@
|
|
|
76
63
|
},
|
|
77
64
|
"additionalProperties": false
|
|
78
65
|
},
|
|
79
|
-
"paths": {
|
|
80
|
-
"type": "object",
|
|
81
|
-
"description": "File path configuration",
|
|
82
|
-
"properties": {
|
|
83
|
-
"taskDir": {
|
|
84
|
-
"type": "string",
|
|
85
|
-
"description": "Directory for pipeline artifacts and state (committed to branch)",
|
|
86
|
-
"default": ".kody/tasks"
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
"additionalProperties": false
|
|
90
|
-
},
|
|
91
66
|
"agent": {
|
|
92
67
|
"type": "object",
|
|
93
68
|
"description": "Agent execution configuration",
|
|
94
69
|
"properties": {
|
|
95
|
-
"runner": {
|
|
96
|
-
"type": "string",
|
|
97
|
-
"description": "Agent runner type",
|
|
98
|
-
"enum": ["claude-code"],
|
|
99
|
-
"default": "claude-code"
|
|
100
|
-
},
|
|
101
|
-
"defaultRunner": {
|
|
102
|
-
"type": "string",
|
|
103
|
-
"description": "Name of the default runner to use",
|
|
104
|
-
"default": "claude"
|
|
105
|
-
},
|
|
106
70
|
"modelMap": {
|
|
107
71
|
"type": "object",
|
|
108
|
-
"description": "Maps model tiers to actual model names.
|
|
72
|
+
"description": "Maps model tiers to actual model names. When provider is set, values should be the provider's model names.",
|
|
109
73
|
"properties": {
|
|
110
74
|
"cheap": {
|
|
111
75
|
"type": "string",
|
|
112
|
-
"description": "Fast/cheap model for taskify stage (e.g., 'haiku' or
|
|
76
|
+
"description": "Fast/cheap model for taskify stage (e.g., 'haiku' or provider model name)",
|
|
113
77
|
"default": "haiku"
|
|
114
78
|
},
|
|
115
79
|
"mid": {
|
|
116
80
|
"type": "string",
|
|
117
|
-
"description": "Mid-tier model for build, review-fix, autofix (e.g., 'sonnet' or
|
|
81
|
+
"description": "Mid-tier model for build, review-fix, autofix (e.g., 'sonnet' or provider model name)",
|
|
118
82
|
"default": "sonnet"
|
|
119
83
|
},
|
|
120
84
|
"strong": {
|
|
121
85
|
"type": "string",
|
|
122
|
-
"description": "Strongest model for plan, review — deep reasoning (e.g., 'opus' or
|
|
86
|
+
"description": "Strongest model for plan, review — deep reasoning (e.g., 'opus' or provider model name)",
|
|
123
87
|
"default": "opus"
|
|
124
88
|
}
|
|
125
89
|
},
|
|
@@ -130,15 +94,10 @@
|
|
|
130
94
|
"description": "LLM provider name. When set (and not 'anthropic'), engine auto-starts LiteLLM proxy and routes model calls to this provider. modelMap values should be the provider's model names.",
|
|
131
95
|
"examples": ["anthropic", "minimax", "openai", "google"]
|
|
132
96
|
},
|
|
133
|
-
"
|
|
97
|
+
"defaultRunner": {
|
|
134
98
|
"type": "string",
|
|
135
|
-
"description": "
|
|
136
|
-
"
|
|
137
|
-
},
|
|
138
|
-
"usePerStageRouting": {
|
|
139
|
-
"type": "boolean",
|
|
140
|
-
"description": "When true, uses stage name as the LiteLLM model alias instead of modelMap tier",
|
|
141
|
-
"default": false
|
|
99
|
+
"description": "Name of the default runner when multiple runners are configured (advanced)",
|
|
100
|
+
"default": "claude"
|
|
142
101
|
},
|
|
143
102
|
"runners": {
|
|
144
103
|
"type": "object",
|