claude-dev-env 1.29.3 → 1.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -0
- package/agents/code-quality-agent.md +279 -24
- package/agents/groq-coder.md +111 -0
- package/commands/plan.md +4 -5
- package/docs/CODE_RULES.md +40 -0
- package/hooks/blocking/code_rules_enforcer.py +775 -8
- package/hooks/blocking/destructive_command_blocker.py +149 -12
- package/hooks/blocking/test_code_rules_enforcer.py +751 -0
- package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +130 -0
- package/hooks/blocking/test_code_rules_enforcer_existence_checks.py +134 -0
- package/hooks/blocking/test_code_rules_enforcer_skip_decorators.py +150 -0
- package/hooks/blocking/test_destructive_command_blocker.py +281 -4
- package/hooks/git-hooks/test_config.py +9 -3
- package/hooks/git-hooks/test_gate_utils.py +9 -3
- package/hooks/git-hooks/test_pre_commit.py +9 -3
- package/hooks/git-hooks/test_pre_push.py +9 -3
- package/hooks/validators/run_all_validators.py +76 -3
- package/hooks/validators/test_output_formatter.py +4 -16
- package/hooks/validators/test_run_all_validators.py +22 -0
- package/hooks/validators/test_run_all_validators_integration.py +2 -11
- package/package.json +1 -1
- package/scripts/config/groq_bugteam_config.py +104 -0
- package/scripts/config/test_groq_bugteam_config.py +11 -0
- package/scripts/config/test_spec_implementer_prompt.py +36 -0
- package/scripts/groq_bugteam.README.md +2 -0
- package/scripts/groq_bugteam.py +74 -15
- package/scripts/groq_bugteam_dotenv.py +40 -0
- package/scripts/groq_bugteam_spec.py +226 -0
- package/scripts/test_groq_bugteam.py +143 -5
- package/scripts/test_groq_bugteam_apply_fix_from_spec.py +426 -0
- package/scripts/test_groq_bugteam_dotenv.py +66 -0
- package/scripts/test_groq_bugteam_spec.py +346 -0
- package/skills/bugteam/SKILL.md +4 -0
- package/skills/bugteam/reference/README.md +16 -0
- package/skills/bugteam/test_skill_additions.py +30 -0
- package/skills/monitor-open-prs/SKILL.md +104 -0
- package/skills/monitor-open-prs/scripts/discover_open_prs.py +69 -0
- package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +149 -0
- package/skills/monitor-open-prs/test_skill_contract.py +43 -0
- package/skills/pr-review-responder/SKILL.md +10 -8
- package/hooks/github-action/pre-push-review.yml +0 -27
- package/hooks/github-action/test_workflow.py +0 -33
- package/skills/pr-review-responder/update_skill.py +0 -297
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Tests for discover_open_prs.py — merges gh-search output across owners."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import json
|
|
7
|
+
import pathlib
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _load_discovery_module():
|
|
14
|
+
scripts_directory = pathlib.Path(__file__).parent
|
|
15
|
+
sys.path.insert(0, str(scripts_directory))
|
|
16
|
+
module_path = scripts_directory / "discover_open_prs.py"
|
|
17
|
+
module_spec = importlib.util.spec_from_file_location(
|
|
18
|
+
"discover_open_prs", module_path
|
|
19
|
+
)
|
|
20
|
+
loaded_module = importlib.util.module_from_spec(module_spec)
|
|
21
|
+
sys.modules["discover_open_prs"] = loaded_module
|
|
22
|
+
module_spec.loader.exec_module(loaded_module)
|
|
23
|
+
return loaded_module
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
discover_open_prs = _load_discovery_module()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _make_fake_gh(responses_by_owner: dict):
|
|
30
|
+
"""Return a callable whose behavior mocks subprocess.run for gh search."""
|
|
31
|
+
|
|
32
|
+
class FakeCompletedProcess:
|
|
33
|
+
def __init__(self, stdout_text: str):
|
|
34
|
+
self.stdout = stdout_text
|
|
35
|
+
self.returncode = 0
|
|
36
|
+
|
|
37
|
+
def fake_run(command_argv, check, capture_output, text):
|
|
38
|
+
owner = _extract_owner_argument(command_argv)
|
|
39
|
+
stdout_text = json.dumps(responses_by_owner.get(owner, []))
|
|
40
|
+
return FakeCompletedProcess(stdout_text)
|
|
41
|
+
|
|
42
|
+
return fake_run
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _extract_owner_argument(command_argv) -> str:
|
|
46
|
+
for each_index, each_token in enumerate(command_argv):
|
|
47
|
+
if each_token == "--owner":
|
|
48
|
+
return command_argv[each_index + 1]
|
|
49
|
+
return ""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestDiscoverOpenPrsBothOwnersReturnResults:
|
|
53
|
+
def test_merges_prs_across_both_owners(self, monkeypatch):
|
|
54
|
+
first_owner_prs = [
|
|
55
|
+
{
|
|
56
|
+
"number": 1,
|
|
57
|
+
"repository": {"nameWithOwner": "jl-cmd/alpha"},
|
|
58
|
+
"url": "https://github.com/jl-cmd/alpha/pull/1",
|
|
59
|
+
"headRefName": "feat-one",
|
|
60
|
+
"baseRefName": "main",
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
second_owner_prs = [
|
|
64
|
+
{
|
|
65
|
+
"number": 2,
|
|
66
|
+
"repository": {"nameWithOwner": "JonEcho/beta"},
|
|
67
|
+
"url": "https://github.com/JonEcho/beta/pull/2",
|
|
68
|
+
"headRefName": "feat-two",
|
|
69
|
+
"baseRefName": "main",
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
fake_run = _make_fake_gh(
|
|
73
|
+
{"jl-cmd": first_owner_prs, "JonEcho": second_owner_prs}
|
|
74
|
+
)
|
|
75
|
+
monkeypatch.setattr(discover_open_prs.subprocess, "run", fake_run)
|
|
76
|
+
|
|
77
|
+
all_discovered = discover_open_prs.discover_open_prs(
|
|
78
|
+
all_owners=["jl-cmd", "JonEcho"]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
assert len(all_discovered) == 2
|
|
82
|
+
discovered_numbers = sorted(each["number"] for each in all_discovered)
|
|
83
|
+
assert discovered_numbers == [1, 2]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TestDiscoverOpenPrsOneOwnerEmpty:
|
|
87
|
+
def test_handles_single_owner_returning_empty_list(self, monkeypatch):
|
|
88
|
+
first_owner_prs = [
|
|
89
|
+
{
|
|
90
|
+
"number": 5,
|
|
91
|
+
"repository": {"nameWithOwner": "jl-cmd/gamma"},
|
|
92
|
+
"url": "https://github.com/jl-cmd/gamma/pull/5",
|
|
93
|
+
"headRefName": "feat-five",
|
|
94
|
+
"baseRefName": "main",
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
fake_run = _make_fake_gh({"jl-cmd": first_owner_prs, "JonEcho": []})
|
|
98
|
+
monkeypatch.setattr(discover_open_prs.subprocess, "run", fake_run)
|
|
99
|
+
|
|
100
|
+
all_discovered = discover_open_prs.discover_open_prs(
|
|
101
|
+
all_owners=["jl-cmd", "JonEcho"]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
assert len(all_discovered) == 1
|
|
105
|
+
assert all_discovered[0]["number"] == 5
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TestDiscoverOpenPrsBothEmpty:
|
|
109
|
+
def test_returns_empty_list_when_no_owner_has_open_prs(self, monkeypatch):
|
|
110
|
+
fake_run = _make_fake_gh({"jl-cmd": [], "JonEcho": []})
|
|
111
|
+
monkeypatch.setattr(discover_open_prs.subprocess, "run", fake_run)
|
|
112
|
+
|
|
113
|
+
all_discovered = discover_open_prs.discover_open_prs(
|
|
114
|
+
all_owners=["jl-cmd", "JonEcho"]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
assert all_discovered == []
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestDiscoverOpenPrsEntryShape:
|
|
121
|
+
def test_each_entry_exposes_number_owner_repo_refs_and_url(self, monkeypatch):
|
|
122
|
+
fake_run = _make_fake_gh(
|
|
123
|
+
{
|
|
124
|
+
"jl-cmd": [
|
|
125
|
+
{
|
|
126
|
+
"number": 42,
|
|
127
|
+
"repository": {"nameWithOwner": "jl-cmd/demo"},
|
|
128
|
+
"url": "https://github.com/jl-cmd/demo/pull/42",
|
|
129
|
+
"headRefName": "feat-demo",
|
|
130
|
+
"baseRefName": "main",
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"JonEcho": [],
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
monkeypatch.setattr(discover_open_prs.subprocess, "run", fake_run)
|
|
137
|
+
|
|
138
|
+
all_discovered = discover_open_prs.discover_open_prs(
|
|
139
|
+
all_owners=["jl-cmd", "JonEcho"]
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
assert len(all_discovered) == 1
|
|
143
|
+
only_entry = all_discovered[0]
|
|
144
|
+
assert only_entry["number"] == 42
|
|
145
|
+
assert only_entry["owner"] == "jl-cmd"
|
|
146
|
+
assert only_entry["repo"] == "demo"
|
|
147
|
+
assert only_entry["head_ref"] == "feat-demo"
|
|
148
|
+
assert only_entry["base_ref"] == "main"
|
|
149
|
+
assert only_entry["url"].endswith("/pull/42")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Markdown assertion tests for monitor-open-prs SKILL.md."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pathlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _read_skill_text() -> str:
|
|
9
|
+
skill_path = pathlib.Path(__file__).parent / "SKILL.md"
|
|
10
|
+
return skill_path.read_text(encoding="utf-8")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_skill_has_frontmatter_name():
|
|
14
|
+
skill_text = _read_skill_text()
|
|
15
|
+
assert skill_text.startswith("---\n")
|
|
16
|
+
assert "name: monitor-open-prs" in skill_text
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_skill_requires_agent_teams_env_var():
|
|
20
|
+
skill_text = _read_skill_text()
|
|
21
|
+
assert "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" in skill_text
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_skill_invokes_bugteam_with_groq_implementer():
|
|
25
|
+
skill_text = _read_skill_text()
|
|
26
|
+
assert "BUGTEAM_FIX_IMPLEMENTER" in skill_text
|
|
27
|
+
assert "groq-coder" in skill_text
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_skill_references_bugbot_retrigger_flag():
|
|
31
|
+
skill_text = _read_skill_text()
|
|
32
|
+
assert "--bugbot-retrigger" in skill_text
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_skill_enumerates_both_owner_scopes():
|
|
36
|
+
skill_text = _read_skill_text()
|
|
37
|
+
assert "jl-cmd" in skill_text
|
|
38
|
+
assert "JonEcho" in skill_text
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_skill_documents_bws_wrapping():
|
|
42
|
+
skill_text = _read_skill_text()
|
|
43
|
+
assert "bws run" in skill_text
|
|
@@ -23,7 +23,7 @@ Before doing ANYTHING:
|
|
|
23
23
|
3. [ ] Fix comments ONE AT A TIME, marking complete as you go
|
|
24
24
|
4. [ ] Draft reply for EVERY comment (DO NOT post directly)
|
|
25
25
|
5. [ ] Create ONE review fix commit (DO NOT squash with original)
|
|
26
|
-
6. [ ]
|
|
26
|
+
6. [ ] Push — the git pre-push hook (installed via `npx claude-dev-env`) runs automatically
|
|
27
27
|
7. [ ] Verify ALL draft replies are prepared
|
|
28
28
|
|
|
29
29
|
**Responding WITHOUT completing this checklist = automatic failure.**
|
|
@@ -112,15 +112,17 @@ git commit -m "fix: address code review feedback
|
|
|
112
112
|
Addresses review comments from PR #{number}"
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
### Rule 6:
|
|
115
|
+
### Rule 6: Pre-Push Gate Fires Automatically
|
|
116
116
|
|
|
117
|
-
**
|
|
117
|
+
**The git pre-push hook (installed via `npx claude-dev-env`) runs automatically on every `git push`.** It covers lint, magic values, boolean naming, imports, and all code-rules enforcer checks — no manual invocation needed.
|
|
118
118
|
|
|
119
|
-
- [ ] FORBIDDEN: Pushing without
|
|
119
|
+
- [ ] FORBIDDEN: Pushing without gate passing
|
|
120
120
|
- [ ] FORBIDDEN: Manually handling draft conversion
|
|
121
|
-
- [x] REQUIRED:
|
|
121
|
+
- [x] REQUIRED: Run `git push`; the pre-push hook fires and blocks if any check fails
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
Use `/qbug` only when you want a full multi-loop PR audit with subagents after the PR is open — it is NOT a substitute for the pre-push gate and refuses when no PR exists yet.
|
|
124
|
+
|
|
125
|
+
**WHY:** The pre-push hook catches ALL patterns reviewers flag: code style, draft status, commit structure. It fires automatically so nothing is missed.
|
|
124
126
|
|
|
125
127
|
### Rule 7: Verify All Drafts Complete
|
|
126
128
|
|
|
@@ -136,7 +138,7 @@ Addresses review comments from PR #{number}"
|
|
|
136
138
|
- **"Let me fix this one quickly before making the checklist"** -> WRONG. Without checklist you will miss others.
|
|
137
139
|
- **"I'll post the replies myself to save time"** -> WRONG. User controls what gets posted.
|
|
138
140
|
- **"This is a small fix, I can squash it"** -> WRONG. Squashing hides the delta from reviewers.
|
|
139
|
-
- **"Pre-push review is overkill for review fixes"** -> WRONG.
|
|
141
|
+
- **"Pre-push review is overkill for review fixes"** -> WRONG. The git pre-push hook catches style issues you introduced while fixing; it fires automatically on every push.
|
|
140
142
|
|
|
141
143
|
</EXTREMELY_IMPORTANT>
|
|
142
144
|
|
|
@@ -151,7 +153,7 @@ PR Review Response Complete
|
|
|
151
153
|
|
|
152
154
|
Fetched: {X} comments (with per_page=100)
|
|
153
155
|
Fixed: {X} issues
|
|
154
|
-
Pre-push
|
|
156
|
+
Pre-push hook: PASSED (fired automatically on git push)
|
|
155
157
|
Draft replies: {X} prepared for user
|
|
156
158
|
Commits: 2 (original + review fix, NOT squashed)
|
|
157
159
|
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
name: Pre-Push Review
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
validate:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
steps:
|
|
12
|
-
- name: Checkout code
|
|
13
|
-
uses: actions/checkout@v4
|
|
14
|
-
|
|
15
|
-
- name: Set up Python
|
|
16
|
-
uses: actions/setup-python@v5
|
|
17
|
-
with:
|
|
18
|
-
python-version: "3.13"
|
|
19
|
-
|
|
20
|
-
- name: Install dependencies
|
|
21
|
-
run: |
|
|
22
|
-
python -m pip install --upgrade pip
|
|
23
|
-
pip install pyyaml pytest
|
|
24
|
-
|
|
25
|
-
- name: Run validators
|
|
26
|
-
working-directory: packages/claude-dev-env/hooks/validators
|
|
27
|
-
run: python run_all_validators.py --json
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"""Tests for GitHub Action workflow YAML validity."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import yaml
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_workflow_is_valid_yaml() -> None:
|
|
9
|
-
"""Test that the workflow file is valid YAML."""
|
|
10
|
-
workflow_path = Path(__file__).parent / "pre-push-review.yml"
|
|
11
|
-
assert workflow_path.exists(), "Workflow file must exist"
|
|
12
|
-
|
|
13
|
-
with open(workflow_path) as f:
|
|
14
|
-
data = yaml.safe_load(f)
|
|
15
|
-
|
|
16
|
-
assert "name" in data
|
|
17
|
-
assert "on" in data or True in data
|
|
18
|
-
assert "jobs" in data
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def test_workflow_has_validate_job() -> None:
|
|
22
|
-
"""Test that workflow has a validate job with required steps."""
|
|
23
|
-
workflow_path = Path(__file__).parent / "pre-push-review.yml"
|
|
24
|
-
|
|
25
|
-
with open(workflow_path) as f:
|
|
26
|
-
data = yaml.safe_load(f)
|
|
27
|
-
|
|
28
|
-
assert "validate" in data["jobs"]
|
|
29
|
-
job = data["jobs"]["validate"]
|
|
30
|
-
assert "steps" in job
|
|
31
|
-
step_names = [s.get("name", "") for s in job["steps"]]
|
|
32
|
-
assert "Checkout code" in step_names
|
|
33
|
-
assert "Set up Python" in step_names
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Update pr-review-responder skill with all changes at once."""
|
|
3
|
-
|
|
4
|
-
import re
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
SKILL_PATH = Path(__file__).parent / "SKILL.md"
|
|
8
|
-
|
|
9
|
-
def main() -> None:
|
|
10
|
-
content = SKILL_PATH.read_text(encoding="utf-8")
|
|
11
|
-
|
|
12
|
-
# 1. Change 7-step to 8-step
|
|
13
|
-
content = content.replace("strict 7-step protocol", "strict 8-step protocol")
|
|
14
|
-
|
|
15
|
-
# 2. Update Step 4: Change from posting to drafting
|
|
16
|
-
old_step4 = '''## STEP 4: REPLY TO EACH COMMENT INLINE (MANDATORY)
|
|
17
|
-
|
|
18
|
-
**For EACH comment, you MUST post an inline reply.**
|
|
19
|
-
|
|
20
|
-
**NOT a summary comment. NOT a general "fixed everything" comment. EACH comment gets INDIVIDUAL reply.**
|
|
21
|
-
|
|
22
|
-
1. **Response format**:
|
|
23
|
-
```
|
|
24
|
-
✅ **Fixed**: [brief description of what was changed]
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
2. **Post inline reply**:
|
|
28
|
-
```bash
|
|
29
|
-
gh api repos/{owner}/{repo}/pulls/comments/{comment_id}/replies \\
|
|
30
|
-
-X POST \\
|
|
31
|
-
-f body="✅ **Fixed**: [description]"
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
3. **Examples**:
|
|
35
|
-
- `✅ **Fixed**: Removed wrapper function, using direct storage.upload_file() calls`
|
|
36
|
-
- `✅ **Fixed**: Extracted shared logic to utils/view_helpers.py`
|
|
37
|
-
- `✅ **Fixed**: Moved CSS values from Python to stylesheet`
|
|
38
|
-
- `✅ **Fixed**: Added type hints to all function parameters`
|
|
39
|
-
|
|
40
|
-
**CRITICAL VALIDATION:**
|
|
41
|
-
- ☐ Did you reply to EVERY comment? (not just some)
|
|
42
|
-
- ☐ Are replies inline? (not summary comment)
|
|
43
|
-
- ☐ Did you mark reply todos complete?
|
|
44
|
-
|
|
45
|
-
**If validation fails:**
|
|
46
|
-
```
|
|
47
|
-
ERROR: Missing inline replies.
|
|
48
|
-
Found {X} comments but only {Y} replies.
|
|
49
|
-
STOPPING execution.
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**Why this matters:** Reviewers need to know WHICH comments were addressed. Summary comments don't cut it.'''
|
|
53
|
-
|
|
54
|
-
new_step4 = '''## STEP 4: DRAFT REPLIES FOR EACH COMMENT (MANDATORY)
|
|
55
|
-
|
|
56
|
-
**For EACH comment, you MUST draft an inline reply for the user to post.**
|
|
57
|
-
|
|
58
|
-
**DO NOT POST COMMENTS DIRECTLY. Draft them and present to user for review.**
|
|
59
|
-
|
|
60
|
-
1. **Response format**:
|
|
61
|
-
```
|
|
62
|
-
✅ **Fixed**: [brief description of what was changed]
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
2. **Draft replies in a clear format for user to copy**:
|
|
66
|
-
```
|
|
67
|
-
DRAFT REPLIES (for user to post):
|
|
68
|
-
================================
|
|
69
|
-
|
|
70
|
-
Comment #1 (file.py:45 - "description of comment"):
|
|
71
|
-
Reply: ✅ **Fixed**: [description of fix]
|
|
72
|
-
|
|
73
|
-
Comment #2 (file.py:67 - "description of comment"):
|
|
74
|
-
Reply: ✅ **Fixed**: [description of fix]
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
3. **Examples**:
|
|
78
|
-
- `✅ **Fixed**: Removed wrapper function, using direct storage.upload_file() calls`
|
|
79
|
-
- `✅ **Fixed**: Extracted shared logic to utils/view_helpers.py`
|
|
80
|
-
- `✅ **Fixed**: Moved CSS values from Python to stylesheet`
|
|
81
|
-
- `✅ **Fixed**: Added type hints to all function parameters`
|
|
82
|
-
|
|
83
|
-
**CRITICAL VALIDATION:**
|
|
84
|
-
- ☐ Did you draft a reply for EVERY comment? (not just some)
|
|
85
|
-
- ☐ Are drafts specific and actionable?
|
|
86
|
-
- ☐ Did you present drafts clearly for user review?
|
|
87
|
-
|
|
88
|
-
**Why this matters:** User controls what gets posted. Drafts ensure nothing is missed while giving user final say.'''
|
|
89
|
-
|
|
90
|
-
content = content.replace(old_step4, new_step4)
|
|
91
|
-
|
|
92
|
-
# 3. Add new Step 5 (pre-push-review) before Step 5 (commits)
|
|
93
|
-
old_step5_header = '''---
|
|
94
|
-
|
|
95
|
-
## STEP 5: KEEP COMMITS SEPARATE (MANDATORY)'''
|
|
96
|
-
|
|
97
|
-
new_step5_and_6 = '''---
|
|
98
|
-
|
|
99
|
-
## STEP 5: RUN PRE-PUSH REVIEW (MANDATORY)
|
|
100
|
-
|
|
101
|
-
**BEFORE committing, you MUST run the pre-push-review skill.**
|
|
102
|
-
|
|
103
|
-
**This catches style violations, anti-patterns, and repeat mistakes BEFORE they get committed.**
|
|
104
|
-
|
|
105
|
-
1. **Invoke the pre-push-review skill**:
|
|
106
|
-
```
|
|
107
|
-
Skill(pre-push-review)
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
2. **CRITICAL VALIDATION**:
|
|
111
|
-
- ☐ Did you run pre-push-review on all changed files?
|
|
112
|
-
- ☐ Did all 22 checks pass?
|
|
113
|
-
- ☐ Did you fix any violations found?
|
|
114
|
-
|
|
115
|
-
3. **If violations found**:
|
|
116
|
-
- Fix the violations FIRST
|
|
117
|
-
- Re-run pre-push-review
|
|
118
|
-
- Only proceed when all checks pass
|
|
119
|
-
|
|
120
|
-
**Why this matters:** Pre-push-review catches the EXACT patterns reviewers flag in code reviews. Running it prevents repeat mistakes.
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## STEP 6: KEEP COMMITS SEPARATE (MANDATORY)'''
|
|
125
|
-
|
|
126
|
-
content = content.replace(old_step5_header, new_step5_and_6)
|
|
127
|
-
|
|
128
|
-
# 4. Renumber Step 6 -> Step 7
|
|
129
|
-
content = content.replace("## STEP 6: VERIFY ALL REPLIES POSTED", "## STEP 7: VERIFY ALL DRAFTS COMPLETE")
|
|
130
|
-
|
|
131
|
-
# 5. Update Step 7 (was 6) content
|
|
132
|
-
old_step6_content = '''**Before declaring success, you MUST verify ALL replies are visible on GitHub.**
|
|
133
|
-
|
|
134
|
-
1. **Check PR comments page**:
|
|
135
|
-
```bash
|
|
136
|
-
gh pr view {pr_number} --comments
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
2. **CRITICAL VALIDATION**:
|
|
140
|
-
- ☐ Are all replies visible?
|
|
141
|
-
- ☐ Do reply counts match comment counts?
|
|
142
|
-
- ☐ No failed posts?
|
|
143
|
-
|
|
144
|
-
3. **If validation fails**:
|
|
145
|
-
```
|
|
146
|
-
ERROR: Reply verification failed.
|
|
147
|
-
Expected {X} replies, found {Y}.
|
|
148
|
-
Check GitHub PR page manually.
|
|
149
|
-
```'''
|
|
150
|
-
|
|
151
|
-
new_step7_content = '''**Before declaring success, you MUST verify ALL reply drafts are prepared.**
|
|
152
|
-
|
|
153
|
-
1. **Review draft replies**:
|
|
154
|
-
- Count of drafts matches count of comments
|
|
155
|
-
- Each draft is specific and actionable
|
|
156
|
-
- Drafts are formatted clearly for user to copy
|
|
157
|
-
|
|
158
|
-
2. **CRITICAL VALIDATION**:
|
|
159
|
-
- ☐ Draft count matches comment count?
|
|
160
|
-
- ☐ Each draft references specific fix?
|
|
161
|
-
- ☐ Drafts presented in clear format?
|
|
162
|
-
|
|
163
|
-
3. **If validation fails**:
|
|
164
|
-
```
|
|
165
|
-
ERROR: Missing draft replies.
|
|
166
|
-
Expected {X} drafts, found {Y}.
|
|
167
|
-
Complete all drafts before proceeding.
|
|
168
|
-
```'''
|
|
169
|
-
|
|
170
|
-
content = content.replace(old_step6_content, new_step7_content)
|
|
171
|
-
|
|
172
|
-
# 6. Renumber Step 7 -> Step 8
|
|
173
|
-
content = content.replace("## STEP 7: FINAL REPORT", "## STEP 8: FINAL REPORT")
|
|
174
|
-
|
|
175
|
-
# 7. Update final report
|
|
176
|
-
old_report = '''```
|
|
177
|
-
✅ PR Review Response Complete
|
|
178
|
-
|
|
179
|
-
Fetched: {X} comments (with per_page=100)
|
|
180
|
-
Fixed: {X} issues
|
|
181
|
-
Replied: {X} inline comments (100% coverage)
|
|
182
|
-
Commits: 2 (original + review fix, NOT squashed)
|
|
183
|
-
|
|
184
|
-
TodoWrite checklist: 100% complete
|
|
185
|
-
All inline replies verified on GitHub
|
|
186
|
-
|
|
187
|
-
PR #{number}: {url}
|
|
188
|
-
|
|
189
|
-
Ready to push!
|
|
190
|
-
```'''
|
|
191
|
-
|
|
192
|
-
new_report = '''```
|
|
193
|
-
✅ PR Review Response Complete
|
|
194
|
-
|
|
195
|
-
Fetched: {X} comments (with per_page=100)
|
|
196
|
-
Fixed: {X} issues
|
|
197
|
-
Pre-push review: PASSED (all 22 checks)
|
|
198
|
-
Draft replies: {X} prepared for user
|
|
199
|
-
Commits: 2 (original + review fix, NOT squashed)
|
|
200
|
-
|
|
201
|
-
TodoWrite checklist: 100% complete
|
|
202
|
-
|
|
203
|
-
DRAFT REPLIES FOR USER TO POST:
|
|
204
|
-
================================
|
|
205
|
-
[List all draft replies here]
|
|
206
|
-
|
|
207
|
-
PR #{number}: {url}
|
|
208
|
-
|
|
209
|
-
Ready to push!
|
|
210
|
-
```'''
|
|
211
|
-
|
|
212
|
-
content = content.replace(old_report, new_report)
|
|
213
|
-
|
|
214
|
-
# 8. Update enforcement mechanisms
|
|
215
|
-
old_enforcement4 = '''4. **Step 4 violation**: Missing inline replies
|
|
216
|
-
```
|
|
217
|
-
ERROR: Missing inline replies to comments.
|
|
218
|
-
Found {X} comments, only {Y} replies posted.
|
|
219
|
-
Every comment requires individual inline reply.
|
|
220
|
-
STOPPING execution.
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
5. **Step 5 violation (ERROR)**: Commits were squashed'''
|
|
224
|
-
|
|
225
|
-
new_enforcement4_5_6 = '''4. **Step 4 violation**: Missing draft replies
|
|
226
|
-
```
|
|
227
|
-
ERROR: Missing draft replies to comments.
|
|
228
|
-
Found {X} comments, only {Y} drafts prepared.
|
|
229
|
-
Every comment requires individual draft reply.
|
|
230
|
-
STOPPING execution.
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
5. **Step 5 violation**: Pre-push review not run or failed
|
|
234
|
-
```
|
|
235
|
-
ERROR: Must run pre-push-review skill before committing.
|
|
236
|
-
This catches style violations and anti-patterns.
|
|
237
|
-
STOPPING execution.
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
6. **Step 6 violation (ERROR)**: Commits were squashed'''
|
|
241
|
-
|
|
242
|
-
content = content.replace(old_enforcement4, new_enforcement4_5_6)
|
|
243
|
-
|
|
244
|
-
# 9. Update Step 6 violation -> Step 7
|
|
245
|
-
content = content.replace(
|
|
246
|
-
'6. **Step 6 violation**: Reply verification failed',
|
|
247
|
-
'7. **Step 7 violation**: Draft verification failed'
|
|
248
|
-
)
|
|
249
|
-
content = content.replace(
|
|
250
|
-
'ERROR: Cannot verify all replies posted to GitHub.\n Check PR page manually: {url}',
|
|
251
|
-
'ERROR: Cannot verify all draft replies prepared.\n Expected {X} drafts, found {Y}.\n Complete all drafts before proceeding.'
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
# 10. Update quick reference
|
|
255
|
-
old_quick_ref = '''# 4. Reply inline to EACH comment
|
|
256
|
-
gh api repos/{owner}/{repo}/pulls/comments/{comment_id}/replies -X POST -f body="✅ Fixed: ..."
|
|
257
|
-
|
|
258
|
-
# 5. Create ONE review fix commit (DON'T squash with original)'''
|
|
259
|
-
|
|
260
|
-
new_quick_ref = '''# 4. Draft replies for user (DO NOT POST)
|
|
261
|
-
# Present drafts in clear format for user to copy and post
|
|
262
|
-
|
|
263
|
-
# 5. Run pre-push-review skill
|
|
264
|
-
Skill(pre-push-review)
|
|
265
|
-
|
|
266
|
-
# 6. Create ONE review fix commit (DON'T squash with original)'''
|
|
267
|
-
|
|
268
|
-
content = content.replace(old_quick_ref, new_quick_ref)
|
|
269
|
-
|
|
270
|
-
# 11. Update remaining quick reference steps
|
|
271
|
-
content = content.replace(
|
|
272
|
-
"# 6. Verify ALL replies posted\ngh pr view {pr_number} --comments",
|
|
273
|
-
"# 7. Verify ALL draft replies complete\n# Count drafts matches count of comments"
|
|
274
|
-
)
|
|
275
|
-
content = content.replace(
|
|
276
|
-
"# 7. Push (keeps commits separate for GitHub visibility)\ngit push",
|
|
277
|
-
"# 8. Push (keeps commits separate for GitHub visibility)\ngit push"
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
# 12. Update root cause section
|
|
281
|
-
content = content.replace(
|
|
282
|
-
"- Did NOT reply inline to each comment\n- Did NOT verify all replies posted",
|
|
283
|
-
"- Did NOT draft replies for each comment\n- Did NOT run pre-push-review"
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
# 13. Update "why protocol is mandatory" section
|
|
287
|
-
content = content.replace(
|
|
288
|
-
"- ✅ Clear communication (inline reply to each)",
|
|
289
|
-
"- ✅ Clear communication (draft reply for each)"
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
SKILL_PATH.write_text(content, encoding="utf-8")
|
|
293
|
-
print("Skill updated successfully!")
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if __name__ == "__main__":
|
|
297
|
-
main()
|