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.
- package/package.json +1 -1
- 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
|
@@ -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
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
24
|
-
`ScheduleWakeup`
|
|
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
|
|
29
|
-
`ScheduleWakeup`. Omit the next wakeup only on
|
|
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
|
-
|
|
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.
|
|
37
|
-
|
|
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`,
|
|
248
|
-
|
|
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`,
|
|
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`,
|
|
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`,
|
|
332
|
-
|
|
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`,
|
|
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`.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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
|
|
4
|
-
|
|
5
|
-
/
|
|
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
|
|