claude-dev-env 2.2.0 → 2.3.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/CLAUDE.md +1 -1
- package/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -284
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +3 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -0
- package/_shared/pr-loop/scripts/pyproject.toml +22 -0
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +5 -0
- package/_shared/pr-loop/scripts/stale_worktree_rule_sweep.py +107 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +55 -73
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +9 -15
- package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +91 -1
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +88 -1
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -0
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -0
- package/_shared/pr-loop/worker-spawn.md +1 -1
- package/agents/code-verifier.md +1 -1
- package/hooks/blocking/code_rules_shared.py +4 -2
- package/hooks/blocking/config/verified_commit_constants.py +11 -0
- package/hooks/blocking/pii_payload_scan.py +78 -20
- package/hooks/blocking/pii_prevention_blocker.py +3 -1
- package/hooks/blocking/test_code_verifier_tools_contract.py +28 -0
- package/hooks/blocking/test_pii_write_surface_ephemeral.py +221 -0
- package/hooks/blocking/test_verification_verdict_store.py +93 -0
- package/hooks/blocking/verification_verdict_store.py +91 -0
- package/hooks/validators/ruff_integration.py +2 -1
- package/hooks/validators/run_all_validators.py +460 -24
- package/hooks/validators/test_ruff_integration.py +21 -0
- package/hooks/validators/test_run_all_validators_pretooluse.py +223 -3
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/cleanup-command-forms.md +23 -0
- package/scripts/CLAUDE.md +2 -1
- package/scripts/check.ps1 +32 -6
- package/scripts/claude-chain.example.json +15 -3
- package/scripts/claude_chain_runner.py +130 -27
- package/scripts/claude_chain_usage.py +346 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -3
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +13 -1
- package/scripts/dev_env_scripts_constants/claude_chain_usage_constants.py +58 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +1 -1
- package/scripts/dev_env_scripts_constants/timing.py +1 -1
- package/scripts/test_claude_chain_runner.py +412 -6
- package/scripts/test_claude_chain_usage.py +534 -0
- package/skills/orchestrator/SKILL.md +1 -20
- package/skills/orchestrator-refresh/SKILL.md +1 -1
- package/skills/pr-converge/SKILL.md +3 -3
- package/skills/pr-converge/reference/examples.md +3 -3
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +3 -3
- package/skills/pr-converge/reference/per-tick.md +5 -5
- package/skills/pr-converge/reference/progress-checklist.md +1 -1
- package/skills/pr-converge/test_step5_host_branch.py +1 -1
- package/skills/team-advisor/SKILL.md +3 -2
- package/skills/usage-pause/SKILL.md +5 -4
- package/skills/usage-pause/scripts/resolve_usage_window.py +68 -13
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +121 -9
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +7 -3
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
"""Behavioral tests for the claude chain weekly-usage report tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
13
|
+
if str(_SCRIPTS_DIR) not in sys.path:
|
|
14
|
+
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
15
|
+
|
|
16
|
+
import claude_chain_runner as chain_runner # noqa: E402
|
|
17
|
+
import claude_chain_usage as usage # noqa: E402
|
|
18
|
+
from dev_env_scripts_constants.claude_chain_constants import ( # noqa: E402
|
|
19
|
+
CHAIN_CONFIG_ERROR_EXIT_CODE,
|
|
20
|
+
CONFIG_CHAIN_KEY,
|
|
21
|
+
CONFIG_COMMAND_KEY,
|
|
22
|
+
CONFIG_CREDENTIALS_PATH_KEY,
|
|
23
|
+
CONFIG_EXTRA_ARGS_KEY,
|
|
24
|
+
CONFIG_FILENAME,
|
|
25
|
+
UTF8_ENCODING,
|
|
26
|
+
)
|
|
27
|
+
from dev_env_scripts_constants.claude_chain_usage_constants import ( # noqa: E402
|
|
28
|
+
CLI_CONFIG_PATH_FLAG,
|
|
29
|
+
FULL_WEEKLY_PERCENT,
|
|
30
|
+
JSON_ACCOUNTS_KEY,
|
|
31
|
+
JSON_COMMAND_KEY,
|
|
32
|
+
JSON_ERROR_KEY,
|
|
33
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY,
|
|
34
|
+
RESOLVE_USAGE_WINDOW_MODULE_NAME,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
PLACEHOLDER_CREDENTIALS_PRIMARY = "/path/to/account-primary/.credentials.json"
|
|
38
|
+
PLACEHOLDER_CREDENTIALS_SECONDARY = "/path/to/account-secondary/.credentials.json"
|
|
39
|
+
PLACEHOLDER_CREDENTIALS_TERTIARY = "/path/to/account-tertiary/.credentials.json"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _entry(
|
|
43
|
+
command: str,
|
|
44
|
+
*,
|
|
45
|
+
extra_args: list[str] | None = None,
|
|
46
|
+
credentials_path: str | None = None,
|
|
47
|
+
) -> dict[str, object]:
|
|
48
|
+
chain_entry: dict[str, object] = {
|
|
49
|
+
CONFIG_COMMAND_KEY: command,
|
|
50
|
+
CONFIG_EXTRA_ARGS_KEY: extra_args if extra_args is not None else [],
|
|
51
|
+
}
|
|
52
|
+
if credentials_path is not None:
|
|
53
|
+
chain_entry[CONFIG_CREDENTIALS_PATH_KEY] = credentials_path
|
|
54
|
+
return chain_entry
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _write_chain_config(
|
|
58
|
+
tmp_path: Path, all_chain_entries: list[dict[str, object]]
|
|
59
|
+
) -> Path:
|
|
60
|
+
config_file = tmp_path / CONFIG_FILENAME
|
|
61
|
+
config_file.write_text(
|
|
62
|
+
json.dumps({CONFIG_CHAIN_KEY: all_chain_entries}), encoding=UTF8_ENCODING
|
|
63
|
+
)
|
|
64
|
+
return config_file
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _probe_from_utilization_by_path(
|
|
68
|
+
utilization_by_path: dict[str, float | Exception],
|
|
69
|
+
) -> usage.WeeklyUtilizationProbe:
|
|
70
|
+
def active_probe(credentials_path: Path) -> float:
|
|
71
|
+
path_key = credentials_path.as_posix()
|
|
72
|
+
probe_outcome = utilization_by_path[path_key]
|
|
73
|
+
if isinstance(probe_outcome, Exception):
|
|
74
|
+
raise probe_outcome
|
|
75
|
+
return probe_outcome
|
|
76
|
+
|
|
77
|
+
return active_probe
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_remaining_percent_is_full_scale_minus_utilization(
|
|
81
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
82
|
+
) -> None:
|
|
83
|
+
config_file = _write_chain_config(
|
|
84
|
+
tmp_path,
|
|
85
|
+
[
|
|
86
|
+
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
87
|
+
_entry("claude-ev", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
88
|
+
],
|
|
89
|
+
)
|
|
90
|
+
monkeypatch.setattr(
|
|
91
|
+
usage,
|
|
92
|
+
"weekly_utilization_probe",
|
|
93
|
+
_probe_from_utilization_by_path(
|
|
94
|
+
{
|
|
95
|
+
PLACEHOLDER_CREDENTIALS_PRIMARY: 42.0,
|
|
96
|
+
PLACEHOLDER_CREDENTIALS_SECONDARY: 9.0,
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
101
|
+
assert [each_report.command for each_report in all_reports] == [
|
|
102
|
+
"claude",
|
|
103
|
+
"claude-ev",
|
|
104
|
+
]
|
|
105
|
+
assert all_reports[0].weekly_remaining_percent == pytest.approx(
|
|
106
|
+
FULL_WEEKLY_PERCENT - 42.0
|
|
107
|
+
)
|
|
108
|
+
assert all_reports[1].weekly_remaining_percent == pytest.approx(
|
|
109
|
+
FULL_WEEKLY_PERCENT - 9.0
|
|
110
|
+
)
|
|
111
|
+
assert all_reports[0].error is None
|
|
112
|
+
assert all_reports[1].error is None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_rank_orders_by_remaining_desc_preserves_ties_and_puts_nulls_last() -> None:
|
|
116
|
+
all_reports = [
|
|
117
|
+
usage.AccountUsageReport(command="first", weekly_remaining_percent=40.0),
|
|
118
|
+
usage.AccountUsageReport(command="second", weekly_remaining_percent=70.0),
|
|
119
|
+
usage.AccountUsageReport(
|
|
120
|
+
command="third", weekly_remaining_percent=None, error="probe failed"
|
|
121
|
+
),
|
|
122
|
+
usage.AccountUsageReport(command="fourth", weekly_remaining_percent=70.0),
|
|
123
|
+
usage.AccountUsageReport(
|
|
124
|
+
command="fifth", weekly_remaining_percent=None, error="no token"
|
|
125
|
+
),
|
|
126
|
+
usage.AccountUsageReport(command="sixth", weekly_remaining_percent=10.0),
|
|
127
|
+
]
|
|
128
|
+
ranked_reports = usage.rank_accounts_by_weekly_remaining(all_reports)
|
|
129
|
+
assert [each_report.command for each_report in ranked_reports] == [
|
|
130
|
+
"second",
|
|
131
|
+
"fourth",
|
|
132
|
+
"first",
|
|
133
|
+
"sixth",
|
|
134
|
+
"third",
|
|
135
|
+
"fifth",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_probe_failure_yields_null_remaining_and_error_string(
|
|
140
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
141
|
+
) -> None:
|
|
142
|
+
config_file = _write_chain_config(
|
|
143
|
+
tmp_path,
|
|
144
|
+
[
|
|
145
|
+
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
146
|
+
_entry("claude-ev", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
147
|
+
],
|
|
148
|
+
)
|
|
149
|
+
monkeypatch.setattr(
|
|
150
|
+
usage,
|
|
151
|
+
"weekly_utilization_probe",
|
|
152
|
+
_probe_from_utilization_by_path(
|
|
153
|
+
{
|
|
154
|
+
PLACEHOLDER_CREDENTIALS_PRIMARY: usage.WeeklyUtilizationProbeError(
|
|
155
|
+
"token expired"
|
|
156
|
+
),
|
|
157
|
+
PLACEHOLDER_CREDENTIALS_SECONDARY: 25.0,
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
162
|
+
assert all_reports[0].weekly_remaining_percent is None
|
|
163
|
+
assert all_reports[0].error == "token expired"
|
|
164
|
+
assert all_reports[1].weekly_remaining_percent == pytest.approx(
|
|
165
|
+
FULL_WEEKLY_PERCENT - 25.0
|
|
166
|
+
)
|
|
167
|
+
assert all_reports[1].error is None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_missing_config_raises_chain_configuration_error(tmp_path: Path) -> None:
|
|
171
|
+
missing_config = tmp_path / "absent-chain.json"
|
|
172
|
+
with pytest.raises(chain_runner.ChainConfigurationError):
|
|
173
|
+
usage.report_chain_weekly_usage(config_path=missing_config)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_empty_config_raises_chain_configuration_error(tmp_path: Path) -> None:
|
|
177
|
+
config_file = _write_chain_config(tmp_path, [])
|
|
178
|
+
with pytest.raises(chain_runner.ChainConfigurationError):
|
|
179
|
+
usage.report_chain_weekly_usage(config_path=config_file)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_entry_without_credentials_path_uses_default(
|
|
183
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
184
|
+
) -> None:
|
|
185
|
+
default_path = Path("/path/to/default/.credentials.json")
|
|
186
|
+
monkeypatch.setattr(usage, "_default_credentials_path", lambda: default_path)
|
|
187
|
+
config_file = _write_chain_config(tmp_path, [_entry("claude")])
|
|
188
|
+
probed_paths: list[Path] = []
|
|
189
|
+
|
|
190
|
+
def active_probe(credentials_path: Path) -> float:
|
|
191
|
+
probed_paths.append(credentials_path)
|
|
192
|
+
return 10.0
|
|
193
|
+
|
|
194
|
+
monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
|
|
195
|
+
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
196
|
+
assert probed_paths == [default_path]
|
|
197
|
+
assert all_reports[0].weekly_remaining_percent == pytest.approx(
|
|
198
|
+
FULL_WEEKLY_PERCENT - 10.0
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_default_credentials_resolution_failure_yields_null_remaining(
|
|
203
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
204
|
+
) -> None:
|
|
205
|
+
def raise_probe_error() -> Path:
|
|
206
|
+
raise usage.WeeklyUtilizationProbeError("probe module not found")
|
|
207
|
+
|
|
208
|
+
monkeypatch.setattr(usage, "_default_credentials_path", raise_probe_error)
|
|
209
|
+
config_file = _write_chain_config(tmp_path, [_entry("claude")])
|
|
210
|
+
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
211
|
+
assert all_reports[0].weekly_remaining_percent is None
|
|
212
|
+
assert all_reports[0].error == "probe module not found"
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_entry_credentials_path_is_passed_to_probe(
|
|
216
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
217
|
+
) -> None:
|
|
218
|
+
config_file = _write_chain_config(
|
|
219
|
+
tmp_path,
|
|
220
|
+
[_entry("claude-ev", credentials_path=PLACEHOLDER_CREDENTIALS_TERTIARY)],
|
|
221
|
+
)
|
|
222
|
+
probed_paths: list[Path] = []
|
|
223
|
+
|
|
224
|
+
def active_probe(credentials_path: Path) -> float:
|
|
225
|
+
probed_paths.append(credentials_path)
|
|
226
|
+
return 0.0
|
|
227
|
+
|
|
228
|
+
monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
|
|
229
|
+
usage.report_chain_weekly_usage(config_path=config_file)
|
|
230
|
+
assert probed_paths == [Path(PLACEHOLDER_CREDENTIALS_TERTIARY)]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_entry_credentials_path_expands_user_home(
|
|
234
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
235
|
+
) -> None:
|
|
236
|
+
tilde_credentials_path = "~/.claude-accounts/secondary/.credentials.json"
|
|
237
|
+
config_file = _write_chain_config(
|
|
238
|
+
tmp_path,
|
|
239
|
+
[_entry("claude-ev", credentials_path=tilde_credentials_path)],
|
|
240
|
+
)
|
|
241
|
+
probed_paths: list[Path] = []
|
|
242
|
+
|
|
243
|
+
def active_probe(credentials_path: Path) -> float:
|
|
244
|
+
probed_paths.append(credentials_path)
|
|
245
|
+
return 0.0
|
|
246
|
+
|
|
247
|
+
monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
|
|
248
|
+
usage.report_chain_weekly_usage(config_path=config_file)
|
|
249
|
+
assert probed_paths == [Path(tilde_credentials_path).expanduser()]
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def test_load_chain_carries_optional_credentials_path(tmp_path: Path) -> None:
|
|
253
|
+
config_file = _write_chain_config(
|
|
254
|
+
tmp_path,
|
|
255
|
+
[
|
|
256
|
+
_entry("claude"),
|
|
257
|
+
_entry("claude-ev", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
258
|
+
],
|
|
259
|
+
)
|
|
260
|
+
all_entries = chain_runner.load_chain(config_file)
|
|
261
|
+
assert all_entries[0].credentials_path is None
|
|
262
|
+
assert all_entries[1].credentials_path == PLACEHOLDER_CREDENTIALS_SECONDARY
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def test_invalid_credentials_path_type_raises(tmp_path: Path) -> None:
|
|
266
|
+
config_file = tmp_path / CONFIG_FILENAME
|
|
267
|
+
config_file.write_text(
|
|
268
|
+
json.dumps(
|
|
269
|
+
{
|
|
270
|
+
CONFIG_CHAIN_KEY: [
|
|
271
|
+
{
|
|
272
|
+
CONFIG_COMMAND_KEY: "claude",
|
|
273
|
+
CONFIG_EXTRA_ARGS_KEY: [],
|
|
274
|
+
CONFIG_CREDENTIALS_PATH_KEY: 12,
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
encoding=UTF8_ENCODING,
|
|
280
|
+
)
|
|
281
|
+
with pytest.raises(chain_runner.ChainConfigurationError):
|
|
282
|
+
chain_runner.load_chain(config_file)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_reports_to_json_payload_matches_cli_contract() -> None:
|
|
286
|
+
all_reports = [
|
|
287
|
+
usage.AccountUsageReport(command="claude", weekly_remaining_percent=58.0),
|
|
288
|
+
usage.AccountUsageReport(
|
|
289
|
+
command="claude-ev",
|
|
290
|
+
weekly_remaining_percent=None,
|
|
291
|
+
error="probe failed",
|
|
292
|
+
),
|
|
293
|
+
]
|
|
294
|
+
payload = usage.reports_to_json_payload(all_reports)
|
|
295
|
+
assert payload == {
|
|
296
|
+
JSON_ACCOUNTS_KEY: [
|
|
297
|
+
{
|
|
298
|
+
JSON_COMMAND_KEY: "claude",
|
|
299
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY: 58.0,
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
JSON_COMMAND_KEY: "claude-ev",
|
|
303
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
|
|
304
|
+
JSON_ERROR_KEY: "probe failed",
|
|
305
|
+
},
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def test_cli_writes_json_accounts_report(
|
|
311
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
312
|
+
) -> None:
|
|
313
|
+
config_file = _write_chain_config(
|
|
314
|
+
tmp_path,
|
|
315
|
+
[
|
|
316
|
+
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
317
|
+
_entry("claude-ev", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
318
|
+
],
|
|
319
|
+
)
|
|
320
|
+
monkeypatch.setattr(
|
|
321
|
+
usage,
|
|
322
|
+
"weekly_utilization_probe",
|
|
323
|
+
_probe_from_utilization_by_path(
|
|
324
|
+
{
|
|
325
|
+
PLACEHOLDER_CREDENTIALS_PRIMARY: 42.0,
|
|
326
|
+
PLACEHOLDER_CREDENTIALS_SECONDARY: usage.WeeklyUtilizationProbeError(
|
|
327
|
+
"no token"
|
|
328
|
+
),
|
|
329
|
+
}
|
|
330
|
+
),
|
|
331
|
+
)
|
|
332
|
+
exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(config_file)])
|
|
333
|
+
captured = capsys.readouterr()
|
|
334
|
+
assert exit_code == 0
|
|
335
|
+
assert captured.err == ""
|
|
336
|
+
payload = json.loads(captured.out)
|
|
337
|
+
assert payload == {
|
|
338
|
+
JSON_ACCOUNTS_KEY: [
|
|
339
|
+
{
|
|
340
|
+
JSON_COMMAND_KEY: "claude",
|
|
341
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY: FULL_WEEKLY_PERCENT - 42.0,
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
JSON_COMMAND_KEY: "claude-ev",
|
|
345
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
|
|
346
|
+
JSON_ERROR_KEY: "no token",
|
|
347
|
+
},
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def test_cli_missing_config_prints_stderr_and_returns_config_error(
|
|
353
|
+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
354
|
+
) -> None:
|
|
355
|
+
missing_config = tmp_path / "absent-chain.json"
|
|
356
|
+
exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(missing_config)])
|
|
357
|
+
captured = capsys.readouterr()
|
|
358
|
+
assert exit_code == CHAIN_CONFIG_ERROR_EXIT_CODE
|
|
359
|
+
assert captured.out == ""
|
|
360
|
+
assert "not found" in captured.err.lower() or "claude chain" in captured.err.lower()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def test_weekly_remaining_from_utilization_uses_full_scale() -> None:
|
|
364
|
+
assert usage.weekly_remaining_from_utilization(9.0) == pytest.approx(91.0)
|
|
365
|
+
assert usage.weekly_remaining_from_utilization(0.0) == pytest.approx(
|
|
366
|
+
FULL_WEEKLY_PERCENT
|
|
367
|
+
)
|
|
368
|
+
assert usage.weekly_remaining_from_utilization(100.0) == pytest.approx(0.0)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def test_probe_weekly_utilization_reuses_resolver_windows(
|
|
372
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
373
|
+
) -> None:
|
|
374
|
+
class _FakeWindows:
|
|
375
|
+
weekly_utilization = 37.5
|
|
376
|
+
|
|
377
|
+
class _FakeResolver:
|
|
378
|
+
def read_oauth_access_token(self, credentials_path: Path, now: object) -> str:
|
|
379
|
+
assert credentials_path == Path(PLACEHOLDER_CREDENTIALS_PRIMARY)
|
|
380
|
+
return "token-value"
|
|
381
|
+
|
|
382
|
+
def resolve_access_token(self, credentials_path: Path, now: object) -> str:
|
|
383
|
+
raise AssertionError("probe must not fall back to session ingress")
|
|
384
|
+
|
|
385
|
+
def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
|
|
386
|
+
assert access_token == "token-value"
|
|
387
|
+
return {"seven_day": {"utilization": 37.5}}
|
|
388
|
+
|
|
389
|
+
def extract_usage_windows(
|
|
390
|
+
self, usage_payload: dict[str, object]
|
|
391
|
+
) -> _FakeWindows:
|
|
392
|
+
assert usage_payload == {"seven_day": {"utilization": 37.5}}
|
|
393
|
+
return _FakeWindows()
|
|
394
|
+
|
|
395
|
+
monkeypatch.setattr(
|
|
396
|
+
usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
|
|
397
|
+
)
|
|
398
|
+
weekly_utilization = usage._probe_weekly_utilization(
|
|
399
|
+
Path(PLACEHOLDER_CREDENTIALS_PRIMARY)
|
|
400
|
+
)
|
|
401
|
+
assert weekly_utilization == pytest.approx(37.5)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def test_probe_weekly_utilization_raises_when_token_missing(
|
|
405
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
406
|
+
) -> None:
|
|
407
|
+
class _FakeResolver:
|
|
408
|
+
def read_oauth_access_token(
|
|
409
|
+
self, credentials_path: Path, now: object
|
|
410
|
+
) -> None:
|
|
411
|
+
return None
|
|
412
|
+
|
|
413
|
+
def resolve_access_token(self, credentials_path: Path, now: object) -> str:
|
|
414
|
+
return "ingress-token-must-not-be-used"
|
|
415
|
+
|
|
416
|
+
def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
|
|
417
|
+
raise AssertionError("must not probe with an ingress fallback token")
|
|
418
|
+
|
|
419
|
+
monkeypatch.setattr(
|
|
420
|
+
usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
|
|
421
|
+
)
|
|
422
|
+
with pytest.raises(usage.WeeklyUtilizationProbeError):
|
|
423
|
+
usage._probe_weekly_utilization(Path(PLACEHOLDER_CREDENTIALS_PRIMARY))
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def test_probe_ignores_ingress_when_credential_token_missing(
|
|
427
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
428
|
+
) -> None:
|
|
429
|
+
config_file = _write_chain_config(
|
|
430
|
+
tmp_path,
|
|
431
|
+
[_entry("claude-ev", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY)],
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
class _FakeResolver:
|
|
435
|
+
def read_oauth_access_token(
|
|
436
|
+
self, credentials_path: Path, now: object
|
|
437
|
+
) -> None:
|
|
438
|
+
return None
|
|
439
|
+
|
|
440
|
+
def resolve_access_token(self, credentials_path: Path, now: object) -> str:
|
|
441
|
+
return "ingress-token-must-not-be-used"
|
|
442
|
+
|
|
443
|
+
def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
|
|
444
|
+
raise AssertionError("must not probe with an ingress fallback token")
|
|
445
|
+
|
|
446
|
+
monkeypatch.setattr(
|
|
447
|
+
usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
|
|
448
|
+
)
|
|
449
|
+
monkeypatch.setattr(usage, "weekly_utilization_probe", usage._probe_weekly_utilization)
|
|
450
|
+
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
451
|
+
assert all_reports[0].weekly_remaining_percent is None
|
|
452
|
+
assert all_reports[0].error is not None
|
|
453
|
+
assert "bearer token" in all_reports[0].error.lower()
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def test_load_failure_yields_per_account_error_and_cli_exits_zero(
|
|
457
|
+
tmp_path: Path,
|
|
458
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
459
|
+
capsys: pytest.CaptureFixture[str],
|
|
460
|
+
) -> None:
|
|
461
|
+
config_file = _write_chain_config(
|
|
462
|
+
tmp_path,
|
|
463
|
+
[
|
|
464
|
+
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
465
|
+
_entry("claude-ev", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
466
|
+
],
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
def active_probe(credentials_path: Path) -> float:
|
|
470
|
+
path_key = credentials_path.as_posix()
|
|
471
|
+
if path_key == PLACEHOLDER_CREDENTIALS_PRIMARY:
|
|
472
|
+
raise ImportError("usage probe module failed to import")
|
|
473
|
+
return 25.0
|
|
474
|
+
|
|
475
|
+
monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
|
|
476
|
+
exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(config_file)])
|
|
477
|
+
captured = capsys.readouterr()
|
|
478
|
+
assert exit_code == 0
|
|
479
|
+
payload = json.loads(captured.out)
|
|
480
|
+
assert payload[JSON_ACCOUNTS_KEY][0][JSON_WEEKLY_REMAINING_PERCENT_KEY] is None
|
|
481
|
+
assert "failed to import" in payload[JSON_ACCOUNTS_KEY][0][JSON_ERROR_KEY]
|
|
482
|
+
assert payload[JSON_ACCOUNTS_KEY][1][JSON_WEEKLY_REMAINING_PERCENT_KEY] == pytest.approx(
|
|
483
|
+
FULL_WEEKLY_PERCENT - 25.0
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def test_failed_exec_module_does_not_poison_sys_modules(
|
|
488
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
489
|
+
) -> None:
|
|
490
|
+
if RESOLVE_USAGE_WINDOW_MODULE_NAME in sys.modules:
|
|
491
|
+
del sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME]
|
|
492
|
+
(tmp_path / "resolve_usage_window.py").write_text(
|
|
493
|
+
"raise ImportError('intentional load failure')\n", encoding="utf-8"
|
|
494
|
+
)
|
|
495
|
+
monkeypatch.setattr(usage, "_usage_pause_scripts_directory", lambda: tmp_path)
|
|
496
|
+
with pytest.raises(ImportError, match="intentional load failure"):
|
|
497
|
+
usage._load_resolve_usage_window_module()
|
|
498
|
+
assert RESOLVE_USAGE_WINDOW_MODULE_NAME not in sys.modules
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def test_load_resolve_usage_window_module_loads_real_probe_api() -> None:
|
|
502
|
+
if RESOLVE_USAGE_WINDOW_MODULE_NAME in sys.modules:
|
|
503
|
+
del sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME]
|
|
504
|
+
loaded_module = usage._load_resolve_usage_window_module()
|
|
505
|
+
assert callable(loaded_module.read_oauth_access_token)
|
|
506
|
+
assert callable(loaded_module.resolve_access_token)
|
|
507
|
+
assert callable(loaded_module.extract_usage_windows)
|
|
508
|
+
assert callable(loaded_module._fetch_usage_payload)
|
|
509
|
+
assert sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME] is loaded_module
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def test_load_resolve_usage_window_module_reuses_cached_module() -> None:
|
|
513
|
+
first_load = usage._load_resolve_usage_window_module()
|
|
514
|
+
second_load = usage._load_resolve_usage_window_module()
|
|
515
|
+
assert first_load is second_load
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def test_usage_module_imports_without_preloading_runner() -> None:
|
|
519
|
+
scripts_directory = str(_SCRIPTS_DIR)
|
|
520
|
+
import_probe = (
|
|
521
|
+
"import sys; "
|
|
522
|
+
f"sys.path.insert(0, {scripts_directory!r}); "
|
|
523
|
+
"import claude_chain_usage; "
|
|
524
|
+
"assert callable(claude_chain_usage.report_chain_weekly_usage); "
|
|
525
|
+
"assert callable(claude_chain_usage.rank_accounts_by_weekly_remaining)"
|
|
526
|
+
)
|
|
527
|
+
completed = subprocess.run(
|
|
528
|
+
[sys.executable, "-S", "-E", "-c", import_probe],
|
|
529
|
+
capture_output=True,
|
|
530
|
+
text=True,
|
|
531
|
+
timeout=60,
|
|
532
|
+
check=False,
|
|
533
|
+
)
|
|
534
|
+
assert completed.returncode == 0, completed.stderr
|
|
@@ -30,32 +30,13 @@ The moment it edits a file or runs a test itself, the pairing breaks —
|
|
|
30
30
|
its own tool use stays orchestration, run-artifact writes, and light
|
|
31
31
|
verification reads.
|
|
32
32
|
|
|
33
|
-
## Design rule — thin prompts, thick artifacts, thin skill
|
|
34
|
-
|
|
35
|
-
Three faces of one rule (reading pinned by the audit spec on
|
|
36
|
-
jl-cmd/claude-dev-env#174: thin means high-signal, not short; thick
|
|
37
|
-
context lives outside the window, reachable by pointers):
|
|
38
|
-
|
|
39
|
-
- **Thin prompts.** A spawn prompt is a dispatch ticket: one task,
|
|
40
|
-
pointers to the artifacts that hold the context, one done-check, a thin
|
|
41
|
-
return contract. The prompt can be thin because the context is thick
|
|
42
|
-
elsewhere.
|
|
43
|
-
- **Thick artifacts.** Durable run state lives in files — the run
|
|
44
|
-
charter, one assignment file per task, result records — written once,
|
|
45
|
-
read by every agent that needs them, and still there when any one
|
|
46
|
-
agent's context is gone.
|
|
47
|
-
- **Thin skill.** This file holds only what changes decisions at
|
|
48
|
-
orchestration time. Standing policy lives where it loads once:
|
|
49
|
-
[`_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md)
|
|
50
|
-
for everything advisor, the agent definitions for executor discipline.
|
|
51
|
-
|
|
52
33
|
## Process
|
|
53
34
|
|
|
54
35
|
1. **Invocation guard.** One `/orchestrator` per session. When the
|
|
55
36
|
refresh loop is already running, do not schedule a second one; reuse
|
|
56
37
|
the live advisor bind and go to step 4.
|
|
57
38
|
2. **Register the discipline reminder.** Schedule it with
|
|
58
|
-
`ScheduleWakeup` at `delaySeconds:
|
|
39
|
+
`ScheduleWakeup` at `delaySeconds: 2700`, prompt
|
|
59
40
|
`/orchestrator-refresh`, where each refresh re-schedules the next one.
|
|
60
41
|
3. **Bind the shared advisor before any executor.** Follow
|
|
61
42
|
[`_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orchestrator-refresh
|
|
3
3
|
description: >-
|
|
4
|
-
Fired by the /orchestrator loop reminder about every
|
|
4
|
+
Fired by the /orchestrator loop reminder about every 45 minutes to
|
|
5
5
|
re-assert the advisor discipline mid-run: orchestrate, route hard decisions
|
|
6
6
|
to the shared advisor (ENDORSE / CORRECTION / PLAN / STOP — SendMessage on
|
|
7
7
|
Claude, Claude CLI chain on a third-party host), reuse warm agents. Triggers:
|
|
@@ -195,7 +195,7 @@ the `persistent_agents` map
|
|
|
195
195
|
record it, and continue the tick. Never abort a tick on a stale id;
|
|
196
196
|
never retry the same dead id.
|
|
197
197
|
- **Fresh every round (never persisted):** the Step 5 host-aware
|
|
198
|
-
`invoke_code_review.py` / `/code-review
|
|
198
|
+
`invoke_code_review.py` / `/code-review xhigh --fix` pass and the Step 6
|
|
199
199
|
bugteam audit (unbiased eyes each round; the enforcer needs the formal
|
|
200
200
|
Skill call), and every `code-verifier` — a named code-verifier never fires
|
|
201
201
|
`SubagentStop`, so no verdict mints (see the named-`code-verifier` entry
|
|
@@ -253,7 +253,7 @@ post a fresh PR in a fresh branch based on origin main to the user.
|
|
|
253
253
|
Windows is Git Bash which cannot execute PowerShell cmdlets. Route all
|
|
254
254
|
PowerShell work through the PowerShell tool or `pwsh -NoProfile -File`.
|
|
255
255
|
- **Cross-repo PR: route cwd into the PR worktree before Step 5 review** —
|
|
256
|
-
`invoke_code_review.py` and `/code-review
|
|
256
|
+
`invoke_code_review.py` and `/code-review xhigh --fix` audit the repo of the
|
|
257
257
|
cwd (the helper's `--cwd`). When the session is rooted in a different repo
|
|
258
258
|
than the PR, `EnterWorktree` cannot re-root (it is scoped to the session's
|
|
259
259
|
repo); resolve the PR worktree and `cd` into it per
|
|
@@ -351,7 +351,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
351
351
|
- [ ] **Static sweep fails** → apply shared fix protocol → push → reset markers
|
|
352
352
|
→ stay CODE_REVIEW → Step 5
|
|
353
353
|
- [ ] **`mode == in_session`** (Claude host, session model opus) → run
|
|
354
|
-
`/code-review
|
|
354
|
+
`/code-review xhigh --fix` in-session (no path args)
|
|
355
355
|
- [ ] **`mode == chain`** (any other host or non-opus session) → helper
|
|
356
356
|
already ran the headless review; read `returncode`,
|
|
357
357
|
`served_command`, and `dirty_tree` from JSON
|
|
@@ -18,7 +18,7 @@ resets push-invalidated markers per [ground-rules.md](ground-rules.md) /
|
|
|
18
18
|
at 270s, re-runs the sweep next tick]
|
|
19
19
|
</example>
|
|
20
20
|
|
|
21
|
-
<example> CODE_REVIEW tick, static sweep clean, `/code-review
|
|
21
|
+
<example> CODE_REVIEW tick, static sweep clean, `/code-review xhigh --fix`
|
|
22
22
|
applies fixes to the working tree. Claude: [commits the applied fixes in one
|
|
23
23
|
commit, pushes, resets push-invalidated markers per
|
|
24
24
|
[ground-rules.md](ground-rules.md) / [state-schema.md](state-schema.md) (all
|
|
@@ -26,7 +26,7 @@ commit, pushes, resets push-invalidated markers per
|
|
|
26
26
|
stays `phase = CODE_REVIEW`, Step 4 at 270s, returns]
|
|
27
27
|
</example>
|
|
28
28
|
|
|
29
|
-
<example> CODE_REVIEW tick, static sweep clean and `/code-review
|
|
29
|
+
<example> CODE_REVIEW tick, static sweep clean and `/code-review xhigh --fix`
|
|
30
30
|
clean (no changes applied). Claude: [sets `code_review_clean_at = HEAD`,
|
|
31
31
|
`phase = BUGTEAM`, runs `Skill({skill: "bugteam", ...})` in same tick]
|
|
32
32
|
</example>
|
|
@@ -94,7 +94,7 @@ applies **Convergence** from `workflows/schedule-wakeup-loop.md`]
|
|
|
94
94
|
|
|
95
95
|
<example> CODE_REVIEW tick, review body says "found 3 potential issues"
|
|
96
96
|
against HEAD (a stale prior finding) but the diff is clean. Claude: [the static
|
|
97
|
-
sweep and `/code-review
|
|
97
|
+
sweep and `/code-review xhigh --fix` both pass, sets `code_review_clean_at =
|
|
98
98
|
HEAD`, `phase = BUGTEAM`]
|
|
99
99
|
</example>
|
|
100
100
|
|
|
@@ -32,6 +32,6 @@ files during fix phase in multi-PR mode.
|
|
|
32
32
|
regardless of phase. A new commit **resets the full convergence cycle**: a
|
|
33
33
|
code-review clean, a bugteam clean, and a Bugbot clean on an older SHA do
|
|
34
34
|
**not** count toward convergence on the new `HEAD`. Re-run the static sweep and
|
|
35
|
-
`/code-review
|
|
35
|
+
`/code-review xhigh --fix` on `current_head`, then bugteam, then the terminal
|
|
36
36
|
Bugbot gate, all on the same `HEAD` with no intervening push. Re-entering in the
|
|
37
37
|
same tick saves a wakeup cycle.
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
- **All `*_clean_at`, `merge_state_status`, and `bugbot_down` / `codex_down` reset on every push.**
|
|
11
11
|
- **`bugbot run` comment is load-bearing.** Literal phrase exactly —
|
|
12
12
|
empirically the only re-trigger Cursor Bugbot recognizes.
|
|
13
|
-
- **Production edits go through `clean-coder`, except `/code-review
|
|
13
|
+
- **Production edits go through `clean-coder`, except `/code-review xhigh --fix`.**
|
|
14
14
|
The lead never hand-edits production files. Every bugbot, bugteam,
|
|
15
15
|
Copilot, or Claude finding spawns `Agent(subagent_type="clean-coder")` to
|
|
16
16
|
apply the fix. The CODE_REVIEW phase is the one exception: `/code-review
|
|
17
|
-
|
|
17
|
+
xhigh --fix` applies its own findings to the working tree, which the next
|
|
18
18
|
CODE_REVIEW/BUGTEAM cycle re-reviews after the loop resets.
|
|
19
19
|
- **Adapt when reality contradicts on-disk state.** If `state.json`,
|
|
20
20
|
`git`, or `gh` disagree with live PR, escalate as hard blocker per
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- **Cross-repo cwd routing is routine, not a fork.** When the PR under
|
|
23
23
|
convergence lives in a different repo than the session is rooted in, route
|
|
24
24
|
the working directory into a checkout of the PR's repo automatically —
|
|
25
|
-
`/code-review
|
|
25
|
+
`/code-review xhigh --fix`, `git`, and every `clean-coder` fix spawn act on the
|
|
26
26
|
repo of the current working directory. The resolution is fixed
|
|
27
27
|
([per-tick.md § Step 1.5](per-tick.md)): resolve the PR worktree, `cd` into
|
|
28
28
|
it, run local work there. Do not pause, ask, or raise it as a material fork.
|