@kody-ade/kody-engine-lite 0.1.103 → 0.1.105
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 +1 -1
- package/dist/bin/cli.js +12 -228
- package/package.json +1 -1
- package/prompts/review-fix.md +8 -12
- package/prompts/taskify.md +2 -19
- package/templates/kody.yml +17 -13
- 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/README.md
CHANGED
|
@@ -154,7 +154,7 @@ kody-engine-lite rerun --issue-number 42 --from verify
|
|
|
154
154
|
|
|
155
155
|
## Documentation
|
|
156
156
|
|
|
157
|
-
**Understand Kody:** [About](docs/ABOUT.md) · [
|
|
157
|
+
**Understand Kody:** [About](docs/ABOUT.md) · [Features](docs/FEATURES.md) · [Pipeline](docs/PIPELINE.md) · [Comparison](docs/COMPARISON.md)
|
|
158
158
|
|
|
159
159
|
**Set up & use:** [CLI](docs/CLI.md) · [Configuration](docs/CONFIGURATION.md) · [Bootstrap](docs/BOOTSTRAP.md) · [LiteLLM](docs/LITELLM.md)
|
|
160
160
|
|
package/dist/bin/cli.js
CHANGED
|
@@ -305,13 +305,7 @@ function getProjectConfig() {
|
|
|
305
305
|
},
|
|
306
306
|
timeouts: raw.timeouts ?? void 0,
|
|
307
307
|
contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers,
|
|
308
|
-
mcp: raw.mcp ? {
|
|
309
|
-
servers: {},
|
|
310
|
-
stages: ["build", "verify", "review", "review-fix"],
|
|
311
|
-
...raw.mcp,
|
|
312
|
-
// Auto-enable when devServer is configured (user can still set enabled: false to override)
|
|
313
|
-
enabled: raw.mcp.enabled ?? !!raw.mcp.devServer
|
|
314
|
-
} : void 0
|
|
308
|
+
mcp: raw.mcp ? { enabled: false, servers: {}, stages: ["build", "verify", "review", "review-fix"], ...raw.mcp } : void 0
|
|
315
309
|
};
|
|
316
310
|
} catch {
|
|
317
311
|
logger.warn("kody.config.json is invalid JSON \u2014 using defaults");
|
|
@@ -867,7 +861,7 @@ var init_github_api = __esm({
|
|
|
867
861
|
"use strict";
|
|
868
862
|
init_logger();
|
|
869
863
|
API_TIMEOUT_MS = 3e4;
|
|
870
|
-
LIFECYCLE_LABELS = ["planning", "building", "review", "
|
|
864
|
+
LIFECYCLE_LABELS = ["planning", "building", "review", "done", "failed", "waiting", "low", "medium", "high"];
|
|
871
865
|
KODY_MARKERS = [
|
|
872
866
|
"Kody Review",
|
|
873
867
|
"\u{1F916} Generated by Kody",
|
|
@@ -1287,20 +1281,6 @@ var init_context_tiers = __esm({
|
|
|
1287
1281
|
});
|
|
1288
1282
|
|
|
1289
1283
|
// src/mcp-config.ts
|
|
1290
|
-
function withPlaywrightIfNeeded(mcpConfig, hasUI) {
|
|
1291
|
-
if (!mcpConfig?.enabled || !hasUI) return mcpConfig;
|
|
1292
|
-
const hasPlaywright = Object.keys(mcpConfig.servers).some(
|
|
1293
|
-
(name) => name.toLowerCase().includes("playwright")
|
|
1294
|
-
);
|
|
1295
|
-
if (hasPlaywright) return mcpConfig;
|
|
1296
|
-
return {
|
|
1297
|
-
...mcpConfig,
|
|
1298
|
-
servers: {
|
|
1299
|
-
...mcpConfig.servers,
|
|
1300
|
-
playwright: PLAYWRIGHT_SERVER
|
|
1301
|
-
}
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
1284
|
function buildMcpConfigJson(mcpConfig) {
|
|
1305
1285
|
if (!mcpConfig?.enabled) return void 0;
|
|
1306
1286
|
if (Object.keys(mcpConfig.servers).length === 0) return void 0;
|
|
@@ -1317,18 +1297,15 @@ function buildMcpConfigJson(mcpConfig) {
|
|
|
1317
1297
|
}
|
|
1318
1298
|
function isMcpEnabledForStage(stageName, mcpConfig) {
|
|
1319
1299
|
if (!mcpConfig?.enabled) return false;
|
|
1300
|
+
if (Object.keys(mcpConfig.servers).length === 0) return false;
|
|
1320
1301
|
const allowedStages = mcpConfig.stages ?? DEFAULT_MCP_STAGES;
|
|
1321
1302
|
return allowedStages.includes(stageName);
|
|
1322
1303
|
}
|
|
1323
|
-
var DEFAULT_MCP_STAGES
|
|
1304
|
+
var DEFAULT_MCP_STAGES;
|
|
1324
1305
|
var init_mcp_config = __esm({
|
|
1325
1306
|
"src/mcp-config.ts"() {
|
|
1326
1307
|
"use strict";
|
|
1327
1308
|
DEFAULT_MCP_STAGES = ["build", "verify", "review", "review-fix"];
|
|
1328
|
-
PLAYWRIGHT_SERVER = {
|
|
1329
|
-
command: "npx",
|
|
1330
|
-
args: ["-y", "@anthropic-ai/mcp-playwright"]
|
|
1331
|
-
};
|
|
1332
1309
|
}
|
|
1333
1310
|
});
|
|
1334
1311
|
|
|
@@ -1546,11 +1523,6 @@ ${prompt}` : prompt;
|
|
|
1546
1523
|
}
|
|
1547
1524
|
if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
|
|
1548
1525
|
assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
|
|
1549
|
-
const qaGuidePath = path5.join(projectDir, ".kody", "qa-guide.md");
|
|
1550
|
-
if (fs5.existsSync(qaGuidePath)) {
|
|
1551
|
-
const qaGuide = fs5.readFileSync(qaGuidePath, "utf-8").trim();
|
|
1552
|
-
assembled = assembled + "\n\n" + qaGuide;
|
|
1553
|
-
}
|
|
1554
1526
|
}
|
|
1555
1527
|
return assembled;
|
|
1556
1528
|
}
|
|
@@ -1713,8 +1685,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
1713
1685
|
if (sessionInfo) {
|
|
1714
1686
|
logger.info(` session: ${SESSION_GROUP[def.name]} (${sessionInfo.resumeSession ? "resume" : "new"})`);
|
|
1715
1687
|
}
|
|
1716
|
-
const
|
|
1717
|
-
const mcpConfigJson = buildMcpConfigJson(mcpForStage);
|
|
1688
|
+
const mcpConfigJson = isMcpEnabledForStage(def.name, config.mcp) ? buildMcpConfigJson(config.mcp) : void 0;
|
|
1718
1689
|
if (mcpConfigJson) {
|
|
1719
1690
|
logger.info(` MCP servers enabled for ${def.name}`);
|
|
1720
1691
|
}
|
|
@@ -1774,7 +1745,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
1774
1745
|
description: plainText.slice(0, 500),
|
|
1775
1746
|
scope: [],
|
|
1776
1747
|
risk_level: "low",
|
|
1777
|
-
hasUI:
|
|
1748
|
+
hasUI: false,
|
|
1778
1749
|
questions: []
|
|
1779
1750
|
}, null, 2);
|
|
1780
1751
|
fs6.writeFileSync(outputPath, fallback);
|
|
@@ -2699,7 +2670,6 @@ function applyPreStageLabel(ctx, def) {
|
|
|
2699
2670
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
2700
2671
|
if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
|
|
2701
2672
|
if (def.name === "review") setLifecycleLabel(ctx.input.issueNumber, "review");
|
|
2702
|
-
if (def.name === "ship") setLifecycleLabel(ctx.input.issueNumber, "shipping");
|
|
2703
2673
|
}
|
|
2704
2674
|
function checkQuestionsAfterStage(ctx, def, state) {
|
|
2705
2675
|
if (def.name !== "taskify" && def.name !== "plan") return null;
|
|
@@ -4537,7 +4507,12 @@ function detectMcpConfig(cwd, pm, pkg) {
|
|
|
4537
4507
|
const defaultPort = isNext ? 3e3 : isVite ? 5173 : 3e3;
|
|
4538
4508
|
const mcp = {
|
|
4539
4509
|
enabled: true,
|
|
4540
|
-
servers: {
|
|
4510
|
+
servers: {
|
|
4511
|
+
playwright: {
|
|
4512
|
+
command: "npx",
|
|
4513
|
+
args: ["@playwright/mcp@latest"]
|
|
4514
|
+
}
|
|
4515
|
+
},
|
|
4541
4516
|
stages: ["build", "review"]
|
|
4542
4517
|
};
|
|
4543
4518
|
if (hasDevScript) {
|
|
@@ -5034,23 +5009,6 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
5034
5009
|
}
|
|
5035
5010
|
}
|
|
5036
5011
|
console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
|
|
5037
|
-
console.log("\n\u2500\u2500 QA Guide \u2500\u2500");
|
|
5038
|
-
const qaGuidePath = path21.join(cwd, ".kody", "qa-guide.md");
|
|
5039
|
-
if (!fs22.existsSync(qaGuidePath) || opts.force) {
|
|
5040
|
-
const discovery = discoverQaContext(cwd);
|
|
5041
|
-
if (discovery.routes.length > 0) {
|
|
5042
|
-
const qaGuide = generateQaGuide(discovery);
|
|
5043
|
-
fs22.writeFileSync(qaGuidePath, qaGuide);
|
|
5044
|
-
console.log(` \u2713 .kody/qa-guide.md (${discovery.routes.length} routes, ${discovery.roles.length} roles)`);
|
|
5045
|
-
if (discovery.loginPage) console.log(` \u2713 Login page detected: ${discovery.loginPage}`);
|
|
5046
|
-
if (discovery.adminPath) console.log(` \u2713 Admin panel detected: ${discovery.adminPath}`);
|
|
5047
|
-
console.log(" \u2139 Add QA_ADMIN_EMAIL, QA_ADMIN_PASSWORD, QA_USER_EMAIL, QA_USER_PASSWORD as GitHub secrets");
|
|
5048
|
-
} else {
|
|
5049
|
-
console.log(" \u25CB No routes detected \u2014 skipping QA guide");
|
|
5050
|
-
}
|
|
5051
|
-
} else {
|
|
5052
|
-
console.log(" \u25CB .kody/qa-guide.md already exists (use --force to regenerate)");
|
|
5053
|
-
}
|
|
5054
5012
|
console.log("\n\u2500\u2500 Labels \u2500\u2500");
|
|
5055
5013
|
try {
|
|
5056
5014
|
let repoSlug = "";
|
|
@@ -5069,7 +5027,6 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
5069
5027
|
{ name: "kody:planning", color: "c5def5", description: "Kody is analyzing and planning" },
|
|
5070
5028
|
{ name: "kody:building", color: "0e8a16", description: "Kody is building code" },
|
|
5071
5029
|
{ name: "kody:review", color: "fbca04", description: "Kody is reviewing code" },
|
|
5072
|
-
{ name: "kody:shipping", color: "1d76db", description: "Kody is creating the pull request" },
|
|
5073
5030
|
{ name: "kody:done", color: "0e8a16", description: "Kody completed successfully" },
|
|
5074
5031
|
{ name: "kody:failed", color: "d93f0b", description: "Kody pipeline failed" },
|
|
5075
5032
|
{ name: "kody:waiting", color: "fef2c0", description: "Kody is waiting for answers" },
|
|
@@ -5128,7 +5085,6 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
5128
5085
|
const filesToCommit = [
|
|
5129
5086
|
".kody/memory/architecture.md",
|
|
5130
5087
|
".kody/memory/conventions.md",
|
|
5131
|
-
".kody/qa-guide.md",
|
|
5132
5088
|
...installedSkillPaths
|
|
5133
5089
|
].filter((f) => fs22.existsSync(path21.join(cwd, f)));
|
|
5134
5090
|
if (fs22.existsSync(path21.join(cwd, "skills-lock.json"))) {
|
|
@@ -5242,178 +5198,6 @@ Create it manually.`, cwd);
|
|
|
5242
5198
|
console.log(" \u2713 Project bootstrap complete!");
|
|
5243
5199
|
console.log(" Kody now has project-specific memory and customized step files.\n");
|
|
5244
5200
|
}
|
|
5245
|
-
function discoverQaContext(cwd) {
|
|
5246
|
-
const result = {
|
|
5247
|
-
routes: [],
|
|
5248
|
-
authFiles: [],
|
|
5249
|
-
loginPage: null,
|
|
5250
|
-
adminPath: null,
|
|
5251
|
-
roles: [],
|
|
5252
|
-
devCommand: "",
|
|
5253
|
-
devPort: 3e3
|
|
5254
|
-
};
|
|
5255
|
-
try {
|
|
5256
|
-
const pkg = JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
|
|
5257
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5258
|
-
const pm = fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs22.existsSync(path21.join(cwd, "yarn.lock")) ? "yarn" : "npm";
|
|
5259
|
-
if (pkg.scripts?.dev) result.devCommand = `${pm} dev`;
|
|
5260
|
-
if (allDeps.next || allDeps.nuxt) result.devPort = 3e3;
|
|
5261
|
-
else if (allDeps.vite) result.devPort = 5173;
|
|
5262
|
-
} catch {
|
|
5263
|
-
}
|
|
5264
|
-
const appDirs = ["src/app", "app"];
|
|
5265
|
-
for (const appDir of appDirs) {
|
|
5266
|
-
const fullAppDir = path21.join(cwd, appDir);
|
|
5267
|
-
if (!fs22.existsSync(fullAppDir)) continue;
|
|
5268
|
-
scanRoutes(fullAppDir, appDir, "", result);
|
|
5269
|
-
break;
|
|
5270
|
-
}
|
|
5271
|
-
const authPatterns = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
|
|
5272
|
-
for (const p of authPatterns) {
|
|
5273
|
-
if (fs22.existsSync(path21.join(cwd, p))) result.authFiles.push(p);
|
|
5274
|
-
}
|
|
5275
|
-
const authConfigGlobs = [
|
|
5276
|
-
"src/app/api/auth",
|
|
5277
|
-
"src/auth",
|
|
5278
|
-
"src/lib/auth",
|
|
5279
|
-
"auth.config.ts",
|
|
5280
|
-
"auth.ts",
|
|
5281
|
-
"src/app/api/oauth"
|
|
5282
|
-
];
|
|
5283
|
-
for (const g of authConfigGlobs) {
|
|
5284
|
-
if (fs22.existsSync(path21.join(cwd, g))) result.authFiles.push(g);
|
|
5285
|
-
}
|
|
5286
|
-
try {
|
|
5287
|
-
const rolePaths = [
|
|
5288
|
-
"src/types",
|
|
5289
|
-
"src/lib",
|
|
5290
|
-
"src/utils",
|
|
5291
|
-
"src/constants",
|
|
5292
|
-
"src/access",
|
|
5293
|
-
"src/collections"
|
|
5294
|
-
];
|
|
5295
|
-
for (const rp of rolePaths) {
|
|
5296
|
-
const dir = path21.join(cwd, rp);
|
|
5297
|
-
if (!fs22.existsSync(dir)) continue;
|
|
5298
|
-
const files = fs22.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
5299
|
-
for (const f of files) {
|
|
5300
|
-
try {
|
|
5301
|
-
const content = fs22.readFileSync(path21.join(dir, f), "utf-8").slice(0, 5e3);
|
|
5302
|
-
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
5303
|
-
if (roleMatches) {
|
|
5304
|
-
for (const m of roleMatches) {
|
|
5305
|
-
const val = m.match(/['"](\w+)['"]/);
|
|
5306
|
-
if (val && !result.roles.includes(val[1])) result.roles.push(val[1]);
|
|
5307
|
-
}
|
|
5308
|
-
}
|
|
5309
|
-
const enumMatch = content.match(/(?:enum|type)\s+\w*[Rr]ole\w*\s*[={]([^}]+)/s);
|
|
5310
|
-
if (enumMatch) {
|
|
5311
|
-
const vals = enumMatch[1].match(/['"](\w+)['"]/g);
|
|
5312
|
-
if (vals) {
|
|
5313
|
-
for (const v of vals) {
|
|
5314
|
-
const clean = v.replace(/['"]/g, "");
|
|
5315
|
-
if (!result.roles.includes(clean)) result.roles.push(clean);
|
|
5316
|
-
}
|
|
5317
|
-
}
|
|
5318
|
-
}
|
|
5319
|
-
} catch {
|
|
5320
|
-
}
|
|
5321
|
-
}
|
|
5322
|
-
}
|
|
5323
|
-
} catch {
|
|
5324
|
-
}
|
|
5325
|
-
return result;
|
|
5326
|
-
}
|
|
5327
|
-
function scanRoutes(dir, baseDir, prefix, result) {
|
|
5328
|
-
let entries;
|
|
5329
|
-
try {
|
|
5330
|
-
entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
5331
|
-
} catch {
|
|
5332
|
-
return;
|
|
5333
|
-
}
|
|
5334
|
-
const hasPage = entries.some((e) => e.isFile() && /^page\.(tsx?|jsx?)$/.test(e.name));
|
|
5335
|
-
if (hasPage) {
|
|
5336
|
-
const routePath = prefix || "/";
|
|
5337
|
-
const group = prefix.startsWith("/admin") ? "admin" : prefix.includes("/login") ? "auth" : prefix.includes("/signup") ? "auth" : prefix.includes("/api") ? "api" : "frontend";
|
|
5338
|
-
result.routes.push({ path: routePath, group });
|
|
5339
|
-
if (prefix.includes("/login")) result.loginPage = routePath;
|
|
5340
|
-
if (prefix.startsWith("/admin") && !result.adminPath) result.adminPath = prefix;
|
|
5341
|
-
}
|
|
5342
|
-
for (const entry of entries) {
|
|
5343
|
-
if (!entry.isDirectory()) continue;
|
|
5344
|
-
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
5345
|
-
let segment = entry.name;
|
|
5346
|
-
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
5347
|
-
scanRoutes(path21.join(dir, entry.name), baseDir, prefix, result);
|
|
5348
|
-
continue;
|
|
5349
|
-
}
|
|
5350
|
-
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
5351
|
-
segment = `:${segment.slice(1, -1)}`;
|
|
5352
|
-
}
|
|
5353
|
-
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
5354
|
-
segment = `:${segment.slice(2, -2)}?`;
|
|
5355
|
-
}
|
|
5356
|
-
scanRoutes(path21.join(dir, entry.name), baseDir, `${prefix}/${segment}`, result);
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
function generateQaGuide(discovery) {
|
|
5360
|
-
const lines = ["# QA Guide", "", "## Authentication", ""];
|
|
5361
|
-
if (discovery.loginPage) {
|
|
5362
|
-
lines.push(`- Login page: \`${discovery.loginPage}\``);
|
|
5363
|
-
}
|
|
5364
|
-
lines.push(
|
|
5365
|
-
"",
|
|
5366
|
-
"### Test Accounts",
|
|
5367
|
-
"<!-- Fill in your test/preview environment credentials below -->",
|
|
5368
|
-
"| Role | Email | Password |",
|
|
5369
|
-
"|------|-------|----------|",
|
|
5370
|
-
"| Admin | admin@example.com | CHANGE_ME |",
|
|
5371
|
-
"| User | user@example.com | CHANGE_ME |",
|
|
5372
|
-
"",
|
|
5373
|
-
"### Login Steps",
|
|
5374
|
-
`1. Navigate to \`${discovery.loginPage ?? "/login"}\``,
|
|
5375
|
-
"2. Enter credentials from the test accounts table above",
|
|
5376
|
-
"3. Submit the login form",
|
|
5377
|
-
"4. Verify redirect to dashboard or home page"
|
|
5378
|
-
);
|
|
5379
|
-
if (discovery.authFiles.length > 0) {
|
|
5380
|
-
lines.push("", "### Auth Files");
|
|
5381
|
-
for (const f of discovery.authFiles) {
|
|
5382
|
-
lines.push(`- \`${f}\``);
|
|
5383
|
-
}
|
|
5384
|
-
}
|
|
5385
|
-
if (discovery.roles.length > 0) {
|
|
5386
|
-
lines.push("", "## Roles", "");
|
|
5387
|
-
for (const role of discovery.roles) {
|
|
5388
|
-
lines.push(`- \`${role}\``);
|
|
5389
|
-
}
|
|
5390
|
-
}
|
|
5391
|
-
lines.push("", "## Key Pages", "");
|
|
5392
|
-
const groups = {};
|
|
5393
|
-
for (const route of discovery.routes) {
|
|
5394
|
-
if (!groups[route.group]) groups[route.group] = [];
|
|
5395
|
-
groups[route.group].push(route.path);
|
|
5396
|
-
}
|
|
5397
|
-
for (const [group, routes] of Object.entries(groups)) {
|
|
5398
|
-
lines.push(`### ${group.charAt(0).toUpperCase() + group.slice(1)}`);
|
|
5399
|
-
const sorted = routes.sort();
|
|
5400
|
-
for (const r of sorted.slice(0, 20)) {
|
|
5401
|
-
lines.push(`- \`${r}\``);
|
|
5402
|
-
}
|
|
5403
|
-
if (sorted.length > 20) {
|
|
5404
|
-
lines.push(`- ... and ${sorted.length - 20} more`);
|
|
5405
|
-
}
|
|
5406
|
-
lines.push("");
|
|
5407
|
-
}
|
|
5408
|
-
lines.push(
|
|
5409
|
-
"## Dev Server",
|
|
5410
|
-
"",
|
|
5411
|
-
`- Command: \`${discovery.devCommand || "pnpm dev"}\``,
|
|
5412
|
-
`- URL: \`http://localhost:${discovery.devPort}\``,
|
|
5413
|
-
""
|
|
5414
|
-
);
|
|
5415
|
-
return lines.join("\n");
|
|
5416
|
-
}
|
|
5417
5201
|
function detectArchitectureBasic(cwd) {
|
|
5418
5202
|
const detected = [];
|
|
5419
5203
|
const pkgPath = path21.join(cwd, "package.json");
|
package/package.json
CHANGED
package/prompts/review-fix.md
CHANGED
|
@@ -5,23 +5,19 @@ mode: primary
|
|
|
5
5
|
tools: [read, write, edit, bash, glob, grep]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
You are a review-fix agent
|
|
8
|
+
You are a review-fix agent. The code review found issues that need fixing.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
RULES (Superpowers Executing Plans discipline):
|
|
10
|
+
RULES:
|
|
13
11
|
1. Fix ONLY Critical and Major issues (ignore Minor findings)
|
|
14
12
|
2. Use Edit for surgical changes — do NOT rewrite entire files
|
|
15
13
|
3. Run tests after EACH fix to verify nothing breaks
|
|
16
|
-
4. If a fix introduces new issues, revert and try a different approach
|
|
17
|
-
5.
|
|
18
|
-
6. Do NOT commit or push — the orchestrator handles git
|
|
14
|
+
4. If a fix introduces new issues, revert and try a different approach
|
|
15
|
+
5. Do NOT commit or push — the orchestrator handles git
|
|
19
16
|
|
|
20
|
-
For each Critical/Major finding:
|
|
17
|
+
Read the review findings carefully. For each Critical/Major finding:
|
|
21
18
|
1. Read the affected file to understand full context
|
|
22
|
-
2.
|
|
23
|
-
3.
|
|
24
|
-
4.
|
|
25
|
-
5. Move to the next finding
|
|
19
|
+
2. Make the minimal change to fix the issue
|
|
20
|
+
3. Run tests to verify the fix
|
|
21
|
+
4. Move to the next finding
|
|
26
22
|
|
|
27
23
|
{{TASK_CONTEXT}}
|
package/prompts/taskify.md
CHANGED
|
@@ -7,15 +7,7 @@ tools: [read, glob, grep]
|
|
|
7
7
|
|
|
8
8
|
You are a task classification agent following the Superpowers Brainstorming methodology.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Before classifying, you MUST explore the project context:
|
|
13
|
-
1. **Examine the codebase** — Use Read, Glob, and Grep to understand project structure, existing patterns, and affected files.
|
|
14
|
-
2. **Find existing solutions** — Search for how similar problems are already solved in this codebase. If a pattern exists, the task should reuse it.
|
|
15
|
-
3. **Challenge assumptions** — Does the task description assume an approach? Are there simpler alternatives? Apply YAGNI ruthlessly.
|
|
16
|
-
4. **Identify ambiguity** — Could the requirements be interpreted two ways? Are there missing edge case decisions?
|
|
17
|
-
|
|
18
|
-
## Output
|
|
10
|
+
Before classifying, examine the codebase to understand the project structure, existing patterns, and affected files. Use Read, Glob, and Grep to explore.
|
|
19
11
|
|
|
20
12
|
Output ONLY valid JSON. No markdown fences. No explanation. No extra text before or after the JSON.
|
|
21
13
|
|
|
@@ -27,7 +19,6 @@ Required JSON format:
|
|
|
27
19
|
"scope": ["list", "of", "exact/file/paths", "affected"],
|
|
28
20
|
"risk_level": "low | medium | high",
|
|
29
21
|
"hasUI": true,
|
|
30
|
-
"existing_patterns": ["list of existing patterns found that the implementation should reuse"],
|
|
31
22
|
"questions": []
|
|
32
23
|
}
|
|
33
24
|
|
|
@@ -40,17 +31,9 @@ Risk level heuristics:
|
|
|
40
31
|
- medium: multiple files, possible side effects, API changes, new dependencies, refactoring existing logic
|
|
41
32
|
- high: core business logic, data migrations, security, authentication, payment processing, database schema changes
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
- List patterns found in the codebase that are relevant to this task
|
|
45
|
-
- Include the file path and a brief description of the pattern
|
|
46
|
-
- If no relevant patterns exist, use an empty array []
|
|
47
|
-
- These inform the planner — reuse existing solutions, don't invent new ones
|
|
48
|
-
|
|
49
|
-
Questions rules (Superpowers Brainstorming discipline):
|
|
34
|
+
Questions rules:
|
|
50
35
|
- ONLY ask product/requirements questions — things you CANNOT determine by reading code
|
|
51
36
|
- Ask about: unclear scope, missing acceptance criteria, ambiguous user behavior, missing edge case decisions
|
|
52
|
-
- Challenge assumptions — if the task implies an approach, consider simpler alternatives
|
|
53
|
-
- Check for ambiguity — could requirements be interpreted two ways?
|
|
54
37
|
- Do NOT ask about technical implementation — that is the planner's job
|
|
55
38
|
- Do NOT ask about things you can find by reading the codebase (file structure, frameworks, patterns)
|
|
56
39
|
- If the task is clear and complete, leave questions as an empty array []
|
package/templates/kody.yml
CHANGED
|
@@ -105,18 +105,10 @@ jobs:
|
|
|
105
105
|
env:
|
|
106
106
|
BODY: ${{ github.event.comment.body }}
|
|
107
107
|
run: |
|
|
108
|
-
# Strip carriage returns — GitHub comments may contain \r\n line endings
|
|
109
|
-
BODY=$(printf '%s' "$BODY" | tr -d '\r')
|
|
110
|
-
|
|
111
108
|
# Extract: @kody [mode] [task-id] [--from stage]
|
|
112
109
|
KODY_ARGS=$(echo "$BODY" | grep -oP '(?:@kody|/kody)\s+\K.*' || echo "")
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
110
|
+
|
|
111
|
+
# Extract flags first (before positional parsing)
|
|
120
112
|
FROM_STAGE=$(echo "$KODY_ARGS" | grep -oP '(?<=--from )\S+' || echo "")
|
|
121
113
|
FEEDBACK=$(echo "$KODY_ARGS" | grep -oP '(?<=--feedback ")[^"]*' || echo "")
|
|
122
114
|
COMPLEXITY=""
|
|
@@ -128,12 +120,24 @@ jobs:
|
|
|
128
120
|
DRY_RUN="true"
|
|
129
121
|
fi
|
|
130
122
|
|
|
131
|
-
#
|
|
123
|
+
# Strip flags and their values for clean positional parsing
|
|
124
|
+
POSITIONAL=$(echo "$KODY_ARGS" | sed -E \
|
|
125
|
+
-e 's/--from\s+\S+//g' \
|
|
126
|
+
-e 's/--feedback\s+"[^"]*"//g' \
|
|
127
|
+
-e 's/--complexity\s+\S+//g' \
|
|
128
|
+
-e 's/--dry-run//g' \
|
|
129
|
+
-e 's/--ci-run-id\s+\S+//g' \
|
|
130
|
+
-e 's/\s+/ /g' -e 's/^ //' -e 's/ $//')
|
|
131
|
+
|
|
132
|
+
MODE=$(echo "$POSITIONAL" | awk '{print $1}')
|
|
133
|
+
TASK_ID=$(echo "$POSITIONAL" | awk '{print $2}')
|
|
134
|
+
|
|
135
|
+
# Validate mode — after flag stripping, only positional args remain
|
|
132
136
|
case "$MODE" in
|
|
133
137
|
full|rerun|fix|fix-ci|status|approve|review|resolve|bootstrap) ;;
|
|
134
138
|
*)
|
|
135
|
-
#
|
|
136
|
-
if [ -n "$MODE" ]
|
|
139
|
+
# First positional isn't a known mode — treat as task-id
|
|
140
|
+
if [ -n "$MODE" ]; then
|
|
137
141
|
TASK_ID="$MODE"
|
|
138
142
|
fi
|
|
139
143
|
MODE="full"
|
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
|
-
}
|