@boshu2/vibe-check 2.2.1 → 2.4.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/.agents/plans/2025-12-28-ai-safety-integration-plan.md +326 -0
- package/.agents/plans/2025-12-29-complexity-driver-plan.md +225 -0
- package/.agents/plans/2025-12-29-complexity-drivers-plan.md +253 -0
- package/.agents/research/2025-12-28-ai-platform-security-integration.md +295 -0
- package/.agents/research/2025-12-29-complexity-driver-architecture.md +392 -0
- package/.agents/research/2025-12-29-complexity-drivers.md +227 -0
- package/.beads/README.md +81 -0
- package/.beads/config.yaml +62 -0
- package/.beads/interactions.jsonl +0 -0
- package/.beads/issues.jsonl +21 -0
- package/.beads/metadata.json +4 -0
- package/.gitattributes +3 -0
- package/AGENTS.md +40 -0
- package/CHANGELOG.md +69 -0
- package/CLAUDE.md +75 -0
- package/README.md +71 -0
- package/dist/ai-safety/contract-drift.d.ts +14 -0
- package/dist/ai-safety/contract-drift.d.ts.map +1 -0
- package/dist/ai-safety/contract-drift.js +230 -0
- package/dist/ai-safety/contract-drift.js.map +1 -0
- package/dist/ai-safety/index.d.ts +43 -0
- package/dist/ai-safety/index.d.ts.map +1 -0
- package/dist/ai-safety/index.js +177 -0
- package/dist/ai-safety/index.js.map +1 -0
- package/dist/ai-safety/scope-violation.d.ts +18 -0
- package/dist/ai-safety/scope-violation.d.ts.map +1 -0
- package/dist/ai-safety/scope-violation.js +150 -0
- package/dist/ai-safety/scope-violation.js.map +1 -0
- package/dist/ai-safety/secret-leakage.d.ts +18 -0
- package/dist/ai-safety/secret-leakage.d.ts.map +1 -0
- package/dist/ai-safety/secret-leakage.js +188 -0
- package/dist/ai-safety/secret-leakage.js.map +1 -0
- package/dist/ai-safety/token-spiral.d.ts +17 -0
- package/dist/ai-safety/token-spiral.d.ts.map +1 -0
- package/dist/ai-safety/token-spiral.js +183 -0
- package/dist/ai-safety/token-spiral.js.map +1 -0
- package/dist/ai-safety/types.d.ts +122 -0
- package/dist/ai-safety/types.d.ts.map +1 -0
- package/dist/ai-safety/types.js +32 -0
- package/dist/ai-safety/types.js.map +1 -0
- package/dist/analyzers/complexity.d.ts +92 -0
- package/dist/analyzers/complexity.d.ts.map +1 -0
- package/dist/analyzers/complexity.js +79 -0
- package/dist/analyzers/complexity.js.map +1 -0
- package/dist/analyzers/modularity.d.ts +3 -1
- package/dist/analyzers/modularity.d.ts.map +1 -1
- package/dist/analyzers/modularity.js +32 -6
- package/dist/analyzers/modularity.js.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/driver.d.ts +18 -0
- package/dist/commands/driver.d.ts.map +1 -0
- package/dist/commands/driver.js +58 -0
- package/dist/commands/driver.js.map +1 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/modularity.d.ts +2 -0
- package/dist/commands/modularity.d.ts.map +1 -1
- package/dist/commands/modularity.js +86 -7
- package/dist/commands/modularity.js.map +1 -1
- package/dist/commands/session.d.ts +9 -0
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +42 -0
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +59 -0
- package/dist/commands/watch.js.map +1 -1
- package/drivers/README.md +327 -0
- package/drivers/go.sh +131 -0
- package/drivers/java.sh +137 -0
- package/drivers/javascript.sh +134 -0
- package/drivers/php.sh +132 -0
- package/drivers/python.sh +90 -0
- package/drivers/rust.sh +132 -0
- package/package.json +4 -1
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Complexity Drivers
|
|
2
|
+
|
|
3
|
+
Drivers wrap language-specific complexity tools to produce standard JSON output conforming to vibe-check's `ComplexityReport` schema.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
vibe-check (kernel) ← reads .vibe-check/complexity.json
|
|
9
|
+
↑
|
|
10
|
+
│ Standard JSON schema
|
|
11
|
+
│
|
|
12
|
+
Drivers (python.sh, javascript.sh, etc.)
|
|
13
|
+
↑
|
|
14
|
+
│ Tool-specific format
|
|
15
|
+
│
|
|
16
|
+
Language tools (radon, complexity-report, gocyclo, etc.)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Standard Schema
|
|
20
|
+
|
|
21
|
+
All drivers must output JSON matching this schema:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
{
|
|
25
|
+
tool: string; // "radon", "complexity-report", etc.
|
|
26
|
+
language: string; // "python", "javascript", etc.
|
|
27
|
+
generatedAt: string; // ISO timestamp
|
|
28
|
+
files: {
|
|
29
|
+
[filepath: string]: {
|
|
30
|
+
functions: Array<{
|
|
31
|
+
name: string;
|
|
32
|
+
complexity: number;
|
|
33
|
+
grade: 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
|
|
34
|
+
line: number;
|
|
35
|
+
endLine?: number;
|
|
36
|
+
}>;
|
|
37
|
+
avgComplexity: number;
|
|
38
|
+
maxComplexity: number;
|
|
39
|
+
grade: 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
summary: {
|
|
43
|
+
totalFiles: number;
|
|
44
|
+
totalFunctions: number;
|
|
45
|
+
avgComplexity: number;
|
|
46
|
+
gradeDistribution: Record<'A'|'B'|'C'|'D'|'E'|'F', number>;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Grade Thresholds
|
|
52
|
+
|
|
53
|
+
All drivers normalize complexity scores to these grades:
|
|
54
|
+
|
|
55
|
+
| Grade | Complexity | Meaning |
|
|
56
|
+
|-------|------------|---------|
|
|
57
|
+
| A | 1-5 | Simple, low risk |
|
|
58
|
+
| B | 6-10 | Slightly complex, acceptable |
|
|
59
|
+
| C | 11-20 | Complex, consider refactoring |
|
|
60
|
+
| D | 21-30 | Very complex, refactor |
|
|
61
|
+
| E | 31-40 | Extremely complex, high risk |
|
|
62
|
+
| F | 41+ | Unmaintainable, must refactor |
|
|
63
|
+
|
|
64
|
+
## Available Drivers
|
|
65
|
+
|
|
66
|
+
### Python (`python.sh`)
|
|
67
|
+
|
|
68
|
+
Wraps [radon](https://radon.readthedocs.io/) for Python cyclomatic complexity.
|
|
69
|
+
|
|
70
|
+
**Requirements:**
|
|
71
|
+
```bash
|
|
72
|
+
pip install radon
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Usage:**
|
|
76
|
+
```bash
|
|
77
|
+
./drivers/python.sh ./src
|
|
78
|
+
./drivers/python.sh ./src > .vibe-check/complexity.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Example Output:**
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"tool": "radon",
|
|
85
|
+
"language": "python",
|
|
86
|
+
"files": {
|
|
87
|
+
"src/main.py": {
|
|
88
|
+
"functions": [
|
|
89
|
+
{"name": "process", "complexity": 7, "grade": "B", "line": 10, "endLine": 25}
|
|
90
|
+
],
|
|
91
|
+
"avgComplexity": 7,
|
|
92
|
+
"maxComplexity": 7,
|
|
93
|
+
"grade": "B"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### JavaScript/TypeScript (`javascript.sh`)
|
|
100
|
+
|
|
101
|
+
Wraps [cyclomatic-complexity](https://github.com/pilotpirxie/cyclomatic-complexity) for JavaScript and TypeScript.
|
|
102
|
+
|
|
103
|
+
**Requirements:**
|
|
104
|
+
```bash
|
|
105
|
+
# npx automatically installs on first run
|
|
106
|
+
# No manual installation needed
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Usage:**
|
|
110
|
+
```bash
|
|
111
|
+
./drivers/javascript.sh ./src
|
|
112
|
+
./drivers/javascript.sh ./src > .vibe-check/complexity.json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Example Output:**
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"tool": "cyclomatic-complexity",
|
|
119
|
+
"language": "javascript",
|
|
120
|
+
"files": {
|
|
121
|
+
"src/app.ts": {
|
|
122
|
+
"functions": [
|
|
123
|
+
{"name": "handleRequest", "complexity": 12, "grade": "C", "line": 15, "endLine": null}
|
|
124
|
+
],
|
|
125
|
+
"avgComplexity": 12,
|
|
126
|
+
"maxComplexity": 12,
|
|
127
|
+
"grade": "C"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Go (`go.sh`)
|
|
134
|
+
|
|
135
|
+
Wraps [gocyclo](https://github.com/fzipp/gocyclo) for Go cyclomatic complexity.
|
|
136
|
+
|
|
137
|
+
**Requirements:**
|
|
138
|
+
```bash
|
|
139
|
+
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Usage:**
|
|
143
|
+
```bash
|
|
144
|
+
./drivers/go.sh ./src
|
|
145
|
+
./drivers/go.sh ./cmd > .vibe-check/complexity.json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Example Output:**
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"tool": "gocyclo",
|
|
152
|
+
"language": "go",
|
|
153
|
+
"files": {
|
|
154
|
+
"cmd/main.go": {
|
|
155
|
+
"functions": [
|
|
156
|
+
{"name": "handleRequest", "complexity": 8, "grade": "B", "line": 25, "endLine": null}
|
|
157
|
+
],
|
|
158
|
+
"avgComplexity": 8,
|
|
159
|
+
"maxComplexity": 8,
|
|
160
|
+
"grade": "B"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Rust (`rust.sh`)
|
|
167
|
+
|
|
168
|
+
Wraps [rust-code-analysis](https://mozilla.github.io/rust-code-analysis/) for Rust cyclomatic complexity.
|
|
169
|
+
|
|
170
|
+
**Requirements:**
|
|
171
|
+
```bash
|
|
172
|
+
cargo install rust-code-analysis-cli
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Usage:**
|
|
176
|
+
```bash
|
|
177
|
+
./drivers/rust.sh ./src
|
|
178
|
+
./drivers/rust.sh ./src > .vibe-check/complexity.json
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Example Output:**
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"tool": "rust-code-analysis",
|
|
185
|
+
"language": "rust",
|
|
186
|
+
"files": {
|
|
187
|
+
"src/main.rs": {
|
|
188
|
+
"functions": [
|
|
189
|
+
{"name": "process_data", "complexity": 6, "grade": "B", "line": 12, "endLine": 45}
|
|
190
|
+
],
|
|
191
|
+
"avgComplexity": 6,
|
|
192
|
+
"maxComplexity": 6,
|
|
193
|
+
"grade": "B"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### PHP (`php.sh`)
|
|
200
|
+
|
|
201
|
+
Wraps [PHPMD](https://phpmd.org/) (PHP Mess Detector) for PHP cyclomatic complexity.
|
|
202
|
+
|
|
203
|
+
**Requirements:**
|
|
204
|
+
```bash
|
|
205
|
+
composer global require phpmd/phpmd
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Usage:**
|
|
209
|
+
```bash
|
|
210
|
+
./drivers/php.sh ./src
|
|
211
|
+
./drivers/php.sh ./src > .vibe-check/complexity.json
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Example Output:**
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"tool": "phpmd",
|
|
218
|
+
"language": "php",
|
|
219
|
+
"files": {
|
|
220
|
+
"src/Controller.php": {
|
|
221
|
+
"functions": [
|
|
222
|
+
{"name": "handleRequest", "complexity": 15, "grade": "C", "line": 23, "endLine": 67}
|
|
223
|
+
],
|
|
224
|
+
"avgComplexity": 15,
|
|
225
|
+
"maxComplexity": 15,
|
|
226
|
+
"grade": "C"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Java (`java.sh`)
|
|
233
|
+
|
|
234
|
+
Wraps [PMD](https://pmd.github.io/) for Java cyclomatic complexity.
|
|
235
|
+
|
|
236
|
+
**Requirements:**
|
|
237
|
+
```bash
|
|
238
|
+
# Download PMD from https://pmd.github.io/
|
|
239
|
+
# Requires JRE (Java Runtime Environment)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Usage:**
|
|
243
|
+
```bash
|
|
244
|
+
./drivers/java.sh ./src
|
|
245
|
+
./drivers/java.sh ./src > .vibe-check/complexity.json
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Example Output:**
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"tool": "pmd",
|
|
252
|
+
"language": "java",
|
|
253
|
+
"files": {
|
|
254
|
+
"src/main/java/Service.java": {
|
|
255
|
+
"functions": [
|
|
256
|
+
{"name": "processRequest", "complexity": 18, "grade": "C", "line": 42, "endLine": 89}
|
|
257
|
+
],
|
|
258
|
+
"avgComplexity": 18,
|
|
259
|
+
"maxComplexity": 18,
|
|
260
|
+
"grade": "C"
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Driver Contract
|
|
267
|
+
|
|
268
|
+
Each driver script must:
|
|
269
|
+
|
|
270
|
+
1. **Accept directory path** as first argument (default: current directory)
|
|
271
|
+
2. **Check tool availability** and exit 1 with JSON error if missing
|
|
272
|
+
3. **Output valid JSON** to stdout conforming to schema
|
|
273
|
+
4. **Exit 0 on success**, non-zero on failure
|
|
274
|
+
5. **Handle empty directories** gracefully (empty `files` object)
|
|
275
|
+
|
|
276
|
+
## Error Format
|
|
277
|
+
|
|
278
|
+
On error, write JSON to stderr and exit non-zero:
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{"error": "radon not installed. Run: pip install radon"}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Writing a New Driver
|
|
285
|
+
|
|
286
|
+
Example template:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
#!/bin/bash
|
|
290
|
+
set -euo pipefail
|
|
291
|
+
|
|
292
|
+
TARGET_DIR="${1:-.}"
|
|
293
|
+
|
|
294
|
+
# Check tool is installed
|
|
295
|
+
if ! command -v your-tool &> /dev/null; then
|
|
296
|
+
echo '{"error": "your-tool not installed"}' >&2
|
|
297
|
+
exit 1
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# Run tool and transform to standard schema
|
|
301
|
+
your-tool "$TARGET_DIR" --json | jq '{
|
|
302
|
+
tool: "your-tool",
|
|
303
|
+
language: "your-language",
|
|
304
|
+
generatedAt: (now | todate),
|
|
305
|
+
files: ...,
|
|
306
|
+
summary: ...
|
|
307
|
+
}'
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
See `python.sh` for a complete example with jq transformation.
|
|
311
|
+
|
|
312
|
+
## Integration with vibe-check
|
|
313
|
+
|
|
314
|
+
Drivers are used via CLI:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# Run driver before analysis
|
|
318
|
+
vibe-check --with-complexity python
|
|
319
|
+
|
|
320
|
+
# Use existing complexity data
|
|
321
|
+
vibe-check --complexity-file .vibe-check/complexity.json
|
|
322
|
+
|
|
323
|
+
# Driver-only mode (outputs to stdout)
|
|
324
|
+
vibe-check driver python ./src > complexity.json
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
See main README for usage details.
|
package/drivers/go.sh
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# drivers/go.sh
|
|
3
|
+
# Wraps gocyclo to produce standard complexity JSON
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./drivers/go.sh [directory]
|
|
6
|
+
# directory: Path to Go code to analyze (default: current directory)
|
|
7
|
+
#
|
|
8
|
+
# Output: JSON conforming to ComplexityReport schema
|
|
9
|
+
# Exit codes: 0 = success, 1 = error (gocyclo not installed or other failure)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
TARGET_DIR="${1:-.}"
|
|
14
|
+
|
|
15
|
+
# Check gocyclo is installed
|
|
16
|
+
if ! command -v gocyclo &> /dev/null; then
|
|
17
|
+
echo '{"error": "gocyclo not installed. Run: go install github.com/fzipp/gocyclo/cmd/gocyclo@latest"}' >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Check if target directory exists
|
|
22
|
+
if [ ! -d "$TARGET_DIR" ]; then
|
|
23
|
+
echo "{\"error\": \"Directory not found: $TARGET_DIR\"}" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Check if there are any Go files
|
|
28
|
+
if ! find "$TARGET_DIR" -name "*.go" ! -path "*/vendor/*" -print -quit 2>/dev/null | grep -q .; then
|
|
29
|
+
# No Go files found, output empty result
|
|
30
|
+
echo '{"tool":"gocyclo","language":"go","generatedAt":"'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'","files":{},"summary":{"totalFiles":0,"totalFunctions":0,"avgComplexity":0,"gradeDistribution":{"A":0,"B":0,"C":0,"D":0,"E":0,"F":0}}}'
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Run gocyclo and transform output
|
|
35
|
+
# gocyclo outputs lines like:
|
|
36
|
+
# 8 main complex /tmp/go-test/main.go:16:1
|
|
37
|
+
# <complexity> <package> <function> <file:row:column>
|
|
38
|
+
|
|
39
|
+
gocyclo "$TARGET_DIR" 2>/dev/null | awk -v target_dir="$TARGET_DIR" '
|
|
40
|
+
BEGIN {
|
|
41
|
+
# Initialize
|
|
42
|
+
file_count = 0
|
|
43
|
+
func_count = 0
|
|
44
|
+
total_complexity = 0
|
|
45
|
+
grade_a = 0; grade_b = 0; grade_c = 0; grade_d = 0; grade_e = 0; grade_f = 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function complexity_to_grade(c) {
|
|
49
|
+
if (c <= 5) return "A"
|
|
50
|
+
else if (c <= 10) return "B"
|
|
51
|
+
else if (c <= 20) return "C"
|
|
52
|
+
else if (c <= 30) return "D"
|
|
53
|
+
else if (c <= 40) return "E"
|
|
54
|
+
else return "F"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function increment_grade(g) {
|
|
58
|
+
if (g == "A") grade_a++
|
|
59
|
+
else if (g == "B") grade_b++
|
|
60
|
+
else if (g == "C") grade_c++
|
|
61
|
+
else if (g == "D") grade_d++
|
|
62
|
+
else if (g == "E") grade_e++
|
|
63
|
+
else grade_f++
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
# Parse: complexity package function file:line:col
|
|
68
|
+
complexity = $1
|
|
69
|
+
pkg = $2
|
|
70
|
+
func_name = $3
|
|
71
|
+
file_line = $4
|
|
72
|
+
|
|
73
|
+
# Split file:line:col
|
|
74
|
+
split(file_line, parts, ":")
|
|
75
|
+
file = parts[1]
|
|
76
|
+
line = parts[2]
|
|
77
|
+
|
|
78
|
+
# Track functions per file
|
|
79
|
+
if (!(file in files)) {
|
|
80
|
+
files[file] = 1
|
|
81
|
+
file_count++
|
|
82
|
+
file_functions[file] = ""
|
|
83
|
+
file_complexities[file] = ""
|
|
84
|
+
file_total[file] = 0
|
|
85
|
+
file_max[file] = 0
|
|
86
|
+
file_func_count[file] = 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func_count++
|
|
90
|
+
total_complexity += complexity
|
|
91
|
+
|
|
92
|
+
grade = complexity_to_grade(complexity)
|
|
93
|
+
|
|
94
|
+
# Append function data (JSON format)
|
|
95
|
+
sep = (file_functions[file] == "") ? "" : ","
|
|
96
|
+
file_functions[file] = file_functions[file] sep sprintf("{\"name\":\"%s\",\"complexity\":%d,\"grade\":\"%s\",\"line\":%d,\"endLine\":null}", func_name, complexity, grade, line)
|
|
97
|
+
|
|
98
|
+
# Track file stats
|
|
99
|
+
file_total[file] += complexity
|
|
100
|
+
file_func_count[file]++
|
|
101
|
+
if (complexity > file_max[file]) {
|
|
102
|
+
file_max[file] = complexity
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
END {
|
|
107
|
+
# Output JSON
|
|
108
|
+
printf "{\"tool\":\"gocyclo\",\"language\":\"go\",\"generatedAt\":\""
|
|
109
|
+
# Get current timestamp
|
|
110
|
+
cmd = "date -u +\"%Y-%m-%dT%H:%M:%SZ\""
|
|
111
|
+
cmd | getline timestamp
|
|
112
|
+
close(cmd)
|
|
113
|
+
printf "%s\",\"files\":{", timestamp
|
|
114
|
+
|
|
115
|
+
first_file = 1
|
|
116
|
+
for (file in files) {
|
|
117
|
+
if (!first_file) printf ","
|
|
118
|
+
first_file = 0
|
|
119
|
+
|
|
120
|
+
avg = (file_func_count[file] > 0) ? file_total[file] / file_func_count[file] : 0
|
|
121
|
+
file_grade = complexity_to_grade(avg)
|
|
122
|
+
increment_grade(file_grade)
|
|
123
|
+
|
|
124
|
+
printf "\"%s\":{\"functions\":[%s],\"avgComplexity\":%.2f,\"maxComplexity\":%d,\"grade\":\"%s\"}", file, file_functions[file], avg, file_max[file], file_grade
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
avg_complexity = (func_count > 0) ? total_complexity / func_count : 0
|
|
128
|
+
|
|
129
|
+
printf "},\"summary\":{\"totalFiles\":%d,\"totalFunctions\":%d,\"avgComplexity\":%.2f,\"gradeDistribution\":{\"A\":%d,\"B\":%d,\"C\":%d,\"D\":%d,\"E\":%d,\"F\":%d}}}", file_count, func_count, avg_complexity, grade_a, grade_b, grade_c, grade_d, grade_e, grade_f
|
|
130
|
+
}
|
|
131
|
+
'
|
package/drivers/java.sh
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# drivers/java.sh
|
|
3
|
+
# Wraps PMD to produce standard complexity JSON
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./drivers/java.sh [directory]
|
|
6
|
+
# directory: Path to Java code to analyze (default: current directory)
|
|
7
|
+
#
|
|
8
|
+
# Output: JSON conforming to ComplexityReport schema
|
|
9
|
+
# Exit codes: 0 = success, 1 = error (pmd not installed or other failure)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
TARGET_DIR="${1:-.}"
|
|
14
|
+
|
|
15
|
+
# Check pmd is installed
|
|
16
|
+
if ! command -v pmd &> /dev/null; then
|
|
17
|
+
echo '{"error": "pmd not installed. Download from: https://pmd.github.io/"}' >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Check if target directory exists
|
|
22
|
+
if [ ! -d "$TARGET_DIR" ]; then
|
|
23
|
+
echo "{\"error\": \"Directory not found: $TARGET_DIR\"}" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Check if there are any Java files
|
|
28
|
+
if ! find "$TARGET_DIR" -name "*.java" ! -path "*/target/*" ! -path "*/build/*" -print -quit 2>/dev/null | grep -q .; then
|
|
29
|
+
# No Java files found, output empty result
|
|
30
|
+
echo '{"tool":"pmd","language":"java","generatedAt":"'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'","files":{},"summary":{"totalFiles":0,"totalFunctions":0,"avgComplexity":0,"gradeDistribution":{"A":0,"B":0,"C":0,"D":0,"E":0,"F":0}}}'
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Run PMD and transform output
|
|
35
|
+
# PMD outputs JSON with violations grouped by file
|
|
36
|
+
# We filter for CyclomaticComplexity rule violations
|
|
37
|
+
# Using --no-cache to avoid caching issues
|
|
38
|
+
# --ignore-violations to ensure exit 0 even with violations found
|
|
39
|
+
|
|
40
|
+
pmd check -d "$TARGET_DIR" \
|
|
41
|
+
-R category/java/design.xml/CyclomaticComplexity \
|
|
42
|
+
-f json \
|
|
43
|
+
--no-cache \
|
|
44
|
+
--ignore-violations-on-exit 2>/dev/null | jq -c '
|
|
45
|
+
# Helper function for complexity to grade conversion
|
|
46
|
+
def complexity_to_grade:
|
|
47
|
+
if . <= 5 then "A"
|
|
48
|
+
elif . <= 10 then "B"
|
|
49
|
+
elif . <= 20 then "C"
|
|
50
|
+
elif . <= 30 then "D"
|
|
51
|
+
elif . <= 40 then "E"
|
|
52
|
+
else "F"
|
|
53
|
+
end;
|
|
54
|
+
|
|
55
|
+
# Process PMD violations format
|
|
56
|
+
# PMD JSON format has .files array with violations
|
|
57
|
+
(.files // []) |
|
|
58
|
+
map(
|
|
59
|
+
select(.violations | length > 0) |
|
|
60
|
+
{
|
|
61
|
+
path: .filename,
|
|
62
|
+
violations: [
|
|
63
|
+
.violations[] |
|
|
64
|
+
# PMD description format: "The {class|method} {name} has a {Cyclomatic|Standard} Complexity of {X}."
|
|
65
|
+
# or "The {class|method} '{name}' has a {Cyclomatic|Standard} cyclomatic complexity of {X}."
|
|
66
|
+
{
|
|
67
|
+
description: .description,
|
|
68
|
+
method: (.description | capture("method .(?<name>[^']+).") | .name),
|
|
69
|
+
beginLine: .beginLine,
|
|
70
|
+
endLine: .endLine,
|
|
71
|
+
# Extract complexity number from description
|
|
72
|
+
complexity: (
|
|
73
|
+
.description |
|
|
74
|
+
capture("complexity of (?<num>[0-9]+)") |
|
|
75
|
+
.num | tonumber
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
} |
|
|
80
|
+
select(.violations | length > 0)
|
|
81
|
+
) |
|
|
82
|
+
|
|
83
|
+
# Group by file and calculate metrics
|
|
84
|
+
map({
|
|
85
|
+
key: .path,
|
|
86
|
+
value: {
|
|
87
|
+
functions: (
|
|
88
|
+
.violations | map({
|
|
89
|
+
name: .method,
|
|
90
|
+
complexity: .complexity,
|
|
91
|
+
grade: (.complexity | complexity_to_grade),
|
|
92
|
+
line: .beginLine,
|
|
93
|
+
endLine: .endLine
|
|
94
|
+
})
|
|
95
|
+
),
|
|
96
|
+
avgComplexity: (
|
|
97
|
+
if (.violations | length) > 0 then
|
|
98
|
+
((.violations | map(.complexity) | add) / (.violations | length))
|
|
99
|
+
else 0 end
|
|
100
|
+
),
|
|
101
|
+
maxComplexity: (
|
|
102
|
+
if (.violations | length) > 0 then
|
|
103
|
+
(.violations | map(.complexity) | max)
|
|
104
|
+
else 0 end
|
|
105
|
+
),
|
|
106
|
+
grade: (
|
|
107
|
+
if (.violations | length) > 0 then
|
|
108
|
+
(((.violations | map(.complexity) | add) / (.violations | length)) | complexity_to_grade)
|
|
109
|
+
else "A" end
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
}) |
|
|
113
|
+
from_entries as $files |
|
|
114
|
+
|
|
115
|
+
# Build final output
|
|
116
|
+
{
|
|
117
|
+
tool: "pmd",
|
|
118
|
+
language: "java",
|
|
119
|
+
generatedAt: (now | todate),
|
|
120
|
+
files: $files,
|
|
121
|
+
summary: {
|
|
122
|
+
totalFiles: ($files | to_entries | length),
|
|
123
|
+
totalFunctions: ([$files | to_entries[].value.functions | length] | add // 0),
|
|
124
|
+
avgComplexity: (
|
|
125
|
+
[$files | to_entries[].value.avgComplexity] |
|
|
126
|
+
if length > 0 then (add / length) else 0 end
|
|
127
|
+
),
|
|
128
|
+
gradeDistribution: (
|
|
129
|
+
[$files | to_entries[].value.grade] |
|
|
130
|
+
reduce .[] as $grade (
|
|
131
|
+
{A: 0, B: 0, C: 0, D: 0, E: 0, F: 0};
|
|
132
|
+
.[$grade] += 1
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
'
|