claude-dev-env 2.1.0 → 2.2.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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/skills/_shared/pr-loop/CLAUDE.md +18 -13
  3. package/skills/_shared/pr-loop/portable-driver.md +150 -0
  4. package/skills/_shared/pr-loop/scripts/CLAUDE.md +3 -0
  5. package/skills/_shared/pr-loop/scripts/build_converge_task_list.py +310 -0
  6. package/skills/_shared/pr-loop/scripts/portable_converge_driver.py +1637 -0
  7. package/skills/_shared/pr-loop/scripts/select_converge_pacer.py +215 -0
  8. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +3 -0
  9. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +56 -0
  10. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +47 -0
  11. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +181 -0
  12. package/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +140 -0
  13. package/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +1119 -0
  14. package/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +207 -0
  15. package/skills/autoconverge/CLAUDE.md +5 -3
  16. package/skills/autoconverge/SKILL.md +185 -74
  17. package/skills/autoconverge/reference/convergence.md +5 -5
  18. package/skills/autoconverge/reference/multi-pr.md +33 -2
  19. package/skills/autoconverge/reference/stop-conditions.md +9 -6
  20. package/skills/autoconverge/test_portable_pacer_gate.py +54 -0
  21. package/skills/copilot-finding-triage/SKILL.md +16 -4
  22. package/skills/pr-converge/CLAUDE.md +4 -3
  23. package/skills/pr-converge/SKILL.md +236 -44
  24. package/skills/pr-converge/reference/multi-pr-orchestration.md +5 -1
  25. package/skills/pr-converge/reference/per-tick.md +51 -24
  26. package/skills/pr-converge/test_portable_pacer_gate.py +67 -0
  27. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -3
@@ -15,29 +15,38 @@ Use on **draft PR**. Cursor Bugbot and `/bugteam` re-run after each push. Fix
15
15
  findings between rounds until back-to-back clean on same `HEAD`, then mark
16
16
  PR ready for review.
17
17
 
18
- Run every tick in parent harness session. Pacing lives in
19
- [`../workflows/schedule-wakeup-loop.md`](../workflows/schedule-wakeup-loop.md) (read before Step 4); see [Pacing
20
- workflow](#pacing-workflow).
18
+ Run every tick in the parent harness session. Pacing depends on the selected
19
+ pacer from pre-flight (`select_converge_pacer.py`):
20
+
21
+ - `schedule_wakeup` — [`../workflows/schedule-wakeup-loop.md`](../workflows/schedule-wakeup-loop.md)
22
+ - `portable` — [`../../_shared/pr-loop/portable-driver.md`](../../_shared/pr-loop/portable-driver.md)
23
+
24
+ See [Pacing workflow](#pacing-workflow).
21
25
 
22
26
  Every BUGTEAM tick runs **bugteam** — never hand-rolled substitute. Fix
23
- protocol per [fix-protocol.md](fix-protocol.md). Pacing stays in main session via
24
- `ScheduleWakeup` (pre-flight aborts when absent).
27
+ protocol per [fix-protocol.md](fix-protocol.md). Pacing stays in the main
28
+ session (native `ScheduleWakeup` or the portable continuous driver).
25
29
 
26
30
  ## Invocation modes
27
31
 
28
- - **`/pr-converge`** runs one tick, then Step 4 schedules the next via
29
- `ScheduleWakeup`. Omit the next wakeup only on convergence or **Stop
30
- conditions**.
32
+ - **`/pr-converge` with `pacer=schedule_wakeup`** runs one tick, then Step 4
33
+ schedules the next via `ScheduleWakeup`. Omit the next wakeup only on
34
+ convergence or **Stop conditions**.
35
+ - **`/pr-converge` with `pacer=portable`** runs ticks continuously in-session
36
+ (poll waits at the same delays); write handoff at budget boundaries. See
37
+ the portable driver.
31
38
 
32
39
  ## Pacing workflow
33
40
 
34
- Read [`../workflows/schedule-wakeup-loop.md`](../workflows/schedule-wakeup-loop.md)
41
+ When `pacer=schedule_wakeup`, read
42
+ [`../workflows/schedule-wakeup-loop.md`](../workflows/schedule-wakeup-loop.md)
35
43
  (installed copy under `$HOME/.claude/skills/pr-converge/workflows/`) before
36
- Step 4. The pre-flight gate guarantees `ScheduleWakeup` is invokable; the
37
- workflow file specifies delays, prompts, and convergence cleanup.
44
+ Step 4. When `pacer=portable`, read
45
+ [`../../_shared/pr-loop/portable-driver.md`](../../_shared/pr-loop/portable-driver.md)
46
+ and skip `ScheduleWakeup` calls.
38
47
 
39
48
  - **`/pr-converge`** (default): loops until convergence. After each tick
40
- (unless converged or stopped), run **Step 4**.
49
+ (unless converged or stopped), run **Step 4** using the selected pacer.
41
50
 
42
51
  ## Step 1: Resolve current HEAD and PR context
43
52
 
@@ -244,8 +253,8 @@ c. Decide (three branches; match first whose predicate holds):
244
253
 
245
254
  - **Failed review (`returncode != 0`, or chain mode with null
246
255
  `served_command`):** The review did not complete a successful serve. Do
247
- not set `code_review_clean_at`. Stay `phase = CODE_REVIEW`, schedule next
248
- wakeup, return. A failed chain often leaves `dirty_tree` false — that is
256
+ not set `code_review_clean_at`. Stay `phase = CODE_REVIEW`, apply Step 4 pacer
257
+ (ScheduleWakeup or portable continue/poll), return. A failed chain often leaves `dirty_tree` false — that is
249
258
  not a clean stamp.
250
259
  - **Fixes applied (working tree dirty / `dirty_tree` true):** Commit the
251
260
  applied fixes in one commit → push, following the shared fix protocol
@@ -253,7 +262,7 @@ c. Decide (three branches; match first whose predicate holds):
253
262
  push-invalidated markers per [ground-rules.md](ground-rules.md) /
254
263
  [state-schema.md](state-schema.md) (all `*_clean_at`, `merge_state_status`,
255
264
  `bugbot_down`, `bugbot_acknowledged_at`, `codex_down`). Stay
256
- `phase = CODE_REVIEW`, schedule next wakeup, return. Every fix push
265
+ `phase = CODE_REVIEW`, apply Step 4 pacer (ScheduleWakeup or portable continue/poll), return. Every fix push
257
266
  re-enters the internal passes on the new HEAD.
258
267
  - **Clean (successful serve: `returncode == 0`, chain `served_command`
259
268
  non-null when `mode == chain`, and `dirty_tree` false):** Set
@@ -318,7 +327,7 @@ d. Decide based on post-bugteam state — order matters. Check
318
327
  pushed-during-bugteam FIRST so a convergence report against a stale HEAD
319
328
  never falsely terminates:
320
329
  - **Audit pushed this tick (clean-at fields reset in step b):**
321
- `phase = CODE_REVIEW`, schedule next wakeup, return. Every fix push
330
+ `phase = CODE_REVIEW`, apply Step 4 pacer (ScheduleWakeup or portable continue/poll), return. Every fix push
322
331
  re-enters the internal passes on the new HEAD.
323
332
  - **Convergence AND no push:** the internal passes are clean on
324
333
  `current_head`. Stamp `bugteam_clean_at = current_head`, then
@@ -328,8 +337,8 @@ never falsely terminates:
328
337
  ([`../../../_shared/pr-loop/fix-protocol.md`](../../../_shared/pr-loop/fix-protocol.md); skill deltas in [fix-protocol.md](fix-protocol.md)). Reset push-invalidated markers
329
338
  per [ground-rules.md](ground-rules.md) / [state-schema.md](state-schema.md)
330
339
  (all `*_clean_at`, `merge_state_status`, `bugbot_down`,
331
- `bugbot_acknowledged_at`, `codex_down`), `phase = CODE_REVIEW`, schedule
332
- next wakeup, return.
340
+ `bugbot_acknowledged_at`, `codex_down`), `phase = CODE_REVIEW`, apply Step 4 pacer
341
+ (ScheduleWakeup or portable continue/poll), return.
333
342
 
334
343
  ### `phase == BUGBOT` (terminal gate)
335
344
 
@@ -386,7 +395,7 @@ pull_request_read(owner=OWNER, repo=REPO, pullNumber=NUMBER, method="get_review_
386
395
  c. Decide (four branches; match first whose predicate holds):
387
396
  - **No bugbot review yet, OR latest review's `commit_id` ≠
388
397
  `current_head`:** Re-trigger bugbot (Step 3), set `bugbot_clean_at =
389
- null`, reset `inline_lag_streak = 0`, schedule next wakeup, return to the
398
+ null`, reset `inline_lag_streak = 0`, apply Step 4 pacer (ScheduleWakeup or portable continue/poll), return to the
390
399
  Bugbot gate next tick.
391
400
  - **`commit_id == current_head` AND zero unaddressed inline AND review
392
401
  body clean:** Set `bugbot_clean_at = current_head`, reset
@@ -402,7 +411,7 @@ c. Decide (four branches; match first whose predicate holds):
402
411
  `state.json`: the clean-coder teammate executes the fix, writes
403
412
  `state.json`, goes idle; the next tick re-enters CODE_REVIEW on the new
404
413
  HEAD. No `state.json` (single-PR): the lead executes it, stays
405
- `phase = CODE_REVIEW`. Schedule next wakeup, return.
414
+ `phase = CODE_REVIEW`. Apply Step 4 pacer (ScheduleWakeup or portable continue/poll), return.
406
415
 
407
416
  ### `phase == COPILOT_WAIT`
408
417
 
@@ -435,12 +444,12 @@ b. Decide (three branches; match first whose predicate holds):
435
444
  (all `*_clean_at`, `merge_state_status`, `bugbot_down`,
436
445
  `bugbot_acknowledged_at`, `codex_down`). **Set `phase = CODE_REVIEW`**
437
446
  (NOT COPILOT_WAIT) — every fix push re-enters the internal passes on the
438
- new HEAD. Schedule next wakeup, return.
447
+ new HEAD. Apply Step 4 pacer (ScheduleWakeup or portable continue/poll), return.
439
448
  - **No Copilot review at `current_head` yet:** Increment
440
449
  `copilot_wait_count` (init 0 on COPILOT_WAIT entry; reset to 0 on
441
450
  every push and on every successful Copilot review). `>= 3` → hard
442
451
  blocker per [stop-conditions.md](stop-conditions.md). Otherwise
443
- schedule next wakeup (360s), return.
452
+ apply Step 4 pacer (ScheduleWakeup or portable continue/poll; 360s wait), return.
444
453
 
445
454
  **Non-negotiable:** After any Copilot fix push, `phase` MUST route to
446
455
  `CODE_REVIEW`. Never cycle COPILOT_WAIT → fix → COPILOT_WAIT. The
@@ -508,8 +517,11 @@ base), never an invented commit range.
508
517
 
509
518
  ## Step 4: Loop pacing
510
519
 
511
- **`ScheduleWakeup` field hints** (prefer [Pacing
512
- workflow](#pacing-workflow)):
520
+ Apply the pacer selected at pre-flight ([Pacing workflow](#pacing-workflow)).
521
+
522
+ ### `pacer=schedule_wakeup`
523
+
524
+ **`ScheduleWakeup` field hints:**
513
525
 
514
526
  - `delaySeconds: 360` after bugbot re-trigger. Exception:
515
527
  BUGBOT inline-lag branch uses `delaySeconds: 90` (no re-trigger;
@@ -521,6 +533,21 @@ workflow](#pacing-workflow)):
521
533
  **On convergence:** apply **Convergence** section of
522
534
  `../workflows/schedule-wakeup-loop.md` (omit wakeups).
523
535
 
536
+ ### `pacer=portable`
537
+
538
+ Do **not** call `ScheduleWakeup`. After writing state and handoff:
539
+
540
+ - **Immediate work next** → continue the next tick in the same turn.
541
+ - **Wait next** (Bugbot queued, COPILOT_WAIT with no review yet) → poll
542
+ in-session for the same delay (`360` default, `90` on Bugbot inline-lag),
543
+ then resume the same step. Honor the same hard caps as the ScheduleWakeup
544
+ path.
545
+ - **Budget too low for a full tick** → stop at the tick boundary; print
546
+ `/pr-converge <PR URL>` and the persisted phase.
547
+
548
+ Full portable rules:
549
+ [`../../_shared/pr-loop/portable-driver.md`](../../_shared/pr-loop/portable-driver.md).
550
+
524
551
  ## Bugteam execution
525
552
 
526
553
  **Second audit** (BUGTEAM phase) is **always** **bugteam** skill: preflight,
@@ -0,0 +1,67 @@
1
+ """Contract checks: pr-converge selects a portable pacer when ScheduleWakeup is absent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+ SKILL_ROOT = Path(__file__).resolve().parent
9
+ SKILL_MARKDOWN_PATH = SKILL_ROOT / "SKILL.md"
10
+ PER_TICK_MARKDOWN_PATH = SKILL_ROOT / "reference" / "per-tick.md"
11
+ PORTABLE_DRIVER_PATH = (
12
+ SKILL_ROOT.parent / "_shared" / "pr-loop" / "portable-driver.md"
13
+ )
14
+
15
+ SELECT_CONVERGE_PACER_SCRIPT = "select_converge_pacer.py"
16
+ PACER_PORTABLE_TOKEN = "pacer=portable"
17
+ PACER_SCHEDULE_WAKEUP_TOKEN = "pacer=schedule_wakeup"
18
+ ABORT_ONLY_PHRASE = "pr-converge requires ScheduleWakeup; aborting"
19
+ PORTABLE_DRIVER_DOC = "portable-driver.md"
20
+ PORTABLE_DRIVER_LINK_PATTERN = re.compile(
21
+ r"\((?P<relative>(?:\.\./)+_shared/pr-loop/portable-driver\.md)\)"
22
+ )
23
+
24
+
25
+ def test_skill_selects_pacer_and_forbids_abort_only_schedule_wakeup() -> None:
26
+ skill_text = SKILL_MARKDOWN_PATH.read_text(encoding="utf-8")
27
+ assert SELECT_CONVERGE_PACER_SCRIPT in skill_text
28
+ assert PACER_PORTABLE_TOKEN in skill_text
29
+ assert PACER_SCHEDULE_WAKEUP_TOKEN in skill_text
30
+ assert PORTABLE_DRIVER_DOC in skill_text
31
+ assert ABORT_ONLY_PHRASE not in skill_text
32
+
33
+
34
+ def test_skill_portable_driver_links_resolve_on_disk() -> None:
35
+ skill_text = SKILL_MARKDOWN_PATH.read_text(encoding="utf-8")
36
+ all_relative_links = PORTABLE_DRIVER_LINK_PATTERN.findall(skill_text)
37
+ assert all_relative_links, "SKILL.md must link portable-driver.md"
38
+ for each_relative_link in all_relative_links:
39
+ resolved_path = (SKILL_MARKDOWN_PATH.parent / each_relative_link).resolve()
40
+ assert resolved_path.is_file(), (
41
+ f"portable-driver link must resolve on disk: "
42
+ f"{each_relative_link} -> {resolved_path}"
43
+ )
44
+ assert resolved_path == PORTABLE_DRIVER_PATH.resolve()
45
+
46
+
47
+ def test_per_tick_documents_both_pacers() -> None:
48
+ per_tick_text = PER_TICK_MARKDOWN_PATH.read_text(encoding="utf-8")
49
+ assert "portable" in per_tick_text
50
+ assert "schedule_wakeup" in per_tick_text or "ScheduleWakeup" in per_tick_text
51
+ assert PORTABLE_DRIVER_DOC in per_tick_text
52
+ assert "pacer=portable" in per_tick_text
53
+ assert "Do **not** call `ScheduleWakeup`" in per_tick_text or (
54
+ "Do not call `ScheduleWakeup`" in per_tick_text
55
+ )
56
+
57
+
58
+ def test_portable_driver_protocol_exists() -> None:
59
+ assert PORTABLE_DRIVER_PATH.is_file()
60
+ portable_text = PORTABLE_DRIVER_PATH.read_text(encoding="utf-8")
61
+ assert "check_convergence.py" in portable_text
62
+ assert "invoke_code_review.py" in portable_text
63
+ assert "resolve_worker_spawn.py" in portable_text
64
+ assert "build_converge_task_list.py" in portable_text
65
+ assert "all_runnable_reviews_clean_same_head" in portable_text
66
+ assert "portable_converge_driver.py" in portable_text
67
+ assert "Never abort solely because" in portable_text
@@ -1,8 +1,10 @@
1
1
  # ScheduleWakeup loop pacing (pr-converge)
2
2
 
3
- Load this document for converge **loop pacing**. The pre-flight in `SKILL.md`
4
- guarantees `ScheduleWakeup` is available before any tick runs. Shared bugbot
5
- / bugteam / Fix protocol steps stay in the main `SKILL.md`.
3
+ Load this document when pre-flight selects `pacer=schedule_wakeup`. When
4
+ `pacer=portable`, use
5
+ [`../../_shared/pr-loop/portable-driver.md`](../../_shared/pr-loop/portable-driver.md)
6
+ instead. Shared bugbot / bugteam / Fix protocol steps stay in the main
7
+ `SKILL.md`.
6
8
 
7
9
  ## Calling ScheduleWakeup
8
10