claude-dev-env 2.0.2 → 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 (110) hide show
  1. package/CLAUDE.md +1 -1
  2. package/hooks/blocking/CLAUDE.md +1 -0
  3. package/hooks/blocking/code_rules_shared.py +126 -47
  4. package/hooks/blocking/config/CLAUDE.md +2 -0
  5. package/hooks/blocking/config/verified_commit_context_constants.py +21 -0
  6. package/hooks/blocking/config/verified_commit_gate_output_constants.py +14 -0
  7. package/hooks/blocking/conftest.py +36 -30
  8. package/hooks/blocking/convergence_gate_blocker.py +76 -8
  9. package/hooks/blocking/plain_language_blocker.py +8 -0
  10. package/hooks/blocking/state_description_blocker.py +4 -1
  11. package/hooks/blocking/test_code_rules_shared.py +120 -20
  12. package/hooks/blocking/test_convergence_gate_blocker.py +146 -0
  13. package/hooks/blocking/test_plain_language_blocker.py +15 -0
  14. package/hooks/blocking/test_state_description_blocker.py +15 -0
  15. package/hooks/blocking/test_verified_commit_config_bootstrap.py +18 -0
  16. package/hooks/blocking/test_verified_commit_gate_additional_context.py +134 -0
  17. package/hooks/blocking/tests/test_verified_commit_gate.py +41 -0
  18. package/hooks/blocking/verified_commit_config_bootstrap.py +22 -10
  19. package/hooks/blocking/verified_commit_gate.py +113 -568
  20. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +28 -0
  21. package/hooks/blocking/verified_commit_gate_parts/__init__.py +1 -0
  22. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +174 -0
  23. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +53 -0
  24. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +80 -0
  25. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +170 -0
  26. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +205 -0
  27. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +10 -0
  28. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +94 -0
  29. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +17 -0
  30. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +38 -0
  31. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +71 -0
  32. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +61 -0
  33. package/hooks/hooks_constants/CLAUDE.md +4 -1
  34. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  35. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +36 -0
  36. package/hooks/hooks_constants/harness_scratchpad_constants.py +10 -9
  37. package/hooks/hooks_constants/mypy_integration_constants.py +16 -0
  38. package/hooks/validators/mypy_integration.py +145 -24
  39. package/hooks/validators/python_antipattern_checks.py +16 -0
  40. package/hooks/validators/run_all_validators.py +9 -3
  41. package/hooks/validators/test_mypy_integration.py +154 -0
  42. package/hooks/validators/test_python_antipattern_checks.py +112 -1
  43. package/hooks/validators/test_run_all_validators_pretooluse.py +16 -0
  44. package/package.json +1 -1
  45. package/rules/CLAUDE.md +1 -0
  46. package/rules/verified-commit-gate-skip.md +28 -0
  47. package/skills/_shared/pr-loop/CLAUDE.md +18 -13
  48. package/skills/_shared/pr-loop/portable-driver.md +150 -0
  49. package/skills/_shared/pr-loop/scripts/CLAUDE.md +3 -0
  50. package/skills/_shared/pr-loop/scripts/build_converge_task_list.py +310 -0
  51. package/skills/_shared/pr-loop/scripts/portable_converge_driver.py +1637 -0
  52. package/skills/_shared/pr-loop/scripts/select_converge_pacer.py +215 -0
  53. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +3 -0
  54. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +56 -0
  55. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +47 -0
  56. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +181 -0
  57. package/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +140 -0
  58. package/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +1119 -0
  59. package/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +207 -0
  60. package/skills/auditing-claude-config/CLAUDE.md +2 -1
  61. package/skills/auditing-claude-config/SKILL.md +114 -176
  62. package/skills/auditing-claude-config/reference/probe-hook.md +74 -0
  63. package/skills/autoconverge/CLAUDE.md +6 -3
  64. package/skills/autoconverge/SKILL.md +421 -346
  65. package/skills/autoconverge/reference/CLAUDE.md +1 -0
  66. package/skills/autoconverge/reference/convergence.md +5 -5
  67. package/skills/autoconverge/reference/copilot-findings.md +51 -0
  68. package/skills/autoconverge/reference/multi-pr.md +33 -2
  69. package/skills/autoconverge/reference/stop-conditions.md +9 -6
  70. package/skills/autoconverge/test_portable_pacer_gate.py +54 -0
  71. package/skills/closeout/SKILL.md +7 -9
  72. package/skills/copilot-finding-triage/SKILL.md +21 -11
  73. package/skills/copilot-review/CLAUDE.md +3 -2
  74. package/skills/copilot-review/SKILL.md +119 -155
  75. package/skills/copilot-review/templates/subagent-prompt.md +49 -0
  76. package/skills/fresh-branch/CLAUDE.md +6 -9
  77. package/skills/fresh-branch/SKILL.md +84 -33
  78. package/skills/fresh-branch/scripts/create_fresh_branch.py +445 -0
  79. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/__init__.py +1 -0
  80. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +74 -0
  81. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +670 -0
  82. package/skills/pr-converge/CLAUDE.md +4 -3
  83. package/skills/pr-converge/SKILL.md +469 -422
  84. package/skills/pr-converge/reference/CLAUDE.md +1 -0
  85. package/skills/pr-converge/reference/multi-pr-orchestration.md +5 -1
  86. package/skills/pr-converge/reference/per-tick.md +51 -24
  87. package/skills/pr-converge/reference/progress-checklist.md +168 -0
  88. package/skills/pr-converge/test_portable_pacer_gate.py +67 -0
  89. package/skills/pr-converge/test_step5_host_branch.py +8 -6
  90. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -3
  91. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  92. package/skills/privacy-hygiene/SKILL.md +68 -115
  93. package/skills/privacy-hygiene/reference/sweep-procedure.md +62 -0
  94. package/skills/session-log/CLAUDE.md +2 -1
  95. package/skills/session-log/SKILL.md +4 -26
  96. package/skills/session-log/templates/frontmatter.md +40 -0
  97. package/skills/skill-builder/CLAUDE.md +8 -7
  98. package/skills/skill-builder/SKILL.md +26 -11
  99. package/skills/skill-builder/references/CLAUDE.md +3 -1
  100. package/skills/skill-builder/references/delegation-map.md +21 -12
  101. package/skills/skill-builder/references/description-field.md +9 -11
  102. package/skills/skill-builder/references/deterministic-elements.md +218 -0
  103. package/skills/skill-builder/references/self-audit-checklist.md +62 -45
  104. package/skills/skill-builder/references/skill-modularity.md +8 -9
  105. package/skills/skill-builder/templates/CLAUDE.md +2 -2
  106. package/skills/skill-builder/templates/gap-analysis.md +15 -0
  107. package/skills/skill-builder/workflows/CLAUDE.md +5 -5
  108. package/skills/skill-builder/workflows/improve-skill.md +18 -9
  109. package/skills/skill-builder/workflows/new-skill.md +23 -15
  110. package/skills/skill-builder/workflows/polish-skill.md +28 -21
@@ -6,6 +6,7 @@ Reference documents for the `pr-converge` skill. These files define the per-tick
6
6
 
7
7
  | File | Purpose |
8
8
  |---|---|
9
+ | `progress-checklist.md` | The per-tick eleven-step checklist (Step 0 open → Step 11 report), each step pointing to its spoke file |
9
10
  | `per-tick.md` | Step-by-step procedure for one tick (resolve HEAD, run the internal CODE_REVIEW and BUGTEAM phases, then the terminal BUGBOT and COPILOT_WAIT gates, schedule next wakeup) |
10
11
  | `convergence-gates.md` | Six gates that must all pass before the PR is marked ready for review |
11
12
  | `fix-protocol.md` | pr-converge deltas on the `pr-fix-protocol` skill: multi-PR teammate obligations and the same-tick bugbot re-trigger rule |
@@ -210,7 +210,11 @@ When bugfix (clean-coder) subagent completes after push:
210
210
  3. Each PR with new subagent results → spawn next agent per rules, all
211
211
  in one parallel message.
212
212
  4. Re-read `state.json` if needed for scheduling.
213
- 5. Call `ScheduleWakeup` with appropriate delay.
213
+ 5. Advance the pacer: on `pacer=schedule_wakeup`, call `ScheduleWakeup`
214
+ with the appropriate delay; on `pacer=portable`, continue or poll
215
+ in-session per
216
+ [`../../_shared/pr-loop/portable-driver.md`](../../_shared/pr-loop/portable-driver.md)
217
+ (no `ScheduleWakeup`).
214
218
  6. Nothing else.
215
219
 
216
220
  ## Memory
@@ -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,168 @@
1
+ # pr-converge progress checklist
2
+
3
+ The per-tick step sequence for the pr-converge loop. The hub
4
+ ([`../SKILL.md`](../SKILL.md)) points here; run these steps in order, one pass
5
+ per tick.
6
+
7
+ State variables (`phase`, `bugbot_clean_at`, `code_review_clean_at`,
8
+ `bugteam_clean_at`, `copilot_clean_at`, `merge_state_status`, counters) are
9
+ defined in [`state-schema.md`](state-schema.md). Ground rules
10
+ in [`ground-rules.md`](ground-rules.md).
11
+
12
+ Each step references its spoke file for full procedural detail — MCP calls,
13
+ script invocations, decision criteria. Every "return to Step N" means the next
14
+ tick starts fresh from that step.
15
+
16
+ **Hard gate: do not advance from any step while ANY unresolved review
17
+ thread exists on the PR.** Sweep semantics and per-thread handling live in
18
+ the shared fix protocol
19
+ ([`../../../_shared/pr-loop/fix-protocol.md`](../../../_shared/pr-loop/fix-protocol.md)
20
+ step 12; skill deltas in [`fix-protocol.md`](fix-protocol.md)).
21
+
22
+ **Full-PR-diff rule: every CODE-REVIEW round (Step 5) and every BUGTEAM
23
+ round (Step 6) covers the FULL `origin/main...HEAD` diff — every file
24
+ the PR touches.** A round that scopes to a subset — only the last commit,
25
+ only files touched since the prior clean SHA, only bugbot-flagged paths,
26
+ or any other delta cut — does not satisfy the gate, and a "clean" verdict
27
+ against a partial diff is not a valid clean. Re-run the round against the
28
+ full diff before recording `code_review_clean_at` or treating the bugteam
29
+ round as converged. This rule holds every tick, every loop, every PR.
30
+
31
+ - [ ] **Step 0: Open the run**
32
+ Apply the `pr-loop-lifecycle` skill's Open section
33
+ (`../../pr-loop-lifecycle/SKILL.md`): permission grant + worktree preflight.
34
+
35
+ - [ ] **Step 1: Resolve PR scope + PR worktree**
36
+ Apply Step 1.5 scope resolution in
37
+ [`per-tick.md`](per-tick.md). Resolve the **PR
38
+ worktree** — the local checkout every local step this tick targets:
39
+ the `EnterWorktree` checkout when the PR shares the session's repo,
40
+ else a checkout of the PR's repo that the working directory routes
41
+ into via `cd`. Cross-repo routing is automatic, not a fork.
42
+ See: [`per-tick.md` § Step 1](per-tick.md)
43
+ and [§ Step 1.5](per-tick.md)
44
+
45
+ - [ ] **Step 2: Initialize loop state**
46
+ `phase = CODE_REVIEW`; all counters at zero; `run_name` resolved.
47
+
48
+ - [ ] **Step 3: Mergeability check**
49
+ See: [`convergence-gates.md` § (c)](convergence-gates.md)
50
+
51
+ ```
52
+ pull_request_read(method="get") → .mergeable_state, .mergeable
53
+ ```
54
+
55
+ - [ ] mergeable → advance to Step 4
56
+ - [ ] not mergeable → rebase → force-push → return to Step 1
57
+
58
+ - [ ] **Step 4: BUGBOT — terminal Bugbot confirmation gate**
59
+ Step 6 routes here after BUGTEAM converges. Bugbot confirms code the
60
+ internal passes already drove to clean.
61
+ See: [`per-tick.md` § BUGBOT terminal gate + Step 3](per-tick.md);
62
+ availability and trigger via `reviewer-gates`
63
+ (`../../reviewer-gates/SKILL.md` § Gate 1 / Gate 3).
64
+
65
+ - [ ] **disabled / down** → `bugbot_down = true` → Step 7
66
+ - [ ] **dirty on `current_head`** → apply shared fix protocol
67
+ ([`../../../_shared/pr-loop/fix-protocol.md`](../../../_shared/pr-loop/fix-protocol.md);
68
+ skill deltas in [`fix-protocol.md`](fix-protocol.md))
69
+ → push → reset push-invalidated markers → `phase = CODE_REVIEW` → Step 5
70
+ - [ ] **clean on `current_head`** → zero unresolved threads (else fix + resolve first)
71
+ → `bugbot_clean_at = current_head` → Step 7
72
+ - [ ] **no review / commit_id mismatch** → `reviewer-gates` Bugbot flow (Gate 3):
73
+ silent pass → stamp + Step 7; queued/triggered → 360s wakeup → Step 4;
74
+ down → `bugbot_down = true` → Step 7
75
+
76
+ - [ ] **Step 5: CODE-REVIEW — static sweep, review, fix, advance**
77
+ Entry phase every tick; re-entered after any fix push.
78
+ See: [`per-tick.md` § CODE_REVIEW entry](per-tick.md).
79
+ Pre-condition: cwd is the Step 1.5 PR worktree on `current_head`.
80
+ Scope: FULL `origin/main...HEAD` diff every tick (no path args, no delta cut).
81
+ Review always runs at effort high on model opus through
82
+ `invoke_code_review.py`. Mode decision inputs: host profile + session
83
+ model. Call:
84
+ `python "$HOME/.claude/scripts/invoke_code_review.py" --cwd <PR-worktree>
85
+ --session-model <alias>`. Chain mode uses that cwd and empty stdin; the
86
+ chain process never commits and never pushes. JSON stdout carries
87
+ `mode` (`in_session` | `chain`), `served_command`, `returncode`, and
88
+ `dirty_tree`. Config/host errors still emit that JSON with non-zero
89
+ `returncode` (no traceback-only failure).
90
+
91
+ - [ ] **Static sweep fails** → apply shared fix protocol → push → reset markers
92
+ → stay CODE_REVIEW → Step 5
93
+ - [ ] **`mode == in_session`** (Claude host, session model opus) → run
94
+ `/code-review high --fix` in-session (no path args)
95
+ - [ ] **`mode == chain`** (any other host or non-opus session) → helper
96
+ already ran the headless review; read `returncode`,
97
+ `served_command`, and `dirty_tree` from JSON
98
+ - [ ] **failed review** (`returncode != 0`, or chain with null
99
+ `served_command`) → do not set `code_review_clean_at` → stay
100
+ CODE_REVIEW → Step 5
101
+ - [ ] **fixes applied** (`dirty_tree` true / working tree dirty) →
102
+ commit + push via shared fix protocol → reset markers → stay
103
+ CODE_REVIEW → Step 5
104
+ - [ ] **clean** (successful serve: `returncode == 0`, chain
105
+ `served_command` non-null when chain, and `dirty_tree` false) →
106
+ zero unresolved threads (else fix + resolve) →
107
+ `code_review_clean_at = current_head` → `phase = BUGTEAM` → Step 6
108
+
109
+ - [ ] **Step 6: BUGTEAM — run, decide, fix, reply, resolve**
110
+ See: [`per-tick.md` § Step 2 BUGTEAM](per-tick.md);
111
+ [`../../bugteam/SKILL.md`](../../bugteam/SKILL.md).
112
+ Pre-condition: `code_review_clean_at == current_head`.
113
+ Mandatory: `Skill({skill: "bugteam", args: "<PR URL>"})` this tick
114
+ (enforcer-blocked otherwise; `qbug` is not a substitute). Scope: FULL
115
+ `origin/main...HEAD` diff. Re-resolve HEAD after bugteam.
116
+
117
+ - [ ] **bugteam pushed** → verify threads replied + resolved → reset markers
118
+ → `phase = CODE_REVIEW` → 360s wakeup → Step 5
119
+ - [ ] **converged, no push** → zero unresolved threads →
120
+ `bugteam_clean_at = current_head` → `phase = BUGBOT` → Step 4
121
+ - [ ] **findings without committed fixes** → apply shared fix protocol → push →
122
+ reset markers → `phase = CODE_REVIEW` → Step 5
123
+
124
+ - [ ] **Step 7: Convergence gates**
125
+ Full procedure: [`convergence-gates.md`](convergence-gates.md).
126
+
127
+ Pre-condition: Step 6 converged AND (`bugbot_clean_at == current_head` OR
128
+ `bugbot_down`). The terminal Bugbot gate (Step 4) sets that state just
129
+ before these gates run. Count unresolved threads before each gate.
130
+ Every gate records evidence; gate (f) cites evidence from (a)–(e).
131
+
132
+ - [ ] **(a) Copilot findings** — fetch Copilot on `current_head`; dirty → fix + return to Step 5; clean → stamp `copilot_clean_at`; absent → continue; when `copilot_down`, skip
133
+ - [ ] **(b) Claude reviewer** — fetch Claude on `current_head`; dirty → fix + return to Step 5; clean or absent → continue
134
+ - [ ] **(c) Mergeability** — `mergeable_state == "clean"` and `mergeable == true`; dirty → rebase + return to Step 1; blocked/behind/unknown/unstable → hard blocker
135
+ - [ ] **(d) Post-convergence Copilot request** — request Copilot when not pending and not `copilot_down`; enter `COPILOT_WAIT` (Step 7a); when `copilot_down`, skip to (e)
136
+ - [ ] **(e) Thread-resolution** — zero unresolved threads across the PR; else sweep + fix/resolve
137
+ - [ ] **(f) Mark ready** — run `check_convergence.py`; exit 0 → `update_pull_request(draft=false)` → Step 8; exit 1 → fix path; exit 2 → retry/stop
138
+
139
+ - [ ] **Step 7a: COPILOT_WAIT — fetch Copilot, decide**
140
+ See: [`per-tick.md` § Step 2 COPILOT_WAIT](per-tick.md).
141
+ Skipped entirely when `copilot_down` (gate (d) never enters this phase).
142
+
143
+ - [ ] **clean** → `copilot_clean_at = current_head` → Step 7 (re-validate (b), (c), (e), (f))
144
+ - [ ] **dirty** → apply shared fix protocol
145
+ ([`../../../_shared/pr-loop/fix-protocol.md`](../../../_shared/pr-loop/fix-protocol.md);
146
+ skill deltas in [`fix-protocol.md`](fix-protocol.md))
147
+ → push → reset markers → `phase = CODE_REVIEW` → Step 5
148
+ - [ ] **no review yet** → increment `copilot_wait_count` → ≥ 3 hard-blocks;
149
+ else 360s wakeup → Step 7a next tick
150
+
151
+ - [ ] **Step 8: Clean working tree**
152
+ `pr-loop-lifecycle` Close (`../../pr-loop-lifecycle/SKILL.md`).
153
+ See: [`../../pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Clean working tree](../../pr-loop-lifecycle/reference/teardown-publish-permissions.md)
154
+
155
+ - [ ] **Step 9: Rewrite PR description**
156
+ `pr-loop-lifecycle` Close.
157
+ See: [`../../pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Publish the final PR description](../../pr-loop-lifecycle/reference/teardown-publish-permissions.md)
158
+
159
+ - [ ] **Step 10: Revoke project permissions (always)**
160
+ `pr-loop-lifecycle` Close (`../../pr-loop-lifecycle/SKILL.md` § Close).
161
+
162
+ - [ ] **Step 11: Print final report**
163
+ Print this block verbatim — no paraphrase, no extra commentary:
164
+ ```
165
+ /pr-converge exit: converged
166
+ Loops: <N>
167
+ Final commit: <SHA>
168
+ ```
@@ -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
@@ -11,7 +11,7 @@ from pathlib import Path
11
11
 
12
12
 
13
13
  SKILL_ROOT = Path(__file__).resolve().parent
14
- SKILL_MARKDOWN_PATH = SKILL_ROOT / "SKILL.md"
14
+ PROGRESS_CHECKLIST_MARKDOWN_PATH = SKILL_ROOT / "reference" / "progress-checklist.md"
15
15
  PER_TICK_MARKDOWN_PATH = SKILL_ROOT / "reference" / "per-tick.md"
16
16
 
17
17
  HELPER_SCRIPT_NAME = "invoke_code_review.py"
@@ -45,10 +45,10 @@ def _code_review_section(per_tick_text: str) -> str:
45
45
  return per_tick_text[section_start:section_end]
46
46
 
47
47
 
48
- def _step_five_checklist(skill_text: str) -> str:
49
- step_start = skill_text.index("**Step 5: CODE-REVIEW")
50
- step_end = skill_text.index("**Step 6: BUGTEAM", step_start)
51
- return skill_text[step_start:step_end]
48
+ def _step_five_checklist(progress_checklist_text: str) -> str:
49
+ step_start = progress_checklist_text.index("**Step 5: CODE-REVIEW")
50
+ step_end = progress_checklist_text.index("**Step 6: BUGTEAM", step_start)
51
+ return progress_checklist_text[step_start:step_end]
52
52
 
53
53
 
54
54
  def test_per_tick_code_review_names_helper_and_mode_inputs() -> None:
@@ -75,7 +75,9 @@ def test_per_tick_code_review_names_helper_and_mode_inputs() -> None:
75
75
 
76
76
 
77
77
  def test_skill_step_five_checklist_names_helper_and_mode_inputs() -> None:
78
- step_five_checklist = _step_five_checklist(_read_markdown(SKILL_MARKDOWN_PATH))
78
+ step_five_checklist = _step_five_checklist(
79
+ _read_markdown(PROGRESS_CHECKLIST_MARKDOWN_PATH)
80
+ )
79
81
  assert HELPER_SCRIPT_NAME in step_five_checklist
80
82
  assert HOST_PROFILE_PHRASE in step_five_checklist
81
83
  assert SESSION_MODEL_PHRASE in step_five_checklist
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pr-loop-cloud-transport
3
- description: Runs any PR-loop skill in a Claude Code session whose gh CLI cannot act on the PR — the binary is absent, gh auth status fails, or the active login cannot act on the PR's owner. A six-step transport workflow loads the GitHub MCP schemas, fixes origin/HEAD before pushes, keys review rules to the live MCP identity, routes every GitHub operation through the gh-to-MCP substitution matrix, reads Copilot status from what lands on the PR, and self-checks the posts the gh-text hooks cannot gate. Use at run start when pr-converge, autoconverge, bugteam, qbug, findbugs, fixbugs, monitor-open-prs, or copilot-review runs in a cloud session, when `command -v gh` or `gh auth status` fails, or when an MCP tool call fails with InputValidationError.
3
+ description: Runs any PR-loop skill in a Claude Code session whose gh CLI cannot act on the PR. Use at run start when pr-converge, autoconverge, bugteam, qbug, findbugs, fixbugs, monitor-open-prs, or copilot-review runs in a cloud session, when `command -v gh` or `gh auth status` fails, or when an MCP tool call fails with InputValidationError.
4
4
  ---
5
5
 
6
6
  # PR-loop cloud transport