claude-dev-env 2.2.1 → 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/_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/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/config/verified_commit_constants.py +11 -0
- package/hooks/blocking/test_code_verifier_tools_contract.py +28 -0
- package/hooks/blocking/test_verification_verdict_store.py +93 -0
- package/hooks/blocking/verification_verdict_store.py +91 -0
- package/package.json +1 -1
- package/scripts/CLAUDE.md +2 -1
- 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
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Report remaining weekly usage for every account in the Claude chain.
|
|
3
|
+
|
|
4
|
+
Loads ``~/.claude/claude-chain.json`` (or a path the caller names), probes each
|
|
5
|
+
entry's weekly utilization through the usage-pause OAuth endpoint, and prints a
|
|
6
|
+
JSON report. Ranking is available as an importable helper; ``run_claude``
|
|
7
|
+
consumes that ranking to choose try order.
|
|
8
|
+
|
|
9
|
+
::
|
|
10
|
+
|
|
11
|
+
python claude_chain_usage.py
|
|
12
|
+
{"accounts": [
|
|
13
|
+
{"command": "claude", "weekly_remaining_percent": 58.0},
|
|
14
|
+
{"command": "claude-ev", "weekly_remaining_percent": null, "error": "..."}
|
|
15
|
+
]}
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
import http.client
|
|
22
|
+
import importlib.util
|
|
23
|
+
import json
|
|
24
|
+
import sys
|
|
25
|
+
import urllib.error
|
|
26
|
+
from collections.abc import Callable
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from types import ModuleType
|
|
31
|
+
|
|
32
|
+
from dev_env_scripts_constants.claude_chain_constants import CHAIN_CONFIG_ERROR_EXIT_CODE
|
|
33
|
+
from dev_env_scripts_constants.claude_chain_usage_constants import (
|
|
34
|
+
CLI_CONFIG_PATH_FLAG,
|
|
35
|
+
FULL_WEEKLY_PERCENT,
|
|
36
|
+
JSON_ACCOUNTS_KEY,
|
|
37
|
+
JSON_COMMAND_KEY,
|
|
38
|
+
JSON_ERROR_KEY,
|
|
39
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY,
|
|
40
|
+
NO_ACCESS_TOKEN_ERROR_TEMPLATE,
|
|
41
|
+
RESOLVE_USAGE_WINDOW_FILENAME,
|
|
42
|
+
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE,
|
|
43
|
+
RESOLVE_USAGE_WINDOW_MODULE_NAME,
|
|
44
|
+
USAGE_PAUSE_SCRIPTS_DIRECTORY_NAME,
|
|
45
|
+
USAGE_PAUSE_SKILL_DIRECTORY_NAME,
|
|
46
|
+
USAGE_PAUSE_SKILL_NAME,
|
|
47
|
+
USAGE_PROBE_FAILED_ERROR_TEMPLATE,
|
|
48
|
+
WEEKLY_UTILIZATION_MISSING_ERROR,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
import claude_chain_runner as chain_runner
|
|
52
|
+
|
|
53
|
+
WeeklyUtilizationProbe = Callable[[Path], float]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class WeeklyUtilizationProbeError(Exception):
|
|
57
|
+
"""Raised when the weekly_utilization_probe cannot measure one account."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class AccountUsageReport:
|
|
62
|
+
"""One chain account's remaining weekly usage, or the reason it is unknown."""
|
|
63
|
+
|
|
64
|
+
command: str
|
|
65
|
+
weekly_remaining_percent: float | None
|
|
66
|
+
error: str | None = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _default_credentials_path() -> Path:
|
|
70
|
+
usage_window_resolver = _load_resolve_usage_window_module()
|
|
71
|
+
return Path.home().joinpath(
|
|
72
|
+
*usage_window_resolver.ALL_CREDENTIALS_RELATIVE_PATH_PARTS
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _credentials_path_for_entry(entry: chain_runner.ChainEntry) -> Path:
|
|
77
|
+
if entry.credentials_path is None:
|
|
78
|
+
return _default_credentials_path()
|
|
79
|
+
return Path(entry.credentials_path).expanduser()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _usage_pause_scripts_directory() -> Path:
|
|
83
|
+
package_root = Path(__file__).resolve().parent.parent
|
|
84
|
+
return (
|
|
85
|
+
package_root
|
|
86
|
+
/ USAGE_PAUSE_SKILL_DIRECTORY_NAME
|
|
87
|
+
/ USAGE_PAUSE_SKILL_NAME
|
|
88
|
+
/ USAGE_PAUSE_SCRIPTS_DIRECTORY_NAME
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _discard_sys_path_entry(path_text: str) -> None:
|
|
93
|
+
if path_text in sys.path:
|
|
94
|
+
sys.path.remove(path_text)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _load_resolve_usage_window_module() -> ModuleType:
|
|
98
|
+
already_loaded = sys.modules.get(RESOLVE_USAGE_WINDOW_MODULE_NAME)
|
|
99
|
+
if already_loaded is not None:
|
|
100
|
+
return already_loaded
|
|
101
|
+
scripts_directory = _usage_pause_scripts_directory()
|
|
102
|
+
module_path = scripts_directory / RESOLVE_USAGE_WINDOW_FILENAME
|
|
103
|
+
if not module_path.is_file():
|
|
104
|
+
raise WeeklyUtilizationProbeError(
|
|
105
|
+
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE.format(
|
|
106
|
+
module_path=module_path
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
scripts_directory_text = str(scripts_directory)
|
|
110
|
+
was_path_inserted = False
|
|
111
|
+
if scripts_directory_text not in sys.path:
|
|
112
|
+
sys.path.insert(0, scripts_directory_text)
|
|
113
|
+
was_path_inserted = True
|
|
114
|
+
is_module_ready = False
|
|
115
|
+
try:
|
|
116
|
+
module_specification = importlib.util.spec_from_file_location(
|
|
117
|
+
RESOLVE_USAGE_WINDOW_MODULE_NAME, module_path
|
|
118
|
+
)
|
|
119
|
+
if module_specification is None or module_specification.loader is None:
|
|
120
|
+
raise WeeklyUtilizationProbeError(
|
|
121
|
+
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE.format(
|
|
122
|
+
module_path=module_path
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
loaded_module = importlib.util.module_from_spec(module_specification)
|
|
126
|
+
sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME] = loaded_module
|
|
127
|
+
module_specification.loader.exec_module(loaded_module)
|
|
128
|
+
is_module_ready = True
|
|
129
|
+
finally:
|
|
130
|
+
if not is_module_ready:
|
|
131
|
+
sys.modules.pop(RESOLVE_USAGE_WINDOW_MODULE_NAME, None)
|
|
132
|
+
if was_path_inserted:
|
|
133
|
+
_discard_sys_path_entry(scripts_directory_text)
|
|
134
|
+
return loaded_module
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _probe_weekly_utilization(credentials_path: Path) -> float:
|
|
138
|
+
usage_window_resolver = _load_resolve_usage_window_module()
|
|
139
|
+
now = datetime.now().astimezone()
|
|
140
|
+
try:
|
|
141
|
+
access_token = usage_window_resolver.read_oauth_access_token(
|
|
142
|
+
credentials_path, now
|
|
143
|
+
)
|
|
144
|
+
if access_token is None:
|
|
145
|
+
raise WeeklyUtilizationProbeError(
|
|
146
|
+
NO_ACCESS_TOKEN_ERROR_TEMPLATE.format(credentials_path=credentials_path)
|
|
147
|
+
)
|
|
148
|
+
usage_payload = usage_window_resolver._fetch_usage_payload(access_token)
|
|
149
|
+
except WeeklyUtilizationProbeError:
|
|
150
|
+
raise
|
|
151
|
+
except (
|
|
152
|
+
urllib.error.URLError,
|
|
153
|
+
http.client.HTTPException,
|
|
154
|
+
TimeoutError,
|
|
155
|
+
OSError,
|
|
156
|
+
ValueError,
|
|
157
|
+
) as probe_error:
|
|
158
|
+
raise WeeklyUtilizationProbeError(
|
|
159
|
+
USAGE_PROBE_FAILED_ERROR_TEMPLATE.format(error=probe_error)
|
|
160
|
+
) from probe_error
|
|
161
|
+
usage_windows = usage_window_resolver.extract_usage_windows(usage_payload)
|
|
162
|
+
if usage_windows.weekly_utilization is None:
|
|
163
|
+
raise WeeklyUtilizationProbeError(WEEKLY_UTILIZATION_MISSING_ERROR)
|
|
164
|
+
return float(usage_windows.weekly_utilization)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
weekly_utilization_probe: WeeklyUtilizationProbe = _probe_weekly_utilization
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def weekly_remaining_from_utilization(weekly_utilization: float) -> float:
|
|
171
|
+
"""Convert weekly utilization percent into remaining weekly percent.
|
|
172
|
+
|
|
173
|
+
::
|
|
174
|
+
|
|
175
|
+
weekly_remaining_from_utilization(42.0) -> 58.0
|
|
176
|
+
weekly_remaining_from_utilization(9.0) -> 91.0
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
weekly_utilization: Used weekly capacity on the 0–100 scale.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Remaining weekly capacity on the same 0–100 scale.
|
|
183
|
+
"""
|
|
184
|
+
return FULL_WEEKLY_PERCENT - weekly_utilization
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _report_for_entry(
|
|
188
|
+
entry: chain_runner.ChainEntry,
|
|
189
|
+
active_probe: WeeklyUtilizationProbe,
|
|
190
|
+
) -> AccountUsageReport:
|
|
191
|
+
try:
|
|
192
|
+
credentials_path = _credentials_path_for_entry(entry)
|
|
193
|
+
weekly_utilization = active_probe(credentials_path)
|
|
194
|
+
except (WeeklyUtilizationProbeError, ImportError, AttributeError) as probe_error:
|
|
195
|
+
return AccountUsageReport(
|
|
196
|
+
command=entry.command,
|
|
197
|
+
weekly_remaining_percent=None,
|
|
198
|
+
error=str(probe_error),
|
|
199
|
+
)
|
|
200
|
+
return AccountUsageReport(
|
|
201
|
+
command=entry.command,
|
|
202
|
+
weekly_remaining_percent=weekly_remaining_from_utilization(
|
|
203
|
+
weekly_utilization
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def report_chain_weekly_usage(*, config_path: Path) -> list[AccountUsageReport]:
|
|
209
|
+
"""Report remaining weekly usage for every account in the chain config.
|
|
210
|
+
|
|
211
|
+
::
|
|
212
|
+
|
|
213
|
+
report_chain_weekly_usage(config_path=Path("chain.json"))
|
|
214
|
+
-> [AccountUsageReport("claude", 58.0), ...]
|
|
215
|
+
|
|
216
|
+
Uses the module-level ``weekly_utilization_probe`` (tests rebind it).
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
config_path: Chain configuration file to load.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
One report per chain entry, in config order.
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
ChainConfigurationError: When the chain configuration cannot be loaded.
|
|
226
|
+
"""
|
|
227
|
+
all_entries = chain_runner.load_chain(config_path)
|
|
228
|
+
return [
|
|
229
|
+
_report_for_entry(each_entry, weekly_utilization_probe)
|
|
230
|
+
for each_entry in all_entries
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _nulls_last_remaining_rank(report: AccountUsageReport) -> tuple[int, float]:
|
|
235
|
+
if report.weekly_remaining_percent is None:
|
|
236
|
+
return (1, 0.0)
|
|
237
|
+
return (0, -report.weekly_remaining_percent)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def rank_accounts_by_weekly_remaining(
|
|
241
|
+
all_reports: list[AccountUsageReport],
|
|
242
|
+
) -> list[AccountUsageReport]:
|
|
243
|
+
"""Order reports by remaining weekly usage, highest first.
|
|
244
|
+
|
|
245
|
+
::
|
|
246
|
+
|
|
247
|
+
[40%, 70%, null, 70%] -> [70% (earlier), 70% (later), 40%, null]
|
|
248
|
+
|
|
249
|
+
Ties keep config order. Unmeasurable accounts (null remaining) sort last.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
all_reports: Reports in chain-config order.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
A new list ordered by remaining weekly percent descending.
|
|
256
|
+
"""
|
|
257
|
+
return sorted(all_reports, key=_nulls_last_remaining_rank)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _account_report_to_json_mapping(
|
|
261
|
+
report: AccountUsageReport,
|
|
262
|
+
) -> dict[str, str | float | None]:
|
|
263
|
+
if report.weekly_remaining_percent is None:
|
|
264
|
+
return {
|
|
265
|
+
JSON_COMMAND_KEY: report.command,
|
|
266
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
|
|
267
|
+
JSON_ERROR_KEY: report.error,
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
JSON_COMMAND_KEY: report.command,
|
|
271
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY: report.weekly_remaining_percent,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def reports_to_json_payload(
|
|
276
|
+
all_reports: list[AccountUsageReport],
|
|
277
|
+
) -> dict[str, list[dict[str, str | float | None]]]:
|
|
278
|
+
"""Build the CLI JSON payload for a list of account reports.
|
|
279
|
+
|
|
280
|
+
::
|
|
281
|
+
|
|
282
|
+
reports_to_json_payload([AccountUsageReport("claude", 58.0)])
|
|
283
|
+
-> {"accounts": [{"command": "claude", "weekly_remaining_percent": 58.0}]}
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
all_reports: Reports in the order they should appear in the payload.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
The ``{"accounts": [...]}`` envelope.
|
|
290
|
+
"""
|
|
291
|
+
return {
|
|
292
|
+
JSON_ACCOUNTS_KEY: [
|
|
293
|
+
_account_report_to_json_mapping(each_report)
|
|
294
|
+
for each_report in all_reports
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _build_argument_parser() -> argparse.ArgumentParser:
|
|
300
|
+
parser = argparse.ArgumentParser(
|
|
301
|
+
description=(
|
|
302
|
+
"Report remaining weekly usage for every account in the Claude chain."
|
|
303
|
+
)
|
|
304
|
+
)
|
|
305
|
+
parser.add_argument(
|
|
306
|
+
CLI_CONFIG_PATH_FLAG,
|
|
307
|
+
dest="config_path",
|
|
308
|
+
default=None,
|
|
309
|
+
help="Path to claude-chain.json; defaults to the per-user chain config.",
|
|
310
|
+
)
|
|
311
|
+
return parser
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def main(all_command_arguments: list[str]) -> int:
|
|
315
|
+
"""Probe the chain and print the weekly-usage JSON report on stdout.
|
|
316
|
+
|
|
317
|
+
::
|
|
318
|
+
|
|
319
|
+
main(["--config-path", "chain.json"]) -> 0 # JSON on stdout
|
|
320
|
+
main(["--config-path", "missing.json"]) -> 3 # error on stderr
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
all_command_arguments: The argument vector after the program name.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Zero on success, or the chain-config error exit code when the config
|
|
327
|
+
cannot be loaded.
|
|
328
|
+
"""
|
|
329
|
+
parser = _build_argument_parser()
|
|
330
|
+
parsed_arguments = parser.parse_args(all_command_arguments)
|
|
331
|
+
resolved_config_path = (
|
|
332
|
+
Path(parsed_arguments.config_path)
|
|
333
|
+
if parsed_arguments.config_path is not None
|
|
334
|
+
else chain_runner.chain_config_path()
|
|
335
|
+
)
|
|
336
|
+
try:
|
|
337
|
+
all_reports = report_chain_weekly_usage(config_path=resolved_config_path)
|
|
338
|
+
except chain_runner.ChainConfigurationError as configuration_error:
|
|
339
|
+
print(str(configuration_error), file=sys.stderr)
|
|
340
|
+
return CHAIN_CONFIG_ERROR_EXIT_CODE
|
|
341
|
+
print(json.dumps(reports_to_json_payload(all_reports), ensure_ascii=True))
|
|
342
|
+
return 0
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
if __name__ == "__main__":
|
|
346
|
+
sys.exit(main(sys.argv[1:]))
|
|
@@ -8,11 +8,12 @@ Named constants for scripts in `scripts/`. Follows the project convention that t
|
|
|
8
8
|
|---|---|
|
|
9
9
|
| `timing.py` | `sweep_empty_dirs.py` - `DEFAULT_AGE_SECONDS` (smallest age before an empty directory is eligible for removal) and `DEFAULT_POLL_INTERVAL` (seconds between sweep passes in continuous-watch mode); `spawn_grok_batch.py` - `WORKER_STAGGER_SECONDS` (seconds between staggered headless grok worker starts); `invoke_code_review.py` - `DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS` (timeout for one headless `/code-review` chain run) |
|
|
10
10
|
| `gh_artifact_upload_constants.py` | `gh_artifact_upload.py` - the `artifacts` release tag, title, and notes body, the GitHub CLI binary name, the asset-name timestamp format and template, the asset download URL template, the notes-file suffix, and the text encoding |
|
|
11
|
-
| `claude_chain_constants.py` | `claude_chain_runner.py` - the chain config filename and home subdirectory, the usage-limit signature text, the per-binary status labels, the default timeout, CLI flag and separator tokens, config JSON keys, invalid-shape reason text, config-error and exhausted-chain message templates, and CLI exit codes |
|
|
11
|
+
| `claude_chain_constants.py` | `claude_chain_runner.py` - the chain config filename and home subdirectory, the usage-limit signature text, the per-binary status labels, the default timeout, CLI flag and separator tokens, config JSON keys (including optional `credentials_path`), invalid-shape reason text, config-error and exhausted-chain message templates, and CLI exit codes |
|
|
12
|
+
| `claude_chain_usage_constants.py` | `claude_chain_usage.py` - full weekly percent scale, usage-pause skill path segments, CLI config-path flag, JSON report keys, and probe error message templates |
|
|
12
13
|
| `grok_worker_constants.py` | `grok_worker_preflight.py`, `grok_headless_runner.py`, and `spawn_grok_batch.py` - the `grok` binary name and CLI flags, model and subcommand tokens, leader-socket and scratch-file name parts, auth and usage-limit signature lists, outcome classifications, fallthrough reasons, tool-profile names and prompt headers, timeouts and turn caps (including launch-failure return code and post-kill grace), ping-cache keys and TTL, batch-spec and summary JSON keys, the prompt-part and report-stream join separators, and the CLI launch-error stderr prefix |
|
|
13
|
-
| `code_review_constants.py` | `invoke_code_review.py` - the `/code-review
|
|
14
|
+
| `code_review_constants.py` | `invoke_code_review.py` - the `/code-review xhigh --fix` prompt, opus model alias, permission-mode flag and value, result mode and JSON keys, session-model CLI flag, git dirty-check tokens, and in-session return markers |
|
|
14
15
|
| `__init__.py` | Empty package marker |
|
|
15
16
|
|
|
16
17
|
## Convention
|
|
17
18
|
|
|
18
|
-
Every constant is `UPPER_SNAKE_CASE` with an explicit type annotation and a docstring. Scripts import from here rather than embedding literal values in their bodies.
|
|
19
|
+
Every constant is `UPPER_SNAKE_CASE` with an explicit type annotation and a docstring. Scripts import from here rather than embedding literal values in their bodies.
|
|
@@ -18,6 +18,9 @@ CLAUDE_HOME_SUBDIRECTORY: str = ".claude"
|
|
|
18
18
|
CONFIG_FILENAME: str = "claude-chain.json"
|
|
19
19
|
"""Real chain-configuration filename read from the user's home directory."""
|
|
20
20
|
|
|
21
|
+
CHAIN_USAGE_MODULE_NAME: str = "claude_chain_usage"
|
|
22
|
+
"""Import name of the weekly-usage report module loaded lazily by the runner."""
|
|
23
|
+
|
|
21
24
|
EXAMPLE_CONFIG_FILENAME: str = "claude-chain.example.json"
|
|
22
25
|
"""Committed template filename referenced in the config-error guidance."""
|
|
23
26
|
|
|
@@ -30,6 +33,9 @@ CONFIG_COMMAND_KEY: str = "command"
|
|
|
30
33
|
CONFIG_EXTRA_ARGS_KEY: str = "extra_args"
|
|
31
34
|
"""Chain-entry key holding per-account arguments appended to each invocation."""
|
|
32
35
|
|
|
36
|
+
CONFIG_CREDENTIALS_PATH_KEY: str = "credentials_path"
|
|
37
|
+
"""Optional chain-entry key naming that account's OAuth credentials file path."""
|
|
38
|
+
|
|
33
39
|
ALL_USAGE_LIMIT_SIGNATURES: tuple[str, ...] = (
|
|
34
40
|
"hit your session limit",
|
|
35
41
|
"usage limit reached",
|
|
@@ -91,9 +97,15 @@ CONFIG_ENTRY_EXTRA_ARGS_INVALID_REASON: str = (
|
|
|
91
97
|
)
|
|
92
98
|
"""Reason detail when a chain entry's extra_args value is the wrong shape."""
|
|
93
99
|
|
|
100
|
+
CONFIG_ENTRY_CREDENTIALS_PATH_INVALID_REASON: str = (
|
|
101
|
+
"a chain entry's 'credentials_path' is not a non-empty string"
|
|
102
|
+
)
|
|
103
|
+
"""Reason detail when a chain entry's credentials_path value is the wrong shape."""
|
|
104
|
+
|
|
94
105
|
CONFIG_MISSING_MESSAGE_TEMPLATE: str = (
|
|
95
106
|
"Claude chain config not found at {config_path}. Copy {example_filename} to "
|
|
96
|
-
"{config_path} and list your
|
|
107
|
+
"{config_path} and list your account binaries. Try order comes from weekly "
|
|
108
|
+
"remaining; config order is the tiebreak."
|
|
97
109
|
)
|
|
98
110
|
"""Guidance shown when the config file is absent."""
|
|
99
111
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Named constants for the claude chain weekly-usage report tool.
|
|
2
|
+
|
|
3
|
+
Per the project's configuration conventions, every scalar and structural
|
|
4
|
+
constant the usage reporter needs lives here rather than inline in the module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
FULL_WEEKLY_PERCENT: float = 100.0
|
|
10
|
+
"""Percent scale ceiling for weekly utilization (remaining = this minus used)."""
|
|
11
|
+
|
|
12
|
+
USAGE_PAUSE_SKILL_DIRECTORY_NAME: str = "skills"
|
|
13
|
+
"""Package-root directory name that holds skill trees (sibling of ``scripts/``)."""
|
|
14
|
+
|
|
15
|
+
USAGE_PAUSE_SKILL_NAME: str = "usage-pause"
|
|
16
|
+
"""Skill directory name under ``skills/`` that owns the OAuth usage probe."""
|
|
17
|
+
|
|
18
|
+
USAGE_PAUSE_SCRIPTS_DIRECTORY_NAME: str = "scripts"
|
|
19
|
+
"""Directory under the usage-pause skill that holds the OAuth probe module."""
|
|
20
|
+
|
|
21
|
+
RESOLVE_USAGE_WINDOW_MODULE_NAME: str = "resolve_usage_window"
|
|
22
|
+
"""Module name of the usage-pause OAuth probe loader."""
|
|
23
|
+
|
|
24
|
+
RESOLVE_USAGE_WINDOW_FILENAME: str = "resolve_usage_window.py"
|
|
25
|
+
"""Filename of the usage-pause OAuth probe module on disk."""
|
|
26
|
+
|
|
27
|
+
JSON_ACCOUNTS_KEY: str = "accounts"
|
|
28
|
+
"""Top-level key in the CLI JSON report holding the per-account list."""
|
|
29
|
+
|
|
30
|
+
JSON_COMMAND_KEY: str = "command"
|
|
31
|
+
"""Per-account JSON key naming the chain binary."""
|
|
32
|
+
|
|
33
|
+
JSON_WEEKLY_REMAINING_PERCENT_KEY: str = "weekly_remaining_percent"
|
|
34
|
+
"""Per-account JSON key for remaining weekly usage percent (or null)."""
|
|
35
|
+
|
|
36
|
+
JSON_ERROR_KEY: str = "error"
|
|
37
|
+
"""Per-account JSON key carrying the unmeasurable-reason string."""
|
|
38
|
+
|
|
39
|
+
CLI_CONFIG_PATH_FLAG: str = "--config-path"
|
|
40
|
+
"""CLI flag that overrides the chain configuration file path."""
|
|
41
|
+
|
|
42
|
+
NO_ACCESS_TOKEN_ERROR_TEMPLATE: str = (
|
|
43
|
+
"no usable bearer token from the OAuth credential file at {credentials_path}"
|
|
44
|
+
)
|
|
45
|
+
"""Error when the entry credential file yields no usable OAuth bearer."""
|
|
46
|
+
|
|
47
|
+
WEEKLY_UTILIZATION_MISSING_ERROR: str = (
|
|
48
|
+
"usage response carried no weekly utilization"
|
|
49
|
+
)
|
|
50
|
+
"""Error when the OAuth usage payload has no readable seven_day utilization."""
|
|
51
|
+
|
|
52
|
+
USAGE_PROBE_FAILED_ERROR_TEMPLATE: str = "usage probe failed: {error}"
|
|
53
|
+
"""Error when the OAuth usage HTTP probe raises."""
|
|
54
|
+
|
|
55
|
+
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE: str = (
|
|
56
|
+
"usage-pause probe module not found at {module_path}"
|
|
57
|
+
)
|
|
58
|
+
"""Error when the resolve_usage_window script cannot be located on disk."""
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
CODE_REVIEW_SLASH_COMMAND: str = "/code-review"
|
|
12
12
|
"""Built-in Claude Code slash command that runs the repository review."""
|
|
13
13
|
|
|
14
|
-
CODE_REVIEW_EFFORT: str = "
|
|
14
|
+
CODE_REVIEW_EFFORT: str = "xhigh"
|
|
15
15
|
"""Effort level passed to the built-in review slash command."""
|
|
16
16
|
|
|
17
17
|
CODE_REVIEW_FIX_FLAG: str = "--fix"
|
|
@@ -13,5 +13,5 @@ DEFAULT_POLL_INTERVAL: int = 30
|
|
|
13
13
|
WORKER_STAGGER_SECONDS: int = 15
|
|
14
14
|
"""Seconds between staggered headless grok worker process starts in a batch."""
|
|
15
15
|
|
|
16
|
-
DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS: int =
|
|
16
|
+
DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS: int = 2400
|
|
17
17
|
"""Default timeout applied to one headless `/code-review` chain invocation, in seconds."""
|