@lvlup-sw/exarchos 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +22 -0
- package/.claude-plugin/plugin.json +17 -0
- package/.mcp.json +17 -0
- package/AGENTS.md +59 -0
- package/CLAUDE.md.template +62 -0
- package/LICENSE +202 -0
- package/README.md +258 -0
- package/commands/autocompact.md +37 -0
- package/commands/checkpoint.md +85 -0
- package/commands/cleanup.md +99 -0
- package/commands/debug.md +145 -0
- package/commands/delegate.md +56 -0
- package/commands/ideate.md +82 -0
- package/commands/plan.md +150 -0
- package/commands/refactor.md +139 -0
- package/commands/reload.md +37 -0
- package/commands/resume.md +130 -0
- package/commands/review.md +51 -0
- package/commands/sync-schemas.md +74 -0
- package/commands/synthesize.md +122 -0
- package/commands/tdd.md +58 -0
- package/dist/exarchos-cli.js +8828 -0
- package/dist/exarchos-mcp.js +50 -0
- package/hooks/hooks.json +53 -0
- package/package.json +59 -0
- package/rules/coding-standards.md +46 -0
- package/rules/mcp-tool-guidance.md +26 -0
- package/rules/pr-descriptions.md +12 -0
- package/rules/rm-safety.md +9 -0
- package/rules/skill-path-resolution.md +10 -0
- package/rules/tdd.md +41 -0
- package/rules/telemetry-awareness.md +9 -0
- package/scripts/assess-refactor-scope.sh +239 -0
- package/scripts/check-benchmark-regression.sh +229 -0
- package/scripts/check-coderabbit.sh +288 -0
- package/scripts/check-coverage-thresholds.sh +194 -0
- package/scripts/check-polish-scope.sh +245 -0
- package/scripts/check-property-tests.sh +167 -0
- package/scripts/check-tdd-compliance.sh +265 -0
- package/scripts/coderabbit-review-gate.sh +518 -0
- package/scripts/debug-review-gate.sh +201 -0
- package/scripts/extract-fix-tasks.sh +179 -0
- package/scripts/extract-task.sh +67 -0
- package/scripts/generate-traceability.sh +209 -0
- package/scripts/investigation-timer.sh +171 -0
- package/scripts/needs-schema-sync.sh +174 -0
- package/scripts/new-project.sh +103 -0
- package/scripts/post-delegation-check.sh +317 -0
- package/scripts/pre-synthesis-check.sh +440 -0
- package/scripts/reconcile-state.sh +346 -0
- package/scripts/reconstruct-stack.sh +432 -0
- package/scripts/review-diff.sh +63 -0
- package/scripts/review-verdict.sh +169 -0
- package/scripts/security-scan.sh +248 -0
- package/scripts/select-debug-track.sh +186 -0
- package/scripts/setup-worktree.sh +323 -0
- package/scripts/spec-coverage-check.sh +230 -0
- package/scripts/static-analysis-gate.sh +236 -0
- package/scripts/sync-labels.sh +122 -0
- package/scripts/validate-companion.sh +161 -0
- package/scripts/validate-dotnet-standards.sh +267 -0
- package/scripts/validate-installation.sh +101 -0
- package/scripts/validate-plugin.sh +223 -0
- package/scripts/validate-refactor.sh +234 -0
- package/scripts/validate-rm.sh +93 -0
- package/scripts/verify-delegation-saga.sh +240 -0
- package/scripts/verify-doc-links.sh +211 -0
- package/scripts/verify-ideate-artifacts.sh +296 -0
- package/scripts/verify-plan-coverage.sh +228 -0
- package/scripts/verify-review-triage.sh +219 -0
- package/scripts/verify-worktree-baseline.sh +159 -0
- package/scripts/verify-worktree.sh +84 -0
- package/settings.json +47 -0
- package/skills/brainstorming/SKILL.md +127 -0
- package/skills/brainstorming/references/design-template.md +65 -0
- package/skills/cleanup/SKILL.md +147 -0
- package/skills/cleanup/references/merge-verification.md +40 -0
- package/skills/debug/SKILL.md +204 -0
- package/skills/debug/references/hotfix-track.md +134 -0
- package/skills/debug/references/investigation-checklist.md +217 -0
- package/skills/debug/references/rca-template.md +150 -0
- package/skills/debug/references/state-schema.md +294 -0
- package/skills/debug/references/thorough-track.md +194 -0
- package/skills/debug/references/triage-questions.md +155 -0
- package/skills/debug/references/troubleshooting.md +47 -0
- package/skills/delegation/SKILL.md +150 -0
- package/skills/delegation/references/adaptive-orchestration.md +31 -0
- package/skills/delegation/references/agent-teams-saga.md +248 -0
- package/skills/delegation/references/fix-mode.md +74 -0
- package/skills/delegation/references/fixer-prompt.md +162 -0
- package/skills/delegation/references/implementer-prompt.md +322 -0
- package/skills/delegation/references/parallel-strategy.md +124 -0
- package/skills/delegation/references/pbt-patterns.md +172 -0
- package/skills/delegation/references/pr-fixes-mode.md +154 -0
- package/skills/delegation/references/state-management.md +51 -0
- package/skills/delegation/references/testing-patterns.md +129 -0
- package/skills/delegation/references/troubleshooting.md +33 -0
- package/skills/delegation/references/workflow-steps.md +127 -0
- package/skills/delegation/references/worktree-enforcement.md +64 -0
- package/skills/dotnet-standards/SKILL.md +269 -0
- package/skills/dotnet-standards/references/csharp-standards.md +120 -0
- package/skills/dotnet-standards/templates/.editorconfig +366 -0
- package/skills/dotnet-standards/templates/Directory.Build.props +56 -0
- package/skills/dotnet-standards/templates/Directory.Packages.props +69 -0
- package/skills/dotnet-standards/templates/global.json +6 -0
- package/skills/dotnet-standards/templates/nuget.config +9 -0
- package/skills/dotnet-standards/templates/stylecop.json +37 -0
- package/skills/git-worktrees/SKILL.md +255 -0
- package/skills/implementation-planning/SKILL.md +233 -0
- package/skills/implementation-planning/references/plan-document-template.md +42 -0
- package/skills/implementation-planning/references/spec-tracing-guide.md +51 -0
- package/skills/implementation-planning/references/task-template.md +43 -0
- package/skills/implementation-planning/references/testing-strategy-guide.md +88 -0
- package/skills/quality-review/SKILL.md +278 -0
- package/skills/quality-review/references/code-quality-checklist.md +159 -0
- package/skills/quality-review/references/review-report-template.md +65 -0
- package/skills/quality-review/references/security-checklist.md +79 -0
- package/skills/quality-review/references/typescript-standards.md +24 -0
- package/skills/refactor/COMMAND.md +67 -0
- package/skills/refactor/SKILL.md +198 -0
- package/skills/refactor/phases/auto-chain.md +262 -0
- package/skills/refactor/phases/brief.md +176 -0
- package/skills/refactor/phases/explore.md +132 -0
- package/skills/refactor/phases/overhaul-delegate.md +136 -0
- package/skills/refactor/phases/overhaul-plan.md +312 -0
- package/skills/refactor/phases/overhaul-review.md +304 -0
- package/skills/refactor/phases/polish-implement.md +349 -0
- package/skills/refactor/phases/polish-validate.md +218 -0
- package/skills/refactor/phases/update-docs.md +234 -0
- package/skills/refactor/references/brief-template.md +81 -0
- package/skills/refactor/references/doc-update-checklist.md +110 -0
- package/skills/refactor/references/explore-checklist.md +73 -0
- package/skills/refactor/references/overhaul-track.md +215 -0
- package/skills/refactor/references/polish-track.md +170 -0
- package/skills/shared/prompts/context-reading.md +58 -0
- package/skills/shared/prompts/report-format.md +54 -0
- package/skills/shared/prompts/tdd-requirements.md +39 -0
- package/skills/shepherd/SKILL.md +264 -0
- package/skills/shepherd/references/assess-checklist.md +124 -0
- package/skills/shepherd/references/fix-strategies.md +191 -0
- package/skills/spec-review/SKILL.md +229 -0
- package/skills/spec-review/references/review-checklist.md +60 -0
- package/skills/sync-schemas/SKILL.md +114 -0
- package/skills/sync-schemas/references/configuration.md +73 -0
- package/skills/synthesis/SKILL.md +129 -0
- package/skills/synthesis/references/pr-descriptions.md +87 -0
- package/skills/synthesis/references/synthesis-steps.md +109 -0
- package/skills/synthesis/references/troubleshooting.md +115 -0
- package/skills/validate-all-skills.sh +57 -0
- package/skills/validate-frontmatter.sh +237 -0
- package/skills/workflow-state/SKILL.md +210 -0
- package/skills/workflow-state/references/mcp-tool-reference.md +111 -0
- package/skills/workflow-state/references/phase-transitions.md +141 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# coderabbit-review-gate.sh - Automated CodeRabbit review cycle gate
|
|
4
|
+
#
|
|
5
|
+
# Counts review rounds, classifies thread severity, auto-resolves outdated
|
|
6
|
+
# threads, and decides whether to approve, wait, or escalate.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# coderabbit-review-gate.sh --owner <owner> --repo <repo> --pr <number> [options]
|
|
10
|
+
# coderabbit-review-gate.sh --help
|
|
11
|
+
#
|
|
12
|
+
# Options:
|
|
13
|
+
# --owner <owner> GitHub repository owner (required)
|
|
14
|
+
# --repo <repo> GitHub repository name (required)
|
|
15
|
+
# --pr <number> PR number to check (required)
|
|
16
|
+
# --dry-run Suppress PR comments (show what would happen)
|
|
17
|
+
# --max-rounds <n> Max review rounds before escalation (default: 4)
|
|
18
|
+
# --allow-skipped Treat PRs with skip-coderabbit label and no CR review as approved
|
|
19
|
+
# --help Show this help message
|
|
20
|
+
#
|
|
21
|
+
# Exit codes:
|
|
22
|
+
# 0 = approve or wait (no human intervention needed yet)
|
|
23
|
+
# 1 = escalate (human review needed) or error
|
|
24
|
+
# 2 = usage error (missing required args)
|
|
25
|
+
#
|
|
26
|
+
# Dependencies: gh (authenticated), jq
|
|
27
|
+
#
|
|
28
|
+
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
|
|
31
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
+
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
33
|
+
|
|
34
|
+
# Colors
|
|
35
|
+
RED='\033[0;31m'
|
|
36
|
+
GREEN='\033[0;32m'
|
|
37
|
+
YELLOW='\033[1;33m'
|
|
38
|
+
NC='\033[0m'
|
|
39
|
+
|
|
40
|
+
# ============================================================
|
|
41
|
+
# USAGE
|
|
42
|
+
# ============================================================
|
|
43
|
+
|
|
44
|
+
usage() {
|
|
45
|
+
cat <<'EOF'
|
|
46
|
+
Usage: coderabbit-review-gate.sh --owner <owner> --repo <repo> --pr <number> [options]
|
|
47
|
+
|
|
48
|
+
Automated CodeRabbit review cycle gate. Counts review rounds, classifies
|
|
49
|
+
thread severity, auto-resolves outdated threads, and decides whether to
|
|
50
|
+
approve, wait, or escalate.
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
--owner <owner> GitHub repository owner (required)
|
|
54
|
+
--repo <repo> GitHub repository name (required)
|
|
55
|
+
--pr <number> PR number to check (required)
|
|
56
|
+
--dry-run Suppress PR comments (show what would happen)
|
|
57
|
+
--max-rounds <n> Max review rounds before escalation (default: 4)
|
|
58
|
+
--allow-skipped Treat PRs with skip-coderabbit label and no CR review as approved
|
|
59
|
+
--help Show this help message
|
|
60
|
+
|
|
61
|
+
Exit codes:
|
|
62
|
+
0 Approve or wait (no human intervention needed yet)
|
|
63
|
+
1 Escalate (human review needed) or error
|
|
64
|
+
2 Usage error (missing required arguments)
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
coderabbit-review-gate.sh --owner myorg --repo myrepo --pr 123
|
|
68
|
+
coderabbit-review-gate.sh --owner myorg --repo myrepo --pr 123 --dry-run
|
|
69
|
+
coderabbit-review-gate.sh --owner myorg --repo myrepo --pr 123 --max-rounds 3
|
|
70
|
+
EOF
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# ============================================================
|
|
74
|
+
# ARGUMENT PARSING
|
|
75
|
+
# ============================================================
|
|
76
|
+
|
|
77
|
+
OWNER=""
|
|
78
|
+
REPO=""
|
|
79
|
+
PR_NUMBER=""
|
|
80
|
+
DRY_RUN=false
|
|
81
|
+
MAX_ROUNDS=4
|
|
82
|
+
ALLOW_SKIPPED=false
|
|
83
|
+
REVIEW_COUNT_TRUSTED=true
|
|
84
|
+
|
|
85
|
+
while [[ $# -gt 0 ]]; do
|
|
86
|
+
case "$1" in
|
|
87
|
+
--owner)
|
|
88
|
+
if [[ $# -lt 2 ]]; then
|
|
89
|
+
echo -e "${RED}ERROR${NC}: --owner requires a value" >&2
|
|
90
|
+
exit 2
|
|
91
|
+
fi
|
|
92
|
+
OWNER="$2"
|
|
93
|
+
shift 2
|
|
94
|
+
;;
|
|
95
|
+
--repo)
|
|
96
|
+
if [[ $# -lt 2 ]]; then
|
|
97
|
+
echo -e "${RED}ERROR${NC}: --repo requires a value" >&2
|
|
98
|
+
exit 2
|
|
99
|
+
fi
|
|
100
|
+
REPO="$2"
|
|
101
|
+
shift 2
|
|
102
|
+
;;
|
|
103
|
+
--pr)
|
|
104
|
+
if [[ $# -lt 2 ]]; then
|
|
105
|
+
echo -e "${RED}ERROR${NC}: --pr requires a value" >&2
|
|
106
|
+
exit 2
|
|
107
|
+
fi
|
|
108
|
+
PR_NUMBER="$2"
|
|
109
|
+
shift 2
|
|
110
|
+
;;
|
|
111
|
+
--dry-run)
|
|
112
|
+
DRY_RUN=true
|
|
113
|
+
shift
|
|
114
|
+
;;
|
|
115
|
+
--allow-skipped)
|
|
116
|
+
ALLOW_SKIPPED=true
|
|
117
|
+
shift
|
|
118
|
+
;;
|
|
119
|
+
--max-rounds)
|
|
120
|
+
if [[ $# -lt 2 ]]; then
|
|
121
|
+
echo -e "${RED}ERROR${NC}: --max-rounds requires a value" >&2
|
|
122
|
+
exit 2
|
|
123
|
+
fi
|
|
124
|
+
MAX_ROUNDS="$2"
|
|
125
|
+
shift 2
|
|
126
|
+
;;
|
|
127
|
+
--help)
|
|
128
|
+
usage
|
|
129
|
+
exit 0
|
|
130
|
+
;;
|
|
131
|
+
-*)
|
|
132
|
+
echo -e "${RED}ERROR${NC}: Unknown option: $1" >&2
|
|
133
|
+
usage >&2
|
|
134
|
+
exit 2
|
|
135
|
+
;;
|
|
136
|
+
*)
|
|
137
|
+
echo -e "${RED}ERROR${NC}: Unexpected argument: $1" >&2
|
|
138
|
+
usage >&2
|
|
139
|
+
exit 2
|
|
140
|
+
;;
|
|
141
|
+
esac
|
|
142
|
+
done
|
|
143
|
+
|
|
144
|
+
# ============================================================
|
|
145
|
+
# VALIDATION
|
|
146
|
+
# ============================================================
|
|
147
|
+
|
|
148
|
+
# Validate GitHub owner/repo name format
|
|
149
|
+
validate_github_name() {
|
|
150
|
+
local name="$1"
|
|
151
|
+
local label="$2"
|
|
152
|
+
if ! [[ "$name" =~ ^[a-zA-Z0-9._-]+$ ]]; then
|
|
153
|
+
echo -e "${RED}ERROR${NC}: Invalid $label: $name (must match ^[a-zA-Z0-9._-]+$)" >&2
|
|
154
|
+
exit 2
|
|
155
|
+
fi
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Validate required arguments
|
|
159
|
+
if [[ -z "$OWNER" ]]; then
|
|
160
|
+
echo -e "${RED}ERROR${NC}: --owner is required" >&2
|
|
161
|
+
usage >&2
|
|
162
|
+
exit 2
|
|
163
|
+
fi
|
|
164
|
+
validate_github_name "$OWNER" "owner"
|
|
165
|
+
|
|
166
|
+
if [[ -z "$REPO" ]]; then
|
|
167
|
+
echo -e "${RED}ERROR${NC}: --repo is required" >&2
|
|
168
|
+
usage >&2
|
|
169
|
+
exit 2
|
|
170
|
+
fi
|
|
171
|
+
validate_github_name "$REPO" "repo"
|
|
172
|
+
|
|
173
|
+
if [[ -z "$PR_NUMBER" ]]; then
|
|
174
|
+
echo -e "${RED}ERROR${NC}: --pr is required" >&2
|
|
175
|
+
usage >&2
|
|
176
|
+
exit 2
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
|
|
180
|
+
echo -e "${RED}ERROR${NC}: PR number must be numeric: $PR_NUMBER" >&2
|
|
181
|
+
exit 2
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# ============================================================
|
|
185
|
+
# DEPENDENCY CHECKS
|
|
186
|
+
# ============================================================
|
|
187
|
+
|
|
188
|
+
if ! command -v gh &> /dev/null; then
|
|
189
|
+
echo -e "${RED}ERROR${NC}: gh CLI is not installed" >&2
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
if ! command -v jq &> /dev/null; then
|
|
194
|
+
echo -e "${RED}ERROR${NC}: jq is not installed" >&2
|
|
195
|
+
exit 1
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# ============================================================
|
|
199
|
+
# GRAPHQL WRAPPER
|
|
200
|
+
# ============================================================
|
|
201
|
+
|
|
202
|
+
# Wrapper for gh api graphql calls — facilitates mock injection via PATH
|
|
203
|
+
gh_graphql() {
|
|
204
|
+
gh api graphql "$@"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# ============================================================
|
|
208
|
+
# REVIEW ROUND COUNTING
|
|
209
|
+
# ============================================================
|
|
210
|
+
|
|
211
|
+
count_review_rounds() {
|
|
212
|
+
local reviews_json
|
|
213
|
+
reviews_json=$(gh_graphql -f query='
|
|
214
|
+
query($owner: String!, $repo: String!, $pr: Int!) {
|
|
215
|
+
repository(owner: $owner, name: $repo) {
|
|
216
|
+
pullRequest(number: $pr) {
|
|
217
|
+
reviews(first: 100) {
|
|
218
|
+
pageInfo { hasNextPage endCursor }
|
|
219
|
+
nodes {
|
|
220
|
+
author { login }
|
|
221
|
+
submittedAt
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
' -f "owner=$OWNER" -f "repo=$REPO" -F "pr=$PR_NUMBER") || {
|
|
228
|
+
echo -e "${YELLOW}WARNING${NC}: Failed to query reviews" >&2
|
|
229
|
+
REVIEW_COUNT_TRUSTED=false
|
|
230
|
+
echo "0"
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# Validate response structure
|
|
235
|
+
if ! echo "$reviews_json" | jq -e '.data.repository.pullRequest' > /dev/null 2>&1; then
|
|
236
|
+
echo -e "${YELLOW}WARNING${NC}: Malformed reviews response" >&2
|
|
237
|
+
REVIEW_COUNT_TRUSTED=false
|
|
238
|
+
echo "0"
|
|
239
|
+
return
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# Warn if there are more pages (>100 reviews is rare; pagination not implemented)
|
|
243
|
+
local has_next
|
|
244
|
+
has_next=$(echo "$reviews_json" | jq -r '.data.repository.pullRequest.reviews.pageInfo.hasNextPage' 2>/dev/null || echo "false")
|
|
245
|
+
if [[ "$has_next" == "true" ]]; then
|
|
246
|
+
echo -e "${YELLOW}WARNING${NC}: More than 100 reviews found; count may be incomplete" >&2
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
echo "$reviews_json" | jq '[.data.repository.pullRequest.reviews.nodes[] | select(.author.login == "coderabbitai")] | length' 2>/dev/null || echo "0"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# ============================================================
|
|
253
|
+
# THREAD QUERYING
|
|
254
|
+
# ============================================================
|
|
255
|
+
|
|
256
|
+
query_all_threads() {
|
|
257
|
+
local cursor=""
|
|
258
|
+
local all_nodes="[]"
|
|
259
|
+
local page_json has_next
|
|
260
|
+
|
|
261
|
+
while :; do
|
|
262
|
+
local -a gql_args=(
|
|
263
|
+
-f query='
|
|
264
|
+
query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
|
|
265
|
+
repository(owner: $owner, name: $repo) {
|
|
266
|
+
pullRequest(number: $pr) {
|
|
267
|
+
reviewThreads(first: 100, after: $cursor) {
|
|
268
|
+
pageInfo { hasNextPage endCursor }
|
|
269
|
+
nodes {
|
|
270
|
+
id
|
|
271
|
+
isResolved
|
|
272
|
+
isOutdated
|
|
273
|
+
comments(first: 1) {
|
|
274
|
+
nodes {
|
|
275
|
+
body
|
|
276
|
+
author { login }
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
'
|
|
285
|
+
-f "owner=$OWNER" -f "repo=$REPO" -F "pr=$PR_NUMBER"
|
|
286
|
+
)
|
|
287
|
+
if [[ -n "$cursor" ]]; then
|
|
288
|
+
gql_args+=(-f "cursor=$cursor")
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
page_json=$(gh_graphql "${gql_args[@]}") || {
|
|
292
|
+
echo -e "${YELLOW}WARNING${NC}: Failed to query review threads" >&2
|
|
293
|
+
# Return what we have so far, filtered
|
|
294
|
+
echo "$all_nodes" | jq '[.[] | select(.isResolved == false and .isOutdated == false)]'
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
all_nodes=$(jq -s '.[0] + .[1]' <(echo "$all_nodes") <(echo "$page_json" | jq '.data.repository.pullRequest.reviewThreads.nodes'))
|
|
299
|
+
|
|
300
|
+
has_next=$(echo "$page_json" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')
|
|
301
|
+
if [[ "$has_next" != "true" ]]; then
|
|
302
|
+
break
|
|
303
|
+
fi
|
|
304
|
+
cursor=$(echo "$page_json" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')
|
|
305
|
+
done
|
|
306
|
+
|
|
307
|
+
jq -n --argjson nodes "$all_nodes" '{"data":{"repository":{"pullRequest":{"reviewThreads":{"nodes":$nodes}}}}}'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
get_active_threads() {
|
|
311
|
+
local all_threads_json="$1"
|
|
312
|
+
# Filter: unresolved, non-outdated, CodeRabbit-authored threads only
|
|
313
|
+
echo "$all_threads_json" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and .isOutdated == false and (.comments.nodes[0].author.login == "coderabbitai"))]'
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
# ============================================================
|
|
317
|
+
# OUTDATED THREAD RESOLUTION
|
|
318
|
+
# ============================================================
|
|
319
|
+
|
|
320
|
+
resolve_outdated_threads() {
|
|
321
|
+
local all_threads_json="$1"
|
|
322
|
+
# Find unresolved outdated threads
|
|
323
|
+
local outdated_thread_ids
|
|
324
|
+
outdated_thread_ids=$(echo "$all_threads_json" | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and .isOutdated == true and (.comments.nodes[0].author.login == "coderabbitai")) | .id')
|
|
325
|
+
|
|
326
|
+
if [[ -z "$outdated_thread_ids" ]]; then
|
|
327
|
+
return 0
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
# Resolve each outdated thread
|
|
331
|
+
while IFS= read -r thread_id; do
|
|
332
|
+
if [[ -z "$thread_id" ]]; then
|
|
333
|
+
continue
|
|
334
|
+
fi
|
|
335
|
+
gh_graphql -f query='
|
|
336
|
+
mutation($threadId: ID!) {
|
|
337
|
+
resolveReviewThread(input: { threadId: $threadId }) {
|
|
338
|
+
thread { id isResolved }
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
' -f "threadId=$thread_id" > /dev/null 2>&1 || {
|
|
342
|
+
echo -e "${YELLOW}WARNING${NC}: Failed to resolve outdated thread $thread_id" >&2
|
|
343
|
+
}
|
|
344
|
+
done <<< "$outdated_thread_ids"
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# ============================================================
|
|
348
|
+
# SEVERITY CLASSIFICATION
|
|
349
|
+
# ============================================================
|
|
350
|
+
|
|
351
|
+
has_blocking_findings() {
|
|
352
|
+
local threads_json="$1"
|
|
353
|
+
# Check if any thread's first comment body contains critical (red circle) or major (orange circle) severity markers
|
|
354
|
+
# Use printf to generate actual emoji bytes for the jq regex
|
|
355
|
+
local red_circle orange_circle
|
|
356
|
+
red_circle=$(printf '\xf0\x9f\x94\xb4') # U+1F534
|
|
357
|
+
orange_circle=$(printf '\xf0\x9f\x9f\xa0') # U+1F7E0
|
|
358
|
+
local blocker_count
|
|
359
|
+
blocker_count=$(echo "$threads_json" | jq --arg rc "$red_circle" --arg oc "$orange_circle" '[.[] | select(.comments.nodes[0] | (.author.login == "coderabbitai") and (.body != null) and (.body | test($rc) or test($oc)))] | length' 2>/dev/null || echo "0")
|
|
360
|
+
|
|
361
|
+
if [[ "$blocker_count" -gt 0 ]]; then
|
|
362
|
+
return 0 # has blockers
|
|
363
|
+
else
|
|
364
|
+
return 1 # no blockers
|
|
365
|
+
fi
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
# ============================================================
|
|
369
|
+
# DECISION LOGIC
|
|
370
|
+
# ============================================================
|
|
371
|
+
|
|
372
|
+
decide_action() {
|
|
373
|
+
local round_count="$1"
|
|
374
|
+
local active_thread_count="$2"
|
|
375
|
+
local has_blockers="$3" # "true" or "false"
|
|
376
|
+
|
|
377
|
+
# Decision matrix:
|
|
378
|
+
# At or past max rounds with blockers → escalate
|
|
379
|
+
# At or past max rounds without blockers → approve
|
|
380
|
+
# Round 1 with no active threads → approve
|
|
381
|
+
# Round 2+ without blockers → approve
|
|
382
|
+
# Otherwise → wait
|
|
383
|
+
if [[ "$round_count" -ge "$MAX_ROUNDS" && "$has_blockers" == "true" ]]; then
|
|
384
|
+
echo "escalate"
|
|
385
|
+
elif [[ "$round_count" -ge "$MAX_ROUNDS" && "$has_blockers" != "true" ]]; then
|
|
386
|
+
echo "approve"
|
|
387
|
+
elif [[ "$round_count" -eq 1 && "$active_thread_count" -eq 0 ]]; then
|
|
388
|
+
echo "approve"
|
|
389
|
+
elif [[ "$round_count" -ge 2 && "$has_blockers" != "true" ]]; then
|
|
390
|
+
echo "approve"
|
|
391
|
+
else
|
|
392
|
+
echo "wait"
|
|
393
|
+
fi
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
# ============================================================
|
|
397
|
+
# PR COMMENTING
|
|
398
|
+
# ============================================================
|
|
399
|
+
|
|
400
|
+
post_action_comment() {
|
|
401
|
+
local action="$1"
|
|
402
|
+
local round_count="$2"
|
|
403
|
+
|
|
404
|
+
case "$action" in
|
|
405
|
+
approve)
|
|
406
|
+
local body
|
|
407
|
+
body=$(cat <<COMMENT
|
|
408
|
+
@coderabbitai approve
|
|
409
|
+
|
|
410
|
+
Automated review gate: Round ${round_count}, no blocking findings. Requesting approval.
|
|
411
|
+
COMMENT
|
|
412
|
+
)
|
|
413
|
+
if ! gh api "repos/$OWNER/$REPO/issues/$PR_NUMBER/comments" -f body="$body" > /dev/null 2>&1; then
|
|
414
|
+
echo -e "${YELLOW}WARNING${NC}: Failed to post approve comment" >&2
|
|
415
|
+
fi
|
|
416
|
+
;;
|
|
417
|
+
escalate)
|
|
418
|
+
local body
|
|
419
|
+
body=$(cat <<COMMENT
|
|
420
|
+
⚠️ **Human Review Needed**
|
|
421
|
+
|
|
422
|
+
CodeRabbit review gate reached round ${round_count} cap with unresolved critical/major findings.
|
|
423
|
+
Please review the outstanding threads and resolve manually.
|
|
424
|
+
COMMENT
|
|
425
|
+
)
|
|
426
|
+
if ! gh api "repos/$OWNER/$REPO/issues/$PR_NUMBER/comments" -f body="$body" > /dev/null 2>&1; then
|
|
427
|
+
echo -e "${YELLOW}WARNING${NC}: Failed to post escalate comment" >&2
|
|
428
|
+
fi
|
|
429
|
+
;;
|
|
430
|
+
wait)
|
|
431
|
+
# No comment needed
|
|
432
|
+
;;
|
|
433
|
+
esac
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
# ============================================================
|
|
437
|
+
# SKIP-LABEL CHECK
|
|
438
|
+
# ============================================================
|
|
439
|
+
|
|
440
|
+
# Check if PR has the skip-coderabbit label
|
|
441
|
+
has_skip_label() {
|
|
442
|
+
local labels_json
|
|
443
|
+
labels_json=$(gh api "repos/$OWNER/$REPO/issues/$PR_NUMBER/labels" 2>/dev/null) || {
|
|
444
|
+
echo -e "${YELLOW}WARNING${NC}: Failed to query PR labels" >&2
|
|
445
|
+
return 1
|
|
446
|
+
}
|
|
447
|
+
echo "$labels_json" | jq -e '[.[] | select(.name == "skip-coderabbit")] | length > 0' > /dev/null 2>&1
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
# ============================================================
|
|
451
|
+
# MAIN
|
|
452
|
+
# ============================================================
|
|
453
|
+
|
|
454
|
+
# 1. Count review rounds
|
|
455
|
+
ROUND_COUNT=$(count_review_rounds)
|
|
456
|
+
|
|
457
|
+
# 1a. Check for --allow-skipped short-circuit
|
|
458
|
+
if [[ "$ALLOW_SKIPPED" == true && "$ROUND_COUNT" -eq 0 && "$REVIEW_COUNT_TRUSTED" == true ]]; then
|
|
459
|
+
if has_skip_label; then
|
|
460
|
+
cat <<SUMMARY
|
|
461
|
+
## CodeRabbit Review Gate
|
|
462
|
+
|
|
463
|
+
- **PR:** ${OWNER}/${REPO}#${PR_NUMBER}
|
|
464
|
+
- **Round:** 0
|
|
465
|
+
- **Active Threads:** 0
|
|
466
|
+
- **Blocking Findings:** false
|
|
467
|
+
- **Action:** approve
|
|
468
|
+
- **Skipped:** true (skip-coderabbit label, no CodeRabbit review)
|
|
469
|
+
SUMMARY
|
|
470
|
+
exit 0
|
|
471
|
+
fi
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
# 2. Query all review threads
|
|
475
|
+
ALL_THREADS_JSON=$(query_all_threads)
|
|
476
|
+
|
|
477
|
+
# 3. Resolve outdated threads
|
|
478
|
+
if [[ "$DRY_RUN" == false ]]; then
|
|
479
|
+
resolve_outdated_threads "$ALL_THREADS_JSON"
|
|
480
|
+
else
|
|
481
|
+
echo -e "${YELLOW}DRY-RUN${NC}: Skipping auto-resolve of outdated threads" >&2
|
|
482
|
+
fi
|
|
483
|
+
|
|
484
|
+
# 4. Get active review threads (unresolved, non-outdated)
|
|
485
|
+
ACTIVE_THREADS_JSON=$(get_active_threads "$ALL_THREADS_JSON")
|
|
486
|
+
ACTIVE_THREAD_COUNT=$(echo "$ACTIVE_THREADS_JSON" | jq 'length')
|
|
487
|
+
|
|
488
|
+
# 5. Classify severity
|
|
489
|
+
HAS_BLOCKERS="false"
|
|
490
|
+
if has_blocking_findings "$ACTIVE_THREADS_JSON"; then
|
|
491
|
+
HAS_BLOCKERS="true"
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
# 6. Decide action
|
|
495
|
+
ACTION=$(decide_action "$ROUND_COUNT" "$ACTIVE_THREAD_COUNT" "$HAS_BLOCKERS")
|
|
496
|
+
|
|
497
|
+
# 7. Post comment (unless dry-run)
|
|
498
|
+
if [[ "$DRY_RUN" == false ]]; then
|
|
499
|
+
post_action_comment "$ACTION" "$ROUND_COUNT"
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
# 8. Output structured summary
|
|
503
|
+
cat <<SUMMARY
|
|
504
|
+
## CodeRabbit Review Gate
|
|
505
|
+
|
|
506
|
+
- **PR:** ${OWNER}/${REPO}#${PR_NUMBER}
|
|
507
|
+
- **Round:** ${ROUND_COUNT}
|
|
508
|
+
- **Active Threads:** ${ACTIVE_THREAD_COUNT}
|
|
509
|
+
- **Blocking Findings:** ${HAS_BLOCKERS}
|
|
510
|
+
- **Action:** ${ACTION}
|
|
511
|
+
SUMMARY
|
|
512
|
+
|
|
513
|
+
# 9. Exit code based on action
|
|
514
|
+
case "$ACTION" in
|
|
515
|
+
approve|wait) exit 0 ;;
|
|
516
|
+
escalate) exit 1 ;;
|
|
517
|
+
*) exit 1 ;;
|
|
518
|
+
esac
|