@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.
Files changed (153) hide show
  1. package/.claude-plugin/marketplace.json +22 -0
  2. package/.claude-plugin/plugin.json +17 -0
  3. package/.mcp.json +17 -0
  4. package/AGENTS.md +59 -0
  5. package/CLAUDE.md.template +62 -0
  6. package/LICENSE +202 -0
  7. package/README.md +258 -0
  8. package/commands/autocompact.md +37 -0
  9. package/commands/checkpoint.md +85 -0
  10. package/commands/cleanup.md +99 -0
  11. package/commands/debug.md +145 -0
  12. package/commands/delegate.md +56 -0
  13. package/commands/ideate.md +82 -0
  14. package/commands/plan.md +150 -0
  15. package/commands/refactor.md +139 -0
  16. package/commands/reload.md +37 -0
  17. package/commands/resume.md +130 -0
  18. package/commands/review.md +51 -0
  19. package/commands/sync-schemas.md +74 -0
  20. package/commands/synthesize.md +122 -0
  21. package/commands/tdd.md +58 -0
  22. package/dist/exarchos-cli.js +8828 -0
  23. package/dist/exarchos-mcp.js +50 -0
  24. package/hooks/hooks.json +53 -0
  25. package/package.json +59 -0
  26. package/rules/coding-standards.md +46 -0
  27. package/rules/mcp-tool-guidance.md +26 -0
  28. package/rules/pr-descriptions.md +12 -0
  29. package/rules/rm-safety.md +9 -0
  30. package/rules/skill-path-resolution.md +10 -0
  31. package/rules/tdd.md +41 -0
  32. package/rules/telemetry-awareness.md +9 -0
  33. package/scripts/assess-refactor-scope.sh +239 -0
  34. package/scripts/check-benchmark-regression.sh +229 -0
  35. package/scripts/check-coderabbit.sh +288 -0
  36. package/scripts/check-coverage-thresholds.sh +194 -0
  37. package/scripts/check-polish-scope.sh +245 -0
  38. package/scripts/check-property-tests.sh +167 -0
  39. package/scripts/check-tdd-compliance.sh +265 -0
  40. package/scripts/coderabbit-review-gate.sh +518 -0
  41. package/scripts/debug-review-gate.sh +201 -0
  42. package/scripts/extract-fix-tasks.sh +179 -0
  43. package/scripts/extract-task.sh +67 -0
  44. package/scripts/generate-traceability.sh +209 -0
  45. package/scripts/investigation-timer.sh +171 -0
  46. package/scripts/needs-schema-sync.sh +174 -0
  47. package/scripts/new-project.sh +103 -0
  48. package/scripts/post-delegation-check.sh +317 -0
  49. package/scripts/pre-synthesis-check.sh +440 -0
  50. package/scripts/reconcile-state.sh +346 -0
  51. package/scripts/reconstruct-stack.sh +432 -0
  52. package/scripts/review-diff.sh +63 -0
  53. package/scripts/review-verdict.sh +169 -0
  54. package/scripts/security-scan.sh +248 -0
  55. package/scripts/select-debug-track.sh +186 -0
  56. package/scripts/setup-worktree.sh +323 -0
  57. package/scripts/spec-coverage-check.sh +230 -0
  58. package/scripts/static-analysis-gate.sh +236 -0
  59. package/scripts/sync-labels.sh +122 -0
  60. package/scripts/validate-companion.sh +161 -0
  61. package/scripts/validate-dotnet-standards.sh +267 -0
  62. package/scripts/validate-installation.sh +101 -0
  63. package/scripts/validate-plugin.sh +223 -0
  64. package/scripts/validate-refactor.sh +234 -0
  65. package/scripts/validate-rm.sh +93 -0
  66. package/scripts/verify-delegation-saga.sh +240 -0
  67. package/scripts/verify-doc-links.sh +211 -0
  68. package/scripts/verify-ideate-artifacts.sh +296 -0
  69. package/scripts/verify-plan-coverage.sh +228 -0
  70. package/scripts/verify-review-triage.sh +219 -0
  71. package/scripts/verify-worktree-baseline.sh +159 -0
  72. package/scripts/verify-worktree.sh +84 -0
  73. package/settings.json +47 -0
  74. package/skills/brainstorming/SKILL.md +127 -0
  75. package/skills/brainstorming/references/design-template.md +65 -0
  76. package/skills/cleanup/SKILL.md +147 -0
  77. package/skills/cleanup/references/merge-verification.md +40 -0
  78. package/skills/debug/SKILL.md +204 -0
  79. package/skills/debug/references/hotfix-track.md +134 -0
  80. package/skills/debug/references/investigation-checklist.md +217 -0
  81. package/skills/debug/references/rca-template.md +150 -0
  82. package/skills/debug/references/state-schema.md +294 -0
  83. package/skills/debug/references/thorough-track.md +194 -0
  84. package/skills/debug/references/triage-questions.md +155 -0
  85. package/skills/debug/references/troubleshooting.md +47 -0
  86. package/skills/delegation/SKILL.md +150 -0
  87. package/skills/delegation/references/adaptive-orchestration.md +31 -0
  88. package/skills/delegation/references/agent-teams-saga.md +248 -0
  89. package/skills/delegation/references/fix-mode.md +74 -0
  90. package/skills/delegation/references/fixer-prompt.md +162 -0
  91. package/skills/delegation/references/implementer-prompt.md +322 -0
  92. package/skills/delegation/references/parallel-strategy.md +124 -0
  93. package/skills/delegation/references/pbt-patterns.md +172 -0
  94. package/skills/delegation/references/pr-fixes-mode.md +154 -0
  95. package/skills/delegation/references/state-management.md +51 -0
  96. package/skills/delegation/references/testing-patterns.md +129 -0
  97. package/skills/delegation/references/troubleshooting.md +33 -0
  98. package/skills/delegation/references/workflow-steps.md +127 -0
  99. package/skills/delegation/references/worktree-enforcement.md +64 -0
  100. package/skills/dotnet-standards/SKILL.md +269 -0
  101. package/skills/dotnet-standards/references/csharp-standards.md +120 -0
  102. package/skills/dotnet-standards/templates/.editorconfig +366 -0
  103. package/skills/dotnet-standards/templates/Directory.Build.props +56 -0
  104. package/skills/dotnet-standards/templates/Directory.Packages.props +69 -0
  105. package/skills/dotnet-standards/templates/global.json +6 -0
  106. package/skills/dotnet-standards/templates/nuget.config +9 -0
  107. package/skills/dotnet-standards/templates/stylecop.json +37 -0
  108. package/skills/git-worktrees/SKILL.md +255 -0
  109. package/skills/implementation-planning/SKILL.md +233 -0
  110. package/skills/implementation-planning/references/plan-document-template.md +42 -0
  111. package/skills/implementation-planning/references/spec-tracing-guide.md +51 -0
  112. package/skills/implementation-planning/references/task-template.md +43 -0
  113. package/skills/implementation-planning/references/testing-strategy-guide.md +88 -0
  114. package/skills/quality-review/SKILL.md +278 -0
  115. package/skills/quality-review/references/code-quality-checklist.md +159 -0
  116. package/skills/quality-review/references/review-report-template.md +65 -0
  117. package/skills/quality-review/references/security-checklist.md +79 -0
  118. package/skills/quality-review/references/typescript-standards.md +24 -0
  119. package/skills/refactor/COMMAND.md +67 -0
  120. package/skills/refactor/SKILL.md +198 -0
  121. package/skills/refactor/phases/auto-chain.md +262 -0
  122. package/skills/refactor/phases/brief.md +176 -0
  123. package/skills/refactor/phases/explore.md +132 -0
  124. package/skills/refactor/phases/overhaul-delegate.md +136 -0
  125. package/skills/refactor/phases/overhaul-plan.md +312 -0
  126. package/skills/refactor/phases/overhaul-review.md +304 -0
  127. package/skills/refactor/phases/polish-implement.md +349 -0
  128. package/skills/refactor/phases/polish-validate.md +218 -0
  129. package/skills/refactor/phases/update-docs.md +234 -0
  130. package/skills/refactor/references/brief-template.md +81 -0
  131. package/skills/refactor/references/doc-update-checklist.md +110 -0
  132. package/skills/refactor/references/explore-checklist.md +73 -0
  133. package/skills/refactor/references/overhaul-track.md +215 -0
  134. package/skills/refactor/references/polish-track.md +170 -0
  135. package/skills/shared/prompts/context-reading.md +58 -0
  136. package/skills/shared/prompts/report-format.md +54 -0
  137. package/skills/shared/prompts/tdd-requirements.md +39 -0
  138. package/skills/shepherd/SKILL.md +264 -0
  139. package/skills/shepherd/references/assess-checklist.md +124 -0
  140. package/skills/shepherd/references/fix-strategies.md +191 -0
  141. package/skills/spec-review/SKILL.md +229 -0
  142. package/skills/spec-review/references/review-checklist.md +60 -0
  143. package/skills/sync-schemas/SKILL.md +114 -0
  144. package/skills/sync-schemas/references/configuration.md +73 -0
  145. package/skills/synthesis/SKILL.md +129 -0
  146. package/skills/synthesis/references/pr-descriptions.md +87 -0
  147. package/skills/synthesis/references/synthesis-steps.md +109 -0
  148. package/skills/synthesis/references/troubleshooting.md +115 -0
  149. package/skills/validate-all-skills.sh +57 -0
  150. package/skills/validate-frontmatter.sh +237 -0
  151. package/skills/workflow-state/SKILL.md +210 -0
  152. package/skills/workflow-state/references/mcp-tool-reference.md +111 -0
  153. 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