greenbump 0.7.0 → 0.9.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 (121) hide show
  1. package/README.md +73 -15
  2. package/README.zh-CN.md +37 -4
  3. package/dist/action/github.test.js +37 -0
  4. package/dist/action/github.test.js.map +1 -0
  5. package/dist/action/main.js +19 -4
  6. package/dist/action/main.js.map +1 -1
  7. package/dist/agent/calllog.js +23 -0
  8. package/dist/agent/calllog.js.map +1 -0
  9. package/dist/agent/calllog.test.js +77 -0
  10. package/dist/agent/calllog.test.js.map +1 -0
  11. package/dist/agent/fixer.js +246 -29
  12. package/dist/agent/fixer.js.map +1 -1
  13. package/dist/agent/fixer.test.js +299 -0
  14. package/dist/agent/fixer.test.js.map +1 -1
  15. package/dist/agent/guard.js +37 -0
  16. package/dist/agent/guard.js.map +1 -0
  17. package/dist/agent/guard.test.js +48 -0
  18. package/dist/agent/guard.test.js.map +1 -0
  19. package/dist/agent/provider-anthropic.js +2 -2
  20. package/dist/agent/provider-anthropic.js.map +1 -1
  21. package/dist/agent/provider-openai.js +2 -2
  22. package/dist/agent/provider-openai.js.map +1 -1
  23. package/dist/benchmarks/run.js +0 -0
  24. package/dist/benchmarks/run.js.map +1 -0
  25. package/dist/cli/interactive.js +46 -11
  26. package/dist/cli/interactive.js.map +1 -1
  27. package/dist/cli.js +37 -7
  28. package/dist/cli.js.map +1 -1
  29. package/dist/engine/analyzers/changelog-parser.js +51 -2
  30. package/dist/engine/analyzers/changelog-parser.js.map +1 -1
  31. package/dist/engine/batch.js +54 -10
  32. package/dist/engine/batch.js.map +1 -1
  33. package/dist/engine/cache/manager.js +19 -1
  34. package/dist/engine/cache/manager.js.map +1 -1
  35. package/dist/engine/cache/manager.test.js +17 -0
  36. package/dist/engine/cache/manager.test.js.map +1 -1
  37. package/dist/engine/change-detector.js +52 -23
  38. package/dist/engine/change-detector.js.map +1 -1
  39. package/dist/engine/change-detector.test.js +72 -0
  40. package/dist/engine/change-detector.test.js.map +1 -0
  41. package/dist/engine/changelog.js +22 -2
  42. package/dist/engine/changelog.js.map +1 -1
  43. package/dist/engine/changelog.test.js +31 -0
  44. package/dist/engine/changelog.test.js.map +1 -0
  45. package/dist/engine/checks.js +10 -4
  46. package/dist/engine/checks.js.map +1 -1
  47. package/dist/engine/context/optimizer.js +14 -44
  48. package/dist/engine/context/optimizer.js.map +1 -1
  49. package/dist/engine/context/optimizer.test.js +23 -1
  50. package/dist/engine/context/optimizer.test.js.map +1 -1
  51. package/dist/engine/ecosystems/cargo.js +2 -1
  52. package/dist/engine/ecosystems/cargo.js.map +1 -1
  53. package/dist/engine/ecosystems/conan.js +2 -1
  54. package/dist/engine/ecosystems/conan.js.map +1 -1
  55. package/dist/engine/ecosystems/elm.js +2 -1
  56. package/dist/engine/ecosystems/elm.js.map +1 -1
  57. package/dist/engine/ecosystems/gradle.js +2 -1
  58. package/dist/engine/ecosystems/gradle.js.map +1 -1
  59. package/dist/engine/ecosystems/http.js +18 -0
  60. package/dist/engine/ecosystems/http.js.map +1 -0
  61. package/dist/engine/ecosystems/maven.js +2 -1
  62. package/dist/engine/ecosystems/maven.js.map +1 -1
  63. package/dist/engine/ecosystems/swiftpm.js +2 -1
  64. package/dist/engine/ecosystems/swiftpm.js.map +1 -1
  65. package/dist/engine/exec.js +37 -7
  66. package/dist/engine/exec.js.map +1 -1
  67. package/dist/engine/exec.test.js +36 -0
  68. package/dist/engine/exec.test.js.map +1 -0
  69. package/dist/engine/fixer/patterns.js +55 -18
  70. package/dist/engine/fixer/patterns.js.map +1 -1
  71. package/dist/engine/fixer/patterns.test.js +95 -1
  72. package/dist/engine/fixer/patterns.test.js.map +1 -1
  73. package/dist/engine/git.js +38 -16
  74. package/dist/engine/git.js.map +1 -1
  75. package/dist/engine/git.test.js +51 -1
  76. package/dist/engine/git.test.js.map +1 -1
  77. package/dist/engine/perf/metrics.js +44 -57
  78. package/dist/engine/perf/metrics.js.map +1 -1
  79. package/dist/engine/run.js +141 -15
  80. package/dist/engine/run.js.map +1 -1
  81. package/dist/engine/run.test.js +65 -1
  82. package/dist/engine/run.test.js.map +1 -1
  83. package/dist/engine/sandbox/docker-manager.js +52 -26
  84. package/dist/engine/sandbox/docker-manager.js.map +1 -1
  85. package/dist/engine/sandbox/template-generator.js +0 -4
  86. package/dist/engine/sandbox/template-generator.js.map +1 -1
  87. package/dist/engine/sandbox/template-generator.test.js +16 -0
  88. package/dist/engine/sandbox/template-generator.test.js.map +1 -0
  89. package/dist/engine/scan/benchmark-crosscheck.test.js +116 -0
  90. package/dist/engine/scan/benchmark-crosscheck.test.js.map +1 -0
  91. package/dist/engine/scan/detect.js +96 -0
  92. package/dist/engine/scan/detect.js.map +1 -0
  93. package/dist/engine/scan/impact.js +128 -0
  94. package/dist/engine/scan/impact.js.map +1 -0
  95. package/dist/engine/scan/impact.test.js +217 -0
  96. package/dist/engine/scan/impact.test.js.map +1 -0
  97. package/dist/engine/scan/indexer.js +129 -0
  98. package/dist/engine/scan/indexer.js.map +1 -0
  99. package/dist/engine/scan/indexer.test.js +149 -0
  100. package/dist/engine/scan/indexer.test.js.map +1 -0
  101. package/dist/engine/scan/probes.js +92 -0
  102. package/dist/engine/scan/probes.js.map +1 -0
  103. package/dist/engine/scan/probes.test.js +100 -0
  104. package/dist/engine/scan/probes.test.js.map +1 -0
  105. package/dist/engine/verify.js +41 -58
  106. package/dist/engine/verify.js.map +1 -1
  107. package/dist/engine/verify.test.js +49 -0
  108. package/dist/engine/verify.test.js.map +1 -0
  109. package/dist/format.js +96 -0
  110. package/dist/format.js.map +1 -1
  111. package/dist/format.test.js +53 -0
  112. package/dist/format.test.js.map +1 -0
  113. package/dist/pr.test.js +48 -0
  114. package/dist/pr.test.js.map +1 -0
  115. package/package.json +4 -4
  116. package/dist/engine/dep-graph.js +0 -101
  117. package/dist/engine/dep-graph.js.map +0 -1
  118. package/dist/engine/staged-fix.js +0 -108
  119. package/dist/engine/staged-fix.js.map +0 -1
  120. package/dist/engine/stages.js +0 -125
  121. package/dist/engine/stages.js.map +0 -1
package/README.md CHANGED
@@ -133,7 +133,7 @@ then re-ran your test suite to confirm the fix — all inside the fix loop shown
133
133
  - 🐳 **Docker sandbox mode** (`--sandbox`) - Run tests in clean containers
134
134
  - 🗄️ **Database integration testing** - Auto-start PostgreSQL, MySQL, Redis, MongoDB
135
135
  - 📊 **Performance regression detection** - Track build time, bundle size, memory usage
136
- - 🎯 **95%+ accuracy** with production-grade verification
136
+ - 🎯 Extra assurance on top of your local build/test run — the sandbox reruns the same checks in a clean container
137
137
 
138
138
  ### 🌍 Multi-Ecosystem Support
139
139
  - **20+ ecosystems**: npm, pip, cargo, maven, poetry, gradle, and more
@@ -143,6 +143,8 @@ then re-ran your test suite to confirm the fix — all inside the fix loop shown
143
143
  ### 🛡️ Safe by Default
144
144
  - **Git branch isolation** - Never touches your main branch
145
145
  - **No auto-merge** - Always requires human review
146
+ - **Hard tool guardrails** - The agent physically cannot write dependency manifests/lockfiles or read secrets (`.env`, keys, credentials) — enforced in the tool layer, not just the prompt
147
+ - **Clear rollback** - A failed fix prints the exact commands to discard the branch and partial edits
146
148
  - **Bounded cost** - `--max-rounds` caps token spend
147
149
  - **Bring your own key** - You control costs, nothing phones home
148
150
 
@@ -167,7 +169,7 @@ then re-ran your test suite to confirm the fix — all inside the fix loop shown
167
169
  - ✅ Multi-ecosystem support
168
170
  - ✅ Actual code fixing
169
171
  - ✅ Model flexibility
170
- - ✅ Production-grade verification (sandbox + databases + performance)
172
+ - ✅ Isolated verification (optional sandbox + database services)
171
173
 
172
174
  ---
173
175
 
@@ -194,10 +196,50 @@ npx greenbump eslint --to 9.15.0
194
196
  # Let greenbump pick the most-outdated dependency
195
197
  npx greenbump
196
198
 
197
- # List outdated dependencies (read-only)
199
+ # Preview upgrade impact — deterministic, no API key, no LLM calls
198
200
  npx greenbump --scan
199
201
  ```
200
202
 
203
+ ### Deterministic preview: `--scan`
204
+
205
+ `--scan` answers one question **without spending a token**: *if I upgrade these
206
+ dependencies, which of my files break?*
207
+
208
+ ```bash
209
+ npx greenbump --scan
210
+ ```
211
+
212
+ ```
213
+ 🌱 greenbump scan — 1 outdated dependency, 1 with known breaking changes
214
+
215
+ zod 3.23.8 → 4.6.7 [breaking: 2 changes — 1 files, 1 locations]
216
+ 1. [high] error.errors → error.issues (Zod 4) (builtin-codemod)
217
+ fix: .error.issues
218
+ src/validate.js:6: return r.error.errors[0].message;
219
+ → 1 location in 1 file
220
+ 2. [high] z.string().email()/.url()/.uuid() → z.email()/z.url()/z.uuid() (Zod 4)
221
+ → 0 locations in 0 files
222
+
223
+ Summary: 1 dependency with breaking changes touch 1 file, 1 location.
224
+ Deterministic preview: no LLM calls, no network — sources: built-in codemods, changelog cache.
225
+ ```
226
+
227
+ Notes on what you're seeing:
228
+
229
+ - **No API key required.** Scan reads its built-in codemod registry and your local
230
+ changelog cache only. It never calls a model and never touches the network.
231
+ - **The count that matters is "how many touch your code"**, not how many exist. Breaking
232
+ changes listed with `0 locations` do not affect this repository.
233
+ - **`unknown` means unknown.** If a dependency has no built-in codemod and no cached
234
+ analysis, scan says so instead of guessing. Run a real upgrade once
235
+ (`npx greenbump <dep>`) and its changelog digest is cached — the next `--scan` is
236
+ free forever.
237
+ - **`--scan --json`** emits the same data as structured JSON (`schemaVersion: 1`) for CI
238
+ or dashboards.
239
+ - **`--scan --no-impact`** restores the plain outdated-dependency list.
240
+ - Results are **file:line precise** and read-only: nothing is modified, nothing is
241
+ committed.
242
+
201
243
  ### Advanced Usage
202
244
 
203
245
  ```bash
@@ -232,7 +274,8 @@ npx greenbump react react-dom --group react-upgrade
232
274
  | `--to <version>` | Target version (default: `latest`). |
233
275
  | `--ecosystem <id>` | Dependency ecosystem (`npm`, `poetry`, `cargo`, `maven`, …). Auto-detected if omitted. |
234
276
  | `--list-ecosystems` | List every supported ecosystem and exit. |
235
- | `--scan` | List outdated dependencies without upgrading (read-only mode). |
277
+ | `--scan` | Preview upgrade impact without upgrading: lists outdated dependencies **and** which files/lines each known breaking change would touch. Deterministic — no LLM, no API key, no network. |
278
+ | `--no-impact` | With `--scan`, skip impact analysis and just list outdated dependencies. |
236
279
  | `--all` | Upgrade every outdated dependency found. |
237
280
  | `--group <name>` | Combine multiple deps into one branch/PR. |
238
281
 
@@ -249,6 +292,7 @@ npx greenbump react react-dom --group react-upgrade
249
292
  |---|---|
250
293
  | `--provider <name>` | Model provider preset (`anthropic`, `openai`, `deepseek`, `groq`, …). |
251
294
  | `--model <model>` | Model id for the fix agent (default: per provider). |
295
+ | `--digest-model <model>` | Cheaper model id for utility tasks (changelog digest); default: same as `--model`. |
252
296
  | `--list-providers` | List built-in provider presets and exit. |
253
297
  | `--max-rounds <n>` | Cap fix-loop rounds / token spend (default: `15`). |
254
298
  | `--max-tokens <n>` | Hard cap on total tokens spent; stops and flags for review on overrun. |
@@ -274,7 +318,7 @@ npx greenbump react react-dom --group react-upgrade
274
318
  | `--cache-stats` | Show cache entry count, size, and per-category breakdown. |
275
319
  | `--cache-clear [category]` | Clear cached entries (`changelogs`, `llm-fixes`, `patterns`; all if omitted). |
276
320
  | `--stats [days]` | Show a usage/cost-savings report for the last N days (default 30) and exit. |
277
- | `--json` | With `--stats`, print the raw JSON summary instead of the formatted report. |
321
+ | `--json` | With `--stats` or `--scan`, print the raw JSON output instead of the formatted report. |
278
322
 
279
323
  #### How the tiered fix strategy keeps costs down
280
324
 
@@ -283,8 +327,8 @@ When an upgrade breaks the build, greenbump tries four escalating fix tiers —
283
327
  | Tier | Strategy | Cost |
284
328
  |---|---|---|
285
329
  | 1 | **Builtin codemods** — regex transforms for well-known breakages (React 18→19, Express 4→5, Zod 3→4, React Router 5→6, Mongoose, Lodash, Node util.\*, …) | 0 tokens |
286
- | 2 | **Learned patterns** — past successful fixes distilled into reusable rules | 0 tokens |
287
- | 3 | **Cached LLM fixes** — identical failure context replays a previous fix | 0 tokens |
330
+ | 2 | **Learned patterns** — extension point for hand-seeded fix rules (not auto-populated yet; see note below) | 0 tokens |
331
+ | 3 | **Cached LLM fixes** — replays a previous fix only when the failure context AND the candidate files' contents both match | 0 tokens |
288
332
  | 4 | **LLM fix loop** — the full agent, only when tiers 1–3 miss | paid |
289
333
 
290
334
  24 builtin codemods ship out of the box (`greenbump --list-codemods` for the full list) —
@@ -294,7 +338,10 @@ edit, so tier 1 never silently misapplies a risky change.
294
338
 
295
339
  Verified end-to-end: Express 4→5 (`app.del`→`app.delete`) and Zod 3→4 (`error.errors`→
296
340
  `error.issues`) are both fixed by tier 1 with **0 input / 0 output tokens**. Successful
297
- LLM fixes are learned into the cache, so repeat failures across projects are free too.
341
+ LLM fixes are stored in the tier-3 cache, so a repeat of the *same* failure on the *same*
342
+ code is free too. (Tier 2 exists as an extension point for hand-seeded patterns — the
343
+ auto-distillation of LLM fixes into generic rules is not implemented yet, and the tier
344
+ table above says so.)
298
345
 
299
346
  #### See what it's actually saving you
300
347
 
@@ -310,10 +357,23 @@ override with `GREENBUMP_STATS_DIR`) — no network calls, nothing leaves your m
310
357
  spent, and an *estimated* dollar figure saved by the free tiers (clearly labeled as an
311
358
  estimate, not a bill).
312
359
 
313
- ### Git
360
+ Every individual LLM call is also recorded to `~/.greenbump/llm-calls.jsonl` with its
361
+ feature (`fix-loop` / `changelog-digest`), model, input/output tokens, latency, retry
362
+ count, and success/failure — so cost is attributable per task, not just per run.
363
+
364
+ #### Model routing and output caps
365
+
366
+ Not every task needs the strong model. The changelog digest is a simple extraction
367
+ task: route it to a cheaper model with `--digest-model` (e.g.
368
+ `greenbump react --digest-model gpt-4o-mini`) while the fix agent keeps `--model`.
369
+ Each call has a bounded output cap (8000 tokens for the fix agent, 2000 for the
370
+ digest), and the digest result is cached per upgrade path so it's paid at most once.
371
+
372
+ ### Git & Isolation
314
373
 
315
374
  | Flag | Description |
316
375
  |---|---|
376
+ | `--worktree` | Run the upgrade in an isolated temporary git worktree to keep the primary directory clean. |
317
377
  | `--no-git` | Operate in place instead of on a new branch. |
318
378
  | `--pr-body` | Print a ready-to-paste PR body. |
319
379
  | `--report-file <path>` | Write a JSON report of the run(s) to this path. |
@@ -409,7 +469,6 @@ graph TB
409
469
  subgraph "Verification Layer"
410
470
  STATIC[Static Analysis<br/>TypeScript + ESLint]
411
471
  CHANGE[Change Detection<br/>Test mods, large deletions]
412
- GRAPH[Dependency Graph<br/>Multi-stage fixes]
413
472
  end
414
473
 
415
474
  subgraph "Sandbox Layer (Optional)"
@@ -442,8 +501,7 @@ graph TB
442
501
  TOOLS --> CHECK
443
502
 
444
503
  STATIC --> CHANGE
445
- CHANGE --> GRAPH
446
- GRAPH --> DOCKER
504
+ CHANGE --> DOCKER
447
505
 
448
506
  DOCKER --> SERVICES
449
507
  SERVICES --> CONTAINER
@@ -467,7 +525,7 @@ graph TB
467
525
  | **Isolation** | Git Branch Manager, Baseline Runner | Isolate work on feature branch, verify starting state |
468
526
  | **Upgrade** | Package Manager, Verification Runner | Install new version and run checks |
469
527
  | **AI Fix** | LLM Agent, Tools, Changelog Fetcher | Automatically fix breaking changes with AI |
470
- | **Verification** | Static Analysis, Change Detection, Dependency Graph | Multi-layer validation (types, lints, suspicious changes) |
528
+ | **Verification** | Static Analysis, Change Detection | Multi-layer validation (types, lints, suspicious changes) |
471
529
  | **Sandbox** | Docker Builder, Service Manager, Container Runner | Optional isolated testing with database services |
472
530
  | **Performance** | Performance Tracker, Regression Detector | Optional performance monitoring and regression alerts |
473
531
  | **Output** | Git Commit, PR Body Generator | Commit changes and generate PR description |
@@ -633,7 +691,7 @@ flowchart LR
633
691
  ```
634
692
 
635
693
  Each layer is independently swappable: pick any ecosystem adapter, any model provider, and
636
- opt into the sandbox only when you need production-grade isolation.
694
+ opt into the sandbox when you want the checks rerun in a clean container.
637
695
 
638
696
  ---
639
697
 
@@ -749,7 +807,7 @@ Full example with all inputs: [examples/greenbump.yml](examples/greenbump.yml)
749
807
  - [x] **v0.1.0**: Core fix loop + multi-ecosystem support
750
808
  - [x] **v0.2.0**: Batch upgrades (`--all`), changelog integration
751
809
  - [x] **v0.3.0**: Static analysis (TypeScript + ESLint) + change detection
752
- - [x] **v0.4.0**: Multi-stage fix strategy + dependency graph analysis
810
+ - [x] **v0.4.0**: AST verification + changelog digest
753
811
  - [x] **v0.5.0**: Docker sandbox + database testing + performance regression detection
754
812
  - [ ] **v0.6.0**: Web UI for monitoring upgrades
755
813
  - [ ] **v0.7.0**: Upgrade impact prediction (before running)
package/README.zh-CN.md CHANGED
@@ -115,7 +115,7 @@ React 19 移除了 `ReactDOM.render`。下面是 greenbump 智能体为修复该
115
115
  - 🐳 **Docker 沙箱模式**(`--sandbox`)— 在干净的容器中运行测试
116
116
  - 🗄️ **数据库集成测试** — 自动启动 PostgreSQL、MySQL、Redis、MongoDB
117
117
  - 📊 **性能回归检测** — 追踪构建时间、包体积、内存占用
118
- - 🎯 **95%+ 准确率** — 生产级验证
118
+ - 🎯 **可复现 benchmark** — 6/6 无 LLM 用例通过(5 个 codemod 修复 + 1 个无误报),LLM 场景需自带 API key 实测(`npm run bench`,见 `benchmarks/`)
119
119
 
120
120
  ### 🌍 多生态系统支持
121
121
  - **20+ 生态系统**:npm、pip、cargo、maven、poetry、gradle 等
@@ -177,10 +177,42 @@ npx greenbump eslint --to 9.15.0
177
177
  # 不指定依赖,让 greenbump 自动选择最过期的一个
178
178
  npx greenbump
179
179
 
180
- # 列出过期依赖(只读模式,不做升级)
180
+ # 预览升级影响 —— 确定性分析,不需要 API key,不调用大模型
181
181
  npx greenbump --scan
182
182
  ```
183
183
 
184
+ ### 确定性预览:`--scan`
185
+
186
+ `--scan` 用**零成本**回答一个问题:*如果升级这些依赖,我的哪些文件会炸?*
187
+
188
+ ```bash
189
+ npx greenbump --scan
190
+ ```
191
+
192
+ ```
193
+ 🌱 greenbump scan — 1 outdated dependency, 1 with known breaking changes
194
+
195
+ zod 3.23.8 → 4.6.7 [breaking: 2 changes — 1 files, 1 locations]
196
+ 1. [high] error.errors → error.issues (Zod 4) (builtin-codemod)
197
+ fix: .error.issues
198
+ src/validate.js:6: return r.error.errors[0].message;
199
+ → 1 location in 1 file
200
+ 2. [high] z.string().email()/.url()/.uuid() → z.email()/z.url()/z.uuid() (Zod 4)
201
+ → 0 locations in 0 files
202
+
203
+ Summary: 1 dependency with breaking changes touch 1 file, 1 location.
204
+ Deterministic preview: no LLM calls, no network — sources: built-in codemods, changelog cache.
205
+ ```
206
+
207
+ 几个要点:
208
+
209
+ - **不需要 API key。** scan 只读取内置 codemod 库和本地 changelog 缓存,不调用任何模型,也不联网。
210
+ - **真正有意义的数字是「影响你几处」,不是「总共多少个」。** 标注 `0 locations` 的破坏性变更对你的仓库没有影响。
211
+ - **`unknown` 就是「不知道」。** 如果某个依赖既没有内置 codemod、也没有缓存分析结果,scan 会如实说明,而不是瞎猜。跑一次真实升级(`npx greenbump <依赖名>`)就会把 changelog 解析结果缓存下来,之后 `--scan` 永久免费。
212
+ - **`--scan --json`** 输出结构化 JSON(`schemaVersion: 1`),方便接入 CI 或看板。
213
+ - **`--scan --no-impact`** 恢复成纯过时依赖列表。
214
+ - 结果是**精确到 file:line** 的,且完全只读:不改文件、不提交。
215
+
184
216
  ### 进阶用法
185
217
 
186
218
  ```bash
@@ -215,7 +247,8 @@ npx greenbump react react-dom --group react-upgrade
215
247
  | `--to <version>` | 目标版本(默认:`latest`)。 |
216
248
  | `--ecosystem <id>` | 依赖生态系统(`npm`、`poetry`、`cargo`、`maven` 等)。省略时自动检测。 |
217
249
  | `--list-ecosystems` | 列出所有支持的生态系统并退出。 |
218
- | `--scan` | 仅列出过期依赖,不执行升级(只读模式)。 |
250
+ | `--scan` | 预览升级影响(不执行升级):列出过期依赖,**并**指出每个已知破坏性变更会触及哪些文件/哪些行。确定性分析——不调用大模型、不需要 API key、不联网。 |
251
+ | `--no-impact` | 配合 `--scan`,跳过影响面分析,仅列出过期依赖。 |
219
252
  | `--all` | 升级所有检测到的过期依赖。 |
220
253
  | `--group <name>` | 将多个依赖合并到同一个分支/PR。 |
221
254
 
@@ -257,7 +290,7 @@ npx greenbump react react-dom --group react-upgrade
257
290
  | `--cache-stats` | 显示缓存条目数、体积及分类统计。 |
258
291
  | `--cache-clear [category]` | 清除缓存条目(`changelogs`、`llm-fixes`、`patterns`;省略则全部清除)。 |
259
292
  | `--stats [days]` | 显示最近 N 天(默认 30 天)的用量与节省报告并退出。 |
260
- | `--json` | 配合 `--stats` 使用,输出原始 JSON 而非格式化报告。 |
293
+ | `--json` | 配合 `--stats` 或 `--scan` 使用,输出原始 JSON 而非格式化报告。 |
261
294
 
262
295
  #### 分层修复策略如何降低成本
263
296
 
@@ -0,0 +1,37 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { getInput, setOutput } from "./github.js";
7
+ test("getInput: reads INPUT_* env vars (GitHub uppercases but keeps dashes) and trims", () => {
8
+ process.env["INPUT_ANTHROPIC-API-KEY"] = " sk-test ";
9
+ try {
10
+ assert.equal(getInput("anthropic-api-key"), "sk-test");
11
+ assert.equal(getInput("missing-input"), "");
12
+ }
13
+ finally {
14
+ delete process.env["INPUT_ANTHROPIC-API-KEY"];
15
+ }
16
+ });
17
+ test("setOutput: writes heredoc format to GITHUB_OUTPUT", async () => {
18
+ const dir = await mkdtemp(join(tmpdir(), "greenbump-ghout-"));
19
+ const file = join(dir, "output");
20
+ process.env.GITHUB_OUTPUT = file;
21
+ try {
22
+ setOutput("status", "pr-opened");
23
+ setOutput("pr-url", "https://example.com/pr/1");
24
+ const content = await readFile(file, "utf8");
25
+ assert.match(content, /status<<ghadelimiter_status\npr-opened\nghadelimiter_status\n/);
26
+ assert.match(content, /pr-url<<ghadelimiter_pr-url\nhttps:\/\/example\.com\/pr\/1\nghadelimiter_pr-url\n/);
27
+ }
28
+ finally {
29
+ delete process.env.GITHUB_OUTPUT;
30
+ await rm(dir, { recursive: true, force: true });
31
+ }
32
+ });
33
+ test("setOutput: without GITHUB_OUTPUT it is a no-op", () => {
34
+ delete process.env.GITHUB_OUTPUT;
35
+ setOutput("status", "anything"); // must not throw
36
+ });
37
+ //# sourceMappingURL=github.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.test.js","sourceRoot":"","sources":["../../src/action/github.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAElD,IAAI,CAAC,iFAAiF,EAAE,GAAG,EAAE;IAC3F,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,GAAG,aAAa,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC,CAAC;QACvD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9C,CAAC;YAAS,CAAC;QACT,OAAO,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;IACnE,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACjC,SAAS,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,+DAA+D,CAAC,CAAC;QACvF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,mFAAmF,CAAC,CAAC;IAC7G,CAAC;YAAS,CAAC;QACT,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;QACjC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC1D,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACjC,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,iBAAiB;AACpD,CAAC,CAAC,CAAC"}
@@ -64,13 +64,21 @@ async function main() {
64
64
  setOutput("status", summary.fixed ? "no-changes" : "unfixed");
65
65
  return;
66
66
  }
67
- // configure a bot identity and push the branch
67
+ // configure a bot identity and push the branch.
68
+ // The token goes in an auth header, NOT the remote URL: git's error output
69
+ // echoes the remote URL on failure, and a URL-embedded token would land in
70
+ // the workflow log. (It still transits argv on the runner — acceptable on
71
+ // ephemeral single-tenant runners; the log leak was the real risk.)
68
72
  await exec("git", ["config", "user.name", "greenbump[bot]"], { cwd });
69
73
  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 });
74
+ const remote = `https://github.com/${repoSlug}.git`;
75
+ const push = await exec("git", [
76
+ "-c", `http.https://github.com/.extraheader=AUTHORIZATION: bearer ${token}`,
77
+ "push", "--force", remote, `HEAD:${summary.branch}`,
78
+ ], { cwd });
72
79
  if (push.code !== 0) {
73
- setFailed(`failed to push branch:\n${push.combined}`);
80
+ // Belt and braces: redact the token even though it should never appear now.
81
+ setFailed(`failed to push branch:\n${push.combined.split(token).join("***")}`);
74
82
  return;
75
83
  }
76
84
  // Both "flagged for review" and "fix loop exhausted without going green"
@@ -92,6 +100,13 @@ async function main() {
92
100
  labels: summary.needsReview ? [reviewLabel] : undefined,
93
101
  });
94
102
  setOutput("pr-url", url ?? "");
103
+ if (!url) {
104
+ // The branch IS pushed but the PR could not be created — saying
105
+ // "pr-opened" here would be lying about what happened.
106
+ setOutput("status", "pr-failed");
107
+ setFailed("branch pushed, but creating the pull request failed (see warnings above).");
108
+ return;
109
+ }
95
110
  setOutput("status", stillBroken ? "unfixed" : summary.needsReview ? "pr-needs-review" : "pr-opened");
96
111
  info(draft ? "opened a draft PR for review." : "opened a PR — build + tests green.");
97
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/action/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/action/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { mkdir, appendFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ function statsDir() {
5
+ return process.env.GREENBUMP_STATS_DIR ?? join(homedir(), ".greenbump");
6
+ }
7
+ function callsPath() {
8
+ return join(statsDir(), "llm-calls.jsonl");
9
+ }
10
+ /**
11
+ * Append one call record. Best-effort: a write failure is logged once and
12
+ * swallowed — cost logging must never fail a run.
13
+ */
14
+ export async function recordLlmCall(rec, onLog) {
15
+ try {
16
+ await mkdir(statsDir(), { recursive: true });
17
+ await appendFile(callsPath(), JSON.stringify(rec) + "\n", "utf8");
18
+ }
19
+ catch (err) {
20
+ onLog?.(`stats: failed to record LLM call (${err.message})`);
21
+ }
22
+ }
23
+ //# sourceMappingURL=calllog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calllog.js","sourceRoot":"","sources":["../../src/agent/calllog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA0BlC,SAAS,QAAQ;IACf,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAkB,EAAE,KAA2B;IACjF,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,MAAM,UAAU,CAAC,SAAS,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,EAAE,CAAC,qCAAsC,GAAa,CAAC,OAAO,GAAG,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC"}
@@ -0,0 +1,77 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { recordLlmCall } from "./calllog.js";
7
+ test("recordLlmCall: appends one JSON line per call to the stats dir", async () => {
8
+ const dir = await mkdtemp(join(tmpdir(), "greenbump-calllog-"));
9
+ const prev = process.env.GREENBUMP_STATS_DIR;
10
+ process.env.GREENBUMP_STATS_DIR = dir;
11
+ try {
12
+ await recordLlmCall({
13
+ ts: 1,
14
+ feature: "fix-loop",
15
+ provider: "stub",
16
+ model: "stub-model",
17
+ inputTokens: 10,
18
+ outputTokens: 5,
19
+ durationMs: 42,
20
+ attempts: 1,
21
+ ok: true,
22
+ });
23
+ await recordLlmCall({
24
+ ts: 2,
25
+ feature: "changelog-digest",
26
+ provider: "stub",
27
+ model: "stub-cheap",
28
+ inputTokens: 0,
29
+ outputTokens: 0,
30
+ durationMs: 7,
31
+ attempts: 3,
32
+ ok: false,
33
+ error: "boom",
34
+ });
35
+ const lines = (await readFile(join(dir, "llm-calls.jsonl"), "utf8")).trim().split("\n");
36
+ assert.equal(lines.length, 2);
37
+ const first = JSON.parse(lines[0]);
38
+ assert.equal(first.feature, "fix-loop");
39
+ assert.equal(first.ok, true);
40
+ const second = JSON.parse(lines[1]);
41
+ assert.equal(second.ok, false);
42
+ assert.equal(second.error, "boom");
43
+ }
44
+ finally {
45
+ if (prev === undefined)
46
+ delete process.env.GREENBUMP_STATS_DIR;
47
+ else
48
+ process.env.GREENBUMP_STATS_DIR = prev;
49
+ await rm(dir, { recursive: true, force: true });
50
+ }
51
+ });
52
+ test("recordLlmCall: a write failure is swallowed and reported via onLog, never thrown", async () => {
53
+ const prev = process.env.GREENBUMP_STATS_DIR;
54
+ process.env.GREENBUMP_STATS_DIR = "/dev/null/impossible";
55
+ const logs = [];
56
+ try {
57
+ await recordLlmCall({
58
+ ts: 1,
59
+ feature: "fix-loop",
60
+ provider: "stub",
61
+ model: "m",
62
+ inputTokens: 0,
63
+ outputTokens: 0,
64
+ durationMs: 1,
65
+ attempts: 1,
66
+ ok: true,
67
+ }, (m) => logs.push(m));
68
+ assert.equal(logs.length, 1);
69
+ }
70
+ finally {
71
+ if (prev === undefined)
72
+ delete process.env.GREENBUMP_STATS_DIR;
73
+ else
74
+ process.env.GREENBUMP_STATS_DIR = prev;
75
+ }
76
+ });
77
+ //# sourceMappingURL=calllog.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calllog.test.js","sourceRoot":"","sources":["../../src/agent/calllog.test.ts"],"names":[],"mappings":"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"}