@kody-ade/kody-engine-lite 0.1.63 → 0.1.65

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/dist/agent-runner.d.ts +4 -0
  2. package/dist/agent-runner.js +122 -0
  3. package/dist/bin/cli.js +162 -8
  4. package/dist/ci/parse-inputs.d.ts +6 -0
  5. package/dist/ci/parse-inputs.js +76 -0
  6. package/dist/ci/parse-safety.d.ts +6 -0
  7. package/dist/ci/parse-safety.js +22 -0
  8. package/dist/cli/args.d.ts +13 -0
  9. package/dist/cli/args.js +42 -0
  10. package/dist/cli/litellm.d.ts +2 -0
  11. package/dist/cli/litellm.js +85 -0
  12. package/dist/cli/task-resolution.d.ts +2 -0
  13. package/dist/cli/task-resolution.js +41 -0
  14. package/dist/config.d.ts +49 -0
  15. package/dist/config.js +72 -0
  16. package/dist/context.d.ts +4 -0
  17. package/dist/context.js +83 -0
  18. package/dist/definitions.d.ts +3 -0
  19. package/dist/definitions.js +59 -0
  20. package/dist/entry.d.ts +1 -0
  21. package/dist/entry.js +236 -0
  22. package/dist/git-utils.d.ts +13 -0
  23. package/dist/git-utils.js +174 -0
  24. package/dist/github-api.d.ts +14 -0
  25. package/dist/github-api.js +114 -0
  26. package/dist/kody-utils.d.ts +1 -0
  27. package/dist/kody-utils.js +9 -0
  28. package/dist/learning/auto-learn.d.ts +2 -0
  29. package/dist/learning/auto-learn.js +169 -0
  30. package/dist/logger.d.ts +14 -0
  31. package/dist/logger.js +51 -0
  32. package/dist/memory.d.ts +1 -0
  33. package/dist/memory.js +20 -0
  34. package/dist/observer.d.ts +9 -0
  35. package/dist/observer.js +80 -0
  36. package/dist/pipeline/complexity.d.ts +3 -0
  37. package/dist/pipeline/complexity.js +12 -0
  38. package/dist/pipeline/executor-registry.d.ts +3 -0
  39. package/dist/pipeline/executor-registry.js +20 -0
  40. package/dist/pipeline/hooks.d.ts +17 -0
  41. package/dist/pipeline/hooks.js +110 -0
  42. package/dist/pipeline/questions.d.ts +2 -0
  43. package/dist/pipeline/questions.js +44 -0
  44. package/dist/pipeline/runner-selection.d.ts +2 -0
  45. package/dist/pipeline/runner-selection.js +13 -0
  46. package/dist/pipeline/state.d.ts +4 -0
  47. package/dist/pipeline/state.js +37 -0
  48. package/dist/pipeline.d.ts +3 -0
  49. package/dist/pipeline.js +213 -0
  50. package/dist/preflight.d.ts +1 -0
  51. package/dist/preflight.js +69 -0
  52. package/dist/retrospective.d.ts +26 -0
  53. package/dist/retrospective.js +211 -0
  54. package/dist/stages/agent.d.ts +2 -0
  55. package/dist/stages/agent.js +94 -0
  56. package/dist/stages/gate.d.ts +2 -0
  57. package/dist/stages/gate.js +32 -0
  58. package/dist/stages/review.d.ts +2 -0
  59. package/dist/stages/review.js +32 -0
  60. package/dist/stages/ship.d.ts +3 -0
  61. package/dist/stages/ship.js +154 -0
  62. package/dist/stages/verify.d.ts +2 -0
  63. package/dist/stages/verify.js +94 -0
  64. package/dist/types.d.ts +61 -0
  65. package/dist/types.js +1 -0
  66. package/dist/validators.d.ts +8 -0
  67. package/dist/validators.js +42 -0
  68. package/dist/verify-runner.d.ts +11 -0
  69. package/dist/verify-runner.js +110 -0
  70. package/kody.config.schema.json +66 -0
  71. package/package.json +8 -9
  72. package/prompts/taskify.md +5 -0
  73. package/templates/kody.yml +6 -1
@@ -0,0 +1,49 @@
1
+ export interface RunnerConfig {
2
+ type: "claude-code";
3
+ }
4
+ export interface KodyConfig {
5
+ quality: {
6
+ typecheck: string;
7
+ lint: string;
8
+ lintFix: string;
9
+ format: string;
10
+ formatFix: string;
11
+ testUnit: string;
12
+ };
13
+ git: {
14
+ defaultBranch: string;
15
+ userEmail?: string;
16
+ userName?: string;
17
+ };
18
+ github: {
19
+ owner: string;
20
+ repo: string;
21
+ };
22
+ paths: {
23
+ taskDir: string;
24
+ };
25
+ agent: {
26
+ runner?: string;
27
+ modelMap: {
28
+ cheap: string;
29
+ mid: string;
30
+ strong: string;
31
+ };
32
+ litellmUrl?: string;
33
+ usePerStageRouting?: boolean;
34
+ defaultRunner?: string;
35
+ runners?: Record<string, RunnerConfig>;
36
+ stageRunners?: Record<string, string>;
37
+ };
38
+ }
39
+ export declare const SIGKILL_GRACE_MS = 5000;
40
+ export declare const MAX_PR_TITLE_LENGTH = 72;
41
+ export declare const STDERR_TAIL_CHARS = 500;
42
+ export declare const API_TIMEOUT_MS = 30000;
43
+ export declare const DEFAULT_MAX_FIX_ATTEMPTS = 2;
44
+ export declare const AGENT_RETRY_DELAY_MS = 2000;
45
+ export declare const VERIFY_COMMAND_TIMEOUT_MS: number;
46
+ export declare const FIX_COMMAND_TIMEOUT_MS: number;
47
+ export declare function setConfigDir(dir: string): void;
48
+ export declare function getProjectConfig(): KodyConfig;
49
+ export declare function resetProjectConfig(): void;
package/dist/config.js ADDED
@@ -0,0 +1,72 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { logger } from "./logger.js";
4
+ const DEFAULT_CONFIG = {
5
+ quality: {
6
+ typecheck: "pnpm -s tsc --noEmit",
7
+ lint: "pnpm -s lint",
8
+ lintFix: "pnpm lint:fix",
9
+ format: "pnpm -s format:check",
10
+ formatFix: "pnpm format:fix",
11
+ testUnit: "pnpm -s test",
12
+ },
13
+ git: {
14
+ defaultBranch: "dev",
15
+ },
16
+ github: {
17
+ owner: "",
18
+ repo: "",
19
+ },
20
+ paths: {
21
+ taskDir: ".tasks",
22
+ },
23
+ agent: {
24
+ runner: "claude-code",
25
+ defaultRunner: "claude",
26
+ modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" },
27
+ },
28
+ };
29
+ // Pipeline constants
30
+ export const SIGKILL_GRACE_MS = 5000;
31
+ export const MAX_PR_TITLE_LENGTH = 72;
32
+ export const STDERR_TAIL_CHARS = 500;
33
+ export const API_TIMEOUT_MS = 30_000;
34
+ export const DEFAULT_MAX_FIX_ATTEMPTS = 2;
35
+ export const AGENT_RETRY_DELAY_MS = 2000;
36
+ export const VERIFY_COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
37
+ export const FIX_COMMAND_TIMEOUT_MS = 2 * 60 * 1000;
38
+ let _config = null;
39
+ let _configDir = null;
40
+ export function setConfigDir(dir) {
41
+ _configDir = dir;
42
+ _config = null;
43
+ }
44
+ export function getProjectConfig() {
45
+ if (_config)
46
+ return _config;
47
+ const configPath = path.join(_configDir ?? process.cwd(), "kody.config.json");
48
+ if (fs.existsSync(configPath)) {
49
+ try {
50
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
51
+ _config = {
52
+ quality: { ...DEFAULT_CONFIG.quality, ...raw.quality },
53
+ git: { ...DEFAULT_CONFIG.git, ...raw.git },
54
+ github: { ...DEFAULT_CONFIG.github, ...raw.github },
55
+ paths: { ...DEFAULT_CONFIG.paths, ...raw.paths },
56
+ agent: { ...DEFAULT_CONFIG.agent, ...raw.agent },
57
+ };
58
+ }
59
+ catch {
60
+ logger.warn("kody.config.json is invalid JSON — using defaults");
61
+ _config = { ...DEFAULT_CONFIG };
62
+ }
63
+ }
64
+ else {
65
+ _config = { ...DEFAULT_CONFIG };
66
+ }
67
+ return _config;
68
+ }
69
+ export function resetProjectConfig() {
70
+ _config = null;
71
+ _configDir = null;
72
+ }
@@ -0,0 +1,4 @@
1
+ export declare function readPromptFile(stageName: string): string;
2
+ export declare function injectTaskContext(prompt: string, taskId: string, taskDir: string, feedback?: string): string;
3
+ export declare function buildFullPrompt(stageName: string, taskId: string, taskDir: string, projectDir: string, feedback?: string): string;
4
+ export declare function resolveModel(modelTier: string, stageName?: string): string;
@@ -0,0 +1,83 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { readProjectMemory } from "./memory.js";
4
+ import { getProjectConfig } from "./config.js";
5
+ const DEFAULT_MODEL_MAP = {
6
+ cheap: "haiku",
7
+ mid: "sonnet",
8
+ strong: "opus",
9
+ };
10
+ const MAX_TASK_CONTEXT_PLAN = 1500;
11
+ const MAX_TASK_CONTEXT_SPEC = 2000;
12
+ export function readPromptFile(stageName) {
13
+ const scriptDir = new URL(".", import.meta.url).pathname;
14
+ // Try multiple resolution paths (dev: src/../prompts, prod: dist/bin/../../prompts)
15
+ const candidates = [
16
+ path.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
17
+ path.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`),
18
+ ];
19
+ for (const candidate of candidates) {
20
+ if (fs.existsSync(candidate)) {
21
+ return fs.readFileSync(candidate, "utf-8");
22
+ }
23
+ }
24
+ throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
25
+ }
26
+ export function injectTaskContext(prompt, taskId, taskDir, feedback) {
27
+ let context = `## Task Context\n`;
28
+ context += `Task ID: ${taskId}\n`;
29
+ context += `Task Directory: ${taskDir}\n`;
30
+ const taskMdPath = path.join(taskDir, "task.md");
31
+ if (fs.existsSync(taskMdPath)) {
32
+ const taskMd = fs.readFileSync(taskMdPath, "utf-8");
33
+ context += `\n## Task Description\n${taskMd}\n`;
34
+ }
35
+ const taskJsonPath = path.join(taskDir, "task.json");
36
+ if (fs.existsSync(taskJsonPath)) {
37
+ try {
38
+ const taskDef = JSON.parse(fs.readFileSync(taskJsonPath, "utf-8"));
39
+ context += `\n## Task Classification\n`;
40
+ context += `Type: ${taskDef.task_type ?? "unknown"}\n`;
41
+ context += `Title: ${taskDef.title ?? "unknown"}\n`;
42
+ context += `Risk: ${taskDef.risk_level ?? "unknown"}\n`;
43
+ }
44
+ catch {
45
+ // Ignore parse errors
46
+ }
47
+ }
48
+ const specPath = path.join(taskDir, "spec.md");
49
+ if (fs.existsSync(specPath)) {
50
+ const spec = fs.readFileSync(specPath, "utf-8");
51
+ const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
52
+ context += `\n## Spec Summary\n${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}\n`;
53
+ }
54
+ const planPath = path.join(taskDir, "plan.md");
55
+ if (fs.existsSync(planPath)) {
56
+ const plan = fs.readFileSync(planPath, "utf-8");
57
+ const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
58
+ context += `\n## Plan Summary\n${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}\n`;
59
+ }
60
+ if (feedback) {
61
+ context += `\n## Human Feedback\n${feedback}\n`;
62
+ }
63
+ return prompt.replace("{{TASK_CONTEXT}}", context);
64
+ }
65
+ export function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
66
+ const memory = readProjectMemory(projectDir);
67
+ const promptTemplate = readPromptFile(stageName);
68
+ const prompt = injectTaskContext(promptTemplate, taskId, taskDir, feedback);
69
+ return memory ? `${memory}\n---\n\n${prompt}` : prompt;
70
+ }
71
+ export function resolveModel(modelTier, stageName) {
72
+ const config = getProjectConfig();
73
+ // Per-stage routing: use stage name as LiteLLM alias
74
+ if (config.agent.usePerStageRouting && stageName) {
75
+ return stageName;
76
+ }
77
+ // Config model map (may be LiteLLM aliases or direct model names)
78
+ const mapped = config.agent.modelMap[modelTier];
79
+ if (mapped)
80
+ return mapped;
81
+ // Fallback to defaults
82
+ return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
83
+ }
@@ -0,0 +1,3 @@
1
+ import type { StageDefinition } from "./types.js";
2
+ export declare const STAGES: StageDefinition[];
3
+ export declare function getStage(name: string): StageDefinition | undefined;
@@ -0,0 +1,59 @@
1
+ export const STAGES = [
2
+ {
3
+ name: "taskify",
4
+ type: "agent",
5
+ modelTier: "cheap",
6
+ timeout: 300_000,
7
+ maxRetries: 1,
8
+ outputFile: "task.json",
9
+ },
10
+ {
11
+ name: "plan",
12
+ type: "agent",
13
+ modelTier: "strong",
14
+ timeout: 600_000,
15
+ maxRetries: 1,
16
+ outputFile: "plan.md",
17
+ },
18
+ {
19
+ name: "build",
20
+ type: "agent",
21
+ modelTier: "mid",
22
+ timeout: 1_200_000,
23
+ maxRetries: 1,
24
+ },
25
+ {
26
+ name: "verify",
27
+ type: "gate",
28
+ modelTier: "cheap",
29
+ timeout: 300_000,
30
+ maxRetries: 2,
31
+ retryWithAgent: "autofix",
32
+ },
33
+ {
34
+ name: "review",
35
+ type: "agent",
36
+ modelTier: "strong",
37
+ timeout: 600_000,
38
+ maxRetries: 1,
39
+ outputFile: "review.md",
40
+ },
41
+ {
42
+ name: "review-fix",
43
+ type: "agent",
44
+ modelTier: "mid",
45
+ timeout: 600_000,
46
+ maxRetries: 1,
47
+ },
48
+ {
49
+ name: "ship",
50
+ type: "deterministic",
51
+ modelTier: "cheap",
52
+ timeout: 120_000,
53
+ maxRetries: 1,
54
+ outputFile: "ship.md",
55
+ },
56
+ ];
57
+ export function getStage(name) {
58
+ return STAGES.find((s) => s.name === name);
59
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/entry.js ADDED
@@ -0,0 +1,236 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { createRunners } from "./agent-runner.js";
4
+ import { runPipeline, printStatus } from "./pipeline.js";
5
+ import { runPreflight } from "./preflight.js";
6
+ import { setConfigDir, getProjectConfig } from "./config.js";
7
+ import { setGhCwd, getIssue, postComment } from "./github-api.js";
8
+ import { logger } from "./logger.js";
9
+ // Extracted modules
10
+ import { parseArgs } from "./cli/args.js";
11
+ import { checkLitellmHealth, tryStartLitellm } from "./cli/litellm.js";
12
+ import { findLatestTaskForIssue, generateTaskId } from "./cli/task-resolution.js";
13
+ async function main() {
14
+ const input = parseArgs();
15
+ // Resolve working directory first (needed for task lookup)
16
+ const projectDir = input.cwd ? path.resolve(input.cwd) : process.cwd();
17
+ if (input.cwd) {
18
+ if (!fs.existsSync(projectDir)) {
19
+ console.error(`--cwd path does not exist: ${projectDir}`);
20
+ process.exit(1);
21
+ }
22
+ setConfigDir(projectDir);
23
+ setGhCwd(projectDir);
24
+ logger.info(`Working directory: ${projectDir}`);
25
+ }
26
+ // Resolve taskId
27
+ let taskId = input.taskId;
28
+ if (!taskId) {
29
+ if ((input.command === "rerun" || input.command === "fix") && input.issueNumber) {
30
+ const found = findLatestTaskForIssue(input.issueNumber, projectDir);
31
+ if (!found) {
32
+ console.error(`No previous task found for issue #${input.issueNumber}`);
33
+ process.exit(1);
34
+ }
35
+ taskId = found;
36
+ logger.info(`Found latest task for issue #${input.issueNumber}: ${taskId}`);
37
+ }
38
+ else if (input.issueNumber) {
39
+ taskId = `${input.issueNumber}-${generateTaskId()}`;
40
+ }
41
+ else if (input.command === "run" && input.task) {
42
+ taskId = generateTaskId();
43
+ }
44
+ else {
45
+ console.error("--task-id is required (or provide --issue-number to auto-generate)");
46
+ process.exit(1);
47
+ }
48
+ }
49
+ const taskDir = path.join(projectDir, ".tasks", taskId);
50
+ fs.mkdirSync(taskDir, { recursive: true });
51
+ // Status command — no preflight needed
52
+ if (input.command === "status") {
53
+ printStatus(taskId, taskDir);
54
+ return;
55
+ }
56
+ // Preflight
57
+ logger.info("Preflight checks:");
58
+ runPreflight();
59
+ // Write task.md if --task provided
60
+ if (input.task) {
61
+ fs.writeFileSync(path.join(taskDir, "task.md"), input.task);
62
+ }
63
+ // Auto-fetch issue body as task if no task.md and issue-number provided
64
+ const taskMdPath = path.join(taskDir, "task.md");
65
+ if (!fs.existsSync(taskMdPath) && input.issueNumber) {
66
+ logger.info(`Fetching issue #${input.issueNumber} body as task...`);
67
+ const issue = getIssue(input.issueNumber);
68
+ if (issue) {
69
+ const taskContent = `# ${issue.title}\n\n${issue.body ?? ""}`;
70
+ fs.writeFileSync(taskMdPath, taskContent);
71
+ logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
72
+ }
73
+ }
74
+ // Verify task.md exists for run
75
+ if (input.command === "run") {
76
+ if (!fs.existsSync(taskMdPath)) {
77
+ console.error("No task.md found. Provide --task, --issue-number, or ensure .tasks/<id>/task.md exists.");
78
+ process.exit(1);
79
+ }
80
+ }
81
+ // Fix command defaults to --from build
82
+ if (input.command === "fix" && !input.fromStage) {
83
+ input.fromStage = "build";
84
+ }
85
+ // Auto-detect --from for rerun if not provided (find paused stage)
86
+ if (input.command === "rerun" && !input.fromStage) {
87
+ const statusPath = path.join(taskDir, "status.json");
88
+ if (fs.existsSync(statusPath)) {
89
+ try {
90
+ const status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
91
+ const stageNames = ["taskify", "plan", "build", "verify", "review", "review-fix", "ship"];
92
+ let foundPaused = false;
93
+ for (const name of stageNames) {
94
+ const s = status.stages[name];
95
+ if (s?.error?.includes("paused")) {
96
+ const idx = stageNames.indexOf(name);
97
+ if (idx < stageNames.length - 1) {
98
+ input.fromStage = stageNames[idx + 1];
99
+ foundPaused = true;
100
+ logger.info(`Auto-detected resume from: ${input.fromStage} (after paused ${name})`);
101
+ break;
102
+ }
103
+ }
104
+ if (s?.state === "failed" || s?.state === "pending") {
105
+ input.fromStage = name;
106
+ foundPaused = true;
107
+ logger.info(`Auto-detected resume from: ${input.fromStage}`);
108
+ break;
109
+ }
110
+ }
111
+ if (!foundPaused) {
112
+ input.fromStage = "taskify";
113
+ logger.info("No paused/failed stage found, resuming from taskify");
114
+ }
115
+ }
116
+ catch {
117
+ console.error("--from <stage> is required (could not read status.json)");
118
+ process.exit(1);
119
+ }
120
+ }
121
+ else {
122
+ // No status.json — fall back to full run with feedback preserved
123
+ logger.info("No status.json found — running full pipeline with feedback");
124
+ input.command = "run";
125
+ }
126
+ }
127
+ // Start LiteLLM proxy if configured and not running
128
+ const config = getProjectConfig();
129
+ let litellmProcess = null;
130
+ const cleanupLitellm = () => { if (litellmProcess) {
131
+ litellmProcess.kill();
132
+ litellmProcess = null;
133
+ } };
134
+ process.on("exit", cleanupLitellm);
135
+ process.on("SIGINT", () => { cleanupLitellm(); process.exit(130); });
136
+ process.on("SIGTERM", () => { cleanupLitellm(); process.exit(143); });
137
+ if (config.agent.litellmUrl) {
138
+ const proxyRunning = await checkLitellmHealth(config.agent.litellmUrl);
139
+ if (!proxyRunning) {
140
+ litellmProcess = await tryStartLitellm(config.agent.litellmUrl, projectDir);
141
+ if (!litellmProcess) {
142
+ logger.warn("LiteLLM not available — falling back to Anthropic models");
143
+ config.agent.litellmUrl = undefined;
144
+ }
145
+ }
146
+ else {
147
+ logger.info(`LiteLLM proxy already running at ${config.agent.litellmUrl}`);
148
+ }
149
+ }
150
+ // Create runners
151
+ const runners = createRunners(config);
152
+ const defaultRunnerName = config.agent.defaultRunner ?? Object.keys(runners)[0] ?? "claude";
153
+ const defaultRunner = runners[defaultRunnerName];
154
+ if (!defaultRunner) {
155
+ console.error(`Default runner "${defaultRunnerName}" not configured`);
156
+ process.exit(1);
157
+ }
158
+ const healthy = await defaultRunner.healthCheck();
159
+ if (!healthy) {
160
+ console.error(`Runner "${defaultRunnerName}" health check failed`);
161
+ process.exit(1);
162
+ }
163
+ // Build context
164
+ const ctx = {
165
+ taskId,
166
+ taskDir,
167
+ projectDir,
168
+ runners,
169
+ input: {
170
+ mode: (input.command === "rerun" || input.command === "fix") ? "rerun" : "full",
171
+ fromStage: input.fromStage,
172
+ dryRun: input.dryRun,
173
+ issueNumber: input.issueNumber,
174
+ feedback: input.feedback,
175
+ local: input.local,
176
+ complexity: input.complexity,
177
+ },
178
+ };
179
+ logger.info(`Task: ${taskId}`);
180
+ logger.info(`Mode: ${ctx.input.mode}${ctx.input.local ? " (local)" : " (CI)"}`);
181
+ if (ctx.input.issueNumber)
182
+ logger.info(`Issue: #${ctx.input.issueNumber}`);
183
+ // Post task-id comment so user knows the ID for rerun
184
+ if (ctx.input.issueNumber && !ctx.input.local && ctx.input.mode === "full") {
185
+ const runUrl = process.env.RUN_URL ?? "";
186
+ const runLink = runUrl ? ` ([logs](${runUrl}))` : "";
187
+ try {
188
+ postComment(ctx.input.issueNumber, `🚀 Kody pipeline started: \`${taskId}\`${runLink}\n\nTo rerun: \`@kody rerun ${taskId} --from <stage>\``);
189
+ }
190
+ catch { /* best effort */ }
191
+ }
192
+ // Run pipeline
193
+ const state = await runPipeline(ctx);
194
+ // Report
195
+ const files = fs.readdirSync(taskDir);
196
+ console.log(`\nArtifacts in ${taskDir}:`);
197
+ for (const f of files) {
198
+ console.log(` ${f}`);
199
+ }
200
+ if (state.state === "failed") {
201
+ // Check if this is a "paused" state (questions posted) — not a real failure
202
+ const isPaused = Object.values(state.stages).some((s) => s.error?.includes("paused") ?? false);
203
+ if (isPaused) {
204
+ process.exit(0);
205
+ }
206
+ // Post failure comment on issue
207
+ if (ctx.input.issueNumber && !ctx.input.local) {
208
+ const failedStage = Object.entries(state.stages).find(([, s]) => s.state === "failed" || s.state === "timeout");
209
+ const stageName = failedStage ? failedStage[0] : "unknown";
210
+ const error = failedStage ? failedStage[1].error ?? "" : "";
211
+ try {
212
+ postComment(ctx.input.issueNumber, `❌ Pipeline failed at **${stageName}**${error ? `: ${error.slice(0, 200)}` : ""}`);
213
+ }
214
+ catch {
215
+ // Best effort
216
+ }
217
+ }
218
+ process.exit(1);
219
+ }
220
+ }
221
+ main().catch(async (err) => {
222
+ const msg = err instanceof Error ? err.message : String(err);
223
+ console.error(msg);
224
+ // Post crash comment if we have issue context
225
+ const issueStr = process.argv.find((_, i, a) => a[i - 1] === "--issue-number") ?? process.env.ISSUE_NUMBER;
226
+ const isLocal = process.argv.includes("--local") || !process.env.GITHUB_ACTIONS;
227
+ if (issueStr && !isLocal) {
228
+ try {
229
+ postComment(parseInt(issueStr, 10), `❌ Pipeline crashed: ${msg.slice(0, 200)}`);
230
+ }
231
+ catch {
232
+ // Best effort
233
+ }
234
+ }
235
+ process.exit(1);
236
+ });
@@ -0,0 +1,13 @@
1
+ export declare function deriveBranchName(issueNumber: number, title: string): string;
2
+ export declare function getDefaultBranch(cwd?: string): string;
3
+ export declare function getCurrentBranch(cwd?: string): string;
4
+ export declare function ensureFeatureBranch(issueNumber: number, title: string, cwd?: string): string;
5
+ export declare function syncWithDefault(cwd?: string): void;
6
+ export declare function commitAll(message: string, cwd?: string): {
7
+ success: boolean;
8
+ hash: string;
9
+ message: string;
10
+ };
11
+ export declare function pushBranch(cwd?: string): void;
12
+ export declare function getChangedFiles(baseBranch: string, cwd?: string): string[];
13
+ export declare function getDiff(baseBranch: string, cwd?: string): string;