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,207 @@
|
|
|
1
|
+
"""Tests for select_converge_pacer — host tool surface → pacer mapping."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
13
|
+
if str(_SCRIPTS_DIR) not in sys.path:
|
|
14
|
+
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
15
|
+
|
|
16
|
+
import select_converge_pacer as pacer_module # noqa: E402
|
|
17
|
+
from skills_pr_loop_constants.pacer_constants import ( # noqa: E402
|
|
18
|
+
CLI_HAS_SCHEDULE_WAKEUP_FLAG,
|
|
19
|
+
CLI_HAS_WORKFLOW_FLAG,
|
|
20
|
+
CLI_SKILL_FLAG,
|
|
21
|
+
ENTRY_SKILL_AUTOCONVERGE,
|
|
22
|
+
ENTRY_SKILL_PR_CONVERGE,
|
|
23
|
+
EXIT_SUCCESS,
|
|
24
|
+
EXIT_USAGE_ERROR,
|
|
25
|
+
PACER_PORTABLE,
|
|
26
|
+
PACER_SCHEDULE_WAKEUP,
|
|
27
|
+
PACER_WORKFLOW,
|
|
28
|
+
RESULT_KEY_ENTRY_SKILL,
|
|
29
|
+
RESULT_KEY_HAS_SCHEDULE_WAKEUP,
|
|
30
|
+
RESULT_KEY_HAS_WORKFLOW,
|
|
31
|
+
RESULT_KEY_PACER,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
SCRIPT_PATH = _SCRIPTS_DIR / "select_converge_pacer.py"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_autoconverge_with_workflow_selects_workflow() -> None:
|
|
38
|
+
selection = pacer_module.select_converge_pacer(
|
|
39
|
+
entry_skill=ENTRY_SKILL_AUTOCONVERGE,
|
|
40
|
+
has_workflow=True,
|
|
41
|
+
has_schedule_wakeup=False,
|
|
42
|
+
)
|
|
43
|
+
assert selection.pacer == PACER_WORKFLOW
|
|
44
|
+
assert selection.entry_skill == ENTRY_SKILL_AUTOCONVERGE
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_autoconverge_without_workflow_selects_portable() -> None:
|
|
48
|
+
selection = pacer_module.select_converge_pacer(
|
|
49
|
+
entry_skill=ENTRY_SKILL_AUTOCONVERGE,
|
|
50
|
+
has_workflow=False,
|
|
51
|
+
has_schedule_wakeup=True,
|
|
52
|
+
)
|
|
53
|
+
assert selection.pacer == PACER_PORTABLE
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_pr_converge_with_schedule_wakeup_selects_schedule_wakeup() -> None:
|
|
57
|
+
selection = pacer_module.select_converge_pacer(
|
|
58
|
+
entry_skill=ENTRY_SKILL_PR_CONVERGE,
|
|
59
|
+
has_workflow=False,
|
|
60
|
+
has_schedule_wakeup=True,
|
|
61
|
+
)
|
|
62
|
+
assert selection.pacer == PACER_SCHEDULE_WAKEUP
|
|
63
|
+
assert selection.entry_skill == ENTRY_SKILL_PR_CONVERGE
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_pr_converge_without_schedule_wakeup_selects_portable() -> None:
|
|
67
|
+
selection = pacer_module.select_converge_pacer(
|
|
68
|
+
entry_skill=ENTRY_SKILL_PR_CONVERGE,
|
|
69
|
+
has_workflow=True,
|
|
70
|
+
has_schedule_wakeup=False,
|
|
71
|
+
)
|
|
72
|
+
assert selection.pacer == PACER_PORTABLE
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_third_party_surface_selects_portable_for_both_entries() -> None:
|
|
76
|
+
for each_skill in (ENTRY_SKILL_AUTOCONVERGE, ENTRY_SKILL_PR_CONVERGE):
|
|
77
|
+
selection = pacer_module.select_converge_pacer(
|
|
78
|
+
entry_skill=each_skill,
|
|
79
|
+
has_workflow=False,
|
|
80
|
+
has_schedule_wakeup=False,
|
|
81
|
+
)
|
|
82
|
+
assert selection.pacer == PACER_PORTABLE, each_skill
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_unknown_entry_skill_raises() -> None:
|
|
86
|
+
with pytest.raises(ValueError, match="entry skill must be one of"):
|
|
87
|
+
pacer_module.select_converge_pacer(
|
|
88
|
+
entry_skill="bugteam",
|
|
89
|
+
has_workflow=False,
|
|
90
|
+
has_schedule_wakeup=False,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_parse_bool_flag_accepts_common_tokens() -> None:
|
|
95
|
+
assert pacer_module.parse_bool_flag("1") is True
|
|
96
|
+
assert pacer_module.parse_bool_flag("true") is True
|
|
97
|
+
assert pacer_module.parse_bool_flag("YES") is True
|
|
98
|
+
assert pacer_module.parse_bool_flag("0") is False
|
|
99
|
+
assert pacer_module.parse_bool_flag("false") is False
|
|
100
|
+
assert pacer_module.parse_bool_flag("OFF") is False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_parse_bool_flag_rejects_unknown_token() -> None:
|
|
104
|
+
with pytest.raises(ValueError, match="boolean token"):
|
|
105
|
+
pacer_module.parse_bool_flag("maybe")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_selection_as_json_dict_uses_stable_keys() -> None:
|
|
109
|
+
selection = pacer_module.select_converge_pacer(
|
|
110
|
+
entry_skill=ENTRY_SKILL_PR_CONVERGE,
|
|
111
|
+
has_workflow=False,
|
|
112
|
+
has_schedule_wakeup=False,
|
|
113
|
+
)
|
|
114
|
+
payload = pacer_module.selection_as_json_dict(selection)
|
|
115
|
+
assert payload[RESULT_KEY_PACER] == PACER_PORTABLE
|
|
116
|
+
assert payload[RESULT_KEY_ENTRY_SKILL] == ENTRY_SKILL_PR_CONVERGE
|
|
117
|
+
assert payload[RESULT_KEY_HAS_WORKFLOW] is False
|
|
118
|
+
assert payload[RESULT_KEY_HAS_SCHEDULE_WAKEUP] is False
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_cli_prints_json_and_exits_zero() -> None:
|
|
122
|
+
completed = subprocess.run(
|
|
123
|
+
[
|
|
124
|
+
sys.executable,
|
|
125
|
+
str(SCRIPT_PATH),
|
|
126
|
+
"--skill",
|
|
127
|
+
ENTRY_SKILL_AUTOCONVERGE,
|
|
128
|
+
"--has-workflow",
|
|
129
|
+
"0",
|
|
130
|
+
"--has-schedule-wakeup",
|
|
131
|
+
"0",
|
|
132
|
+
],
|
|
133
|
+
check=False,
|
|
134
|
+
capture_output=True,
|
|
135
|
+
text=True,
|
|
136
|
+
)
|
|
137
|
+
assert completed.returncode == EXIT_SUCCESS, completed.stderr
|
|
138
|
+
payload = json.loads(completed.stdout)
|
|
139
|
+
assert payload[RESULT_KEY_PACER] == PACER_PORTABLE
|
|
140
|
+
assert payload[RESULT_KEY_ENTRY_SKILL] == ENTRY_SKILL_AUTOCONVERGE
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_cli_rejects_bad_bool_with_usage_exit() -> None:
|
|
144
|
+
completed = subprocess.run(
|
|
145
|
+
[
|
|
146
|
+
sys.executable,
|
|
147
|
+
str(SCRIPT_PATH),
|
|
148
|
+
"--skill",
|
|
149
|
+
ENTRY_SKILL_PR_CONVERGE,
|
|
150
|
+
"--has-workflow",
|
|
151
|
+
"nope",
|
|
152
|
+
"--has-schedule-wakeup",
|
|
153
|
+
"0",
|
|
154
|
+
],
|
|
155
|
+
check=False,
|
|
156
|
+
capture_output=True,
|
|
157
|
+
text=True,
|
|
158
|
+
)
|
|
159
|
+
assert completed.returncode == EXIT_USAGE_ERROR
|
|
160
|
+
assert "boolean token" in completed.stderr
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_main_returns_usage_error_on_value_error(
|
|
164
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
165
|
+
) -> None:
|
|
166
|
+
def raise_value_error(**_kwargs: object) -> object:
|
|
167
|
+
raise ValueError("entry skill must be one of")
|
|
168
|
+
|
|
169
|
+
monkeypatch.setattr(
|
|
170
|
+
pacer_module, "select_converge_pacer", raise_value_error
|
|
171
|
+
)
|
|
172
|
+
exit_code = pacer_module.main(
|
|
173
|
+
[
|
|
174
|
+
"--skill",
|
|
175
|
+
ENTRY_SKILL_PR_CONVERGE,
|
|
176
|
+
"--has-workflow",
|
|
177
|
+
"0",
|
|
178
|
+
"--has-schedule-wakeup",
|
|
179
|
+
"0",
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
assert exit_code == EXIT_USAGE_ERROR
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_build_argument_parser_requires_skill_and_host_flags() -> None:
|
|
186
|
+
parser = pacer_module.build_argument_parser()
|
|
187
|
+
all_option_strings = {
|
|
188
|
+
each_option
|
|
189
|
+
for each_action in parser._actions
|
|
190
|
+
for each_option in each_action.option_strings
|
|
191
|
+
}
|
|
192
|
+
assert CLI_SKILL_FLAG in all_option_strings
|
|
193
|
+
assert CLI_HAS_WORKFLOW_FLAG in all_option_strings
|
|
194
|
+
assert CLI_HAS_SCHEDULE_WAKEUP_FLAG in all_option_strings
|
|
195
|
+
parsed_namespace = parser.parse_args(
|
|
196
|
+
[
|
|
197
|
+
CLI_SKILL_FLAG,
|
|
198
|
+
ENTRY_SKILL_AUTOCONVERGE,
|
|
199
|
+
CLI_HAS_WORKFLOW_FLAG,
|
|
200
|
+
"1",
|
|
201
|
+
CLI_HAS_SCHEDULE_WAKEUP_FLAG,
|
|
202
|
+
"0",
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
assert parsed_namespace.skill == ENTRY_SKILL_AUTOCONVERGE
|
|
206
|
+
assert parsed_namespace.has_workflow == "1"
|
|
207
|
+
assert parsed_namespace.has_schedule_wakeup == "0"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Trigger:** `/autoconverge`, "autoconverge this PR", "converge this PR in one run", "run the converge workflow", "drive the PR to ready autonomously".
|
|
4
4
|
|
|
5
|
-
Drives one draft PR to convergence in
|
|
5
|
+
Drives one draft PR to convergence in one autonomous run. On `pacer=workflow`, each round runs a deterministic static sweep first, then a code-review pass, a bug-audit (with its adversarial second pass), and a self-review parity pass in parallel on the same HEAD, deduplicates findings, applies every fix in one commit, and re-verifies; state lives in the workflow journal. On `pacer=portable`, the continuous driver in `_shared/pr-loop/portable-driver.md` runs the shared pr-converge phase machine with the same helpers and `check_convergence.py` ready definition. Once the internal passes are clean, Cursor Bugbot, Copilot, and Codex run as terminal confirmation gates, and the run marks the PR ready on convergence.
|
|
6
6
|
|
|
7
7
|
## Subdirectories
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ Drives one draft PR to convergence in a single autonomous workflow run. Each rou
|
|
|
15
15
|
|
|
16
16
|
| File | Role |
|
|
17
17
|
|---|---|
|
|
18
|
-
| `SKILL.md` | Full entry-point protocol: pre-flight steps, worktree setup,
|
|
18
|
+
| `SKILL.md` | Full entry-point protocol: pacer selection, pre-flight steps, worktree setup, Workflow or portable path, budget-aware boundaries, teardown, and the convergence loop summary. |
|
|
19
19
|
| `workflow/converge.mjs` | Main convergence workflow. Runs a static sweep then the internal lenses each round, applies fixes, runs Bugbot, Copilot, and Codex as terminal gates, checks convergence, and marks the PR ready. |
|
|
20
20
|
| `workflow/aggregate_runs.py` | Merges every autoconverge journal for a PR into one deduped journal. |
|
|
21
21
|
| `workflow/convergence_summary.py` | Builds the convergence-summary agent prompt over the merged findings. |
|
|
@@ -31,4 +31,6 @@ Drives one draft PR to convergence in a single autonomous workflow run. Each rou
|
|
|
31
31
|
|
|
32
32
|
## Entry point
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Selects `workflow` or `portable` via `select_converge_pacer.py`. Missing
|
|
35
|
+
`Workflow` selects portable and continues — it does not abort. The `SKILL.md`
|
|
36
|
+
body specifies the pre-flight sequence and each pacer's run path.
|
|
@@ -1,48 +1,66 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: autoconverge
|
|
3
3
|
description: >-
|
|
4
|
-
Drives one draft PR to convergence in one autonomous run
|
|
5
|
-
code
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
Drives one draft PR to convergence in one autonomous run with a host-selected
|
|
5
|
+
pacer. On workflow: three parallel lenses (code-review, bug-audit, self-review)
|
|
6
|
+
and one-commit fixes via converge.mjs. On portable: pr-converge continuous ticks
|
|
7
|
+
via portable-driver.md. Bugbot, Copilot, and Codex as terminal confirmation
|
|
8
|
+
gates before ready. Use when the user says '/autoconverge', 'autoconverge this
|
|
9
|
+
PR', 'converge this PR in one run', 'run the converge workflow', or 'drive the
|
|
10
|
+
PR to ready autonomously'.
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
# Autoconverge
|
|
12
14
|
|
|
13
15
|
One launch drives the whole loop to convergence. The `/autoconverge` skill
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
state file. State lives in the workflow's own variables; resume is handled by
|
|
18
|
-
the workflow journal.
|
|
16
|
+
scans the tool list, selects a **pacer** (Workflow or portable), then resolves
|
|
17
|
+
PR scope, enters a worktree, grants project permissions, and drives the shared
|
|
18
|
+
converge product to ready (or a named blocker).
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
| Pacer | Host surface | How the loop runs |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `workflow` | Tool list includes `Workflow` | `converge.mjs` background pass; state in the workflow journal |
|
|
23
|
+
| `portable` | `Workflow` absent | Continuous in-session ticks per [`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md); same helpers and `check_convergence.py` ready definition |
|
|
24
|
+
|
|
25
|
+
`pr-converge` selects `schedule_wakeup` or `portable` the same way. Both
|
|
26
|
+
entry skills share the helper scripts and the convergence gate.
|
|
23
27
|
|
|
24
28
|
## Run scope: one PR or several
|
|
25
29
|
|
|
26
30
|
Decide the scope from how many PRs the user named, then follow that path:
|
|
27
31
|
|
|
28
|
-
1. **One PR** → the single-PR run described below
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
PR
|
|
32
|
+
1. **One PR** → the single-PR run described below: one worktree, one pacer
|
|
33
|
+
launch (`converge.mjs` on `pacer=workflow`, portable continuous ticks on
|
|
34
|
+
`pacer=portable`), one teardown.
|
|
35
|
+
2. **Several PRs** → the [Multiple PRs](reference/multi-pr.md) run: on
|
|
36
|
+
`pacer=workflow`, `workflow/converge_multi.mjs` drives every PR in parallel;
|
|
37
|
+
on `pacer=portable`, run the portable driver once per PR (serial or host
|
|
38
|
+
fan-out), then one teardown per PR.
|
|
34
39
|
|
|
35
|
-
The single-PR sections (Requirements, Pre-flight, Run the workflow
|
|
36
|
-
each describe one converge run. The multi-PR reference reuses
|
|
37
|
-
and adds only what fanning out needs: a per-PR worktree and a
|
|
38
|
-
loop.
|
|
40
|
+
The single-PR sections (Requirements, Pre-flight, Run the workflow or portable
|
|
41
|
+
driver, Teardown) each describe one converge run. The multi-PR reference reuses
|
|
42
|
+
them once per PR and adds only what fanning out needs: a per-PR worktree and a
|
|
43
|
+
per-PR teardown loop.
|
|
39
44
|
|
|
40
45
|
## Requirements
|
|
41
46
|
|
|
42
|
-
Scan the tool list
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
Scan the tool list for `Workflow` and `ScheduleWakeup`, then select the pacer:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
python "$HOME/.claude/skills/_shared/pr-loop/scripts/select_converge_pacer.py" \
|
|
51
|
+
--skill autoconverge \
|
|
52
|
+
--has-workflow <0|1> \
|
|
53
|
+
--has-schedule-wakeup <0|1>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `pacer=workflow` — continue with Pre-flight and **Run the workflow** below.
|
|
57
|
+
- `pacer=portable` — continue with Pre-flight (portable worktree rules when
|
|
58
|
+
`EnterWorktree` is absent), then the continuous driver in
|
|
59
|
+
[`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md).
|
|
60
|
+
**Do not abort** because the Workflow tool is missing.
|
|
61
|
+
|
|
62
|
+
Transport still needs authenticated GitHub access for the PR's owner (`gh` or
|
|
63
|
+
`pr-loop-cloud-transport`).
|
|
46
64
|
|
|
47
65
|
## Review-lens boundary
|
|
48
66
|
|
|
@@ -65,11 +83,30 @@ orchestrating session's own steps, and the script's agent-prompt text carries
|
|
|
65
83
|
|
|
66
84
|
## Pre-flight (main session)
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
0. **Build the task list (step 1 of every run).** After Copilot quota / down
|
|
87
|
+
flags and Codex required/down are known, run only:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
python "$HOME/.claude/skills/_shared/pr-loop/scripts/build_converge_task_list.py" \
|
|
91
|
+
--bugbot-down <0|1> --copilot-down <0|1> \
|
|
92
|
+
--codex-down <0|1> --codex-required <0|1>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Register every `tasks[]` entry on the session task list. **Final task** is
|
|
96
|
+
always `all_runnable_reviews_clean_same_head` (`done_when` in the JSON).
|
|
97
|
+
The run is complete only when that final task is completed — all runnable
|
|
98
|
+
code reviews CLEAN on the same HEAD. Do not invent tasks in prose. On
|
|
99
|
+
`pacer=portable`, `open-run` returns the same list; still register it as
|
|
100
|
+
step 1 before driving reviews.
|
|
101
|
+
|
|
102
|
+
1. **Enter a worktree.** When `EnterWorktree` is in the tool list, call it with
|
|
103
|
+
no arguments before any `gh`, `git`, file read, or edit. `gh`/`git` Bash
|
|
104
|
+
calls do not auto-isolate, so isolation is mandatory. If it fails, report
|
|
105
|
+
and stop. A bare `EnterWorktree` branches from `origin/main`; step 2
|
|
106
|
+
positions the worktree on the PR's head ref. When `EnterWorktree` is
|
|
107
|
+
absent, isolate with git worktree machinery per
|
|
108
|
+
[`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md)
|
|
109
|
+
§ Isolation and worktree, then continue step 2.
|
|
73
110
|
|
|
74
111
|
2. **Resolve PR scope.** When the user passed a PR URL or number, parse owner,
|
|
75
112
|
repo, and number from it. Otherwise read the current branch's PR:
|
|
@@ -78,14 +115,15 @@ orchestrating session's own steps, and the script's agent-prompt text carries
|
|
|
78
115
|
ready, mark it draft first (`gh pr ready <n> --repo <o>/<r> --undo`) so the
|
|
79
116
|
loop owns the ready transition.
|
|
80
117
|
|
|
81
|
-
**Position the worktree on the PR branch.** The
|
|
118
|
+
**Position the worktree on the PR branch.** The run reviews
|
|
82
119
|
`git diff origin/main...HEAD` against this worktree's local `HEAD` and pushes
|
|
83
120
|
each fix to the PR branch, so the worktree sits on the PR's head ref at the PR
|
|
84
|
-
HEAD before the
|
|
121
|
+
HEAD before the loop starts. A worktree fresh off `origin/main` has
|
|
85
122
|
`HEAD == origin/main`, shows an empty diff, and reports a false convergence
|
|
86
123
|
with zero findings. When a local worktree already tracks the PR branch, enter
|
|
87
|
-
that one
|
|
88
|
-
|
|
124
|
+
that one (pass its path to `EnterWorktree` when that tool exists; otherwise
|
|
125
|
+
`cd` into it). Otherwise put the checkout on the branch with
|
|
126
|
+
`gh pr checkout <number> --repo <owner>/<repo>`
|
|
89
127
|
(or `git fetch origin <headRefName>` then `git switch <headRefName>`). Confirm
|
|
90
128
|
before launching: `git rev-parse --abbrev-ref HEAD` equals the PR's head ref
|
|
91
129
|
and local `HEAD` equals the PR head SHA.
|
|
@@ -93,27 +131,32 @@ orchestrating session's own steps, and the script's agent-prompt text carries
|
|
|
93
131
|
3. **Verify the worktree is the PR's repo (strict pre-flight).** Run
|
|
94
132
|
`python "$HOME/.claude/skills/_shared/pr-loop/scripts/preflight_worktree.py" --owner <owner> --repo <repo> --mode strict`.
|
|
95
133
|
It confirms the working directory is a checkout of the PR's own repo and
|
|
96
|
-
that `git worktree` machinery is healthy
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
git checkout at all cannot continue.
|
|
134
|
+
that `git worktree` machinery is healthy. A non-zero exit prints a
|
|
135
|
+
`PREFLIGHT_OUTCOME` line and an `ABORT` line: report that line and stop.
|
|
136
|
+
Autoconverge runs inside the PR's own repo, so a working directory rooted in
|
|
137
|
+
a different repo (for example, `claude-dev-env` while the PR lives in
|
|
138
|
+
`llm-settings`) or in no git checkout at all cannot continue.
|
|
102
139
|
|
|
103
140
|
4. **Grant project permissions.** Apply the `pr-loop-lifecycle` skill's Open
|
|
104
141
|
section (`../pr-loop-lifecycle/SKILL.md`) — the grant command
|
|
105
142
|
(`grant_project_claude_permissions.py`) and the auto-mode `AskUserQuestion`
|
|
106
143
|
escalation for a blocked grant both live there.
|
|
107
144
|
|
|
108
|
-
5. **Copilot quota pre-check.** Before the
|
|
145
|
+
5. **Copilot quota pre-check.** Before the pacer starts the loop, apply the
|
|
109
146
|
`reviewer-gates` skill's Copilot quota gate (`../reviewer-gates/SKILL.md`)
|
|
110
|
-
once. Exit 0 maps to `copilotDisabled: false`
|
|
111
|
-
non-zero exit maps to `copilotDisabled: true`, and the
|
|
112
|
-
the Copilot gate with no agent spawned.
|
|
147
|
+
once. Exit 0 maps to `copilotDisabled: false` / `copilot_down=false`; any
|
|
148
|
+
non-zero exit maps to `copilotDisabled: true` / `copilot_down=true`, and the
|
|
149
|
+
run skips the Copilot gate with no agent spawned.
|
|
150
|
+
|
|
151
|
+
6. **Branch on pacer.** When `pacer=portable`, follow
|
|
152
|
+
[`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md)
|
|
153
|
+
§ Continuous tick loop and § Autoconverge entry on portable pacer, then
|
|
154
|
+
skip **Run the workflow** (no Workflow tool). When `pacer=workflow`,
|
|
155
|
+
continue with **Run the workflow**.
|
|
113
156
|
|
|
114
157
|
## Run the workflow
|
|
115
158
|
|
|
116
|
-
Call the `Workflow` tool against the colocated script:
|
|
159
|
+
(`pacer=workflow` only.) Call the `Workflow` tool against the colocated script:
|
|
117
160
|
|
|
118
161
|
```
|
|
119
162
|
Workflow({
|
|
@@ -160,26 +203,89 @@ skips a reviewer that is down or out of quota without re-probing. This list is t
|
|
|
160
203
|
|
|
161
204
|
## Copilot findings — tier, verify, then route
|
|
162
205
|
|
|
163
|
-
The Copilot gate tiers each finding
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
206
|
+
The Copilot gate tiers each finding: a **self-healing** finding (style, type
|
|
207
|
+
hints, imports, formatting, magic-value extraction, test-only or doc-vs-code
|
|
208
|
+
fixes — nothing that changes observable runtime behavior) flows into the fix
|
|
209
|
+
round with no user notification. A **code-concern** finding (logic, security,
|
|
210
|
+
data handling, error-handling semantics, concurrency — the tier whenever in
|
|
211
|
+
doubt) goes to a verification stage before any routing.
|
|
212
|
+
|
|
213
|
+
Each code-concern finding gets its own verifier agent, all in parallel, inside
|
|
214
|
+
the workflow. A verdict is conclusive only when an actual check ran: the verifier
|
|
215
|
+
executes a command against the flagged HEAD — running the code path with crafted
|
|
216
|
+
inputs, forcing the claimed error condition, or running a purpose-built test —
|
|
217
|
+
and captures its output. The verdict carries
|
|
218
|
+
`{ verdict, checkCommand, checkOutput, evidence }`; a conclusive verdict with an
|
|
219
|
+
empty `checkCommand` or `checkOutput` downgrades to inconclusive.
|
|
220
|
+
|
|
221
|
+
- **confirmed** — the check reproduces the defect. The finding becomes
|
|
222
|
+
self-healing: it joins the fix round carrying its repro, and the fix re-runs
|
|
223
|
+
that same check, adds a regression test where the suite covers the surface,
|
|
224
|
+
lands in one commit, pushes, and replies on the thread with the fix SHA and the
|
|
225
|
+
before/after output. No page.
|
|
226
|
+
- **refuted** — the check shows the code already behaves correctly in the exact
|
|
227
|
+
scenario the finding claims is broken. The workflow replies on the thread with
|
|
228
|
+
the command and output, resolves it, and counts it clean. No page.
|
|
229
|
+
- **inconclusive** — everything else, and the verifier's default: no runnable
|
|
230
|
+
check exists, the check is infeasible here, the results are ambiguous, or the
|
|
231
|
+
fix needs a product decision. Any doubt sorts here. Only inconclusive findings
|
|
232
|
+
page the user.
|
|
233
|
+
|
|
234
|
+
A round whose code concerns all confirm or refute never returns
|
|
235
|
+
`blocker: "user-review"`. On one or more inconclusive findings, the workflow
|
|
236
|
+
stops with `converged: false`, `blocker: "user-review"`, and a `userReview`
|
|
237
|
+
field carrying
|
|
238
|
+
`{ reviewUrl, findings: [{ file, line, severity, tier, title, evidence }] }` —
|
|
239
|
+
`evidence` is the verifier's one-line note stating what check was attempted and
|
|
240
|
+
why it was not decisive.
|
|
241
|
+
|
|
242
|
+
The wait for a human belongs to the orchestrating session. On a
|
|
243
|
+
`blocker: "user-review"` return, run the
|
|
244
|
+
[`copilot-finding-triage`](../copilot-finding-triage/SKILL.md) skill for the ntfy
|
|
245
|
+
page only (the per-finding summary and evidence note plus the `reviewUrl`
|
|
246
|
+
Copilot review link), then hold for the user's response by pacer. On
|
|
247
|
+
`pacer=portable`, override triage's hold step — ntfy plus the portable hold
|
|
248
|
+
below; do not follow triage's `ScheduleWakeup` / `send_later` path.
|
|
249
|
+
|
|
250
|
+
- **`pacer=workflow`** — triage hold: 45-minute `ScheduleWakeup` (or
|
|
251
|
+
`send_later` when that is the host arm).
|
|
252
|
+
- **`pacer=portable`** — portable hold only: in-session poll or handoff per
|
|
253
|
+
[`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md);
|
|
254
|
+
never `ScheduleWakeup` or `send_later`.
|
|
255
|
+
|
|
256
|
+
When the user answers within the window, follow their direction. When the
|
|
257
|
+
window closes with no response, run normal teardown and report the
|
|
258
|
+
inconclusive findings un-reviewed.
|
|
171
259
|
|
|
172
260
|
## Budget stop
|
|
173
261
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
262
|
+
Branch the stop and resume path on the selected pacer. Full rule:
|
|
263
|
+
[`reference/stop-conditions.md`](reference/stop-conditions.md) § Budget stop.
|
|
264
|
+
|
|
265
|
+
- **`pacer=workflow`** — `converge.mjs` paces against the workflow `budget` API
|
|
266
|
+
and stops at a round boundary when a full round does not fit. On a
|
|
267
|
+
`blocker: "budget"` return, write the durable handoff with the run id and the
|
|
268
|
+
`Workflow({scriptPath, resumeFromRunId})` resume command before stopping, so a
|
|
269
|
+
fresh session resumes the paced run without the stopped session's transcript.
|
|
270
|
+
- **`pacer=portable`** — stop at a tick boundary when the session cannot cover a
|
|
271
|
+
full clean tick; write the durable handoff and resume with
|
|
272
|
+
`/autoconverge <PR URL>` (see
|
|
273
|
+
[`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md)).
|
|
274
|
+
Do not instruct a Workflow resume on the portable path.
|
|
275
|
+
|
|
276
|
+
## Teardown
|
|
277
|
+
|
|
278
|
+
### `pacer=portable`
|
|
279
|
+
|
|
280
|
+
Skip Workflow-only report steps that need a workflow run id (journal merge,
|
|
281
|
+
HTML closing report, Artifact publish). Run `pr-loop-lifecycle` Close
|
|
282
|
+
(description rewrite when converged, working-tree clean, permission revoke)
|
|
283
|
+
and print the final report block using tick/`check_convergence` outcomes.
|
|
284
|
+
User-review holds use the portable in-session poll (or handoff) from
|
|
285
|
+
[`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md)
|
|
286
|
+
— not `ScheduleWakeup`. Resume command: `/autoconverge <PR URL>`.
|
|
181
287
|
|
|
182
|
-
|
|
288
|
+
### `pacer=workflow` (on workflow completion)
|
|
183
289
|
|
|
184
290
|
Teardown runs as an ordered checkpoint list. After each checkpoint finishes,
|
|
185
291
|
re-write the durable handoff with `--phase teardown`, the run id, and the
|
|
@@ -201,15 +307,18 @@ On teardown entry, when `~/.claude/runtime/pr-loop/bugteam-pr-<N>/handoff.json`
|
|
|
201
307
|
exists, read its `completed_steps` and skip any checkpoint the list already
|
|
202
308
|
names, so a resumed run performs only the checkpoints left.
|
|
203
309
|
|
|
204
|
-
On a `blocker: "user-review"` return
|
|
205
|
-
findings that stayed inconclusive after the
|
|
206
|
-
|
|
310
|
+
On a `blocker: "user-review"` return under `pacer=workflow`, the workflow held
|
|
311
|
+
one or more code-concern findings that stayed inconclusive after the
|
|
312
|
+
executed-check verification stage. Run the
|
|
313
|
+
[`copilot-finding-triage`](../copilot-finding-triage/SKILL.md) gate before
|
|
207
314
|
teardown: send the ntfy alert with the per-finding summary and evidence note and
|
|
208
|
-
the `userReview.reviewUrl` link, then hold with a 45-minute `ScheduleWakeup
|
|
209
|
-
on the user's direction when it
|
|
210
|
-
with no response, fall through
|
|
211
|
-
`userReview.findings` un-reviewed. The PR
|
|
212
|
-
workflow marked nothing ready.
|
|
315
|
+
the `userReview.reviewUrl` link, then hold with a 45-minute `ScheduleWakeup` (or
|
|
316
|
+
`send_later` when that is the host arm). Act on the user's direction when it
|
|
317
|
+
arrives inside the window; when the window closes with no response, fall through
|
|
318
|
+
to normal teardown and report the `userReview.findings` un-reviewed. The PR
|
|
319
|
+
stays a draft in this path — the workflow marked nothing ready. Under
|
|
320
|
+
`pacer=portable`, use the portable teardown hold above (ntfy plus in-session
|
|
321
|
+
poll or handoff; never `ScheduleWakeup`).
|
|
213
322
|
|
|
214
323
|
Before the checkpoints, when the workflow returned a non-null `copilotNote`
|
|
215
324
|
(the Copilot gate was bypassed), query the PR's reviews once more for a
|
|
@@ -277,8 +386,10 @@ When the user names several PRs, run the multi-PR path in
|
|
|
277
386
|
[`reference/multi-pr.md`](reference/multi-pr.md): one worktree per PR
|
|
278
387
|
(`git worktree add` on each head ref, strict pre-flight per worktree, one
|
|
279
388
|
permission grant per repository, one Copilot quota check for the whole run),
|
|
280
|
-
then
|
|
281
|
-
|
|
389
|
+
then launch by pacer — `pacer=workflow`: one `workflow/converge_multi.mjs`
|
|
390
|
+
call with one entry per PR; `pacer=portable`: portable driver once per PR
|
|
391
|
+
(serial or host fan-out) — then single-PR teardown once per PR and a
|
|
392
|
+
one-line-per-PR summary.
|
|
282
393
|
|
|
283
394
|
## Self-closing loop: converge the deferred PRs
|
|
284
395
|
|
|
@@ -294,6 +405,7 @@ also carries the Conventional-Commit title rule each hardening PR must meet.
|
|
|
294
405
|
## Folder map
|
|
295
406
|
|
|
296
407
|
- `SKILL.md` — this hub.
|
|
408
|
+
- [`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md) — portable pacer when `Workflow` is absent.
|
|
297
409
|
- `workflow/converge.mjs` — the convergence workflow script.
|
|
298
410
|
- `workflow/converge_multi.mjs` — the multi-PR fan-out driver: one `converge.mjs` child run per PR in parallel, each pinned to its PR worktree via `repoPath`.
|
|
299
411
|
- `workflow/aggregate_runs.py` — merges every autoconverge journal for a PR into one journal and returns its deduped findings, fix summaries, round count, and final SHA.
|
|
@@ -301,7 +413,6 @@ also carries the Conventional-Commit title rule each hardening PR must meet.
|
|
|
301
413
|
- `workflow/render_report.py` — builds the closing convergence insights HTML report, taking the summary from `--summary-file`.
|
|
302
414
|
- `workflow/autoconverge_report_constants/` — named constants for the report builder and the summary prompt.
|
|
303
415
|
- `reference/convergence.md` — the whole loop: reuse pass, round shape, terminal gates, model tiers, ready definition.
|
|
304
|
-
- `reference/copilot-findings.md` — the Copilot gate tiering, per-finding verification, and the `userReview` return contract.
|
|
305
416
|
- `reference/stop-conditions.md` — every way the run ends short of ready, including the budget stop.
|
|
306
417
|
- `reference/gotchas.md` — hard-won failure lessons.
|
|
307
418
|
- `reference/closing-report.md` — the closing HTML report: data source, build steps, publishing.
|
|
@@ -66,11 +66,11 @@ confirmation gates that are expected to return zero.
|
|
|
66
66
|
- **Code-review lens** — a correctness-focused review pass (`code-quality-agent`),
|
|
67
67
|
report-only workflow agent — see runCodeReviewLens in workflow/converge.mjs for its configuration.
|
|
68
68
|
The built-in `/code-review` command is a separate surface outside this
|
|
69
|
-
workflow path.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
workflow path. On `pacer=workflow`, this lens runs inside `converge.mjs`.
|
|
70
|
+
On `pacer=portable`, the continuous driver uses the pr-converge CODE_REVIEW
|
|
71
|
+
phase through `invoke_code_review.py` instead of `runCodeReviewLens`
|
|
72
|
+
([`../../_shared/pr-loop/portable-driver.md`](../../_shared/pr-loop/portable-driver.md);
|
|
73
|
+
[`../../pr-converge/reference/per-tick.md`](../../pr-converge/reference/per-tick.md)).
|
|
74
74
|
- **Bug-audit lens** — the bug-audit (`code-quality-agent`) applying the
|
|
75
75
|
shared A–P rubric from `_shared/pr-loop/audit-contract.md`, then its
|
|
76
76
|
adversarial second pass, and the doc-parity, test-assertion, and
|