claude-dev-env 1.34.1 → 1.36.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/agents/clean-coder.md +109 -1
- package/agents/docs-agent.md +1 -1
- package/agents/project-docs-analyzer.md +0 -1
- package/agents/skill-to-agent-converter.md +0 -1
- package/bin/install.mjs +28 -8
- package/bin/install.test.mjs +9 -1
- package/commands/initialize.md +0 -1
- package/commands/readability-review.md +4 -4
- package/commands/review-plan.md +2 -4
- package/commands/stubcheck.md +1 -2
- package/docs/CODE_RULES.md +3 -0
- package/docs/agents-md-alignment-plan.md +123 -0
- package/hooks/blocking/code_rules_enforcer.py +686 -60
- package/hooks/blocking/es_exe_path_rewriter.py +10 -4
- package/hooks/blocking/test_code_rules_enforcer.py +273 -39
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +97 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
- package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +328 -0
- package/hooks/blocking/test_code_rules_enforcer_config_path.py +0 -20
- package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +33 -11
- package/hooks/blocking/test_code_rules_enforcer_existence_checks.py +0 -18
- package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
- package/hooks/blocking/test_code_rules_enforcer_inline_literal_collections.py +155 -0
- package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +194 -0
- package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -13
- package/hooks/blocking/test_code_rules_enforcer_skip_decorators.py +0 -26
- package/hooks/blocking/test_code_rules_enforcer_string_magic.py +234 -0
- package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
- package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
- package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
- package/hooks/blocking/windows_rmtree_blocker.py +23 -6
- package/hooks/config/banned_identifiers_constants.py +24 -0
- package/hooks/config/hardcoded_user_path_constants.py +12 -0
- package/hooks/config/hook_log_extractor_constants.py +1 -1
- package/hooks/config/pre_tool_use_stdin.py +48 -0
- package/hooks/config/setup_project_paths_constants.py +4 -0
- package/hooks/config/stuttering_check_config.py +14 -0
- package/hooks/config/stuttering_import_binding_constants.py +11 -0
- package/hooks/config/sys_path_insert_constants.py +4 -0
- package/hooks/config/test_banned_identifiers_constants.py +48 -0
- package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
- package/hooks/config/test_hook_log_extractor_constants.py +3 -3
- package/hooks/config/test_pre_tool_use_stdin.py +80 -0
- package/hooks/config/unused_module_import_constants.py +7 -0
- package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
- package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
- package/hooks/git-hooks/config.py +3 -3
- package/hooks/git-hooks/test_gate_utils.py +10 -10
- package/hooks/mypy.ini +2 -0
- package/package.json +1 -1
- package/rules/gh-paginate.md +125 -0
- package/skills/bugteam/CONSTRAINTS.md +12 -6
- package/skills/bugteam/PROMPTS.md +0 -39
- package/skills/bugteam/SKILL.md +93 -125
- package/skills/bugteam/SKILL_EVALS.md +25 -23
- package/skills/bugteam/reference/README.md +2 -0
- package/skills/bugteam/reference/audit-and-teammates.md +2 -2
- package/skills/bugteam/reference/copilot-gap-analysis.md +12 -0
- package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
- package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
- package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
- package/skills/bugteam/test_skill_additions.py +13 -4
- package/skills/bugteam/test_team_lifecycle.py +94 -0
- package/skills/findbugs/SKILL.md +3 -3
- package/skills/fixbugs/SKILL.md +4 -4
- package/skills/monitor-open-prs/SKILL.md +32 -2
- package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
- package/skills/pr-converge/SKILL.md +576 -95
- package/skills/pr-converge/scripts/README.md +145 -0
- package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
- package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
- package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
- package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
- package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
- package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
- package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
- package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
- package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
- package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
- package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
- package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
- package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
- package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
- package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
- package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
- package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
- package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
- package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
- package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
- package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
- package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
- package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
- package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
- package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
- package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
- package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
- package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
- package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
- package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
- package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
- package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
- package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
- package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
- package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
- package/skills/pr-converge/scripts/view_pr_context.py +47 -0
- package/skills/pr-converge/test_team_lifecycle.py +47 -0
- package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
- package/skills/qbug/SKILL.md +4 -4
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
- package/skills/resume-review/SKILL.md +261 -0
- package/agents/agent-writer.md +0 -157
- package/agents/config-centralizer.md +0 -686
- package/agents/config-extraction-agent.md +0 -225
- package/agents/doc-orchestrator.md +0 -47
- package/agents/docx-agent.md +0 -211
- package/agents/magic-value-eliminator-agent.md +0 -72
- package/agents/mandatory-agent-workflow-agent.md +0 -88
- package/agents/parallel-workflow-coordinator.md +0 -779
- package/agents/pdf-agent.md +0 -302
- package/agents/project-context-loader.md +0 -238
- package/agents/readability-review-agent.md +0 -76
- package/agents/refactoring-specialist.md +0 -69
- package/agents/right-sized-engineer.md +0 -129
- package/agents/session-continuity-manager.md +0 -53
- package/agents/stub-detector-agent.md +0 -140
- package/agents/tdd-test-writer.md +0 -62
- package/agents/test-data-builder.md +0 -68
- package/agents/tooling-builder.md +0 -78
- package/agents/validation-expert.md +0 -71
- package/agents/xlsx-agent.md +0 -169
- package/skills/bugteam/scripts/README.md +0 -58
- package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
- package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
- package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
- package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
- package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
- /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
; cursor-agents-continue.ahk
|
|
2
|
+
; Sends "continue" + Enter to a Cursor Agents window every 5 minutes when enabled.
|
|
3
|
+
; Toggle with Ctrl+Alt+A. Optional first CLI arg overrides the target window title substring.
|
|
4
|
+
; A small colored pill on the upper-right corner of the rightmost monitor shows ON/OFF state.
|
|
5
|
+
|
|
6
|
+
#Requires AutoHotkey v2.0
|
|
7
|
+
#SingleInstance Force
|
|
8
|
+
|
|
9
|
+
SetTitleMatchMode 2
|
|
10
|
+
|
|
11
|
+
DEFAULT_TARGET_WINDOW_TITLE := "Cursor Agents"
|
|
12
|
+
SEND_INTERVAL_MILLISECONDS := 300000
|
|
13
|
+
ACTIVATION_TIMEOUT_SECONDS := 2
|
|
14
|
+
FOCUS_SETTLE_SLEEP_MS := 150
|
|
15
|
+
POST_TEXT_SLEEP_MS := 50
|
|
16
|
+
CONTINUE_PHRASE := "continue"
|
|
17
|
+
|
|
18
|
+
INDICATOR_DESIGN_WIDTH_PX := 230
|
|
19
|
+
INDICATOR_DESIGN_HEIGHT_PX := 28
|
|
20
|
+
INDICATOR_DESIGN_RIGHT_OFFSET_PX := 16
|
|
21
|
+
INDICATOR_DESIGN_TOP_OFFSET_PX := 8
|
|
22
|
+
INDICATOR_TEXT_DESIGN_PADDING_PX := 4
|
|
23
|
+
INDICATOR_TEXT_DESIGN_HEIGHT_PX := 20
|
|
24
|
+
|
|
25
|
+
INDICATOR_FONT_NAME := "Segoe UI"
|
|
26
|
+
INDICATOR_FONT_POINT_SIZE := 9
|
|
27
|
+
INDICATOR_FONT_OPTIONS := "s" INDICATOR_FONT_POINT_SIZE " cWhite Bold"
|
|
28
|
+
|
|
29
|
+
COLOR_OFF_BACKGROUND := "800000"
|
|
30
|
+
COLOR_ON_BACKGROUND := "006400"
|
|
31
|
+
LABEL_OFF := "AGENTS: OFF (Ctrl+Alt+A)"
|
|
32
|
+
LABEL_ON := "AGENTS: ON (Ctrl+Alt+A)"
|
|
33
|
+
|
|
34
|
+
DPI_REFERENCE := 96
|
|
35
|
+
|
|
36
|
+
AUTO_START_FLAG := "--start-on"
|
|
37
|
+
|
|
38
|
+
INT32_MINIMUM_VALUE := -2147483648
|
|
39
|
+
|
|
40
|
+
terminate_other_script_instances() {
|
|
41
|
+
stop_script := A_ScriptDir "\cursor-agents-continue-stop-others.ps1"
|
|
42
|
+
if !FileExist(stop_script)
|
|
43
|
+
return
|
|
44
|
+
RunWait('pwsh -NoProfile -NoLogo -ExecutionPolicy Bypass -File "' stop_script '" -KeepProcessId ' ProcessExist(), , "Hide")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
terminate_other_script_instances()
|
|
48
|
+
|
|
49
|
+
target_window_title := resolve_target_window_title_from_arguments()
|
|
50
|
+
should_auto_start := has_auto_start_flag()
|
|
51
|
+
is_enabled := false
|
|
52
|
+
|
|
53
|
+
indicator := unset
|
|
54
|
+
indicator_label := unset
|
|
55
|
+
build_indicator()
|
|
56
|
+
render_indicator(false)
|
|
57
|
+
if (should_auto_start)
|
|
58
|
+
apply_enabled_state(true)
|
|
59
|
+
|
|
60
|
+
^!a:: {
|
|
61
|
+
global is_enabled
|
|
62
|
+
apply_enabled_state(!is_enabled)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
apply_enabled_state(next_enabled) {
|
|
66
|
+
global is_enabled
|
|
67
|
+
is_enabled := next_enabled
|
|
68
|
+
render_indicator(is_enabled)
|
|
69
|
+
if (is_enabled) {
|
|
70
|
+
send_continue_to_target()
|
|
71
|
+
SetTimer(send_continue_to_target, SEND_INTERVAL_MILLISECONDS)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
SetTimer(send_continue_to_target, 0)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
has_auto_start_flag() {
|
|
78
|
+
each_arg_index := 1
|
|
79
|
+
while (each_arg_index <= A_Args.Length) {
|
|
80
|
+
if (A_Args[each_arg_index] = AUTO_START_FLAG)
|
|
81
|
+
return true
|
|
82
|
+
each_arg_index++
|
|
83
|
+
}
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
resolve_target_window_title_from_arguments() {
|
|
88
|
+
candidate := ""
|
|
89
|
+
each_arg_index := 1
|
|
90
|
+
while (each_arg_index <= A_Args.Length) {
|
|
91
|
+
arg_value := A_Args[each_arg_index]
|
|
92
|
+
if (arg_value = AUTO_START_FLAG) {
|
|
93
|
+
each_arg_index++
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (candidate = "") {
|
|
97
|
+
candidate := arg_value
|
|
98
|
+
}
|
|
99
|
+
each_arg_index++
|
|
100
|
+
}
|
|
101
|
+
if (candidate = "")
|
|
102
|
+
return DEFAULT_TARGET_WINDOW_TITLE
|
|
103
|
+
return IsInteger(candidate) ? "ahk_pid " candidate : candidate
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
build_indicator() {
|
|
108
|
+
global indicator, indicator_label
|
|
109
|
+
dpi_scale_factor := A_ScreenDPI / DPI_REFERENCE
|
|
110
|
+
scaled_width := Round(INDICATOR_DESIGN_WIDTH_PX * dpi_scale_factor)
|
|
111
|
+
scaled_height := Round(INDICATOR_DESIGN_HEIGHT_PX * dpi_scale_factor)
|
|
112
|
+
scaled_right_offset := Round(INDICATOR_DESIGN_RIGHT_OFFSET_PX * dpi_scale_factor)
|
|
113
|
+
scaled_top_offset := Round(INDICATOR_DESIGN_TOP_OFFSET_PX * dpi_scale_factor)
|
|
114
|
+
scaled_text_padding := Round(INDICATOR_TEXT_DESIGN_PADDING_PX * dpi_scale_factor)
|
|
115
|
+
scaled_text_height := Round(INDICATOR_TEXT_DESIGN_HEIGHT_PX * dpi_scale_factor)
|
|
116
|
+
scaled_text_width := scaled_width - (scaled_text_padding * 2)
|
|
117
|
+
|
|
118
|
+
rightmost_monitor_bounds := find_rightmost_monitor_bounds()
|
|
119
|
+
|
|
120
|
+
indicator := Gui("-Caption +AlwaysOnTop +ToolWindow -DPIScale")
|
|
121
|
+
indicator.BackColor := COLOR_OFF_BACKGROUND
|
|
122
|
+
indicator.SetFont(INDICATOR_FONT_OPTIONS, INDICATOR_FONT_NAME)
|
|
123
|
+
indicator_label := indicator.Add(
|
|
124
|
+
"Text",
|
|
125
|
+
"x" scaled_text_padding " y" scaled_text_padding
|
|
126
|
+
. " w" scaled_text_width " h" scaled_text_height
|
|
127
|
+
. " Center BackgroundTrans",
|
|
128
|
+
LABEL_OFF
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
indicator_x := rightmost_monitor_bounds.right_edge - scaled_width - scaled_right_offset
|
|
132
|
+
indicator_y := rightmost_monitor_bounds.top_edge + scaled_top_offset
|
|
133
|
+
indicator.Show("x" indicator_x " y" indicator_y " w" scaled_width " h" scaled_height " NoActivate")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
render_indicator(is_currently_enabled) {
|
|
137
|
+
global indicator, indicator_label
|
|
138
|
+
indicator.BackColor := is_currently_enabled ? COLOR_ON_BACKGROUND : COLOR_OFF_BACKGROUND
|
|
139
|
+
indicator_label.Text := is_currently_enabled ? LABEL_ON : LABEL_OFF
|
|
140
|
+
WinRedraw("ahk_id " indicator.Hwnd)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
send_continue_to_target() {
|
|
144
|
+
global target_window_title
|
|
145
|
+
if (!WinExist(target_window_title))
|
|
146
|
+
return
|
|
147
|
+
try {
|
|
148
|
+
WinActivate(target_window_title)
|
|
149
|
+
if (!WinWaitActive(target_window_title, , ACTIVATION_TIMEOUT_SECONDS))
|
|
150
|
+
return
|
|
151
|
+
} catch TargetError {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
Sleep FOCUS_SETTLE_SLEEP_MS
|
|
155
|
+
SendText CONTINUE_PHRASE
|
|
156
|
+
Sleep POST_TEXT_SLEEP_MS
|
|
157
|
+
Send "{Enter}"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
find_rightmost_monitor_bounds() {
|
|
161
|
+
monitor_count := MonitorGetCount()
|
|
162
|
+
rightmost_edge_px := INT32_MINIMUM_VALUE
|
|
163
|
+
rightmost_monitor_top_px := 0
|
|
164
|
+
loop monitor_count {
|
|
165
|
+
MonitorGet(A_Index, &monitor_left, &monitor_top, &monitor_right, &monitor_bottom)
|
|
166
|
+
if (monitor_right > rightmost_edge_px) {
|
|
167
|
+
rightmost_edge_px := monitor_right
|
|
168
|
+
rightmost_monitor_top_px := monitor_top
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { right_edge: rightmost_edge_px, top_edge: rightmost_monitor_top_px }
|
|
172
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Evict cached ``config`` package bindings before local ``config`` imports.
|
|
2
|
+
|
|
3
|
+
Mirrors ``_evict_config_module`` in the repository root ``conftest.py``: stale
|
|
4
|
+
``config`` or ``config.*`` entries from other packages must not satisfy
|
|
5
|
+
``from config.…`` in these scripts after ``sys.path`` inserts the script directory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import importlib
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def evict_cached_config_modules() -> None:
|
|
15
|
+
for each_cached_module_name in list(sys.modules):
|
|
16
|
+
is_config_root = each_cached_module_name == "config"
|
|
17
|
+
is_config_submodule = each_cached_module_name.startswith("config.")
|
|
18
|
+
if is_config_root or is_config_submodule:
|
|
19
|
+
sys.modules.pop(each_cached_module_name, None)
|
|
20
|
+
importlib.invalidate_caches()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Fetch unaddressed Cursor Bugbot inline comments for the latest Bugbot review on a commit.
|
|
2
|
+
|
|
3
|
+
Uses ``fetch_bugbot_reviews`` to find the newest submitted Bugbot review whose ``commit_id`` matches the caller
|
|
4
|
+
``current_head``, then returns only ``cursor[bot]`` inline comments whose ``pull_request_review_id`` matches that
|
|
5
|
+
review. This avoids misclassifying a PR when Bugbot posts more than one review on the same SHA: older inline threads
|
|
6
|
+
stay anchored to the earlier review id even when they share the same commit id.
|
|
7
|
+
|
|
8
|
+
Wraps the gh CLI invocation required by the gh-paginate rule for the comments list:
|
|
9
|
+
``gh api`` on ``repos/{owner}/{repo}/pulls/{number}/comments`` with ``--paginate --slurp`` and external JSON handling.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
if str(Path(__file__).resolve().parent) not in sys.path:
|
|
19
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
20
|
+
|
|
21
|
+
from evict_cached_config_modules import evict_cached_config_modules
|
|
22
|
+
|
|
23
|
+
evict_cached_config_modules()
|
|
24
|
+
|
|
25
|
+
from config.pr_converge_constants import (
|
|
26
|
+
CURSOR_BOT_LOGIN,
|
|
27
|
+
GH_INLINE_COMMENTS_PATH_TEMPLATE,
|
|
28
|
+
)
|
|
29
|
+
from fetch_bugbot_reviews import fetch_bugbot_reviews
|
|
30
|
+
from review_field_helpers import body_of, login_of
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def fetch_bugbot_inline_comments(
|
|
34
|
+
*,
|
|
35
|
+
owner: str,
|
|
36
|
+
repo: str,
|
|
37
|
+
number: int,
|
|
38
|
+
current_head: str,
|
|
39
|
+
) -> list[dict[str, object]]:
|
|
40
|
+
"""Return cursor[bot] inline comments for the latest Bugbot review on ``current_head``.
|
|
41
|
+
|
|
42
|
+
Each entry contains comment_id, commit_id, path, line, and body.
|
|
43
|
+
"""
|
|
44
|
+
all_bugbot_reviews = fetch_bugbot_reviews(owner=owner, repo=repo, number=number)
|
|
45
|
+
latest_bugbot_review_for_head = next(
|
|
46
|
+
(
|
|
47
|
+
each_review
|
|
48
|
+
for each_review in all_bugbot_reviews
|
|
49
|
+
if each_review.get("commit_id") == current_head
|
|
50
|
+
),
|
|
51
|
+
None,
|
|
52
|
+
)
|
|
53
|
+
if latest_bugbot_review_for_head is None:
|
|
54
|
+
return []
|
|
55
|
+
target_pull_request_review_id = latest_bugbot_review_for_head["review_id"]
|
|
56
|
+
comments_endpoint = GH_INLINE_COMMENTS_PATH_TEMPLATE.format(
|
|
57
|
+
owner=owner, repo=repo, number=number
|
|
58
|
+
)
|
|
59
|
+
gh_command: list[str] = [
|
|
60
|
+
"gh",
|
|
61
|
+
"api",
|
|
62
|
+
comments_endpoint,
|
|
63
|
+
"--paginate",
|
|
64
|
+
"--slurp",
|
|
65
|
+
]
|
|
66
|
+
completed = subprocess.run(
|
|
67
|
+
gh_command,
|
|
68
|
+
capture_output=True,
|
|
69
|
+
check=True,
|
|
70
|
+
text=True,
|
|
71
|
+
encoding="utf-8",
|
|
72
|
+
errors="replace",
|
|
73
|
+
)
|
|
74
|
+
pages: list[list[dict[str, object]]] = json.loads(completed.stdout)
|
|
75
|
+
all_flat_comments = [each_comment for each_page in pages for each_comment in each_page]
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
"comment_id": each_comment["id"],
|
|
79
|
+
"commit_id": each_comment.get("commit_id"),
|
|
80
|
+
"path": each_comment.get("path"),
|
|
81
|
+
"line": each_comment.get("line"),
|
|
82
|
+
"body": body_of(each_comment),
|
|
83
|
+
}
|
|
84
|
+
for each_comment in all_flat_comments
|
|
85
|
+
if login_of(each_comment) == CURSOR_BOT_LOGIN
|
|
86
|
+
and each_comment.get("commit_id") == current_head
|
|
87
|
+
and each_comment.get("pull_request_review_id") == target_pull_request_review_id
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def main() -> int:
|
|
92
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
93
|
+
parser.add_argument("--owner", required=True)
|
|
94
|
+
parser.add_argument("--repo", required=True)
|
|
95
|
+
parser.add_argument("--number", required=True, type=int)
|
|
96
|
+
parser.add_argument("--commit", required=True, dest="current_head")
|
|
97
|
+
parsed_arguments = parser.parse_args()
|
|
98
|
+
all_comments = fetch_bugbot_inline_comments(
|
|
99
|
+
owner=parsed_arguments.owner,
|
|
100
|
+
repo=parsed_arguments.repo,
|
|
101
|
+
number=parsed_arguments.number,
|
|
102
|
+
current_head=parsed_arguments.current_head,
|
|
103
|
+
)
|
|
104
|
+
json.dump(all_comments, sys.stdout)
|
|
105
|
+
sys.stdout.write("\n")
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
sys.exit(main())
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Fetch Cursor Bugbot reviews newest-first, classified as dirty or clean.
|
|
2
|
+
|
|
3
|
+
Wraps the gh CLI invocation required by the gh-paginate rule:
|
|
4
|
+
`gh api '...?per_page=100' --paginate --slurp` piped through external Python
|
|
5
|
+
JSON handling (instead of `gh --jq`, which runs per-page and breaks cross-page
|
|
6
|
+
operations like sort/reverse — see GitHub CLI #10459).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
if str(Path(__file__).resolve().parent) not in sys.path:
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
18
|
+
|
|
19
|
+
from evict_cached_config_modules import evict_cached_config_modules
|
|
20
|
+
|
|
21
|
+
evict_cached_config_modules()
|
|
22
|
+
|
|
23
|
+
from config.pr_converge_constants import (
|
|
24
|
+
BUGBOT_DIRTY_BODY_REGEX,
|
|
25
|
+
CURSOR_BOT_LOGIN,
|
|
26
|
+
GH_REVIEWS_PATH_TEMPLATE,
|
|
27
|
+
)
|
|
28
|
+
from review_field_helpers import body_of, login_of, submitted_at_of
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def fetch_bugbot_reviews(
|
|
32
|
+
*,
|
|
33
|
+
owner: str,
|
|
34
|
+
repo: str,
|
|
35
|
+
number: int,
|
|
36
|
+
) -> list[dict[str, object]]:
|
|
37
|
+
"""Return Cursor Bugbot reviews newest-first, each with a clean/dirty classification.
|
|
38
|
+
|
|
39
|
+
Each entry contains review_id, commit_id, submitted_at, body, and classification.
|
|
40
|
+
"""
|
|
41
|
+
reviews_endpoint = GH_REVIEWS_PATH_TEMPLATE.format(
|
|
42
|
+
owner=owner, repo=repo, number=number
|
|
43
|
+
)
|
|
44
|
+
gh_command: list[str] = [
|
|
45
|
+
"gh",
|
|
46
|
+
"api",
|
|
47
|
+
reviews_endpoint,
|
|
48
|
+
"--paginate",
|
|
49
|
+
"--slurp",
|
|
50
|
+
]
|
|
51
|
+
completed = subprocess.run(
|
|
52
|
+
gh_command,
|
|
53
|
+
capture_output=True,
|
|
54
|
+
check=True,
|
|
55
|
+
text=True,
|
|
56
|
+
encoding="utf-8",
|
|
57
|
+
errors="replace",
|
|
58
|
+
)
|
|
59
|
+
pages: list[list[dict[str, object]]] = json.loads(completed.stdout)
|
|
60
|
+
all_flat_reviews = [each_review for each_page in pages for each_review in each_page]
|
|
61
|
+
all_bugbot_reviews = [
|
|
62
|
+
each_review
|
|
63
|
+
for each_review in all_flat_reviews
|
|
64
|
+
if login_of(each_review) == CURSOR_BOT_LOGIN
|
|
65
|
+
and each_review.get("submitted_at") is not None
|
|
66
|
+
and each_review.get("id") is not None
|
|
67
|
+
]
|
|
68
|
+
all_bugbot_reviews.sort(
|
|
69
|
+
key=lambda each_review: submitted_at_of(each_review), reverse=True
|
|
70
|
+
)
|
|
71
|
+
dirty_pattern = re.compile(BUGBOT_DIRTY_BODY_REGEX)
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
"review_id": each_review["id"],
|
|
75
|
+
"commit_id": each_review.get("commit_id"),
|
|
76
|
+
"submitted_at": each_review["submitted_at"],
|
|
77
|
+
"body": body_of(each_review),
|
|
78
|
+
"classification": (
|
|
79
|
+
"dirty"
|
|
80
|
+
if dirty_pattern.search(body_of(each_review))
|
|
81
|
+
else "clean"
|
|
82
|
+
),
|
|
83
|
+
}
|
|
84
|
+
for each_review in all_bugbot_reviews
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> int:
|
|
89
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
90
|
+
parser.add_argument("--owner", required=True)
|
|
91
|
+
parser.add_argument("--repo", required=True)
|
|
92
|
+
parser.add_argument("--number", required=True, type=int)
|
|
93
|
+
parsed_arguments = parser.parse_args()
|
|
94
|
+
all_reviews = fetch_bugbot_reviews(
|
|
95
|
+
owner=parsed_arguments.owner, repo=parsed_arguments.repo, number=parsed_arguments.number
|
|
96
|
+
)
|
|
97
|
+
json.dump(all_reviews, sys.stdout)
|
|
98
|
+
sys.stdout.write("\n")
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
sys.exit(main())
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Fetch unaddressed Copilot inline comments for the latest Copilot review on a commit.
|
|
2
|
+
|
|
3
|
+
Uses ``fetch_copilot_reviews`` to find the newest submitted Copilot review whose ``commit_id`` matches the caller
|
|
4
|
+
``current_head``, then returns only ``copilot-pull-request-reviewer[bot]`` inline comments whose
|
|
5
|
+
``pull_request_review_id`` matches that review. This avoids misclassifying a PR when Copilot posts more than one review
|
|
6
|
+
on the same SHA: older inline threads stay anchored to the earlier review id even when they share the same commit id.
|
|
7
|
+
|
|
8
|
+
Wraps the gh CLI invocation required by the gh-paginate rule for the comments list:
|
|
9
|
+
``gh api`` on ``repos/{owner}/{repo}/pulls/{number}/comments`` with ``--paginate --slurp`` and external JSON handling.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
if str(Path(__file__).resolve().parent) not in sys.path:
|
|
19
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
20
|
+
|
|
21
|
+
from evict_cached_config_modules import evict_cached_config_modules
|
|
22
|
+
|
|
23
|
+
evict_cached_config_modules()
|
|
24
|
+
|
|
25
|
+
from config.pr_converge_constants import (
|
|
26
|
+
COPILOT_REVIEWER_LOGIN,
|
|
27
|
+
GH_INLINE_COMMENTS_PATH_TEMPLATE,
|
|
28
|
+
)
|
|
29
|
+
from fetch_copilot_reviews import fetch_copilot_reviews
|
|
30
|
+
from review_field_helpers import body_of, login_of
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def fetch_copilot_inline_comments(
|
|
34
|
+
*,
|
|
35
|
+
owner: str,
|
|
36
|
+
repo: str,
|
|
37
|
+
number: int,
|
|
38
|
+
current_head: str,
|
|
39
|
+
) -> list[dict[str, object]]:
|
|
40
|
+
"""Return Copilot inline comments for the latest Copilot review on ``current_head``.
|
|
41
|
+
|
|
42
|
+
Each entry contains comment_id, commit_id, path, line, and body.
|
|
43
|
+
"""
|
|
44
|
+
all_copilot_reviews = fetch_copilot_reviews(owner=owner, repo=repo, number=number)
|
|
45
|
+
latest_copilot_review_for_head = next(
|
|
46
|
+
(
|
|
47
|
+
each_review
|
|
48
|
+
for each_review in all_copilot_reviews
|
|
49
|
+
if each_review.get("commit_id") == current_head
|
|
50
|
+
),
|
|
51
|
+
None,
|
|
52
|
+
)
|
|
53
|
+
if latest_copilot_review_for_head is None:
|
|
54
|
+
return []
|
|
55
|
+
target_pull_request_review_id = latest_copilot_review_for_head["review_id"]
|
|
56
|
+
comments_endpoint = GH_INLINE_COMMENTS_PATH_TEMPLATE.format(
|
|
57
|
+
owner=owner, repo=repo, number=number
|
|
58
|
+
)
|
|
59
|
+
gh_command: list[str] = [
|
|
60
|
+
"gh",
|
|
61
|
+
"api",
|
|
62
|
+
comments_endpoint,
|
|
63
|
+
"--paginate",
|
|
64
|
+
"--slurp",
|
|
65
|
+
]
|
|
66
|
+
completed = subprocess.run(
|
|
67
|
+
gh_command,
|
|
68
|
+
capture_output=True,
|
|
69
|
+
check=True,
|
|
70
|
+
text=True,
|
|
71
|
+
encoding="utf-8",
|
|
72
|
+
errors="replace",
|
|
73
|
+
)
|
|
74
|
+
pages: list[list[dict[str, object]]] = json.loads(completed.stdout)
|
|
75
|
+
all_flat_comments = [
|
|
76
|
+
each_comment for each_page in pages for each_comment in each_page
|
|
77
|
+
]
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
"comment_id": each_comment["id"],
|
|
81
|
+
"commit_id": each_comment.get("commit_id"),
|
|
82
|
+
"path": each_comment.get("path"),
|
|
83
|
+
"line": each_comment.get("line"),
|
|
84
|
+
"body": body_of(each_comment),
|
|
85
|
+
}
|
|
86
|
+
for each_comment in all_flat_comments
|
|
87
|
+
if login_of(each_comment) == COPILOT_REVIEWER_LOGIN
|
|
88
|
+
and each_comment.get("commit_id") == current_head
|
|
89
|
+
and each_comment.get("pull_request_review_id") == target_pull_request_review_id
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main() -> int:
|
|
94
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
95
|
+
parser.add_argument("--owner", required=True)
|
|
96
|
+
parser.add_argument("--repo", required=True)
|
|
97
|
+
parser.add_argument("--number", required=True, type=int)
|
|
98
|
+
parser.add_argument("--commit", required=True, dest="current_head")
|
|
99
|
+
parsed_arguments = parser.parse_args()
|
|
100
|
+
all_comments = fetch_copilot_inline_comments(
|
|
101
|
+
owner=parsed_arguments.owner,
|
|
102
|
+
repo=parsed_arguments.repo,
|
|
103
|
+
number=parsed_arguments.number,
|
|
104
|
+
current_head=parsed_arguments.current_head,
|
|
105
|
+
)
|
|
106
|
+
json.dump(all_comments, sys.stdout)
|
|
107
|
+
sys.stdout.write("\n")
|
|
108
|
+
return 0
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
sys.exit(main())
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Fetch GitHub Copilot reviewer reviews newest-first, classified as dirty or clean.
|
|
2
|
+
|
|
3
|
+
Wraps the gh CLI invocation required by the gh-paginate rule:
|
|
4
|
+
``gh api '...?per_page=100' --paginate --slurp`` piped through external Python
|
|
5
|
+
JSON handling (instead of ``gh --jq``, which runs per-page and breaks cross-page
|
|
6
|
+
operations like sort/reverse - see GitHub CLI #10459).
|
|
7
|
+
|
|
8
|
+
Classification follows the review's ``state`` field:
|
|
9
|
+
- ``APPROVED`` -> ``"clean"``
|
|
10
|
+
- ``CHANGES_REQUESTED`` -> ``"dirty"``
|
|
11
|
+
- ``COMMENTED`` with non-empty body -> ``"dirty"`` (Copilot uses COMMENTED + body
|
|
12
|
+
to flag findings without a hard block)
|
|
13
|
+
- everything else -> ``"clean"`` (no actionable findings on PR)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
if str(Path(__file__).resolve().parent) not in sys.path:
|
|
23
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
24
|
+
|
|
25
|
+
from evict_cached_config_modules import evict_cached_config_modules
|
|
26
|
+
|
|
27
|
+
evict_cached_config_modules()
|
|
28
|
+
|
|
29
|
+
from config.pr_converge_constants import (
|
|
30
|
+
ALL_COPILOT_DIRTY_REVIEW_STATES,
|
|
31
|
+
COPILOT_CLEAN_REVIEW_STATE,
|
|
32
|
+
COPILOT_REVIEWER_LOGIN,
|
|
33
|
+
COPILOT_SOFT_DIRTY_REVIEW_STATE,
|
|
34
|
+
GH_REVIEWS_PATH_TEMPLATE,
|
|
35
|
+
)
|
|
36
|
+
from review_field_helpers import body_of, login_of, state_of, submitted_at_of
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def fetch_copilot_reviews(
|
|
40
|
+
*,
|
|
41
|
+
owner: str,
|
|
42
|
+
repo: str,
|
|
43
|
+
number: int,
|
|
44
|
+
) -> list[dict[str, object]]:
|
|
45
|
+
"""Return Copilot reviews newest-first, each with a clean/dirty classification.
|
|
46
|
+
|
|
47
|
+
Each entry contains review_id, commit_id, submitted_at, state, body, and classification.
|
|
48
|
+
"""
|
|
49
|
+
reviews_endpoint = GH_REVIEWS_PATH_TEMPLATE.format(
|
|
50
|
+
owner=owner, repo=repo, number=number
|
|
51
|
+
)
|
|
52
|
+
gh_command: list[str] = [
|
|
53
|
+
"gh",
|
|
54
|
+
"api",
|
|
55
|
+
reviews_endpoint,
|
|
56
|
+
"--paginate",
|
|
57
|
+
"--slurp",
|
|
58
|
+
]
|
|
59
|
+
completed = subprocess.run(
|
|
60
|
+
gh_command,
|
|
61
|
+
capture_output=True,
|
|
62
|
+
check=True,
|
|
63
|
+
text=True,
|
|
64
|
+
encoding="utf-8",
|
|
65
|
+
errors="replace",
|
|
66
|
+
)
|
|
67
|
+
pages: list[list[dict[str, object]]] = json.loads(completed.stdout)
|
|
68
|
+
all_flat_reviews = [each_review for each_page in pages for each_review in each_page]
|
|
69
|
+
all_copilot_reviews = [
|
|
70
|
+
each_review
|
|
71
|
+
for each_review in all_flat_reviews
|
|
72
|
+
if login_of(each_review) == COPILOT_REVIEWER_LOGIN
|
|
73
|
+
and each_review.get("submitted_at") is not None
|
|
74
|
+
and each_review.get("id") is not None
|
|
75
|
+
]
|
|
76
|
+
all_copilot_reviews.sort(
|
|
77
|
+
key=lambda each_review: submitted_at_of(each_review), reverse=True
|
|
78
|
+
)
|
|
79
|
+
return [
|
|
80
|
+
{
|
|
81
|
+
"review_id": each_review["id"],
|
|
82
|
+
"commit_id": each_review.get("commit_id"),
|
|
83
|
+
"submitted_at": each_review["submitted_at"],
|
|
84
|
+
"state": state_of(each_review),
|
|
85
|
+
"body": body_of(each_review),
|
|
86
|
+
"classification": _classify_review(each_review),
|
|
87
|
+
}
|
|
88
|
+
for each_review in all_copilot_reviews
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _classify_review(field_by_key: dict[str, object]) -> str:
|
|
93
|
+
review_state = state_of(field_by_key)
|
|
94
|
+
if review_state == COPILOT_CLEAN_REVIEW_STATE:
|
|
95
|
+
return "clean"
|
|
96
|
+
if review_state not in ALL_COPILOT_DIRTY_REVIEW_STATES:
|
|
97
|
+
return "clean"
|
|
98
|
+
state_requires_body = review_state == COPILOT_SOFT_DIRTY_REVIEW_STATE
|
|
99
|
+
if state_requires_body and not body_of(field_by_key):
|
|
100
|
+
return "clean"
|
|
101
|
+
return "dirty"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main() -> int:
|
|
105
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
106
|
+
parser.add_argument("--owner", required=True)
|
|
107
|
+
parser.add_argument("--repo", required=True)
|
|
108
|
+
parser.add_argument("--number", required=True, type=int)
|
|
109
|
+
parsed_arguments = parser.parse_args()
|
|
110
|
+
all_reviews = fetch_copilot_reviews(
|
|
111
|
+
owner=parsed_arguments.owner,
|
|
112
|
+
repo=parsed_arguments.repo,
|
|
113
|
+
number=parsed_arguments.number,
|
|
114
|
+
)
|
|
115
|
+
json.dump(all_reviews, sys.stdout)
|
|
116
|
+
sys.stdout.write("\n")
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
sys.exit(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Mark a draft PR as ready for review.
|
|
2
|
+
|
|
3
|
+
Convergence action invoked by pr-converge when both bugbot and bugteam are
|
|
4
|
+
clean against the same HEAD.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
if str(Path(__file__).resolve().parent) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
14
|
+
|
|
15
|
+
from evict_cached_config_modules import evict_cached_config_modules
|
|
16
|
+
|
|
17
|
+
evict_cached_config_modules()
|
|
18
|
+
|
|
19
|
+
from config.pr_converge_constants import GH_REPO_ARG_TEMPLATE
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def mark_pr_ready(*, owner: str, repo: str, number: int) -> None:
|
|
23
|
+
"""Run `gh pr ready <number> --repo <owner>/<repo>`."""
|
|
24
|
+
repo_arg = GH_REPO_ARG_TEMPLATE.format(owner=owner, repo=repo)
|
|
25
|
+
gh_command: list[str] = [
|
|
26
|
+
"gh",
|
|
27
|
+
"pr",
|
|
28
|
+
"ready",
|
|
29
|
+
str(number),
|
|
30
|
+
"--repo",
|
|
31
|
+
repo_arg,
|
|
32
|
+
]
|
|
33
|
+
subprocess.run(
|
|
34
|
+
gh_command,
|
|
35
|
+
capture_output=True,
|
|
36
|
+
check=True,
|
|
37
|
+
text=True,
|
|
38
|
+
encoding="utf-8",
|
|
39
|
+
errors="replace",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main() -> int:
|
|
44
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
45
|
+
parser.add_argument("--owner", required=True)
|
|
46
|
+
parser.add_argument("--repo", required=True)
|
|
47
|
+
parser.add_argument("--number", required=True, type=int)
|
|
48
|
+
parsed_arguments = parser.parse_args()
|
|
49
|
+
mark_pr_ready(owner=parsed_arguments.owner, repo=parsed_arguments.repo, number=parsed_arguments.number)
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
sys.exit(main())
|