@kody-ade/kody-engine-lite 0.1.7 → 0.1.9
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 +27 -10
- package/dist/bin/cli.js +329 -42
- package/package.json +1 -1
- package/templates/kody.yml +10 -2
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ git push
|
|
|
112
112
|
Then comment on any issue:
|
|
113
113
|
|
|
114
114
|
```
|
|
115
|
-
@kody
|
|
115
|
+
@kody
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
## CLI Usage
|
|
@@ -121,19 +121,25 @@ Then comment on any issue:
|
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
123
|
# Run against current directory
|
|
124
|
-
kody-engine-lite run --task
|
|
124
|
+
kody-engine-lite run --task "Add a sum function to src/math.ts with tests"
|
|
125
125
|
|
|
126
126
|
# Run against a different project
|
|
127
|
-
kody-engine-lite run --task
|
|
127
|
+
kody-engine-lite run --task "Add feature X" --cwd /path/to/project
|
|
128
128
|
|
|
129
129
|
# Run from a GitHub issue (fetches issue body as task)
|
|
130
|
-
kody-engine-lite run --
|
|
130
|
+
kody-engine-lite run --issue-number 1 --cwd /path/to/project
|
|
131
131
|
|
|
132
132
|
# Dry run (no agent calls)
|
|
133
|
-
kody-engine-lite run --task
|
|
133
|
+
kody-engine-lite run --task "Test" --dry-run
|
|
134
134
|
|
|
135
|
-
# Resume from a failed stage
|
|
136
|
-
kody-engine-lite rerun --
|
|
135
|
+
# Resume from a failed/paused stage (auto-detects which stage)
|
|
136
|
+
kody-engine-lite rerun --issue-number 1
|
|
137
|
+
|
|
138
|
+
# Fix — rerun from build stage (skip taskify/plan)
|
|
139
|
+
kody-engine-lite fix --issue-number 1
|
|
140
|
+
|
|
141
|
+
# Fix with feedback
|
|
142
|
+
kody-engine-lite fix --issue-number 1 --feedback "Use middleware pattern instead"
|
|
137
143
|
|
|
138
144
|
# Check pipeline status
|
|
139
145
|
kody-engine-lite status --task-id my-task
|
|
@@ -154,9 +160,11 @@ Comment on any issue:
|
|
|
154
160
|
```
|
|
155
161
|
@kody # Run full pipeline (auto-generates task-id)
|
|
156
162
|
@kody full <task-id> # Run with specific task-id
|
|
157
|
-
@kody rerun
|
|
158
|
-
@kody rerun
|
|
159
|
-
@kody
|
|
163
|
+
@kody rerun # Resume latest task (auto-detects stage)
|
|
164
|
+
@kody rerun --from <stage> # Resume from specific stage
|
|
165
|
+
@kody fix # Re-build from build stage (skip taskify/plan)
|
|
166
|
+
@kody fix --feedback "Use X instead of Y" # Fix with specific guidance
|
|
167
|
+
@kody approve # Approve + provide answers to questions
|
|
160
168
|
@kody status <task-id> # Check status
|
|
161
169
|
```
|
|
162
170
|
|
|
@@ -195,6 +203,15 @@ Kody resumes automatically from where it paused, with your answers injected as c
|
|
|
195
203
|
| review-fix | sonnet | Applies known fixes from review findings |
|
|
196
204
|
| ship | — | Pushes branch + creates PR + comments on issue |
|
|
197
205
|
|
|
206
|
+
### Branch Syncing
|
|
207
|
+
|
|
208
|
+
On every run/rerun/fix, Kody automatically:
|
|
209
|
+
1. Checks out (or creates) the feature branch
|
|
210
|
+
2. Pulls latest from the default branch and merges into the feature branch
|
|
211
|
+
3. If there's a merge conflict, skips the sync and warns
|
|
212
|
+
|
|
213
|
+
This ensures the feature branch is always up-to-date before building.
|
|
214
|
+
|
|
198
215
|
### Automatic Loops
|
|
199
216
|
|
|
200
217
|
- **Verify + autofix**: If verify fails, runs lint-fix + format-fix + autofix agent, retries up to 2 times
|
package/dist/bin/cli.js
CHANGED
|
@@ -109,11 +109,17 @@ function createClaudeCodeRunner() {
|
|
|
109
109
|
}
|
|
110
110
|
function createOpenCodeRunner() {
|
|
111
111
|
return {
|
|
112
|
-
async run(stageName, prompt,
|
|
112
|
+
async run(stageName, prompt, model, timeout, _taskDir, options) {
|
|
113
|
+
const args2 = ["run"];
|
|
114
|
+
if (model) {
|
|
115
|
+
args2.push("--model", model);
|
|
116
|
+
}
|
|
117
|
+
args2.push(prompt);
|
|
113
118
|
return runSubprocess(
|
|
114
119
|
"opencode",
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
args2,
|
|
121
|
+
"",
|
|
122
|
+
// opencode takes message as positional, not stdin
|
|
117
123
|
timeout,
|
|
118
124
|
options
|
|
119
125
|
);
|
|
@@ -582,6 +588,27 @@ function ensureFeatureBranch(issueNumber, title, cwd) {
|
|
|
582
588
|
logger.info(` Created new branch: ${branchName}`);
|
|
583
589
|
return branchName;
|
|
584
590
|
}
|
|
591
|
+
function syncWithDefault(cwd) {
|
|
592
|
+
const defaultBranch = getDefaultBranch(cwd);
|
|
593
|
+
const current = getCurrentBranch(cwd);
|
|
594
|
+
if (current === defaultBranch) return;
|
|
595
|
+
try {
|
|
596
|
+
git(["fetch", "origin", defaultBranch], { cwd, timeout: 3e4 });
|
|
597
|
+
} catch {
|
|
598
|
+
logger.warn(" Failed to fetch latest from origin");
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
git(["merge", `origin/${defaultBranch}`, "--no-edit"], { cwd, timeout: 3e4 });
|
|
603
|
+
logger.info(` Synced with origin/${defaultBranch}`);
|
|
604
|
+
} catch {
|
|
605
|
+
try {
|
|
606
|
+
git(["merge", "--abort"], { cwd });
|
|
607
|
+
} catch {
|
|
608
|
+
}
|
|
609
|
+
logger.warn(` Merge conflict with origin/${defaultBranch} \u2014 skipping sync`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
585
612
|
function commitAll(message, cwd) {
|
|
586
613
|
const status = git(["status", "--porcelain"], { cwd });
|
|
587
614
|
if (!status) {
|
|
@@ -892,8 +919,9 @@ async function executeAgentStage(ctx, def) {
|
|
|
892
919
|
}
|
|
893
920
|
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
|
|
894
921
|
const model = resolveModel(def.modelTier, def.name);
|
|
895
|
-
logger.info(` model=${model} timeout=${def.timeout / 1e3}s`);
|
|
896
922
|
const config = getProjectConfig();
|
|
923
|
+
const runnerName = config.agent.stageRunners?.[def.name] ?? config.agent.defaultRunner ?? Object.keys(ctx.runners)[0] ?? "claude";
|
|
924
|
+
logger.info(` runner=${runnerName} model=${model} timeout=${def.timeout / 1e3}s`);
|
|
897
925
|
const extraEnv = {};
|
|
898
926
|
if (config.agent.litellmUrl) {
|
|
899
927
|
extraEnv.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
|
|
@@ -1190,8 +1218,9 @@ async function runPipeline(ctx) {
|
|
|
1190
1218
|
const taskMdPath = path4.join(ctx.taskDir, "task.md");
|
|
1191
1219
|
const title = fs4.existsSync(taskMdPath) ? fs4.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
1192
1220
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
1221
|
+
syncWithDefault(ctx.projectDir);
|
|
1193
1222
|
} catch (err) {
|
|
1194
|
-
logger.warn(` Failed to create feature branch: ${err}`);
|
|
1223
|
+
logger.warn(` Failed to create/sync feature branch: ${err}`);
|
|
1195
1224
|
}
|
|
1196
1225
|
}
|
|
1197
1226
|
let complexity = ctx.input.complexity ?? "high";
|
|
@@ -1621,12 +1650,13 @@ function parseArgs() {
|
|
|
1621
1650
|
console.log(`Usage:
|
|
1622
1651
|
kody run --task-id <id> [--task "<desc>"] [--cwd <path>] [--issue-number <n>] [--complexity low|medium|high] [--feedback "<text>"] [--local] [--dry-run]
|
|
1623
1652
|
kody rerun --task-id <id> --from <stage> [--cwd <path>] [--issue-number <n>]
|
|
1653
|
+
kody fix --task-id <id> [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
|
|
1624
1654
|
kody status --task-id <id> [--cwd <path>]
|
|
1625
1655
|
kody --help`);
|
|
1626
1656
|
process.exit(0);
|
|
1627
1657
|
}
|
|
1628
1658
|
const command2 = args2[0];
|
|
1629
|
-
if (!["run", "rerun", "status"].includes(command2)) {
|
|
1659
|
+
if (!["run", "rerun", "fix", "status"].includes(command2)) {
|
|
1630
1660
|
console.error(`Unknown command: ${command2}`);
|
|
1631
1661
|
process.exit(1);
|
|
1632
1662
|
}
|
|
@@ -1671,7 +1701,7 @@ async function main() {
|
|
|
1671
1701
|
}
|
|
1672
1702
|
let taskId = input.taskId;
|
|
1673
1703
|
if (!taskId) {
|
|
1674
|
-
if (input.command === "rerun" && input.issueNumber) {
|
|
1704
|
+
if ((input.command === "rerun" || input.command === "fix") && input.issueNumber) {
|
|
1675
1705
|
const found = findLatestTaskForIssue(input.issueNumber, projectDir);
|
|
1676
1706
|
if (!found) {
|
|
1677
1707
|
console.error(`No previous task found for issue #${input.issueNumber}`);
|
|
@@ -1717,6 +1747,9 @@ ${issue.body ?? ""}`;
|
|
|
1717
1747
|
process.exit(1);
|
|
1718
1748
|
}
|
|
1719
1749
|
}
|
|
1750
|
+
if (input.command === "fix" && !input.fromStage) {
|
|
1751
|
+
input.fromStage = "build";
|
|
1752
|
+
}
|
|
1720
1753
|
if (input.command === "rerun" && !input.fromStage) {
|
|
1721
1754
|
const statusPath = path5.join(taskDir, "status.json");
|
|
1722
1755
|
if (fs6.existsSync(statusPath)) {
|
|
@@ -1774,7 +1807,7 @@ ${issue.body ?? ""}`;
|
|
|
1774
1807
|
projectDir,
|
|
1775
1808
|
runners,
|
|
1776
1809
|
input: {
|
|
1777
|
-
mode: input.command === "rerun" ? "rerun" : "full",
|
|
1810
|
+
mode: input.command === "rerun" || input.command === "fix" ? "rerun" : "full",
|
|
1778
1811
|
fromStage: input.fromStage,
|
|
1779
1812
|
dryRun: input.dryRun,
|
|
1780
1813
|
issueNumber: input.issueNumber,
|
|
@@ -2004,6 +2037,270 @@ function detectArchitecture(cwd) {
|
|
|
2004
2037
|
if (configs.length > 0) detected.push(`- Config files: ${configs.join(", ")}`);
|
|
2005
2038
|
return detected;
|
|
2006
2039
|
}
|
|
2040
|
+
function detectBasicConfig(cwd) {
|
|
2041
|
+
let pm = "pnpm";
|
|
2042
|
+
if (fs7.existsSync(path6.join(cwd, "yarn.lock"))) pm = "yarn";
|
|
2043
|
+
else if (fs7.existsSync(path6.join(cwd, "bun.lockb"))) pm = "bun";
|
|
2044
|
+
else if (!fs7.existsSync(path6.join(cwd, "pnpm-lock.yaml")) && fs7.existsSync(path6.join(cwd, "package-lock.json"))) pm = "npm";
|
|
2045
|
+
let defaultBranch = "main";
|
|
2046
|
+
try {
|
|
2047
|
+
const ref = execFileSync7("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
2048
|
+
encoding: "utf-8",
|
|
2049
|
+
timeout: 5e3,
|
|
2050
|
+
cwd,
|
|
2051
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2052
|
+
}).trim();
|
|
2053
|
+
defaultBranch = ref.replace("refs/remotes/origin/", "");
|
|
2054
|
+
} catch {
|
|
2055
|
+
try {
|
|
2056
|
+
execFileSync7("git", ["rev-parse", "--verify", "origin/dev"], {
|
|
2057
|
+
encoding: "utf-8",
|
|
2058
|
+
timeout: 5e3,
|
|
2059
|
+
cwd,
|
|
2060
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2061
|
+
});
|
|
2062
|
+
defaultBranch = "dev";
|
|
2063
|
+
} catch {
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
let owner = "";
|
|
2067
|
+
let repo = "";
|
|
2068
|
+
try {
|
|
2069
|
+
const remote = execFileSync7("git", ["remote", "get-url", "origin"], {
|
|
2070
|
+
encoding: "utf-8",
|
|
2071
|
+
timeout: 5e3,
|
|
2072
|
+
cwd,
|
|
2073
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2074
|
+
}).trim();
|
|
2075
|
+
const match = remote.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
|
|
2076
|
+
if (match) {
|
|
2077
|
+
owner = match[1];
|
|
2078
|
+
repo = match[2];
|
|
2079
|
+
}
|
|
2080
|
+
} catch {
|
|
2081
|
+
}
|
|
2082
|
+
const hasOpenCode = fs7.existsSync(path6.join(cwd, "opencode.json"));
|
|
2083
|
+
return { defaultBranch, owner, repo, pm, hasOpenCode };
|
|
2084
|
+
}
|
|
2085
|
+
function smartInit(cwd) {
|
|
2086
|
+
const basic = detectBasicConfig(cwd);
|
|
2087
|
+
let context = "";
|
|
2088
|
+
const readIfExists = (rel, maxChars = 3e3) => {
|
|
2089
|
+
const p = path6.join(cwd, rel);
|
|
2090
|
+
if (fs7.existsSync(p)) {
|
|
2091
|
+
const content = fs7.readFileSync(p, "utf-8");
|
|
2092
|
+
return content.slice(0, maxChars);
|
|
2093
|
+
}
|
|
2094
|
+
return null;
|
|
2095
|
+
};
|
|
2096
|
+
const pkgJson = readIfExists("package.json");
|
|
2097
|
+
if (pkgJson) context += `## package.json
|
|
2098
|
+
${pkgJson}
|
|
2099
|
+
|
|
2100
|
+
`;
|
|
2101
|
+
const tsconfig = readIfExists("tsconfig.json", 1e3);
|
|
2102
|
+
if (tsconfig) context += `## tsconfig.json
|
|
2103
|
+
${tsconfig}
|
|
2104
|
+
|
|
2105
|
+
`;
|
|
2106
|
+
const readme = readIfExists("README.md", 2e3);
|
|
2107
|
+
if (readme) context += `## README.md (first 2000 chars)
|
|
2108
|
+
${readme}
|
|
2109
|
+
|
|
2110
|
+
`;
|
|
2111
|
+
const claudeMd = readIfExists("CLAUDE.md", 3e3);
|
|
2112
|
+
if (claudeMd) context += `## CLAUDE.md
|
|
2113
|
+
${claudeMd}
|
|
2114
|
+
|
|
2115
|
+
`;
|
|
2116
|
+
try {
|
|
2117
|
+
const topDirs = fs7.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
2118
|
+
context += `## Top-level directories
|
|
2119
|
+
${topDirs.join(", ")}
|
|
2120
|
+
|
|
2121
|
+
`;
|
|
2122
|
+
const srcDir = path6.join(cwd, "src");
|
|
2123
|
+
if (fs7.existsSync(srcDir)) {
|
|
2124
|
+
const srcDirs = fs7.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2125
|
+
context += `## src/ subdirectories
|
|
2126
|
+
${srcDirs.join(", ")}
|
|
2127
|
+
|
|
2128
|
+
`;
|
|
2129
|
+
}
|
|
2130
|
+
} catch {
|
|
2131
|
+
}
|
|
2132
|
+
const existingFiles = [];
|
|
2133
|
+
for (const f of [".env.example", "CLAUDE.md", ".ai-docs", "opencode.json", "vitest.config.ts", "vitest.config.mts", "jest.config.ts", "playwright.config.ts", ".eslintrc.js", "eslint.config.mjs", ".prettierrc"]) {
|
|
2134
|
+
if (fs7.existsSync(path6.join(cwd, f))) existingFiles.push(f);
|
|
2135
|
+
}
|
|
2136
|
+
if (existingFiles.length) context += `## Config files present
|
|
2137
|
+
${existingFiles.join(", ")}
|
|
2138
|
+
|
|
2139
|
+
`;
|
|
2140
|
+
context += `## Detected: package manager=${basic.pm}, default branch=${basic.defaultBranch}, github=${basic.owner}/${basic.repo}, opencode=${basic.hasOpenCode}
|
|
2141
|
+
`;
|
|
2142
|
+
const prompt = `You are analyzing a project to configure Kody (an autonomous SDLC pipeline).
|
|
2143
|
+
|
|
2144
|
+
Given this project context, output ONLY a JSON object with EXACTLY this structure:
|
|
2145
|
+
|
|
2146
|
+
{
|
|
2147
|
+
"config": {
|
|
2148
|
+
"quality": {
|
|
2149
|
+
"typecheck": "${basic.pm} <script or command>",
|
|
2150
|
+
"lint": "${basic.pm} <script or command>",
|
|
2151
|
+
"lintFix": "${basic.pm} <script or command>",
|
|
2152
|
+
"format": "${basic.pm} <script or command>",
|
|
2153
|
+
"formatFix": "${basic.pm} <script or command>",
|
|
2154
|
+
"testUnit": "${basic.pm} <script or command>"
|
|
2155
|
+
},
|
|
2156
|
+
"git": { "defaultBranch": "${basic.defaultBranch}" },
|
|
2157
|
+
"github": { "owner": "${basic.owner}", "repo": "${basic.repo}" },
|
|
2158
|
+
"paths": { "taskDir": ".tasks" },
|
|
2159
|
+
"agent": {
|
|
2160
|
+
"runner": "${basic.hasOpenCode ? "opencode" : "claude-code"}",
|
|
2161
|
+
"defaultRunner": "${basic.hasOpenCode ? "opencode" : "claude"}",
|
|
2162
|
+
"modelMap": { "cheap": "haiku", "mid": "sonnet", "strong": "opus" }
|
|
2163
|
+
}
|
|
2164
|
+
},
|
|
2165
|
+
"architecture": "# Architecture\\n\\n<markdown content>",
|
|
2166
|
+
"conventions": "# Conventions\\n\\n<markdown content>"
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
CRITICAL rules for config.quality:
|
|
2170
|
+
- Every command MUST start with "${basic.pm}" (e.g., "${basic.pm} typecheck", "${basic.pm} lint")
|
|
2171
|
+
- Look at the package.json "scripts" section to find the correct script names
|
|
2172
|
+
- testUnit must run ONLY unit tests \u2014 exclude integration and e2e tests. If there's a "test:unit" script use it. Otherwise use "test" but add exclude flags for int/e2e.
|
|
2173
|
+
- If a script doesn't exist and can't be inferred, set the value to ""
|
|
2174
|
+
- Do NOT invent commands that don't exist in package.json scripts
|
|
2175
|
+
|
|
2176
|
+
Rules for architecture (markdown string):
|
|
2177
|
+
- Be specific about THIS project
|
|
2178
|
+
- Include: framework, language, database, testing, key directories, data flow
|
|
2179
|
+
- Reference CLAUDE.md and .ai-docs/ if they exist
|
|
2180
|
+
- Keep under 50 lines
|
|
2181
|
+
|
|
2182
|
+
Rules for conventions (markdown string):
|
|
2183
|
+
- Extract actual patterns from the project
|
|
2184
|
+
- If CLAUDE.md exists, reference it
|
|
2185
|
+
- If .ai-docs/ exists, reference it
|
|
2186
|
+
- Keep under 30 lines
|
|
2187
|
+
|
|
2188
|
+
Output ONLY valid JSON. No markdown fences. No explanation before or after.
|
|
2189
|
+
|
|
2190
|
+
${context}`;
|
|
2191
|
+
console.log(" \u23F3 Analyzing project with Claude Code...");
|
|
2192
|
+
try {
|
|
2193
|
+
const output = execFileSync7("claude", [
|
|
2194
|
+
"--print",
|
|
2195
|
+
"--model",
|
|
2196
|
+
"haiku",
|
|
2197
|
+
"--dangerously-skip-permissions",
|
|
2198
|
+
prompt
|
|
2199
|
+
], {
|
|
2200
|
+
encoding: "utf-8",
|
|
2201
|
+
timeout: 12e4,
|
|
2202
|
+
cwd,
|
|
2203
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2204
|
+
}).trim();
|
|
2205
|
+
const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2206
|
+
const parsed = JSON.parse(cleaned);
|
|
2207
|
+
const config = parsed.config ?? {};
|
|
2208
|
+
if (!config.git) config.git = {};
|
|
2209
|
+
if (!config.github) config.github = {};
|
|
2210
|
+
if (!config.paths) config.paths = {};
|
|
2211
|
+
if (!config.agent) config.agent = {};
|
|
2212
|
+
config.git.defaultBranch = config.git.defaultBranch || basic.defaultBranch;
|
|
2213
|
+
config.github.owner = config.github.owner || basic.owner;
|
|
2214
|
+
config.github.repo = config.github.repo || basic.repo;
|
|
2215
|
+
config.paths.taskDir = config.paths.taskDir || ".tasks";
|
|
2216
|
+
config.agent.runner = config.agent.runner || (basic.hasOpenCode ? "opencode" : "claude-code");
|
|
2217
|
+
config.agent.defaultRunner = config.agent.defaultRunner || (basic.hasOpenCode ? "opencode" : "claude");
|
|
2218
|
+
if (!config.agent.modelMap) {
|
|
2219
|
+
config.agent.modelMap = { cheap: "haiku", mid: "sonnet", strong: "opus" };
|
|
2220
|
+
}
|
|
2221
|
+
validateQualityCommands(cwd, config, basic.pm);
|
|
2222
|
+
return {
|
|
2223
|
+
config,
|
|
2224
|
+
architecture: parsed.architecture ?? "",
|
|
2225
|
+
conventions: parsed.conventions ?? ""
|
|
2226
|
+
};
|
|
2227
|
+
} catch (err) {
|
|
2228
|
+
console.log(" \u26A0 Smart detection failed, falling back to basic detection");
|
|
2229
|
+
return {
|
|
2230
|
+
config: buildFallbackConfig(cwd, basic),
|
|
2231
|
+
architecture: "",
|
|
2232
|
+
conventions: ""
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
function validateQualityCommands(cwd, config, pm) {
|
|
2237
|
+
let scripts = {};
|
|
2238
|
+
try {
|
|
2239
|
+
const pkg = JSON.parse(fs7.readFileSync(path6.join(cwd, "package.json"), "utf-8"));
|
|
2240
|
+
scripts = pkg.scripts ?? {};
|
|
2241
|
+
} catch {
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
const quality = config.quality ?? {};
|
|
2245
|
+
const overrides = [
|
|
2246
|
+
{ key: "typecheck", preferred: ["typecheck", "type-check"] },
|
|
2247
|
+
{ key: "lint", preferred: ["lint"] },
|
|
2248
|
+
{ key: "lintFix", preferred: ["lint:fix", "lint-fix"] },
|
|
2249
|
+
{ key: "format", preferred: ["format:check", "format-check", "prettier:check"] },
|
|
2250
|
+
{ key: "formatFix", preferred: ["format", "format:fix", "format-fix"] },
|
|
2251
|
+
{ key: "testUnit", preferred: ["test:unit", "test-unit", "test:ci"] }
|
|
2252
|
+
];
|
|
2253
|
+
for (const { key, preferred } of overrides) {
|
|
2254
|
+
const match = preferred.find((s) => scripts[s]);
|
|
2255
|
+
if (match) {
|
|
2256
|
+
const correct = `${pm} ${match}`;
|
|
2257
|
+
if (quality[key] !== correct) {
|
|
2258
|
+
quality[key] = correct;
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
if (quality[key]) {
|
|
2262
|
+
const scriptName = quality[key].replace(`${pm} `, "");
|
|
2263
|
+
if (scriptName && !scripts[scriptName] && !scriptName.includes(" ")) {
|
|
2264
|
+
quality[key] = "";
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
config.quality = quality;
|
|
2269
|
+
}
|
|
2270
|
+
function buildFallbackConfig(cwd, basic) {
|
|
2271
|
+
const pkg = (() => {
|
|
2272
|
+
try {
|
|
2273
|
+
return JSON.parse(fs7.readFileSync(path6.join(cwd, "package.json"), "utf-8"));
|
|
2274
|
+
} catch {
|
|
2275
|
+
return {};
|
|
2276
|
+
}
|
|
2277
|
+
})();
|
|
2278
|
+
const scripts = pkg.scripts ?? {};
|
|
2279
|
+
const find = (...c) => {
|
|
2280
|
+
for (const s of c) {
|
|
2281
|
+
if (scripts[s]) return `${basic.pm} ${s}`;
|
|
2282
|
+
}
|
|
2283
|
+
return "";
|
|
2284
|
+
};
|
|
2285
|
+
return {
|
|
2286
|
+
quality: {
|
|
2287
|
+
typecheck: find("typecheck", "type-check") || (pkg.devDependencies?.typescript ? `${basic.pm} tsc --noEmit` : ""),
|
|
2288
|
+
lint: find("lint"),
|
|
2289
|
+
lintFix: find("lint:fix", "lint-fix"),
|
|
2290
|
+
format: find("format:check"),
|
|
2291
|
+
formatFix: find("format", "format:fix"),
|
|
2292
|
+
testUnit: find("test:unit", "test", "test:ci")
|
|
2293
|
+
},
|
|
2294
|
+
git: { defaultBranch: basic.defaultBranch },
|
|
2295
|
+
github: { owner: basic.owner, repo: basic.repo },
|
|
2296
|
+
paths: { taskDir: ".tasks" },
|
|
2297
|
+
agent: {
|
|
2298
|
+
runner: basic.hasOpenCode ? "opencode" : "claude-code",
|
|
2299
|
+
defaultRunner: basic.hasOpenCode ? "opencode" : "claude",
|
|
2300
|
+
modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2007
2304
|
function initCommand(opts) {
|
|
2008
2305
|
const cwd = process.cwd();
|
|
2009
2306
|
console.log(`
|
|
@@ -2027,26 +2324,11 @@ function initCommand(opts) {
|
|
|
2027
2324
|
console.log(" \u2713 .github/workflows/kody.yml");
|
|
2028
2325
|
}
|
|
2029
2326
|
const configDest = path6.join(cwd, "kody.config.json");
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
lintFix: "",
|
|
2036
|
-
format: "",
|
|
2037
|
-
formatFix: "",
|
|
2038
|
-
testUnit: "pnpm test"
|
|
2039
|
-
},
|
|
2040
|
-
git: { defaultBranch: "main" },
|
|
2041
|
-
github: { owner: "", repo: "" },
|
|
2042
|
-
paths: { taskDir: ".tasks" },
|
|
2043
|
-
agent: {
|
|
2044
|
-
runner: "claude-code",
|
|
2045
|
-
modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
|
|
2046
|
-
}
|
|
2047
|
-
};
|
|
2048
|
-
fs7.writeFileSync(configDest, JSON.stringify(defaultConfig, null, 2) + "\n");
|
|
2049
|
-
console.log(" \u2713 kody.config.json (created \u2014 edit github.owner and github.repo)");
|
|
2327
|
+
let smartResult = null;
|
|
2328
|
+
if (!fs7.existsSync(configDest) || opts.force) {
|
|
2329
|
+
smartResult = smartInit(cwd);
|
|
2330
|
+
fs7.writeFileSync(configDest, JSON.stringify(smartResult.config, null, 2) + "\n");
|
|
2331
|
+
console.log(" \u2713 kody.config.json (auto-configured)");
|
|
2050
2332
|
} else {
|
|
2051
2333
|
console.log(" \u25CB kody.config.json (exists)");
|
|
2052
2334
|
}
|
|
@@ -2172,33 +2454,38 @@ function initCommand(opts) {
|
|
|
2172
2454
|
console.log(" \u2717 kody.config.json \u2014 invalid JSON");
|
|
2173
2455
|
}
|
|
2174
2456
|
}
|
|
2175
|
-
console.log("\n\u2500\u2500
|
|
2457
|
+
console.log("\n\u2500\u2500 Project Memory \u2500\u2500");
|
|
2176
2458
|
const memoryDir = path6.join(cwd, ".kody", "memory");
|
|
2459
|
+
fs7.mkdirSync(memoryDir, { recursive: true });
|
|
2177
2460
|
const archPath = path6.join(memoryDir, "architecture.md");
|
|
2178
|
-
|
|
2179
|
-
|
|
2461
|
+
const conventionsPath = path6.join(memoryDir, "conventions.md");
|
|
2462
|
+
if (fs7.existsSync(archPath) && !opts.force) {
|
|
2463
|
+
console.log(" \u25CB .kody/memory/architecture.md (exists, use --force to regenerate)");
|
|
2464
|
+
} else if (smartResult?.architecture) {
|
|
2465
|
+
fs7.writeFileSync(archPath, smartResult.architecture);
|
|
2466
|
+
const lineCount = smartResult.architecture.split("\n").length;
|
|
2467
|
+
console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines, LLM-generated)`);
|
|
2180
2468
|
} else {
|
|
2181
2469
|
const archItems = detectArchitecture(cwd);
|
|
2182
2470
|
if (archItems.length > 0) {
|
|
2183
|
-
fs7.mkdirSync(memoryDir, { recursive: true });
|
|
2184
2471
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2185
|
-
|
|
2472
|
+
fs7.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
|
|
2186
2473
|
|
|
2187
2474
|
## Overview
|
|
2188
2475
|
${archItems.join("\n")}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
console.log(` \u2713 .kody/memory/architecture.md (${archItems.length} items detected)`);
|
|
2192
|
-
for (const item of archItems) {
|
|
2193
|
-
console.log(` ${item}`);
|
|
2194
|
-
}
|
|
2476
|
+
`);
|
|
2477
|
+
console.log(` \u2713 .kody/memory/architecture.md (${archItems.length} items, basic detection)`);
|
|
2195
2478
|
} else {
|
|
2196
2479
|
console.log(" \u25CB No architecture detected");
|
|
2197
2480
|
}
|
|
2198
2481
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2482
|
+
if (fs7.existsSync(conventionsPath) && !opts.force) {
|
|
2483
|
+
console.log(" \u25CB .kody/memory/conventions.md (exists, use --force to regenerate)");
|
|
2484
|
+
} else if (smartResult?.conventions) {
|
|
2485
|
+
fs7.writeFileSync(conventionsPath, smartResult.conventions);
|
|
2486
|
+
const lineCount = smartResult.conventions.split("\n").length;
|
|
2487
|
+
console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines, LLM-generated)`);
|
|
2488
|
+
} else {
|
|
2202
2489
|
fs7.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
|
|
2203
2490
|
console.log(" \u2713 .kody/memory/conventions.md (seed)");
|
|
2204
2491
|
}
|
package/package.json
CHANGED
package/templates/kody.yml
CHANGED
|
@@ -103,7 +103,7 @@ jobs:
|
|
|
103
103
|
|
|
104
104
|
# Validate mode
|
|
105
105
|
case "$MODE" in
|
|
106
|
-
full|rerun|status|approve) ;;
|
|
106
|
+
full|rerun|fix|status|approve) ;;
|
|
107
107
|
*)
|
|
108
108
|
# If first arg isn't a mode, it might be a task-id or nothing
|
|
109
109
|
if [ -n "$MODE" ] && [ "$MODE" != "" ]; then
|
|
@@ -196,7 +196,15 @@ jobs:
|
|
|
196
196
|
FEEDBACK: ${{ github.event.inputs.feedback || needs.parse.outputs.feedback }}
|
|
197
197
|
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
|
|
198
198
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
199
|
-
run:
|
|
199
|
+
run: |
|
|
200
|
+
CMD="run"
|
|
201
|
+
[ "$MODE" = "rerun" ] && CMD="rerun"
|
|
202
|
+
[ "$MODE" = "fix" ] && CMD="fix"
|
|
203
|
+
ARGS="--task-id $TASK_ID --issue-number $ISSUE_NUMBER"
|
|
204
|
+
[ -n "$FROM_STAGE" ] && ARGS="$ARGS --from $FROM_STAGE"
|
|
205
|
+
[ -n "$FEEDBACK" ] && ARGS="$ARGS --feedback \"$FEEDBACK\""
|
|
206
|
+
[ "$DRY_RUN" = "true" ] && ARGS="$ARGS --dry-run"
|
|
207
|
+
kody-engine-lite $CMD $ARGS
|
|
200
208
|
|
|
201
209
|
- name: Pipeline summary
|
|
202
210
|
if: always()
|