greenbump 0.4.0 → 0.6.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.
- package/README.md +673 -66
- package/README.zh-CN.md +525 -100
- package/dist/agent/fixer.js +161 -12
- package/dist/agent/fixer.js.map +1 -1
- package/dist/agent/fixer.test.js +410 -0
- package/dist/agent/fixer.test.js.map +1 -0
- package/dist/cli/interactive.js +81 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli.js +53 -0
- package/dist/cli.js.map +1 -1
- package/dist/engine/analyzers/changelog-parser.js +121 -0
- package/dist/engine/analyzers/changelog-parser.js.map +1 -0
- package/dist/engine/batch.test.js +59 -0
- package/dist/engine/batch.test.js.map +1 -0
- package/dist/engine/cache/manager.js +165 -0
- package/dist/engine/cache/manager.js.map +1 -0
- package/dist/engine/cache/manager.test.js +75 -0
- package/dist/engine/cache/manager.test.js.map +1 -0
- package/dist/engine/checks.test.js +92 -0
- package/dist/engine/checks.test.js.map +1 -0
- package/dist/engine/context/optimizer.js +189 -0
- package/dist/engine/context/optimizer.js.map +1 -0
- package/dist/engine/context/optimizer.test.js +81 -0
- package/dist/engine/context/optimizer.test.js.map +1 -0
- package/dist/engine/ecosystems/parsers.test.js +255 -0
- package/dist/engine/ecosystems/parsers.test.js.map +1 -0
- package/dist/engine/fixer/patterns.js +356 -0
- package/dist/engine/fixer/patterns.js.map +1 -0
- package/dist/engine/fixer/patterns.test.js +124 -0
- package/dist/engine/fixer/patterns.test.js.map +1 -0
- package/dist/engine/git.js +1 -0
- package/dist/engine/git.js.map +1 -1
- package/dist/engine/git.test.js +111 -0
- package/dist/engine/git.test.js.map +1 -0
- package/dist/engine/perf/metrics.js +171 -0
- package/dist/engine/perf/metrics.js.map +1 -0
- package/dist/engine/perf/regression.js +115 -0
- package/dist/engine/perf/regression.js.map +1 -0
- package/dist/engine/run.js +93 -2
- package/dist/engine/run.js.map +1 -1
- package/dist/engine/run.test.js +47 -0
- package/dist/engine/run.test.js.map +1 -0
- package/dist/engine/sandbox/docker-manager.js +215 -0
- package/dist/engine/sandbox/docker-manager.js.map +1 -0
- package/dist/engine/sandbox/orchestrator.js +107 -0
- package/dist/engine/sandbox/orchestrator.js.map +1 -0
- package/dist/engine/sandbox/service-detector.js +147 -0
- package/dist/engine/sandbox/service-detector.js.map +1 -0
- package/dist/engine/sandbox/template-generator.js +241 -0
- package/dist/engine/sandbox/template-generator.js.map +1 -0
- package/dist/engine/verifiers/ast-analyzer.js +212 -0
- package/dist/engine/verifiers/ast-analyzer.js.map +1 -0
- package/dist/engine/verifiers/ast-analyzer.test.js +96 -0
- package/dist/engine/verifiers/ast-analyzer.test.js.map +1 -0
- package/dist/engine/workspace.test.js +95 -0
- package/dist/engine/workspace.test.js.map +1 -0
- package/dist/exitcode.test.js +62 -0
- package/dist/exitcode.test.js.map +1 -0
- package/dist/format.js +15 -0
- package/dist/format.js.map +1 -1
- package/dist/report.test.js +56 -0
- package/dist/report.test.js.map +1 -0
- package/package.json +7 -4
- package/CHANGELOG.md +0 -93
- package/CONTRIBUTING.md +0 -52
- package/SECURITY.md +0 -38
- package/action-dist/index.cjs +0 -19935
- package/action.yml +0 -76
- package/competitive-analysis.md +0 -304
- package/examples/greenbump.yml +0 -37
- package/examples/legacy-repo/package.json +0 -12
- package/examples/legacy-repo/test.js +0 -17
- package/examples/multi-file-repo/models/todo.js +0 -8
- package/examples/multi-file-repo/models/user.js +0 -11
- package/examples/multi-file-repo/package.json +0 -12
- package/examples/multi-file-repo/services/session.js +0 -9
- package/examples/multi-file-repo/test.js +0 -19
- package/optimization-roadmap.md +0 -772
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, writeFile, rm, mkdir } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { runFixLoop } from "./fixer.js";
|
|
7
|
+
import { resetCacheForTests } from "../engine/cache/manager.js";
|
|
8
|
+
async function withTmpDir(fn) {
|
|
9
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-fixer-"));
|
|
10
|
+
// Isolate the on-disk cache per test: otherwise a fix learned in one test
|
|
11
|
+
// (or left over from a real run in ~/.greenbump) would be replayed in
|
|
12
|
+
// another and silently skip the LLM loop these tests exercise.
|
|
13
|
+
const cacheDir = await mkdtemp(join(tmpdir(), "greenbump-cache-"));
|
|
14
|
+
const prevCacheDir = process.env.GREENBUMP_CACHE_DIR;
|
|
15
|
+
process.env.GREENBUMP_CACHE_DIR = cacheDir;
|
|
16
|
+
resetCacheForTests();
|
|
17
|
+
try {
|
|
18
|
+
await fn(dir);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
if (prevCacheDir === undefined)
|
|
22
|
+
delete process.env.GREENBUMP_CACHE_DIR;
|
|
23
|
+
else
|
|
24
|
+
process.env.GREENBUMP_CACHE_DIR = prevCacheDir;
|
|
25
|
+
resetCacheForTests();
|
|
26
|
+
await rm(cacheDir, { recursive: true, force: true });
|
|
27
|
+
await rm(dir, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** A scripted provider: returns each entry in `turns` in order, one per send() call. */
|
|
31
|
+
function scriptedProvider(turns) {
|
|
32
|
+
let i = 0;
|
|
33
|
+
const calls = [];
|
|
34
|
+
return {
|
|
35
|
+
name: "stub",
|
|
36
|
+
model: "stub-model",
|
|
37
|
+
calls,
|
|
38
|
+
async send(_system, messages, _tools) {
|
|
39
|
+
calls.push(messages);
|
|
40
|
+
const turn = turns[Math.min(i, turns.length - 1)];
|
|
41
|
+
i++;
|
|
42
|
+
return {
|
|
43
|
+
text: turn.text ?? "",
|
|
44
|
+
toolCalls: turn.toolCalls ?? [],
|
|
45
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function writePkg(dir, scripts) {
|
|
51
|
+
await writeFile(join(dir, "package.json"), JSON.stringify({ name: "tmp", scripts }), "utf8");
|
|
52
|
+
}
|
|
53
|
+
const baseOpts = {
|
|
54
|
+
pm: "npm",
|
|
55
|
+
dep: "lodash",
|
|
56
|
+
from: "4.17.20",
|
|
57
|
+
to: "4.17.21",
|
|
58
|
+
failureOutput: "TypeError: _.pad is not a function",
|
|
59
|
+
maxRounds: 5,
|
|
60
|
+
};
|
|
61
|
+
test("runFixLoop: write_file then run_check passing ends the loop as fixed, tracking the edited file", async () => {
|
|
62
|
+
await withTmpDir(async (dir) => {
|
|
63
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"", test: "node -e \"process.exit(0)\"" });
|
|
64
|
+
await writeFile(join(dir, "app.js"), "old content", "utf8");
|
|
65
|
+
const provider = scriptedProvider([
|
|
66
|
+
{
|
|
67
|
+
toolCalls: [
|
|
68
|
+
{ id: "1", name: "write_file", input: { path: "app.js", content: "new content" } },
|
|
69
|
+
{ id: "2", name: "run_check", input: {} },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
const result = await runFixLoop({ ...baseOpts, cwd: dir, provider });
|
|
74
|
+
assert.equal(result.fixed, true);
|
|
75
|
+
assert.equal(result.rounds, 1);
|
|
76
|
+
assert.deepEqual(result.editedFiles, ["app.js"]);
|
|
77
|
+
assert.equal(result.usage.inputTokens, 10);
|
|
78
|
+
assert.equal(result.usage.outputTokens, 5);
|
|
79
|
+
const { readFile } = await import("node:fs/promises");
|
|
80
|
+
assert.equal(await readFile(join(dir, "app.js"), "utf8"), "new content");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
test("runFixLoop: run_check failure keeps the loop going to the next round", async () => {
|
|
84
|
+
await withTmpDir(async (dir) => {
|
|
85
|
+
await writePkg(dir, { build: "node -e \"process.exit(1)\"" });
|
|
86
|
+
const provider = scriptedProvider([
|
|
87
|
+
{ toolCalls: [{ id: "1", name: "run_check", input: {} }] },
|
|
88
|
+
{ toolCalls: [{ id: "2", name: "run_check", input: {} }] },
|
|
89
|
+
]);
|
|
90
|
+
const result = await runFixLoop({ ...baseOpts, cwd: dir, provider, maxRounds: 2 });
|
|
91
|
+
assert.equal(result.fixed, false);
|
|
92
|
+
assert.equal(result.rounds, 2);
|
|
93
|
+
assert.equal(provider.calls.length, 2);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
test("runFixLoop: exhausting maxRounds without a passing check reports not fixed", async () => {
|
|
97
|
+
await withTmpDir(async (dir) => {
|
|
98
|
+
await writePkg(dir, { build: "node -e \"process.exit(1)\"" });
|
|
99
|
+
const provider = scriptedProvider([{ toolCalls: [{ id: "1", name: "run_check", input: {} }] }]);
|
|
100
|
+
const result = await runFixLoop({ ...baseOpts, cwd: dir, provider, maxRounds: 3 });
|
|
101
|
+
assert.equal(result.fixed, false);
|
|
102
|
+
assert.equal(result.rounds, 3);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
test("runFixLoop: model stopping without a tool call triggers one final verification check", async () => {
|
|
106
|
+
await withTmpDir(async (dir) => {
|
|
107
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"" });
|
|
108
|
+
const provider = scriptedProvider([{ text: "I believe this is fixed now.", toolCalls: [] }]);
|
|
109
|
+
const result = await runFixLoop({ ...baseOpts, cwd: dir, provider, maxRounds: 5 });
|
|
110
|
+
assert.equal(result.fixed, true);
|
|
111
|
+
assert.equal(result.rounds, 1);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
test("runFixLoop: read_file/write_file reject paths that escape the project root", async () => {
|
|
115
|
+
await withTmpDir(async (dir) => {
|
|
116
|
+
await writePkg(dir, {});
|
|
117
|
+
const provider = scriptedProvider([
|
|
118
|
+
{
|
|
119
|
+
toolCalls: [
|
|
120
|
+
{ id: "1", name: "write_file", input: { path: "../../etc/evil", content: "pwned" } },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
{ toolCalls: [{ id: "2", name: "run_check", input: {} }] },
|
|
124
|
+
]);
|
|
125
|
+
await runFixLoop({ ...baseOpts, cwd: dir, provider, maxRounds: 2 });
|
|
126
|
+
// The tool call result fed back to the model should carry the error,
|
|
127
|
+
// not a silently-succeeded write outside the project.
|
|
128
|
+
const secondCallMessages = provider.calls[1];
|
|
129
|
+
const toolMsg = secondCallMessages.find((m) => m.role === "tool");
|
|
130
|
+
assert.ok(toolMsg);
|
|
131
|
+
assert.equal(toolMsg.results[0].isError, true);
|
|
132
|
+
assert.match(toolMsg.results[0].content, /escapes project root/);
|
|
133
|
+
const { pathExists } = await import("../engine/ecosystems/types.js");
|
|
134
|
+
assert.equal(await pathExists(join(dir, "..", "..", "etc", "evil")), false);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
test("runFixLoop: opts.deps builds a multi-dependency prompt listing every upgrade and changelog", async () => {
|
|
138
|
+
await withTmpDir(async (dir) => {
|
|
139
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"" });
|
|
140
|
+
const provider = scriptedProvider([{ toolCalls: [{ id: "1", name: "run_check", input: {} }] }]);
|
|
141
|
+
const result = await runFixLoop({
|
|
142
|
+
...baseOpts,
|
|
143
|
+
cwd: dir,
|
|
144
|
+
provider,
|
|
145
|
+
maxRounds: 3,
|
|
146
|
+
deps: [
|
|
147
|
+
{ dep: "lodash", from: "4.17.20", to: "4.17.21", changelog: "lodash notes" },
|
|
148
|
+
{ dep: "axios", from: "1.2.0", to: "1.3.0", changelog: "axios notes" },
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
assert.equal(result.fixed, true);
|
|
152
|
+
const firstCallMessages = provider.calls[0];
|
|
153
|
+
const userMsg = firstCallMessages.find((m) => m.role === "user");
|
|
154
|
+
assert.ok(userMsg);
|
|
155
|
+
assert.match(userMsg.text, /lodash/);
|
|
156
|
+
assert.match(userMsg.text, /axios/);
|
|
157
|
+
assert.match(userMsg.text, /lodash notes/);
|
|
158
|
+
assert.match(userMsg.text, /axios notes/);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
test("runFixLoop: single-dep call sites (no opts.deps) keep producing the original singular prompt", async () => {
|
|
162
|
+
await withTmpDir(async (dir) => {
|
|
163
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"" });
|
|
164
|
+
const provider = scriptedProvider([{ toolCalls: [{ id: "1", name: "run_check", input: {} }] }]);
|
|
165
|
+
await runFixLoop({ ...baseOpts, cwd: dir, provider, maxRounds: 3 });
|
|
166
|
+
const firstCallMessages = provider.calls[0];
|
|
167
|
+
const userMsg = firstCallMessages.find((m) => m.role === "user");
|
|
168
|
+
assert.ok(userMsg);
|
|
169
|
+
assert.match(userMsg.text, /`lodash` from 4\.17\.20 to 4\.17\.21/);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
test("runFixLoop: hitting maxTokens stops the loop early and reports budgetExceeded", async () => {
|
|
173
|
+
await withTmpDir(async (dir) => {
|
|
174
|
+
await writePkg(dir, { build: "node -e \"process.exit(1)\"" });
|
|
175
|
+
// Each round costs 10+5=15 tokens (see scriptedProvider); cap at 20 so round 2 trips it.
|
|
176
|
+
const provider = scriptedProvider([
|
|
177
|
+
{ toolCalls: [{ id: "1", name: "run_check", input: {} }] },
|
|
178
|
+
{ toolCalls: [{ id: "2", name: "run_check", input: {} }] },
|
|
179
|
+
{ toolCalls: [{ id: "3", name: "run_check", input: {} }] },
|
|
180
|
+
]);
|
|
181
|
+
const result = await runFixLoop({ ...baseOpts, cwd: dir, provider, maxRounds: 5, maxTokens: 20 });
|
|
182
|
+
assert.equal(result.fixed, false);
|
|
183
|
+
assert.equal(result.budgetExceeded, true);
|
|
184
|
+
assert.equal(result.rounds, 2);
|
|
185
|
+
assert.equal(provider.calls.length, 2);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
test("runFixLoop: without maxTokens set, the loop runs to completion as before", async () => {
|
|
189
|
+
await withTmpDir(async (dir) => {
|
|
190
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"" });
|
|
191
|
+
const provider = scriptedProvider([{ toolCalls: [{ id: "1", name: "run_check", input: {} }] }]);
|
|
192
|
+
const result = await runFixLoop({ ...baseOpts, cwd: dir, provider, maxRounds: 3 });
|
|
193
|
+
assert.equal(result.fixed, true);
|
|
194
|
+
assert.equal(result.budgetExceeded, false);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
test("runFixLoop: list_dir and search_code let the agent inspect real project files", async () => {
|
|
198
|
+
await withTmpDir(async (dir) => {
|
|
199
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"" });
|
|
200
|
+
await mkdir(join(dir, "src"));
|
|
201
|
+
await writeFile(join(dir, "src", "util.js"), "export function pad(s) { return _.pad(s); }\n", "utf8");
|
|
202
|
+
const captured = [];
|
|
203
|
+
const provider = {
|
|
204
|
+
name: "stub",
|
|
205
|
+
model: "stub-model",
|
|
206
|
+
async send(_system, messages, _tools) {
|
|
207
|
+
const last = messages[messages.length - 1];
|
|
208
|
+
if (last.role === "tool") {
|
|
209
|
+
captured.push(last.results.map((r) => r.content).join("\n"));
|
|
210
|
+
}
|
|
211
|
+
if (captured.length === 0) {
|
|
212
|
+
return {
|
|
213
|
+
text: "",
|
|
214
|
+
toolCalls: [{ id: "1", name: "list_dir", input: { path: "src" } }],
|
|
215
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (captured.length === 1) {
|
|
219
|
+
return {
|
|
220
|
+
text: "",
|
|
221
|
+
toolCalls: [{ id: "2", name: "search_code", input: { query: "_.pad", path: "src" } }],
|
|
222
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
text: "",
|
|
227
|
+
toolCalls: [{ id: "3", name: "run_check", input: {} }],
|
|
228
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
229
|
+
};
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
const result = await runFixLoop({ ...baseOpts, cwd: dir, provider, maxRounds: 5 });
|
|
233
|
+
assert.equal(result.fixed, true);
|
|
234
|
+
assert.match(captured[0], /util\.js/);
|
|
235
|
+
assert.match(captured[1], /src\/util\.js:1: .*_\.pad/);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
// ── v0.6.0: free tiers ────────────────────────────────────────────────────
|
|
239
|
+
test("runFixLoop: builtin codemod fixes React 18→19 without calling the LLM", async () => {
|
|
240
|
+
await withTmpDir(async (dir) => {
|
|
241
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"", test: "node -e \"process.exit(0)\"" });
|
|
242
|
+
await mkdir(join(dir, "src"));
|
|
243
|
+
await writeFile(join(dir, "src", "index.tsx"), `import ReactDOM from 'react-dom';\nimport App from './App';\n\nReactDOM.render(<App />, document.getElementById('root'));\n`, "utf8");
|
|
244
|
+
const provider = scriptedProvider([{ toolCalls: [{ id: "1", name: "run_check", input: {} }] }]);
|
|
245
|
+
const result = await runFixLoop({
|
|
246
|
+
...baseOpts,
|
|
247
|
+
cwd: dir,
|
|
248
|
+
provider,
|
|
249
|
+
dep: "react-dom",
|
|
250
|
+
from: "18.3.1",
|
|
251
|
+
to: "19.2.0",
|
|
252
|
+
failureOutput: "TypeError: ReactDOM.render is not a function\n at src/index.tsx:4:1",
|
|
253
|
+
});
|
|
254
|
+
assert.equal(result.fixed, true);
|
|
255
|
+
assert.equal(result.fixedByTier, 1);
|
|
256
|
+
assert.equal(result.usage.inputTokens, 0);
|
|
257
|
+
assert.equal(result.usage.outputTokens, 0);
|
|
258
|
+
assert.equal(provider.calls.length, 0); // LLM never called
|
|
259
|
+
const { readFile } = await import("node:fs/promises");
|
|
260
|
+
const fixed = await readFile(join(dir, "src", "index.tsx"), "utf8");
|
|
261
|
+
assert.match(fixed, /createRoot/);
|
|
262
|
+
assert.doesNotMatch(fixed, /ReactDOM\.render/);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
test("runFixLoop: --no-free-tiers skips codemods and goes straight to the LLM", async () => {
|
|
266
|
+
await withTmpDir(async (dir) => {
|
|
267
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"", test: "node -e \"process.exit(0)\"" });
|
|
268
|
+
await mkdir(join(dir, "src"));
|
|
269
|
+
await writeFile(join(dir, "src", "index.tsx"), `import ReactDOM from 'react-dom';\nReactDOM.render(<App />, document.getElementById('root'));\n`, "utf8");
|
|
270
|
+
const provider = scriptedProvider([{ toolCalls: [{ id: "1", name: "run_check", input: {} }] }]);
|
|
271
|
+
const result = await runFixLoop({
|
|
272
|
+
...baseOpts,
|
|
273
|
+
cwd: dir,
|
|
274
|
+
provider,
|
|
275
|
+
dep: "react-dom",
|
|
276
|
+
from: "18.3.1",
|
|
277
|
+
to: "19.2.0",
|
|
278
|
+
failureOutput: "TypeError: ReactDOM.render is not a function\n at src/index.tsx:4:1",
|
|
279
|
+
noFreeTiers: true,
|
|
280
|
+
});
|
|
281
|
+
assert.equal(result.fixed, true);
|
|
282
|
+
assert.equal(provider.calls.length, 1); // LLM was called
|
|
283
|
+
assert.equal(result.fixedByTier, 4);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
test("runFixLoop: a successful LLM fix is cached, so the second identical run is free", async () => {
|
|
287
|
+
const failureOutput = "TypeError: _.padStart is not a function";
|
|
288
|
+
// Share one cache dir across two project dirs
|
|
289
|
+
const sharedCache = await mkdtemp(join(tmpdir(), "greenbump-shared-cache-"));
|
|
290
|
+
const prevCacheDir = process.env.GREENBUMP_CACHE_DIR;
|
|
291
|
+
process.env.GREENBUMP_CACHE_DIR = sharedCache;
|
|
292
|
+
resetCacheForTests();
|
|
293
|
+
async function runOnce(providerTurns) {
|
|
294
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-fixer-"));
|
|
295
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"", test: "node -e \"process.exit(0)\"" });
|
|
296
|
+
await writeFile(join(dir, "app.js"), "old content", "utf8");
|
|
297
|
+
const provider = scriptedProvider(providerTurns);
|
|
298
|
+
try {
|
|
299
|
+
const result = await runFixLoop({ ...baseOpts, cwd: dir, provider, failureOutput });
|
|
300
|
+
return { result, provider };
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
await rm(dir, { recursive: true, force: true });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
// First run: LLM writes the fix, loop verifies → learned into cache
|
|
308
|
+
const first = await runOnce([
|
|
309
|
+
{
|
|
310
|
+
toolCalls: [
|
|
311
|
+
{ id: "1", name: "write_file", input: { path: "app.js", content: "new content" } },
|
|
312
|
+
{ id: "2", name: "run_check", input: {} },
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
]);
|
|
316
|
+
assert.equal(first.result.fixed, true);
|
|
317
|
+
assert.equal(first.result.fixedByTier, 4);
|
|
318
|
+
assert.equal(first.provider.calls.length, 1);
|
|
319
|
+
// Second run (different project, same failure): cache replay, zero LLM calls
|
|
320
|
+
const second = await runOnce([{ toolCalls: [{ id: "1", name: "run_check", input: {} }] }]);
|
|
321
|
+
assert.equal(second.result.fixed, true);
|
|
322
|
+
assert.equal(second.result.fixedByTier, 3);
|
|
323
|
+
assert.equal(second.result.cacheHit, true);
|
|
324
|
+
assert.equal(second.result.usage.inputTokens, 0);
|
|
325
|
+
assert.equal(second.provider.calls.length, 0);
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
if (prevCacheDir === undefined)
|
|
329
|
+
delete process.env.GREENBUMP_CACHE_DIR;
|
|
330
|
+
else
|
|
331
|
+
process.env.GREENBUMP_CACHE_DIR = prevCacheDir;
|
|
332
|
+
resetCacheForTests();
|
|
333
|
+
await rm(sharedCache, { recursive: true, force: true });
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
// ── v0.6.0: interactive mode ──────────────────────────────────────────────
|
|
337
|
+
test("runFixLoop: interactive reject prevents the write and tells the agent", async () => {
|
|
338
|
+
await withTmpDir(async (dir) => {
|
|
339
|
+
await writePkg(dir, { build: "node -e \"process.exit(1)\"" });
|
|
340
|
+
await writeFile(join(dir, "app.js"), "old content", "utf8");
|
|
341
|
+
const provider = scriptedProvider([
|
|
342
|
+
{ toolCalls: [{ id: "1", name: "write_file", input: { path: "app.js", content: "new content" } }] },
|
|
343
|
+
{ toolCalls: [{ id: "2", name: "run_check", input: {} }] },
|
|
344
|
+
]);
|
|
345
|
+
const result = await runFixLoop({
|
|
346
|
+
...baseOpts,
|
|
347
|
+
cwd: dir,
|
|
348
|
+
provider,
|
|
349
|
+
maxRounds: 2,
|
|
350
|
+
onFixSuggestion: async () => ({ action: "reject" }),
|
|
351
|
+
});
|
|
352
|
+
assert.equal(result.fixed, false);
|
|
353
|
+
// The file must NOT have been written
|
|
354
|
+
const { readFile } = await import("node:fs/promises");
|
|
355
|
+
assert.equal(await readFile(join(dir, "app.js"), "utf8"), "old content");
|
|
356
|
+
// The rejection was fed back to the model
|
|
357
|
+
const secondCall = provider.calls[1];
|
|
358
|
+
const toolMsg = secondCall.find((m) => m.role === "tool");
|
|
359
|
+
assert.ok(toolMsg);
|
|
360
|
+
assert.match(toolMsg.results[0].content, /REJECTED/);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
test("runFixLoop: interactive edit replaces the AI's content with the user's", async () => {
|
|
364
|
+
await withTmpDir(async (dir) => {
|
|
365
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"" });
|
|
366
|
+
await writeFile(join(dir, "app.js"), "old content", "utf8");
|
|
367
|
+
const provider = scriptedProvider([
|
|
368
|
+
{ toolCalls: [{ id: "1", name: "write_file", input: { path: "app.js", content: "AI content" } }] },
|
|
369
|
+
{ toolCalls: [{ id: "2", name: "run_check", input: {} }] },
|
|
370
|
+
]);
|
|
371
|
+
const result = await runFixLoop({
|
|
372
|
+
...baseOpts,
|
|
373
|
+
cwd: dir,
|
|
374
|
+
provider,
|
|
375
|
+
maxRounds: 2,
|
|
376
|
+
onFixSuggestion: async () => ({ action: "edit", content: "user-edited content" }),
|
|
377
|
+
});
|
|
378
|
+
assert.equal(result.fixed, true);
|
|
379
|
+
const { readFile } = await import("node:fs/promises");
|
|
380
|
+
assert.equal(await readFile(join(dir, "app.js"), "utf8"), "user-edited content");
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
test("runFixLoop: interactive accept applies the edit as before", async () => {
|
|
384
|
+
await withTmpDir(async (dir) => {
|
|
385
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"" });
|
|
386
|
+
await writeFile(join(dir, "app.js"), "old content", "utf8");
|
|
387
|
+
const provider = scriptedProvider([
|
|
388
|
+
{ toolCalls: [{ id: "1", name: "write_file", input: { path: "app.js", content: "new content" } }] },
|
|
389
|
+
{ toolCalls: [{ id: "2", name: "run_check", input: {} }] },
|
|
390
|
+
]);
|
|
391
|
+
const seen = [];
|
|
392
|
+
const result = await runFixLoop({
|
|
393
|
+
...baseOpts,
|
|
394
|
+
cwd: dir,
|
|
395
|
+
provider,
|
|
396
|
+
maxRounds: 2,
|
|
397
|
+
onFixSuggestion: async (s) => {
|
|
398
|
+
seen.push(s.path);
|
|
399
|
+
assert.ok(s.diff.includes("-old content"));
|
|
400
|
+
assert.ok(s.diff.includes("+new content"));
|
|
401
|
+
return { action: "accept" };
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
assert.equal(result.fixed, true);
|
|
405
|
+
assert.deepEqual(seen, ["app.js"]);
|
|
406
|
+
const { readFile } = await import("node:fs/promises");
|
|
407
|
+
assert.equal(await readFile(join(dir, "app.js"), "utf8"), "new content");
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
//# sourceMappingURL=fixer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixer.test.js","sourceRoot":"","sources":["../../src/agent/fixer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
export function createInteractiveHandler(opts) {
|
|
4
|
+
const maxDiffLines = opts?.maxDiffLines ?? 80;
|
|
5
|
+
const autoAcceptUnder = opts?.autoAcceptUnderLines ?? 0;
|
|
6
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
7
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, (a) => resolve(a.trim())));
|
|
8
|
+
let acceptAll = false;
|
|
9
|
+
const handler = async (suggestion) => {
|
|
10
|
+
if (acceptAll)
|
|
11
|
+
return { action: "accept" };
|
|
12
|
+
const diffLines = suggestion.diff.split("\n");
|
|
13
|
+
const changedLines = diffLines.filter((l) => l.startsWith("+") || l.startsWith("-")).length;
|
|
14
|
+
if (autoAcceptUnder > 0 && changedLines <= autoAcceptUnder) {
|
|
15
|
+
return { action: "accept" };
|
|
16
|
+
}
|
|
17
|
+
process.stderr.write("\n" + pc.bold(pc.cyan("🤖 AI proposed edit")) + pc.dim(` — ${suggestion.path}`) + "\n");
|
|
18
|
+
const shown = diffLines.slice(0, maxDiffLines);
|
|
19
|
+
for (const line of shown) {
|
|
20
|
+
if (line.startsWith("+"))
|
|
21
|
+
process.stderr.write(pc.green(line) + "\n");
|
|
22
|
+
else if (line.startsWith("-"))
|
|
23
|
+
process.stderr.write(pc.red(line) + "\n");
|
|
24
|
+
else
|
|
25
|
+
process.stderr.write(pc.dim(line) + "\n");
|
|
26
|
+
}
|
|
27
|
+
if (diffLines.length > maxDiffLines) {
|
|
28
|
+
process.stderr.write(pc.dim(`… (${diffLines.length - maxDiffLines} more diff lines)\n`));
|
|
29
|
+
}
|
|
30
|
+
for (;;) {
|
|
31
|
+
const answer = (await ask(pc.bold("Apply? ") +
|
|
32
|
+
`[${pc.green("y")}]es / [${pc.red("n")}]o / [${pc.yellow("e")}]dit / [${pc.blue("s")}]kip / [${pc.magenta("a")}]ll: `)).toLowerCase();
|
|
33
|
+
switch (answer) {
|
|
34
|
+
case "y":
|
|
35
|
+
case "yes":
|
|
36
|
+
case "":
|
|
37
|
+
return { action: "accept" };
|
|
38
|
+
case "n":
|
|
39
|
+
case "no":
|
|
40
|
+
return { action: "reject" };
|
|
41
|
+
case "s":
|
|
42
|
+
case "skip":
|
|
43
|
+
return { action: "skip" };
|
|
44
|
+
case "a":
|
|
45
|
+
case "all":
|
|
46
|
+
acceptAll = true;
|
|
47
|
+
return { action: "accept" };
|
|
48
|
+
case "e":
|
|
49
|
+
case "edit": {
|
|
50
|
+
process.stderr.write(pc.dim("Enter new content. End with a single '.' on its own line:\n"));
|
|
51
|
+
const lines = [];
|
|
52
|
+
for (;;) {
|
|
53
|
+
const line = await ask("");
|
|
54
|
+
if (line === ".")
|
|
55
|
+
break;
|
|
56
|
+
lines.push(line);
|
|
57
|
+
}
|
|
58
|
+
if (lines.length === 0) {
|
|
59
|
+
process.stderr.write(pc.yellow("empty input — edit cancelled, asking again\n"));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
return { action: "edit", content: lines.join("\n") + "\n" };
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
process.stderr.write(pc.dim("unknown choice — y/n/e/s/a\n"));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
handler,
|
|
71
|
+
close: () => rl.close(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Print the post-run summary of an interactive session. */
|
|
75
|
+
export function printInteractiveSummary(stats) {
|
|
76
|
+
const total = stats.accepted + stats.rejected + stats.edited + stats.skipped;
|
|
77
|
+
if (total === 0)
|
|
78
|
+
return;
|
|
79
|
+
process.stderr.write(pc.dim(`\ninteractive: ${stats.accepted} accepted, ${stats.edited} edited, ${stats.rejected} rejected, ${stats.skipped} skipped\n`));
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=interactive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive.js","sourceRoot":"","sources":["../../src/cli/interactive.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,9 @@ import { exitCodeFor, overallExitCode } from "./exitcode.js";
|
|
|
12
12
|
import { buildReport, writeReport } from "./report.js";
|
|
13
13
|
import { detectOutdatedAll, WORKSPACE_ROOT } from "./engine/workspace.js";
|
|
14
14
|
import { detectPackageManager } from "./engine/pm.js";
|
|
15
|
+
import { createInteractiveHandler } from "./cli/interactive.js";
|
|
16
|
+
import { listBuiltinCodemods } from "./engine/fixer/patterns.js";
|
|
17
|
+
import { getCache } from "./engine/cache/manager.js";
|
|
15
18
|
const program = new Command();
|
|
16
19
|
program
|
|
17
20
|
.name("greenbump")
|
|
@@ -38,6 +41,17 @@ program
|
|
|
38
41
|
.option("--fail-fast", "abort a batch on the first hard failure instead of continuing")
|
|
39
42
|
.option("--workspace <path>", "disambiguate a dep that's outdated at different versions in multiple workspace packages")
|
|
40
43
|
.option("--scan", "list outdated dependencies without upgrading (read-only mode)")
|
|
44
|
+
.option("--sandbox", "run tests in isolated Docker container (requires Docker)")
|
|
45
|
+
.option("--services <services>", "comma-separated list of services to start (postgres,redis,mongodb)")
|
|
46
|
+
.option("--keep-container", "keep Docker container after run for debugging")
|
|
47
|
+
.option("--detect-regressions", "check for performance regressions after upgrade")
|
|
48
|
+
.option("-i, --interactive", "confirm each AI-proposed edit before applying (y/n/e/s/a)")
|
|
49
|
+
.option("--no-free-tiers", "skip codemods/patterns/cache and go straight to the LLM")
|
|
50
|
+
.option("--no-cache", "disable changelog + LLM-response caching")
|
|
51
|
+
.option("--list-codemods", "list built-in free codemods and exit")
|
|
52
|
+
.option("--cache-stats", "show cache statistics and exit")
|
|
53
|
+
.option("--cache-clear [category]", "clear the cache (changelogs, llm-fixes, patterns) and exit")
|
|
54
|
+
.option("--no-ast-analysis", "disable post-fix API surface analysis")
|
|
41
55
|
.action(async (deps, opts) => {
|
|
42
56
|
if (opts.listProviders) {
|
|
43
57
|
console.log("Built-in provider presets:\n" + listProviders());
|
|
@@ -45,6 +59,28 @@ program
|
|
|
45
59
|
console.log("Any other OpenAI-compatible service: --base-url <url> --model <id> --api-key <key>");
|
|
46
60
|
process.exit(0);
|
|
47
61
|
}
|
|
62
|
+
if (opts.listCodemods) {
|
|
63
|
+
console.log("Built-in free codemods (applied before any LLM call):\n" + listBuiltinCodemods());
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
if (opts.cacheStats) {
|
|
67
|
+
const cache = getCache();
|
|
68
|
+
await cache.init();
|
|
69
|
+
const stats = await cache.stats();
|
|
70
|
+
console.log(`Cache: ${stats.entries} entries, ${(stats.sizeBytes / 1024 / 1024).toFixed(1)} MB`);
|
|
71
|
+
for (const [cat, s] of Object.entries(stats.byCategory)) {
|
|
72
|
+
console.log(` ${cat}: ${s.entries} entries, ${(s.sizeBytes / 1024).toFixed(1)} KB`);
|
|
73
|
+
}
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
if (opts.cacheClear !== undefined) {
|
|
77
|
+
const cache = getCache();
|
|
78
|
+
await cache.init();
|
|
79
|
+
const category = typeof opts.cacheClear === "string" ? opts.cacheClear : undefined;
|
|
80
|
+
await cache.clear(category);
|
|
81
|
+
console.log(pc.green(`✓ Cleared cache${category ? ` (${category})` : ""}.`));
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
48
84
|
if (opts.listEcosystems) {
|
|
49
85
|
console.log("Supported dependency ecosystems:\n" + listEcosystems());
|
|
50
86
|
process.exit(0);
|
|
@@ -97,6 +133,11 @@ program
|
|
|
97
133
|
console.error(` ${elapsed} ${pc.dim(m)}`);
|
|
98
134
|
};
|
|
99
135
|
const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
|
|
136
|
+
// Interactive mode: wire up the readline-based confirmation handler.
|
|
137
|
+
let interactive;
|
|
138
|
+
if (opts.interactive) {
|
|
139
|
+
interactive = createInteractiveHandler();
|
|
140
|
+
}
|
|
100
141
|
const baseOpts = {
|
|
101
142
|
cwd,
|
|
102
143
|
ecosystem: opts.ecosystem,
|
|
@@ -110,6 +151,15 @@ program
|
|
|
110
151
|
maxTokens: opts.maxTokens ? parseInt(opts.maxTokens, 10) : undefined,
|
|
111
152
|
noGit: opts.git === false,
|
|
112
153
|
onLog: log,
|
|
154
|
+
sandbox: opts.sandbox,
|
|
155
|
+
services: opts.services ? opts.services.split(",").map((s) => s.trim()) : undefined,
|
|
156
|
+
keepContainer: opts.keepContainer,
|
|
157
|
+
detectRegressions: opts.detectRegressions,
|
|
158
|
+
noFreeTiers: opts.freeTiers === false,
|
|
159
|
+
noCache: opts.cache === false,
|
|
160
|
+
interactive: opts.interactive,
|
|
161
|
+
onFixSuggestion: interactive?.handler,
|
|
162
|
+
astAnalysis: opts.astAnalysis !== false,
|
|
113
163
|
};
|
|
114
164
|
const isBatch = opts.all || deps.length > 1;
|
|
115
165
|
try {
|
|
@@ -128,6 +178,7 @@ program
|
|
|
128
178
|
if (opts.reportFile) {
|
|
129
179
|
await writeReport(opts.reportFile, buildReport(results));
|
|
130
180
|
}
|
|
181
|
+
interactive?.close();
|
|
131
182
|
process.exit(overallExitCode(results));
|
|
132
183
|
}
|
|
133
184
|
const summary = await run({
|
|
@@ -143,9 +194,11 @@ program
|
|
|
143
194
|
if (opts.reportFile) {
|
|
144
195
|
await writeReport(opts.reportFile, buildReport([summary]));
|
|
145
196
|
}
|
|
197
|
+
interactive?.close();
|
|
146
198
|
process.exit(exitCodeFor(summary));
|
|
147
199
|
}
|
|
148
200
|
catch (err) {
|
|
201
|
+
interactive?.close();
|
|
149
202
|
console.error("");
|
|
150
203
|
if (err instanceof RunError) {
|
|
151
204
|
console.error(pc.red(`✗ ${err.message}`));
|