@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,229 @@
1
+ #!/usr/bin/env bash
2
+ # Check Benchmark Regression
3
+ # Compare benchmark results against stored baselines and detect performance regressions.
4
+ #
5
+ # Usage: check-benchmark-regression.sh --results <path> --baselines <path> [--threshold 10]
6
+ #
7
+ # Exit codes:
8
+ # 0 = all benchmarks within threshold (or improved)
9
+ # 1 = regression detected (measured exceeds baseline by more than threshold)
10
+ # 2 = usage error (missing required args, missing file)
11
+
12
+ set -euo pipefail
13
+
14
+ # ============================================================
15
+ # ARGUMENT PARSING
16
+ # ============================================================
17
+
18
+ RESULTS_FILE=""
19
+ BASELINES_FILE=""
20
+ THRESHOLD=10
21
+
22
+ usage() {
23
+ cat << 'USAGE'
24
+ Usage: check-benchmark-regression.sh --results <path> --baselines <path> [--threshold 10]
25
+
26
+ Required:
27
+ --results <path> Path to benchmark results JSON file
28
+ --baselines <path> Path to baselines JSON file
29
+
30
+ Optional:
31
+ --threshold <percent> Regression threshold percentage (default: 10)
32
+ --help Show this help message
33
+
34
+ Exit codes:
35
+ 0 All benchmarks within threshold (or improved)
36
+ 1 Regression detected
37
+ 2 Usage error (missing required args, missing file)
38
+ USAGE
39
+ }
40
+
41
+ while [[ $# -gt 0 ]]; do
42
+ case "$1" in
43
+ --results)
44
+ if [[ -z "${2:-}" ]]; then
45
+ echo "Error: --results requires a path argument" >&2
46
+ exit 2
47
+ fi
48
+ RESULTS_FILE="$2"
49
+ shift 2
50
+ ;;
51
+ --baselines)
52
+ if [[ -z "${2:-}" ]]; then
53
+ echo "Error: --baselines requires a path argument" >&2
54
+ exit 2
55
+ fi
56
+ BASELINES_FILE="$2"
57
+ shift 2
58
+ ;;
59
+ --threshold)
60
+ if [[ -z "${2:-}" ]]; then
61
+ echo "Error: --threshold requires a number argument" >&2
62
+ exit 2
63
+ fi
64
+ THRESHOLD="$2"
65
+ shift 2
66
+ ;;
67
+ --help)
68
+ usage
69
+ exit 0
70
+ ;;
71
+ *)
72
+ echo "Error: Unknown argument '$1'" >&2
73
+ usage >&2
74
+ exit 2
75
+ ;;
76
+ esac
77
+ done
78
+
79
+ if [[ -z "$RESULTS_FILE" || -z "$BASELINES_FILE" ]]; then
80
+ echo "Error: --results and --baselines are required" >&2
81
+ usage >&2
82
+ exit 2
83
+ fi
84
+
85
+ if [[ ! -f "$RESULTS_FILE" ]]; then
86
+ echo "Error: Results file not found: $RESULTS_FILE" >&2
87
+ exit 2
88
+ fi
89
+
90
+ if [[ ! -f "$BASELINES_FILE" ]]; then
91
+ echo "Error: Baselines file not found: $BASELINES_FILE" >&2
92
+ exit 2
93
+ fi
94
+
95
+ # ============================================================
96
+ # DEPENDENCY CHECK
97
+ # ============================================================
98
+
99
+ if ! command -v jq &>/dev/null; then
100
+ echo "Error: jq is required but not installed" >&2
101
+ exit 2
102
+ fi
103
+
104
+ # ============================================================
105
+ # VALIDATE JSON
106
+ # ============================================================
107
+
108
+ if ! jq empty "$RESULTS_FILE" 2>/dev/null; then
109
+ echo "Error: Invalid JSON in results file: $RESULTS_FILE" >&2
110
+ exit 2
111
+ fi
112
+
113
+ if ! jq empty "$BASELINES_FILE" 2>/dev/null; then
114
+ echo "Error: Invalid JSON in baselines file: $BASELINES_FILE" >&2
115
+ exit 2
116
+ fi
117
+
118
+ # ============================================================
119
+ # COMPARE BENCHMARKS
120
+ # ============================================================
121
+
122
+ CHECK_PASS=0
123
+ CHECK_FAIL=0
124
+ CHECK_IMPROVED=0
125
+ RESULTS=()
126
+ TABLE_ROWS=()
127
+
128
+ # Get list of operations from results file
129
+ OPERATIONS=$(jq -r 'keys[]' "$RESULTS_FILE")
130
+
131
+ for op in $OPERATIONS; do
132
+ # Get measured value — iterate over all metric keys in the result
133
+ METRICS=$(jq -r --arg op "$op" '.[$op] | keys[]' "$RESULTS_FILE")
134
+
135
+ for metric in $METRICS; do
136
+ MEASURED=$(jq -r --arg op "$op" --arg m "$metric" '.[$op][$m]' "$RESULTS_FILE")
137
+ BASELINE=$(jq -r --arg op "$op" --arg m "$metric" '.baselines[$op][$m] // empty' "$BASELINES_FILE")
138
+
139
+ # Skip if no matching baseline
140
+ if [[ -z "$BASELINE" ]]; then
141
+ RESULTS+=("- **SKIP**: \`$op\` ($metric) — no baseline found")
142
+ TABLE_ROWS+=("| $op | $metric | — | $MEASURED | — | SKIP |")
143
+ continue
144
+ fi
145
+
146
+ # Validate BASELINE is numeric
147
+ if ! awk -v val="$BASELINE" 'BEGIN { exit (val+0 == val) ? 0 : 1 }'; then
148
+ RESULTS+=("- **SKIP**: \`$op\` ($metric) — non-numeric baseline: $BASELINE")
149
+ TABLE_ROWS+=("| $op | $metric | $BASELINE | $MEASURED | — | SKIP |")
150
+ continue
151
+ fi
152
+
153
+ # Skip if baseline is zero (cannot compute percentage change)
154
+ if awk -v val="$BASELINE" 'BEGIN { exit (val+0 == 0) ? 0 : 1 }'; then
155
+ RESULTS+=("- **SKIP**: \`$op\` ($metric) — zero baseline")
156
+ TABLE_ROWS+=("| $op | $metric | 0 | $MEASURED | — | SKIP |")
157
+ continue
158
+ fi
159
+
160
+ # Calculate regression percentage using awk (safe variable passing)
161
+ CHANGE_PCT=$(awk -v m="$MEASURED" -v b="$BASELINE" 'BEGIN { printf "%.1f", (m - b) / b * 100 }')
162
+
163
+ # Format change with sign prefix (awk printf already adds - for negatives)
164
+ CHANGE_DISPLAY=$(awk -v m="$MEASURED" -v b="$BASELINE" 'BEGIN { v = (m - b) / b * 100; if (v >= 0) printf "+%.1f", v; else printf "%.1f", v }')
165
+
166
+ # Check if regression exceeds threshold
167
+ IS_REGRESSION=$(awk -v c="$CHANGE_PCT" -v t="$THRESHOLD" 'BEGIN { print (c > t) ? 1 : 0 }')
168
+ IS_IMPROVEMENT=$(awk -v c="$CHANGE_PCT" -v t="$THRESHOLD" 'BEGIN { print (c < -t) ? 1 : 0 }')
169
+
170
+ if [[ "$IS_REGRESSION" -eq 1 ]]; then
171
+ RESULTS+=("- **FAIL**: \`$op\` ($metric) regressed from ${BASELINE} to ${MEASURED} (${CHANGE_DISPLAY}%, threshold ${THRESHOLD}%)")
172
+ TABLE_ROWS+=("| $op | $metric | $BASELINE | $MEASURED | ${CHANGE_DISPLAY}% | FAIL |")
173
+ CHECK_FAIL=$((CHECK_FAIL + 1))
174
+ elif [[ "$IS_IMPROVEMENT" -eq 1 ]]; then
175
+ RESULTS+=("- **INFO**: \`$op\` ($metric) improved from ${BASELINE} to ${MEASURED} (${CHANGE_DISPLAY}%), consider updating baseline")
176
+ TABLE_ROWS+=("| $op | $metric | $BASELINE | $MEASURED | ${CHANGE_DISPLAY}% | IMPROVED |")
177
+ CHECK_IMPROVED=$((CHECK_IMPROVED + 1))
178
+ CHECK_PASS=$((CHECK_PASS + 1))
179
+ else
180
+ RESULTS+=("- **PASS**: \`$op\` ($metric) within bounds (${BASELINE} baseline, ${MEASURED} measured, ${CHANGE_DISPLAY}%)")
181
+ TABLE_ROWS+=("| $op | $metric | $BASELINE | $MEASURED | ${CHANGE_DISPLAY}% | PASS |")
182
+ CHECK_PASS=$((CHECK_PASS + 1))
183
+ fi
184
+ done
185
+ done
186
+
187
+ # ============================================================
188
+ # STRUCTURED OUTPUT
189
+ # ============================================================
190
+
191
+ echo "## Benchmark Regression Report"
192
+ echo ""
193
+ echo "**Results file:** \`$RESULTS_FILE\`"
194
+ echo "**Baselines file:** \`$BASELINES_FILE\`"
195
+ echo "**Threshold:** ${THRESHOLD}%"
196
+ echo ""
197
+
198
+ echo "### Summary Table"
199
+ echo ""
200
+ echo "| Operation | Metric | Baseline | Measured | Change | Status |"
201
+ echo "|-----------|--------|----------|----------|--------|--------|"
202
+ for row in "${TABLE_ROWS[@]}"; do
203
+ echo "$row"
204
+ done
205
+ echo ""
206
+
207
+ echo "### Details"
208
+ echo ""
209
+ for result in "${RESULTS[@]}"; do
210
+ echo "$result"
211
+ done
212
+ echo ""
213
+
214
+ TOTAL=$((CHECK_PASS + CHECK_FAIL))
215
+ echo "---"
216
+ echo ""
217
+
218
+ if [[ $CHECK_IMPROVED -gt 0 ]]; then
219
+ echo "**Note:** $CHECK_IMPROVED metric(s) showed improvement — consider updating baselines."
220
+ echo ""
221
+ fi
222
+
223
+ if [[ $CHECK_FAIL -eq 0 ]]; then
224
+ echo "**Result: PASS** ($CHECK_PASS/$TOTAL metrics within threshold)"
225
+ exit 0
226
+ else
227
+ echo "**Result: FAIL** ($CHECK_FAIL/$TOTAL metrics regressed beyond ${THRESHOLD}% threshold)"
228
+ exit 1
229
+ fi
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # check-coderabbit.sh - Query CodeRabbit review state on GitHub PRs
4
+ #
5
+ # Classifies each PR deterministically:
6
+ # APPROVED → pass
7
+ # CHANGES_REQUESTED → fail
8
+ # PENDING → fail
9
+ # No CodeRabbit review → pass (CodeRabbit not installed or hasn't reviewed)
10
+ #
11
+ # When multiple CodeRabbit reviews exist, the latest by submitted_at wins.
12
+ #
13
+ # Usage:
14
+ # check-coderabbit.sh --owner <owner> --repo <repo> <pr-number> [<pr-number>...]
15
+ # check-coderabbit.sh --help
16
+ #
17
+ # Exit codes:
18
+ # 0 = all PRs pass (APPROVED or no CodeRabbit review)
19
+ # 1 = at least one PR has CHANGES_REQUESTED, PENDING, or API error
20
+ # 2 = usage error (missing required args)
21
+ #
22
+ # Dependencies: gh (authenticated), jq
23
+ #
24
+
25
+ set -euo pipefail
26
+
27
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ REPO_ROOT="$(dirname "$SCRIPT_DIR")"
29
+
30
+ # Colors
31
+ RED='\033[0;31m'
32
+ GREEN='\033[0;32m'
33
+ YELLOW='\033[1;33m'
34
+ NC='\033[0m'
35
+
36
+ # ============================================================
37
+ # USAGE
38
+ # ============================================================
39
+
40
+ usage() {
41
+ cat <<'EOF'
42
+ Usage: check-coderabbit.sh --owner <owner> --repo <repo> <pr-number> [<pr-number>...]
43
+
44
+ Query CodeRabbit review state on GitHub PRs and classify them deterministically.
45
+
46
+ Options:
47
+ --owner <owner> GitHub repository owner (required)
48
+ --repo <repo> GitHub repository name (required)
49
+ --json Output machine-readable JSON instead of markdown table
50
+ --help Show this help message
51
+
52
+ Arguments:
53
+ <pr-number> One or more PR numbers to check
54
+
55
+ Exit codes:
56
+ 0 All PRs pass (APPROVED or no CodeRabbit review)
57
+ 1 At least one PR has CHANGES_REQUESTED, PENDING, or API error
58
+ 2 Usage error (missing required arguments)
59
+
60
+ Examples:
61
+ check-coderabbit.sh --owner myorg --repo myrepo 123
62
+ check-coderabbit.sh --owner myorg --repo myrepo 100 101 102
63
+ check-coderabbit.sh --owner myorg --repo myrepo --json 123 456
64
+ EOF
65
+ }
66
+
67
+ # ============================================================
68
+ # ARGUMENT PARSING
69
+ # ============================================================
70
+
71
+ OWNER=""
72
+ REPO=""
73
+ JSON_OUTPUT=false
74
+ PR_NUMBERS=()
75
+
76
+ while [[ $# -gt 0 ]]; do
77
+ case "$1" in
78
+ --owner)
79
+ if [[ $# -lt 2 ]]; then
80
+ echo -e "${RED}ERROR${NC}: --owner requires a value" >&2
81
+ exit 2
82
+ fi
83
+ OWNER="$2"
84
+ shift 2
85
+ ;;
86
+ --repo)
87
+ if [[ $# -lt 2 ]]; then
88
+ echo -e "${RED}ERROR${NC}: --repo requires a value" >&2
89
+ exit 2
90
+ fi
91
+ REPO="$2"
92
+ shift 2
93
+ ;;
94
+ --json)
95
+ JSON_OUTPUT=true
96
+ shift
97
+ ;;
98
+ --help)
99
+ usage
100
+ exit 0
101
+ ;;
102
+ -*)
103
+ echo -e "${RED}ERROR${NC}: Unknown option: $1" >&2
104
+ usage >&2
105
+ exit 2
106
+ ;;
107
+ *)
108
+ PR_NUMBERS+=("$1")
109
+ shift
110
+ ;;
111
+ esac
112
+ done
113
+
114
+ # Validate GitHub owner/repo name format
115
+ validate_github_name() {
116
+ local name="$1"
117
+ local label="$2"
118
+ if ! [[ "$name" =~ ^[a-zA-Z0-9._-]+$ ]]; then
119
+ echo -e "${RED}ERROR${NC}: Invalid $label: $name (must match ^[a-zA-Z0-9._-]+$)" >&2
120
+ exit 2
121
+ fi
122
+ }
123
+
124
+ # Validate required arguments
125
+ if [[ -z "$OWNER" ]]; then
126
+ echo -e "${RED}ERROR${NC}: --owner is required" >&2
127
+ usage >&2
128
+ exit 2
129
+ fi
130
+ validate_github_name "$OWNER" "owner"
131
+
132
+ if [[ -z "$REPO" ]]; then
133
+ echo -e "${RED}ERROR${NC}: --repo is required" >&2
134
+ usage >&2
135
+ exit 2
136
+ fi
137
+ validate_github_name "$REPO" "repo"
138
+
139
+ if [[ ${#PR_NUMBERS[@]} -eq 0 ]]; then
140
+ echo -e "${RED}ERROR${NC}: At least one PR number is required" >&2
141
+ usage >&2
142
+ exit 2
143
+ fi
144
+
145
+ # ============================================================
146
+ # DEPENDENCY CHECKS
147
+ # ============================================================
148
+
149
+ if ! command -v gh &> /dev/null; then
150
+ echo -e "${RED}ERROR${NC}: gh CLI is not installed" >&2
151
+ exit 1
152
+ fi
153
+
154
+ if ! command -v jq &> /dev/null; then
155
+ echo -e "${RED}ERROR${NC}: jq is not installed" >&2
156
+ exit 1
157
+ fi
158
+
159
+ # ============================================================
160
+ # REVIEW CLASSIFICATION
161
+ # ============================================================
162
+
163
+ # Classify a CodeRabbit review state into pass/fail
164
+ # APPROVED → pass, NONE (no review) → pass, everything else → fail
165
+ classify_state() {
166
+ local state="$1"
167
+ case "$state" in
168
+ APPROVED) echo "pass" ;;
169
+ NONE) echo "pass" ;;
170
+ *) echo "fail" ;;
171
+ esac
172
+ }
173
+
174
+ # Get the latest CodeRabbit review state for a PR
175
+ # Returns: STATE or "NONE" if no CodeRabbit review found
176
+ # Exit code: 0 on success, 1 on API error
177
+ get_coderabbit_state() {
178
+ local owner="$1"
179
+ local repo="$2"
180
+ local pr_number="$3"
181
+
182
+ local reviews_json
183
+ if ! reviews_json=$(gh api --paginate "repos/$owner/$repo/pulls/$pr_number/reviews" 2>&1); then
184
+ echo "API_ERROR: $reviews_json" >&2
185
+ return 1
186
+ fi
187
+
188
+ # Filter to CodeRabbit reviews only
189
+ # Match both official (coderabbitai) and legacy (coderabbit-ai) login variants
190
+ # Sort by submitted_at descending, take the first (latest)
191
+ # Use jq -s to slurp paginated output (multiple JSON arrays) into one
192
+ local latest_state
193
+ latest_state=$(echo "$reviews_json" | jq -s -r '
194
+ add | [.[] | select(
195
+ .user.login == "coderabbitai[bot]"
196
+ or .user.login == "coderabbitai"
197
+ or .user.login == "coderabbit-ai[bot]"
198
+ or .user.login == "coderabbit-ai"
199
+ )]
200
+ | sort_by(.submitted_at)
201
+ | reverse
202
+ | .[0].state // "NONE"
203
+ ')
204
+
205
+ echo "$latest_state"
206
+ }
207
+
208
+ # ============================================================
209
+ # MAIN
210
+ # ============================================================
211
+
212
+ HAS_FAILURE=false
213
+ RESULTS=()
214
+
215
+ for pr in "${PR_NUMBERS[@]}"; do
216
+ # Validate PR number is numeric
217
+ if ! [[ "$pr" =~ ^[0-9]+$ ]]; then
218
+ echo -e "${YELLOW}WARNING${NC}: Skipping invalid PR number: $pr" >&2
219
+ RESULTS+=("$pr|INVALID|skip")
220
+ continue
221
+ fi
222
+
223
+ # Query the API
224
+ STATE=$(get_coderabbit_state "$OWNER" "$REPO" "$pr" 2>&1) || {
225
+ echo -e "${RED}ERROR${NC}: Failed to query PR #$pr: $STATE" >&2
226
+ RESULTS+=("$pr|API_ERROR|fail")
227
+ HAS_FAILURE=true
228
+ continue
229
+ }
230
+
231
+ # Handle API_ERROR messages that came through stdout
232
+ if [[ "$STATE" == API_ERROR* ]]; then
233
+ echo -e "${RED}ERROR${NC}: Failed to query PR #$pr: $STATE" >&2
234
+ RESULTS+=("$pr|API_ERROR|fail")
235
+ HAS_FAILURE=true
236
+ continue
237
+ fi
238
+
239
+ # Classify
240
+ VERDICT=$(classify_state "$STATE")
241
+ RESULTS+=("$pr|$STATE|$VERDICT")
242
+
243
+ if [[ "$VERDICT" == "fail" ]]; then
244
+ HAS_FAILURE=true
245
+ fi
246
+ done
247
+
248
+ # ============================================================
249
+ # OUTPUT
250
+ # ============================================================
251
+
252
+ if [[ "$JSON_OUTPUT" == true ]]; then
253
+ # JSON output mode
254
+ echo "["
255
+ for (( i=0; i<${#RESULTS[@]}; i++ )); do
256
+ IFS='|' read -r pr state verdict <<< "${RESULTS[$i]}"
257
+ COMMA=""
258
+ if [[ $i -lt $((${#RESULTS[@]} - 1)) ]]; then
259
+ COMMA=","
260
+ fi
261
+ echo " {\"pr\": \"$pr\", \"state\": \"$state\", \"verdict\": \"$verdict\"}$COMMA"
262
+ done
263
+ echo "]"
264
+ else
265
+ # Markdown table output
266
+ echo "| PR | State | Verdict |"
267
+ echo "|----|-------|---------|"
268
+ for result in "${RESULTS[@]}"; do
269
+ IFS='|' read -r pr state verdict <<< "$result"
270
+ if [[ "$verdict" == "pass" ]]; then
271
+ echo "| #$pr | $state | ${verdict} |"
272
+ elif [[ "$verdict" == "skip" ]]; then
273
+ echo "| $pr | $state | ${verdict} |"
274
+ else
275
+ echo "| #$pr | $state | ${verdict} |"
276
+ fi
277
+ done
278
+ fi
279
+
280
+ # ============================================================
281
+ # EXIT CODE
282
+ # ============================================================
283
+
284
+ if [[ "$HAS_FAILURE" == true ]]; then
285
+ exit 1
286
+ else
287
+ exit 0
288
+ fi
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env bash
2
+ # Check Coverage Thresholds
3
+ # Parse test coverage output (Vitest/Istanbul JSON format) and compare against thresholds.
4
+ #
5
+ # Usage: check-coverage-thresholds.sh --coverage-file <path> [--line-threshold 80] [--branch-threshold 70] [--function-threshold 100]
6
+ #
7
+ # Exit codes:
8
+ # 0 = all thresholds met
9
+ # 1 = below threshold
10
+ # 2 = usage error (missing required args, missing file)
11
+
12
+ set -euo pipefail
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+
16
+ # Colors
17
+ RED='\033[0;31m'
18
+ GREEN='\033[0;32m'
19
+ YELLOW='\033[1;33m'
20
+ NC='\033[0m'
21
+
22
+ # ============================================================
23
+ # ARGUMENT PARSING
24
+ # ============================================================
25
+
26
+ COVERAGE_FILE=""
27
+ LINE_THRESHOLD=80
28
+ BRANCH_THRESHOLD=70
29
+ FUNCTION_THRESHOLD=100
30
+
31
+ usage() {
32
+ cat << 'USAGE'
33
+ Usage: check-coverage-thresholds.sh --coverage-file <path> [--line-threshold 80] [--branch-threshold 70] [--function-threshold 100]
34
+
35
+ Required:
36
+ --coverage-file <path> Path to coverage JSON file (Istanbul/Vitest format)
37
+
38
+ Optional:
39
+ --line-threshold <num> Line coverage threshold percentage (default: 80)
40
+ --branch-threshold <num> Branch coverage threshold percentage (default: 70)
41
+ --function-threshold <num> Function coverage threshold percentage (default: 100)
42
+ --help Show this help message
43
+
44
+ Exit codes:
45
+ 0 All thresholds met
46
+ 1 Below threshold
47
+ 2 Usage error (missing required args, missing file)
48
+ USAGE
49
+ }
50
+
51
+ while [[ $# -gt 0 ]]; do
52
+ case "$1" in
53
+ --coverage-file)
54
+ if [[ -z "${2:-}" ]]; then
55
+ echo "Error: --coverage-file requires a path argument" >&2
56
+ exit 2
57
+ fi
58
+ COVERAGE_FILE="$2"
59
+ shift 2
60
+ ;;
61
+ --line-threshold)
62
+ if [[ -z "${2:-}" ]]; then
63
+ echo "Error: --line-threshold requires a number argument" >&2
64
+ exit 2
65
+ fi
66
+ LINE_THRESHOLD="$2"
67
+ shift 2
68
+ ;;
69
+ --branch-threshold)
70
+ if [[ -z "${2:-}" ]]; then
71
+ echo "Error: --branch-threshold requires a number argument" >&2
72
+ exit 2
73
+ fi
74
+ BRANCH_THRESHOLD="$2"
75
+ shift 2
76
+ ;;
77
+ --function-threshold)
78
+ if [[ -z "${2:-}" ]]; then
79
+ echo "Error: --function-threshold requires a number argument" >&2
80
+ exit 2
81
+ fi
82
+ FUNCTION_THRESHOLD="$2"
83
+ shift 2
84
+ ;;
85
+ --help)
86
+ usage
87
+ exit 0
88
+ ;;
89
+ *)
90
+ echo "Error: Unknown argument '$1'" >&2
91
+ usage >&2
92
+ exit 2
93
+ ;;
94
+ esac
95
+ done
96
+
97
+ if [[ -z "$COVERAGE_FILE" ]]; then
98
+ echo "Error: --coverage-file is required" >&2
99
+ usage >&2
100
+ exit 2
101
+ fi
102
+
103
+ if [[ ! -f "$COVERAGE_FILE" ]]; then
104
+ echo "Error: Coverage file not found: $COVERAGE_FILE" >&2
105
+ exit 2
106
+ fi
107
+
108
+ # ============================================================
109
+ # DEPENDENCY CHECK
110
+ # ============================================================
111
+
112
+ if ! command -v jq &>/dev/null; then
113
+ echo "Error: jq is required but not installed" >&2
114
+ exit 2
115
+ fi
116
+
117
+ # ============================================================
118
+ # PARSE COVERAGE DATA
119
+ # ============================================================
120
+
121
+ # Validate JSON
122
+ if ! jq empty "$COVERAGE_FILE" 2>/dev/null; then
123
+ echo "Error: Invalid JSON in coverage file: $COVERAGE_FILE" >&2
124
+ exit 2
125
+ fi
126
+
127
+ # Extract percentages from Istanbul/Vitest coverage-summary.json format
128
+ LINE_PCT="$(jq '.total.lines.pct // 0' "$COVERAGE_FILE")"
129
+ BRANCH_PCT="$(jq '.total.branches.pct // 0' "$COVERAGE_FILE")"
130
+ FUNCTION_PCT="$(jq '.total.functions.pct // 0' "$COVERAGE_FILE")"
131
+
132
+ # ============================================================
133
+ # CHECK THRESHOLDS
134
+ # ============================================================
135
+
136
+ CHECK_PASS=0
137
+ CHECK_FAIL=0
138
+ RESULTS=()
139
+
140
+ check_threshold() {
141
+ local name="$1"
142
+ local actual="$2"
143
+ local threshold="$3"
144
+
145
+ # Use awk for floating-point comparison
146
+ if awk "BEGIN { exit !($actual >= $threshold) }"; then
147
+ RESULTS+=("- **PASS**: $name — ${actual}% >= ${threshold}%")
148
+ CHECK_PASS=$((CHECK_PASS + 1))
149
+ else
150
+ RESULTS+=("- **FAIL**: $name — ${actual}% < ${threshold}%")
151
+ CHECK_FAIL=$((CHECK_FAIL + 1))
152
+ fi
153
+ }
154
+
155
+ check_threshold "lines" "$LINE_PCT" "$LINE_THRESHOLD"
156
+ check_threshold "branches" "$BRANCH_PCT" "$BRANCH_THRESHOLD"
157
+ check_threshold "functions" "$FUNCTION_PCT" "$FUNCTION_THRESHOLD"
158
+
159
+ # ============================================================
160
+ # STRUCTURED OUTPUT
161
+ # ============================================================
162
+
163
+ echo "## Coverage Threshold Report"
164
+ echo ""
165
+ echo "**Coverage file:** \`$COVERAGE_FILE\`"
166
+ echo ""
167
+
168
+ echo "### Thresholds"
169
+ echo ""
170
+ echo "| Metric | Actual | Threshold | Status |"
171
+ echo "|--------|--------|-----------|--------|"
172
+ echo "| lines | ${LINE_PCT}% | ${LINE_THRESHOLD}% | $(awk "BEGIN { print ($LINE_PCT >= $LINE_THRESHOLD) ? \"PASS\" : \"FAIL\" }") |"
173
+ echo "| branches | ${BRANCH_PCT}% | ${BRANCH_THRESHOLD}% | $(awk "BEGIN { print ($BRANCH_PCT >= $BRANCH_THRESHOLD) ? \"PASS\" : \"FAIL\" }") |"
174
+ echo "| functions | ${FUNCTION_PCT}% | ${FUNCTION_THRESHOLD}% | $(awk "BEGIN { print ($FUNCTION_PCT >= $FUNCTION_THRESHOLD) ? \"PASS\" : \"FAIL\" }") |"
175
+ echo ""
176
+
177
+ echo "### Check Results"
178
+ echo ""
179
+ for result in "${RESULTS[@]}"; do
180
+ echo "$result"
181
+ done
182
+
183
+ echo ""
184
+ TOTAL=$((CHECK_PASS + CHECK_FAIL))
185
+ echo "---"
186
+ echo ""
187
+
188
+ if [[ $CHECK_FAIL -eq 0 ]]; then
189
+ echo "**Result: PASS** ($CHECK_PASS/$TOTAL metrics meet thresholds)"
190
+ exit 0
191
+ else
192
+ echo "**Result: FAIL** ($CHECK_FAIL/$TOTAL metrics below threshold)"
193
+ exit 1
194
+ fi