claude-dev-env 1.29.3 → 1.30.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.
Files changed (41) hide show
  1. package/agents/code-quality-agent.md +279 -24
  2. package/agents/groq-coder.md +111 -0
  3. package/commands/plan.md +4 -5
  4. package/hooks/blocking/code_rules_enforcer.py +775 -8
  5. package/hooks/blocking/destructive_command_blocker.py +149 -12
  6. package/hooks/blocking/test_code_rules_enforcer.py +751 -0
  7. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +130 -0
  8. package/hooks/blocking/test_code_rules_enforcer_existence_checks.py +134 -0
  9. package/hooks/blocking/test_code_rules_enforcer_skip_decorators.py +150 -0
  10. package/hooks/blocking/test_destructive_command_blocker.py +281 -4
  11. package/hooks/git-hooks/test_config.py +9 -3
  12. package/hooks/git-hooks/test_gate_utils.py +9 -3
  13. package/hooks/git-hooks/test_pre_commit.py +9 -3
  14. package/hooks/git-hooks/test_pre_push.py +9 -3
  15. package/hooks/validators/run_all_validators.py +76 -3
  16. package/hooks/validators/test_output_formatter.py +4 -16
  17. package/hooks/validators/test_run_all_validators.py +22 -0
  18. package/hooks/validators/test_run_all_validators_integration.py +2 -11
  19. package/package.json +1 -1
  20. package/scripts/config/groq_bugteam_config.py +104 -0
  21. package/scripts/config/test_groq_bugteam_config.py +11 -0
  22. package/scripts/config/test_spec_implementer_prompt.py +36 -0
  23. package/scripts/groq_bugteam.README.md +2 -0
  24. package/scripts/groq_bugteam.py +74 -15
  25. package/scripts/groq_bugteam_dotenv.py +40 -0
  26. package/scripts/groq_bugteam_spec.py +226 -0
  27. package/scripts/test_groq_bugteam.py +143 -5
  28. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +426 -0
  29. package/scripts/test_groq_bugteam_dotenv.py +66 -0
  30. package/scripts/test_groq_bugteam_spec.py +346 -0
  31. package/skills/bugteam/SKILL.md +4 -0
  32. package/skills/bugteam/reference/README.md +16 -0
  33. package/skills/bugteam/test_skill_additions.py +30 -0
  34. package/skills/monitor-open-prs/SKILL.md +104 -0
  35. package/skills/monitor-open-prs/scripts/discover_open_prs.py +69 -0
  36. package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +149 -0
  37. package/skills/monitor-open-prs/test_skill_contract.py +43 -0
  38. package/skills/pr-review-responder/SKILL.md +10 -8
  39. package/hooks/github-action/pre-push-review.yml +0 -27
  40. package/hooks/github-action/test_workflow.py +0 -33
  41. 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. [ ] Run pre-push-review skill before pushing
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: Run Pre-Push Review
115
+ ### Rule 6: Pre-Push Gate Fires Automatically
116
116
 
117
- **NEVER push without running pre-push-review skill.**
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 pre-push-review
119
+ - [ ] FORBIDDEN: Pushing without gate passing
120
120
  - [ ] FORBIDDEN: Manually handling draft conversion
121
- - [x] REQUIRED: Invoke pre-push-review skill which handles all 24 checks + draft conversion
121
+ - [x] REQUIRED: Run `git push`; the pre-push hook fires and blocks if any check fails
122
122
 
123
- **WHY:** Pre-push-review catches ALL patterns reviewers flag: code style, draft status, commit structure. Delegating to it ensures nothing is missed.
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. It catches style issues you introduced while fixing.
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 review: PASSED (all 24 checks)
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()