@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.
Files changed (97) hide show
  1. package/.claude-plugin/plugin.json +7 -2
  2. package/AGENTS.md +2 -2
  3. package/README.md +70 -57
  4. package/agents/.gitkeep +0 -0
  5. package/agents/fixer.md +62 -0
  6. package/agents/implementer.md +68 -0
  7. package/agents/reviewer.md +50 -0
  8. package/commands/ideate.md +1 -1
  9. package/commands/plan.md +1 -1
  10. package/commands/review.md +58 -0
  11. package/dist/exarchos.js +583 -146
  12. package/hooks/hooks.json +8 -0
  13. package/package.json +12 -4
  14. package/scripts/sync-versions.sh +31 -22
  15. package/skills/brainstorming/SKILL.md +16 -0
  16. package/skills/brainstorming/references/design-template.md +9 -0
  17. package/skills/debug/SKILL.md +41 -0
  18. package/skills/debug/references/hotfix-track.md +12 -49
  19. package/skills/debug/references/investigation-checklist.md +3 -3
  20. package/skills/debug/references/thorough-track.md +12 -42
  21. package/skills/delegation/SKILL.md +104 -37
  22. package/skills/delegation/references/agent-teams-saga.md +4 -1
  23. package/skills/delegation/references/fix-mode.md +46 -6
  24. package/skills/delegation/references/implementer-prompt.md +29 -1
  25. package/skills/delegation/references/parallel-strategy.md +1 -1
  26. package/skills/delegation/references/state-management.md +33 -0
  27. package/skills/delegation/references/workflow-steps.md +6 -10
  28. package/skills/delegation/references/worktree-enforcement.md +13 -7
  29. package/skills/git-worktrees/SKILL.md +6 -9
  30. package/skills/implementation-planning/SKILL.md +32 -27
  31. package/skills/implementation-planning/references/task-template.md +20 -0
  32. package/skills/implementation-planning/references/testing-strategy-guide.md +22 -1
  33. package/skills/implementation-planning/references/worked-example.md +2 -2
  34. package/skills/quality-review/SKILL.md +103 -10
  35. package/skills/quality-review/references/auto-transition.md +1 -1
  36. package/skills/quality-review/references/axiom-integration.md +135 -0
  37. package/skills/refactor/SKILL.md +32 -39
  38. package/skills/refactor/phases/polish-implement.md +2 -2
  39. package/skills/refactor/phases/polish-validate.md +1 -1
  40. package/skills/refactor/references/doc-update-checklist.md +2 -3
  41. package/skills/refactor/references/explore-checklist.md +4 -6
  42. package/skills/refactor/references/overhaul-track.md +20 -50
  43. package/skills/refactor/references/polish-track.md +18 -46
  44. package/skills/shared/prompts/context-reading.md +7 -7
  45. package/skills/shared/references/mcp-tool-guidance.md +14 -1
  46. package/skills/shared/references/tdd.md +17 -0
  47. package/skills/shepherd/SKILL.md +38 -10
  48. package/skills/shepherd/references/fix-strategies.md +1 -2
  49. package/skills/shepherd/references/shepherd-event-schemas.md +56 -0
  50. package/skills/spec-review/SKILL.md +46 -10
  51. package/skills/spec-review/references/worked-example.md +1 -1
  52. package/skills/synthesis/SKILL.md +32 -10
  53. package/skills/synthesis/references/github-native-stacking.md +3 -4
  54. package/skills/synthesis/references/synthesis-steps.md +11 -13
  55. package/skills/workflow-state/SKILL.md +11 -3
  56. package/skills/workflow-state/references/mcp-tool-reference.md +5 -5
  57. package/skills/workflow-state/references/phase-transitions.md +6 -3
  58. package/.claude-plugin/marketplace.json +0 -34
  59. package/scripts/assess-refactor-scope.sh +0 -239
  60. package/scripts/check-coderabbit.sh +0 -288
  61. package/scripts/check-context-economy.sh +0 -405
  62. package/scripts/check-coverage-thresholds.sh +0 -194
  63. package/scripts/check-operational-resilience.sh +0 -306
  64. package/scripts/check-polish-scope.sh +0 -245
  65. package/scripts/check-post-merge.sh +0 -185
  66. package/scripts/check-pr-comments.sh +0 -127
  67. package/scripts/check-task-decomposition.sh +0 -451
  68. package/scripts/check-tdd-compliance.sh +0 -265
  69. package/scripts/check-workflow-determinism.sh +0 -312
  70. package/scripts/debug-review-gate.sh +0 -201
  71. package/scripts/extract-fix-tasks.sh +0 -179
  72. package/scripts/extract-task.sh +0 -67
  73. package/scripts/generate-traceability.sh +0 -209
  74. package/scripts/investigation-timer.sh +0 -171
  75. package/scripts/needs-schema-sync.sh +0 -174
  76. package/scripts/new-project.sh +0 -103
  77. package/scripts/post-delegation-check.sh +0 -317
  78. package/scripts/pre-synthesis-check.sh +0 -475
  79. package/scripts/reconcile-state.sh +0 -346
  80. package/scripts/review-diff.sh +0 -63
  81. package/scripts/review-verdict.sh +0 -169
  82. package/scripts/security-scan.sh +0 -248
  83. package/scripts/select-debug-track.sh +0 -186
  84. package/scripts/setup-worktree.sh +0 -323
  85. package/scripts/spec-coverage-check.sh +0 -230
  86. package/scripts/static-analysis-gate.sh +0 -261
  87. package/scripts/validate-companion.sh +0 -161
  88. package/scripts/validate-pr-body.sh +0 -158
  89. package/scripts/validate-pr-stack.sh +0 -146
  90. package/scripts/verify-delegation-saga.sh +0 -240
  91. package/scripts/verify-doc-links.sh +0 -211
  92. package/scripts/verify-ideate-artifacts.sh +0 -296
  93. package/scripts/verify-plan-coverage.sh +0 -408
  94. package/scripts/verify-provenance-chain.sh +0 -310
  95. package/scripts/verify-review-triage.sh +0 -219
  96. package/scripts/verify-worktree-baseline.sh +0 -159
  97. 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