@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,134 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# drivers/javascript.sh
|
|
3
|
+
# Wraps cyclomatic-complexity to produce standard complexity JSON
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./drivers/javascript.sh [directory]
|
|
6
|
+
# directory: Path to JavaScript/TypeScript code to analyze (default: current directory)
|
|
7
|
+
#
|
|
8
|
+
# Output: JSON conforming to ComplexityReport schema
|
|
9
|
+
# Exit codes: 0 = success, 1 = error (npx not available or other failure)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
TARGET_DIR="${1:-.}"
|
|
14
|
+
|
|
15
|
+
# Check if npx is available
|
|
16
|
+
if ! command -v npx &> /dev/null; then
|
|
17
|
+
echo '{"error": "npx not found. Install Node.js and npm."}' >&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
|
+
# Find all JS/TS files (excluding common build/dep directories)
|
|
28
|
+
FILES=$(find "$TARGET_DIR" -type f \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) \
|
|
29
|
+
! -path "*/node_modules/*" \
|
|
30
|
+
! -path "*/dist/*" \
|
|
31
|
+
! -path "*/build/*" \
|
|
32
|
+
! -path "*/.next/*" \
|
|
33
|
+
! -path "*/coverage/*" 2>/dev/null)
|
|
34
|
+
|
|
35
|
+
# If no files found, output empty result
|
|
36
|
+
if [ -z "$FILES" ]; then
|
|
37
|
+
echo '{"tool":"cyclomatic-complexity","language":"javascript","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}}}'
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Build glob pattern for cyclomatic-complexity
|
|
42
|
+
# It expects quoted glob patterns like 'src/**/*.ts'
|
|
43
|
+
PATTERN="${TARGET_DIR}/**/*.{js,ts,jsx,tsx}"
|
|
44
|
+
|
|
45
|
+
# Run cyclomatic-complexity and transform output
|
|
46
|
+
# cyclomatic-complexity outputs JSON like:
|
|
47
|
+
# [
|
|
48
|
+
# {
|
|
49
|
+
# "file": "path/to/file.js",
|
|
50
|
+
# "functionComplexities": [
|
|
51
|
+
# {"name": "func", "complexity": 5, "line": 10}
|
|
52
|
+
# ],
|
|
53
|
+
# "complexitySum": 5,
|
|
54
|
+
# "complexityLevel": "ok"
|
|
55
|
+
# }
|
|
56
|
+
# ]
|
|
57
|
+
|
|
58
|
+
npx --yes cyclomatic-complexity "$PATTERN" --json 2>/dev/null | jq -c '
|
|
59
|
+
# Filter out empty files and global scope functions
|
|
60
|
+
map(
|
|
61
|
+
select(.functionComplexities | length > 0) |
|
|
62
|
+
. + {functionComplexities: [.functionComplexities[] | select(.name != "global")]}
|
|
63
|
+
) |
|
|
64
|
+
map(select(.functionComplexities | length > 0)) |
|
|
65
|
+
|
|
66
|
+
# Transform to standard schema
|
|
67
|
+
(
|
|
68
|
+
map({
|
|
69
|
+
key: .file,
|
|
70
|
+
value: {
|
|
71
|
+
functions: (
|
|
72
|
+
.functionComplexities | map({
|
|
73
|
+
name: .name,
|
|
74
|
+
complexity: .complexity,
|
|
75
|
+
grade: (
|
|
76
|
+
if .complexity <= 5 then "A"
|
|
77
|
+
elif .complexity <= 10 then "B"
|
|
78
|
+
elif .complexity <= 20 then "C"
|
|
79
|
+
elif .complexity <= 30 then "D"
|
|
80
|
+
elif .complexity <= 40 then "E"
|
|
81
|
+
else "F"
|
|
82
|
+
end
|
|
83
|
+
),
|
|
84
|
+
line: .line,
|
|
85
|
+
endLine: null
|
|
86
|
+
})
|
|
87
|
+
),
|
|
88
|
+
avgComplexity: (
|
|
89
|
+
if (.functionComplexities | length) > 0 then
|
|
90
|
+
((.functionComplexities | map(.complexity) | add) / (.functionComplexities | length))
|
|
91
|
+
else 0 end
|
|
92
|
+
),
|
|
93
|
+
maxComplexity: (
|
|
94
|
+
if (.functionComplexities | length) > 0 then
|
|
95
|
+
(.functionComplexities | map(.complexity) | max)
|
|
96
|
+
else 0 end
|
|
97
|
+
),
|
|
98
|
+
grade: (
|
|
99
|
+
if (.functionComplexities | length) > 0 then
|
|
100
|
+
((.functionComplexities | map(.complexity) | add) / (.functionComplexities | length)) as $avg |
|
|
101
|
+
if $avg <= 5 then "A"
|
|
102
|
+
elif $avg <= 10 then "B"
|
|
103
|
+
elif $avg <= 20 then "C"
|
|
104
|
+
elif $avg <= 30 then "D"
|
|
105
|
+
elif $avg <= 40 then "E"
|
|
106
|
+
else "F"
|
|
107
|
+
end
|
|
108
|
+
else "A" end
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}) | from_entries
|
|
112
|
+
) as $files |
|
|
113
|
+
{
|
|
114
|
+
tool: "cyclomatic-complexity",
|
|
115
|
+
language: "javascript",
|
|
116
|
+
generatedAt: (now | todate),
|
|
117
|
+
files: $files,
|
|
118
|
+
summary: {
|
|
119
|
+
totalFiles: ($files | to_entries | length),
|
|
120
|
+
totalFunctions: ([$files | to_entries[].value.functions | length] | add // 0),
|
|
121
|
+
avgComplexity: (
|
|
122
|
+
[$files | to_entries[].value.avgComplexity] |
|
|
123
|
+
if length > 0 then (add / length) else 0 end
|
|
124
|
+
),
|
|
125
|
+
gradeDistribution: (
|
|
126
|
+
[$files | to_entries[].value.grade] |
|
|
127
|
+
reduce .[] as $grade (
|
|
128
|
+
{A: 0, B: 0, C: 0, D: 0, E: 0, F: 0};
|
|
129
|
+
.[$grade] += 1
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
'
|
package/drivers/php.sh
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# drivers/php.sh
|
|
3
|
+
# Wraps PHPMD to produce standard complexity JSON
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./drivers/php.sh [directory]
|
|
6
|
+
# directory: Path to PHP code to analyze (default: current directory)
|
|
7
|
+
#
|
|
8
|
+
# Output: JSON conforming to ComplexityReport schema
|
|
9
|
+
# Exit codes: 0 = success, 1 = error (phpmd not installed or other failure)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
TARGET_DIR="${1:-.}"
|
|
14
|
+
|
|
15
|
+
# Check phpmd is installed
|
|
16
|
+
if ! command -v phpmd &> /dev/null; then
|
|
17
|
+
echo '{"error": "phpmd not installed. Run: composer global require phpmd/phpmd"}' >&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 PHP files
|
|
28
|
+
if ! find "$TARGET_DIR" -name "*.php" ! -path "*/vendor/*" -print -quit 2>/dev/null | grep -q .; then
|
|
29
|
+
# No PHP files found, output empty result
|
|
30
|
+
echo '{"tool":"phpmd","language":"php","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 PHPMD and transform output
|
|
35
|
+
# PHPMD outputs JSON violations, we need to extract cyclomatic complexity violations
|
|
36
|
+
# and transform them to our schema
|
|
37
|
+
# Using --ignore-violations-on-exit to ensure exit 0 even with violations
|
|
38
|
+
|
|
39
|
+
phpmd "$TARGET_DIR" json codesize --exclude vendor --ignore-violations-on-exit 2>/dev/null | jq -c '
|
|
40
|
+
# Helper function for complexity to grade conversion
|
|
41
|
+
def complexity_to_grade:
|
|
42
|
+
if . <= 5 then "A"
|
|
43
|
+
elif . <= 10 then "B"
|
|
44
|
+
elif . <= 20 then "C"
|
|
45
|
+
elif . <= 30 then "D"
|
|
46
|
+
elif . <= 40 then "E"
|
|
47
|
+
else "F"
|
|
48
|
+
end;
|
|
49
|
+
|
|
50
|
+
# Process PHPMD violations format
|
|
51
|
+
# Extract only CyclomaticComplexity violations
|
|
52
|
+
.files |
|
|
53
|
+
map(
|
|
54
|
+
select(.violations | length > 0) |
|
|
55
|
+
{
|
|
56
|
+
path: .file,
|
|
57
|
+
violations: [
|
|
58
|
+
.violations[] |
|
|
59
|
+
select(.rule == "CyclomaticComplexity") |
|
|
60
|
+
{
|
|
61
|
+
# Parse complexity from description like "The method foo() has a Cyclomatic Complexity of 12."
|
|
62
|
+
description: .description,
|
|
63
|
+
method: .method,
|
|
64
|
+
beginLine: .beginLine,
|
|
65
|
+
endLine: .endLine,
|
|
66
|
+
# Extract complexity number from description using regex
|
|
67
|
+
complexity: (
|
|
68
|
+
.description |
|
|
69
|
+
capture("Cyclomatic Complexity of (?<num>[0-9]+)") |
|
|
70
|
+
.num | tonumber
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
} |
|
|
75
|
+
select(.violations | length > 0)
|
|
76
|
+
) |
|
|
77
|
+
|
|
78
|
+
# Group by file and calculate metrics
|
|
79
|
+
map({
|
|
80
|
+
key: .path,
|
|
81
|
+
value: {
|
|
82
|
+
functions: (
|
|
83
|
+
.violations | map({
|
|
84
|
+
name: .method,
|
|
85
|
+
complexity: .complexity,
|
|
86
|
+
grade: (.complexity | complexity_to_grade),
|
|
87
|
+
line: .beginLine,
|
|
88
|
+
endLine: .endLine
|
|
89
|
+
})
|
|
90
|
+
),
|
|
91
|
+
avgComplexity: (
|
|
92
|
+
if (.violations | length) > 0 then
|
|
93
|
+
((.violations | map(.complexity) | add) / (.violations | length))
|
|
94
|
+
else 0 end
|
|
95
|
+
),
|
|
96
|
+
maxComplexity: (
|
|
97
|
+
if (.violations | length) > 0 then
|
|
98
|
+
(.violations | map(.complexity) | max)
|
|
99
|
+
else 0 end
|
|
100
|
+
),
|
|
101
|
+
grade: (
|
|
102
|
+
if (.violations | length) > 0 then
|
|
103
|
+
(((.violations | map(.complexity) | add) / (.violations | length)) | complexity_to_grade)
|
|
104
|
+
else "A" end
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
}) |
|
|
108
|
+
from_entries as $files |
|
|
109
|
+
|
|
110
|
+
# Build final output
|
|
111
|
+
{
|
|
112
|
+
tool: "phpmd",
|
|
113
|
+
language: "php",
|
|
114
|
+
generatedAt: (now | todate),
|
|
115
|
+
files: $files,
|
|
116
|
+
summary: {
|
|
117
|
+
totalFiles: ($files | to_entries | length),
|
|
118
|
+
totalFunctions: ([$files | to_entries[].value.functions | length] | add // 0),
|
|
119
|
+
avgComplexity: (
|
|
120
|
+
[$files | to_entries[].value.avgComplexity] |
|
|
121
|
+
if length > 0 then (add / length) else 0 end
|
|
122
|
+
),
|
|
123
|
+
gradeDistribution: (
|
|
124
|
+
[$files | to_entries[].value.grade] |
|
|
125
|
+
reduce .[] as $grade (
|
|
126
|
+
{A: 0, B: 0, C: 0, D: 0, E: 0, F: 0};
|
|
127
|
+
.[$grade] += 1
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
'
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# drivers/python.sh
|
|
3
|
+
# Wraps radon to produce standard complexity JSON
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./drivers/python.sh [directory]
|
|
6
|
+
# directory: Path to Python code to analyze (default: current directory)
|
|
7
|
+
#
|
|
8
|
+
# Output: JSON conforming to ComplexityReport schema
|
|
9
|
+
# Exit codes: 0 = success, 1 = error (radon not installed or other failure)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
TARGET_DIR="${1:-.}"
|
|
14
|
+
|
|
15
|
+
# Check radon is installed
|
|
16
|
+
if ! command -v radon &> /dev/null; then
|
|
17
|
+
echo '{"error": "radon not installed. Run: pip install radon"}' >&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
|
+
# Run radon and transform output
|
|
28
|
+
# radon cc outputs JSON like:
|
|
29
|
+
# {
|
|
30
|
+
# "file.py": [
|
|
31
|
+
# {"name": "func", "complexity": 5, "rank": "A", "lineno": 10, ...}
|
|
32
|
+
# ]
|
|
33
|
+
# }
|
|
34
|
+
radon cc "$TARGET_DIR" -j --total-average 2>/dev/null | jq -c '
|
|
35
|
+
(to_entries | map({
|
|
36
|
+
key: .key,
|
|
37
|
+
value: {
|
|
38
|
+
functions: (.value | map({
|
|
39
|
+
name: .name,
|
|
40
|
+
complexity: .complexity,
|
|
41
|
+
grade: .rank,
|
|
42
|
+
line: .lineno,
|
|
43
|
+
endLine: .endline
|
|
44
|
+
})),
|
|
45
|
+
avgComplexity: (
|
|
46
|
+
if (.value | length) > 0 then
|
|
47
|
+
((.value | map(.complexity) | add) / (.value | length))
|
|
48
|
+
else 0 end
|
|
49
|
+
),
|
|
50
|
+
maxComplexity: (
|
|
51
|
+
if (.value | length) > 0 then
|
|
52
|
+
(.value | map(.complexity) | max)
|
|
53
|
+
else 0 end
|
|
54
|
+
),
|
|
55
|
+
grade: (
|
|
56
|
+
if (.value | length) > 0 then
|
|
57
|
+
((.value | map(.complexity) | add) / (.value | length)) as $avg |
|
|
58
|
+
if $avg <= 5 then "A"
|
|
59
|
+
elif $avg <= 10 then "B"
|
|
60
|
+
elif $avg <= 20 then "C"
|
|
61
|
+
elif $avg <= 30 then "D"
|
|
62
|
+
elif $avg <= 40 then "E"
|
|
63
|
+
else "F"
|
|
64
|
+
end
|
|
65
|
+
else "A" end
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}) | from_entries) as $files |
|
|
69
|
+
{
|
|
70
|
+
tool: "radon",
|
|
71
|
+
language: "python",
|
|
72
|
+
generatedAt: (now | todate),
|
|
73
|
+
files: $files,
|
|
74
|
+
summary: {
|
|
75
|
+
totalFiles: ($files | to_entries | length),
|
|
76
|
+
totalFunctions: ([$files | to_entries[].value.functions | length] | add // 0),
|
|
77
|
+
avgComplexity: (
|
|
78
|
+
[$files | to_entries[].value.avgComplexity] |
|
|
79
|
+
if length > 0 then (add / length) else 0 end
|
|
80
|
+
),
|
|
81
|
+
gradeDistribution: (
|
|
82
|
+
[$files | to_entries[].value.grade] |
|
|
83
|
+
reduce .[] as $grade (
|
|
84
|
+
{A: 0, B: 0, C: 0, D: 0, E: 0, F: 0};
|
|
85
|
+
.[$grade] += 1
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
'
|
package/drivers/rust.sh
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# drivers/rust.sh
|
|
3
|
+
# Wraps rust-code-analysis-cli to produce standard complexity JSON
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./drivers/rust.sh [directory]
|
|
6
|
+
# directory: Path to Rust code to analyze (default: current directory)
|
|
7
|
+
#
|
|
8
|
+
# Output: JSON conforming to ComplexityReport schema
|
|
9
|
+
# Exit codes: 0 = success, 1 = error (rust-code-analysis-cli not installed or other failure)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
TARGET_DIR="${1:-.}"
|
|
14
|
+
|
|
15
|
+
# Check rust-code-analysis-cli is installed
|
|
16
|
+
if ! command -v rust-code-analysis-cli &> /dev/null; then
|
|
17
|
+
echo '{"error": "rust-code-analysis-cli not installed. Run: cargo install rust-code-analysis-cli"}' >&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 Rust files
|
|
28
|
+
if ! find "$TARGET_DIR" -name "*.rs" ! -path "*/target/*" -print -quit 2>/dev/null | grep -q .; then
|
|
29
|
+
# No Rust files found, output empty result
|
|
30
|
+
echo '{"tool":"rust-code-analysis","language":"rust","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 rust-code-analysis-cli and transform output
|
|
35
|
+
# The tool outputs JSON with metrics for each file analyzed
|
|
36
|
+
# We need to transform this to match our ComplexityReport schema
|
|
37
|
+
|
|
38
|
+
# Create temp directory for JSON outputs
|
|
39
|
+
TEMP_DIR=$(mktemp -d)
|
|
40
|
+
trap "rm -rf $TEMP_DIR" EXIT
|
|
41
|
+
|
|
42
|
+
# Run rust-code-analysis-cli on the directory
|
|
43
|
+
# -m: enable metrics computation
|
|
44
|
+
# -O json: output format JSON
|
|
45
|
+
# -p: path to analyze
|
|
46
|
+
# Output goes to current directory with .json extension added to original filename
|
|
47
|
+
(cd "$TEMP_DIR" && rust-code-analysis-cli -m -p "$TARGET_DIR" -O json 2>/dev/null) || {
|
|
48
|
+
echo '{"error": "rust-code-analysis-cli failed to analyze code"}' >&2
|
|
49
|
+
exit 1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Find all generated JSON files and merge them
|
|
53
|
+
# rust-code-analysis creates one JSON per source file
|
|
54
|
+
find "$TEMP_DIR" -name "*.json" -type f 2>/dev/null | jq -s -c '
|
|
55
|
+
# Helper function for complexity to grade conversion
|
|
56
|
+
def complexity_to_grade:
|
|
57
|
+
if . <= 5 then "A"
|
|
58
|
+
elif . <= 10 then "B"
|
|
59
|
+
elif . <= 20 then "C"
|
|
60
|
+
elif . <= 30 then "D"
|
|
61
|
+
elif . <= 40 then "E"
|
|
62
|
+
else "F"
|
|
63
|
+
end;
|
|
64
|
+
|
|
65
|
+
# Process each file'\''s metrics
|
|
66
|
+
map(
|
|
67
|
+
# Extract the original file path from name field
|
|
68
|
+
.name as $filepath |
|
|
69
|
+
|
|
70
|
+
# Get cyclomatic complexity for functions
|
|
71
|
+
(.spaces // []) |
|
|
72
|
+
|
|
73
|
+
# Find all functions and their complexity
|
|
74
|
+
[.. | select(.kind? == "function" or .kind? == "method") | {
|
|
75
|
+
name: .name.name,
|
|
76
|
+
complexity: (.metrics.cyclomatic.sum // 0),
|
|
77
|
+
line: .start_line,
|
|
78
|
+
endLine: .end_line
|
|
79
|
+
}] as $functions |
|
|
80
|
+
|
|
81
|
+
# Calculate file-level metrics
|
|
82
|
+
{
|
|
83
|
+
key: $filepath,
|
|
84
|
+
value: {
|
|
85
|
+
functions: ($functions | map(
|
|
86
|
+
. + {grade: (.complexity | complexity_to_grade)}
|
|
87
|
+
)),
|
|
88
|
+
avgComplexity: (
|
|
89
|
+
if ($functions | length) > 0 then
|
|
90
|
+
(($functions | map(.complexity) | add) / ($functions | length))
|
|
91
|
+
else 0 end
|
|
92
|
+
),
|
|
93
|
+
maxComplexity: (
|
|
94
|
+
if ($functions | length) > 0 then
|
|
95
|
+
($functions | map(.complexity) | max)
|
|
96
|
+
else 0 end
|
|
97
|
+
),
|
|
98
|
+
grade: (
|
|
99
|
+
if ($functions | length) > 0 then
|
|
100
|
+
((($functions | map(.complexity) | add) / ($functions | length)) | complexity_to_grade)
|
|
101
|
+
else "A" end
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
) |
|
|
106
|
+
|
|
107
|
+
# Convert to object with file paths as keys
|
|
108
|
+
from_entries as $files |
|
|
109
|
+
|
|
110
|
+
# Build final output
|
|
111
|
+
{
|
|
112
|
+
tool: "rust-code-analysis",
|
|
113
|
+
language: "rust",
|
|
114
|
+
generatedAt: (now | todate),
|
|
115
|
+
files: $files,
|
|
116
|
+
summary: {
|
|
117
|
+
totalFiles: ($files | to_entries | length),
|
|
118
|
+
totalFunctions: ([$files | to_entries[].value.functions | length] | add // 0),
|
|
119
|
+
avgComplexity: (
|
|
120
|
+
[$files | to_entries[].value.avgComplexity] |
|
|
121
|
+
if length > 0 then (add / length) else 0 end
|
|
122
|
+
),
|
|
123
|
+
gradeDistribution: (
|
|
124
|
+
[$files | to_entries[].value.grade] |
|
|
125
|
+
reduce .[] as $grade (
|
|
126
|
+
{A: 0, B: 0, C: 0, D: 0, E: 0, F: 0};
|
|
127
|
+
.[$grade] += 1
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
'
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@boshu2/vibe-check",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"description": "Track git commit patterns during AI-assisted coding (experimental)",
|
|
6
6
|
"main": "dist/cli.js",
|
|
7
7
|
"bin": {
|
|
@@ -33,9 +33,12 @@
|
|
|
33
33
|
"commander": "^14.0.2",
|
|
34
34
|
"date-fns": "^4.1.0",
|
|
35
35
|
"enquirer": "^2.4.1",
|
|
36
|
+
"js-yaml": "^4.1.1",
|
|
37
|
+
"minimatch": "^10.1.1",
|
|
36
38
|
"simple-git": "^3.30.0"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
41
|
+
"@types/js-yaml": "^4.0.9",
|
|
39
42
|
"@types/node": "^25.0.2",
|
|
40
43
|
"@vitest/coverage-v8": "^4.0.15",
|
|
41
44
|
"ts-node": "^10.9.2",
|