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.
Files changed (44) hide show
  1. package/CLAUDE.md +1 -1
  2. package/_shared/pr-loop/scripts/pyproject.toml +22 -0
  3. package/hooks/blocking/code_rules_shared.py +4 -2
  4. package/hooks/blocking/pii_payload_scan.py +78 -20
  5. package/hooks/blocking/pii_prevention_blocker.py +3 -1
  6. package/hooks/blocking/test_pii_write_surface_ephemeral.py +221 -0
  7. package/hooks/validators/ruff_integration.py +2 -1
  8. package/hooks/validators/run_all_validators.py +460 -24
  9. package/hooks/validators/test_ruff_integration.py +21 -0
  10. package/hooks/validators/test_run_all_validators_pretooluse.py +223 -3
  11. package/package.json +1 -1
  12. package/rules/CLAUDE.md +1 -0
  13. package/rules/cleanup-command-forms.md +23 -0
  14. package/scripts/check.ps1 +32 -6
  15. package/skills/_shared/pr-loop/CLAUDE.md +18 -13
  16. package/skills/_shared/pr-loop/portable-driver.md +150 -0
  17. package/skills/_shared/pr-loop/scripts/CLAUDE.md +3 -0
  18. package/skills/_shared/pr-loop/scripts/build_converge_task_list.py +310 -0
  19. package/skills/_shared/pr-loop/scripts/portable_converge_driver.py +1637 -0
  20. package/skills/_shared/pr-loop/scripts/select_converge_pacer.py +215 -0
  21. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +3 -0
  22. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +56 -0
  23. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +47 -0
  24. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +181 -0
  25. package/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +140 -0
  26. package/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +1119 -0
  27. package/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +207 -0
  28. package/skills/autoconverge/CLAUDE.md +5 -3
  29. package/skills/autoconverge/SKILL.md +185 -74
  30. package/skills/autoconverge/reference/convergence.md +5 -5
  31. package/skills/autoconverge/reference/multi-pr.md +33 -2
  32. package/skills/autoconverge/reference/stop-conditions.md +9 -6
  33. package/skills/autoconverge/test_portable_pacer_gate.py +54 -0
  34. package/skills/copilot-finding-triage/SKILL.md +16 -4
  35. package/skills/pr-converge/CLAUDE.md +4 -3
  36. package/skills/pr-converge/SKILL.md +236 -44
  37. package/skills/pr-converge/reference/multi-pr-orchestration.md +5 -1
  38. package/skills/pr-converge/reference/per-tick.md +51 -24
  39. package/skills/pr-converge/test_portable_pacer_gate.py +67 -0
  40. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -3
  41. package/skills/usage-pause/SKILL.md +5 -4
  42. package/skills/usage-pause/scripts/resolve_usage_window.py +68 -13
  43. package/skills/usage-pause/scripts/test_resolve_usage_window.py +121 -9
  44. 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:]))