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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Tests for reviewer_specs.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- each ReviewerSpec instance carries the documented login_filter_substring
|
|
5
|
+
- bugbot_spec.classify_review uses the dirty-body regex
|
|
6
|
+
- copilot_spec.classify_review dispatches off review state plus body
|
|
7
|
+
- claude_spec.classify_review dispatches off review state plus body
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import importlib.util
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from types import ModuleType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_module() -> ModuleType:
|
|
18
|
+
module_path = Path(__file__).parent / "reviewer_specs.py"
|
|
19
|
+
spec = importlib.util.spec_from_file_location("reviewer_specs", module_path)
|
|
20
|
+
assert spec is not None
|
|
21
|
+
assert spec.loader is not None
|
|
22
|
+
module = importlib.util.module_from_spec(spec)
|
|
23
|
+
spec.loader.exec_module(module)
|
|
24
|
+
return module
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
reviewer_specs_module = _load_module()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_bugbot_spec_uses_cursor_login_filter_substring() -> None:
|
|
31
|
+
assert reviewer_specs_module.bugbot_spec.login_filter_substring == "cursor"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_copilot_spec_uses_copilot_login_filter_substring() -> None:
|
|
35
|
+
assert reviewer_specs_module.copilot_spec.login_filter_substring == "copilot"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_claude_spec_uses_claude_login_filter_substring() -> None:
|
|
39
|
+
assert reviewer_specs_module.claude_spec.login_filter_substring == "claude"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_bugbot_classify_returns_dirty_when_body_matches_findings_pattern() -> None:
|
|
43
|
+
review_payload = {
|
|
44
|
+
"body": "Cursor Bugbot has reviewed your changes and found 2 potential issues.",
|
|
45
|
+
}
|
|
46
|
+
assert reviewer_specs_module.bugbot_spec.classify_review(review_payload) == "dirty"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_bugbot_classify_returns_clean_when_body_lacks_findings_pattern() -> None:
|
|
50
|
+
review_payload = {
|
|
51
|
+
"body": "Bugbot reviewed your changes and found no new issues!",
|
|
52
|
+
}
|
|
53
|
+
assert reviewer_specs_module.bugbot_spec.classify_review(review_payload) == "clean"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_copilot_classify_returns_clean_when_state_is_approved() -> None:
|
|
57
|
+
review_payload = {"state": "APPROVED", "body": "lgtm"}
|
|
58
|
+
assert reviewer_specs_module.copilot_spec.classify_review(review_payload) == "clean"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_copilot_classify_returns_dirty_when_state_is_changes_requested() -> None:
|
|
62
|
+
review_payload = {"state": "CHANGES_REQUESTED", "body": "fix this"}
|
|
63
|
+
assert reviewer_specs_module.copilot_spec.classify_review(review_payload) == "dirty"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_copilot_classify_returns_dirty_when_state_is_commented_with_body() -> None:
|
|
67
|
+
review_payload = {"state": "COMMENTED", "body": "minor nit"}
|
|
68
|
+
assert reviewer_specs_module.copilot_spec.classify_review(review_payload) == "dirty"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_copilot_classify_returns_clean_when_state_is_commented_with_empty_body() -> (
|
|
72
|
+
None
|
|
73
|
+
):
|
|
74
|
+
review_payload = {"state": "COMMENTED", "body": ""}
|
|
75
|
+
assert reviewer_specs_module.copilot_spec.classify_review(review_payload) == "clean"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_copilot_classify_returns_clean_when_state_is_unknown() -> None:
|
|
79
|
+
review_payload = {"state": "DISMISSED", "body": "ignored"}
|
|
80
|
+
assert reviewer_specs_module.copilot_spec.classify_review(review_payload) == "clean"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_claude_classify_returns_clean_when_state_is_approved() -> None:
|
|
84
|
+
review_payload = {"state": "APPROVED", "body": "lgtm"}
|
|
85
|
+
assert reviewer_specs_module.claude_spec.classify_review(review_payload) == "clean"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_claude_classify_returns_dirty_when_state_is_changes_requested() -> None:
|
|
89
|
+
review_payload = {"state": "CHANGES_REQUESTED", "body": "fix this"}
|
|
90
|
+
assert reviewer_specs_module.claude_spec.classify_review(review_payload) == "dirty"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_claude_classify_returns_dirty_when_state_is_commented_with_body() -> None:
|
|
94
|
+
review_payload = {"state": "COMMENTED", "body": "minor nit"}
|
|
95
|
+
assert reviewer_specs_module.claude_spec.classify_review(review_payload) == "dirty"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_claude_classify_returns_clean_when_state_is_commented_with_empty_body() -> (
|
|
99
|
+
None
|
|
100
|
+
):
|
|
101
|
+
review_payload = {"state": "COMMENTED", "body": ""}
|
|
102
|
+
assert reviewer_specs_module.claude_spec.classify_review(review_payload) == "clean"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_claude_classify_returns_clean_when_state_is_unknown() -> None:
|
|
106
|
+
review_payload = {"state": "DISMISSED", "body": "ignored"}
|
|
107
|
+
assert reviewer_specs_module.claude_spec.classify_review(review_payload) == "clean"
|
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
# ScheduleWakeup loop pacing (pr-converge)
|
|
2
2
|
|
|
3
|
-
Load this document
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Load this document for converge **loop pacing**. The pre-flight in `SKILL.md`
|
|
4
|
+
guarantees `ScheduleWakeup` is available before any tick runs. Shared bugbot
|
|
5
|
+
/ bugteam / Fix protocol steps stay in the main `SKILL.md`.
|
|
6
6
|
|
|
7
7
|
## Session behavior
|
|
8
8
|
|
|
9
|
-
Call `ScheduleWakeup` from this same session so the next tick fires back into **this** transcript with the prior tick's state line and PR
|
|
10
|
-
context still addressable.
|
|
9
|
+
Call `ScheduleWakeup` from this same session so the next tick fires back into **this** transcript with the prior tick's state line and PR context still addressable.
|
|
11
10
|
|
|
12
|
-
##
|
|
11
|
+
## Calling ScheduleWakeup
|
|
13
12
|
|
|
14
|
-
At end of tick (unless convergence or another stop condition already
|
|
13
|
+
At end of tick (unless convergence or another stop condition already
|
|
14
|
+
omitted pacing), call `ScheduleWakeup` with:
|
|
15
15
|
|
|
16
|
-
- `delaySeconds: 270` whenever bugbot was just re-triggered (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
- `delaySeconds: 270` whenever bugbot was just re-triggered (by the
|
|
17
|
+
bugbot re-trigger in `../reference/per-tick.md`, by Fix protocol's
|
|
18
|
+
mandatory re-trigger, or by BUGTEAM's same-tick re-trigger). Bugbot
|
|
19
|
+
finishes a review in 1–4 minutes, so 270s stays under the 5-minute
|
|
20
|
+
prompt-cache TTL with margin past bugbot's typical upper bound. The
|
|
21
|
+
exception is the BUGBOT inline-lag branch (see below).
|
|
22
|
+
- `reason`: one short sentence on what is being awaited, including the
|
|
23
|
+
current `phase` and `bugbot_clean_at` SHA when set.
|
|
24
|
+
- `prompt: "/pr-converge"` — re-enters this skill on the next firing.
|
|
23
25
|
|
|
24
|
-
## BUGBOT inline-lag
|
|
26
|
+
## BUGBOT inline-lag
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
See [`../reference/per-tick.md`](../reference/per-tick.md) — the BUGBOT
|
|
29
|
+
inline-lag branch (review body says findings, inline API returns zero
|
|
30
|
+
matching for `current_head`) uses `delaySeconds: 90` because no
|
|
31
|
+
re-trigger fired and only GitHub's inline-comments API needs to catch up.
|
|
28
32
|
|
|
29
33
|
## Convergence
|
|
30
34
|
|
|
31
|
-
On
|
|
32
|
-
pacer.
|
|
35
|
+
On convergence: **omit** further `ScheduleWakeup` calls.
|
|
33
36
|
|
|
34
|
-
## Stop / safety
|
|
37
|
+
## Stop / safety
|
|
35
38
|
|
|
36
|
-
On hard blockers or user stop: omit `ScheduleWakeup` per main skill **Stop conditions**.
|
|
37
|
-
skip AHK shutdown commands in the companion AHK workflow.
|
|
39
|
+
On hard blockers or user stop: omit `ScheduleWakeup` per main skill **Stop conditions**.
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# Bugteam — Path A workflow (orchestrated teams)
|
|
2
|
-
|
|
3
|
-
Load when bugteam `SKILL.md` **Path routing** selects **Path A** (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` equals **`1`** after trim). Execute **after** shared `SKILL.md` steps through **Step 2 loop state**; then use this file for **harness-only** steps. Shared gate scripts, Step 3 cycle numbering, Step 2.5 `jq`/`gh` payload shapes, `PROMPTS.md`, and outcome XML rules remain in **`SKILL.md`**.
|
|
4
|
-
|
|
5
|
-
## Step 2 harness — lifecycle, `TeamCreate`, and team metadata
|
|
6
|
-
|
|
7
|
-
**This session is the lead.** Step 2 first resolves the team lifecycle from the [Team lifecycle](../SKILL.md#team-lifecycle-path-a-only) table in `SKILL.md`, then either creates or attaches to a team.
|
|
8
|
-
|
|
9
|
-
**Lifecycle resolution (read once, then apply):**
|
|
10
|
-
|
|
11
|
-
```python
|
|
12
|
-
import os
|
|
13
|
-
import re
|
|
14
|
-
|
|
15
|
-
mode = os.environ.get("BUGTEAM_TEAM_LIFECYCLE", "auto").strip().lower() or "auto"
|
|
16
|
-
attached_team_name = os.environ.get("BUGTEAM_TEAM_NAME", "").strip()
|
|
17
|
-
|
|
18
|
-
if mode == "attach" and not attached_team_name:
|
|
19
|
-
raise RuntimeError(
|
|
20
|
-
"BUGTEAM_TEAM_LIFECYCLE=attach requires BUGTEAM_TEAM_NAME to name an existing team."
|
|
21
|
-
)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The mode then drives Step 2's `TeamCreate` decision:
|
|
25
|
-
|
|
26
|
-
- **`mode == "attach"`** — skip `TeamCreate` entirely. Set `team_name = attached_team_name`, `team_owned = false`. Continue to teammate spawns using that `team_name`.
|
|
27
|
-
|
|
28
|
-
- **`mode == "owned"`** — call `TeamCreate(team_name=<computed_team_name>, ...)` (form below). On the runtime error matching `Already leading team "(.+)"\.` → **fail** with `Already leading team <existing>; rerun with BUGTEAM_TEAM_LIFECYCLE=attach BUGTEAM_TEAM_NAME=<existing>`. On success, set `team_owned = true`.
|
|
29
|
-
|
|
30
|
-
- **`mode == "auto"`** (default) — call `TeamCreate(team_name=<computed_team_name>, ...)`. On the same runtime error, parse the existing team name with the regex `r'Already leading team "([^"]+)"'`, set `team_name = <existing>`, `team_owned = false`, and continue without retrying `TeamCreate`. On success, set `team_owned = true`. Both branches honor §Team name below.
|
|
31
|
-
|
|
32
|
-
**`TeamCreate` call shape (only when `mode != "attach"` and the auto branch did not already attach):**
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
TeamCreate(
|
|
36
|
-
team_name="<computed_team_name>",
|
|
37
|
-
description="Bugteam audit/fix loop for PR <number> (<owner>/<repo>)",
|
|
38
|
-
agent_type="team-lead"
|
|
39
|
-
)
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Team name:** For a single-PR invocation use `bugteam-pr-<number>-<YYYYMMDDHHMMSS>`. For a multi-PR invocation use `bugteam-<YYYYMMDDHHMMSS>`. The timestamp is captured once at team-creation time. Apply the no-PR fallback (`bugteam-<sanitized-head>-<YYYYMMDDHHMMSS>`) only when no PR resolves at all. `TeamCreate` implements natural-language team creation ([`sources.md`](../sources.md) § Team creation in natural language).
|
|
43
|
-
|
|
44
|
-
**Sanitize head branch (no-PR only):** replace characters outside `[A-Za-z0-9._-]` with `-` (e.g. `feat/foo*bar` → `feat-foo-bar`). Apply once; reuse everywhere below.
|
|
45
|
-
|
|
46
|
-
**`<team_temp_dir>`:** `Path(tempfile.gettempdir()) / team_name` (lead resolves once to an absolute path; every shell gets that literal string).
|
|
47
|
-
|
|
48
|
-
**Roles (spawned per loop, not here):** bugfind → `code-quality-agent` opus (4.7) at xhigh effort; bugfix → `clean-coder` opus (4.7) at xhigh effort. `model="opus"` resolves to Opus 4.7 on the Anthropic API and runs at the model's default `xhigh` effort level — see [`CONSTRAINTS.md`](../CONSTRAINTS.md) § **Opus 4.7 at xhigh effort for both teammates** for rationale. **Display:** inherit `teammateMode` from `~/.claude.json`. Reference subagent types by name when spawning teammates ([`sources.md`](../sources.md) § Referencing subagent types when spawning teammates).
|
|
49
|
-
|
|
50
|
-
**Optional Groq-backed FIX path (explicit opt-in only):** when the user explicitly sets `BUGTEAM_FIX_IMPLEMENTER=groq-coder` before invocation, spawn the FIX teammate with `subagent_type="groq-coder"`. Before Step 3, `groq_bugteam.py` loads `packages/claude-dev-env/.env` when that file exists (gitignored; start from `packages/claude-dev-env/.env.example`). If `GROQ_API_KEY` is still unset after that load, stop and prompt the user to create `packages/claude-dev-env/.env` from the example path above—do not continue the Groq path without a key. Any other `BUGTEAM_FIX_IMPLEMENTER` value (or unset) keeps `clean-coder` on Opus. The FIX spawn XML in [`PROMPTS.md`](../PROMPTS.md) is identical for both implementers.
|
|
51
|
-
|
|
52
|
-
**`--bugbot-retrigger` flag:** when present on the `/bugteam` invocation, after every successful FIX push in Step 3, post an additional `bugbot run` issue comment via the Step 2.5 issue-comments fallback endpoint (`POST .../issues/{issue}/comments`) to re-trigger Cursor's bugbot on the new commit. Omit when the flag is absent.
|
|
53
|
-
|
|
54
|
-
## Step 2.5 — who posts (Path A)
|
|
55
|
-
|
|
56
|
-
**Bugfind** posts one `POST .../pulls/<n>/reviews` per loop after audit. **Bugfix** posts `.../comments/<id>/replies` after push. The **lead’s** only PR write before Step 4.5 is unchanged from `SKILL.md` (Step 4.5 body edit).
|
|
57
|
-
|
|
58
|
-
## AUDIT spawn (Path A)
|
|
59
|
-
|
|
60
|
-
After shared setup in `SKILL.md` (`mkdir`, `gh pr diff`):
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
Agent(
|
|
64
|
-
subagent_type="code-quality-agent",
|
|
65
|
-
name="bugfind-pr<N>-loop<L>",
|
|
66
|
-
team_name="<team_name>",
|
|
67
|
-
model="opus",
|
|
68
|
-
description="Bugfind audit PR <N> loop <L>",
|
|
69
|
-
prompt="<audit XML; see PROMPTS.md>"
|
|
70
|
-
)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Fresh `Agent` each loop; teammate context excludes lead history ([`sources.md`](../sources.md) § Teammate context isolation). [`PROMPTS.md`](../PROMPTS.md): XML + outcome schema. Lead reads `.bugteam-pr<N>-loop<L>.outcomes.xml`, fills `loop_comment_index` per `SKILL.md`.
|
|
74
|
-
|
|
75
|
-
**Shutdown:** If `Agent` returned and the teammate already ended, skip. Otherwise:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
SendMessage(
|
|
79
|
-
to="bugfind-pr<N>-loop<L>",
|
|
80
|
-
message={"type": "shutdown_request", "reason": "audit PR <N> loop <L> complete; outcome XML captured"}
|
|
81
|
-
)
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
`approve: false` → `error: bugfind teammate refused shutdown` → Step 4 then 5 per `SKILL.md`.
|
|
85
|
-
|
|
86
|
-
**Parallel auditors (`loop_count >= 4`):** after three full audit/fix rounds without convergence, issue three `Agent` calls in one assistant message with `team_name="<team_name>"` per `SKILL.md` § parallel variant naming (`-a` / `-b` / `-c`). Shutdown: parallel `SendMessage` to `b` and `c`, then `a`.
|
|
87
|
-
|
|
88
|
-
## FIX spawn (Path A)
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
Agent(
|
|
92
|
-
subagent_type="clean-coder",
|
|
93
|
-
name="bugfix-pr<N>-loop<L>",
|
|
94
|
-
team_name="<team_name>",
|
|
95
|
-
model="opus",
|
|
96
|
-
description="Bugfix PR <N> loop <L>",
|
|
97
|
-
prompt="<fix XML; see PROMPTS.md>"
|
|
98
|
-
)
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
**Shutdown:** same pattern as AUDIT; `SendMessage(to="bugfix-pr<N>-loop<L>", message={"type": "shutdown_request", "reason": "fix PR <N> loop <L> complete; commit <sha7> pushed"})`. `approve: false` → `error: bugfix teammate refused shutdown` → Step 4 then 5.
|
|
102
|
-
|
|
103
|
-
Verify and outcome handling: unchanged from `SKILL.md` § FIX action (**Verify**, stuck message, [`PROMPTS.md`](../PROMPTS.md)).
|
|
104
|
-
|
|
105
|
-
## Step 4 harness — after worktree remove, before shared `rmtree`
|
|
106
|
-
|
|
107
|
-
Run **after** `SKILL.md` § Step 4 step 1 (`git worktree remove` for each PR).
|
|
108
|
-
|
|
109
|
-
1. For each live teammate **spawned by this invocation**: `SendMessage(to="<name>", message={"type": "shutdown_request", "reason": "bugteam cycle ending"})`. `approve: false` on cleanup → log and continue. Teammates that pre-existed an attached team (mode `attach` or `auto` after the attach branch) are owned by the orchestrator, not this invocation — leave them alone.
|
|
110
|
-
|
|
111
|
-
2. `TeamDelete()` — **only when `team_owned=true`** (set in Step 2 lifecycle resolution). When `team_owned=false`, **skip `TeamDelete`** and log `cleanup: skipped TeamDelete (team not owned by this invocation; lifecycle=<mode>)`. The orchestrator that originally created the team owns teardown — see [Team lifecycle](../SKILL.md#team-lifecycle-path-a-only).
|
|
112
|
-
|
|
113
|
-
Then continue with `SKILL.md` § Step 4 step 3 (`<team_temp_dir>` cleanup, also gated on `team_owned`).
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Bugteam — Path B workflow (Task harness)
|
|
2
|
-
|
|
3
|
-
Load when bugteam `SKILL.md` **Path routing** selects **Path B** (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` is not exactly **`1`** after trim — typical Cursor IDE). Execute **after** shared `SKILL.md` steps through **Step 2 loop state**. **Do not** run `TeamCreate` or `TeamDelete`. Harness substitutions vs Path A:
|
|
4
|
-
|
|
5
|
-
| Path A | Path B |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| `TeamCreate(...)` | **Omit.** |
|
|
8
|
-
| `Agent(..., team_name=..., ...)` | **`Task`** (or host-equivalent) with the same `model` and prompt contracts as Path A — **omit** `team_name`. `subagent_type` follows this file's **AUDIT** / **FIX spawn** sections (Claude Code: `code-quality-agent` / `clean-coder`; Cursor: `code-quality-agent` / `generalPurpose` + mandatory `clean-coder.md` **Read** on FIX when the enum rejects `clean-coder`). |
|
|
9
|
-
| `SendMessage` shutdown | **Omit.** Await Task completion. |
|
|
10
|
-
| `TeamDelete()` | **Omit.** |
|
|
11
|
-
| Three parallel `Agent` (`loop_count >= 4`) | Three parallel **`Task`** with `subagent_type="code-quality-agent"`; merge outcomes in the lead like Path A `-a`/`-b`/`-c`. |
|
|
12
|
-
|
|
13
|
-
## Step 2 harness — Path B
|
|
14
|
-
|
|
15
|
-
No `TeamCreate`. After shared `SKILL.md` **Step 1** completes (PR scope **and** `<team_temp_dir>/pr-<N>/` per Step 1 items 1–3 there), use the same `team_name` string only as a **logical label** for paths under that `<team_temp_dir>`; do not pass `team_name` into spawns.
|
|
16
|
-
|
|
17
|
-
**`--bugbot-retrigger` flag:** same as Path A [`workflow-path-a-orchestrated-teams.md`](workflow-path-a-orchestrated-teams.md) § Step 2 harness — the **lead** posts the issue comment after each successful FIX push when the flag is present.
|
|
18
|
-
|
|
19
|
-
## Step 2.5 — who posts (Path B)
|
|
20
|
-
|
|
21
|
-
The **lead** runs the **same** `gh api` / `jq` sequences as `SKILL.md` **Step 2.5** after reading each **`Task`** handoff / outcome XML — same JSON shapes and anchor rules; only **who executes the shell** changes.
|
|
22
|
-
|
|
23
|
-
## AUDIT spawn (Path B)
|
|
24
|
-
|
|
25
|
-
After shared setup in `SKILL.md` (`mkdir`, `gh pr diff`):
|
|
26
|
-
|
|
27
|
-
`Task` with `subagent_type="code-quality-agent"`, **omit** `team_name`, same `model`, `description`, and `prompt="<audit XML; see PROMPTS.md>"` as Path A [`workflow-path-a-orchestrated-teams.md`](workflow-path-a-orchestrated-teams.md) § AUDIT spawn.
|
|
28
|
-
|
|
29
|
-
Fresh **Task** each loop (clean-room intent: do not reuse prior Task transcript as audit input). Lead reads `.bugteam-pr<N>-loop<L>.outcomes.xml`, fills `loop_comment_index` per `SKILL.md`.
|
|
30
|
-
|
|
31
|
-
**Parallel auditors (`loop_count >= 4`):** three **`Task`** calls with `subagent_type="code-quality-agent"` in one assistant message; merge outcomes in the lead exactly as Path A documents for variants `-a`/`-b`/`-c`. Await all three Tasks — no `SendMessage`.
|
|
32
|
-
|
|
33
|
-
## FIX spawn (Path B)
|
|
34
|
-
|
|
35
|
-
**Hosts that accept `clean-coder` as a `Task` subtype (typical Claude Code):** `Task` with `subagent_type="clean-coder"` (or `subagent_type="groq-coder"` when `BUGTEAM_FIX_IMPLEMENTER=groq-coder` per Path A optional Groq rules), **omit** `team_name`, same fields otherwise as Path A [`workflow-path-a-orchestrated-teams.md`](workflow-path-a-orchestrated-teams.md) § FIX spawn. Await Task completion — no `SendMessage`.
|
|
36
|
-
|
|
37
|
-
**Cursor and other hosts with a fixed `Task` enum (no `clean-coder` value):** use `Task` with `subagent_type: "generalPurpose"` and put the **same** FIX obligations from Path A into the **`prompt`**, after a mandatory first step to **Read** the clean-coder agent markdown: macOS/Linux `$HOME/.claude/agents/clean-coder.md`, Windows `%USERPROFILE%\.claude\agents\clean-coder.md`. State that file is binding for naming, TDD when behavior changes, hooks, one commit, and scope. Do **not** use a bare `generalPurpose` prompt without that Read. Same bundle as [`../../pr-converge/SKILL.md`](../../pr-converge/SKILL.md) Fix protocol **Implement**. If `Task` cannot run, stop and notify the user.
|
|
38
|
-
|
|
39
|
-
Verify and outcome handling: unchanged from `SKILL.md` § FIX action.
|
|
40
|
-
## Step 4 harness — after worktree remove, before shared `rmtree`
|
|
41
|
-
|
|
42
|
-
Run **after** `SKILL.md` § Step 4 step 1 (`git worktree remove` for each PR).
|
|
43
|
-
|
|
44
|
-
**Omit** teammate `SendMessage` rounds and **`TeamDelete()`**. Then continue with `SKILL.md` § Step 4 step 3 (shared `rmtree` on `<team_temp_dir>`).
|
|
45
|
-
|
|
46
|
-
## Clean-room note
|
|
47
|
-
|
|
48
|
-
Path B approximates Path A isolation by spawning a **new** Task per AUDIT (and per FIX) with the same prompt contract as Path A, without reusing prior Task context as audit input.
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"""Markdown assertion tests for bugteam team-lifecycle decoupling.
|
|
2
|
-
|
|
3
|
-
Locks in the contract that the bugteam skill must:
|
|
4
|
-
- support three team lifecycle modes (`owned`, `attach`, `auto`)
|
|
5
|
-
- default to `auto` (back-compat for solo invocations, safe for nested ones)
|
|
6
|
-
- skip `TeamDelete` when the invocation did not create the team
|
|
7
|
-
- parse the runtime's `Already leading team "<name>"` error and attach
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import pathlib
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _read(relative_path: str) -> str:
|
|
16
|
-
here = pathlib.Path(__file__).parent
|
|
17
|
-
return (here / relative_path).read_text(encoding="utf-8")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _skill_text() -> str:
|
|
21
|
-
return _read("SKILL.md")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _path_a_text() -> str:
|
|
25
|
-
return _read("reference/workflow-path-a-orchestrated-teams.md")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _constraints_text() -> str:
|
|
29
|
-
return _read("CONSTRAINTS.md")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def test_skill_documents_three_team_lifecycle_modes():
|
|
33
|
-
skill_text = _skill_text()
|
|
34
|
-
assert "BUGTEAM_TEAM_LIFECYCLE" in skill_text
|
|
35
|
-
assert "owned" in skill_text
|
|
36
|
-
assert "attach" in skill_text
|
|
37
|
-
assert "auto" in skill_text
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def test_skill_documents_auto_as_default_lifecycle():
|
|
41
|
-
skill_text = _skill_text()
|
|
42
|
-
assert "default" in skill_text.lower()
|
|
43
|
-
assert "BUGTEAM_TEAM_LIFECYCLE" in skill_text
|
|
44
|
-
auto_default_phrases = [
|
|
45
|
-
"default: `auto`",
|
|
46
|
-
"default `auto`",
|
|
47
|
-
"defaults to `auto`",
|
|
48
|
-
"defaults to auto",
|
|
49
|
-
"default to `auto`",
|
|
50
|
-
]
|
|
51
|
-
assert any(phrase in skill_text for phrase in auto_default_phrases)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def test_skill_documents_BUGTEAM_TEAM_NAME_env_for_attach_mode():
|
|
55
|
-
skill_text = _skill_text()
|
|
56
|
-
assert "BUGTEAM_TEAM_NAME" in skill_text
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_path_a_workflow_handles_already_leading_team_error():
|
|
60
|
-
workflow_text = _path_a_text()
|
|
61
|
-
assert 'Already leading team "' in workflow_text
|
|
62
|
-
assert "team_owned" in workflow_text
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def test_path_a_workflow_step_4_skips_team_delete_when_not_owned():
|
|
66
|
-
workflow_text = _path_a_text()
|
|
67
|
-
assert "TeamDelete" in workflow_text
|
|
68
|
-
assert "team_owned" in workflow_text
|
|
69
|
-
skip_phrases = [
|
|
70
|
-
"skip `TeamDelete`",
|
|
71
|
-
"skip TeamDelete",
|
|
72
|
-
"omit `TeamDelete`",
|
|
73
|
-
"omit TeamDelete",
|
|
74
|
-
"do not call `TeamDelete`",
|
|
75
|
-
"do not call TeamDelete",
|
|
76
|
-
]
|
|
77
|
-
assert any(phrase in workflow_text for phrase in skip_phrases)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_path_a_workflow_documents_attach_mode_reuses_team_name():
|
|
81
|
-
workflow_text = _path_a_text()
|
|
82
|
-
assert "BUGTEAM_TEAM_NAME" in workflow_text
|
|
83
|
-
assert "attach" in workflow_text
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def test_constraints_lead_only_cleanup_includes_team_owned():
|
|
87
|
-
constraints_text = _constraints_text()
|
|
88
|
-
assert "team_owned" in constraints_text
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def test_constraints_warn_against_owned_mode_inside_orchestrator():
|
|
92
|
-
constraints_text = _constraints_text()
|
|
93
|
-
assert "orchestrator" in constraints_text.lower()
|
|
94
|
-
assert "attach" in constraints_text
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def test_skill_md_physical_lines_fit_eighty_column_limit():
|
|
98
|
-
skill_text = _skill_text()
|
|
99
|
-
for each_line_number, each_physical_line in enumerate(skill_text.splitlines(), 1):
|
|
100
|
-
assert len(each_physical_line) <= 80, (
|
|
101
|
-
"SKILL.md line %s exceeds 80 columns (%s chars)"
|
|
102
|
-
% (each_line_number, len(each_physical_line))
|
|
103
|
-
)
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""Markdown assertion tests for monitor-open-prs team lifecycle.
|
|
2
|
-
|
|
3
|
-
Locks in the contract that the sweep must:
|
|
4
|
-
- own a single long-lived team for every dispatched /bugteam
|
|
5
|
-
- pass attach mode + the team name into each per-PR /bugteam dispatch
|
|
6
|
-
- tear down only after all PR polling completes
|
|
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_creates_one_team_for_the_whole_sweep():
|
|
20
|
-
skill_text = _skill_text()
|
|
21
|
-
assert "TeamCreate" in skill_text
|
|
22
|
-
sweep_phrases = [
|
|
23
|
-
"one team for the whole sweep",
|
|
24
|
-
"single team for the sweep",
|
|
25
|
-
"single long-lived team",
|
|
26
|
-
]
|
|
27
|
-
assert any(phrase in skill_text for phrase in sweep_phrases)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def test_skill_passes_attach_lifecycle_to_each_bugteam_dispatch():
|
|
31
|
-
skill_text = _skill_text()
|
|
32
|
-
assert "BUGTEAM_TEAM_LIFECYCLE" in skill_text
|
|
33
|
-
assert "attach" in skill_text
|
|
34
|
-
assert "BUGTEAM_TEAM_NAME" in skill_text
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_skill_tears_down_team_after_polling_completes():
|
|
38
|
-
skill_text = _skill_text()
|
|
39
|
-
assert "TeamDelete" in skill_text
|
|
40
|
-
teardown_phrases = [
|
|
41
|
-
"after every PR has exited polling",
|
|
42
|
-
"after polling completes",
|
|
43
|
-
"after the sweep",
|
|
44
|
-
"after all polling",
|
|
45
|
-
]
|
|
46
|
-
assert any(phrase in skill_text for phrase in teardown_phrases)
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
"""Open a follow-up draft PR addressing Copilot findings from the parent PR.
|
|
2
|
-
|
|
3
|
-
Subprocess sequence:
|
|
4
|
-
|
|
5
|
-
1. ``gh pr view <parent_number> --json baseRefName`` to resolve the parent's base ref.
|
|
6
|
-
2. ``git fetch origin <head_sha>`` to make the SHA available locally.
|
|
7
|
-
3. ``git switch -c <new_branch> <head_sha>`` to create the follow-up branch off ``head_sha``.
|
|
8
|
-
4. ``git push -u origin <new_branch>`` to publish it.
|
|
9
|
-
5. ``gh pr create --draft --base <base_ref> --head <new_branch> --title <...> --body-file <findings_file>``
|
|
10
|
-
per the gh-body-file rule.
|
|
11
|
-
|
|
12
|
-
Returns the trimmed PR URL emitted by ``gh pr create`` on stdout.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
import argparse
|
|
16
|
-
import json
|
|
17
|
-
import subprocess
|
|
18
|
-
import sys
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
|
|
21
|
-
if str(Path(__file__).resolve().parent) not in sys.path:
|
|
22
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
23
|
-
|
|
24
|
-
from evict_cached_config_modules import evict_cached_config_modules
|
|
25
|
-
|
|
26
|
-
evict_cached_config_modules()
|
|
27
|
-
|
|
28
|
-
from config.pr_converge_constants import (
|
|
29
|
-
COPILOT_FOLLOWUP_BRANCH_TEMPLATE,
|
|
30
|
-
COPILOT_FOLLOWUP_PR_TITLE_TEMPLATE,
|
|
31
|
-
COPILOT_FOLLOWUP_SHORT_SHA_LENGTH,
|
|
32
|
-
GH_REPO_ARG_TEMPLATE,
|
|
33
|
-
PR_BASE_REF_FIELDS,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def open_followup_copilot_pr(
|
|
38
|
-
*,
|
|
39
|
-
owner: str,
|
|
40
|
-
repo: str,
|
|
41
|
-
parent_number: int,
|
|
42
|
-
head: str,
|
|
43
|
-
findings_file: Path,
|
|
44
|
-
) -> str:
|
|
45
|
-
"""Create the follow-up branch + draft PR; return the new PR URL."""
|
|
46
|
-
repo_arg = GH_REPO_ARG_TEMPLATE.format(owner=owner, repo=repo)
|
|
47
|
-
parent_base_ref = _resolve_parent_base_ref(
|
|
48
|
-
parent_number=parent_number, repo_arg=repo_arg
|
|
49
|
-
)
|
|
50
|
-
short_sha = head[:COPILOT_FOLLOWUP_SHORT_SHA_LENGTH]
|
|
51
|
-
new_branch_name = COPILOT_FOLLOWUP_BRANCH_TEMPLATE.format(
|
|
52
|
-
parent_number=parent_number, short_sha=short_sha
|
|
53
|
-
)
|
|
54
|
-
_run_checked(["git", "fetch", "origin", head])
|
|
55
|
-
_run_checked(["git", "switch", "-c", new_branch_name, head])
|
|
56
|
-
_run_checked(["git", "push", "-u", "origin", new_branch_name])
|
|
57
|
-
pr_title = COPILOT_FOLLOWUP_PR_TITLE_TEMPLATE.format(parent_number=parent_number)
|
|
58
|
-
completed = _run_checked(
|
|
59
|
-
[
|
|
60
|
-
"gh",
|
|
61
|
-
"pr",
|
|
62
|
-
"create",
|
|
63
|
-
"--repo",
|
|
64
|
-
repo_arg,
|
|
65
|
-
"--draft",
|
|
66
|
-
"--base",
|
|
67
|
-
parent_base_ref,
|
|
68
|
-
"--head",
|
|
69
|
-
new_branch_name,
|
|
70
|
-
"--title",
|
|
71
|
-
pr_title,
|
|
72
|
-
"--body-file",
|
|
73
|
-
str(findings_file),
|
|
74
|
-
]
|
|
75
|
-
)
|
|
76
|
-
return completed.stdout.strip()
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _resolve_parent_base_ref(*, parent_number: int, repo_arg: str) -> str:
|
|
80
|
-
completed = _run_checked(
|
|
81
|
-
[
|
|
82
|
-
"gh",
|
|
83
|
-
"pr",
|
|
84
|
-
"view",
|
|
85
|
-
str(parent_number),
|
|
86
|
-
"--repo",
|
|
87
|
-
repo_arg,
|
|
88
|
-
"--json",
|
|
89
|
-
PR_BASE_REF_FIELDS,
|
|
90
|
-
]
|
|
91
|
-
)
|
|
92
|
-
parent_pr_metadata = json.loads(completed.stdout)
|
|
93
|
-
base_ref_name_field = parent_pr_metadata.get("baseRefName")
|
|
94
|
-
if not isinstance(base_ref_name_field, str):
|
|
95
|
-
raise TypeError(
|
|
96
|
-
f"gh pr view baseRefName field is not str: {type(base_ref_name_field).__name__}"
|
|
97
|
-
)
|
|
98
|
-
return base_ref_name_field
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _run_checked(all_command_arguments: list[str]) -> subprocess.CompletedProcess:
|
|
102
|
-
return subprocess.run(
|
|
103
|
-
all_command_arguments,
|
|
104
|
-
capture_output=True,
|
|
105
|
-
check=True,
|
|
106
|
-
text=True,
|
|
107
|
-
encoding="utf-8",
|
|
108
|
-
errors="replace",
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def main() -> int:
|
|
113
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
114
|
-
parser.add_argument("--owner", required=True)
|
|
115
|
-
parser.add_argument("--repo", required=True)
|
|
116
|
-
parser.add_argument(
|
|
117
|
-
"--parent-number", required=True, type=int, dest="parent_number"
|
|
118
|
-
)
|
|
119
|
-
parser.add_argument("--head", required=True)
|
|
120
|
-
parser.add_argument(
|
|
121
|
-
"--findings-file", required=True, type=Path, dest="findings_file"
|
|
122
|
-
)
|
|
123
|
-
parsed_arguments = parser.parse_args()
|
|
124
|
-
new_pr_url = open_followup_copilot_pr(
|
|
125
|
-
owner=parsed_arguments.owner,
|
|
126
|
-
repo=parsed_arguments.repo,
|
|
127
|
-
parent_number=parsed_arguments.parent_number,
|
|
128
|
-
head=parsed_arguments.head,
|
|
129
|
-
findings_file=parsed_arguments.findings_file,
|
|
130
|
-
)
|
|
131
|
-
sys.stdout.write(f"{new_pr_url}\n")
|
|
132
|
-
return 0
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if __name__ == "__main__":
|
|
136
|
-
sys.exit(main())
|