greenbump 0.2.0

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 (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/README.zh-CN.md +213 -0
  4. package/action-dist/index.cjs +19695 -0
  5. package/action.yml +76 -0
  6. package/dist/action/github.js +70 -0
  7. package/dist/action/github.js.map +1 -0
  8. package/dist/action/main.js +99 -0
  9. package/dist/action/main.js.map +1 -0
  10. package/dist/agent/factory.js +72 -0
  11. package/dist/agent/factory.js.map +1 -0
  12. package/dist/agent/fixer.js +307 -0
  13. package/dist/agent/fixer.js.map +1 -0
  14. package/dist/agent/fixer.test.js +224 -0
  15. package/dist/agent/fixer.test.js.map +1 -0
  16. package/dist/agent/provider-anthropic.js +59 -0
  17. package/dist/agent/provider-anthropic.js.map +1 -0
  18. package/dist/agent/provider-openai.js +65 -0
  19. package/dist/agent/provider-openai.js.map +1 -0
  20. package/dist/agent/provider.js +4 -0
  21. package/dist/agent/provider.js.map +1 -0
  22. package/dist/cli.js +138 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/engine/batch.js +246 -0
  25. package/dist/engine/batch.js.map +1 -0
  26. package/dist/engine/batch.test.js +59 -0
  27. package/dist/engine/batch.test.js.map +1 -0
  28. package/dist/engine/changelog.js +66 -0
  29. package/dist/engine/changelog.js.map +1 -0
  30. package/dist/engine/checks.js +41 -0
  31. package/dist/engine/checks.js.map +1 -0
  32. package/dist/engine/checks.test.js +92 -0
  33. package/dist/engine/checks.test.js.map +1 -0
  34. package/dist/engine/detect.js +5 -0
  35. package/dist/engine/detect.js.map +1 -0
  36. package/dist/engine/ecosystems/bundler.js +59 -0
  37. package/dist/engine/ecosystems/bundler.js.map +1 -0
  38. package/dist/engine/ecosystems/cargo.js +83 -0
  39. package/dist/engine/ecosystems/cargo.js.map +1 -0
  40. package/dist/engine/ecosystems/cocoapods.js +47 -0
  41. package/dist/engine/ecosystems/cocoapods.js.map +1 -0
  42. package/dist/engine/ecosystems/composer.js +32 -0
  43. package/dist/engine/ecosystems/composer.js.map +1 -0
  44. package/dist/engine/ecosystems/conan.js +106 -0
  45. package/dist/engine/ecosystems/conan.js.map +1 -0
  46. package/dist/engine/ecosystems/elm.js +77 -0
  47. package/dist/engine/ecosystems/elm.js.map +1 -0
  48. package/dist/engine/ecosystems/go.js +72 -0
  49. package/dist/engine/ecosystems/go.js.map +1 -0
  50. package/dist/engine/ecosystems/gradle.js +90 -0
  51. package/dist/engine/ecosystems/gradle.js.map +1 -0
  52. package/dist/engine/ecosystems/index.js +67 -0
  53. package/dist/engine/ecosystems/index.js.map +1 -0
  54. package/dist/engine/ecosystems/maven.js +77 -0
  55. package/dist/engine/ecosystems/maven.js.map +1 -0
  56. package/dist/engine/ecosystems/mix.js +64 -0
  57. package/dist/engine/ecosystems/mix.js.map +1 -0
  58. package/dist/engine/ecosystems/npm.js +47 -0
  59. package/dist/engine/ecosystems/npm.js.map +1 -0
  60. package/dist/engine/ecosystems/nuget.js +50 -0
  61. package/dist/engine/ecosystems/nuget.js.map +1 -0
  62. package/dist/engine/ecosystems/parsers.test.js +255 -0
  63. package/dist/engine/ecosystems/parsers.test.js.map +1 -0
  64. package/dist/engine/ecosystems/pip.js +61 -0
  65. package/dist/engine/ecosystems/pip.js.map +1 -0
  66. package/dist/engine/ecosystems/pipenv.js +37 -0
  67. package/dist/engine/ecosystems/pipenv.js.map +1 -0
  68. package/dist/engine/ecosystems/pnpm.js +48 -0
  69. package/dist/engine/ecosystems/pnpm.js.map +1 -0
  70. package/dist/engine/ecosystems/poetry.js +42 -0
  71. package/dist/engine/ecosystems/poetry.js.map +1 -0
  72. package/dist/engine/ecosystems/pub.js +32 -0
  73. package/dist/engine/ecosystems/pub.js.map +1 -0
  74. package/dist/engine/ecosystems/python-shared.js +29 -0
  75. package/dist/engine/ecosystems/python-shared.js.map +1 -0
  76. package/dist/engine/ecosystems/swiftpm.js +102 -0
  77. package/dist/engine/ecosystems/swiftpm.js.map +1 -0
  78. package/dist/engine/ecosystems/types.js +19 -0
  79. package/dist/engine/ecosystems/types.js.map +1 -0
  80. package/dist/engine/ecosystems/uv.js +36 -0
  81. package/dist/engine/ecosystems/uv.js.map +1 -0
  82. package/dist/engine/ecosystems/yarn.js +62 -0
  83. package/dist/engine/ecosystems/yarn.js.map +1 -0
  84. package/dist/engine/exec.js +47 -0
  85. package/dist/engine/exec.js.map +1 -0
  86. package/dist/engine/git.js +42 -0
  87. package/dist/engine/git.js.map +1 -0
  88. package/dist/engine/git.test.js +111 -0
  89. package/dist/engine/git.test.js.map +1 -0
  90. package/dist/engine/pm.js +14 -0
  91. package/dist/engine/pm.js.map +1 -0
  92. package/dist/engine/run.js +167 -0
  93. package/dist/engine/run.js.map +1 -0
  94. package/dist/engine/run.test.js +47 -0
  95. package/dist/engine/run.test.js.map +1 -0
  96. package/dist/engine/upgrade.js +10 -0
  97. package/dist/engine/upgrade.js.map +1 -0
  98. package/dist/engine/workspace.js +155 -0
  99. package/dist/engine/workspace.js.map +1 -0
  100. package/dist/engine/workspace.test.js +95 -0
  101. package/dist/engine/workspace.test.js.map +1 -0
  102. package/dist/exitcode.js +51 -0
  103. package/dist/exitcode.js.map +1 -0
  104. package/dist/exitcode.test.js +62 -0
  105. package/dist/exitcode.test.js.map +1 -0
  106. package/dist/format.js +101 -0
  107. package/dist/format.js.map +1 -0
  108. package/dist/pr.js +63 -0
  109. package/dist/pr.js.map +1 -0
  110. package/dist/report.js +13 -0
  111. package/dist/report.js.map +1 -0
  112. package/dist/report.test.js +56 -0
  113. package/dist/report.test.js.map +1 -0
  114. package/dist/test-utils.js +36 -0
  115. package/dist/test-utils.js.map +1 -0
  116. package/package.json +56 -0
package/action.yml ADDED
@@ -0,0 +1,76 @@
1
+ name: "greenbump"
2
+ description: "Upgrade a dependency and let an AI agent fix the code it breaks, then open a green PR."
3
+ branding:
4
+ icon: "arrow-up-circle"
5
+ color: "green"
6
+ inputs:
7
+ anthropic-api-key:
8
+ description: "Your Anthropic API key (uses Claude). You pay for the tokens a fix costs."
9
+ required: false
10
+ deepseek-api-key:
11
+ description: "Your DeepSeek API key (uses DeepSeek, OpenAI-compatible). Provide this OR anthropic-api-key."
12
+ required: false
13
+ github-token:
14
+ description: "Token used to push the branch and open the PR. Use a PAT if you want CI to run on the PR."
15
+ required: false
16
+ default: ${{ github.token }}
17
+ dependency:
18
+ description: "Dependency to upgrade. Omit to pick the most-outdated one."
19
+ required: false
20
+ to:
21
+ description: "Target version (default: latest)."
22
+ required: false
23
+ ecosystem:
24
+ description: "Dependency ecosystem (npm, poetry, cargo, maven, …). Auto-detected if omitted."
25
+ required: false
26
+ build-cmd:
27
+ description: "Override the build command, e.g. \"make build\"."
28
+ required: false
29
+ test-cmd:
30
+ description: "Override the test command, e.g. \"make test\"."
31
+ required: false
32
+ provider:
33
+ description: "Model provider preset (openai, anthropic, deepseek, groq, …). Auto-detected if omitted."
34
+ required: false
35
+ model:
36
+ description: "Model id for the fix agent (default: per provider)."
37
+ required: false
38
+ base-url:
39
+ description: "Custom OpenAI-compatible endpoint (or override a preset)."
40
+ required: false
41
+ api-key:
42
+ description: "API key for --provider/--base-url. Prefer anthropic-api-key/deepseek-api-key for the built-in presets."
43
+ required: false
44
+ max-rounds:
45
+ description: "Max fix-loop rounds (caps token spend)."
46
+ required: false
47
+ default: "15"
48
+ max-tokens:
49
+ description: "Hard cap on total tokens spent by the fix loop; stops and flags the PR for review on overrun."
50
+ required: false
51
+ base-branch:
52
+ description: "Base branch for the PR (default: the triggering branch)."
53
+ required: false
54
+ review-label:
55
+ description: "Label added to the PR when it needs a human look (test file touched, or unverifiable). Auto-created if it doesn't exist."
56
+ required: false
57
+ default: "needs-review"
58
+ report-file:
59
+ description: "Write a JSON report of the run to this path."
60
+ required: false
61
+ outputs:
62
+ status:
63
+ description: "One of: pr-opened, pr-needs-review, no-changes, unfixed, skipped."
64
+ pr-url:
65
+ description: "URL of the opened PR, if any."
66
+ dependency:
67
+ description: "The dependency that was upgraded."
68
+ from:
69
+ description: "Version before the upgrade."
70
+ to:
71
+ description: "Version after the upgrade."
72
+ fixed:
73
+ description: "Whether build + tests ended up green."
74
+ runs:
75
+ using: "node20"
76
+ main: "action-dist/index.cjs"
@@ -0,0 +1,70 @@
1
+ import { appendFileSync } from "node:fs";
2
+ /** Read a GitHub Action input from its INPUT_* env var (matching @actions/core). */
3
+ export function getInput(name) {
4
+ const key = `INPUT_${name.replace(/ /g, "_").toUpperCase()}`;
5
+ return (process.env[key] ?? "").trim();
6
+ }
7
+ /** Write an Action output to $GITHUB_OUTPUT. */
8
+ export function setOutput(name, value) {
9
+ const file = process.env.GITHUB_OUTPUT;
10
+ if (!file)
11
+ return;
12
+ // multiline-safe heredoc format
13
+ const delim = `ghadelimiter_${name}`;
14
+ appendFileSync(file, `${name}<<${delim}\n${value}\n${delim}\n`);
15
+ }
16
+ export function info(msg) {
17
+ process.stdout.write(msg + "\n");
18
+ }
19
+ export function setFailed(msg) {
20
+ process.stdout.write(`::error::${msg}\n`);
21
+ process.exitCode = 1;
22
+ }
23
+ /** Create a PR via the REST API using Node's global fetch. Returns the PR URL, or null on failure. */
24
+ export async function createPullRequest(args) {
25
+ const res = await fetch(`https://api.github.com/repos/${args.owner}/${args.repo}/pulls`, {
26
+ method: "POST",
27
+ headers: {
28
+ Authorization: `Bearer ${args.token}`,
29
+ Accept: "application/vnd.github+json",
30
+ "X-GitHub-Api-Version": "2022-11-28",
31
+ "Content-Type": "application/json",
32
+ },
33
+ body: JSON.stringify({
34
+ title: args.title,
35
+ body: args.body,
36
+ head: args.head,
37
+ base: args.base,
38
+ draft: args.draft,
39
+ }),
40
+ });
41
+ if (!res.ok) {
42
+ const text = await res.text();
43
+ info(`::warning::could not open PR (${res.status}): ${text}`);
44
+ return null;
45
+ }
46
+ const data = (await res.json());
47
+ info(`opened PR #${data.number}: ${data.html_url}`);
48
+ if (args.labels?.length && data.number) {
49
+ await addLabels(args, data.number, args.labels);
50
+ }
51
+ return data.html_url ?? null;
52
+ }
53
+ /** The "create PR" endpoint doesn't accept labels directly — attach them via a follow-up call. */
54
+ async function addLabels(args, prNumber, labels) {
55
+ const res = await fetch(`https://api.github.com/repos/${args.owner}/${args.repo}/issues/${prNumber}/labels`, {
56
+ method: "POST",
57
+ headers: {
58
+ Authorization: `Bearer ${args.token}`,
59
+ Accept: "application/vnd.github+json",
60
+ "X-GitHub-Api-Version": "2022-11-28",
61
+ "Content-Type": "application/json",
62
+ },
63
+ body: JSON.stringify({ labels }),
64
+ });
65
+ if (!res.ok) {
66
+ const text = await res.text();
67
+ info(`::warning::could not add label(s) (${res.status}): ${text}`);
68
+ }
69
+ }
70
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/action/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { run, RunError } from "../engine/run.js";
2
+ import { renderPrBody } from "../pr.js";
3
+ import { exec } from "../engine/exec.js";
4
+ import { buildReport, writeReport } from "../report.js";
5
+ import { getInput, setOutput, info, setFailed, createPullRequest, } from "./github.js";
6
+ async function main() {
7
+ // Map action inputs onto the env vars the provider factory reads.
8
+ const anthropicKey = getInput("anthropic-api-key") || process.env.ANTHROPIC_API_KEY || "";
9
+ const deepseekKey = getInput("deepseek-api-key") || process.env.DEEPSEEK_API_KEY || "";
10
+ if (anthropicKey)
11
+ process.env.ANTHROPIC_API_KEY = anthropicKey;
12
+ if (deepseekKey)
13
+ process.env.DEEPSEEK_API_KEY = deepseekKey;
14
+ const token = getInput("github-token") || process.env.GITHUB_TOKEN || "";
15
+ if (!anthropicKey && !deepseekKey) {
16
+ setFailed("an API key is required: set anthropic-api-key or deepseek-api-key (you pay for tokens).");
17
+ return;
18
+ }
19
+ if (!token) {
20
+ setFailed("github-token is required to push a branch and open a PR.");
21
+ return;
22
+ }
23
+ const cwd = process.cwd();
24
+ const repoSlug = process.env.GITHUB_REPOSITORY ?? "";
25
+ const [owner, repo] = repoSlug.split("/");
26
+ const base = getInput("base-branch") || process.env.GITHUB_REF_NAME || "main";
27
+ let summary;
28
+ try {
29
+ summary = await run({
30
+ cwd,
31
+ dep: getInput("dependency") || undefined,
32
+ to: getInput("to") || undefined,
33
+ ecosystem: getInput("ecosystem") || undefined,
34
+ buildCmd: getInput("build-cmd") || undefined,
35
+ testCmd: getInput("test-cmd") || undefined,
36
+ provider: getInput("provider") || undefined,
37
+ model: getInput("model") || undefined,
38
+ baseURL: getInput("base-url") || undefined,
39
+ apiKey: getInput("api-key") || undefined,
40
+ maxRounds: parseInt(getInput("max-rounds") || "15", 10),
41
+ maxTokens: getInput("max-tokens") ? parseInt(getInput("max-tokens"), 10) : undefined,
42
+ onLog: info,
43
+ });
44
+ }
45
+ catch (err) {
46
+ if (err instanceof RunError) {
47
+ info(`nothing to do: ${err.message}`);
48
+ setOutput("status", "skipped");
49
+ return;
50
+ }
51
+ setFailed(`greenbump failed: ${err.message}`);
52
+ return;
53
+ }
54
+ setOutput("dependency", summary.dep);
55
+ setOutput("from", summary.from);
56
+ setOutput("to", summary.to);
57
+ setOutput("fixed", String(summary.fixed));
58
+ const reportFile = getInput("report-file");
59
+ if (reportFile) {
60
+ await writeReport(reportFile, buildReport([summary]));
61
+ }
62
+ if (!summary.committed || !summary.branch) {
63
+ info("no committable changes produced — not opening a PR.");
64
+ setOutput("status", summary.fixed ? "no-changes" : "unfixed");
65
+ return;
66
+ }
67
+ // configure a bot identity and push the branch
68
+ await exec("git", ["config", "user.name", "greenbump[bot]"], { cwd });
69
+ await exec("git", ["config", "user.email", "greenbump@users.noreply.github.com"], { cwd });
70
+ const remote = `https://x-access-token:${token}@github.com/${repoSlug}.git`;
71
+ const push = await exec("git", ["push", "--force", remote, `HEAD:${summary.branch}`], { cwd });
72
+ if (push.code !== 0) {
73
+ setFailed(`failed to push branch:\n${push.combined}`);
74
+ return;
75
+ }
76
+ // Both "flagged for review" and "fix loop exhausted without going green"
77
+ // are not-safe-to-merge-blindly states — both get a draft PR. Only the
78
+ // action output/label distinguishes which one it was.
79
+ const stillBroken = summary.neededFix && !summary.fixed;
80
+ const draft = summary.needsReview || stillBroken;
81
+ const reviewLabel = getInput("review-label") || "needs-review";
82
+ const title = `chore(deps): bump ${summary.dep} ${summary.from} → ${summary.to}`;
83
+ const url = await createPullRequest({
84
+ token,
85
+ owner,
86
+ repo,
87
+ head: summary.branch,
88
+ base,
89
+ title,
90
+ body: renderPrBody(summary),
91
+ draft,
92
+ labels: summary.needsReview ? [reviewLabel] : undefined,
93
+ });
94
+ setOutput("pr-url", url ?? "");
95
+ setOutput("status", stillBroken ? "unfixed" : summary.needsReview ? "pr-needs-review" : "pr-opened");
96
+ info(draft ? "opened a draft PR for review." : "opened a PR — build + tests green.");
97
+ }
98
+ main().catch((err) => setFailed(`unexpected: ${err.message}`));
99
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/action/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { AnthropicProvider } from "./provider-anthropic.js";
2
+ import { OpenAICompatProvider } from "./provider-openai.js";
3
+ // Mainstream providers. Most speak the OpenAI protocol; only Claude is native
4
+ // Anthropic. Add a row here to support a new provider.
5
+ export const PRESETS = {
6
+ openai: { label: "OpenAI", protocol: "openai", baseURL: "https://api.openai.com/v1", keyEnv: "OPENAI_API_KEY" },
7
+ anthropic: { label: "Anthropic (Claude)", protocol: "anthropic", keyEnv: "ANTHROPIC_API_KEY", defaultModel: "claude-sonnet-5" },
8
+ deepseek: { label: "DeepSeek", protocol: "openai", baseURL: "https://api.deepseek.com", keyEnv: "DEEPSEEK_API_KEY", defaultModel: "deepseek-v4-pro" },
9
+ gemini: { label: "Google Gemini", protocol: "openai", baseURL: "https://generativelanguage.googleapis.com/v1beta/openai", keyEnv: "GEMINI_API_KEY" },
10
+ groq: { label: "Groq", protocol: "openai", baseURL: "https://api.groq.com/openai/v1", keyEnv: "GROQ_API_KEY" },
11
+ mistral: { label: "Mistral", protocol: "openai", baseURL: "https://api.mistral.ai/v1", keyEnv: "MISTRAL_API_KEY" },
12
+ xai: { label: "xAI Grok", protocol: "openai", baseURL: "https://api.x.ai/v1", keyEnv: "XAI_API_KEY" },
13
+ openrouter: { label: "OpenRouter", protocol: "openai", baseURL: "https://openrouter.ai/api/v1", keyEnv: "OPENROUTER_API_KEY" },
14
+ moonshot: { label: "Moonshot (Kimi)", protocol: "openai", baseURL: "https://api.moonshot.cn/v1", keyEnv: "MOONSHOT_API_KEY" },
15
+ qwen: { label: "Qwen (DashScope)", protocol: "openai", baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", keyEnv: "DASHSCOPE_API_KEY" },
16
+ ollama: { label: "Ollama (local)", protocol: "openai", baseURL: "http://localhost:11434/v1", keyEnv: "OLLAMA_API_KEY", keyOptional: true },
17
+ };
18
+ // Order for auto-detection when no --provider is given.
19
+ const DETECT_ORDER = ["anthropic", "deepseek", "openai", "gemini", "groq", "mistral", "xai", "openrouter", "moonshot", "qwen"];
20
+ export function listProviders() {
21
+ return Object.entries(PRESETS)
22
+ .map(([k, p]) => ` ${k.padEnd(11)} ${p.label}${p.defaultModel ? ` (default: ${p.defaultModel})` : ""}`)
23
+ .join("\n");
24
+ }
25
+ /** Which preset to use: explicit --provider, else first one with a key in env. */
26
+ function pickPresetName(choice) {
27
+ if (choice.provider) {
28
+ if (!PRESETS[choice.provider]) {
29
+ throw new Error(`Unknown provider "${choice.provider}". Known:\n${listProviders()}`);
30
+ }
31
+ return choice.provider;
32
+ }
33
+ return DETECT_ORDER.find((name) => process.env[PRESETS[name].keyEnv]);
34
+ }
35
+ export function createProvider(choice = {}) {
36
+ // Generic escape hatch: a raw --base-url with no preset => OpenAI-compatible.
37
+ const presetName = pickPresetName(choice);
38
+ if (!presetName && choice.baseURL) {
39
+ const key = choice.apiKey || process.env.OPENAI_API_KEY || "none";
40
+ if (!choice.model)
41
+ throw new Error("--base-url requires --model (no default known for a custom endpoint).");
42
+ return new OpenAICompatProvider("custom", key, choice.baseURL, choice.model);
43
+ }
44
+ if (!presetName) {
45
+ throw new Error(`No API key found and no --provider given. Set one of these env vars, or pass --provider:\n${listProviders()}`);
46
+ }
47
+ const preset = PRESETS[presetName];
48
+ const apiKey = choice.apiKey || process.env[preset.keyEnv] || (preset.keyOptional ? "none" : "");
49
+ if (!apiKey) {
50
+ throw new Error(`${preset.label} selected but ${preset.keyEnv} is not set.`);
51
+ }
52
+ const model = choice.model || preset.defaultModel;
53
+ if (!model) {
54
+ throw new Error(`${preset.label} needs an explicit model — pass --model <id> (model ids change often, so no default is baked in).`);
55
+ }
56
+ const baseURL = choice.baseURL || preset.baseURL;
57
+ if (preset.protocol === "anthropic") {
58
+ return new AnthropicProvider(apiKey, model, baseURL);
59
+ }
60
+ return new OpenAICompatProvider(presetName, apiKey, baseURL, model);
61
+ }
62
+ /** True if any provider is usable (a key in env, or an explicit provider/base-url). */
63
+ export function hasAnyKey(choice = {}) {
64
+ try {
65
+ createProvider(choice);
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/agent/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,307 @@
1
+ import { readFile, writeFile, readdir, stat } from "node:fs/promises";
2
+ import { resolve, relative, isAbsolute, join } from "node:path";
3
+ import { runChecks } from "../engine/checks.js";
4
+ import { getAdapter } from "../engine/pm.js";
5
+ function buildSystemPrompt(pm, deps) {
6
+ const adapter = getAdapter(pm);
7
+ const protectedFiles = [...adapter.manifestFiles, ...adapter.lockFiles].join(", ");
8
+ const upgradeLine = deps && deps.length > 1
9
+ ? `${deps.length} dependencies were just upgraded together and it broke the build or tests:\n${deps
10
+ .map((d) => `- ${d.dep} ${d.from} → ${d.to}`)
11
+ .join("\n")}`
12
+ : "A dependency was just upgraded and it broke the build or tests.";
13
+ return `You are greenbump's fix agent. ${upgradeLine}
14
+ Your job: edit the project's source code so that build and tests pass again — WITHOUT downgrading any dependency and WITHOUT weakening or deleting tests to make them pass.
15
+
16
+ Rules:
17
+ - Make the smallest correct change that adapts the code to the new version's API.
18
+ - Prefer following each dependency's documented migration path (renamed exports, changed signatures, moved modules, new required options). If release notes are provided below, treat them as authoritative over guessing.
19
+ - Use search_code to find ALL call sites of the breaking API across the repo before editing — a partial fix that leaves other files broken wastes rounds.
20
+ - Never edit ${protectedFiles}. The upgrade is intentional.
21
+ - Never delete or trivially rewrite a test just to make it green. Fix the real cause.
22
+ - Use run_check to verify. When run_check reports ok, you are done — stop.
23
+ - You have a limited number of rounds. If you're running low, prioritize verifying your current fix over continuing to explore.
24
+ - Work efficiently: read only the files you need.`;
25
+ }
26
+ const tools = [
27
+ {
28
+ name: "list_dir",
29
+ description: "List files and subdirectories at a project-relative path.",
30
+ parameters: {
31
+ type: "object",
32
+ properties: { path: { type: "string", description: "project-relative dir, '.' for root" } },
33
+ required: ["path"],
34
+ },
35
+ },
36
+ {
37
+ name: "search_code",
38
+ description: "Search the repo's source files for a literal string or regex (case-insensitive substring match per line). Use this to find every call site of a breaking API before editing.",
39
+ parameters: {
40
+ type: "object",
41
+ properties: {
42
+ query: { type: "string", description: "text or regex to search for" },
43
+ path: { type: "string", description: "project-relative dir to search under (default: '.')" },
44
+ },
45
+ required: ["query"],
46
+ },
47
+ },
48
+ {
49
+ name: "read_file",
50
+ description: "Read a project-relative file's contents.",
51
+ parameters: {
52
+ type: "object",
53
+ properties: { path: { type: "string" } },
54
+ required: ["path"],
55
+ },
56
+ },
57
+ {
58
+ name: "write_file",
59
+ description: "Overwrite a project-relative file with new contents.",
60
+ parameters: {
61
+ type: "object",
62
+ properties: { path: { type: "string" }, content: { type: "string" } },
63
+ required: ["path", "content"],
64
+ },
65
+ },
66
+ {
67
+ name: "run_check",
68
+ description: "Run build + tests. Returns whether they pass and any failure output.",
69
+ parameters: { type: "object", properties: {}, required: [] },
70
+ },
71
+ ];
72
+ /** Resolve a project-relative path and refuse anything escaping cwd. */
73
+ function safePath(cwd, p) {
74
+ const abs = isAbsolute(p) ? p : resolve(cwd, p);
75
+ const rel = relative(cwd, abs);
76
+ if (rel.startsWith("..") || isAbsolute(rel)) {
77
+ throw new Error(`path escapes project root: ${p}`);
78
+ }
79
+ return abs;
80
+ }
81
+ const IGNORE_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"]);
82
+ const SEARCHABLE_EXT = new Set([
83
+ ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts",
84
+ ".json", ".vue", ".svelte", ".md", ".yml", ".yaml",
85
+ ]);
86
+ const MAX_SEARCH_RESULTS = 200;
87
+ const MAX_SEARCH_FILE_SIZE = 1_000_000;
88
+ async function searchCode(cwd, root, query) {
89
+ let re;
90
+ try {
91
+ re = new RegExp(query, "i");
92
+ }
93
+ catch {
94
+ re = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
95
+ }
96
+ const results = [];
97
+ async function walk(dir) {
98
+ if (results.length >= MAX_SEARCH_RESULTS)
99
+ return;
100
+ let entries;
101
+ try {
102
+ entries = await readdir(dir, { withFileTypes: true });
103
+ }
104
+ catch {
105
+ return;
106
+ }
107
+ for (const e of entries) {
108
+ if (results.length >= MAX_SEARCH_RESULTS)
109
+ return;
110
+ if (IGNORE_DIRS.has(e.name))
111
+ continue;
112
+ const abs = join(dir, e.name);
113
+ if (e.isDirectory()) {
114
+ await walk(abs);
115
+ continue;
116
+ }
117
+ const dot = e.name.lastIndexOf(".");
118
+ if (dot < 0 || !SEARCHABLE_EXT.has(e.name.slice(dot)))
119
+ continue;
120
+ let s;
121
+ try {
122
+ s = await stat(abs);
123
+ }
124
+ catch {
125
+ continue;
126
+ }
127
+ if (s.size > MAX_SEARCH_FILE_SIZE)
128
+ continue;
129
+ let content;
130
+ try {
131
+ content = await readFile(abs, "utf8");
132
+ }
133
+ catch {
134
+ continue;
135
+ }
136
+ const rel = relative(cwd, abs);
137
+ const lines = content.split("\n");
138
+ for (let i = 0; i < lines.length; i++) {
139
+ if (re.test(lines[i])) {
140
+ results.push(`${rel}:${i + 1}: ${lines[i].trim().slice(0, 200)}`);
141
+ if (results.length >= MAX_SEARCH_RESULTS)
142
+ break;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ await walk(safePath(cwd, root));
148
+ if (results.length === 0)
149
+ return "(no matches)";
150
+ const suffix = results.length >= MAX_SEARCH_RESULTS ? "\n...(truncated, refine your query)" : "";
151
+ return results.join("\n") + suffix;
152
+ }
153
+ async function toolResult(cwd, pm, checkOverrides, name, input) {
154
+ switch (name) {
155
+ case "list_dir": {
156
+ const abs = safePath(cwd, input.path ?? ".");
157
+ const entries = await readdir(abs, { withFileTypes: true });
158
+ const lines = entries
159
+ .filter((e) => !IGNORE_DIRS.has(e.name))
160
+ .map((e) => (e.isDirectory() ? `${e.name}/` : e.name));
161
+ return { text: lines.join("\n") || "(empty)" };
162
+ }
163
+ case "search_code": {
164
+ const text = await searchCode(cwd, input.path ?? ".", input.query);
165
+ return { text };
166
+ }
167
+ case "read_file": {
168
+ const abs = safePath(cwd, input.path);
169
+ const s = await stat(abs);
170
+ if (s.size > 200_000)
171
+ return { text: "(file too large to read)" };
172
+ return { text: await readFile(abs, "utf8") };
173
+ }
174
+ case "write_file": {
175
+ const abs = safePath(cwd, input.path);
176
+ await writeFile(abs, input.content, "utf8");
177
+ return { text: `wrote ${input.path}` };
178
+ }
179
+ case "run_check": {
180
+ const r = await runChecks(pm, cwd, checkOverrides);
181
+ if (r.ok)
182
+ return { text: "PASS: build and tests are green.", checkOk: true };
183
+ return {
184
+ text: `FAIL (${r.failedStep}):\n${r.output}`,
185
+ checkOk: false,
186
+ };
187
+ }
188
+ default:
189
+ return { text: `unknown tool ${name}` };
190
+ }
191
+ }
192
+ const MAX_RETRIES = 3;
193
+ const RETRY_BASE_DELAY_MS = 1_000;
194
+ function isRetryable(err) {
195
+ const msg = err instanceof Error ? err.message : String(err);
196
+ return /connection error|ECONNRESET|ETIMEDOUT|EAI_AGAIN|fetch failed|network|429|500|502|503|504/i.test(msg);
197
+ }
198
+ /** Call the provider with retry/backoff for transient network or server errors. */
199
+ async function sendWithRetry(provider, system, messages, toolSpecs, log) {
200
+ let lastErr;
201
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
202
+ try {
203
+ return await provider.send(system, messages, toolSpecs);
204
+ }
205
+ catch (err) {
206
+ lastErr = err;
207
+ if (attempt === MAX_RETRIES || !isRetryable(err))
208
+ throw err;
209
+ const delay = RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
210
+ log(`provider request failed (${err.message}) — retrying in ${delay}ms (attempt ${attempt}/${MAX_RETRIES})`);
211
+ await new Promise((r) => setTimeout(r, delay));
212
+ }
213
+ }
214
+ throw lastErr;
215
+ }
216
+ export async function runFixLoop(opts) {
217
+ const { provider, pm, checkOverrides } = opts;
218
+ const log = opts.onLog ?? (() => { });
219
+ const usage = { inputTokens: 0, outputTokens: 0 };
220
+ const editedFiles = new Set();
221
+ const SYSTEM = buildSystemPrompt(pm, opts.deps);
222
+ const upgradeSummary = opts.deps && opts.deps.length > 1
223
+ ? opts.deps.map((d) => `\`${d.dep}\` from ${d.from} to ${d.to}`).join(", ")
224
+ : `\`${opts.dep}\` from ${opts.from} to ${opts.to}`;
225
+ const changelogBlock = opts.deps && opts.deps.length > 1
226
+ ? opts.deps
227
+ .filter((d) => d.changelog)
228
+ .map((d) => `\n\nRelease notes for ${d.dep}@${d.to}:\n\n${d.changelog}`)
229
+ .join("")
230
+ : opts.changelog
231
+ ? `\n\nHere are the release notes for ${opts.dep}@${opts.to} — use them to find the correct migration:\n\n${opts.changelog}`
232
+ : "";
233
+ const messages = [
234
+ {
235
+ role: "user",
236
+ text: `The dependenc${opts.deps && opts.deps.length > 1 ? "ies" : "y"} ${upgradeSummary} ${opts.deps && opts.deps.length > 1 ? "were" : "was"} upgraded.
237
+ This broke the project. Here is the failing output:
238
+
239
+ \`\`\`
240
+ ${opts.failureOutput}
241
+ \`\`\`${changelogBlock}
242
+
243
+ Fix the source code so build and tests pass. Call run_check to verify before finishing.`,
244
+ },
245
+ ];
246
+ let fixed = false;
247
+ let budgetExceeded = false;
248
+ let round = 0;
249
+ for (round = 1; round <= opts.maxRounds; round++) {
250
+ const remaining = opts.maxRounds - round + 1;
251
+ const system = remaining <= 2 ? `${SYSTEM}\n\nYou have ${remaining} round(s) left. Wrap up and verify now.` : SYSTEM;
252
+ const turn = await sendWithRetry(provider, system, messages, tools, log);
253
+ usage.inputTokens += turn.usage.inputTokens;
254
+ usage.outputTokens += turn.usage.outputTokens;
255
+ messages.push({ role: "assistant", text: turn.text, toolCalls: turn.toolCalls });
256
+ if (opts.maxTokens !== undefined && usage.inputTokens + usage.outputTokens >= opts.maxTokens) {
257
+ log(`round ${round}: hit token budget (${usage.inputTokens + usage.outputTokens}/${opts.maxTokens}) — stopping without a final fix, flagging for review`);
258
+ budgetExceeded = true;
259
+ break;
260
+ }
261
+ if (turn.toolCalls.length === 0) {
262
+ // Model stopped without a tool call — verify once and finish.
263
+ const r = await runChecks(pm, opts.cwd, checkOverrides);
264
+ fixed = r.ok;
265
+ log(`round ${round}: agent stopped, final check ${r.ok ? "PASS" : "FAIL"}`);
266
+ break;
267
+ }
268
+ const results = [];
269
+ for (const tc of turn.toolCalls) {
270
+ if (tc.name === "write_file") {
271
+ editedFiles.add(tc.input.path);
272
+ }
273
+ let out;
274
+ try {
275
+ out = await toolResult(opts.cwd, pm, checkOverrides, tc.name, tc.input);
276
+ }
277
+ catch (err) {
278
+ out = { text: `error: ${err.message}` };
279
+ }
280
+ log(`round ${round}: ${tc.name} ${tc.input.path ?? tc.input.query ?? ""}`.trim());
281
+ if (out.checkOk)
282
+ fixed = true;
283
+ results.push({
284
+ id: tc.id,
285
+ content: out.text,
286
+ isError: out.text.startsWith("error:") || undefined,
287
+ });
288
+ }
289
+ messages.push({ role: "tool", results });
290
+ if (fixed) {
291
+ log(`round ${round}: check passed — fixed`);
292
+ break;
293
+ }
294
+ }
295
+ return {
296
+ fixed,
297
+ // `round` overshoots by 1 when the for-loop exhausts naturally (its
298
+ // increment runs once more before the loop condition fails) — clamp so
299
+ // callers never see e.g. "16 rounds" for a --max-rounds 15 run.
300
+ rounds: Math.min(round, opts.maxRounds),
301
+ unverifiable: false,
302
+ usage,
303
+ editedFiles: [...editedFiles],
304
+ budgetExceeded,
305
+ };
306
+ }
307
+ //# sourceMappingURL=fixer.js.map