@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.
Files changed (77) hide show
  1. package/.agents/plans/2025-12-28-ai-safety-integration-plan.md +326 -0
  2. package/.agents/plans/2025-12-29-complexity-driver-plan.md +225 -0
  3. package/.agents/plans/2025-12-29-complexity-drivers-plan.md +253 -0
  4. package/.agents/research/2025-12-28-ai-platform-security-integration.md +295 -0
  5. package/.agents/research/2025-12-29-complexity-driver-architecture.md +392 -0
  6. package/.agents/research/2025-12-29-complexity-drivers.md +227 -0
  7. package/.beads/README.md +81 -0
  8. package/.beads/config.yaml +62 -0
  9. package/.beads/interactions.jsonl +0 -0
  10. package/.beads/issues.jsonl +21 -0
  11. package/.beads/metadata.json +4 -0
  12. package/.gitattributes +3 -0
  13. package/AGENTS.md +40 -0
  14. package/CHANGELOG.md +69 -0
  15. package/CLAUDE.md +75 -0
  16. package/README.md +71 -0
  17. package/dist/ai-safety/contract-drift.d.ts +14 -0
  18. package/dist/ai-safety/contract-drift.d.ts.map +1 -0
  19. package/dist/ai-safety/contract-drift.js +230 -0
  20. package/dist/ai-safety/contract-drift.js.map +1 -0
  21. package/dist/ai-safety/index.d.ts +43 -0
  22. package/dist/ai-safety/index.d.ts.map +1 -0
  23. package/dist/ai-safety/index.js +177 -0
  24. package/dist/ai-safety/index.js.map +1 -0
  25. package/dist/ai-safety/scope-violation.d.ts +18 -0
  26. package/dist/ai-safety/scope-violation.d.ts.map +1 -0
  27. package/dist/ai-safety/scope-violation.js +150 -0
  28. package/dist/ai-safety/scope-violation.js.map +1 -0
  29. package/dist/ai-safety/secret-leakage.d.ts +18 -0
  30. package/dist/ai-safety/secret-leakage.d.ts.map +1 -0
  31. package/dist/ai-safety/secret-leakage.js +188 -0
  32. package/dist/ai-safety/secret-leakage.js.map +1 -0
  33. package/dist/ai-safety/token-spiral.d.ts +17 -0
  34. package/dist/ai-safety/token-spiral.d.ts.map +1 -0
  35. package/dist/ai-safety/token-spiral.js +183 -0
  36. package/dist/ai-safety/token-spiral.js.map +1 -0
  37. package/dist/ai-safety/types.d.ts +122 -0
  38. package/dist/ai-safety/types.d.ts.map +1 -0
  39. package/dist/ai-safety/types.js +32 -0
  40. package/dist/ai-safety/types.js.map +1 -0
  41. package/dist/analyzers/complexity.d.ts +92 -0
  42. package/dist/analyzers/complexity.d.ts.map +1 -0
  43. package/dist/analyzers/complexity.js +79 -0
  44. package/dist/analyzers/complexity.js.map +1 -0
  45. package/dist/analyzers/modularity.d.ts +3 -1
  46. package/dist/analyzers/modularity.d.ts.map +1 -1
  47. package/dist/analyzers/modularity.js +32 -6
  48. package/dist/analyzers/modularity.js.map +1 -1
  49. package/dist/cli.js +2 -1
  50. package/dist/cli.js.map +1 -1
  51. package/dist/commands/driver.d.ts +18 -0
  52. package/dist/commands/driver.d.ts.map +1 -0
  53. package/dist/commands/driver.js +58 -0
  54. package/dist/commands/driver.js.map +1 -0
  55. package/dist/commands/index.d.ts +1 -0
  56. package/dist/commands/index.d.ts.map +1 -1
  57. package/dist/commands/index.js +1 -0
  58. package/dist/commands/index.js.map +1 -1
  59. package/dist/commands/modularity.d.ts +2 -0
  60. package/dist/commands/modularity.d.ts.map +1 -1
  61. package/dist/commands/modularity.js +86 -7
  62. package/dist/commands/modularity.js.map +1 -1
  63. package/dist/commands/session.d.ts +9 -0
  64. package/dist/commands/session.d.ts.map +1 -1
  65. package/dist/commands/session.js +42 -0
  66. package/dist/commands/session.js.map +1 -1
  67. package/dist/commands/watch.d.ts.map +1 -1
  68. package/dist/commands/watch.js +59 -0
  69. package/dist/commands/watch.js.map +1 -1
  70. package/drivers/README.md +327 -0
  71. package/drivers/go.sh +131 -0
  72. package/drivers/java.sh +137 -0
  73. package/drivers/javascript.sh +134 -0
  74. package/drivers/php.sh +132 -0
  75. package/drivers/python.sh +90 -0
  76. package/drivers/rust.sh +132 -0
  77. 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
+ '
@@ -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
+ '