@lvlup-sw/exarchos 2.4.3 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +7 -2
- package/AGENTS.md +2 -2
- package/README.md +70 -57
- package/agents/.gitkeep +0 -0
- package/agents/fixer.md +62 -0
- package/agents/implementer.md +68 -0
- package/agents/reviewer.md +50 -0
- package/commands/ideate.md +1 -1
- package/commands/plan.md +1 -1
- package/commands/review.md +58 -0
- package/dist/exarchos.js +583 -146
- package/hooks/hooks.json +8 -0
- package/package.json +12 -4
- package/scripts/sync-versions.sh +31 -22
- package/skills/brainstorming/SKILL.md +16 -0
- package/skills/brainstorming/references/design-template.md +9 -0
- package/skills/debug/SKILL.md +41 -0
- package/skills/debug/references/hotfix-track.md +12 -49
- package/skills/debug/references/investigation-checklist.md +3 -3
- package/skills/debug/references/thorough-track.md +12 -42
- package/skills/delegation/SKILL.md +104 -37
- package/skills/delegation/references/agent-teams-saga.md +4 -1
- package/skills/delegation/references/fix-mode.md +46 -6
- package/skills/delegation/references/implementer-prompt.md +29 -1
- package/skills/delegation/references/parallel-strategy.md +1 -1
- package/skills/delegation/references/state-management.md +33 -0
- package/skills/delegation/references/workflow-steps.md +6 -10
- package/skills/delegation/references/worktree-enforcement.md +13 -7
- package/skills/git-worktrees/SKILL.md +6 -9
- package/skills/implementation-planning/SKILL.md +32 -27
- package/skills/implementation-planning/references/task-template.md +20 -0
- package/skills/implementation-planning/references/testing-strategy-guide.md +22 -1
- package/skills/implementation-planning/references/worked-example.md +2 -2
- package/skills/quality-review/SKILL.md +103 -10
- package/skills/quality-review/references/auto-transition.md +1 -1
- package/skills/quality-review/references/axiom-integration.md +135 -0
- package/skills/refactor/SKILL.md +32 -39
- package/skills/refactor/phases/polish-implement.md +2 -2
- package/skills/refactor/phases/polish-validate.md +1 -1
- package/skills/refactor/references/doc-update-checklist.md +2 -3
- package/skills/refactor/references/explore-checklist.md +4 -6
- package/skills/refactor/references/overhaul-track.md +20 -50
- package/skills/refactor/references/polish-track.md +18 -46
- package/skills/shared/prompts/context-reading.md +7 -7
- package/skills/shared/references/mcp-tool-guidance.md +14 -1
- package/skills/shared/references/tdd.md +17 -0
- package/skills/shepherd/SKILL.md +38 -10
- package/skills/shepherd/references/fix-strategies.md +1 -2
- package/skills/shepherd/references/shepherd-event-schemas.md +56 -0
- package/skills/spec-review/SKILL.md +46 -10
- package/skills/spec-review/references/worked-example.md +1 -1
- package/skills/synthesis/SKILL.md +32 -10
- package/skills/synthesis/references/github-native-stacking.md +3 -4
- package/skills/synthesis/references/synthesis-steps.md +11 -13
- package/skills/workflow-state/SKILL.md +11 -3
- package/skills/workflow-state/references/mcp-tool-reference.md +5 -5
- package/skills/workflow-state/references/phase-transitions.md +6 -3
- package/.claude-plugin/marketplace.json +0 -34
- package/scripts/assess-refactor-scope.sh +0 -239
- package/scripts/check-coderabbit.sh +0 -288
- package/scripts/check-context-economy.sh +0 -405
- package/scripts/check-coverage-thresholds.sh +0 -194
- package/scripts/check-operational-resilience.sh +0 -306
- package/scripts/check-polish-scope.sh +0 -245
- package/scripts/check-post-merge.sh +0 -185
- package/scripts/check-pr-comments.sh +0 -127
- package/scripts/check-task-decomposition.sh +0 -451
- package/scripts/check-tdd-compliance.sh +0 -265
- package/scripts/check-workflow-determinism.sh +0 -312
- package/scripts/debug-review-gate.sh +0 -201
- package/scripts/extract-fix-tasks.sh +0 -179
- package/scripts/extract-task.sh +0 -67
- package/scripts/generate-traceability.sh +0 -209
- package/scripts/investigation-timer.sh +0 -171
- package/scripts/needs-schema-sync.sh +0 -174
- package/scripts/new-project.sh +0 -103
- package/scripts/post-delegation-check.sh +0 -317
- package/scripts/pre-synthesis-check.sh +0 -475
- package/scripts/reconcile-state.sh +0 -346
- package/scripts/review-diff.sh +0 -63
- package/scripts/review-verdict.sh +0 -169
- package/scripts/security-scan.sh +0 -248
- package/scripts/select-debug-track.sh +0 -186
- package/scripts/setup-worktree.sh +0 -323
- package/scripts/spec-coverage-check.sh +0 -230
- package/scripts/static-analysis-gate.sh +0 -261
- package/scripts/validate-companion.sh +0 -161
- package/scripts/validate-pr-body.sh +0 -158
- package/scripts/validate-pr-stack.sh +0 -146
- package/scripts/verify-delegation-saga.sh +0 -240
- package/scripts/verify-doc-links.sh +0 -211
- package/scripts/verify-ideate-artifacts.sh +0 -296
- package/scripts/verify-plan-coverage.sh +0 -408
- package/scripts/verify-provenance-chain.sh +0 -310
- package/scripts/verify-review-triage.sh +0 -219
- package/scripts/verify-worktree-baseline.sh +0 -159
- package/scripts/verify-worktree.sh +0 -84
|
@@ -1,288 +0,0 @@
|
|
|
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 [[ -z "${PR_NUMBERS+x}" ]] || [[ ${#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
|
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Check Context Economy (T-20)
|
|
3
|
-
# Scans code changes for context-economy concerns: oversized files, long functions,
|
|
4
|
-
# wide diffs, and large generated files.
|
|
5
|
-
#
|
|
6
|
-
# Usage: check-context-economy.sh --diff-file <path>
|
|
7
|
-
# check-context-economy.sh --repo-root <path> --base-branch <branch>
|
|
8
|
-
#
|
|
9
|
-
# Exit codes:
|
|
10
|
-
# 0 = no findings
|
|
11
|
-
# 1 = findings detected
|
|
12
|
-
# 2 = usage error
|
|
13
|
-
|
|
14
|
-
set -euo pipefail
|
|
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
|
-
DIFF_FILE=""
|
|
27
|
-
REPO_ROOT=""
|
|
28
|
-
BASE_BRANCH=""
|
|
29
|
-
|
|
30
|
-
usage() {
|
|
31
|
-
cat << 'USAGE'
|
|
32
|
-
Usage: check-context-economy.sh --diff-file <path>
|
|
33
|
-
check-context-economy.sh --repo-root <path> --base-branch <branch>
|
|
34
|
-
|
|
35
|
-
Check code changes for complexity that impacts LLM context consumption.
|
|
36
|
-
|
|
37
|
-
Options:
|
|
38
|
-
--diff-file <path> Path to a unified diff file
|
|
39
|
-
--repo-root <path> Repository root (used with --base-branch)
|
|
40
|
-
--base-branch <branch> Base branch to diff against (used with --repo-root)
|
|
41
|
-
--help Show this help message
|
|
42
|
-
|
|
43
|
-
Checks:
|
|
44
|
-
- Source file length (>400 lines) MEDIUM
|
|
45
|
-
- Function/method length (>80 lines) MEDIUM
|
|
46
|
-
- Diff breadth (>30 files changed) MEDIUM
|
|
47
|
-
- Large generated files (>1000 lines) LOW
|
|
48
|
-
|
|
49
|
-
Exit codes:
|
|
50
|
-
0 No context-economy findings
|
|
51
|
-
1 Context-economy findings detected
|
|
52
|
-
2 Usage error
|
|
53
|
-
USAGE
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
while [[ $# -gt 0 ]]; do
|
|
57
|
-
case "$1" in
|
|
58
|
-
--diff-file)
|
|
59
|
-
if [[ -z "${2:-}" ]]; then
|
|
60
|
-
echo "Error: --diff-file requires a path argument" >&2
|
|
61
|
-
exit 2
|
|
62
|
-
fi
|
|
63
|
-
DIFF_FILE="$2"
|
|
64
|
-
shift 2
|
|
65
|
-
;;
|
|
66
|
-
--repo-root)
|
|
67
|
-
if [[ -z "${2:-}" ]]; then
|
|
68
|
-
echo "Error: --repo-root requires a path argument" >&2
|
|
69
|
-
exit 2
|
|
70
|
-
fi
|
|
71
|
-
REPO_ROOT="$2"
|
|
72
|
-
shift 2
|
|
73
|
-
;;
|
|
74
|
-
--base-branch)
|
|
75
|
-
if [[ -z "${2:-}" ]]; then
|
|
76
|
-
echo "Error: --base-branch requires a branch argument" >&2
|
|
77
|
-
exit 2
|
|
78
|
-
fi
|
|
79
|
-
BASE_BRANCH="$2"
|
|
80
|
-
shift 2
|
|
81
|
-
;;
|
|
82
|
-
--help)
|
|
83
|
-
usage
|
|
84
|
-
exit 0
|
|
85
|
-
;;
|
|
86
|
-
*)
|
|
87
|
-
echo "Error: Unknown argument '$1'" >&2
|
|
88
|
-
usage >&2
|
|
89
|
-
exit 2
|
|
90
|
-
;;
|
|
91
|
-
esac
|
|
92
|
-
done
|
|
93
|
-
|
|
94
|
-
# Validate inputs
|
|
95
|
-
if [[ -z "$DIFF_FILE" && ( -z "$REPO_ROOT" || -z "$BASE_BRANCH" ) ]]; then
|
|
96
|
-
echo "Error: Must provide --diff-file or both --repo-root and --base-branch" >&2
|
|
97
|
-
usage >&2
|
|
98
|
-
exit 2
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
# ============================================================
|
|
102
|
-
# DIFF RESOLUTION
|
|
103
|
-
# ============================================================
|
|
104
|
-
|
|
105
|
-
DIFF_CONTENT=""
|
|
106
|
-
CHANGED_FILES=""
|
|
107
|
-
|
|
108
|
-
if [[ -n "$DIFF_FILE" ]]; then
|
|
109
|
-
if [[ ! -f "$DIFF_FILE" ]]; then
|
|
110
|
-
echo "Error: Diff file not found: $DIFF_FILE" >&2
|
|
111
|
-
exit 2
|
|
112
|
-
fi
|
|
113
|
-
DIFF_CONTENT="$(cat "$DIFF_FILE")"
|
|
114
|
-
# Extract file names from the diff
|
|
115
|
-
CHANGED_FILES="$(echo "$DIFF_CONTENT" | grep -oP '^diff --git a/\K[^ ]+' || true)"
|
|
116
|
-
else
|
|
117
|
-
if [[ ! -d "$REPO_ROOT/.git" ]]; then
|
|
118
|
-
echo "Error: Not a git repository: $REPO_ROOT" >&2
|
|
119
|
-
exit 2
|
|
120
|
-
fi
|
|
121
|
-
CHANGED_FILES=$(cd "$REPO_ROOT" && git diff --name-only "$BASE_BRANCH"...HEAD 2>/dev/null) || {
|
|
122
|
-
echo "Error: Failed to generate diff from $BASE_BRANCH" >&2
|
|
123
|
-
exit 2
|
|
124
|
-
}
|
|
125
|
-
DIFF_CONTENT="$(cd "$REPO_ROOT" && git diff "$BASE_BRANCH"...HEAD 2>/dev/null)" || true
|
|
126
|
-
fi
|
|
127
|
-
|
|
128
|
-
# ============================================================
|
|
129
|
-
# CHECKS
|
|
130
|
-
# ============================================================
|
|
131
|
-
|
|
132
|
-
FINDINGS=()
|
|
133
|
-
FINDING_COUNT=0
|
|
134
|
-
CHECKS_PASSED=0
|
|
135
|
-
TOTAL_CHECKS=4
|
|
136
|
-
|
|
137
|
-
add_finding() {
|
|
138
|
-
local severity="$1"
|
|
139
|
-
local message="$2"
|
|
140
|
-
FINDINGS+=("- **${severity}** ${message}")
|
|
141
|
-
FINDING_COUNT=$((FINDING_COUNT + 1))
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
# ----------------------------------------------------------
|
|
145
|
-
# Check 1: Source file length (>400 lines for .ts/.js files)
|
|
146
|
-
# ----------------------------------------------------------
|
|
147
|
-
|
|
148
|
-
check_source_file_length() {
|
|
149
|
-
local has_finding=false
|
|
150
|
-
|
|
151
|
-
if [[ -n "$REPO_ROOT" ]]; then
|
|
152
|
-
# repo-root mode: check actual file sizes
|
|
153
|
-
while IFS= read -r file; do
|
|
154
|
-
[[ -z "$file" ]] && continue
|
|
155
|
-
case "$file" in
|
|
156
|
-
*.ts|*.js) ;;
|
|
157
|
-
*) continue ;;
|
|
158
|
-
esac
|
|
159
|
-
local_path="$REPO_ROOT/$file"
|
|
160
|
-
[[ -f "$local_path" ]] || continue
|
|
161
|
-
|
|
162
|
-
line_count=$(wc -l < "$local_path")
|
|
163
|
-
if [[ "$line_count" -gt 400 ]]; then
|
|
164
|
-
add_finding "MEDIUM" "\`$file\` — Source file exceeds 400 lines ($line_count lines)"
|
|
165
|
-
has_finding=true
|
|
166
|
-
fi
|
|
167
|
-
done <<< "$CHANGED_FILES"
|
|
168
|
-
else
|
|
169
|
-
# diff-file mode: count added lines per .ts/.js file as proxy
|
|
170
|
-
local current_file=""
|
|
171
|
-
local added_lines=0
|
|
172
|
-
|
|
173
|
-
while IFS= read -r line; do
|
|
174
|
-
if [[ "$line" =~ ^diff\ --git\ a/(.+)\ b/ ]]; then
|
|
175
|
-
# Emit finding for previous file if applicable
|
|
176
|
-
if [[ -n "$current_file" && "$added_lines" -gt 400 ]]; then
|
|
177
|
-
case "$current_file" in
|
|
178
|
-
*.ts|*.js)
|
|
179
|
-
add_finding "MEDIUM" "\`$current_file\` — Source file exceeds 400 lines ($added_lines added lines)"
|
|
180
|
-
has_finding=true
|
|
181
|
-
;;
|
|
182
|
-
esac
|
|
183
|
-
fi
|
|
184
|
-
current_file="${BASH_REMATCH[1]}"
|
|
185
|
-
added_lines=0
|
|
186
|
-
fi
|
|
187
|
-
if [[ "$line" =~ ^\+[^+] ]]; then
|
|
188
|
-
added_lines=$((added_lines + 1))
|
|
189
|
-
fi
|
|
190
|
-
done <<< "$DIFF_CONTENT"
|
|
191
|
-
# Check last file
|
|
192
|
-
if [[ -n "$current_file" && "$added_lines" -gt 400 ]]; then
|
|
193
|
-
case "$current_file" in
|
|
194
|
-
*.ts|*.js)
|
|
195
|
-
add_finding "MEDIUM" "\`$current_file\` — Source file exceeds 400 lines ($added_lines added lines)"
|
|
196
|
-
has_finding=true
|
|
197
|
-
;;
|
|
198
|
-
esac
|
|
199
|
-
fi
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
|
-
if [[ "$has_finding" == "false" ]]; then
|
|
203
|
-
CHECKS_PASSED=$((CHECKS_PASSED + 1))
|
|
204
|
-
fi
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
# ----------------------------------------------------------
|
|
208
|
-
# Check 2: Function/method length (>80 lines)
|
|
209
|
-
# ----------------------------------------------------------
|
|
210
|
-
|
|
211
|
-
check_function_length() {
|
|
212
|
-
local has_finding=false
|
|
213
|
-
|
|
214
|
-
# Only works in --repo-root mode (need actual files for brace analysis)
|
|
215
|
-
if [[ -z "$REPO_ROOT" ]]; then
|
|
216
|
-
CHECKS_PASSED=$((CHECKS_PASSED + 1))
|
|
217
|
-
return
|
|
218
|
-
fi
|
|
219
|
-
|
|
220
|
-
while IFS= read -r file; do
|
|
221
|
-
[[ -z "$file" ]] && continue
|
|
222
|
-
case "$file" in
|
|
223
|
-
*.ts|*.js) ;;
|
|
224
|
-
*) continue ;;
|
|
225
|
-
esac
|
|
226
|
-
local_path="$REPO_ROOT/$file"
|
|
227
|
-
[[ -f "$local_path" ]] || continue
|
|
228
|
-
|
|
229
|
-
# Find function/method signatures and measure distance between them
|
|
230
|
-
func_lines=$(grep -nE '^\s*(export\s+)?(async\s+)?function\s|^\s*(public|private|protected|static|async)\s.*\(|^\s*\w+\s*[:=]\s*(async\s+)?\(' "$local_path" 2>/dev/null | cut -d: -f1 || true)
|
|
231
|
-
|
|
232
|
-
if [[ -z "$func_lines" ]]; then
|
|
233
|
-
continue
|
|
234
|
-
fi
|
|
235
|
-
|
|
236
|
-
total_lines=$(wc -l < "$local_path")
|
|
237
|
-
prev_line=0
|
|
238
|
-
|
|
239
|
-
while IFS= read -r func_line; do
|
|
240
|
-
if [[ "$prev_line" -gt 0 ]]; then
|
|
241
|
-
span=$((func_line - prev_line))
|
|
242
|
-
if [[ "$span" -gt 80 ]]; then
|
|
243
|
-
add_finding "MEDIUM" "\`$file\` — Function/method exceeds 80 lines (~${span} lines starting at line $prev_line)"
|
|
244
|
-
has_finding=true
|
|
245
|
-
break # Report once per file
|
|
246
|
-
fi
|
|
247
|
-
fi
|
|
248
|
-
prev_line="$func_line"
|
|
249
|
-
done <<< "$func_lines"
|
|
250
|
-
|
|
251
|
-
# Check last function to end of file
|
|
252
|
-
if [[ "$prev_line" -gt 0 ]]; then
|
|
253
|
-
span=$((total_lines - prev_line))
|
|
254
|
-
if [[ "$span" -gt 80 ]]; then
|
|
255
|
-
# Only add if we haven't already reported for this file
|
|
256
|
-
local already_reported=false
|
|
257
|
-
for f in "${FINDINGS[@]+"${FINDINGS[@]}"}"; do
|
|
258
|
-
if echo "$f" | grep -qF "$file" && echo "$f" | grep -qF "Function/method"; then
|
|
259
|
-
already_reported=true
|
|
260
|
-
break
|
|
261
|
-
fi
|
|
262
|
-
done
|
|
263
|
-
if [[ "$already_reported" == false ]]; then
|
|
264
|
-
add_finding "MEDIUM" "\`$file\` — Function/method exceeds 80 lines (~${span} lines starting at line $prev_line)"
|
|
265
|
-
has_finding=true
|
|
266
|
-
fi
|
|
267
|
-
fi
|
|
268
|
-
fi
|
|
269
|
-
done <<< "$CHANGED_FILES"
|
|
270
|
-
|
|
271
|
-
if [[ "$has_finding" == "false" ]]; then
|
|
272
|
-
CHECKS_PASSED=$((CHECKS_PASSED + 1))
|
|
273
|
-
fi
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
# ----------------------------------------------------------
|
|
277
|
-
# Check 3: Diff breadth (>30 files changed)
|
|
278
|
-
# ----------------------------------------------------------
|
|
279
|
-
|
|
280
|
-
check_diff_breadth() {
|
|
281
|
-
local file_count=0
|
|
282
|
-
|
|
283
|
-
while IFS= read -r file; do
|
|
284
|
-
[[ -z "$file" ]] && continue
|
|
285
|
-
file_count=$((file_count + 1))
|
|
286
|
-
done <<< "$CHANGED_FILES"
|
|
287
|
-
|
|
288
|
-
if [[ "$file_count" -gt 30 ]]; then
|
|
289
|
-
add_finding "MEDIUM" "Diff breadth: $file_count files changed (threshold: 30)"
|
|
290
|
-
else
|
|
291
|
-
CHECKS_PASSED=$((CHECKS_PASSED + 1))
|
|
292
|
-
fi
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
# ----------------------------------------------------------
|
|
296
|
-
# Check 4: Large generated files (>1000 lines with markers)
|
|
297
|
-
# ----------------------------------------------------------
|
|
298
|
-
|
|
299
|
-
check_large_generated_files() {
|
|
300
|
-
local has_finding=false
|
|
301
|
-
|
|
302
|
-
if [[ -n "$REPO_ROOT" ]]; then
|
|
303
|
-
# repo-root mode: check actual files
|
|
304
|
-
while IFS= read -r file; do
|
|
305
|
-
[[ -z "$file" ]] && continue
|
|
306
|
-
local_path="$REPO_ROOT/$file"
|
|
307
|
-
[[ -f "$local_path" ]] || continue
|
|
308
|
-
|
|
309
|
-
line_count=$(wc -l < "$local_path")
|
|
310
|
-
if [[ "$line_count" -gt 1000 ]]; then
|
|
311
|
-
# Check for auto-generated markers in first 20 lines
|
|
312
|
-
if head -20 "$local_path" | grep -qiE '@generated|AUTO-GENERATED|eslint-disable|auto[- ]?generated|do not edit|generated by|this file is generated|machine generated' 2>/dev/null; then
|
|
313
|
-
add_finding "LOW" "\`$file\` — Large generated file ($line_count lines) with auto-generated marker"
|
|
314
|
-
has_finding=true
|
|
315
|
-
fi
|
|
316
|
-
fi
|
|
317
|
-
done <<< "$CHANGED_FILES"
|
|
318
|
-
else
|
|
319
|
-
# diff-file mode: detect via added lines and markers
|
|
320
|
-
local current_file=""
|
|
321
|
-
local added_lines=0
|
|
322
|
-
local has_generated_marker=false
|
|
323
|
-
|
|
324
|
-
while IFS= read -r line; do
|
|
325
|
-
if [[ "$line" =~ ^diff\ --git\ a/(.+)\ b/ ]]; then
|
|
326
|
-
# Check previous file
|
|
327
|
-
if [[ -n "$current_file" ]]; then
|
|
328
|
-
if [[ "$has_generated_marker" == "true" && "$added_lines" -gt 0 ]]; then
|
|
329
|
-
add_finding "LOW" "\`$current_file\` — Generated file detected in diff ($added_lines added lines)"
|
|
330
|
-
has_finding=true
|
|
331
|
-
elif [[ "$added_lines" -gt 1000 ]]; then
|
|
332
|
-
add_finding "LOW" "\`$current_file\` — $added_lines added lines (possible generated file, threshold: 1000)"
|
|
333
|
-
has_finding=true
|
|
334
|
-
fi
|
|
335
|
-
fi
|
|
336
|
-
current_file="${BASH_REMATCH[1]}"
|
|
337
|
-
added_lines=0
|
|
338
|
-
has_generated_marker=false
|
|
339
|
-
continue
|
|
340
|
-
fi
|
|
341
|
-
|
|
342
|
-
if [[ "$line" =~ ^\+[^+] ]]; then
|
|
343
|
-
added_lines=$((added_lines + 1))
|
|
344
|
-
if echo "$line" | grep -qiE '(auto[- ]?generated|do not edit|generated by|this file is generated|machine generated)'; then
|
|
345
|
-
has_generated_marker=true
|
|
346
|
-
fi
|
|
347
|
-
fi
|
|
348
|
-
done <<< "$DIFF_CONTENT"
|
|
349
|
-
|
|
350
|
-
# Check last file
|
|
351
|
-
if [[ -n "$current_file" ]]; then
|
|
352
|
-
if [[ "$has_generated_marker" == "true" && "$added_lines" -gt 0 ]]; then
|
|
353
|
-
add_finding "LOW" "\`$current_file\` — Generated file detected in diff ($added_lines added lines)"
|
|
354
|
-
has_finding=true
|
|
355
|
-
elif [[ "$added_lines" -gt 1000 ]]; then
|
|
356
|
-
add_finding "LOW" "\`$current_file\` — $added_lines added lines (possible generated file, threshold: 1000)"
|
|
357
|
-
has_finding=true
|
|
358
|
-
fi
|
|
359
|
-
fi
|
|
360
|
-
fi
|
|
361
|
-
|
|
362
|
-
if [[ "$has_finding" == "false" ]]; then
|
|
363
|
-
CHECKS_PASSED=$((CHECKS_PASSED + 1))
|
|
364
|
-
fi
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
# Run all checks
|
|
368
|
-
check_source_file_length
|
|
369
|
-
check_function_length
|
|
370
|
-
check_diff_breadth
|
|
371
|
-
check_large_generated_files
|
|
372
|
-
|
|
373
|
-
# ============================================================
|
|
374
|
-
# STRUCTURED OUTPUT
|
|
375
|
-
# ============================================================
|
|
376
|
-
|
|
377
|
-
echo "## Context Economy Report"
|
|
378
|
-
echo ""
|
|
379
|
-
|
|
380
|
-
if [[ -n "$DIFF_FILE" ]]; then
|
|
381
|
-
echo "**Source:** \`$DIFF_FILE\`"
|
|
382
|
-
else
|
|
383
|
-
echo "**Source:** \`$REPO_ROOT\` (diff against \`$BASE_BRANCH\`)"
|
|
384
|
-
fi
|
|
385
|
-
echo ""
|
|
386
|
-
|
|
387
|
-
if [[ $FINDING_COUNT -eq 0 ]]; then
|
|
388
|
-
echo "No context-economy concerns detected."
|
|
389
|
-
echo ""
|
|
390
|
-
echo "---"
|
|
391
|
-
echo ""
|
|
392
|
-
echo "**Result: PASS** ($CHECKS_PASSED/$TOTAL_CHECKS checks passed)"
|
|
393
|
-
exit 0
|
|
394
|
-
else
|
|
395
|
-
echo "**Findings ($FINDING_COUNT):**"
|
|
396
|
-
echo ""
|
|
397
|
-
for finding in "${FINDINGS[@]}"; do
|
|
398
|
-
echo "$finding"
|
|
399
|
-
done
|
|
400
|
-
echo ""
|
|
401
|
-
echo "---"
|
|
402
|
-
echo ""
|
|
403
|
-
echo "**Result: FINDINGS** ($FINDING_COUNT findings detected)"
|
|
404
|
-
exit 1
|
|
405
|
-
fi
|