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
|
@@ -165,7 +165,7 @@ def test_should_flag_each_unused_in_multi_import() -> None:
|
|
|
165
165
|
)
|
|
166
166
|
|
|
167
167
|
|
|
168
|
-
def
|
|
168
|
+
def test_should_not_flag_when_referenced_in_annotation() -> None:
|
|
169
169
|
source = (
|
|
170
170
|
"from typing import List\n\ndef run(xs: List[int]) -> None:\n return None\n"
|
|
171
171
|
)
|
|
@@ -242,3 +242,159 @@ def test_should_skip_star_import() -> None:
|
|
|
242
242
|
assert issues == [], (
|
|
243
243
|
f"Star imports cannot be meaningfully tracked - skip to avoid false positives, got: {issues}"
|
|
244
244
|
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_should_flag_when_name_only_appears_in_comment() -> None:
|
|
248
|
+
source = (
|
|
249
|
+
"import json\n"
|
|
250
|
+
"\n"
|
|
251
|
+
"# json reserved for later\n"
|
|
252
|
+
"def run() -> None:\n"
|
|
253
|
+
" return None\n"
|
|
254
|
+
)
|
|
255
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
256
|
+
assert any("json" in each_issue for each_issue in issues), (
|
|
257
|
+
f"Mentions in comments must not count as references, got: {issues}"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_should_not_skip_when_type_checking_only_in_string_constant() -> None:
|
|
262
|
+
source = (
|
|
263
|
+
'from config.constants import UNUSED_NAME\n'
|
|
264
|
+
'\n'
|
|
265
|
+
'HELP_TEXT = "See TYPE_CHECKING docs"\n'
|
|
266
|
+
'\n'
|
|
267
|
+
"def run() -> None:\n"
|
|
268
|
+
" return None\n"
|
|
269
|
+
)
|
|
270
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
271
|
+
assert any("UNUSED_NAME" in each_issue for each_issue in issues), (
|
|
272
|
+
f"Substring TYPE_CHECKING in prose must not skip the scan, got: {issues}"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_should_flag_when_noqa_lists_only_non_f401_codes() -> None:
|
|
277
|
+
source = (
|
|
278
|
+
"from config.constants import UNUSED # noqa: E402\n"
|
|
279
|
+
"\n"
|
|
280
|
+
"def run() -> None:\n"
|
|
281
|
+
" return None\n"
|
|
282
|
+
)
|
|
283
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
284
|
+
assert any("UNUSED" in each_issue for each_issue in issues), (
|
|
285
|
+
f"E402-only noqa must not suppress unused-import findings, got: {issues}"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_should_skip_when_noqa_is_bare() -> None:
|
|
290
|
+
source = (
|
|
291
|
+
"from config.constants import UNUSED # noqa\n"
|
|
292
|
+
"\n"
|
|
293
|
+
"def run() -> None:\n"
|
|
294
|
+
" return None\n"
|
|
295
|
+
)
|
|
296
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
297
|
+
assert issues == [], f"Bare noqa must suppress unused import, got: {issues}"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_should_flag_import_when_only_shadowed_local_name_is_loaded() -> None:
|
|
301
|
+
source = (
|
|
302
|
+
"import json\n"
|
|
303
|
+
"\n"
|
|
304
|
+
"def run(json: object) -> object:\n"
|
|
305
|
+
" return json\n"
|
|
306
|
+
)
|
|
307
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
308
|
+
assert any("json" in each_issue for each_issue in issues), (
|
|
309
|
+
f"Local shadow bindings must not count as import references, got: {issues}"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def test_should_skip_when_type_checking_uses_imported_alias() -> None:
|
|
314
|
+
source = (
|
|
315
|
+
"from typing import TYPE_CHECKING as IS_TYPE_CHECKING\n"
|
|
316
|
+
"from config.constants import UNUSED_NAME\n"
|
|
317
|
+
"\n"
|
|
318
|
+
"if IS_TYPE_CHECKING:\n"
|
|
319
|
+
" from somewhere import OtherName\n"
|
|
320
|
+
"\n"
|
|
321
|
+
"def run() -> None:\n"
|
|
322
|
+
" return None\n"
|
|
323
|
+
)
|
|
324
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
325
|
+
assert issues == [], (
|
|
326
|
+
f"TYPE_CHECKING imported aliases must skip annotation-only files, got: {issues}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def test_should_skip_when_type_checking_uses_module_alias() -> None:
|
|
331
|
+
source = (
|
|
332
|
+
"import typing as t\n"
|
|
333
|
+
"from config.constants import UNUSED_NAME\n"
|
|
334
|
+
"\n"
|
|
335
|
+
"if t.TYPE_CHECKING:\n"
|
|
336
|
+
" from somewhere import OtherName\n"
|
|
337
|
+
"\n"
|
|
338
|
+
"def run() -> None:\n"
|
|
339
|
+
" return None\n"
|
|
340
|
+
)
|
|
341
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
342
|
+
assert issues == [], (
|
|
343
|
+
f"TYPE_CHECKING module aliases must skip annotation-only files, got: {issues}"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def test_should_not_flag_when_referenced_in_quoted_annotation() -> None:
|
|
348
|
+
source = (
|
|
349
|
+
"from typing import List\n"
|
|
350
|
+
"\n"
|
|
351
|
+
"def run(xs: \"List[int]\") -> None:\n"
|
|
352
|
+
" return None\n"
|
|
353
|
+
)
|
|
354
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
355
|
+
assert issues == [], (
|
|
356
|
+
f"Quoted annotations must count as import references, got: {issues}"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def test_should_flag_when_noqa_only_appears_inside_string_literal() -> None:
|
|
361
|
+
source = (
|
|
362
|
+
"from config.constants import UNUSED; MARKER = '# noqa: F401'\n"
|
|
363
|
+
"\n"
|
|
364
|
+
"def run() -> None:\n"
|
|
365
|
+
" return None\n"
|
|
366
|
+
)
|
|
367
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
368
|
+
assert any("UNUSED" in each_issue for each_issue in issues), (
|
|
369
|
+
f"String literal noqa text must not suppress unused imports, got: {issues}"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def test_should_not_flag_when_class_body_binding_matches_import_used_in_method() -> None:
|
|
374
|
+
source = (
|
|
375
|
+
"import os\n"
|
|
376
|
+
"\n"
|
|
377
|
+
"class Foo:\n"
|
|
378
|
+
" os = 'linux'\n"
|
|
379
|
+
"\n"
|
|
380
|
+
" def bar(self) -> str:\n"
|
|
381
|
+
" return os.path.join('a', 'b')\n"
|
|
382
|
+
)
|
|
383
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
384
|
+
assert issues == [], (
|
|
385
|
+
f"Class body bindings must not shadow module-level imports inside methods, got: {issues}"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def test_should_not_flag_when_comprehension_variable_matches_import_used_after() -> None:
|
|
390
|
+
source = (
|
|
391
|
+
"import os\n"
|
|
392
|
+
"\n"
|
|
393
|
+
"def run() -> str:\n"
|
|
394
|
+
" result = [x for os in [1, 2, 3]]\n"
|
|
395
|
+
" return os.path.join('a', 'b')\n"
|
|
396
|
+
)
|
|
397
|
+
issues = check_unused_module_level_imports(source, PRODUCTION_FILE_PATH)
|
|
398
|
+
assert issues == [], (
|
|
399
|
+
f"Comprehension iteration variables must not shadow enclosing scope bindings, got: {issues}"
|
|
400
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Tests for unused module import constants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
9
|
+
if str(_HOOKS_ROOT) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
11
|
+
|
|
12
|
+
from config.unused_module_import_constants import line_suppresses_unused_import_via_noqa
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_line_suppresses_bare_noqa() -> None:
|
|
16
|
+
assert line_suppresses_unused_import_via_noqa(
|
|
17
|
+
"from x import y # noqa"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_line_suppresses_noqa_with_f401_code() -> None:
|
|
22
|
+
assert line_suppresses_unused_import_via_noqa(
|
|
23
|
+
"from x import y # noqa: F401"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_line_suppresses_noqa_with_mixed_codes_including_f401() -> None:
|
|
28
|
+
assert line_suppresses_unused_import_via_noqa(
|
|
29
|
+
"from x import y # noqa: E402, F401"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_line_does_not_suppress_noqa_with_only_non_f401_codes() -> None:
|
|
34
|
+
assert not line_suppresses_unused_import_via_noqa(
|
|
35
|
+
"from x import y # noqa: E402"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_line_does_not_suppress_without_noqa() -> None:
|
|
40
|
+
assert not line_suppresses_unused_import_via_noqa(
|
|
41
|
+
"from x import y # type: ignore"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_line_does_not_suppress_noqa_inside_string_literal() -> None:
|
|
46
|
+
assert not line_suppresses_unused_import_via_noqa(
|
|
47
|
+
"from x import y; marker = '# noqa: F401'"
|
|
48
|
+
)
|
|
@@ -1,7 +1,48 @@
|
|
|
1
1
|
"""Constants for the unused module-level import scan in ``code_rules_enforcer``."""
|
|
2
2
|
|
|
3
|
+
import io
|
|
4
|
+
import re
|
|
5
|
+
import tokenize
|
|
6
|
+
|
|
7
|
+
PYFLAKES_UNUSED_IMPORT_RULE_CODE: str = "F401"
|
|
8
|
+
NOQA_DIRECTIVE_PATTERN: re.Pattern[str] = re.compile(
|
|
9
|
+
r"#\s*noqa\b(?:\s*:\s*([^\n#]+))?",
|
|
10
|
+
re.IGNORECASE,
|
|
11
|
+
)
|
|
3
12
|
MAX_UNUSED_IMPORT_ISSUES: int = 25
|
|
4
13
|
UNUSED_IMPORT_GUIDANCE: str = (
|
|
5
14
|
"remove unused import; if kept for side effects, mark with `# noqa: F401`"
|
|
6
15
|
)
|
|
7
16
|
TYPE_CHECKING_IDENTIFIER: str = "TYPE_CHECKING"
|
|
17
|
+
ALL_TYPING_MODULE_NAMES: frozenset[str] = frozenset({"typing", "typing_extensions"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _comment_text_from_line(line_text: str) -> str | None:
|
|
21
|
+
try:
|
|
22
|
+
for each_token in tokenize.generate_tokens(io.StringIO(line_text).readline):
|
|
23
|
+
if each_token.type == tokenize.COMMENT:
|
|
24
|
+
return each_token.string
|
|
25
|
+
except tokenize.TokenError:
|
|
26
|
+
return None
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def line_suppresses_unused_import_via_noqa(line_text: str) -> bool:
|
|
31
|
+
"""Return True only for bare ``# noqa`` / ``#noqa`` or a code list that includes F401."""
|
|
32
|
+
comment_text = _comment_text_from_line(line_text)
|
|
33
|
+
if comment_text is None:
|
|
34
|
+
return False
|
|
35
|
+
match = NOQA_DIRECTIVE_PATTERN.search(comment_text)
|
|
36
|
+
if match is None:
|
|
37
|
+
return False
|
|
38
|
+
codes_part = match.group(1)
|
|
39
|
+
if codes_part is None or not codes_part.strip():
|
|
40
|
+
return True
|
|
41
|
+
for each_fragment in codes_part.split(","):
|
|
42
|
+
stripped = each_fragment.strip()
|
|
43
|
+
if not stripped:
|
|
44
|
+
continue
|
|
45
|
+
first_token = stripped.split()[0]
|
|
46
|
+
if first_token.upper() == PYFLAKES_UNUSED_IMPORT_RULE_CODE:
|
|
47
|
+
return True
|
|
48
|
+
return False
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-dev-env",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.0",
|
|
4
4
|
"description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"hooks/",
|
|
20
20
|
"system-prompts/",
|
|
21
21
|
"scripts/",
|
|
22
|
+
"_shared/",
|
|
22
23
|
"CLAUDE.md"
|
|
23
24
|
],
|
|
24
25
|
"keywords": [
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bg-agent
|
|
3
|
+
description: Delegates a task to a background agent. Invoked as "bg-agent [task to do]". Claude picks a suitable agent type from the available agents list and spawns it via Agent with run_in_background: true. Triggers on "/bg-agent", "bg-agent", "background agent for this".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# bg-agent
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Delegates a task to a background agent so the main session can continue without waiting. This is the programmatic invocation path for background work — other skills (e.g. gotcha) and the user can both invoke it.
|
|
11
|
+
|
|
12
|
+
**Announce at start:** "Delegating to a background agent: `<one-line summary of task>`."
|
|
13
|
+
|
|
14
|
+
## Instructions
|
|
15
|
+
|
|
16
|
+
### Step 1 — Parse the task
|
|
17
|
+
|
|
18
|
+
The user (or calling skill) provides a task description after `bg-agent`. Example:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
bg-agent add a gotcha to the rebase skill about force-push lease format
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Extract the full task description from the arguments.
|
|
25
|
+
|
|
26
|
+
### Step 2 — Select the right agent
|
|
27
|
+
|
|
28
|
+
Review the available agent types (listed in the system prompt's Agent tool description) and pick the most suitable one for the task:
|
|
29
|
+
|
|
30
|
+
- **Read-only tasks** (research, search, exploring code) → Explore agent or general-purpose agent.
|
|
31
|
+
- **Code authoring tasks** (writing/editing skill files, creating PRs) → general-purpose agent with `run_in_background: true`.
|
|
32
|
+
- **Specialized tasks** → pick the agent whose description best matches the task. For example, use `pr-description-writer` for PR descriptions, `git-commit-crafter` for commits.
|
|
33
|
+
|
|
34
|
+
If no specialized agent fits, use the general-purpose agent.
|
|
35
|
+
|
|
36
|
+
### Step 3 — Spawn the background agent
|
|
37
|
+
|
|
38
|
+
Use the `Agent` tool with `run_in_background: true`. Write a self-contained prompt that:
|
|
39
|
+
|
|
40
|
+
- States the exact goal and expected output.
|
|
41
|
+
- Lists the files or directories involved (from the caller's context).
|
|
42
|
+
- Includes any constraints (do not create a PR, do not push, etc.).
|
|
43
|
+
- Specifies what success looks like.
|
|
44
|
+
|
|
45
|
+
Example for a gotcha-adding task:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Agent({
|
|
49
|
+
description: "Add gotcha to skill file",
|
|
50
|
+
prompt: "Add a gotcha entry to packages/claude-dev-env/skills/rebase/SKILL.md. The gotcha is: 'force-push --force-with-lease requires the full <branch>:<sha> format, not just the branch name.' Add it under the ## Gotchas section. If no ## Gotchas section exists, create one at the bottom of the file.",
|
|
51
|
+
subagent_type: "general-purpose",
|
|
52
|
+
run_in_background: true
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Step 4 — Report spawn
|
|
57
|
+
|
|
58
|
+
Confirm the agent was spawned and state its task in one sentence. The caller does not need to wait for completion — background agents notify on completion automatically.
|
|
59
|
+
|
|
60
|
+
## Constraints
|
|
61
|
+
|
|
62
|
+
- Always use `run_in_background: true`. This skill is specifically for background delegation.
|
|
63
|
+
- Never run the task inline in the main session. The point is to offload it.
|
|
64
|
+
- If the task requires a PR, the spawned agent handles the full flow (branch → commit → push → PR).
|
|
65
|
+
- Return control to the caller immediately after spawning. Do not poll for completion.
|
|
66
|
+
|
|
67
|
+
## Gotchas
|
|
68
|
+
|
|
69
|
+
See the gotcha reference at the bottom of this file. When a new gotcha is discovered during use, invoke `/gotcha` to add it here.
|
|
@@ -1,35 +1,26 @@
|
|
|
1
1
|
# Bugteam — invariants and design rationale
|
|
2
2
|
|
|
3
|
-
## Path A vs Path B
|
|
4
|
-
|
|
5
|
-
**Path A** (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`): the constraints below apply as written — `TeamCreate`, isolated teammate sessions, lead-only `TeamDelete`. **Path B** (Task harness): read [`reference/workflow-path-b-task-harness.md`](reference/workflow-path-b-task-harness.md) for harness-only steps; **agent types** (`code-quality-agent`, `clean-coder`), **models**, **one commit per fix**, **gate-before-AUDIT**, **10-loop cap**, and **outcome XML** remain identical to `SKILL.md`. Path B intentionally uses **`Task`** from the lead instead of teammate isolation — see that file **Clean-room note**.
|
|
6
|
-
|
|
7
3
|
## Constraints
|
|
8
4
|
|
|
9
|
-
- **
|
|
10
|
-
- **Path B — Cursor `Task` registry.** When the host `Task` tool rejects `subagent_type="clean-coder"`, Path B FIX MUST use `subagent_type: "generalPurpose"` plus the mandatory **Read** of `clean-coder.md` in the FIX prompt per [`reference/workflow-path-b-task-harness.md`](reference/workflow-path-b-task-harness.md) (FIX spawn, Cursor host split). This is the documented shim, not an ad-hoc `generalPurpose` bypass of the clean-coder contract.
|
|
11
|
-
- **Path A — orchestrator-only `TeamCreate`.** Only the lead session (this session, when `/bugteam` is invoked) calls `TeamCreate`. Teammates never call `TeamCreate` — if a teammate's spawn prompt instructs it to, that is a skill defect. When additional parallel work is needed (e.g., parallel auditors from loop 4 onward, supplementary audit of adjacent files), the lead spawns additional teammates into the EXISTING team by passing the current `team_name` to every `Agent(...)` call. Multiple teammate "sets" live inside one team under one orchestrator. The runtime enforces this: `TeamCreate` called while the session already leads a team returns the error `Already leading team "<name>". A leader can only manage one team at a time. Use TeamDelete to end the current team before creating a new one.` — direct quote from the runtime's response when this invariant is violated. The Step 2 lifecycle resolution in [Team lifecycle](SKILL.md#team-lifecycle-path-a-only) parses this exact error in `auto` mode to attach to the existing team rather than fail. **Path B:** no `TeamCreate`; parallel work uses parallel **`Task`** calls per [`reference/workflow-path-b-task-harness.md`](reference/workflow-path-b-task-harness.md).
|
|
12
|
-
- **One team per invocation, multi-PR supported.** All PRs in a single /bugteam invocation share one team created by the orchestrator. Per-PR identity lives in the teammate name prefix (`bugfind-pr<N>-loop<L>` / `bugfix-pr<N>-loop<L>`) and the `<team_temp_dir>/pr-<N>/` subfolder containing that PR's git worktree, diff patches, and outcome XML files.
|
|
5
|
+
- **One run per invocation, multi-PR supported.** All PRs in a single /bugteam invocation share one `run_temp_dir`. Per-PR identity lives in the subagent name prefix (`bugfind-pr<N>-loop<L>` / `bugfix-pr<N>-loop<L>`) and the `<run_temp_dir>/pr-<N>/` subfolder containing that PR's git worktree, diff patches, and outcome XML files.
|
|
13
6
|
- **Grant before any spawn, revoke before any return.** Step 0 grants project `.claude/**` permissions; Step 5 revokes. Both are mandatory. Revoke runs on every exit path including error, cap-reached, and stuck.
|
|
14
|
-
- **Fresh
|
|
7
|
+
- **Fresh subagent per loop.** Both bugfind and bugfix are spawned new each loop. Reusing a subagent across loops accumulates context inside that subagent's window — defeats clean-room.
|
|
15
8
|
- **One up-front confirmation = whole cycle.** The `/bugteam` invocation authorizes the entire cycle; every subsequent decision runs on that single authorization.
|
|
16
9
|
- **10-loop hard cap.** Counted as **AUDIT** completions (increment in Step 3). Standards-fix passes before an audit do not advance `loop_count`. Worst case includes extra clean-coder spawns for the code-rules gate.
|
|
17
10
|
- **Code rules gate before every AUDIT.** Run `_shared/pr-loop/scripts/code_rules_gate.py` (resolved via `${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/code_rules_gate.py`) until exit **0** before spawning **bugfind**. Same `validate_content` logic as `hooks/blocking/code_rules_enforcer.py`.
|
|
18
|
-
- **Clean-room audits, every loop.** Each bugfind
|
|
19
|
-
- **Targeted fixes.** Each fix
|
|
20
|
-
- **Opus 4.7 at xhigh effort for both
|
|
21
|
-
- **Fix
|
|
11
|
+
- **Clean-room audits, every loop.** Each bugfind subagent's spawn prompt contains only the PR scope, audit rubric, and the current loop number. Prior loop history stays in the lead.
|
|
12
|
+
- **Targeted fixes.** Each fix subagent sees ONLY the most recent audit's findings. Prior loops are invisible to the fix subagent.
|
|
13
|
+
- **Opus 4.7 at xhigh effort for both subagents.** Both `Agent(...)` spawns pass `model="opus"`, which resolves to Opus 4.7 on the Anthropic API. Opus 4.7's default effort level in Claude Code is `xhigh` (https://code.claude.com/docs/en/model-config — *"On Opus 4.7, the default effort is `xhigh` for all plans and providers."*), so no `effort` override is needed at spawn time. Effort is set per-subagent in YAML frontmatter, not via the `Agent` tool's parameters; `code-quality-agent` and `clean-coder` rely on the model default. The trade vs Sonnet is higher per-loop cost in exchange for deeper audit recall and stronger fix correctness on bug-hunting work, which the per-PR loop economics tolerate (10-loop hard cap bounds total spend).
|
|
14
|
+
- **Fix subagent receives the latest audit as its input contract.** Passing the audit's findings to the fix subagent is the input contract — each loop's fix run operates on the current audit's output and only that.
|
|
22
15
|
- **One commit per fix action.** Loops produce one commit per loop, not one per bug.
|
|
23
16
|
- **Linear branch, fixed PR base.** Every loop appends one forward-only commit; existing commits and the PR base stay intact throughout the cycle.
|
|
24
|
-
- **Lead-only cleanup
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **Cleanup all `.bugteam-*` files on exit.** `.bugteam-loop-*.patch`, `.bugteam-loop-*.outcomes.xml`, `.bugteam-final.diff`, `.bugteam-original-body.md`, `.bugteam-final-body.md`. Working directory ends clean.
|
|
28
|
-
- **Audit/fix comment posting.** **Path A:** Bugfind posts ONE per-loop review (parent body + child finding comments in a single batched POST, with review-fallback to a top-level issue comment). Bugfix posts the fix replies after committing. All comment, review, and reply POSTs belong to the teammates; the lead's single PR-write action is the final description rewrite at Step 4.5. **Path B:** the **lead** performs the same POSTs after Task handoffs (`SKILL.md` Step 2.5 + [`reference/workflow-path-b-task-harness.md`](reference/workflow-path-b-task-harness.md) § Step 2.5).
|
|
17
|
+
- **Lead-only cleanup.** Cleanup runs in the lead (this session) only. Step 4 removes the full `<run_temp_dir>` so no loop patches leak between runs.
|
|
18
|
+
- **Cleanup all `.bugteam-*` files on exit.** The per-run `<run_temp_dir>` is removed entirely by Step 4, which covers `<run_temp_dir>/pr-<N>/loop-<L>.patch` and `<run_temp_dir>/pr-<N>/loop-<L>-{b,c}.outcomes.xml`. The per-loop outcomes XML at `<worktree_path>/.bugteam-pr<N>-loop<L>.outcomes.xml` is removed with the worktree. Step 4.5 deletes `.bugteam-final.diff`, `.bugteam-original-body.md`, and `.bugteam-final-body.md`. Working directory ends clean.
|
|
19
|
+
- **Audit/fix comment posting.** The bugfind subagent posts ONE per-loop review (parent body + child finding comments in a single batched POST, with review-fallback to a top-level issue comment). The bugfix subagent posts the fix replies after committing. All comment, review, and reply POSTs belong to the subagents; the lead's single PR-write action is the final description rewrite at Step 4.5.
|
|
29
20
|
- **Lead owns the final PR description rewrite only** (Step 4.5), and only via the `pr-description-writer` agent. The lead does not compose the description inline.
|
|
30
21
|
- **One review per loop, findings as child comments of that review.** Each loop posts a single pull-request review whose body is the loop header and whose `comments[]` are the anchored findings. Each loop's review stands alone — one review created per loop, fully self-contained on the PR conversation.
|
|
31
22
|
- **PR description rewrite on every exit.** Step 4.5 runs on `converged`, `cap reached`, and `stuck`. On `error`, the rewrite is best-effort; if it fails, surface the error in the final report and continue to revoke.
|
|
32
|
-
- **Outcome XML, not JSON.** Both
|
|
23
|
+
- **Outcome XML, not JSON.** Both subagents write structured outcome data (findings or fix outcomes) to `.bugteam-pr<N>-loop<L>.outcomes.xml`. The lead reads these files between actions. XML chosen for parser robustness against multi-line, special-character, and quoted reason fields.
|
|
33
24
|
|
|
34
25
|
## Why this design
|
|
35
26
|
|
|
@@ -18,7 +18,7 @@ Keep the spawn prompt self-contained: reference only the PR scope, audit rubric,
|
|
|
18
18
|
cd into `<worktree_path>` before any git, gh, or file operation.
|
|
19
19
|
|
|
20
20
|
<scope>
|
|
21
|
-
<diff_path>Absolute path to the per-PR patch file: <
|
|
21
|
+
<diff_path>Absolute path to the per-PR patch file: <run_temp_dir>/pr-<N>/loop-<L>.patch (same path as gh pr diff redirect in AUDIT)</diff_path>
|
|
22
22
|
<scope_rule>Audit only lines added or modified in the diff. Pre-existing code on untouched lines is out of scope.</scope_rule>
|
|
23
23
|
</scope>
|
|
24
24
|
|
|
@@ -76,8 +76,8 @@ cd into `<worktree_path>` before any git, gh, or file operation.
|
|
|
76
76
|
</comment_posting>
|
|
77
77
|
|
|
78
78
|
<output_format>
|
|
79
|
-
|
|
80
|
-
the PR's worktree directory (<worktree_path>). Return only that path on stdout. The schema:
|
|
79
|
+
For the primary (-a) auditor: write the outcome XML below to .bugteam-pr<N>-loop<L>.outcomes.xml inside
|
|
80
|
+
the PR's worktree directory (<worktree_path>). For sibling auditors (-b/-c): write to <run_temp_dir>/pr-<N>/loop-<L>-{b,c}.outcomes.xml (absolute path passed in prompt). Return only that path on stdout. The schema:
|
|
81
81
|
</output_format>
|
|
82
82
|
```
|
|
83
83
|
|