claude-dev-env 8.43.5 → 8.43.6
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/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/advisor/reference/cli-chain.md +9 -12
- package/_shared/advisor/reference/lifecycle.md +1 -1
- package/_shared/advisor/reference/third-party-bind.md +3 -3
- package/bin/install.test.mjs +1 -1
- package/docs/second-claude-account.md +11 -13
- package/package.json +1 -1
- package/scripts/_code_review_test_support.py +29 -39
- package/scripts/claude_account_worker.py +46 -297
- package/scripts/claude_account_worker_process.py +18 -154
- package/scripts/claude_account_worker_report.py +8 -4
- package/scripts/claude_chain_usage.py +7 -271
- package/scripts/codec_forwarding_test_support.py +11 -65
- package/scripts/dev_env_scripts_constants/claude_account_worker_constants.py +6 -24
- package/scripts/invoke_code_review.py +34 -56
- package/scripts/resolve_worker_spawn.py +40 -72
- package/scripts/test_account_broker_guard.py +0 -4
- package/scripts/test_claude_account_worker.py +105 -350
- package/scripts/test_claude_chain_usage.py +50 -510
- package/scripts/test_dispatcher_profile_import.py +5 -9
- package/scripts/test_invoke_code_review.py +7 -6
- package/scripts/test_invoke_code_review_chain.py +26 -0
- package/scripts/test_invoke_code_review_cli.py +8 -8
- package/scripts/test_invoke_code_review_codec.py +3 -11
- package/scripts/test_invoke_code_review_contract.py +5 -1
- package/scripts/test_resolve_worker_spawn.py +118 -265
- package/scripts/test_resolve_worker_spawn_codec.py +9 -21
- package/scripts/tests/test_code_review_constants.py +2 -2
- package/scripts/claude_account_choice.py +0 -422
- package/scripts/claude_chain_runner.py +0 -1289
- package/scripts/test_claude_account_choice.py +0 -355
- package/scripts/test_claude_chain_runner.py +0 -1839
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
"""Tests for the picker that names which Claude account a runner job uses."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
from datetime import datetime, timedelta, timezone
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
import pytest
|
|
10
|
-
|
|
11
|
-
import claude_account_choice as choice
|
|
12
|
-
from claude_chain_usage import AccountUsageMeters, WeeklyUtilizationProbeError
|
|
13
|
-
|
|
14
|
-
NOW = datetime(2026, 9, 22, 21, 0, tzinfo=timezone.utc)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def meters(
|
|
18
|
-
*,
|
|
19
|
-
session_used: float | None,
|
|
20
|
-
weekly_used: float | None,
|
|
21
|
-
weekly_resets_in: timedelta | None,
|
|
22
|
-
session_resets_in: timedelta = timedelta(hours=2),
|
|
23
|
-
) -> AccountUsageMeters:
|
|
24
|
-
return AccountUsageMeters(
|
|
25
|
-
session_utilization=session_used,
|
|
26
|
-
session_resets_at=NOW + session_resets_in,
|
|
27
|
-
weekly_utilization=weekly_used,
|
|
28
|
-
weekly_resets_at=None if weekly_resets_in is None else NOW + weekly_resets_in,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class TestChooseAccount:
|
|
33
|
-
def test_should_pick_main_when_week_resets_soon(self) -> None:
|
|
34
|
-
main_meters = meters(
|
|
35
|
-
session_used=10.0, weekly_used=80.0, weekly_resets_in=timedelta(hours=5)
|
|
36
|
-
)
|
|
37
|
-
second_meters = meters(
|
|
38
|
-
session_used=10.0, weekly_used=82.0, weekly_resets_in=timedelta(days=4)
|
|
39
|
-
)
|
|
40
|
-
decision = choice.choose_account(
|
|
41
|
-
main_meters=main_meters, second_meters=second_meters, now=NOW
|
|
42
|
-
)
|
|
43
|
-
assert decision.account == choice.CHOICE_MAIN
|
|
44
|
-
|
|
45
|
-
def test_should_pick_second_when_main_week_resets_later_than_a_day(self) -> None:
|
|
46
|
-
main_meters = meters(
|
|
47
|
-
session_used=0.0, weekly_used=5.0, weekly_resets_in=timedelta(hours=30)
|
|
48
|
-
)
|
|
49
|
-
second_meters = meters(
|
|
50
|
-
session_used=10.0, weekly_used=40.0, weekly_resets_in=timedelta(days=4)
|
|
51
|
-
)
|
|
52
|
-
decision = choice.choose_account(
|
|
53
|
-
main_meters=main_meters, second_meters=second_meters, now=NOW
|
|
54
|
-
)
|
|
55
|
-
assert decision.account == choice.CHOICE_SECOND
|
|
56
|
-
|
|
57
|
-
def test_should_keep_main_once_it_nears_its_weekly_ceiling(self) -> None:
|
|
58
|
-
main_meters = meters(
|
|
59
|
-
session_used=0.0, weekly_used=90.0, weekly_resets_in=timedelta(hours=3)
|
|
60
|
-
)
|
|
61
|
-
second_meters = meters(
|
|
62
|
-
session_used=0.0, weekly_used=10.0, weekly_resets_in=timedelta(days=4)
|
|
63
|
-
)
|
|
64
|
-
decision = choice.choose_account(
|
|
65
|
-
main_meters=main_meters, second_meters=second_meters, now=NOW
|
|
66
|
-
)
|
|
67
|
-
assert decision.account == choice.CHOICE_SECOND
|
|
68
|
-
|
|
69
|
-
def test_should_keep_main_when_its_five_hour_window_is_half_spent(self) -> None:
|
|
70
|
-
main_meters = meters(
|
|
71
|
-
session_used=50.0, weekly_used=20.0, weekly_resets_in=timedelta(hours=3)
|
|
72
|
-
)
|
|
73
|
-
second_meters = meters(
|
|
74
|
-
session_used=0.0, weekly_used=10.0, weekly_resets_in=timedelta(days=4)
|
|
75
|
-
)
|
|
76
|
-
decision = choice.choose_account(
|
|
77
|
-
main_meters=main_meters, second_meters=second_meters, now=NOW
|
|
78
|
-
)
|
|
79
|
-
assert decision.account == choice.CHOICE_SECOND
|
|
80
|
-
|
|
81
|
-
def test_should_never_pick_main_when_its_meter_is_unread(self) -> None:
|
|
82
|
-
second_meters = meters(
|
|
83
|
-
session_used=95.0, weekly_used=99.0, weekly_resets_in=timedelta(days=4)
|
|
84
|
-
)
|
|
85
|
-
decision = choice.choose_account(
|
|
86
|
-
main_meters=None, second_meters=second_meters, now=NOW
|
|
87
|
-
)
|
|
88
|
-
assert decision.account == choice.CHOICE_WAIT
|
|
89
|
-
|
|
90
|
-
def test_should_never_pick_main_when_its_reset_time_is_missing(self) -> None:
|
|
91
|
-
main_meters = meters(session_used=0.0, weekly_used=0.0, weekly_resets_in=None)
|
|
92
|
-
second_meters = meters(
|
|
93
|
-
session_used=95.0, weekly_used=99.0, weekly_resets_in=timedelta(days=4)
|
|
94
|
-
)
|
|
95
|
-
decision = choice.choose_account(
|
|
96
|
-
main_meters=main_meters, second_meters=second_meters, now=NOW
|
|
97
|
-
)
|
|
98
|
-
assert decision.account == choice.CHOICE_WAIT
|
|
99
|
-
|
|
100
|
-
def test_should_pick_second_when_its_meter_is_unread(self) -> None:
|
|
101
|
-
decision = choice.choose_account(main_meters=None, second_meters=None, now=NOW)
|
|
102
|
-
assert decision.account == choice.CHOICE_SECOND
|
|
103
|
-
assert decision.reason == choice.REASON_SECOND_UNREAD
|
|
104
|
-
|
|
105
|
-
def test_should_wait_and_name_the_session_reset_when_the_five_hour_window_blocks(
|
|
106
|
-
self,
|
|
107
|
-
) -> None:
|
|
108
|
-
second_meters = meters(
|
|
109
|
-
session_used=90.0,
|
|
110
|
-
weekly_used=50.0,
|
|
111
|
-
weekly_resets_in=timedelta(days=4),
|
|
112
|
-
session_resets_in=timedelta(hours=1),
|
|
113
|
-
)
|
|
114
|
-
decision = choice.choose_account(
|
|
115
|
-
main_meters=None, second_meters=second_meters, now=NOW
|
|
116
|
-
)
|
|
117
|
-
assert decision.account == choice.CHOICE_WAIT
|
|
118
|
-
assert (NOW + timedelta(hours=1)).isoformat() in decision.reason
|
|
119
|
-
|
|
120
|
-
def test_should_wait_and_name_the_weekly_reset_when_the_week_blocks(self) -> None:
|
|
121
|
-
second_meters = meters(
|
|
122
|
-
session_used=10.0, weekly_used=95.0, weekly_resets_in=timedelta(days=2)
|
|
123
|
-
)
|
|
124
|
-
decision = choice.choose_account(
|
|
125
|
-
main_meters=None, second_meters=second_meters, now=NOW
|
|
126
|
-
)
|
|
127
|
-
assert decision.account == choice.CHOICE_WAIT
|
|
128
|
-
assert (NOW + timedelta(days=2)).isoformat() in decision.reason
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
class TestReadAccountMeters:
|
|
132
|
-
def test_should_return_none_when_the_probe_cannot_read_the_account(
|
|
133
|
-
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
134
|
-
) -> None:
|
|
135
|
-
def failing_probe(credentials_path: Path) -> AccountUsageMeters:
|
|
136
|
-
raise WeeklyUtilizationProbeError(f"no token in {credentials_path}")
|
|
137
|
-
|
|
138
|
-
monkeypatch.setattr(choice, "probe_account_meters", failing_probe)
|
|
139
|
-
assert choice.read_account_meters(tmp_path / ".credentials.json") is None
|
|
140
|
-
|
|
141
|
-
def test_should_return_the_probed_meters(
|
|
142
|
-
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
143
|
-
) -> None:
|
|
144
|
-
probed = meters(
|
|
145
|
-
session_used=1.0, weekly_used=2.0, weekly_resets_in=timedelta(days=1)
|
|
146
|
-
)
|
|
147
|
-
monkeypatch.setattr(choice, "probe_account_meters", lambda credentials_path: probed)
|
|
148
|
-
assert choice.read_account_meters(tmp_path / ".credentials.json") == probed
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
class TestDecisionPayload:
|
|
152
|
-
def test_should_name_no_config_directory_for_a_wait(self) -> None:
|
|
153
|
-
decision = choice.AccountDecision(account=choice.CHOICE_WAIT, reason="full")
|
|
154
|
-
assert choice.decision_payload(decision, config_directory=None) == {
|
|
155
|
-
"account": "wait",
|
|
156
|
-
"config_dir": None,
|
|
157
|
-
"reason": "full",
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
class TestMetersPayload:
|
|
162
|
-
def test_should_name_no_meters_for_an_unread_account(self) -> None:
|
|
163
|
-
assert choice.meters_payload(None) is None
|
|
164
|
-
|
|
165
|
-
def test_should_carry_each_used_percent_and_reset_time(self) -> None:
|
|
166
|
-
read_meters = meters(
|
|
167
|
-
session_used=12.0,
|
|
168
|
-
weekly_used=34.0,
|
|
169
|
-
weekly_resets_in=timedelta(days=3),
|
|
170
|
-
session_resets_in=timedelta(hours=1),
|
|
171
|
-
)
|
|
172
|
-
assert choice.meters_payload(read_meters) == {
|
|
173
|
-
"session_used_percent": 12.0,
|
|
174
|
-
"session_resets_at": "2026-09-22T22:00:00+00:00",
|
|
175
|
-
"weekly_used_percent": 34.0,
|
|
176
|
-
"weekly_resets_at": "2026-09-25T21:00:00+00:00",
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
def test_should_name_no_reset_time_the_meter_left_out(self) -> None:
|
|
180
|
-
read_meters = meters(session_used=1.0, weekly_used=None, weekly_resets_in=None)
|
|
181
|
-
payload = choice.meters_payload(read_meters)
|
|
182
|
-
assert payload is not None
|
|
183
|
-
assert payload["weekly_used_percent"] is None
|
|
184
|
-
assert payload["weekly_resets_at"] is None
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
class TestMain:
|
|
188
|
-
def test_should_print_the_choice_and_the_config_directory_of_the_chosen_account(
|
|
189
|
-
self,
|
|
190
|
-
tmp_path: Path,
|
|
191
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
192
|
-
capsys: pytest.CaptureFixture[str],
|
|
193
|
-
) -> None:
|
|
194
|
-
main_home = tmp_path / "main"
|
|
195
|
-
second_home = tmp_path / "second"
|
|
196
|
-
meters_by_credentials_path = {
|
|
197
|
-
main_home / ".credentials.json": None,
|
|
198
|
-
second_home / ".credentials.json": meters(
|
|
199
|
-
session_used=0.0, weekly_used=10.0, weekly_resets_in=timedelta(days=4)
|
|
200
|
-
),
|
|
201
|
-
}
|
|
202
|
-
monkeypatch.setattr(
|
|
203
|
-
choice,
|
|
204
|
-
"read_account_meters",
|
|
205
|
-
lambda credentials_path: meters_by_credentials_path[credentials_path],
|
|
206
|
-
)
|
|
207
|
-
exit_code = choice.main(
|
|
208
|
-
[
|
|
209
|
-
"--main-config-dir",
|
|
210
|
-
str(main_home),
|
|
211
|
-
"--second-config-dir",
|
|
212
|
-
str(second_home),
|
|
213
|
-
]
|
|
214
|
-
)
|
|
215
|
-
printed = json.loads(capsys.readouterr().out)
|
|
216
|
-
assert exit_code == 0
|
|
217
|
-
assert printed["account"] == choice.CHOICE_SECOND
|
|
218
|
-
assert printed["config_dir"] == str(second_home)
|
|
219
|
-
assert printed["meters"]["main"] is None
|
|
220
|
-
assert printed["meters"]["second"]["weekly_used_percent"] == 10.0
|
|
221
|
-
|
|
222
|
-
def test_should_print_no_config_directory_when_the_job_waits(
|
|
223
|
-
self,
|
|
224
|
-
tmp_path: Path,
|
|
225
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
226
|
-
capsys: pytest.CaptureFixture[str],
|
|
227
|
-
) -> None:
|
|
228
|
-
blocked_meters = meters(
|
|
229
|
-
session_used=99.0, weekly_used=99.0, weekly_resets_in=timedelta(days=4)
|
|
230
|
-
)
|
|
231
|
-
monkeypatch.setattr(
|
|
232
|
-
choice, "read_account_meters", lambda credentials_path: blocked_meters
|
|
233
|
-
)
|
|
234
|
-
exit_code = choice.main(
|
|
235
|
-
[
|
|
236
|
-
"--main-config-dir",
|
|
237
|
-
str(tmp_path / "main"),
|
|
238
|
-
"--second-config-dir",
|
|
239
|
-
str(tmp_path / "second"),
|
|
240
|
-
]
|
|
241
|
-
)
|
|
242
|
-
printed = json.loads(capsys.readouterr().out)
|
|
243
|
-
assert exit_code == 0
|
|
244
|
-
assert printed["account"] == choice.CHOICE_WAIT
|
|
245
|
-
assert printed["config_dir"] is None
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def test_should_pick_second_extra_in_order_and_print_every_meter(
|
|
249
|
-
tmp_path: Path,
|
|
250
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
251
|
-
capsys: pytest.CaptureFixture[str],
|
|
252
|
-
) -> None:
|
|
253
|
-
main_home = tmp_path / "main"
|
|
254
|
-
first_extra = tmp_path / "first"
|
|
255
|
-
second_extra = tmp_path / "next"
|
|
256
|
-
third_extra = tmp_path / "last"
|
|
257
|
-
readings = {
|
|
258
|
-
main_home: None,
|
|
259
|
-
first_extra: meters(
|
|
260
|
-
session_used=90.0, weekly_used=95.0, weekly_resets_in=timedelta(days=4)
|
|
261
|
-
),
|
|
262
|
-
second_extra: meters(
|
|
263
|
-
session_used=10.0, weekly_used=20.0, weekly_resets_in=timedelta(days=4)
|
|
264
|
-
),
|
|
265
|
-
third_extra: meters(
|
|
266
|
-
session_used=30.0, weekly_used=40.0, weekly_resets_in=timedelta(days=4)
|
|
267
|
-
),
|
|
268
|
-
}
|
|
269
|
-
monkeypatch.setattr(
|
|
270
|
-
choice, "read_account_meters", lambda path: readings[path.parent]
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
assert choice.main(
|
|
274
|
-
[
|
|
275
|
-
"--main-config-dir",
|
|
276
|
-
str(main_home),
|
|
277
|
-
"--second-config-dir",
|
|
278
|
-
str(first_extra),
|
|
279
|
-
"--extra-config-dir",
|
|
280
|
-
str(second_extra),
|
|
281
|
-
"--extra-config-dir",
|
|
282
|
-
str(third_extra),
|
|
283
|
-
]
|
|
284
|
-
) == 0
|
|
285
|
-
printed = json.loads(capsys.readouterr().out)
|
|
286
|
-
|
|
287
|
-
assert printed["account"] == "extra_2"
|
|
288
|
-
assert printed["config_dir"] == str(second_extra)
|
|
289
|
-
assert list(printed["meters"]) == ["main", "second", "extra_2", "extra_3"]
|
|
290
|
-
assert printed["meters"]["main"] is None
|
|
291
|
-
assert printed["meters"]["second"]["weekly_used_percent"] == 95.0
|
|
292
|
-
assert printed["meters"]["extra_2"]["weekly_used_percent"] == 20.0
|
|
293
|
-
assert printed["meters"]["extra_3"]["weekly_used_percent"] == 40.0
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def test_should_wait_for_first_usable_reset_when_all_extras_are_full(
|
|
297
|
-
tmp_path: Path,
|
|
298
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
299
|
-
capsys: pytest.CaptureFixture[str],
|
|
300
|
-
) -> None:
|
|
301
|
-
first_extra = tmp_path / "first"
|
|
302
|
-
second_extra = tmp_path / "next"
|
|
303
|
-
third_extra = tmp_path / "last"
|
|
304
|
-
readings = {
|
|
305
|
-
first_extra: meters(
|
|
306
|
-
session_used=90.0,
|
|
307
|
-
weekly_used=95.0,
|
|
308
|
-
weekly_resets_in=timedelta(hours=1),
|
|
309
|
-
session_resets_in=timedelta(hours=3),
|
|
310
|
-
),
|
|
311
|
-
second_extra: meters(
|
|
312
|
-
session_used=10.0, weekly_used=95.0, weekly_resets_in=timedelta(hours=2)
|
|
313
|
-
),
|
|
314
|
-
third_extra: meters(
|
|
315
|
-
session_used=90.0, weekly_used=10.0, weekly_resets_in=timedelta(days=4),
|
|
316
|
-
session_resets_in=timedelta(hours=4),
|
|
317
|
-
),
|
|
318
|
-
}
|
|
319
|
-
monkeypatch.setattr(
|
|
320
|
-
choice, "read_account_meters", lambda path: readings.get(path.parent)
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
assert choice.main(
|
|
324
|
-
[
|
|
325
|
-
"--main-config-dir",
|
|
326
|
-
str(tmp_path / "main"),
|
|
327
|
-
"--second-config-dir",
|
|
328
|
-
str(first_extra),
|
|
329
|
-
"--extra-config-dir",
|
|
330
|
-
str(second_extra),
|
|
331
|
-
"--extra-config-dir",
|
|
332
|
-
str(third_extra),
|
|
333
|
-
]
|
|
334
|
-
) == 0
|
|
335
|
-
printed = json.loads(capsys.readouterr().out)
|
|
336
|
-
|
|
337
|
-
assert printed["account"] == choice.CHOICE_WAIT
|
|
338
|
-
assert printed["config_dir"] is None
|
|
339
|
-
assert (NOW + timedelta(hours=2)).isoformat() in printed["reason"]
|
|
340
|
-
assert (NOW + timedelta(hours=1)).isoformat() not in printed["reason"]
|
|
341
|
-
assert list(printed["meters"]) == ["main", "second", "extra_2", "extra_3"]
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def test_should_reject_duplicate_extra_config_directories(tmp_path: Path) -> None:
|
|
345
|
-
with pytest.raises(SystemExit) as error:
|
|
346
|
-
choice.main(
|
|
347
|
-
[
|
|
348
|
-
"--second-config-dir",
|
|
349
|
-
str(tmp_path / "first"),
|
|
350
|
-
"--extra-config-dir",
|
|
351
|
-
str(tmp_path / "first"),
|
|
352
|
-
]
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
assert error.value.code == 2
|