@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,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
|