@kody-ade/kody-engine-lite 0.1.62 → 0.1.64

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.
Files changed (73) hide show
  1. package/README.md +5 -4
  2. package/dist/bin/cli.js +162 -8
  3. package/kody.config.schema.json +66 -0
  4. package/package.json +8 -9
  5. package/prompts/taskify.md +5 -0
  6. package/dist/agent-runner.d.ts +0 -4
  7. package/dist/agent-runner.js +0 -122
  8. package/dist/ci/parse-inputs.d.ts +0 -6
  9. package/dist/ci/parse-inputs.js +0 -76
  10. package/dist/ci/parse-safety.d.ts +0 -6
  11. package/dist/ci/parse-safety.js +0 -22
  12. package/dist/cli/args.d.ts +0 -13
  13. package/dist/cli/args.js +0 -42
  14. package/dist/cli/litellm.d.ts +0 -2
  15. package/dist/cli/litellm.js +0 -85
  16. package/dist/cli/task-resolution.d.ts +0 -2
  17. package/dist/cli/task-resolution.js +0 -41
  18. package/dist/config.d.ts +0 -49
  19. package/dist/config.js +0 -72
  20. package/dist/context.d.ts +0 -4
  21. package/dist/context.js +0 -83
  22. package/dist/definitions.d.ts +0 -3
  23. package/dist/definitions.js +0 -59
  24. package/dist/entry.d.ts +0 -1
  25. package/dist/entry.js +0 -236
  26. package/dist/git-utils.d.ts +0 -13
  27. package/dist/git-utils.js +0 -174
  28. package/dist/github-api.d.ts +0 -14
  29. package/dist/github-api.js +0 -114
  30. package/dist/kody-utils.d.ts +0 -1
  31. package/dist/kody-utils.js +0 -9
  32. package/dist/learning/auto-learn.d.ts +0 -2
  33. package/dist/learning/auto-learn.js +0 -169
  34. package/dist/logger.d.ts +0 -14
  35. package/dist/logger.js +0 -51
  36. package/dist/memory.d.ts +0 -1
  37. package/dist/memory.js +0 -20
  38. package/dist/observer.d.ts +0 -9
  39. package/dist/observer.js +0 -80
  40. package/dist/pipeline/complexity.d.ts +0 -3
  41. package/dist/pipeline/complexity.js +0 -12
  42. package/dist/pipeline/executor-registry.d.ts +0 -3
  43. package/dist/pipeline/executor-registry.js +0 -20
  44. package/dist/pipeline/hooks.d.ts +0 -17
  45. package/dist/pipeline/hooks.js +0 -110
  46. package/dist/pipeline/questions.d.ts +0 -2
  47. package/dist/pipeline/questions.js +0 -44
  48. package/dist/pipeline/runner-selection.d.ts +0 -2
  49. package/dist/pipeline/runner-selection.js +0 -13
  50. package/dist/pipeline/state.d.ts +0 -4
  51. package/dist/pipeline/state.js +0 -37
  52. package/dist/pipeline.d.ts +0 -3
  53. package/dist/pipeline.js +0 -213
  54. package/dist/preflight.d.ts +0 -1
  55. package/dist/preflight.js +0 -69
  56. package/dist/retrospective.d.ts +0 -26
  57. package/dist/retrospective.js +0 -211
  58. package/dist/stages/agent.d.ts +0 -2
  59. package/dist/stages/agent.js +0 -94
  60. package/dist/stages/gate.d.ts +0 -2
  61. package/dist/stages/gate.js +0 -32
  62. package/dist/stages/review.d.ts +0 -2
  63. package/dist/stages/review.js +0 -32
  64. package/dist/stages/ship.d.ts +0 -3
  65. package/dist/stages/ship.js +0 -154
  66. package/dist/stages/verify.d.ts +0 -2
  67. package/dist/stages/verify.js +0 -94
  68. package/dist/types.d.ts +0 -61
  69. package/dist/types.js +0 -1
  70. package/dist/validators.d.ts +0 -8
  71. package/dist/validators.js +0 -42
  72. package/dist/verify-runner.d.ts +0 -11
  73. package/dist/verify-runner.js +0 -110
package/README.md CHANGED
@@ -40,7 +40,7 @@ Most AI coding tools are **autocomplete** (Copilot) or **chat-based** (Cursor, C
40
40
  └──────────────────────────┬──────────────────────────────────┘
41
41
 
42
42
  ┌────────────▼────────────┐
43
- │ LOW? skip to
43
+ │ LOW? skip to
44
44
  │ MEDIUM? continue │
45
45
  │ HIGH? continue │
46
46
  └────────────┬────────────┘
@@ -124,10 +124,10 @@ This analyzes your project and generates:
124
124
  - **Config** (`kody.config.json` — auto-detected quality commands, git, GitHub settings)
125
125
  - **Project memory** (`.kody/memory/` — architecture and conventions)
126
126
  - **Customized step files** (`.kody/steps/` — see below)
127
- - **GitHub labels** for lifecycle tracking
128
-
129
127
  Then commits and pushes everything.
130
128
 
129
+ > **Note:** GitHub labels for lifecycle tracking are created automatically during `@kody bootstrap`.
130
+
131
131
  ### 4. Use
132
132
 
133
133
  Comment on any GitHub issue:
@@ -246,10 +246,11 @@ name: build
246
246
  | Doc | What's in it |
247
247
  |-----|-------------|
248
248
  | [Pipeline](docs/PIPELINE.md) | Stage details, shared sessions, complexity skipping, artifacts |
249
+ | [Bootstrap](docs/BOOTSTRAP.md) | Project memory, step files, labels — what bootstrap generates and when to run it |
249
250
  | [Features](docs/FEATURES.md) | Risk gate, diagnosis, sessions, retrospective, auto-learn, labels |
250
251
  | [LiteLLM](docs/LITELLM.md) | Non-Anthropic model setup, auto-start, tested providers |
251
252
  | [Configuration](docs/CONFIGURATION.md) | Full config reference, env vars, workflow setup |
252
- | [Comparison](docs/COMPARISON.md) | vs Copilot, Devin, Cursor, Cline, SWE-agent, OpenHands |
253
+ | [Comparison](docs/COMPARISON.md) | vs Copilot, Devin, Cursor, Cline, OpenHands, SWE-agent |
253
254
  | [Architecture](docs/ARCHITECTURE.md) | Source tree, state machine diagram, development guide |
254
255
  | [FAQ](docs/FAQ.md) | Common questions about usage, models, security, cost |
255
256
 
package/dist/bin/cli.js CHANGED
@@ -96,6 +96,9 @@ function createClaudeCodeRunner() {
96
96
  "--allowedTools",
97
97
  "Bash,Edit,Read,Write,Glob,Grep"
98
98
  ];
99
+ if (options?.mcpConfigJson) {
100
+ args2.push("--mcp-config", options.mcpConfigJson);
101
+ }
99
102
  if (options?.sessionId) {
100
103
  if (options.resumeSession) {
101
104
  args2.push("--resume", options.sessionId);
@@ -274,7 +277,8 @@ function getProjectConfig() {
274
277
  git: { ...DEFAULT_CONFIG.git, ...raw.git },
275
278
  github: { ...DEFAULT_CONFIG.github, ...raw.github },
276
279
  agent: { ...DEFAULT_CONFIG.agent, ...raw.agent },
277
- contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers
280
+ contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers,
281
+ mcp: raw.mcp ? { enabled: false, servers: {}, stages: ["build", "verify", "review", "review-fix"], ...raw.mcp } : void 0
278
282
  };
279
283
  } catch {
280
284
  logger.warn("kody.config.json is invalid JSON \u2014 using defaults");
@@ -1104,6 +1108,35 @@ var init_context_tiers = __esm({
1104
1108
  }
1105
1109
  });
1106
1110
 
1111
+ // src/mcp-config.ts
1112
+ function buildMcpConfigJson(mcpConfig) {
1113
+ if (!mcpConfig?.enabled) return void 0;
1114
+ if (Object.keys(mcpConfig.servers).length === 0) return void 0;
1115
+ const config = { mcpServers: {} };
1116
+ const mcpServers = config.mcpServers;
1117
+ for (const [name, server] of Object.entries(mcpConfig.servers)) {
1118
+ mcpServers[name] = {
1119
+ command: server.command,
1120
+ args: server.args ?? [],
1121
+ ...server.env ? { env: server.env } : {}
1122
+ };
1123
+ }
1124
+ return JSON.stringify(config);
1125
+ }
1126
+ function isMcpEnabledForStage(stageName, mcpConfig) {
1127
+ if (!mcpConfig?.enabled) return false;
1128
+ if (Object.keys(mcpConfig.servers).length === 0) return false;
1129
+ const allowedStages = mcpConfig.stages ?? DEFAULT_MCP_STAGES;
1130
+ return allowedStages.includes(stageName);
1131
+ }
1132
+ var DEFAULT_MCP_STAGES;
1133
+ var init_mcp_config = __esm({
1134
+ "src/mcp-config.ts"() {
1135
+ "use strict";
1136
+ DEFAULT_MCP_STAGES = ["build", "verify", "review", "review-fix"];
1137
+ }
1138
+ });
1139
+
1107
1140
  // src/context.ts
1108
1141
  import * as fs5 from "fs";
1109
1142
  import * as path5 from "path";
@@ -1194,18 +1227,132 @@ ${feedback}
1194
1227
  }
1195
1228
  return prompt.replace("{{TASK_CONTEXT}}", context);
1196
1229
  }
1230
+ function taskHasUI(taskDir) {
1231
+ const taskJsonPath = path5.join(taskDir, "task.json");
1232
+ if (!fs5.existsSync(taskJsonPath)) return true;
1233
+ try {
1234
+ const taskDef = JSON.parse(fs5.readFileSync(taskJsonPath, "utf-8"));
1235
+ return taskDef.hasUI !== false;
1236
+ } catch {
1237
+ return true;
1238
+ }
1239
+ }
1240
+ function getDevServerInfo(taskDir) {
1241
+ const config = getProjectConfig();
1242
+ const ds = config.mcp?.devServer;
1243
+ if (!ds) return void 0;
1244
+ return {
1245
+ command: ds.command,
1246
+ url: ds.url,
1247
+ readyPattern: ds.readyPattern ?? "Ready in|compiled|started server|Local:",
1248
+ readyTimeout: ds.readyTimeout ?? 30
1249
+ };
1250
+ }
1251
+ function getBrowserToolGuidance(stageName, taskDir) {
1252
+ const devServer = getDevServerInfo(taskDir);
1253
+ const devServerBlock = devServer ? `
1254
+ ### Dev Server Setup (REQUIRED before browsing)
1255
+ You MUST start the dev server before using any browser navigation tools:
1256
+ \`\`\`bash
1257
+ # Start the dev server in the background
1258
+ ${devServer.command} &
1259
+ # Wait for it to be ready (look for "${devServer.readyPattern}" in output)
1260
+ sleep 5
1261
+ \`\`\`
1262
+ The dev server URL is: ${devServer.url}
1263
+ After you are done browsing, kill the dev server: \`kill %1 2>/dev/null || true\`` : `
1264
+ ### Dev Server Setup (REQUIRED before browsing)
1265
+ You MUST start the project's dev server before using any browser navigation tools.
1266
+ Check package.json for the dev command (usually \`pnpm dev\` or \`npm run dev\`).
1267
+ \`\`\`bash
1268
+ # Start the dev server in the background
1269
+ pnpm dev &
1270
+ # Wait for it to be ready
1271
+ sleep 5
1272
+ \`\`\`
1273
+ After you are done browsing, kill the dev server: \`kill %1 2>/dev/null || true\``;
1274
+ if (stageName === "build" || stageName === "review-fix") {
1275
+ return `## Browser Visual Verification (MANDATORY for UI tasks)
1276
+
1277
+ This task involves UI changes. You MUST visually verify your implementation using the browser tools.
1278
+ ${devServerBlock}
1279
+
1280
+ ### Available Browser Tools
1281
+ - \`mcp__playwright__browser_navigate\` \u2014 go to a URL
1282
+ - \`mcp__playwright__browser_snapshot\` \u2014 capture accessibility tree (shows all elements, text, roles)
1283
+ - \`mcp__playwright__browser_take_screenshot\` \u2014 take a visual screenshot
1284
+ - \`mcp__playwright__browser_click\` \u2014 click an element (by text, role, or ref from snapshot)
1285
+ - \`mcp__playwright__browser_type\` \u2014 type text into an input field
1286
+ - \`mcp__playwright__browser_fill_form\` \u2014 fill multiple form fields at once
1287
+ - \`mcp__playwright__browser_hover\` \u2014 hover over an element (test hover states)
1288
+ - \`mcp__playwright__browser_select_option\` \u2014 select a dropdown option
1289
+ - \`mcp__playwright__browser_press_key\` \u2014 press keyboard keys (Enter, Escape, Tab, etc.)
1290
+ - \`mcp__playwright__browser_resize\` \u2014 resize viewport (test responsive layouts)
1291
+ - \`mcp__playwright__browser_wait_for\` \u2014 wait for text to appear/disappear
1292
+ - \`mcp__playwright__browser_evaluate\` \u2014 run JavaScript on the page
1293
+
1294
+ ### Verification Steps (DO ALL OF THESE)
1295
+ 1. Start the dev server (see above)
1296
+ 2. Use \`browser_navigate\` to go to the affected page(s)
1297
+ 3. Use \`browser_snapshot\` to capture the page and verify elements are present
1298
+ 4. **Test interactions**: if the task involves buttons, forms, search, toggles, or any interactive elements \u2014 click them, type into them, and verify the result with another snapshot
1299
+ 5. If the task mentions responsive behavior, use \`browser_resize\` to test at different widths (e.g., 1200px, 768px, 480px) and take snapshots at each
1300
+ 6. Kill the dev server when done
1301
+
1302
+ Do NOT skip the browser verification. The visual check AND interaction testing are required parts of implementing UI changes.`;
1303
+ }
1304
+ if (stageName === "review") {
1305
+ return `## Browser Visual Verification (MANDATORY for UI review)
1306
+
1307
+ This task involves UI changes. You MUST visually verify the implementation using the browser tools before giving your verdict.
1308
+ ${devServerBlock}
1309
+
1310
+ ### Available Browser Tools
1311
+ - \`mcp__playwright__browser_navigate\` \u2014 go to a URL
1312
+ - \`mcp__playwright__browser_snapshot\` \u2014 capture accessibility tree (shows all elements, text, roles)
1313
+ - \`mcp__playwright__browser_take_screenshot\` \u2014 take a visual screenshot
1314
+ - \`mcp__playwright__browser_click\` \u2014 click an element
1315
+ - \`mcp__playwright__browser_type\` \u2014 type text into an input
1316
+ - \`mcp__playwright__browser_hover\` \u2014 hover over an element
1317
+ - \`mcp__playwright__browser_resize\` \u2014 resize viewport
1318
+ - \`mcp__playwright__browser_wait_for\` \u2014 wait for text to appear/disappear
1319
+
1320
+ ### Review Verification Steps (DO ALL OF THESE)
1321
+ 1. Start the dev server (see above)
1322
+ 2. Use \`browser_navigate\` to go to the affected page(s)
1323
+ 3. Use \`browser_snapshot\` to verify elements, layout, and text content
1324
+ 4. **Test interactions**: click buttons, fill forms, test search \u2014 verify the UI responds correctly
1325
+ 5. If the task mentions responsive behavior, use \`browser_resize\` to test at different widths
1326
+ 6. Include your browser verification findings in the review (what you saw, what you interacted with, what worked/failed)
1327
+ 7. Kill the dev server when done
1328
+
1329
+ Do NOT skip the browser verification. A review of UI changes without visual AND interaction verification is incomplete.`;
1330
+ }
1331
+ return `## Browser Tools Available
1332
+
1333
+ You have access to Playwright MCP browser tools for visual verification.
1334
+ ${devServerBlock}
1335
+
1336
+ Use browser tools to navigate to pages and take snapshots to verify UI output.`;
1337
+ }
1197
1338
  function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
1198
1339
  const config = getProjectConfig();
1340
+ let assembled;
1199
1341
  if (config.contextTiers?.enabled) {
1200
- return buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback);
1201
- }
1202
- const memory = readProjectMemory(projectDir);
1203
- const promptTemplate = readPromptFile(stageName, projectDir);
1204
- const prompt = injectTaskContext(promptTemplate, taskId, taskDir, feedback);
1205
- return memory ? `${memory}
1342
+ assembled = buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback);
1343
+ } else {
1344
+ const memory = readProjectMemory(projectDir);
1345
+ const promptTemplate = readPromptFile(stageName, projectDir);
1346
+ const prompt = injectTaskContext(promptTemplate, taskId, taskDir, feedback);
1347
+ assembled = memory ? `${memory}
1206
1348
  ---
1207
1349
 
1208
1350
  ${prompt}` : prompt;
1351
+ }
1352
+ if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
1353
+ assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
1354
+ }
1355
+ return assembled;
1209
1356
  }
1210
1357
  function buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback) {
1211
1358
  const config = getProjectConfig();
@@ -1241,6 +1388,7 @@ var init_context = __esm({
1241
1388
  init_memory();
1242
1389
  init_config();
1243
1390
  init_context_tiers();
1391
+ init_mcp_config();
1244
1392
  DEFAULT_MODEL_MAP = {
1245
1393
  cheap: "haiku",
1246
1394
  mid: "sonnet",
@@ -1365,11 +1513,16 @@ async function executeAgentStage(ctx, def) {
1365
1513
  if (sessionInfo) {
1366
1514
  logger.info(` session: ${SESSION_GROUP[def.name]} (${sessionInfo.resumeSession ? "resume" : "new"})`);
1367
1515
  }
1516
+ const mcpConfigJson = isMcpEnabledForStage(def.name, config.mcp) ? buildMcpConfigJson(config.mcp) : void 0;
1517
+ if (mcpConfigJson) {
1518
+ logger.info(` MCP servers enabled for ${def.name}`);
1519
+ }
1368
1520
  const runner = getRunnerForStage(ctx, def.name);
1369
1521
  const result = await runner.run(def.name, prompt, model, def.timeout, ctx.taskDir, {
1370
1522
  cwd: ctx.projectDir,
1371
1523
  env: extraEnv,
1372
- ...sessionInfo
1524
+ ...sessionInfo,
1525
+ mcpConfigJson
1373
1526
  });
1374
1527
  if (result.outcome !== "completed") {
1375
1528
  return { outcome: result.outcome, error: result.error, retries: 0 };
@@ -1447,6 +1600,7 @@ var init_agent = __esm({
1447
1600
  init_context();
1448
1601
  init_validators();
1449
1602
  init_config();
1603
+ init_mcp_config();
1450
1604
  init_runner_selection();
1451
1605
  init_logger();
1452
1606
  SESSION_GROUP = {
@@ -128,6 +128,72 @@
128
128
  }
129
129
  },
130
130
  "additionalProperties": false
131
+ },
132
+ "mcp": {
133
+ "type": "object",
134
+ "description": "MCP (Model Context Protocol) server configuration. Enables external tools like browser automation.",
135
+ "properties": {
136
+ "enabled": {
137
+ "type": "boolean",
138
+ "description": "Enable MCP server integration",
139
+ "default": false
140
+ },
141
+ "servers": {
142
+ "type": "object",
143
+ "description": "Named MCP server definitions. Each key is a server name.",
144
+ "additionalProperties": {
145
+ "type": "object",
146
+ "properties": {
147
+ "command": {
148
+ "type": "string",
149
+ "description": "Command to start the MCP server (e.g., 'npx')"
150
+ },
151
+ "args": {
152
+ "type": "array",
153
+ "items": { "type": "string" },
154
+ "description": "Command arguments"
155
+ },
156
+ "env": {
157
+ "type": "object",
158
+ "additionalProperties": { "type": "string" },
159
+ "description": "Environment variables for the server process"
160
+ }
161
+ },
162
+ "required": ["command"]
163
+ }
164
+ },
165
+ "stages": {
166
+ "type": "array",
167
+ "items": { "type": "string" },
168
+ "description": "Stages that can use MCP tools. Defaults to [\"build\", \"verify\", \"review\", \"review-fix\"]",
169
+ "default": ["build", "verify", "review", "review-fix"]
170
+ },
171
+ "devServer": {
172
+ "type": "object",
173
+ "description": "Dev server configuration for browser tool verification",
174
+ "properties": {
175
+ "command": {
176
+ "type": "string",
177
+ "description": "Command to start the dev server (e.g., 'pnpm dev')"
178
+ },
179
+ "url": {
180
+ "type": "string",
181
+ "description": "URL where the dev server will be accessible (e.g., 'http://localhost:3000')"
182
+ },
183
+ "readyPattern": {
184
+ "type": "string",
185
+ "description": "Regex pattern to match in stdout when server is ready. Default: 'Ready in|compiled|started server|Local:'"
186
+ },
187
+ "readyTimeout": {
188
+ "type": "number",
189
+ "description": "Seconds to wait for the server to be ready. Default: 30"
190
+ }
191
+ },
192
+ "required": ["command", "url"]
193
+ }
194
+ },
195
+ "required": ["enabled", "servers"],
196
+ "additionalProperties": false
131
197
  }
132
198
  },
133
199
  "additionalProperties": false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,13 +13,6 @@
13
13
  "templates",
14
14
  "kody.config.schema.json"
15
15
  ],
16
- "scripts": {
17
- "kody": "tsx src/entry.ts",
18
- "build": "tsup",
19
- "test": "vitest run",
20
- "typecheck": "tsc --noEmit",
21
- "prepublishOnly": "pnpm build"
22
- },
23
16
  "dependencies": {
24
17
  "dotenv": "^16.4.7"
25
18
  },
@@ -32,5 +25,11 @@
32
25
  },
33
26
  "engines": {
34
27
  "node": ">=22"
28
+ },
29
+ "scripts": {
30
+ "kody": "tsx src/entry.ts",
31
+ "build": "tsup",
32
+ "test": "vitest run",
33
+ "typecheck": "tsc --noEmit"
35
34
  }
36
- }
35
+ }
@@ -18,9 +18,14 @@ Required JSON format:
18
18
  "description": "Clear description of what the task requires",
19
19
  "scope": ["list", "of", "exact/file/paths", "affected"],
20
20
  "risk_level": "low | medium | high",
21
+ "hasUI": true,
21
22
  "questions": []
22
23
  }
23
24
 
25
+ hasUI heuristics:
26
+ - true: task touches frontend files (.tsx, .jsx, .vue, .svelte, .css, .scss, .html), UI components, pages, layouts, or styles
27
+ - false: task is purely backend, CLI, API, database, config, docs, or infrastructure
28
+
24
29
  Risk level heuristics:
25
30
  - low: single file change, no breaking changes, docs, config, isolated scripts, test additions, style changes
26
31
  - medium: multiple files, possible side effects, API changes, new dependencies, refactoring existing logic
@@ -1,4 +0,0 @@
1
- import type { AgentRunner } from "./types.js";
2
- import type { KodyConfig } from "./config.js";
3
- export declare function createClaudeCodeRunner(): AgentRunner;
4
- export declare function createRunners(config: KodyConfig): Record<string, AgentRunner>;
@@ -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
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Parses @kody / /kody comment body into structured inputs.
3
- * Run by the parse job in GitHub Actions.
4
- * Reads from env, writes to $GITHUB_OUTPUT.
5
- */
6
- export {};
@@ -1,76 +0,0 @@
1
- /**
2
- * Parses @kody / /kody comment body into structured inputs.
3
- * Run by the parse job in GitHub Actions.
4
- * Reads from env, writes to $GITHUB_OUTPUT.
5
- */
6
- import * as fs from "fs";
7
- const outputFile = process.env.GITHUB_OUTPUT;
8
- const triggerType = process.env.TRIGGER_TYPE ?? "dispatch";
9
- function output(key, value) {
10
- if (outputFile) {
11
- fs.appendFileSync(outputFile, `${key}=${value}\n`);
12
- }
13
- console.log(`${key}=${value}`);
14
- }
15
- // For workflow_dispatch, pass through inputs
16
- if (triggerType === "dispatch") {
17
- output("task_id", process.env.INPUT_TASK_ID ?? "");
18
- output("mode", process.env.INPUT_MODE ?? "full");
19
- output("from_stage", process.env.INPUT_FROM_STAGE ?? "");
20
- output("issue_number", process.env.INPUT_ISSUE_NUMBER ?? "");
21
- output("feedback", process.env.INPUT_FEEDBACK ?? "");
22
- output("valid", process.env.INPUT_TASK_ID ? "true" : "false");
23
- output("trigger_type", "dispatch");
24
- process.exit(0);
25
- }
26
- // For issue_comment, parse the comment body
27
- const commentBody = process.env.COMMENT_BODY ?? "";
28
- const issueNumber = process.env.ISSUE_NUMBER ?? "";
29
- // Match: @kody [mode] [task-id] [--from stage] [--feedback "text"]
30
- const kodyMatch = commentBody.match(/(?:@kody|\/kody)\s*(.*)/i);
31
- if (!kodyMatch) {
32
- output("valid", "false");
33
- output("trigger_type", "comment");
34
- process.exit(0);
35
- }
36
- const parts = kodyMatch[1].trim().split(/\s+/);
37
- const validModes = ["full", "rerun", "status"];
38
- let mode = "full";
39
- let taskId = "";
40
- let fromStage = "";
41
- let feedback = "";
42
- let i = 0;
43
- // First arg: mode or task-id
44
- if (parts[i] && validModes.includes(parts[i])) {
45
- mode = parts[i];
46
- i++;
47
- }
48
- // Second arg: task-id
49
- if (parts[i] && !parts[i].startsWith("--")) {
50
- taskId = parts[i];
51
- i++;
52
- }
53
- // Named args
54
- while (i < parts.length) {
55
- if (parts[i] === "--from" && parts[i + 1]) {
56
- fromStage = parts[i + 1];
57
- i += 2;
58
- }
59
- else if (parts[i] === "--feedback" && parts[i + 1]) {
60
- // Collect quoted feedback
61
- const rest = parts.slice(i + 1).join(" ");
62
- const quoted = rest.match(/^"([^"]*)"/);
63
- feedback = quoted ? quoted[1] : parts[i + 1];
64
- break;
65
- }
66
- else {
67
- i++;
68
- }
69
- }
70
- output("task_id", taskId);
71
- output("mode", mode);
72
- output("from_stage", fromStage);
73
- output("issue_number", issueNumber);
74
- output("feedback", feedback);
75
- output("valid", taskId ? "true" : "false");
76
- output("trigger_type", "comment");
@@ -1,6 +0,0 @@
1
- /**
2
- * Validates that a comment trigger is safe to execute.
3
- * Run by the parse job in GitHub Actions.
4
- * Reads from env, writes to $GITHUB_OUTPUT.
5
- */
6
- export {};
@@ -1,22 +0,0 @@
1
- /**
2
- * Validates that a comment trigger is safe to execute.
3
- * Run by the parse job in GitHub Actions.
4
- * Reads from env, writes to $GITHUB_OUTPUT.
5
- */
6
- import * as fs from "fs";
7
- const ALLOWED_ASSOCIATIONS = ["COLLABORATOR", "MEMBER", "OWNER"];
8
- const association = process.env.COMMENT_AUTHOR_ASSOCIATION ?? "";
9
- const outputFile = process.env.GITHUB_OUTPUT;
10
- function output(key, value) {
11
- if (outputFile) {
12
- fs.appendFileSync(outputFile, `${key}=${value}\n`);
13
- }
14
- console.log(`${key}=${value}`);
15
- }
16
- if (!ALLOWED_ASSOCIATIONS.includes(association)) {
17
- output("valid", "false");
18
- output("reason", `Author association '${association}' not in allowlist: ${ALLOWED_ASSOCIATIONS.join(", ")}`);
19
- process.exit(0);
20
- }
21
- output("valid", "true");
22
- output("reason", "");
@@ -1,13 +0,0 @@
1
- export interface CliInput {
2
- command: "run" | "rerun" | "fix" | "status";
3
- taskId?: string;
4
- task?: string;
5
- fromStage?: string;
6
- dryRun?: boolean;
7
- cwd?: string;
8
- issueNumber?: number;
9
- feedback?: string;
10
- local?: boolean;
11
- complexity?: "low" | "medium" | "high";
12
- }
13
- export declare function parseArgs(): CliInput;