benchkit-cli 0.1.0__py3-none-any.whl

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 (149) hide show
  1. benchkit/__init__.py +1 -0
  2. benchkit/_pi_proxy.py +219 -0
  3. benchkit/answer_key_guard.ts +49 -0
  4. benchkit/artifacts.py +87 -0
  5. benchkit/benchmarks/__init__.py +105 -0
  6. benchkit/benchmarks/aider_polyglot.py +405 -0
  7. benchkit/benchmarks/arc.py +52 -0
  8. benchkit/benchmarks/banking77.py +149 -0
  9. benchkit/benchmarks/base.py +19 -0
  10. benchkit/benchmarks/boolq.py +49 -0
  11. benchkit/benchmarks/chartqa.py +133 -0
  12. benchkit/benchmarks/code_verifier.py +77 -0
  13. benchkit/benchmarks/downloads.py +166 -0
  14. benchkit/benchmarks/evalplus.py +288 -0
  15. benchkit/benchmarks/git_surgery.py +485 -0
  16. benchkit/benchmarks/gpqa.py +56 -0
  17. benchkit/benchmarks/gsm8k.py +97 -0
  18. benchkit/benchmarks/hellaswag.py +49 -0
  19. benchkit/benchmarks/humaneval.py +103 -0
  20. benchkit/benchmarks/ifeval.py +56 -0
  21. benchkit/benchmarks/ifeval_instructions.py +477 -0
  22. benchkit/benchmarks/mbpp.py +60 -0
  23. benchkit/benchmarks/mc_arena.py +479 -0
  24. benchkit/benchmarks/mcq.py +94 -0
  25. benchkit/benchmarks/medfailbench.py +537 -0
  26. benchkit/benchmarks/mmlu.py +54 -0
  27. benchkit/benchmarks/mmlu_pro.py +62 -0
  28. benchkit/benchmarks/mmvp.py +115 -0
  29. benchkit/benchmarks/ocrbench.py +96 -0
  30. benchkit/benchmarks/openbookqa.py +52 -0
  31. benchkit/benchmarks/patcheval.py +929 -0
  32. benchkit/benchmarks/piqa.py +52 -0
  33. benchkit/benchmarks/ruler.py +172 -0
  34. benchkit/benchmarks/ruler_generator.py +535 -0
  35. benchkit/benchmarks/sanity.py +174 -0
  36. benchkit/benchmarks/treejs_arena.py +268 -0
  37. benchkit/benchmarks/truthfulqa.py +52 -0
  38. benchkit/benchmarks/utils.py +25 -0
  39. benchkit/benchmarks/vision.py +230 -0
  40. benchkit/benchmarks/vstar.py +108 -0
  41. benchkit/benchmarks/winogrande.py +52 -0
  42. benchkit/benchmarks/xstest.py +116 -0
  43. benchkit/browser.py +429 -0
  44. benchkit/catalog.py +121 -0
  45. benchkit/cli.py +1000 -0
  46. benchkit/client.py +1413 -0
  47. benchkit/controls.py +87 -0
  48. benchkit/datasets/MMLU_LICENSE.txt +21 -0
  49. benchkit/datasets/README.md +142 -0
  50. benchkit/datasets/arc_challenge.jsonl +1172 -0
  51. benchkit/datasets/banking77.jsonl +3080 -0
  52. benchkit/datasets/banking77_intents.json +79 -0
  53. benchkit/datasets/boolq.jsonl +3270 -0
  54. benchkit/datasets/code_verifier.jsonl +500 -0
  55. benchkit/datasets/gpqa_diamond.jsonl +198 -0
  56. benchkit/datasets/gsm8k.jsonl +1319 -0
  57. benchkit/datasets/hellaswag.jsonl +1000 -0
  58. benchkit/datasets/humaneval.jsonl +164 -0
  59. benchkit/datasets/ifeval.jsonl +541 -0
  60. benchkit/datasets/mbpp.jsonl +500 -0
  61. benchkit/datasets/mc_blocks_1_20_1.jsonl +1003 -0
  62. benchkit/datasets/medfailbench_hard30.jsonl +30 -0
  63. benchkit/datasets/medfailbench_hard30.md +13 -0
  64. benchkit/datasets/openbookqa.jsonl +500 -0
  65. benchkit/datasets/piqa.jsonl +1838 -0
  66. benchkit/datasets/sanity_code.jsonl +6 -0
  67. benchkit/datasets/truthfulqa.jsonl +817 -0
  68. benchkit/datasets/winogrande.jsonl +1267 -0
  69. benchkit/datasets/xstest.csv +451 -0
  70. benchkit/decision.py +194 -0
  71. benchkit/demo.py +451 -0
  72. benchkit/dockerfiles.py +247 -0
  73. benchkit/engine.py +1270 -0
  74. benchkit/evaluation.py +195 -0
  75. benchkit/events.py +141 -0
  76. benchkit/executor.py +96 -0
  77. benchkit/git_surgery/backport-release-stack/setup.sh +328 -0
  78. benchkit/git_surgery/backport-release-stack/verify.sh +119 -0
  79. benchkit/git_surgery/bisect-the-regression/setup.sh +49 -0
  80. benchkit/git_surgery/bisect-the-regression/verify.sh +34 -0
  81. benchkit/git_surgery/rebase-conflict-chain/setup.sh +65 -0
  82. benchkit/git_surgery/rebase-conflict-chain/verify.sh +43 -0
  83. benchkit/git_surgery/recover-complex-stash/setup.sh +152 -0
  84. benchkit/git_surgery/recover-complex-stash/verify.sh +95 -0
  85. benchkit/git_surgery/recover-lost-work/setup.sh +40 -0
  86. benchkit/git_surgery/recover-lost-work/verify.sh +36 -0
  87. benchkit/git_surgery/repair-force-pushed-remote/setup.sh +171 -0
  88. benchkit/git_surgery/repair-force-pushed-remote/verify.sh +83 -0
  89. benchkit/git_surgery/revert-merge-with-followups/setup.sh +198 -0
  90. benchkit/git_surgery/revert-merge-with-followups/verify.sh +98 -0
  91. benchkit/git_surgery/secret-in-history/README.md +27 -0
  92. benchkit/git_surgery/secret-in-history/setup.sh +107 -0
  93. benchkit/git_surgery/secret-in-history/verify.sh +106 -0
  94. benchkit/git_surgery/split-the-mega-commit/setup.sh +50 -0
  95. benchkit/git_surgery/split-the-mega-commit/verify.sh +48 -0
  96. benchkit/git_surgery/untangle-nested-submodules/setup.sh +193 -0
  97. benchkit/git_surgery/untangle-nested-submodules/verify.sh +90 -0
  98. benchkit/history.py +474 -0
  99. benchkit/jobs.py +332 -0
  100. benchkit/leakage.py +51 -0
  101. benchkit/looping.py +873 -0
  102. benchkit/mc_render.py +274 -0
  103. benchkit/mc_viewer/dist/atlas.png +0 -0
  104. benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
  105. benchkit/mc_viewer/dist/version.json +4 -0
  106. benchkit/mc_viewer/dist/viewer.html +23 -0
  107. benchkit/mc_viewer/dist/viewer.js +2 -0
  108. benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +25 -0
  109. benchkit/mc_viewer/dist/worker.js +2 -0
  110. benchkit/mc_viewer/dist/worker.js.LICENSE.txt +19 -0
  111. benchkit/metrics.py +100 -0
  112. benchkit/ollama.py +60 -0
  113. benchkit/perf.py +353 -0
  114. benchkit/perf_report.py +207 -0
  115. benchkit/perturbations.py +208 -0
  116. benchkit/pi_agent.py +476 -0
  117. benchkit/pi_package/package-lock.json +1827 -0
  118. benchkit/pi_package/package.json +8 -0
  119. benchkit/report.py +142 -0
  120. benchkit/report_markdown.py +645 -0
  121. benchkit/results.py +688 -0
  122. benchkit/retry.py +163 -0
  123. benchkit/runner.py +769 -0
  124. benchkit/sandbox.py +1167 -0
  125. benchkit/template_check.py +291 -0
  126. benchkit/templates/arena.html +504 -0
  127. benchkit/templates/history.html +58 -0
  128. benchkit/templates/perf.html +509 -0
  129. benchkit/templates/report.html +769 -0
  130. benchkit/tui/__init__.py +5 -0
  131. benchkit/tui/app.py +87 -0
  132. benchkit/tui/app.tcss +512 -0
  133. benchkit/tui/formatting.py +84 -0
  134. benchkit/tui/screens/__init__.py +1 -0
  135. benchkit/tui/screens/connect.py +191 -0
  136. benchkit/tui/screens/detail.py +288 -0
  137. benchkit/tui/screens/help.py +84 -0
  138. benchkit/tui/screens/modals.py +544 -0
  139. benchkit/tui/screens/results.py +361 -0
  140. benchkit/tui/screens/run.py +988 -0
  141. benchkit/tui/screens/setup.py +733 -0
  142. benchkit/tui/theme.py +135 -0
  143. benchkit/tui/widgets.py +120 -0
  144. benchkit_cli-0.1.0.dist-info/METADATA +294 -0
  145. benchkit_cli-0.1.0.dist-info/RECORD +149 -0
  146. benchkit_cli-0.1.0.dist-info/WHEEL +5 -0
  147. benchkit_cli-0.1.0.dist-info/entry_points.txt +3 -0
  148. benchkit_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
  149. benchkit_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
4
+ workspace="${2:-/workspace/backport-release-stack}"
5
+ slug="$(printf '%s' "$seed:backport" | sha256sum | cut -c1-12)"
6
+ mkdir -p "$workspace"
7
+ cd "$workspace"
8
+ git init -q -b release-2.x
9
+ git config user.name "BenchKit Generator"
10
+ git config user.email "generator@benchkit.invalid"
11
+ git config commit.gpgSign false
12
+ git config core.autocrlf false
13
+ export GIT_AUTHOR_NAME="BenchKit Generator" GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
14
+ export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
15
+ export GIT_CONFIG_GLOBAL=/dev/null
16
+ clock=1700500000
17
+ commit() {
18
+ clock=$((clock + 60))
19
+ git add -A
20
+ GIT_AUTHOR_DATE="@$clock +0000" GIT_COMMITTER_DATE="@$clock +0000" git commit -q "$@"
21
+ }
22
+ replace() {
23
+ python3 - "$@" <<'PY'
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ path, old, new = Path(sys.argv[1]), sys.argv[2], sys.argv[3]
28
+ text = path.read_text()
29
+ if old not in text:
30
+ raise SystemExit(f"setup: {old!r} not found in {path}")
31
+ path.write_text(text.replace(old, new, 1))
32
+ PY
33
+ }
34
+
35
+ mkdir -p ledger docs
36
+ printf '__pycache__/\n' > .gitignore
37
+ : > ledger/__init__.py
38
+ cat > ledger/money.py <<'PY'
39
+ """Amount parsing and formatting in integer cents."""
40
+
41
+
42
+ def parse_amount(text: str) -> int:
43
+ value = text
44
+ if not value.isdigit():
45
+ raise ValueError(f"bad amount: {text!r}")
46
+ return int(value)
47
+
48
+
49
+ def format_amount(cents: int) -> str:
50
+ return f"{cents // 100}.{cents % 100:02d}"
51
+ PY
52
+ cat > ledger/discount.py <<'PY'
53
+ """Discounts applied to order totals."""
54
+
55
+
56
+ def apply_discount(total: int, discount: int) -> int:
57
+ return total - discount
58
+ PY
59
+ cat > ledger/config.py <<'PY'
60
+ """Runtime configuration."""
61
+
62
+ DEFAULTS = {
63
+ "timeout": 30,
64
+ "retries": 3,
65
+ }
66
+
67
+ VERSION = "1.0.0"
68
+ PY
69
+ cat > test_core.py <<'PY'
70
+ import unittest
71
+
72
+ from ledger.config import DEFAULTS
73
+ from ledger.discount import apply_discount
74
+ from ledger.money import format_amount, parse_amount
75
+
76
+
77
+ class CoreTests(unittest.TestCase):
78
+ def test_parse_amount(self):
79
+ self.assertEqual(parse_amount("1250"), 1250)
80
+
81
+ def test_parse_amount_rejects_text(self):
82
+ with self.assertRaises(ValueError):
83
+ parse_amount("ten")
84
+
85
+ def test_format_amount(self):
86
+ self.assertEqual(format_amount(1205), "12.05")
87
+
88
+ def test_apply_discount(self):
89
+ self.assertEqual(apply_discount(500, 120), 380)
90
+
91
+ def test_retries(self):
92
+ self.assertEqual(DEFAULTS["retries"], 3)
93
+ PY
94
+ cat > CHANGELOG.md <<EOF
95
+ # Ledger changelog ($slug)
96
+
97
+ EOF
98
+ printf '# Ledger %s\n\nRun tests with `python3 -m unittest discover -v`.\n' "$slug" > README.md
99
+ commit -m "bootstrap ledger $slug"
100
+ fork="$(git rev-parse HEAD)"
101
+
102
+ noise() {
103
+ local index="$1"
104
+ case $((index % 4)) in
105
+ 0)
106
+ printf -- '- %s: maintenance entry %02d\n' "$slug" "$index" >> CHANGELOG.md
107
+ commit -m "chore: update changelog ($index)"
108
+ ;;
109
+ 1)
110
+ if [[ ! -f ledger/export.py ]]; then
111
+ cat > ledger/export.py <<'PY'
112
+ """CSV export, new in 2.x."""
113
+
114
+ COLUMNS = ["id"]
115
+
116
+
117
+ def header() -> str:
118
+ return ",".join(COLUMNS)
119
+ PY
120
+ cat > test_export.py <<'PY'
121
+ import unittest
122
+
123
+ from ledger.export import COLUMNS, header
124
+
125
+
126
+ class ExportTests(unittest.TestCase):
127
+ def test_header(self):
128
+ self.assertEqual(header(), ",".join(COLUMNS))
129
+ PY
130
+ commit -m "export: introduce CSV export"
131
+ else
132
+ replace ledger/export.py ']' ", \"col$index\"]"
133
+ commit -m "export: add column col$index"
134
+ fi
135
+ ;;
136
+ 2)
137
+ printf 'Release note %02d for %s.\n' "$index" "$slug" >> "docs/release-$((index % 3)).md"
138
+ commit -m "docs: expand release notes ($index)"
139
+ ;;
140
+ 3)
141
+ printf 'step-%02d\n' "$index" >> docs/pipeline.txt
142
+ commit -m "ci: adjust pipeline step $index"
143
+ ;;
144
+ esac
145
+ }
146
+
147
+ for index in $(seq 1 70); do
148
+ case "$index" in
149
+ 6)
150
+ python3 - <<'PY'
151
+ from pathlib import Path
152
+
153
+ path = Path("ledger/money.py")
154
+ text = path.read_text()
155
+ text = text.replace(" value = text\n if not value.isdigit():", " raw = text\n if not raw.isdigit():")
156
+ text = text.replace("return int(value)", "return int(raw)")
157
+ path.write_text(text)
158
+ PY
159
+ commit -m "refactor: rename parse_amount locals"
160
+ ;;
161
+ 11)
162
+ replace ledger/money.py 'def parse_amount(text: str) -> int:
163
+ raw = text
164
+ ' 'def parse_amount(text: str, allow_commas: bool = False) -> int:
165
+ raw = text
166
+ if allow_commas:
167
+ raw = raw.replace(",", "")
168
+ '
169
+ cat >> test_export.py <<'PY'
170
+
171
+
172
+ class SeparatorTests(unittest.TestCase):
173
+ def test_thousands_separators(self):
174
+ from ledger.money import parse_amount
175
+
176
+ self.assertEqual(parse_amount("1,250", allow_commas=True), 1250)
177
+ PY
178
+ commit -m "feat: accept thousands separators"
179
+ ;;
180
+ 17)
181
+ replace ledger/money.py ' raw = text
182
+ ' ' raw = text.strip()
183
+ '
184
+ cat > test_bk101.py <<'PY'
185
+ import unittest
186
+
187
+ from ledger.money import parse_amount
188
+
189
+
190
+ class WhitespaceTests(unittest.TestCase):
191
+ def test_surrounding_whitespace_is_ignored(self):
192
+ self.assertEqual(parse_amount(" 42\n"), 42)
193
+ PY
194
+ commit -m "Fix whitespace handling in parse_amount" -m "Fixes: BK-101"
195
+ ;;
196
+ 19)
197
+ printf -- '- Fix typo in the BK-104 changelog entry\n' >> CHANGELOG.md
198
+ commit -m "Fix typo in BK-104 changelog entry"
199
+ ;;
200
+ 22)
201
+ printf '# Parsing\n\nSurrounding whitespace is ignored (BK-101).\n' > docs/parsing.md
202
+ commit -m "BK-101: document whitespace parsing" -m "Refs: BK-101"
203
+ ;;
204
+ 28)
205
+ replace ledger/money.py ' raw = text.strip()
206
+ ' ' raw = text.strip()
207
+ sign = -1 if raw.startswith("-") else 1
208
+ raw = raw.removeprefix("-")
209
+ '
210
+ replace ledger/money.py 'return int(raw)' 'return sign * int(raw)'
211
+ cat > test_bk104.py <<'PY'
212
+ import unittest
213
+
214
+ from ledger.money import parse_amount
215
+
216
+
217
+ class NegativeAmountTests(unittest.TestCase):
218
+ def test_negative_amounts(self):
219
+ self.assertEqual(parse_amount("-75"), -75)
220
+ PY
221
+ commit -m "Accept negative amounts" -m "Fixes: BK-104"
222
+ ;;
223
+ 34)
224
+ replace ledger/money.py ' if not raw.isdigit():' ' if len(raw) > 18:
225
+ raise ValueError(f"amount too long: {text!r}")
226
+ if not raw.isdigit():'
227
+ commit -m "Reject overlong amounts" -m "Fixes: BK-1040"
228
+ ;;
229
+ 39)
230
+ replace ledger/money.py ' return f"{cents // 100}.{cents % 100:02d}"' ' return f"{cents / 100:.2f}"'
231
+ cat > test_bk107.py <<'PY'
232
+ import unittest
233
+
234
+ from ledger.money import format_amount
235
+
236
+
237
+ class NegativeFormatTests(unittest.TestCase):
238
+ def test_negative_cents(self):
239
+ self.assertEqual(format_amount(-5), "-0.05")
240
+ PY
241
+ commit -m "Format negative amounts" -m "Fixes: BK-107"
242
+ attempt="$(git rev-parse HEAD)"
243
+ ;;
244
+ 43)
245
+ git revert --no-edit --no-commit "$attempt" >/dev/null
246
+ commit -m "Revert \"Format negative amounts\"" \
247
+ -m "This reverts commit $attempt. Float formatting loses cents above 2**53." \
248
+ -m "Refs: BK-107"
249
+ ;;
250
+ 48)
251
+ replace ledger/money.py ' return f"{cents // 100}.{cents % 100:02d}"' ' sign = "-" if cents < 0 else ""
252
+ cents = abs(cents)
253
+ return f"{sign}{cents // 100}.{cents % 100:02d}"'
254
+ cat > test_bk107.py <<'PY'
255
+ import unittest
256
+
257
+ from ledger.money import format_amount
258
+
259
+
260
+ class NegativeFormatTests(unittest.TestCase):
261
+ def test_negative_cents(self):
262
+ self.assertEqual(format_amount(-5), "-0.05")
263
+
264
+ def test_large_amounts_stay_exact(self):
265
+ self.assertEqual(format_amount(123456789012345678), "1234567890123456.78")
266
+ PY
267
+ commit -m "Format negative amounts without floats" -m "Fixes: BK-107"
268
+ ;;
269
+ 54)
270
+ replace ledger/discount.py 'return total - discount' 'return max(0, total - discount)'
271
+ cat > test_bk112.py <<'PY'
272
+ import unittest
273
+
274
+ from ledger.discount import apply_discount
275
+
276
+
277
+ class ClampTests(unittest.TestCase):
278
+ def test_discount_never_goes_negative(self):
279
+ self.assertEqual(apply_discount(100, 250), 0)
280
+ PY
281
+ commit -m "Clamp discounted totals at zero" -m "Fixes: BK-112"
282
+ ;;
283
+ 57)
284
+ replace ledger/config.py 'VERSION = "1.0.0"' 'VERSION = "2.0.0"
285
+ RELEASE_CHANNEL = "2.x"'
286
+ commit -m "Bump version to 2.0.0"
287
+ ;;
288
+ 60)
289
+ replace ledger/config.py '"timeout": 30,' '"timeout": 10,'
290
+ cat > test_bk115.py <<'PY'
291
+ import unittest
292
+
293
+ from ledger.config import DEFAULTS
294
+
295
+
296
+ class TimeoutTests(unittest.TestCase):
297
+ def test_default_timeout(self):
298
+ self.assertEqual(DEFAULTS["timeout"], 10)
299
+ PY
300
+ commit -m "Lower default request timeout" -m "Fixes: BK-115"
301
+ ;;
302
+ 65)
303
+ printf -- '- Follow-up for BK-115 timeouts in docs\n' >> CHANGELOG.md
304
+ commit -m "Lower default request timeout in docs" -m "Refs: BK-115"
305
+ ;;
306
+ *)
307
+ noise "$index"
308
+ ;;
309
+ esac
310
+ done
311
+
312
+ git switch -q -c maint-1.x "$fork"
313
+ replace ledger/discount.py 'return total - discount' 'return max(0, total - discount)'
314
+ cat > test_bk112.py <<'PY'
315
+ import unittest
316
+
317
+ from ledger.discount import apply_discount
318
+
319
+
320
+ class ClampTests(unittest.TestCase):
321
+ def test_discount_never_goes_negative(self):
322
+ self.assertEqual(apply_discount(100, 250), 0)
323
+ PY
324
+ commit -m "Clamp discounted totals at zero (BK-112)"
325
+ replace ledger/config.py 'VERSION = "1.0.0"' 'VERSION = "1.0.3"'
326
+ printf -- '- 1.0.3 maintenance release\n' >> CHANGELOG.md
327
+ commit -m "Release 1.0.3"
328
+ git status --porcelain | grep -q . && exit 1 || true
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+ seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
4
+ workspace="${2:-/workspace/backport-release-stack}"
5
+ emit() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
6
+ ids="selected_fixes skipped_equivalent intermediate_tests final_behavior no_release_leak"
7
+ if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
8
+ for id in $ids; do emit "$id" 0 "repository missing"; done
9
+ emit trap 1 "repository replaced"
10
+ exit 0
11
+ fi
12
+ reference_root="$(mktemp -d)"
13
+ reference="$reference_root/reference"
14
+ bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
15
+ fixed() { git -C "$reference" log release-2.x --format=%H --grep="^Fixes: $1\$" -1; }
16
+ release_tip="$(git -C "$reference" rev-parse release-2.x)"
17
+ maint_tip="$(git -C "$reference" rev-parse maint-1.x)"
18
+ expected_sources="$(fixed BK-101) $(fixed BK-104) $(fixed BK-107) $(fixed BK-115)"
19
+ equivalent="$(fixed BK-112)"
20
+ release_only="$(git -C "$reference" log release-2.x --format=%H --grep='^refactor: rename parse_amount locals$' -1)"
21
+ rm -rf "$reference_root"
22
+
23
+ git_w() { git -C "$workspace" "$@" 2>/dev/null; }
24
+ release_now="$(git_w rev-parse -q --verify refs/heads/release-2.x)"
25
+ maint_now="$(git_w rev-parse -q --verify refs/heads/maint-1.x)"
26
+ git_w merge-base --is-ancestor "$maint_tip" "$maint_now" && kept=1 || kept=0
27
+ [[ "$release_now" == "$release_tip" && "$kept" == 1 ]] && untouched=1 || untouched=0
28
+
29
+ new_commits=()
30
+ if [[ "$kept" == 1 ]]; then
31
+ mapfile -t new_commits < <(git_w rev-list --reverse --first-parent "$maint_tip..$maint_now")
32
+ fi
33
+ merges="$(git_w rev-list --merges --count "$maint_tip..$maint_now" || printf 0)"
34
+ sources=()
35
+ for commit in "${new_commits[@]}"; do
36
+ source="$(git_w log -1 --format=%B "$commit" | sed -n 's/^(cherry picked from commit \([0-9a-f]\{40\}\))$/\1/p' | tail -1)"
37
+ sources+=("${source:-none}")
38
+ done
39
+ actual_sources="${sources[*]:-}"
40
+ [[ "$untouched" == 1 && "$actual_sources" == "$expected_sources" && "$merges" == 0 ]] && selected=1 || selected=0
41
+ emit selected_fixes "$selected" "count=${#new_commits[@]} merges=$merges release_and_maint_base_kept=$untouched sources=$actual_sources"
42
+
43
+ empty=0
44
+ duplicate=0
45
+ for index in "${!new_commits[@]}"; do
46
+ commit="${new_commits[$index]}"
47
+ [[ -z "$(git_w diff-tree --no-commit-id --name-only -r "$commit")" ]] && empty=1
48
+ [[ "${sources[$index]}" == "$equivalent" ]] && duplicate=1
49
+ git_w log -1 --format=%B "$commit" | grep -q 'BK-112' && duplicate=1
50
+ done
51
+ [[ "${#new_commits[@]}" -gt 0 && "$empty" == 0 && "$duplicate" == 0 ]] && skipped=1 || skipped=0
52
+ emit skipped_equivalent "$skipped" "empty_commits=$empty duplicated_bk112=$duplicate"
53
+
54
+ # Hidden behavior per backport, in the order they must land.
55
+ checks=(
56
+ 'assert parse_amount(" 42\n") == 42'
57
+ 'assert parse_amount(" -75 ") == -75'
58
+ 'assert format_amount(-5) == "-0.05"; assert format_amount(123456789012345678) == "1234567890123456.78"'
59
+ 'assert DEFAULTS["timeout"] == 10'
60
+ )
61
+ preamble='from ledger.config import DEFAULTS
62
+ from ledger.discount import apply_discount
63
+ from ledger.money import format_amount, parse_amount
64
+ assert parse_amount("1250") == 1250
65
+ assert format_amount(1205) == "12.05"
66
+ assert apply_discount(100, 250) == 0
67
+ assert DEFAULTS["retries"] == 3'
68
+ run_archive() {
69
+ local commit="$1" script="$2" tmp result=1
70
+ tmp="$(mktemp -d)"
71
+ if git_w archive "$commit" | tar -x -C "$tmp" 2>/dev/null; then
72
+ (cd "$tmp" && python3 -B -c "$script" && python3 -B -m unittest discover) >/dev/null 2>&1 && result=0
73
+ fi
74
+ rm -rf "$tmp"
75
+ return "$result"
76
+ }
77
+ intermediate=0
78
+ passing=0
79
+ if [[ "$selected" == 1 ]]; then
80
+ intermediate=1
81
+ for index in "${!new_commits[@]}"; do
82
+ script="$preamble"
83
+ for prior in $(seq 0 "$index"); do script+=$'\n'"${checks[$prior]}"; done
84
+ if run_archive "${new_commits[$index]}" "$script"; then
85
+ passing=$((passing + 1))
86
+ else
87
+ intermediate=0
88
+ fi
89
+ done
90
+ fi
91
+ emit intermediate_tests "$intermediate" "passing_backports=$passing/${#new_commits[@]}"
92
+
93
+ final_script="$preamble"
94
+ for check in "${checks[@]}"; do final_script+=$'\n'"$check"; done
95
+ final_script+=$'\nfrom ledger import config\nassert config.VERSION == "1.0.3"'
96
+ final=0
97
+ [[ "${#new_commits[@]}" -gt 0 ]] && run_archive "$maint_now" "$final_script" && final=1
98
+ emit final_behavior "$final" "all_fixes_and_maint_version=$final"
99
+
100
+ leak=1
101
+ if [[ "${#new_commits[@]}" -gt 0 ]]; then
102
+ leak_script='import inspect
103
+ from ledger import config, money
104
+ assert not hasattr(config, "RELEASE_CHANNEL")
105
+ assert "allow_commas" not in inspect.signature(money.parse_amount).parameters
106
+ assert money.parse_amount("1" * 30) == int("1" * 30)'
107
+ git_w cat-file -e "$maint_now:ledger/export.py" || {
108
+ tmp="$(mktemp -d)"
109
+ git_w archive "$maint_now" | tar -x -C "$tmp" 2>/dev/null
110
+ (cd "$tmp" && python3 -B -c "$leak_script") >/dev/null 2>&1 && leak=0
111
+ rm -rf "$tmp"
112
+ }
113
+ fi
114
+ emit no_release_leak "$((1 - leak))" "release_only_changes=$leak"
115
+
116
+ trap=0
117
+ [[ -n "$maint_now" ]] && git_w merge-base --is-ancestor "$release_only" "$maint_now" && trap=1
118
+ [[ -z "$maint_now" ]] && trap=1
119
+ emit trap "$trap" "release_history_merged_into_maint=$trap"
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
4
+ workspace="${2:-/workspace/bisect-the-regression}"
5
+ slug="$(printf '%s' "$seed:bisect" | sha256sum | cut -c1-12)"
6
+ bad_index=$((10 + seed % 19))
7
+ mkdir -p "$workspace"
8
+ cd "$workspace"
9
+ git init -q -b main
10
+ git config user.name "BenchKit Generator"
11
+ git config user.email "generator@benchkit.invalid"
12
+ git config commit.gpgSign false
13
+ git config core.autocrlf false
14
+ export GIT_AUTHOR_NAME="BenchKit Generator" GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
15
+ export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
16
+ commit_at() {
17
+ local timestamp="$1"
18
+ shift
19
+ GIT_AUTHOR_DATE="@$timestamp +0000" GIT_COMMITTER_DATE="@$timestamp +0000" git commit -q "$@"
20
+ }
21
+ cat > calculator.py <<'PY'
22
+ def shipping_total(items: int) -> int:
23
+ """Return the shipping charge for a number of items."""
24
+ return items * 3
25
+ PY
26
+ cat > test_calculator.py <<'PY'
27
+ import unittest
28
+ from calculator import shipping_total
29
+
30
+
31
+ class ShippingTests(unittest.TestCase):
32
+ def test_shipping_total(self):
33
+ self.assertEqual(shipping_total(7), 21)
34
+ PY
35
+ printf '# Regression history %s\n\nRun tests with `python3 -m unittest discover -v`.\n' "$slug" > README.md
36
+ git add .
37
+ commit_at 1700100000 -m "bootstrap shipping calculator $slug"
38
+ git tag "benchkit-bisect-baseline-$slug"
39
+ for index in $(seq 1 39); do
40
+ printf 'note-%02d=%s\n' "$index" "$(printf '%s' "$seed:$index" | sha256sum | cut -c1-8)" >> changelog.txt
41
+ if [[ "$index" == "$bad_index" ]]; then
42
+ python3 -c 'from pathlib import Path; p=Path("calculator.py"); p.write_text(p.read_text().replace("items * 3", "items * 4"))'
43
+ message="optimize shipping coefficient"
44
+ else
45
+ message="record maintenance note $index"
46
+ fi
47
+ git add .
48
+ commit_at "$((1700100000 + index * 60))" -m "$message"
49
+ done
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+ seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
4
+ workspace="${2:-/workspace/bisect-the-regression}"
5
+ emit() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
6
+ if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
7
+ for id in used_bisect identified_commit bug_fixed tests_pass history_preserved; do emit "$id" 0 "repository missing"; done
8
+ emit trap 1 "repository replaced"
9
+ exit 0
10
+ fi
11
+ reference_root="$(mktemp -d)"
12
+ reference="$reference_root/reference"
13
+ bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
14
+ expected="$(git -C "$reference" log --format=%H --grep='^optimize shipping coefficient$' -1)"
15
+ expected_test="$(git -C "$reference" rev-parse HEAD:test_calculator.py)"
16
+ expected_count="$(git -C "$reference" rev-list --count HEAD)"
17
+ rm -rf "$reference_root"
18
+ reported="$(tr -d '[:space:]' < "$workspace/offending-commit.txt" 2>/dev/null || true)"
19
+ [[ "$reported" == "$expected" ]] && identified=1 || identified=0
20
+ emit used_bisect 1 "trace-verified"
21
+ emit identified_commit "$identified" "expected=$expected reported=$reported"
22
+ grep -Fq 'return items * 3' "$workspace/calculator.py" 2>/dev/null && fixed=1 || fixed=0
23
+ emit bug_fixed "$fixed" "shipping_formula=$fixed"
24
+ test_exit=0
25
+ (cd "$workspace" && python3 -m unittest discover -v) >/tmp/git-surgery-bisect-tests.txt 2>&1 || test_exit=$?
26
+ emit tests_pass "$((test_exit == 0))" "exit=$test_exit"
27
+ count="$(git -C "$workspace" rev-list --count HEAD 2>/dev/null || printf 0)"
28
+ baseline="$(printf '%s' "$seed:bisect" | sha256sum | cut -c1-12)"
29
+ git -C "$workspace" rev-parse -q --verify "refs/tags/benchkit-bisect-baseline-$baseline" >/dev/null 2>&1 && lineage=1 || lineage=0
30
+ [[ "$count" -ge "$expected_count" && "$lineage" == 1 ]] && history=1 || history=0
31
+ emit history_preserved "$history" "commit_count=$count baseline=$lineage"
32
+ actual_test="$(git -C "$workspace" rev-parse HEAD:test_calculator.py 2>/dev/null || true)"
33
+ [[ -n "$expected_test" && "$actual_test" == "$expected_test" ]] && trap=0 || trap=1
34
+ emit trap "$trap" "test_blob_preserved=$((1 - trap))"
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
4
+ workspace="${2:-/workspace/rebase-conflict-chain}"
5
+ slug="$(printf '%s' "$seed:rebase" | sha256sum | cut -c1-12)"
6
+ mkdir -p "$workspace"
7
+ cd "$workspace"
8
+ git init -q -b main
9
+ git config user.name "BenchKit Generator"
10
+ git config user.email "generator@benchkit.invalid"
11
+ git config commit.gpgSign false
12
+ git config core.autocrlf false
13
+ export GIT_AUTHOR_NAME="BenchKit Generator" GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
14
+ export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
15
+ commit_at() { local t="$1"; shift; GIT_AUTHOR_DATE="@$t +0000" GIT_COMMITTER_DATE="@$t +0000" git commit -q "$@"; }
16
+ cat > auth.py <<'PY'
17
+ def identify(user: str) -> str:
18
+ return user.strip()
19
+ PY
20
+ cat > billing.py <<'PY'
21
+ def total(subtotal: int) -> int:
22
+ return subtotal
23
+ PY
24
+ cat > report.py <<'PY'
25
+ def render(items: list[str]) -> str:
26
+ return ",".join(items)
27
+ PY
28
+ cat > test_behavior.py <<'PY'
29
+ import unittest
30
+ from auth import identify
31
+ from billing import total
32
+ from report import render
33
+
34
+
35
+ class BehaviorTests(unittest.TestCase):
36
+ def test_auth_keeps_audit_and_normalization(self):
37
+ self.assertEqual(identify(" Alice "), "audit:alice")
38
+
39
+ def test_billing_keeps_discount_and_tax(self):
40
+ self.assertEqual(total(100), 105)
41
+
42
+ def test_report_keeps_header_and_uppercase(self):
43
+ self.assertEqual(render(["one", "two"]), "items:ONE,TWO")
44
+ PY
45
+ printf '# Conflict chain %s\n\nRun tests with `python3 -m unittest discover -v`.\n' "$slug" > README.md
46
+ git add .
47
+ commit_at 1700400000 -m "bootstrap conflict chain $slug"
48
+ git tag "benchkit-rebase-baseline-$slug"
49
+ git switch -q -c feature
50
+ python3 -c 'from pathlib import Path; p=Path("auth.py"); p.write_text(p.read_text().replace("return user.strip()", "return f\"audit:{user.strip()}\""))'
51
+ git add auth.py
52
+ commit_at 1700400060 -m "add authentication audit marker"
53
+ python3 -c 'from pathlib import Path; p=Path("billing.py"); p.write_text(p.read_text().replace("return subtotal", "return subtotal - 5"))'
54
+ git add billing.py
55
+ commit_at 1700400120 -m "add loyalty discount"
56
+ python3 -c 'from pathlib import Path; p=Path("report.py"); p.write_text(p.read_text().replace("return \",\".join(items)", "return \",\".join(item.upper() for item in items)"))'
57
+ git add report.py
58
+ commit_at 1700400180 -m "add uppercase report format"
59
+ git switch -q main
60
+ python3 -c 'from pathlib import Path; p=Path("auth.py"); p.write_text(p.read_text().replace("return user.strip()", "return user.strip().lower()"))'
61
+ python3 -c 'from pathlib import Path; p=Path("billing.py"); p.write_text(p.read_text().replace("return subtotal", "return subtotal + 10"))'
62
+ python3 -c 'from pathlib import Path; p=Path("report.py"); p.write_text(p.read_text().replace("return \",\".join(items)", "return \"items:\" + \",\".join(items)"))'
63
+ git add auth.py billing.py report.py
64
+ commit_at 1700400240 -m "move main behavior forward"
65
+ git switch -q feature
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+ seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
4
+ workspace="${2:-/workspace/rebase-conflict-chain}"
5
+ emit() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
6
+ if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
7
+ for id in started_rebase three_commits_ordered both_sides_preserved tests_pass; do emit "$id" 0 "repository missing"; done
8
+ emit trap 1 "repository replaced"
9
+ exit 0
10
+ fi
11
+ emit started_rebase 1 "trace-verified"
12
+ reference_root="$(mktemp -d)"
13
+ reference="$reference_root/reference"
14
+ bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
15
+ main_tip="$(git -C "$reference" rev-parse main)"
16
+ expected_test="$(git -C "$reference" rev-parse main:test_behavior.py)"
17
+ rm -rf "$reference_root"
18
+ base="$(git -C "$workspace" merge-base HEAD main 2>/dev/null || true)"
19
+ count="$(git -C "$workspace" rev-list --count main..HEAD 2>/dev/null || printf 0)"
20
+ subjects="$(git -C "$workspace" log --reverse --format=%s main..HEAD 2>/dev/null | tr '\n' '|')"
21
+ expected='add authentication audit marker|add loyalty discount|add uppercase report format|'
22
+ [[ "$base" == "$main_tip" && "$count" == 3 && "$subjects" == "$expected" ]] && ordered=1 || ordered=0
23
+ emit three_commits_ordered "$ordered" "count=$count subjects=$subjects"
24
+ verified_tree="$(mktemp -d)"
25
+ git -C "$workspace" archive HEAD 2>/dev/null | tar -x -C "$verified_tree"
26
+ behavior=0
27
+ (cd "$verified_tree" && python3 -c '
28
+ from auth import identify
29
+ from billing import total
30
+ from report import render
31
+
32
+ assert identify(" Alice ") == "audit:alice"
33
+ assert total(100) == 105
34
+ assert render(["one", "two"]) == "items:ONE,TWO"
35
+ ') >/tmp/git-surgery-rebase-behavior.txt 2>&1 && behavior=1
36
+ emit both_sides_preserved "$behavior" "semantic_merge=$behavior"
37
+ test_exit=0
38
+ (cd "$verified_tree" && python3 -m unittest discover -v) >/tmp/git-surgery-rebase-tests.txt 2>&1 || test_exit=$?
39
+ rm -rf "$verified_tree"
40
+ emit tests_pass "$((test_exit == 0))" "exit=$test_exit"
41
+ actual_test="$(git -C "$workspace" rev-parse HEAD:test_behavior.py 2>/dev/null || true)"
42
+ if [[ "$behavior" == 0 || -z "$expected_test" || "$actual_test" != "$expected_test" ]]; then trap=1; else trap=0; fi
43
+ emit trap "$trap" "side_lost_or_tests_changed=$trap"