claude-dev-env 2.1.0 → 2.2.1
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/pr-loop/scripts/pyproject.toml +22 -0
- package/hooks/blocking/code_rules_shared.py +4 -2
- package/hooks/blocking/pii_payload_scan.py +78 -20
- package/hooks/blocking/pii_prevention_blocker.py +3 -1
- package/hooks/blocking/test_pii_write_surface_ephemeral.py +221 -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/check.ps1 +32 -6
- package/skills/_shared/pr-loop/CLAUDE.md +18 -13
- package/skills/_shared/pr-loop/portable-driver.md +150 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +3 -0
- package/skills/_shared/pr-loop/scripts/build_converge_task_list.py +310 -0
- package/skills/_shared/pr-loop/scripts/portable_converge_driver.py +1637 -0
- package/skills/_shared/pr-loop/scripts/select_converge_pacer.py +215 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +3 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +56 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +47 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +181 -0
- package/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +140 -0
- package/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +1119 -0
- package/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +207 -0
- package/skills/autoconverge/CLAUDE.md +5 -3
- package/skills/autoconverge/SKILL.md +185 -74
- package/skills/autoconverge/reference/convergence.md +5 -5
- package/skills/autoconverge/reference/multi-pr.md +33 -2
- package/skills/autoconverge/reference/stop-conditions.md +9 -6
- package/skills/autoconverge/test_portable_pacer_gate.py +54 -0
- package/skills/copilot-finding-triage/SKILL.md +16 -4
- package/skills/pr-converge/CLAUDE.md +4 -3
- package/skills/pr-converge/SKILL.md +236 -44
- package/skills/pr-converge/reference/multi-pr-orchestration.md +5 -1
- package/skills/pr-converge/reference/per-tick.md +51 -24
- package/skills/pr-converge/test_portable_pacer_gate.py +67 -0
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -3
- 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,310 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""``build_converge_task_list.py`` — ordered review tasks for a run.
|
|
3
|
+
|
|
4
|
+
Step 1 of autoconverge / portable pr-converge: call this script. Do not invent
|
|
5
|
+
tasks in prose. The final task is always all runnable code reviews CLEAN on
|
|
6
|
+
the same HEAD.
|
|
7
|
+
|
|
8
|
+
::
|
|
9
|
+
|
|
10
|
+
python build_converge_task_list.py \\
|
|
11
|
+
[--bugbot-down 0|1] [--copilot-down 0|1] \\
|
|
12
|
+
[--codex-down 0|1] [--codex-required 0|1]
|
|
13
|
+
|
|
14
|
+
Stdout JSON::
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
"tasks": [...],
|
|
18
|
+
"runnable_review_ids": [...],
|
|
19
|
+
"skipped_review_ids": [...],
|
|
20
|
+
"final_task_id": "all_runnable_reviews_clean_same_head",
|
|
21
|
+
"done_when": "..."
|
|
22
|
+
}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import json
|
|
29
|
+
import sys
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
_self_dir = Path(__file__).resolve().parent
|
|
33
|
+
if str(_self_dir) not in sys.path:
|
|
34
|
+
sys.path.insert(0, str(_self_dir))
|
|
35
|
+
|
|
36
|
+
from select_converge_pacer import parse_bool_flag # noqa: E402
|
|
37
|
+
from skills_pr_loop_constants.converge_task_list_constants import ( # noqa: E402
|
|
38
|
+
CLI_BUGBOT_DOWN_FLAG,
|
|
39
|
+
CLI_CODEX_DOWN_FLAG,
|
|
40
|
+
CLI_CODEX_REQUIRED_FLAG,
|
|
41
|
+
CLI_COPILOT_DOWN_FLAG,
|
|
42
|
+
DONE_WHEN_TEXT,
|
|
43
|
+
EXIT_SUCCESS,
|
|
44
|
+
EXIT_USAGE_ERROR,
|
|
45
|
+
RESULT_KEY_DONE_WHEN,
|
|
46
|
+
RESULT_KEY_FINAL_TASK_ID,
|
|
47
|
+
RESULT_KEY_RUNNABLE_REVIEW_IDS,
|
|
48
|
+
RESULT_KEY_SKIPPED_REVIEW_IDS,
|
|
49
|
+
RESULT_KEY_TASKS,
|
|
50
|
+
SKIP_REASON_BUGBOT_DOWN,
|
|
51
|
+
SKIP_REASON_CODEX_DOWN,
|
|
52
|
+
SKIP_REASON_CODEX_NOT_REQUIRED,
|
|
53
|
+
SKIP_REASON_COPILOT_DOWN,
|
|
54
|
+
TASK_FIELD_ID,
|
|
55
|
+
TASK_FIELD_IS_RUNNABLE,
|
|
56
|
+
TASK_FIELD_KIND,
|
|
57
|
+
TASK_FIELD_SKIP_REASON,
|
|
58
|
+
TASK_FIELD_TITLE,
|
|
59
|
+
TASK_ID_ALL_CLEAN_SAME_HEAD,
|
|
60
|
+
TASK_ID_BUGBOT,
|
|
61
|
+
TASK_ID_BUGTEAM,
|
|
62
|
+
TASK_ID_CODE_REVIEW,
|
|
63
|
+
TASK_ID_CODEX,
|
|
64
|
+
TASK_ID_COPILOT,
|
|
65
|
+
TASK_KIND_FINAL,
|
|
66
|
+
TASK_KIND_REVIEW,
|
|
67
|
+
TASK_TITLE_ALL_CLEAN_SAME_HEAD,
|
|
68
|
+
TASK_TITLE_BUGBOT,
|
|
69
|
+
TASK_TITLE_BUGTEAM,
|
|
70
|
+
TASK_TITLE_CODE_REVIEW,
|
|
71
|
+
TASK_TITLE_CODEX,
|
|
72
|
+
TASK_TITLE_COPILOT,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _review_task(
|
|
77
|
+
*,
|
|
78
|
+
task_id: str,
|
|
79
|
+
title: str,
|
|
80
|
+
is_runnable: bool,
|
|
81
|
+
skip_reason: str | None,
|
|
82
|
+
) -> dict[str, object]:
|
|
83
|
+
task: dict[str, object] = {
|
|
84
|
+
TASK_FIELD_ID: task_id,
|
|
85
|
+
TASK_FIELD_TITLE: title,
|
|
86
|
+
TASK_FIELD_KIND: TASK_KIND_REVIEW,
|
|
87
|
+
TASK_FIELD_IS_RUNNABLE: is_runnable,
|
|
88
|
+
}
|
|
89
|
+
if skip_reason is not None:
|
|
90
|
+
task[TASK_FIELD_SKIP_REASON] = skip_reason
|
|
91
|
+
return task
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _append_always_runnable_reviews(
|
|
95
|
+
all_tasks: list[dict[str, object]],
|
|
96
|
+
) -> None:
|
|
97
|
+
all_tasks.append(
|
|
98
|
+
_review_task(
|
|
99
|
+
task_id=TASK_ID_CODE_REVIEW,
|
|
100
|
+
title=TASK_TITLE_CODE_REVIEW,
|
|
101
|
+
is_runnable=True,
|
|
102
|
+
skip_reason=None,
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
all_tasks.append(
|
|
106
|
+
_review_task(
|
|
107
|
+
task_id=TASK_ID_BUGTEAM,
|
|
108
|
+
title=TASK_TITLE_BUGTEAM,
|
|
109
|
+
is_runnable=True,
|
|
110
|
+
skip_reason=None,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _append_bugbot_task(
|
|
116
|
+
all_tasks: list[dict[str, object]],
|
|
117
|
+
*,
|
|
118
|
+
is_bugbot_down: bool,
|
|
119
|
+
) -> None:
|
|
120
|
+
if is_bugbot_down:
|
|
121
|
+
all_tasks.append(
|
|
122
|
+
_review_task(
|
|
123
|
+
task_id=TASK_ID_BUGBOT,
|
|
124
|
+
title=TASK_TITLE_BUGBOT,
|
|
125
|
+
is_runnable=False,
|
|
126
|
+
skip_reason=SKIP_REASON_BUGBOT_DOWN,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
return
|
|
130
|
+
all_tasks.append(
|
|
131
|
+
_review_task(
|
|
132
|
+
task_id=TASK_ID_BUGBOT,
|
|
133
|
+
title=TASK_TITLE_BUGBOT,
|
|
134
|
+
is_runnable=True,
|
|
135
|
+
skip_reason=None,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _append_copilot_task(
|
|
141
|
+
all_tasks: list[dict[str, object]],
|
|
142
|
+
*,
|
|
143
|
+
is_copilot_down: bool,
|
|
144
|
+
) -> None:
|
|
145
|
+
if is_copilot_down:
|
|
146
|
+
all_tasks.append(
|
|
147
|
+
_review_task(
|
|
148
|
+
task_id=TASK_ID_COPILOT,
|
|
149
|
+
title=TASK_TITLE_COPILOT,
|
|
150
|
+
is_runnable=False,
|
|
151
|
+
skip_reason=SKIP_REASON_COPILOT_DOWN,
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
return
|
|
155
|
+
all_tasks.append(
|
|
156
|
+
_review_task(
|
|
157
|
+
task_id=TASK_ID_COPILOT,
|
|
158
|
+
title=TASK_TITLE_COPILOT,
|
|
159
|
+
is_runnable=True,
|
|
160
|
+
skip_reason=None,
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _append_codex_task(
|
|
166
|
+
all_tasks: list[dict[str, object]],
|
|
167
|
+
*,
|
|
168
|
+
is_codex_down: bool,
|
|
169
|
+
is_codex_required: bool,
|
|
170
|
+
) -> None:
|
|
171
|
+
if is_codex_down:
|
|
172
|
+
all_tasks.append(
|
|
173
|
+
_review_task(
|
|
174
|
+
task_id=TASK_ID_CODEX,
|
|
175
|
+
title=TASK_TITLE_CODEX,
|
|
176
|
+
is_runnable=False,
|
|
177
|
+
skip_reason=SKIP_REASON_CODEX_DOWN,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
return
|
|
181
|
+
if not is_codex_required:
|
|
182
|
+
all_tasks.append(
|
|
183
|
+
_review_task(
|
|
184
|
+
task_id=TASK_ID_CODEX,
|
|
185
|
+
title=TASK_TITLE_CODEX,
|
|
186
|
+
is_runnable=False,
|
|
187
|
+
skip_reason=SKIP_REASON_CODEX_NOT_REQUIRED,
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
return
|
|
191
|
+
all_tasks.append(
|
|
192
|
+
_review_task(
|
|
193
|
+
task_id=TASK_ID_CODEX,
|
|
194
|
+
title=TASK_TITLE_CODEX,
|
|
195
|
+
is_runnable=True,
|
|
196
|
+
skip_reason=None,
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _append_final_task(all_tasks: list[dict[str, object]]) -> None:
|
|
202
|
+
all_tasks.append(
|
|
203
|
+
{
|
|
204
|
+
TASK_FIELD_ID: TASK_ID_ALL_CLEAN_SAME_HEAD,
|
|
205
|
+
TASK_FIELD_TITLE: TASK_TITLE_ALL_CLEAN_SAME_HEAD,
|
|
206
|
+
TASK_FIELD_KIND: TASK_KIND_FINAL,
|
|
207
|
+
TASK_FIELD_IS_RUNNABLE: True,
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _partition_review_ids(
|
|
213
|
+
all_tasks: list[dict[str, object]],
|
|
214
|
+
) -> tuple[list[str], list[str]]:
|
|
215
|
+
all_runnable_review_ids = [
|
|
216
|
+
str(each_task[TASK_FIELD_ID])
|
|
217
|
+
for each_task in all_tasks
|
|
218
|
+
if each_task[TASK_FIELD_KIND] == TASK_KIND_REVIEW
|
|
219
|
+
and each_task[TASK_FIELD_IS_RUNNABLE] is True
|
|
220
|
+
]
|
|
221
|
+
all_skipped_review_ids = [
|
|
222
|
+
str(each_task[TASK_FIELD_ID])
|
|
223
|
+
for each_task in all_tasks
|
|
224
|
+
if each_task[TASK_FIELD_KIND] == TASK_KIND_REVIEW
|
|
225
|
+
and each_task[TASK_FIELD_IS_RUNNABLE] is False
|
|
226
|
+
]
|
|
227
|
+
return all_runnable_review_ids, all_skipped_review_ids
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def build_converge_task_list(
|
|
231
|
+
*,
|
|
232
|
+
is_bugbot_down: bool,
|
|
233
|
+
is_copilot_down: bool,
|
|
234
|
+
is_codex_down: bool,
|
|
235
|
+
is_codex_required: bool,
|
|
236
|
+
) -> dict[str, object]:
|
|
237
|
+
"""Return the ordered task list and done criterion for one converge run.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
is_bugbot_down: Skip Bugbot as a runnable review.
|
|
241
|
+
is_copilot_down: Skip Copilot as a runnable review.
|
|
242
|
+
is_codex_down: Skip Codex even when usage would require it.
|
|
243
|
+
is_codex_required: Weekly usage / policy requires a Codex review.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
JSON-serializable control object with ``tasks`` and ``done_when``.
|
|
247
|
+
"""
|
|
248
|
+
all_tasks: list[dict[str, object]] = []
|
|
249
|
+
_append_always_runnable_reviews(all_tasks)
|
|
250
|
+
_append_bugbot_task(all_tasks, is_bugbot_down=is_bugbot_down)
|
|
251
|
+
_append_copilot_task(all_tasks, is_copilot_down=is_copilot_down)
|
|
252
|
+
_append_codex_task(
|
|
253
|
+
all_tasks,
|
|
254
|
+
is_codex_down=is_codex_down,
|
|
255
|
+
is_codex_required=is_codex_required,
|
|
256
|
+
)
|
|
257
|
+
_append_final_task(all_tasks)
|
|
258
|
+
all_runnable_review_ids, all_skipped_review_ids = _partition_review_ids(
|
|
259
|
+
all_tasks
|
|
260
|
+
)
|
|
261
|
+
return {
|
|
262
|
+
RESULT_KEY_TASKS: all_tasks,
|
|
263
|
+
RESULT_KEY_RUNNABLE_REVIEW_IDS: all_runnable_review_ids,
|
|
264
|
+
RESULT_KEY_SKIPPED_REVIEW_IDS: all_skipped_review_ids,
|
|
265
|
+
RESULT_KEY_FINAL_TASK_ID: TASK_ID_ALL_CLEAN_SAME_HEAD,
|
|
266
|
+
RESULT_KEY_DONE_WHEN: DONE_WHEN_TEXT,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def build_argument_parser() -> argparse.ArgumentParser:
|
|
271
|
+
"""Build the CLI parser for ``build_converge_task_list``.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Configured argument parser.
|
|
275
|
+
"""
|
|
276
|
+
parser = argparse.ArgumentParser(description="build_converge_task_list")
|
|
277
|
+
parser.add_argument(CLI_BUGBOT_DOWN_FLAG, default="0")
|
|
278
|
+
parser.add_argument(CLI_COPILOT_DOWN_FLAG, default="0")
|
|
279
|
+
parser.add_argument(CLI_CODEX_DOWN_FLAG, default="0")
|
|
280
|
+
parser.add_argument(CLI_CODEX_REQUIRED_FLAG, default="0")
|
|
281
|
+
return parser
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def main(all_argv: list[str]) -> int:
|
|
285
|
+
"""CLI entry: print one task-list JSON object on stdout.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
all_argv: Argument vector without program name.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Process exit code.
|
|
292
|
+
"""
|
|
293
|
+
parser = build_argument_parser()
|
|
294
|
+
parsed_arguments = parser.parse_args(all_argv)
|
|
295
|
+
try:
|
|
296
|
+
task_list_payload = build_converge_task_list(
|
|
297
|
+
is_bugbot_down=parse_bool_flag(parsed_arguments.bugbot_down),
|
|
298
|
+
is_copilot_down=parse_bool_flag(parsed_arguments.copilot_down),
|
|
299
|
+
is_codex_down=parse_bool_flag(parsed_arguments.codex_down),
|
|
300
|
+
is_codex_required=parse_bool_flag(parsed_arguments.codex_required),
|
|
301
|
+
)
|
|
302
|
+
except ValueError as validation_error:
|
|
303
|
+
print(str(validation_error), file=sys.stderr)
|
|
304
|
+
return EXIT_USAGE_ERROR
|
|
305
|
+
print(json.dumps(task_list_payload, sort_keys=True))
|
|
306
|
+
return EXIT_SUCCESS
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
if __name__ == "__main__":
|
|
310
|
+
raise SystemExit(main(sys.argv[1:]))
|