claude-dev-env 1.0.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 (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/agents/agent-writer.md +157 -0
  4. package/agents/clasp-deployment-orchestrator.md +609 -0
  5. package/agents/clean-coder.md +295 -0
  6. package/agents/code-quality-agent.md +40 -0
  7. package/agents/code-standards-agent.md +93 -0
  8. package/agents/config-centralizer.md +686 -0
  9. package/agents/config-extraction-agent.md +225 -0
  10. package/agents/doc-orchestrator.md +47 -0
  11. package/agents/docs-agent.md +112 -0
  12. package/agents/docx-agent.md +211 -0
  13. package/agents/git-commit-crafter.md +100 -0
  14. package/agents/magic-value-eliminator-agent.md +72 -0
  15. package/agents/mandatory-agent-workflow-agent.md +88 -0
  16. package/agents/parallel-workflow-coordinator.md +779 -0
  17. package/agents/pdf-agent.md +302 -0
  18. package/agents/plan-executor.md +226 -0
  19. package/agents/pr-description-writer.md +87 -0
  20. package/agents/project-context-loader.md +238 -0
  21. package/agents/project-docs-analyzer.md +54 -0
  22. package/agents/project-structure-organizer-agent.md +72 -0
  23. package/agents/readability-review-agent.md +76 -0
  24. package/agents/refactoring-specialist.md +69 -0
  25. package/agents/right-sized-engineer.md +129 -0
  26. package/agents/session-continuity-manager.md +53 -0
  27. package/agents/skill-to-agent-converter.md +371 -0
  28. package/agents/skill-writer-agent.md +470 -0
  29. package/agents/stub-detector-agent.md +140 -0
  30. package/agents/tdd-test-writer.md +62 -0
  31. package/agents/test-data-builder.md +68 -0
  32. package/agents/tooling-builder.md +78 -0
  33. package/agents/user-docs-writer.md +67 -0
  34. package/agents/validation-expert.md +71 -0
  35. package/agents/workflow-visual-documenter.md +82 -0
  36. package/agents/xlsx-agent.md +169 -0
  37. package/bin/install.mjs +256 -0
  38. package/commands/commit.md +28 -0
  39. package/commands/docupdate.md +322 -0
  40. package/commands/implement.md +102 -0
  41. package/commands/initialize.md +91 -0
  42. package/commands/plan.md +63 -0
  43. package/commands/pr-comments.md +47 -0
  44. package/commands/readability-review.md +20 -0
  45. package/commands/review-plan.md +7 -0
  46. package/commands/right-size.md +15 -0
  47. package/commands/stubcheck.md +89 -0
  48. package/commands/sum.md +30 -0
  49. package/docs/CODE_RULES.md +186 -0
  50. package/docs/DJANGO_PATTERNS.md +80 -0
  51. package/docs/REACT_PATTERNS.md +185 -0
  52. package/docs/TEST_QUALITY.md +104 -0
  53. package/hooks/advisory/migration-safety-advisor.py +49 -0
  54. package/hooks/advisory/refactor-guard.py +205 -0
  55. package/hooks/blocking/block-main-commit.py +168 -0
  56. package/hooks/blocking/code-rules-enforcer.py +549 -0
  57. package/hooks/blocking/destructive-command-blocker.py +107 -0
  58. package/hooks/blocking/docker-settings-guard.py +44 -0
  59. package/hooks/blocking/hedging-language-blocker.py +130 -0
  60. package/hooks/blocking/parallel-task-blocker.py +69 -0
  61. package/hooks/blocking/pr-description-enforcer.py +87 -0
  62. package/hooks/blocking/pyautogui-scroll-blocker.py +74 -0
  63. package/hooks/blocking/sensitive-file-protector.py +70 -0
  64. package/hooks/blocking/tdd-enforcer.py +62 -0
  65. package/hooks/blocking/test-preflight-check.py +343 -0
  66. package/hooks/blocking/write-existing-file-blocker.py +63 -0
  67. package/hooks/git-hooks/post-commit.py +103 -0
  68. package/hooks/github-action/test_workflow.py +33 -0
  69. package/hooks/hooks.json +246 -0
  70. package/hooks/lifecycle/config-change-guard.py +84 -0
  71. package/hooks/lifecycle/session-end-cleanup.py +59 -0
  72. package/hooks/notification/attention-needed-notify.py +63 -0
  73. package/hooks/notification/claude-notification-handler.py +59 -0
  74. package/hooks/notification/notification_utils.py +206 -0
  75. package/hooks/rewrite-plugin-paths.py +116 -0
  76. package/hooks/session/bulk-edit-reminder.py +30 -0
  77. package/hooks/session/code-rules-reminder.py +97 -0
  78. package/hooks/session/compact-context-reinject.py +39 -0
  79. package/hooks/session/hook-structure-context.py +140 -0
  80. package/hooks/session/plugin-data-dir-cleanup.py +39 -0
  81. package/hooks/validation/code-style-validator.py +145 -0
  82. package/hooks/validation/e2e-test-validator.py +142 -0
  83. package/hooks/validation/hook-format-validator.py +66 -0
  84. package/hooks/validation/mypy_validator.py +180 -0
  85. package/hooks/validators/README.md +125 -0
  86. package/hooks/validators/VALIDATION_REPORT.md +287 -0
  87. package/hooks/validators/__init__.py +19 -0
  88. package/hooks/validators/abbreviation_checks.py +82 -0
  89. package/hooks/validators/code_quality_checks.py +133 -0
  90. package/hooks/validators/comment_checks.py +188 -0
  91. package/hooks/validators/file_structure_checks.py +182 -0
  92. package/hooks/validators/git_checks.py +107 -0
  93. package/hooks/validators/health_check.py +214 -0
  94. package/hooks/validators/magic_value_checks.py +81 -0
  95. package/hooks/validators/mypy_integration.py +52 -0
  96. package/hooks/validators/output_formatter.py +266 -0
  97. package/hooks/validators/pr_reference_checks.py +72 -0
  98. package/hooks/validators/python_antipattern_checks.py +110 -0
  99. package/hooks/validators/python_style_checks.py +364 -0
  100. package/hooks/validators/react_checks.py +90 -0
  101. package/hooks/validators/ruff_integration.py +80 -0
  102. package/hooks/validators/run_all_validators.py +772 -0
  103. package/hooks/validators/security_checks.py +135 -0
  104. package/hooks/validators/test_abbreviation_checks.py +76 -0
  105. package/hooks/validators/test_bad.tsx +7 -0
  106. package/hooks/validators/test_code_quality_checks.py +129 -0
  107. package/hooks/validators/test_file_structure_checks.py +307 -0
  108. package/hooks/validators/test_files/01_basic_component.tsx +10 -0
  109. package/hooks/validators/test_files/02_component_without_react.tsx +10 -0
  110. package/hooks/validators/test_files/03_pure_component.tsx +10 -0
  111. package/hooks/validators/test_files/04_pure_component_import.tsx +10 -0
  112. package/hooks/validators/test_files/05_typescript_generics.tsx +14 -0
  113. package/hooks/validators/test_files/06_typescript_two_generics.tsx +18 -0
  114. package/hooks/validators/test_files/07_multiline_declaration.tsx +11 -0
  115. package/hooks/validators/test_files/08_error_boundary_valid.tsx +14 -0
  116. package/hooks/validators/test_files/09_error_boundary_with_other_class.tsx +20 -0
  117. package/hooks/validators/test_files/10_inheritance_chain.tsx +16 -0
  118. package/hooks/validators/test_files/11_ts_file.ts +10 -0
  119. package/hooks/validators/test_files/12_non_react_class.tsx +14 -0
  120. package/hooks/validators/test_files/13_functional_component.tsx +8 -0
  121. package/hooks/validators/test_files/14_indented_class.tsx +13 -0
  122. package/hooks/validators/test_files/15_getDerivedStateFromError.tsx +14 -0
  123. package/hooks/validators/test_files/16_mixed_components.tsx +20 -0
  124. package/hooks/validators/test_files/EXECUTIVE_SUMMARY.md +175 -0
  125. package/hooks/validators/test_files/TEST_RESULTS_TABLE.txt +60 -0
  126. package/hooks/validators/test_files/VALIDATION_REPORT.md +201 -0
  127. package/hooks/validators/test_files/async_views.py +23 -0
  128. package/hooks/validators/test_files/async_with_imports.py +14 -0
  129. package/hooks/validators/test_files/bad_inline_imports.py +37 -0
  130. package/hooks/validators/test_files/management/commands/cmd_01_no_debug_check.py +10 -0
  131. package/hooks/validators/test_files/management/commands/cmd_02_proper_debug_check.py +14 -0
  132. package/hooks/validators/test_files/management/commands/cmd_03_debug_check_with_return.py +14 -0
  133. package/hooks/validators/test_files/management/commands/cmd_04_imported_DEBUG.py +14 -0
  134. package/hooks/validators/test_files/management/commands/cmd_05_debug_check_in_helper.py +16 -0
  135. package/hooks/validators/test_files/management/commands/cmd_06_debug_check_late.py +22 -0
  136. package/hooks/validators/test_files/management/commands/cmd_07_positive_debug_check.py +15 -0
  137. package/hooks/validators/test_files/management/commands/cmd_08_debug_with_and.py +14 -0
  138. package/hooks/validators/test_files/not_management_command.py +10 -0
  139. package/hooks/validators/test_files/skip_decorators/test_01_simple_skip.py +8 -0
  140. package/hooks/validators/test_files/skip_decorators/test_02_pytest_skipif.py +8 -0
  141. package/hooks/validators/test_files/skip_decorators/test_03_unittest_skipIf.py +8 -0
  142. package/hooks/validators/test_files/skip_decorators/test_04_skip_with_parens.py +8 -0
  143. package/hooks/validators/test_files/skip_decorators/test_05_xfail.py +7 -0
  144. package/hooks/validators/test_files/skip_decorators/test_06_custom_skip.py +11 -0
  145. package/hooks/validators/test_files/skip_decorators/test_07_capital_Skip.py +8 -0
  146. package/hooks/validators/test_files/skip_decorators/test_08_skipUnless.py +7 -0
  147. package/hooks/validators/test_files/skip_decorators/test_09_pytest_mark_skip_simple.py +7 -0
  148. package/hooks/validators/test_files/test_async_functions.py +45 -0
  149. package/hooks/validators/test_files/test_purecomponent/PureComponentExample.tsx +7 -0
  150. package/hooks/validators/test_files/test_purecomponent/ReactPureComponentExample.tsx +7 -0
  151. package/hooks/validators/test_git_checks.py +295 -0
  152. package/hooks/validators/test_good.tsx +5 -0
  153. package/hooks/validators/test_health_check.py +57 -0
  154. package/hooks/validators/test_magic_value_checks.py +63 -0
  155. package/hooks/validators/test_mypy_integration.py +27 -0
  156. package/hooks/validators/test_output_formatter.py +150 -0
  157. package/hooks/validators/test_pr_reference_checks.py +41 -0
  158. package/hooks/validators/test_python_antipattern_checks.py +113 -0
  159. package/hooks/validators/test_python_style_checks.py +439 -0
  160. package/hooks/validators/test_react_checks.py +213 -0
  161. package/hooks/validators/test_results.txt +25 -0
  162. package/hooks/validators/test_ruff_integration.py +27 -0
  163. package/hooks/validators/test_run_all_validators.py +228 -0
  164. package/hooks/validators/test_run_all_validators_integration.py +48 -0
  165. package/hooks/validators/test_safety_checks.py +243 -0
  166. package/hooks/validators/test_security_checks.py +105 -0
  167. package/hooks/validators/test_test_safety_checks.py +321 -0
  168. package/hooks/validators/test_todo_checks.py +39 -0
  169. package/hooks/validators/test_type_safety_checks.py +85 -0
  170. package/hooks/validators/test_useless_test_checks.py +55 -0
  171. package/hooks/validators/test_validator_base.py +26 -0
  172. package/hooks/validators/test_verify_paths.py +34 -0
  173. package/hooks/validators/todo_checks.py +59 -0
  174. package/hooks/validators/type_safety_checks.py +101 -0
  175. package/hooks/validators/useless_test_checks.py +92 -0
  176. package/hooks/validators/validator_base.py +19 -0
  177. package/hooks/validators/verify_paths.py +57 -0
  178. package/hooks/workflow/auto-formatter.py +114 -0
  179. package/hooks/workflow/investigation-tracker-reset.py +46 -0
  180. package/package.json +30 -0
  181. package/rules/agent-spawn-protocol.md +47 -0
  182. package/rules/cleanup-temp-files.md +27 -0
  183. package/rules/code-reviews.md +11 -0
  184. package/rules/code-standards.md +43 -0
  185. package/rules/conservative-action.md +20 -0
  186. package/rules/context7.md +12 -0
  187. package/rules/explore-thoroughly.md +27 -0
  188. package/rules/git-workflow.md +42 -0
  189. package/rules/parallel-tools.md +23 -0
  190. package/rules/research-mode.md +23 -0
  191. package/rules/right-sized-engineering.md +28 -0
  192. package/rules/tdd.md +7 -0
  193. package/rules/testing.md +12 -0
  194. package/skills/agent-prompt/SKILL.md +102 -0
  195. package/skills/anthropic-plan/SKILL.md +107 -0
  196. package/skills/everything-search/SKILL.md +144 -0
  197. package/skills/ingest/SKILL.md +40 -0
  198. package/skills/npm-creator/SKILL.md +183 -0
  199. package/skills/pr-review-responder/EXAMPLES.md +590 -0
  200. package/skills/pr-review-responder/PRINCIPLES.md +539 -0
  201. package/skills/pr-review-responder/README.md +209 -0
  202. package/skills/pr-review-responder/SKILL.md +202 -0
  203. package/skills/pr-review-responder/TESTING.md +407 -0
  204. package/skills/pr-review-responder/scripts/respond_to_reviews.py +376 -0
  205. package/skills/pr-review-responder/update_skill.py +297 -0
  206. package/skills/prompt-generator/REFERENCE.md +150 -0
  207. package/skills/prompt-generator/SKILL.md +154 -0
  208. package/skills/readability-review/SKILL.md +127 -0
  209. package/skills/recall/SKILL.md +27 -0
  210. package/skills/remember/SKILL.md +63 -0
  211. package/skills/rule-audit/SKILL.md +307 -0
  212. package/skills/rule-creator/SKILL.md +150 -0
  213. package/skills/skill-writer/REFERENCE.md +246 -0
  214. package/skills/skill-writer/SKILL.md +270 -0
  215. package/skills/tdd-team/SKILL.md +128 -0
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Standalone script to respond to GitHub PR review comments.
4
+
5
+ Usage:
6
+ python respond_to_reviews.py [--pr PR_NUMBER] [--auto-approve]
7
+
8
+ Requirements:
9
+ - gh CLI installed and authenticated
10
+ - Git repository with GitHub remote
11
+ - Python 3.8+
12
+ """
13
+
14
+ import json
15
+ import subprocess
16
+ import sys
17
+ from dataclasses import dataclass
18
+ from pathlib import Path
19
+ from typing import Dict, List, Optional, Set
20
+
21
+
22
+ @dataclass
23
+ class ReviewComment:
24
+ id: int
25
+ path: str
26
+ line: int
27
+ body: str
28
+ user: str
29
+ created_at: str
30
+ in_reply_to: Optional[int]
31
+
32
+
33
+ @dataclass
34
+ class FileChange:
35
+ path: str
36
+ lines_changed: Set[int]
37
+ diff: str
38
+
39
+
40
+ def run_command(cmd: List[str]) -> str:
41
+ """Run shell command and return output."""
42
+ try:
43
+ result = subprocess.run(
44
+ cmd,
45
+ capture_output=True,
46
+ text=True,
47
+ check=True
48
+ )
49
+ return result.stdout.strip()
50
+ except subprocess.CalledProcessError as e:
51
+ print(f"Error running command: {' '.join(cmd)}", file=sys.stderr)
52
+ print(f"Error: {e.stderr}", file=sys.stderr)
53
+ sys.exit(1)
54
+
55
+
56
+ def get_current_pr() -> Optional[Dict]:
57
+ """Get PR number for current branch."""
58
+ output = run_command(['gh', 'pr', 'view', '--json', 'number,title,url'])
59
+ if not output:
60
+ return None
61
+ return json.loads(output)
62
+
63
+
64
+ def get_review_comments(pr_number: int, repo: str) -> List[ReviewComment]:
65
+ """Fetch all review comments from PR."""
66
+ cmd = [
67
+ 'gh', 'api',
68
+ f'repos/{repo}/pulls/{pr_number}/comments',
69
+ '--jq',
70
+ '.[] | {id, path, line, body, user: .user.login, created_at, in_reply_to}'
71
+ ]
72
+ output = run_command(cmd)
73
+
74
+ comments = []
75
+ for line in output.split('\n'):
76
+ if not line:
77
+ continue
78
+ data = json.loads(line)
79
+ comments.append(ReviewComment(
80
+ id=data['id'],
81
+ path=data['path'],
82
+ line=data['line'],
83
+ body=data['body'],
84
+ user=data['user'],
85
+ created_at=data['created_at'],
86
+ in_reply_to=data.get('in_reply_to')
87
+ ))
88
+
89
+ return comments
90
+
91
+
92
+ def get_current_user() -> str:
93
+ """Get current GitHub username."""
94
+ return run_command(['gh', 'api', 'user', '--jq', '.login'])
95
+
96
+
97
+ def filter_unresponded_comments(
98
+ comments: List[ReviewComment],
99
+ current_user: str
100
+ ) -> List[ReviewComment]:
101
+ """Filter for comments that haven't been responded to."""
102
+ # Group comments by thread (in_reply_to chain)
103
+ threads: Dict[int, List[ReviewComment]] = {}
104
+
105
+ for comment in comments:
106
+ if comment.in_reply_to is None:
107
+ # Top-level comment
108
+ thread_id = comment.id
109
+ else:
110
+ # Reply to another comment
111
+ thread_id = comment.in_reply_to
112
+
113
+ if thread_id not in threads:
114
+ threads[thread_id] = []
115
+ threads[thread_id].append(comment)
116
+
117
+ # Find threads where we haven't replied
118
+ unresponded = []
119
+ for thread_id, thread_comments in threads.items():
120
+ # Check if current user has replied in this thread
121
+ user_replied = any(c.user == current_user for c in thread_comments)
122
+
123
+ if not user_replied:
124
+ # Find the original comment (first in thread)
125
+ original = min(thread_comments, key=lambda c: c.created_at)
126
+ if original.user != current_user:
127
+ unresponded.append(original)
128
+
129
+ return unresponded
130
+
131
+
132
+ def get_changed_files() -> List[FileChange]:
133
+ """Get files changed in the last commit."""
134
+ # Get list of changed files
135
+ files_output = run_command(['git', 'diff', '--name-only', 'HEAD~1..HEAD'])
136
+
137
+ changes = []
138
+ for file_path in files_output.split('\n'):
139
+ if not file_path:
140
+ continue
141
+
142
+ # Get diff for this file
143
+ diff = run_command(['git', 'diff', 'HEAD~1..HEAD', '--', file_path])
144
+
145
+ # Parse changed line numbers from diff
146
+ lines_changed = set()
147
+ for line in diff.split('\n'):
148
+ if line.startswith('@@'):
149
+ # Parse @@ -old_start,old_count +new_start,new_count @@
150
+ parts = line.split(' ')
151
+ if len(parts) >= 3:
152
+ new_range = parts[2] # +new_start,new_count
153
+ if ',' in new_range:
154
+ start, count = new_range[1:].split(',')
155
+ start_line = int(start)
156
+ count_lines = int(count)
157
+ lines_changed.update(range(start_line, start_line + count_lines))
158
+
159
+ changes.append(FileChange(
160
+ path=file_path,
161
+ lines_changed=lines_changed,
162
+ diff=diff
163
+ ))
164
+
165
+ return changes
166
+
167
+
168
+ def match_comments_to_changes(
169
+ comments: List[ReviewComment],
170
+ changes: List[FileChange]
171
+ ) -> List[tuple[ReviewComment, FileChange]]:
172
+ """Match review comments to file changes."""
173
+ matches = []
174
+
175
+ changes_by_path = {c.path: c for c in changes}
176
+
177
+ for comment in comments:
178
+ if comment.path in changes_by_path:
179
+ change = changes_by_path[comment.path]
180
+ # Check if the commented line was changed
181
+ if comment.line in change.lines_changed or not change.lines_changed:
182
+ # Either the exact line changed, or we changed the file (good enough)
183
+ matches.append((comment, change))
184
+
185
+ return matches
186
+
187
+
188
+ def draft_response(comment: ReviewComment, change: FileChange) -> str:
189
+ """Draft a concise response to a review comment."""
190
+ # Analyze the diff to understand what changed
191
+ diff_lines = change.diff.split('\n')
192
+
193
+ # Look for common patterns
194
+ if 'class ' in change.diff and '- class ' in change.diff:
195
+ return "Removed wrapper class, using direct approach"
196
+
197
+ if 'def ' in change.diff:
198
+ if '+ def ' in change.diff:
199
+ return "Extracted to shared function"
200
+ if 'Type[' in change.diff or ': ' in change.diff:
201
+ return "Added type hints"
202
+
203
+ if 'import ' in change.diff:
204
+ return "Updated imports"
205
+
206
+ if '.css' in comment.path or 'style' in change.diff:
207
+ return "Moved CSS values to stylesheet"
208
+
209
+ if 'select_related' in change.diff or 'prefetch_related' in change.diff:
210
+ return "Added query optimization to eliminate N+1"
211
+
212
+ if comment.path.endswith('.py'):
213
+ # Generic Python change
214
+ return f"Updated {Path(comment.path).name}"
215
+
216
+ # Generic fallback
217
+ return f"Addressed feedback in {comment.path}"
218
+
219
+
220
+ def post_response(comment_id: int, response: str, repo: str) -> bool:
221
+ """Post response to GitHub review comment."""
222
+ formatted_response = f"✅ **Fixed**: {response}"
223
+
224
+ try:
225
+ run_command([
226
+ 'gh', 'api',
227
+ f'repos/{repo}/pulls/comments/{comment_id}/replies',
228
+ '-X', 'POST',
229
+ '-f', f'body={formatted_response}'
230
+ ])
231
+ return True
232
+ except Exception as e:
233
+ print(f"Failed to post response: {e}", file=sys.stderr)
234
+ return False
235
+
236
+
237
+ def get_repo_name() -> str:
238
+ """Get owner/repo from git remote."""
239
+ remote_url = run_command(['git', 'remote', 'get-url', 'origin'])
240
+
241
+ # Parse GitHub URL
242
+ # SSH: git@github.com:owner/repo.git
243
+ # HTTPS: https://github.com/owner/repo.git
244
+
245
+ if 'github.com' not in remote_url:
246
+ print("Error: Not a GitHub repository", file=sys.stderr)
247
+ sys.exit(1)
248
+
249
+ if remote_url.startswith('git@'):
250
+ # SSH format
251
+ repo_part = remote_url.split(':')[1]
252
+ else:
253
+ # HTTPS format
254
+ repo_part = '/'.join(remote_url.split('/')[-2:])
255
+
256
+ # Remove .git suffix
257
+ return repo_part.replace('.git', '')
258
+
259
+
260
+ def main():
261
+ import argparse
262
+
263
+ parser = argparse.ArgumentParser(
264
+ description='Respond to GitHub PR review comments'
265
+ )
266
+ parser.add_argument(
267
+ '--pr',
268
+ type=int,
269
+ help='PR number (auto-detected if not provided)'
270
+ )
271
+ parser.add_argument(
272
+ '--auto-approve',
273
+ action='store_true',
274
+ help='Auto-approve all responses without confirmation'
275
+ )
276
+
277
+ args = parser.parse_args()
278
+
279
+ # Get PR number
280
+ if args.pr:
281
+ pr_number = args.pr
282
+ pr_title = f"PR #{pr_number}"
283
+ pr_url = ""
284
+ else:
285
+ pr = get_current_pr()
286
+ if not pr:
287
+ print("Error: No PR found for current branch", file=sys.stderr)
288
+ print("Create a PR first or specify --pr NUMBER", file=sys.stderr)
289
+ sys.exit(1)
290
+ pr_number = pr['number']
291
+ pr_title = pr['title']
292
+ pr_url = pr['url']
293
+
294
+ print(f"Checking PR #{pr_number}: {pr_title}")
295
+ if pr_url:
296
+ print(f"URL: {pr_url}")
297
+ print()
298
+
299
+ # Get repository name
300
+ repo = get_repo_name()
301
+
302
+ # Get current user
303
+ current_user = get_current_user()
304
+
305
+ # Fetch review comments
306
+ print("Fetching review comments...")
307
+ all_comments = get_review_comments(pr_number, repo)
308
+ print(f"Found {len(all_comments)} total review comments")
309
+
310
+ # Filter for unresponded comments
311
+ unresponded = filter_unresponded_comments(all_comments, current_user)
312
+ print(f"Found {len(unresponded)} unresponded comments")
313
+
314
+ if not unresponded:
315
+ print("\nNo unresponded comments found!")
316
+ return
317
+
318
+ # Get changed files
319
+ print("\nAnalyzing recent changes...")
320
+ changes = get_changed_files()
321
+ print(f"Found {len(changes)} changed files in last commit")
322
+
323
+ # Match comments to changes
324
+ matches = match_comments_to_changes(unresponded, changes)
325
+
326
+ if not matches:
327
+ print("\nNo review comments match your recent changes.")
328
+ print(f"\nReview comments are about:")
329
+ for comment in unresponded:
330
+ print(f" - {comment.path}:{comment.line}")
331
+ print(f"\nBut you changed:")
332
+ for change in changes:
333
+ print(f" - {change.path}")
334
+ return
335
+
336
+ # Draft responses
337
+ print(f"\nFound {len(matches)} review comments addressed:\n")
338
+
339
+ responses = []
340
+ for i, (comment, change) in enumerate(matches, 1):
341
+ response = draft_response(comment, change)
342
+ responses.append((comment, response))
343
+
344
+ print(f"{i}. @{comment.user} on {comment.path}:{comment.line}")
345
+ print(f" Comment: {comment.body[:80]}...")
346
+ print(f" Response: ✅ **Fixed**: {response}")
347
+ print()
348
+
349
+ # Get approval
350
+ if not args.auto_approve:
351
+ answer = input(f"Post these {len(responses)} responses to the PR? (y/n) ")
352
+ if answer.lower() != 'y':
353
+ print("Cancelled.")
354
+ return
355
+
356
+ # Post responses
357
+ print("\nPosting responses...")
358
+ success_count = 0
359
+
360
+ for comment, response in responses:
361
+ if post_response(comment.id, response, repo):
362
+ print(f" ✓ {comment.path}:{comment.line}")
363
+ success_count += 1
364
+ else:
365
+ print(f" ✗ {comment.path}:{comment.line}")
366
+
367
+ print(f"\nPosted {success_count}/{len(responses)} responses to PR #{pr_number}")
368
+
369
+ if pr_url:
370
+ print(f"View PR: {pr_url}")
371
+
372
+ print("\nReady to push!")
373
+
374
+
375
+ if __name__ == '__main__':
376
+ main()
@@ -0,0 +1,297 @@
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()