claude-dev-env 1.36.1 → 1.37.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/_shared/pr-loop/audit-contract.md +159 -0
- package/_shared/pr-loop/code-rules-gate.md +64 -0
- package/_shared/pr-loop/fix-protocol.md +37 -0
- package/_shared/pr-loop/gh-payloads.md +85 -0
- package/_shared/pr-loop/scripts/README.md +20 -0
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +234 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +975 -0
- package/_shared/pr-loop/scripts/config/__init__.py +0 -0
- package/_shared/pr-loop/scripts/config/claude_permissions_constants.py +36 -0
- package/_shared/pr-loop/scripts/config/claude_settings_keys_constants.py +11 -0
- package/_shared/pr-loop/scripts/config/code_rules_gate_constants.py +56 -0
- package/_shared/pr-loop/scripts/config/fix_hookspath_constants.py +25 -0
- package/_shared/pr-loop/scripts/config/gh_util_constants.py +31 -0
- package/_shared/pr-loop/scripts/config/preflight_constants.py +68 -0
- package/_shared/pr-loop/scripts/fix_hookspath.py +260 -0
- package/_shared/pr-loop/scripts/gh_util.py +193 -0
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +130 -0
- package/_shared/pr-loop/scripts/preflight.py +449 -0
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +156 -0
- package/_shared/pr-loop/scripts/tests/conftest.py +51 -0
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +135 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +169 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +58 -0
- package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +50 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +917 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +102 -0
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +374 -0
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +47 -0
- package/_shared/pr-loop/scripts/tests/test_gh_util.py +257 -0
- package/_shared/pr-loop/scripts/tests/test_gh_util_constants.py +61 -0
- package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +49 -0
- package/_shared/pr-loop/scripts/tests/test_preflight.py +670 -0
- package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +77 -0
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +49 -0
- package/_shared/pr-loop/state-schema.md +81 -0
- package/hooks/blocking/code_rules_enforcer.py +269 -23
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +157 -1
- package/hooks/config/test_unused_module_import_constants.py +48 -0
- package/hooks/config/unused_module_import_constants.py +41 -0
- package/package.json +2 -1
- package/skills/bg-agent/SKILL.md +69 -0
- package/skills/bugteam/CONSTRAINTS.md +10 -19
- package/skills/bugteam/PROMPTS.md +3 -3
- package/skills/bugteam/SKILL.md +103 -202
- package/skills/bugteam/SKILL_EVALS.md +75 -114
- package/skills/bugteam/reference/README.md +2 -4
- package/skills/bugteam/reference/design-rationale.md +3 -8
- package/skills/bugteam/reference/team-setup.md +11 -19
- package/skills/bugteam/reference/teardown-publish-permissions.md +2 -14
- package/skills/bugteam/scripts/config/__init__.py +0 -0
- package/skills/bugteam/scripts/config/reflow_skill_md_constants.py +12 -0
- package/skills/bugteam/scripts/reflow_skill_md.py +51 -47
- package/skills/bugteam/sources.md +1 -25
- package/skills/bugteam/test_skill_additions.py +4 -13
- package/skills/fresh-branch/SKILL.md +71 -0
- package/skills/gotcha/SKILL.md +73 -0
- package/skills/monitor-open-prs/SKILL.md +4 -37
- package/skills/monitor-open-prs/test_skill_contract.py +0 -5
- package/skills/pr-converge/SKILL.md +60 -1298
- package/skills/pr-converge/reference/convergence-gates.md +118 -0
- package/skills/pr-converge/reference/examples.md +76 -0
- package/skills/pr-converge/reference/fix-protocol.md +54 -0
- package/skills/pr-converge/reference/ground-rules.md +13 -0
- package/skills/pr-converge/reference/multi-pr-orchestration.md +204 -0
- package/skills/pr-converge/reference/per-tick.md +201 -0
- package/skills/pr-converge/reference/state-schema.md +19 -0
- package/skills/pr-converge/reference/stop-conditions.md +26 -0
- package/skills/pr-converge/scripts/README.md +36 -9
- package/skills/pr-converge/scripts/check_pr_mergeability.py +1 -2
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +58 -5
- package/skills/pr-converge/scripts/config/reflow_skill_md_constants.py +13 -0
- package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +0 -24
- package/skills/pr-converge/scripts/cursor-agents-continue.ahk +22 -2
- package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +19 -59
- package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +15 -61
- package/skills/pr-converge/scripts/fetch_claude_inline_comments.py +70 -0
- package/skills/pr-converge/scripts/fetch_claude_reviews.py +61 -0
- package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +19 -61
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +14 -74
- package/skills/pr-converge/scripts/reflow_skill_md.py +71 -50
- package/skills/pr-converge/scripts/reviewer_fetch_core.py +153 -0
- package/skills/pr-converge/scripts/reviewer_specs.py +98 -0
- package/skills/pr-converge/scripts/test_cursor_agents_continue.py +65 -0
- package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +107 -6
- package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +85 -6
- package/skills/pr-converge/scripts/test_fetch_claude_inline_comments.py +485 -0
- package/skills/pr-converge/scripts/test_fetch_claude_reviews.py +368 -0
- package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +74 -6
- package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +94 -8
- package/skills/pr-converge/scripts/test_reflow_skill_md.py +162 -0
- package/skills/pr-converge/scripts/test_reviewer_fetch_core.py +448 -0
- package/skills/pr-converge/scripts/test_reviewer_specs.py +107 -0
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +24 -22
- package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +0 -113
- package/skills/bugteam/reference/workflow-path-b-task-harness.md +0 -48
- package/skills/bugteam/test_team_lifecycle.py +0 -103
- package/skills/monitor-open-prs/test_team_lifecycle.py +0 -46
- package/skills/pr-converge/scripts/open_followup_copilot_pr.py +0 -136
- package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +0 -236
- package/skills/pr-converge/test_team_lifecycle.py +0 -56
- package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +0 -108
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
"""Tests for open_followup_copilot_pr.
|
|
2
|
-
|
|
3
|
-
Covers:
|
|
4
|
-
- branch name follows COPILOT_FOLLOWUP_BRANCH_TEMPLATE with the short SHA
|
|
5
|
-
- subprocess sequence: gh pr view (base ref) -> git fetch -> git switch -c -> git push -> gh pr create
|
|
6
|
-
- gh pr create uses --draft, --base, --head, --title, --body-file (per gh-body-file rule)
|
|
7
|
-
- the returned PR URL is the trimmed stdout from gh pr create
|
|
8
|
-
- subprocess errors propagate
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import importlib.util
|
|
14
|
-
import json
|
|
15
|
-
import subprocess
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from types import ModuleType
|
|
18
|
-
from unittest.mock import MagicMock, patch
|
|
19
|
-
|
|
20
|
-
import pytest
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _load_module() -> ModuleType:
|
|
24
|
-
module_path = Path(__file__).parent / "open_followup_copilot_pr.py"
|
|
25
|
-
spec = importlib.util.spec_from_file_location(
|
|
26
|
-
"open_followup_copilot_pr", module_path
|
|
27
|
-
)
|
|
28
|
-
assert spec is not None
|
|
29
|
-
assert spec.loader is not None
|
|
30
|
-
module = importlib.util.module_from_spec(spec)
|
|
31
|
-
spec.loader.exec_module(module)
|
|
32
|
-
return module
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
open_followup_copilot_pr_module = _load_module()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _completed(stdout: str) -> subprocess.CompletedProcess:
|
|
39
|
-
process = MagicMock(spec=subprocess.CompletedProcess)
|
|
40
|
-
process.stdout = stdout
|
|
41
|
-
process.returncode = 0
|
|
42
|
-
return process
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _scripted_subprocess_runs(
|
|
46
|
-
*,
|
|
47
|
-
base_ref_payload: str,
|
|
48
|
-
new_pr_url: str,
|
|
49
|
-
) -> list[subprocess.CompletedProcess]:
|
|
50
|
-
return [
|
|
51
|
-
_completed(base_ref_payload),
|
|
52
|
-
_completed(""),
|
|
53
|
-
_completed(""),
|
|
54
|
-
_completed(""),
|
|
55
|
-
_completed(new_pr_url),
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_should_build_branch_name_from_parent_number_and_short_sha(
|
|
60
|
-
tmp_path: Path,
|
|
61
|
-
) -> None:
|
|
62
|
-
findings_file = tmp_path / "findings.md"
|
|
63
|
-
findings_file.write_text("- Item 1\n", encoding="utf-8")
|
|
64
|
-
payload_sequence = _scripted_subprocess_runs(
|
|
65
|
-
base_ref_payload=json.dumps({"baseRefName": "main"}),
|
|
66
|
-
new_pr_url="https://github.com/acme/widget/pull/313\n",
|
|
67
|
-
)
|
|
68
|
-
with patch("subprocess.run", side_effect=payload_sequence) as mock_run:
|
|
69
|
-
open_followup_copilot_pr_module.open_followup_copilot_pr(
|
|
70
|
-
owner="acme",
|
|
71
|
-
repo="widget",
|
|
72
|
-
parent_number=312,
|
|
73
|
-
head="abc12345deadbeefcafe",
|
|
74
|
-
findings_file=findings_file,
|
|
75
|
-
)
|
|
76
|
-
git_switch_argv = mock_run.call_args_list[2][0][0]
|
|
77
|
-
expected_branch = "chore/copilot-followup-312-abc12345"
|
|
78
|
-
assert expected_branch in git_switch_argv
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def test_should_invoke_subprocess_call_sequence_in_documented_order(
|
|
82
|
-
tmp_path: Path,
|
|
83
|
-
) -> None:
|
|
84
|
-
findings_file = tmp_path / "findings.md"
|
|
85
|
-
findings_file.write_text("- Item\n", encoding="utf-8")
|
|
86
|
-
payload_sequence = _scripted_subprocess_runs(
|
|
87
|
-
base_ref_payload=json.dumps({"baseRefName": "main"}),
|
|
88
|
-
new_pr_url="https://github.com/acme/widget/pull/313\n",
|
|
89
|
-
)
|
|
90
|
-
with patch("subprocess.run", side_effect=payload_sequence) as mock_run:
|
|
91
|
-
open_followup_copilot_pr_module.open_followup_copilot_pr(
|
|
92
|
-
owner="acme",
|
|
93
|
-
repo="widget",
|
|
94
|
-
parent_number=312,
|
|
95
|
-
head="abc12345deadbeefcafe",
|
|
96
|
-
findings_file=findings_file,
|
|
97
|
-
)
|
|
98
|
-
invoked_command_sequence = [
|
|
99
|
-
each_call[0][0] for each_call in mock_run.call_args_list
|
|
100
|
-
]
|
|
101
|
-
assert invoked_command_sequence[0][0:3] == ["gh", "pr", "view"]
|
|
102
|
-
assert invoked_command_sequence[1][0:2] == ["git", "fetch"]
|
|
103
|
-
assert invoked_command_sequence[2][0:3] == ["git", "switch", "-c"]
|
|
104
|
-
assert invoked_command_sequence[3][0:2] == ["git", "push"]
|
|
105
|
-
assert invoked_command_sequence[4][0:3] == ["gh", "pr", "create"]
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def test_should_invoke_gh_pr_create_with_draft_and_body_file_flags(
|
|
109
|
-
tmp_path: Path,
|
|
110
|
-
) -> None:
|
|
111
|
-
findings_file = tmp_path / "findings.md"
|
|
112
|
-
findings_file.write_text("- Finding A\n", encoding="utf-8")
|
|
113
|
-
payload_sequence = _scripted_subprocess_runs(
|
|
114
|
-
base_ref_payload=json.dumps({"baseRefName": "develop"}),
|
|
115
|
-
new_pr_url="https://github.com/acme/widget/pull/444\n",
|
|
116
|
-
)
|
|
117
|
-
with patch("subprocess.run", side_effect=payload_sequence) as mock_run:
|
|
118
|
-
open_followup_copilot_pr_module.open_followup_copilot_pr(
|
|
119
|
-
owner="acme",
|
|
120
|
-
repo="widget",
|
|
121
|
-
parent_number=312,
|
|
122
|
-
head="abc12345deadbeef",
|
|
123
|
-
findings_file=findings_file,
|
|
124
|
-
)
|
|
125
|
-
pr_create_argv = mock_run.call_args_list[4][0][0]
|
|
126
|
-
assert pr_create_argv[0:3] == ["gh", "pr", "create"]
|
|
127
|
-
assert "--draft" in pr_create_argv
|
|
128
|
-
assert "--base" in pr_create_argv
|
|
129
|
-
assert "develop" in pr_create_argv
|
|
130
|
-
assert "--head" in pr_create_argv
|
|
131
|
-
assert "--title" in pr_create_argv
|
|
132
|
-
assert "--body-file" in pr_create_argv
|
|
133
|
-
body_file_argv = pr_create_argv[pr_create_argv.index("--body-file") + 1]
|
|
134
|
-
assert body_file_argv == str(findings_file)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def test_should_render_pr_title_via_named_template_constant(tmp_path: Path) -> None:
|
|
138
|
-
findings_file = tmp_path / "findings.md"
|
|
139
|
-
findings_file.write_text("- Finding\n", encoding="utf-8")
|
|
140
|
-
payload_sequence = _scripted_subprocess_runs(
|
|
141
|
-
base_ref_payload=json.dumps({"baseRefName": "main"}),
|
|
142
|
-
new_pr_url="https://github.com/acme/widget/pull/513\n",
|
|
143
|
-
)
|
|
144
|
-
with patch("subprocess.run", side_effect=payload_sequence) as mock_run:
|
|
145
|
-
open_followup_copilot_pr_module.open_followup_copilot_pr(
|
|
146
|
-
owner="acme",
|
|
147
|
-
repo="widget",
|
|
148
|
-
parent_number=312,
|
|
149
|
-
head="abc12345deadbeef",
|
|
150
|
-
findings_file=findings_file,
|
|
151
|
-
)
|
|
152
|
-
pr_create_argv = mock_run.call_args_list[4][0][0]
|
|
153
|
-
title_index = pr_create_argv.index("--title")
|
|
154
|
-
title_value = pr_create_argv[title_index + 1]
|
|
155
|
-
assert title_value == "chore: address Copilot findings from PR #312"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def test_should_return_trimmed_pr_url(tmp_path: Path) -> None:
|
|
159
|
-
findings_file = tmp_path / "findings.md"
|
|
160
|
-
findings_file.write_text("- Finding\n", encoding="utf-8")
|
|
161
|
-
payload_sequence = _scripted_subprocess_runs(
|
|
162
|
-
base_ref_payload=json.dumps({"baseRefName": "main"}),
|
|
163
|
-
new_pr_url=" https://github.com/acme/widget/pull/313 \n",
|
|
164
|
-
)
|
|
165
|
-
with patch("subprocess.run", side_effect=payload_sequence):
|
|
166
|
-
new_pr_url = open_followup_copilot_pr_module.open_followup_copilot_pr(
|
|
167
|
-
owner="acme",
|
|
168
|
-
repo="widget",
|
|
169
|
-
parent_number=312,
|
|
170
|
-
head="abc12345deadbeef",
|
|
171
|
-
findings_file=findings_file,
|
|
172
|
-
)
|
|
173
|
-
assert new_pr_url == "https://github.com/acme/widget/pull/313"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def test_should_pass_repo_arg_to_gh_pr_view_for_base_ref(tmp_path: Path) -> None:
|
|
177
|
-
findings_file = tmp_path / "findings.md"
|
|
178
|
-
findings_file.write_text("- Finding\n", encoding="utf-8")
|
|
179
|
-
payload_sequence = _scripted_subprocess_runs(
|
|
180
|
-
base_ref_payload=json.dumps({"baseRefName": "main"}),
|
|
181
|
-
new_pr_url="https://github.com/acme/widget/pull/313\n",
|
|
182
|
-
)
|
|
183
|
-
with patch("subprocess.run", side_effect=payload_sequence) as mock_run:
|
|
184
|
-
open_followup_copilot_pr_module.open_followup_copilot_pr(
|
|
185
|
-
owner="acme",
|
|
186
|
-
repo="widget",
|
|
187
|
-
parent_number=312,
|
|
188
|
-
head="abc12345deadbeef",
|
|
189
|
-
findings_file=findings_file,
|
|
190
|
-
)
|
|
191
|
-
pr_view_argv = mock_run.call_args_list[0][0][0]
|
|
192
|
-
assert pr_view_argv[0:3] == ["gh", "pr", "view"]
|
|
193
|
-
assert "--repo" in pr_view_argv
|
|
194
|
-
repo_arg_value = pr_view_argv[pr_view_argv.index("--repo") + 1]
|
|
195
|
-
assert repo_arg_value == "acme/widget"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def test_should_pass_repo_arg_to_gh_pr_create_for_followup_pr(
|
|
199
|
-
tmp_path: Path,
|
|
200
|
-
) -> None:
|
|
201
|
-
findings_file = tmp_path / "findings.md"
|
|
202
|
-
findings_file.write_text("- Finding\n", encoding="utf-8")
|
|
203
|
-
payload_sequence = _scripted_subprocess_runs(
|
|
204
|
-
base_ref_payload=json.dumps({"baseRefName": "main"}),
|
|
205
|
-
new_pr_url="https://github.com/acme/widget/pull/313\n",
|
|
206
|
-
)
|
|
207
|
-
with patch("subprocess.run", side_effect=payload_sequence) as mock_run:
|
|
208
|
-
open_followup_copilot_pr_module.open_followup_copilot_pr(
|
|
209
|
-
owner="acme",
|
|
210
|
-
repo="widget",
|
|
211
|
-
parent_number=312,
|
|
212
|
-
head="abc12345deadbeef",
|
|
213
|
-
findings_file=findings_file,
|
|
214
|
-
)
|
|
215
|
-
pr_create_argv = mock_run.call_args_list[4][0][0]
|
|
216
|
-
assert pr_create_argv[0:3] == ["gh", "pr", "create"]
|
|
217
|
-
assert "--repo" in pr_create_argv
|
|
218
|
-
repo_arg_value = pr_create_argv[pr_create_argv.index("--repo") + 1]
|
|
219
|
-
assert repo_arg_value == "acme/widget"
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def test_should_raise_when_subprocess_fails(tmp_path: Path) -> None:
|
|
223
|
-
findings_file = tmp_path / "findings.md"
|
|
224
|
-
findings_file.write_text("- Finding\n", encoding="utf-8")
|
|
225
|
-
failure = subprocess.CalledProcessError(
|
|
226
|
-
returncode=1, cmd=["gh"], stderr="auth failure"
|
|
227
|
-
)
|
|
228
|
-
with patch("subprocess.run", side_effect=failure):
|
|
229
|
-
with pytest.raises(subprocess.CalledProcessError):
|
|
230
|
-
open_followup_copilot_pr_module.open_followup_copilot_pr(
|
|
231
|
-
owner="acme",
|
|
232
|
-
repo="widget",
|
|
233
|
-
parent_number=312,
|
|
234
|
-
head="abc12345",
|
|
235
|
-
findings_file=findings_file,
|
|
236
|
-
)
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"""Markdown assertion tests for pr-converge orchestrator team lifecycle.
|
|
2
|
-
|
|
3
|
-
Locks in the contract that pr-converge multi-PR orchestration must:
|
|
4
|
-
- own a single long-lived team for the whole sweep
|
|
5
|
-
- pass that team to every bugteam invocation via attach mode
|
|
6
|
-
- tear down only when every PR reaches `converged` or `blocked`
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import pathlib
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _skill_text() -> str:
|
|
15
|
-
here = pathlib.Path(__file__).parent
|
|
16
|
-
return (here / "SKILL.md").read_text(encoding="utf-8")
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def test_skill_documents_orchestrator_owned_team_in_multi_pr_mode():
|
|
20
|
-
skill_text = _skill_text()
|
|
21
|
-
assert "team_name" in skill_text
|
|
22
|
-
assert "TeamCreate" in skill_text
|
|
23
|
-
assert "orchestrator" in skill_text.lower()
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def test_skill_passes_attach_mode_to_bugteam_invocations():
|
|
27
|
-
skill_text = _skill_text()
|
|
28
|
-
assert "BUGTEAM_TEAM_LIFECYCLE" in skill_text
|
|
29
|
-
assert "attach" in skill_text
|
|
30
|
-
assert "BUGTEAM_TEAM_NAME" in skill_text
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def test_skill_tears_down_team_only_on_full_convergence():
|
|
34
|
-
skill_text = _skill_text()
|
|
35
|
-
assert "TeamDelete" in skill_text
|
|
36
|
-
convergence_phrases = [
|
|
37
|
-
"every PR",
|
|
38
|
-
"all PRs",
|
|
39
|
-
"fully converged",
|
|
40
|
-
"every prs[",
|
|
41
|
-
]
|
|
42
|
-
assert any(phrase in skill_text for phrase in convergence_phrases)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def test_state_schema_includes_team_name_field():
|
|
46
|
-
skill_text = _skill_text()
|
|
47
|
-
assert '"team_name"' in skill_text or "team_name:" in skill_text
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def test_skill_md_physical_lines_fit_eighty_column_limit():
|
|
51
|
-
skill_text = _skill_text()
|
|
52
|
-
for each_line_number, each_physical_line in enumerate(skill_text.splitlines(), 1):
|
|
53
|
-
assert len(each_physical_line) <= 80, (
|
|
54
|
-
"SKILL.md line %s exceeds 80 columns (%s chars)"
|
|
55
|
-
% (each_line_number, len(each_physical_line))
|
|
56
|
-
)
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# AHK auto-continue loop pacing (pr-converge)
|
|
2
|
-
|
|
3
|
-
Load this document when **`ScheduleWakeup` is not available** in this session (orchestrated teams disabled, restricted tool registry, Cursor
|
|
4
|
-
without that primitive, or the user wants a visibly-running pacer). Follow it for **every** instruction below that depends on that choice.
|
|
5
|
-
Shared bugbot / bugteam / Fix protocol steps stay in the main `SKILL.md`.
|
|
6
|
-
|
|
7
|
-
## Session behavior
|
|
8
|
-
|
|
9
|
-
Keep ticks in the **same** window the auto-typer targets so each `continue` re-enters here and reads the same state line and `gh` context.
|
|
10
|
-
|
|
11
|
-
## Why this path exists
|
|
12
|
-
|
|
13
|
-
It is not a separate "mode" the user must remember — bare `/pr-converge` already implies loop-until-done; when the primary wakeup tool is
|
|
14
|
-
missing, fall through to AHK automatically. The per-tick work is unchanged; what changes is who fires the next tick. Instead of
|
|
15
|
-
`ScheduleWakeup` re-entering the skill, an external AutoHotkey utility auto-types `continue` into the active Claude Code window every 5
|
|
16
|
-
minutes, and the model treats each `continue` as the next tick trigger.
|
|
17
|
-
|
|
18
|
-
**AHK is loop pacing only:** every `phase == BUGTEAM` tick still runs **`/bugteam`** via the bugteam skill per Step 2 of the main skill —
|
|
19
|
-
nothing here replaces that audit.
|
|
20
|
-
|
|
21
|
-
**Fix protocol** commits use **`Task`** with **`subagent_type: "generalPurpose"`** and the **clean-coder preamble** from the main
|
|
22
|
-
[`SKILL.md` Fix protocol](../SKILL.md#fix-protocol) section (same as ScheduleWakeup pacing — Cursor has no `clean-coder` `subagent_type`).
|
|
23
|
-
|
|
24
|
-
Ensure `~/.claude/agents/clean-coder.md` exists (Windows: `%USERPROFILE%\.claude\agents\clean-coder.md`). Optionally also copy it to
|
|
25
|
-
`.cursor/agents/clean-coder.md` in the repo when you want the file co-located with the checkout; the spawn **prompt** must still name the
|
|
26
|
-
absolute path the subagent should **Read** first.
|
|
27
|
-
|
|
28
|
-
### One-time setup at the start of the loop
|
|
29
|
-
|
|
30
|
-
The skill bundles its driver scripts under `scripts/` and resolves them at runtime via `$HOME\.claude\skills\pr-converge\scripts\…` (the same
|
|
31
|
-
convention `/logifix` uses). The bundled `.cmd` launchers locate their siblings via `%~dp0`, so they need no path arguments — only the AHK
|
|
32
|
-
target PID.
|
|
33
|
-
|
|
34
|
-
Run these two commands in order (PowerShell-friendly Bash escaping):
|
|
35
|
-
|
|
36
|
-
1. Resolve the PID of the GUI ancestor that hosts this Claude Code session:
|
|
37
|
-
```bash
|
|
38
|
-
pwsh -NoProfile -ExecutionPolicy Bypass -File "$HOME\.claude\skills\pr-converge\scripts\caller-window-pid.ps1"
|
|
39
|
-
```
|
|
40
|
-
Capture the printed integer as `caller_pid`. Verify it points at the right window before continuing:
|
|
41
|
-
```bash
|
|
42
|
-
pwsh -NoProfile -Command "Get-Process -Id $caller_pid | Select-Object Id,ProcessName,MainWindowTitle"
|
|
43
|
-
```
|
|
44
|
-
2. Launch the auto-typer attached to that PID with auto-start enabled. The bundled launcher accepts the PID as its first arg and the
|
|
45
|
-
`--start-on` flag is forwarded to the AHK script:
|
|
46
|
-
```bash
|
|
47
|
-
"$HOME\.claude\skills\pr-converge\scripts\cursor-agents-continue.cmd" $caller_pid --start-on
|
|
48
|
-
```
|
|
49
|
-
AutoHotkey v2 must be installed at `C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe`.
|
|
50
|
-
|
|
51
|
-
### Per-tick behavior under this driver
|
|
52
|
-
|
|
53
|
-
- Run Steps 1–3 of **Per-tick work** in the main `SKILL.md` exactly as written.
|
|
54
|
-
- In **Step 4**, do **not** call `ScheduleWakeup` — the auto-typer is the pacer (this is the fallback branch of Step 4 in the main skill).
|
|
55
|
-
- End every assistant response with the literal sentence `Awaiting next "continue" tick.` so the next iteration is unambiguously identifiable
|
|
56
|
-
in the transcript.
|
|
57
|
-
- When the next user message is `continue` (auto-typed by AHK) or any close paraphrase, treat it as the next tick of default-loop
|
|
58
|
-
`/pr-converge` and re-enter from Step 1 against the freshest PR state.
|
|
59
|
-
|
|
60
|
-
### Convergence cleanup
|
|
61
|
-
|
|
62
|
-
On back-to-back clean (the existing convergence rule in the main skill), run `gh pr ready`, then kill the auto-typer when this session used
|
|
63
|
-
this AHK pacing path:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
pwsh -NoProfile -Command "Get-CimInstance Win32_Process -Filter \"Name='AutoHotkey64.exe'\" | Where-Object CommandLine -like '*cursor-agents-continue.ahk*' | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }"
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Report convergence in the same one-sentence shape as the standard flow, plus a second sentence noting the auto-typer was stopped. The skill
|
|
70
|
-
returns; no next tick fires.
|
|
71
|
-
|
|
72
|
-
### Gotchas
|
|
73
|
-
|
|
74
|
-
- **Resolver fallback semantics matter.** `caller-window-pid.ps1` walks up the parent process chain, terminates at `explorer.exe`, and falls
|
|
75
|
-
back to the foreground window when no GUI ancestor is found. Always verify `MainWindowTitle` after capture — if it isn't the Claude Code
|
|
76
|
-
session, the auto-typer will fire `continue` into the wrong window and the loop stalls silently.
|
|
77
|
-
- **Tick-duration vs. 5-minute cadence.** The auto-typer fires every 5 minutes regardless of model activity. A tick that runs longer than 5
|
|
78
|
-
minutes will receive a queued `continue` while still in flight; Claude Code processes these sequentially, so there's no corruption, but the
|
|
79
|
-
loop pace becomes irregular. Don't try to "fix" this by shortening the AHK interval — the `bugbot run` cadence already has its own pacing
|
|
80
|
-
baked into the standard flow.
|
|
81
|
-
- **AHK runs as `#SingleInstance Force`.** Re-running the launcher replaces the prior instance silently. Safe to re-issue if the loop appears
|
|
82
|
-
stalled.
|
|
83
|
-
- **`Stop-Process -Force` on `AutoHotkey64` is broad.** It kills every AHK instance, not just the one this skill started. When the user has
|
|
84
|
-
unrelated AHK utilities running, scope the kill by command-line match instead:
|
|
85
|
-
```bash
|
|
86
|
-
pwsh -NoProfile -Command "Get-CimInstance Win32_Process -Filter \"Name='AutoHotkey64.exe'\" | Where-Object CommandLine -like '*cursor-agents-continue.ahk*' | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }"
|
|
87
|
-
```
|
|
88
|
-
- **State-line responsibility is unchanged.** The state line (phase, bugbot_clean_at, inline_lag_streak, tick_count) is still emitted at the
|
|
89
|
-
end of every tick — it's how the next tick reads prior state. The auto-typer only fires `continue`; it does not preserve state for you.
|
|
90
|
-
- **No `tick_count` ceiling.** `tick_count` is observability-only (same as the main skill and `state-schema.md`). This path ends on convergence or **Stop conditions** in `SKILL.md`, not on a tick counter.
|
|
91
|
-
- **`/bugteam` is not optional for BUGTEAM ticks.** AHK only paces **when** the next tick runs; it does not replace the bugteam skill. Skipping
|
|
92
|
-
**`/bugteam`** after a clean Bugbot review breaks the back-to-back contract.
|
|
93
|
-
- **Fix protocol:** use **`Task` + `generalPurpose`** with the clean-coder **Read** preamble from the main [`SKILL.md` Fix protocol](../SKILL.md#fix-protocol)
|
|
94
|
-
(never a bare `generalPurpose` production edit). Ensure the clean-coder agent markdown exists at `~/.claude/agents/clean-coder.md` (Windows:
|
|
95
|
-
`%USERPROFILE%\.claude\agents\clean-coder.md`); copy into `.cursor/agents/` only if you want a repo-local duplicate.
|
|
96
|
-
|
|
97
|
-
## BUGBOT inline-lag (this path only)
|
|
98
|
-
|
|
99
|
-
When Step 2 BUGBOT branch c routes to API lag and you are on **this** pacing path: complete Step 4 per **Per-tick behavior under this driver**
|
|
100
|
-
above (fixed AHK cadence — there is no 60s shortcut). The inline comments should appear on the next tick.
|
|
101
|
-
|
|
102
|
-
## Convergence
|
|
103
|
-
|
|
104
|
-
On back-to-back clean: stop the auto-typer per **Convergence cleanup** above; omit `ScheduleWakeup` (not used on this path).
|
|
105
|
-
|
|
106
|
-
## Stop / safety (this path)
|
|
107
|
-
|
|
108
|
-
On hard blockers or user stop: omit loop pacing and stop the AHK auto-typer if it was started, per main skill **Stop conditions**. Use the same **scoped** `Get-CimInstance` / `Stop-Process` command as **Convergence cleanup** (command-line match on `cursor-agents-continue.ahk`) so unrelated AutoHotkey instances are not killed.
|