@northbridge-security/secureai 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/README.md +122 -0
- package/.claude/commands/architect/clean.md +978 -0
- package/.claude/commands/architect/kiss.md +762 -0
- package/.claude/commands/architect/review.md +704 -0
- package/.claude/commands/catchup.md +90 -0
- package/.claude/commands/code.md +115 -0
- package/.claude/commands/commit.md +1218 -0
- package/.claude/commands/cover.md +1298 -0
- package/.claude/commands/fmea.md +275 -0
- package/.claude/commands/kaizen.md +312 -0
- package/.claude/commands/pr.md +503 -0
- package/.claude/commands/todo.md +99 -0
- package/.claude/commands/worktree.md +738 -0
- package/.claude/commands/wrapup.md +103 -0
- package/LICENSE +183 -0
- package/README.md +108 -0
- package/dist/cli.js +75634 -0
- package/docs/agents/devops-reviewer.md +889 -0
- package/docs/agents/kiss-simplifier.md +1088 -0
- package/docs/agents/typescript.md +8 -0
- package/docs/guides/README.md +109 -0
- package/docs/guides/agents.clean.arch.md +244 -0
- package/docs/guides/agents.clean.arch.ts.md +1314 -0
- package/docs/guides/agents.gotask.md +1037 -0
- package/docs/guides/agents.markdown.md +1209 -0
- package/docs/guides/agents.onepassword.md +285 -0
- package/docs/guides/agents.sonar.md +857 -0
- package/docs/guides/agents.tdd.md +838 -0
- package/docs/guides/agents.tdd.ts.md +1062 -0
- package/docs/guides/agents.typesript.md +1389 -0
- package/docs/guides/github-mcp.md +1075 -0
- package/package.json +130 -0
- package/packages/secureai-cli/src/cli.ts +21 -0
- package/tasks/README.md +880 -0
- package/tasks/aws.yml +64 -0
- package/tasks/bash.yml +118 -0
- package/tasks/bun.yml +738 -0
- package/tasks/claude.yml +183 -0
- package/tasks/docker.yml +420 -0
- package/tasks/docs.yml +127 -0
- package/tasks/git.yml +1336 -0
- package/tasks/gotask.yml +132 -0
- package/tasks/json.yml +77 -0
- package/tasks/markdown.yml +95 -0
- package/tasks/onepassword.yml +350 -0
- package/tasks/security.yml +102 -0
- package/tasks/sonar.yml +437 -0
- package/tasks/template.yml +74 -0
- package/tasks/vscode.yml +103 -0
- package/tasks/yaml.yml +121 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Security Tasks
|
|
2
|
+
# Provides local SAST scanning
|
|
3
|
+
|
|
4
|
+
version: "3"
|
|
5
|
+
|
|
6
|
+
vars:
|
|
7
|
+
DEFAULT_PATTERNS: "*.json,**/*.json,!node_modules/**,!dist/**,!.taskmaster/**"
|
|
8
|
+
|
|
9
|
+
tasks:
|
|
10
|
+
default:
|
|
11
|
+
desc: "Show available security tasks"
|
|
12
|
+
aliases: [help, h]
|
|
13
|
+
silent: true
|
|
14
|
+
cmds:
|
|
15
|
+
- |
|
|
16
|
+
# Color codes
|
|
17
|
+
GREEN='\033[0;32m'
|
|
18
|
+
YELLOW='\033[0;33m'
|
|
19
|
+
BOLD='\033[1m'
|
|
20
|
+
NC='\033[0m'
|
|
21
|
+
|
|
22
|
+
echo -e "${BOLD}Security${NC}"
|
|
23
|
+
echo ""
|
|
24
|
+
echo "Command Alias Description Examples"
|
|
25
|
+
echo "───────────────────────────────────────────────────────────────────────────────────────────────────"
|
|
26
|
+
echo -e " ${GREEN}task security:scan${NC} ${YELLOW}s${NC} Run semgrep security scanner "
|
|
27
|
+
echo -e " ${GREEN}task security:fix${NC} ${YELLOW}f${NC} Auto-fix security issues "
|
|
28
|
+
echo -e " ${GREEN}task security:trufflehog${NC} ${YELLOW}th${NC} Scan for secrets with truffleHog "
|
|
29
|
+
|
|
30
|
+
scan:
|
|
31
|
+
desc: Run security scanner on entire repo
|
|
32
|
+
aliases: [s]
|
|
33
|
+
silent: true
|
|
34
|
+
cmds:
|
|
35
|
+
- |
|
|
36
|
+
# Source secrets if .env has 1Password references
|
|
37
|
+
if [ -f .env ] && grep -q "op://" .env 2>/dev/null; then
|
|
38
|
+
TEMP_ENV=$(task op:export)
|
|
39
|
+
source "$TEMP_ENV"
|
|
40
|
+
fi
|
|
41
|
+
semgrep --config=auto --allow-local-builds -q
|
|
42
|
+
|
|
43
|
+
fix:
|
|
44
|
+
desc: Run security scanner on entire repo and automatically apply fixes
|
|
45
|
+
aliases: [sf]
|
|
46
|
+
silent: true
|
|
47
|
+
cmds:
|
|
48
|
+
- |
|
|
49
|
+
# Source secrets if .env has 1Password references
|
|
50
|
+
if [ -f .env ] && grep -q "op://" .env 2>/dev/null; then
|
|
51
|
+
TEMP_ENV=$(task op:export)
|
|
52
|
+
source "$TEMP_ENV"
|
|
53
|
+
fi
|
|
54
|
+
semgrep --config=auto --autofix --allow-local-builds -q
|
|
55
|
+
|
|
56
|
+
trufflehog:
|
|
57
|
+
desc: Run truffleHog secret scanner
|
|
58
|
+
silent: true
|
|
59
|
+
aliases: [th]
|
|
60
|
+
cmds:
|
|
61
|
+
- |
|
|
62
|
+
GREEN='\033[0;32m'
|
|
63
|
+
YELLOW='\033[0;33m'
|
|
64
|
+
RED='\033[0;31m'
|
|
65
|
+
NC='\033[0m'
|
|
66
|
+
|
|
67
|
+
echo -e "${YELLOW}🔍${NC} Running truffleHog secret scanner..."
|
|
68
|
+
|
|
69
|
+
# Check if trufflehog is installed, install if not
|
|
70
|
+
if ! command -v trufflehog >/dev/null 2>&1; then
|
|
71
|
+
echo -e "${YELLOW}⚠️${NC} trufflehog not installed, installing..."
|
|
72
|
+
brew install trufflehog
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Create temporary exclusion file (regex patterns for directories to skip)
|
|
76
|
+
EXCLUDE_FILE=$(mktemp)
|
|
77
|
+
{
|
|
78
|
+
echo "node_modules/"
|
|
79
|
+
echo "__pycache__/"
|
|
80
|
+
echo "venv/"
|
|
81
|
+
echo "\.venv/"
|
|
82
|
+
echo "dist/"
|
|
83
|
+
echo "build/"
|
|
84
|
+
echo "\.git/"
|
|
85
|
+
echo "\.taskmaster/"
|
|
86
|
+
echo "\.logs/"
|
|
87
|
+
echo "coverage/"
|
|
88
|
+
} > "$EXCLUDE_FILE"
|
|
89
|
+
|
|
90
|
+
# Run truffleHog on current directory (exclude common build/dependency dirs)
|
|
91
|
+
echo "Scanning filesystem..."
|
|
92
|
+
if trufflehog filesystem . --exclude-paths="$EXCLUDE_FILE" --no-update --fail; then
|
|
93
|
+
echo -e "${GREEN}✓${NC} No secrets found by truffleHog"
|
|
94
|
+
EXIT_CODE=0
|
|
95
|
+
else
|
|
96
|
+
echo -e "${RED}❌${NC} truffleHog found secrets!"
|
|
97
|
+
EXIT_CODE=1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Clean up temporary file
|
|
101
|
+
rm -f "$EXCLUDE_FILE"
|
|
102
|
+
exit $EXIT_CODE
|
package/tasks/sonar.yml
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# SonarQube Code Quality Tasks
|
|
2
|
+
# Provides SonarQube analysis integration and issue management
|
|
3
|
+
#
|
|
4
|
+
# Configuration:
|
|
5
|
+
# - Set SONAR_TOKEN in .env file
|
|
6
|
+
# - Supports 1Password references: op://vault/item/field
|
|
7
|
+
# - Project key read from sonar-project.properties in repository root
|
|
8
|
+
|
|
9
|
+
version: "3"
|
|
10
|
+
|
|
11
|
+
vars:
|
|
12
|
+
REPORT_PATH: "{{.PWD}}/.logs/sonar"
|
|
13
|
+
SONAR_HOST: '{{.SONAR_HOST | default "https://sonarcloud.io"}}'
|
|
14
|
+
|
|
15
|
+
tasks:
|
|
16
|
+
default:
|
|
17
|
+
desc: "Show available SonarQube tasks"
|
|
18
|
+
aliases: [help, h]
|
|
19
|
+
silent: true
|
|
20
|
+
cmds:
|
|
21
|
+
- |
|
|
22
|
+
# Color codes
|
|
23
|
+
GREEN='\033[0;32m'
|
|
24
|
+
YELLOW='\033[0;33m'
|
|
25
|
+
BOLD='\033[1m'
|
|
26
|
+
NC='\033[0m'
|
|
27
|
+
|
|
28
|
+
echo -e "${BOLD}SonarQube Code Quality${NC}"
|
|
29
|
+
echo ""
|
|
30
|
+
echo "Command Alias Description Examples"
|
|
31
|
+
echo "───────────────────────────────────────────────────────────────────────────────────────────────────"
|
|
32
|
+
echo -e "${BOLD}Analysis:${NC}"
|
|
33
|
+
echo -e " ${GREEN}task sonar:scan${NC} ${YELLOW}s${NC} Run SonarQube analysis locally"
|
|
34
|
+
echo -e " ${GREEN}task sonar:download${NC} ${YELLOW}dl${NC} Download issues from SonarQube"
|
|
35
|
+
echo -e " ${GREEN}task sonar:issues${NC} ${YELLOW}i${NC} Display downloaded findings"
|
|
36
|
+
echo ""
|
|
37
|
+
echo -e "${BOLD}Configuration:${NC}"
|
|
38
|
+
echo -e " ${GREEN}task sonar:setup${NC} Install sonar-scanner CLI"
|
|
39
|
+
echo ""
|
|
40
|
+
echo -e "${BOLD}Usage Examples:${NC}"
|
|
41
|
+
echo -e " task sonar:download Download issues"
|
|
42
|
+
echo -e " task sonar:issues View findings"
|
|
43
|
+
echo -e " task sonar:scan Run local scan"
|
|
44
|
+
echo ""
|
|
45
|
+
|
|
46
|
+
setup:
|
|
47
|
+
desc: Install sonar-scanner CLI if not present
|
|
48
|
+
silent: true
|
|
49
|
+
cmds:
|
|
50
|
+
- |
|
|
51
|
+
# Check if already installed
|
|
52
|
+
if command -v sonar-scanner &> /dev/null; then
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Color codes
|
|
57
|
+
GREEN='\033[0;32m'
|
|
58
|
+
NC='\033[0m'
|
|
59
|
+
|
|
60
|
+
echo "Installing sonar-scanner..."
|
|
61
|
+
|
|
62
|
+
# Detect OS
|
|
63
|
+
OS="$(uname -s)"
|
|
64
|
+
case "$OS" in
|
|
65
|
+
Darwin)
|
|
66
|
+
if command -v brew &> /dev/null; then
|
|
67
|
+
brew install sonar-scanner --quiet
|
|
68
|
+
else
|
|
69
|
+
echo "Error: Homebrew not found. Install from https://brew.sh"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
;;
|
|
73
|
+
Linux)
|
|
74
|
+
# Download to local directory
|
|
75
|
+
INSTALL_DIR="$HOME/.local/bin"
|
|
76
|
+
mkdir -p "$INSTALL_DIR"
|
|
77
|
+
|
|
78
|
+
curl -sSL https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.8.0.2856-linux.zip -o /tmp/sonar-scanner.zip
|
|
79
|
+
unzip -q /tmp/sonar-scanner.zip -d /tmp/
|
|
80
|
+
mv /tmp/sonar-scanner-*/* "$INSTALL_DIR/"
|
|
81
|
+
rm -rf /tmp/sonar-scanner*
|
|
82
|
+
|
|
83
|
+
echo -e "${GREEN}✓${NC} Installed to $INSTALL_DIR"
|
|
84
|
+
;;
|
|
85
|
+
*)
|
|
86
|
+
echo "Error: Unsupported OS. Install from https://docs.sonarcloud.io"
|
|
87
|
+
exit 1
|
|
88
|
+
;;
|
|
89
|
+
esac
|
|
90
|
+
|
|
91
|
+
if command -v sonar-scanner &> /dev/null; then
|
|
92
|
+
echo -e "${GREEN}✓${NC} sonar-scanner installed"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
scan:
|
|
96
|
+
desc: Run SonarQube analysis locally
|
|
97
|
+
silent: true
|
|
98
|
+
dotenv: [".env"]
|
|
99
|
+
deps: [setup]
|
|
100
|
+
cmds:
|
|
101
|
+
- |
|
|
102
|
+
# Color codes
|
|
103
|
+
GREEN='\033[0;32m'
|
|
104
|
+
RED='\033[0;31m'
|
|
105
|
+
NC='\033[0m'
|
|
106
|
+
|
|
107
|
+
# Source secrets if .env has 1Password references
|
|
108
|
+
if [ -f .env ] && grep -q "op://" .env 2>/dev/null; then
|
|
109
|
+
source $(task op:export)
|
|
110
|
+
|
|
111
|
+
# Check if token is still encrypted (force a refresh)
|
|
112
|
+
if [[ "${SONAR_TOKEN:-}" == *"op://"* ]]; then
|
|
113
|
+
source $(task op:export:force)
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# Validate token
|
|
118
|
+
if [ -z "$SONAR_TOKEN" ]; then
|
|
119
|
+
echo -e "${RED}❌ SONAR_TOKEN not set${NC}" >&2
|
|
120
|
+
echo "Add to .env: SONAR_TOKEN=your-token" >&2
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Create report directory
|
|
125
|
+
mkdir -p {{.REPORT_PATH}}
|
|
126
|
+
|
|
127
|
+
# Run scanner
|
|
128
|
+
sonar-scanner \
|
|
129
|
+
-Dsonar.host.url={{.SONAR_HOST}} \
|
|
130
|
+
-Dsonar.token=$SONAR_TOKEN \
|
|
131
|
+
-Dsonar.working.directory={{.REPORT_PATH}}/.scannerwork \
|
|
132
|
+
2>&1 | tee {{.REPORT_PATH}}/scan.log > /dev/null
|
|
133
|
+
|
|
134
|
+
if [ $? -eq 0 ]; then
|
|
135
|
+
echo -e "${GREEN}✓${NC} Scan complete → {{.REPORT_PATH}}/scan.log"
|
|
136
|
+
else
|
|
137
|
+
echo -e "${RED}❌ Scan failed${NC}" >&2
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
download:
|
|
142
|
+
desc: Download SonarQube issues for project
|
|
143
|
+
aliases: [dl]
|
|
144
|
+
silent: true
|
|
145
|
+
dotenv: [".env"]
|
|
146
|
+
cmds:
|
|
147
|
+
- |
|
|
148
|
+
set -euo pipefail
|
|
149
|
+
|
|
150
|
+
# Color codes
|
|
151
|
+
GREEN='\033[0;32m'
|
|
152
|
+
RED='\033[0;31m'
|
|
153
|
+
CYAN='\033[0;36m'
|
|
154
|
+
NC='\033[0m'
|
|
155
|
+
|
|
156
|
+
# Source secrets if .env has 1Password references
|
|
157
|
+
if [ -f .env ] && grep -q "op://" .env 2>/dev/null; then
|
|
158
|
+
source $(task op:export)
|
|
159
|
+
|
|
160
|
+
# Check if token is still encrypted (force a refresh)
|
|
161
|
+
if [[ "${SONAR_TOKEN:-}" == *"op://"* ]]; then
|
|
162
|
+
source $(task op:export:force)
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Validate token
|
|
167
|
+
if [ -z "$SONAR_TOKEN" ]; then
|
|
168
|
+
echo -e "${RED}❌ SONAR_TOKEN not set${NC}" >&2
|
|
169
|
+
echo "Add to .env: SONAR_TOKEN=your-token" >&2
|
|
170
|
+
exit 1
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Read project key from sonar-project.properties
|
|
174
|
+
if [ ! -f "sonar-project.properties" ]; then
|
|
175
|
+
echo -e "${RED}❌ sonar-project.properties not found${NC}" >&2
|
|
176
|
+
echo "Create configuration file in repository root" >&2
|
|
177
|
+
exit 1
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
PROJECT_KEY=$(grep "^sonar.projectKey=" sonar-project.properties | cut -d'=' -f2)
|
|
181
|
+
if [ -z "$PROJECT_KEY" ]; then
|
|
182
|
+
echo -e "${RED}❌ sonar.projectKey not found in sonar-project.properties${NC}" >&2
|
|
183
|
+
exit 1
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# Detect PR context
|
|
187
|
+
CURRENT_BRANCH=$(task git:branch:current)
|
|
188
|
+
DEFAULT_BRANCH=$(task git:branch:default)
|
|
189
|
+
PR_NUMBER=$(gh pr list --head "$CURRENT_BRANCH" --base "$DEFAULT_BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
|
|
190
|
+
|
|
191
|
+
# Show what we're analyzing
|
|
192
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
193
|
+
PR_TITLE=$(gh pr view "$PR_NUMBER" --json title --jq '.title' 2>/dev/null || echo "")
|
|
194
|
+
echo -e "${CYAN}ℹ${NC} Analyzing PR #$PR_NUMBER: $PR_TITLE"
|
|
195
|
+
echo -e "${CYAN}ℹ${NC} Project: $PROJECT_KEY"
|
|
196
|
+
echo -e "${CYAN}ℹ${NC} Branch: $CURRENT_BRANCH → $DEFAULT_BRANCH"
|
|
197
|
+
ANALYSIS_TYPE="PR #$PR_NUMBER"
|
|
198
|
+
else
|
|
199
|
+
echo -e "${CYAN}ℹ${NC} Analyzing default branch"
|
|
200
|
+
echo -e "${CYAN}ℹ${NC} Project: $PROJECT_KEY"
|
|
201
|
+
echo -e "${CYAN}ℹ${NC} Branch: $DEFAULT_BRANCH"
|
|
202
|
+
ANALYSIS_TYPE="Branch: $DEFAULT_BRANCH"
|
|
203
|
+
fi
|
|
204
|
+
echo ""
|
|
205
|
+
|
|
206
|
+
# Create report directory
|
|
207
|
+
mkdir -p {{.REPORT_PATH}}
|
|
208
|
+
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
|
209
|
+
RAW_FILE="{{.REPORT_PATH}}/issues-${TIMESTAMP}-raw.json"
|
|
210
|
+
REPORT_FILE="{{.REPORT_PATH}}/issues-${TIMESTAMP}.txt"
|
|
211
|
+
|
|
212
|
+
# Fetch issues from SonarQube API (PR-specific or default branch)
|
|
213
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
214
|
+
# Try PR-specific analysis first
|
|
215
|
+
ISSUES_API="{{.SONAR_HOST}}/api/issues/search?pullRequest=$PR_NUMBER&componentKeys=$PROJECT_KEY&ps=500"
|
|
216
|
+
HOTSPOTS_API="{{.SONAR_HOST}}/api/hotspots/search?pullRequest=$PR_NUMBER&projectKey=$PROJECT_KEY&ps=500"
|
|
217
|
+
QG_API="{{.SONAR_HOST}}/api/qualitygates/project_status?projectKey=$PROJECT_KEY&pullRequest=$PR_NUMBER"
|
|
218
|
+
|
|
219
|
+
# Fetch PR data
|
|
220
|
+
ISSUES_DATA=$(curl -s -X GET "$ISSUES_API" -H "Authorization: Bearer $SONAR_TOKEN")
|
|
221
|
+
PR_TOTAL=$(echo "$ISSUES_DATA" | jq '.total // 0')
|
|
222
|
+
|
|
223
|
+
# If PR analysis doesn't exist or is empty, fall back to default branch
|
|
224
|
+
if [ "$PR_TOTAL" -eq 0 ]; then
|
|
225
|
+
echo -e "${YELLOW}⚠${NC} PR analysis not found, showing default branch analysis"
|
|
226
|
+
ANALYSIS_TYPE="Branch: $DEFAULT_BRANCH (PR not analyzed)"
|
|
227
|
+
ISSUES_API="{{.SONAR_HOST}}/api/issues/search?componentKeys=$PROJECT_KEY&ps=500"
|
|
228
|
+
HOTSPOTS_API="{{.SONAR_HOST}}/api/hotspots/search?projectKey=$PROJECT_KEY&ps=500"
|
|
229
|
+
QG_API="{{.SONAR_HOST}}/api/qualitygates/project_status?projectKey=$PROJECT_KEY"
|
|
230
|
+
fi
|
|
231
|
+
else
|
|
232
|
+
# Default branch analysis
|
|
233
|
+
ISSUES_API="{{.SONAR_HOST}}/api/issues/search?componentKeys=$PROJECT_KEY&ps=500"
|
|
234
|
+
HOTSPOTS_API="{{.SONAR_HOST}}/api/hotspots/search?projectKey=$PROJECT_KEY&ps=500"
|
|
235
|
+
QG_API="{{.SONAR_HOST}}/api/qualitygates/project_status?projectKey=$PROJECT_KEY"
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Fetch issues
|
|
239
|
+
ISSUES_DATA=$(curl -s -X GET "$ISSUES_API" -H "Authorization: Bearer $SONAR_TOKEN")
|
|
240
|
+
if [ $? -ne 0 ]; then
|
|
241
|
+
echo -e "${RED}❌ Failed to fetch issues${NC}" >&2
|
|
242
|
+
exit 1
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Fetch security hotspots
|
|
246
|
+
HOTSPOTS_DATA=$(curl -s -X GET "$HOTSPOTS_API" -H "Authorization: Bearer $SONAR_TOKEN")
|
|
247
|
+
|
|
248
|
+
# Fetch quality gate status
|
|
249
|
+
QG_DATA=$(curl -s -X GET "$QG_API" -H "Authorization: Bearer $SONAR_TOKEN")
|
|
250
|
+
|
|
251
|
+
# Fetch coverage metrics
|
|
252
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
253
|
+
MEASURES_API="{{.SONAR_HOST}}/api/measures/component?component=$PROJECT_KEY&pullRequest=$PR_NUMBER&metricKeys=new_coverage,new_lines_to_cover,new_uncovered_lines,coverage,lines_to_cover,uncovered_lines"
|
|
254
|
+
else
|
|
255
|
+
MEASURES_API="{{.SONAR_HOST}}/api/measures/component?component=$PROJECT_KEY&metricKeys=coverage,lines_to_cover,uncovered_lines"
|
|
256
|
+
fi
|
|
257
|
+
COVERAGE_DATA=$(curl -s -X GET "$MEASURES_API" -H "Authorization: Bearer $SONAR_TOKEN")
|
|
258
|
+
|
|
259
|
+
# Save raw responses
|
|
260
|
+
echo "$ISSUES_DATA" > "$RAW_FILE"
|
|
261
|
+
echo "$HOTSPOTS_DATA" > "{{.REPORT_PATH}}/hotspots-${TIMESTAMP}-raw.json"
|
|
262
|
+
echo "$QG_DATA" > "{{.REPORT_PATH}}/quality-gate-${TIMESTAMP}-raw.json"
|
|
263
|
+
echo "$COVERAGE_DATA" > "{{.REPORT_PATH}}/coverage-${TIMESTAMP}-raw.json"
|
|
264
|
+
|
|
265
|
+
# Count issues by severity
|
|
266
|
+
TOTAL=$(echo "$ISSUES_DATA" | jq '.total // 0')
|
|
267
|
+
BLOCKER=$(echo "$ISSUES_DATA" | jq '[.issues[] | select(.severity == "BLOCKER")] | length')
|
|
268
|
+
CRITICAL=$(echo "$ISSUES_DATA" | jq '[.issues[] | select(.severity == "CRITICAL")] | length')
|
|
269
|
+
MAJOR=$(echo "$ISSUES_DATA" | jq '[.issues[] | select(.severity == "MAJOR")] | length')
|
|
270
|
+
MINOR=$(echo "$ISSUES_DATA" | jq '[.issues[] | select(.severity == "MINOR")] | length')
|
|
271
|
+
|
|
272
|
+
# Count security hotspots
|
|
273
|
+
HOTSPOTS_TOTAL=$(echo "$HOTSPOTS_DATA" | jq '.paging.total // 0')
|
|
274
|
+
HOTSPOTS_TO_REVIEW=$(echo "$HOTSPOTS_DATA" | jq '[.hotspots[] | select(.status == "TO_REVIEW")] | length')
|
|
275
|
+
HOTSPOTS_REVIEWED=$(echo "$HOTSPOTS_DATA" | jq '[.hotspots[] | select(.status == "REVIEWED")] | length')
|
|
276
|
+
|
|
277
|
+
# Get quality gate status
|
|
278
|
+
QG_STATUS=$(echo "$QG_DATA" | jq -r '.projectStatus.status // "NONE"')
|
|
279
|
+
QG_CONDITIONS=$(echo "$QG_DATA" | jq -r '.projectStatus.conditions[]? | select(.status != "OK") | " - \(.metricKey): \(.actualValue) (threshold: \(.errorThreshold // .warningThreshold))"' 2>/dev/null || echo "")
|
|
280
|
+
|
|
281
|
+
# Generate report
|
|
282
|
+
{
|
|
283
|
+
echo "SonarQube Analysis Report"
|
|
284
|
+
echo "========================="
|
|
285
|
+
echo "Project: $PROJECT_KEY"
|
|
286
|
+
echo "Analysis: $ANALYSIS_TYPE"
|
|
287
|
+
echo "Date: $(date)"
|
|
288
|
+
echo ""
|
|
289
|
+
|
|
290
|
+
# Quality Gate Status
|
|
291
|
+
echo "Quality Gate: $QG_STATUS"
|
|
292
|
+
if [ "$QG_STATUS" != "OK" ] && [ -n "$QG_CONDITIONS" ]; then
|
|
293
|
+
echo "Failed Conditions:"
|
|
294
|
+
echo "$QG_CONDITIONS"
|
|
295
|
+
fi
|
|
296
|
+
echo ""
|
|
297
|
+
|
|
298
|
+
# Issues Summary
|
|
299
|
+
echo "Issues: $TOTAL"
|
|
300
|
+
if [ "$TOTAL" -gt 0 ]; then
|
|
301
|
+
echo "By Severity:"
|
|
302
|
+
[ "$BLOCKER" -gt 0 ] && echo " Blocker: $BLOCKER"
|
|
303
|
+
[ "$CRITICAL" -gt 0 ] && echo " Critical: $CRITICAL"
|
|
304
|
+
[ "$MAJOR" -gt 0 ] && echo " Major: $MAJOR"
|
|
305
|
+
[ "$MINOR" -gt 0 ] && echo " Minor: $MINOR"
|
|
306
|
+
fi
|
|
307
|
+
echo ""
|
|
308
|
+
|
|
309
|
+
# Security Hotspots Summary
|
|
310
|
+
echo "Security Hotspots: $HOTSPOTS_TOTAL"
|
|
311
|
+
if [ "$HOTSPOTS_TOTAL" -gt 0 ]; then
|
|
312
|
+
echo " To Review: $HOTSPOTS_TO_REVIEW"
|
|
313
|
+
echo " Reviewed: $HOTSPOTS_REVIEWED"
|
|
314
|
+
fi
|
|
315
|
+
echo ""
|
|
316
|
+
|
|
317
|
+
# Coverage Summary
|
|
318
|
+
COVERAGE=$(echo "$COVERAGE_DATA" | jq -r '.component.measures[]? | select(.metric == "coverage") | .value' 2>/dev/null || echo "")
|
|
319
|
+
NEW_COVERAGE=$(echo "$COVERAGE_DATA" | jq -r '.component.measures[]? | select(.metric == "new_coverage") | .periods[0].value' 2>/dev/null || echo "")
|
|
320
|
+
LINES_TO_COVER=$(echo "$COVERAGE_DATA" | jq -r '.component.measures[]? | select(.metric == "lines_to_cover") | .value' 2>/dev/null || echo "")
|
|
321
|
+
UNCOVERED_LINES=$(echo "$COVERAGE_DATA" | jq -r '.component.measures[]? | select(.metric == "uncovered_lines") | .value' 2>/dev/null || echo "")
|
|
322
|
+
NEW_LINES_TO_COVER=$(echo "$COVERAGE_DATA" | jq -r '.component.measures[]? | select(.metric == "new_lines_to_cover") | .periods[0].value' 2>/dev/null || echo "")
|
|
323
|
+
NEW_UNCOVERED_LINES=$(echo "$COVERAGE_DATA" | jq -r '.component.measures[]? | select(.metric == "new_uncovered_lines") | .periods[0].value' 2>/dev/null || echo "")
|
|
324
|
+
|
|
325
|
+
if [ -n "$COVERAGE" ] || [ -n "$NEW_COVERAGE" ]; then
|
|
326
|
+
echo "Coverage:"
|
|
327
|
+
if [ -n "$COVERAGE" ]; then
|
|
328
|
+
echo " Overall: ${COVERAGE}%"
|
|
329
|
+
if [ -n "$LINES_TO_COVER" ] && [ -n "$UNCOVERED_LINES" ]; then
|
|
330
|
+
COVERED_LINES=$((LINES_TO_COVER - UNCOVERED_LINES))
|
|
331
|
+
echo " Covered: $COVERED_LINES / $LINES_TO_COVER lines"
|
|
332
|
+
fi
|
|
333
|
+
fi
|
|
334
|
+
if [ -n "$NEW_COVERAGE" ]; then
|
|
335
|
+
echo " New Code: ${NEW_COVERAGE}%"
|
|
336
|
+
if [ -n "$NEW_LINES_TO_COVER" ] && [ -n "$NEW_UNCOVERED_LINES" ]; then
|
|
337
|
+
NEW_COVERED_LINES=$(echo "$NEW_LINES_TO_COVER - $NEW_UNCOVERED_LINES" | bc)
|
|
338
|
+
echo " Covered: $NEW_COVERED_LINES / $NEW_LINES_TO_COVER new lines"
|
|
339
|
+
fi
|
|
340
|
+
# Highlight if below 80% threshold
|
|
341
|
+
if [ -n "$NEW_COVERAGE" ] && (( $(echo "$NEW_COVERAGE < 80" | bc -l) )); then
|
|
342
|
+
echo " ⚠ Below 80% threshold"
|
|
343
|
+
fi
|
|
344
|
+
fi
|
|
345
|
+
echo ""
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
# Detailed Issues
|
|
349
|
+
if [ "$TOTAL" -gt 0 ]; then
|
|
350
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
351
|
+
echo "DETAILED ISSUES"
|
|
352
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
353
|
+
echo ""
|
|
354
|
+
|
|
355
|
+
echo "$ISSUES_DATA" | jq -r '.issues[] |
|
|
356
|
+
"[\(.severity)] \(.component | split(":")[1]):\(.line // "??")\n" +
|
|
357
|
+
"Type: \(.type)\n" +
|
|
358
|
+
"Rule: \(.rule)\n" +
|
|
359
|
+
"Message: \(.message)\n" +
|
|
360
|
+
"───────────────────────────────────────────────────────────────"'
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
# Detailed Security Hotspots
|
|
364
|
+
if [ "$HOTSPOTS_TOTAL" -gt 0 ]; then
|
|
365
|
+
echo ""
|
|
366
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
367
|
+
echo "SECURITY HOTSPOTS"
|
|
368
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
369
|
+
echo ""
|
|
370
|
+
|
|
371
|
+
echo "$HOTSPOTS_DATA" | jq -r '.hotspots[] |
|
|
372
|
+
"[\(.vulnerabilityProbability)] \(.component | split(":")[1]):\(.line // "??")\n" +
|
|
373
|
+
"Status: \(.status)\n" +
|
|
374
|
+
"Category: \(.securityCategory)\n" +
|
|
375
|
+
"Message: \(.message)\n" +
|
|
376
|
+
"───────────────────────────────────────────────────────────────"'
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
# Summary at bottom
|
|
380
|
+
if [ "$TOTAL" -eq 0 ] && [ "$HOTSPOTS_TOTAL" -eq 0 ]; then
|
|
381
|
+
echo "No issues or security hotspots found."
|
|
382
|
+
fi
|
|
383
|
+
} > "$REPORT_FILE"
|
|
384
|
+
|
|
385
|
+
# Calculate total findings
|
|
386
|
+
TOTAL_FINDINGS=$((TOTAL + HOTSPOTS_TOTAL))
|
|
387
|
+
|
|
388
|
+
# Display summary
|
|
389
|
+
if [ "$QG_STATUS" != "OK" ]; then
|
|
390
|
+
echo -e "${RED}❌ Quality Gate: $QG_STATUS${NC}"
|
|
391
|
+
else
|
|
392
|
+
echo -e "${GREEN}✓${NC} Quality Gate: $QG_STATUS"
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
if [ "$TOTAL_FINDINGS" -gt 0 ]; then
|
|
396
|
+
echo -e "${CYAN}ℹ${NC} Downloaded $TOTAL issues + $HOTSPOTS_TOTAL hotspots → $REPORT_FILE"
|
|
397
|
+
|
|
398
|
+
# Open in VSCode only if findings exist
|
|
399
|
+
if command -v code &> /dev/null; then
|
|
400
|
+
code "$REPORT_FILE" 2>/dev/null || true
|
|
401
|
+
elif command -v code-insiders &> /dev/null; then
|
|
402
|
+
code-insiders "$REPORT_FILE" 2>/dev/null || true
|
|
403
|
+
fi
|
|
404
|
+
else
|
|
405
|
+
echo -e "${GREEN}✓${NC} No issues or hotspots found"
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
issues:
|
|
409
|
+
desc: Display most recent SonarQube findings
|
|
410
|
+
aliases: [i]
|
|
411
|
+
silent: true
|
|
412
|
+
cmds:
|
|
413
|
+
- |
|
|
414
|
+
# Color codes
|
|
415
|
+
GREEN='\033[0;32m'
|
|
416
|
+
RED='\033[0;31m'
|
|
417
|
+
NC='\033[0m'
|
|
418
|
+
|
|
419
|
+
# Find most recent report
|
|
420
|
+
LATEST_REPORT=$(ls -t {{.REPORT_PATH}}/issues-*.txt 2>/dev/null | head -1)
|
|
421
|
+
|
|
422
|
+
if [ -z "$LATEST_REPORT" ]; then
|
|
423
|
+
echo -e "${RED}❌ No reports found${NC}" >&2
|
|
424
|
+
echo "Run: task sonar:download" >&2
|
|
425
|
+
exit 1
|
|
426
|
+
fi
|
|
427
|
+
|
|
428
|
+
# Extract summary
|
|
429
|
+
TOTAL=$(grep "^Total issues:" "$LATEST_REPORT" | awk '{print $3}')
|
|
430
|
+
echo -e "${GREEN}✓${NC} Found $TOTAL issues → $LATEST_REPORT"
|
|
431
|
+
|
|
432
|
+
# Open in VSCode
|
|
433
|
+
if command -v code &> /dev/null; then
|
|
434
|
+
code "$LATEST_REPORT" 2>/dev/null || true
|
|
435
|
+
elif command -v code-insiders &> /dev/null; then
|
|
436
|
+
code-insiders "$LATEST_REPORT" 2>/dev/null || true
|
|
437
|
+
fi
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Taskfile.yml - Project task automation
|
|
2
|
+
# Generated by @northbridge-security/ai-toolkit
|
|
3
|
+
#
|
|
4
|
+
# This file references shared task definitions from the ai-toolkit npm package.
|
|
5
|
+
# Customize by adding project-specific tasks or overriding shared tasks.
|
|
6
|
+
|
|
7
|
+
version: '3'
|
|
8
|
+
|
|
9
|
+
# Path to ai-toolkit tasks in node_modules
|
|
10
|
+
vars:
|
|
11
|
+
TASK_LIB: node_modules/@northbridge-security/ai-toolkit/tasks
|
|
12
|
+
|
|
13
|
+
# Include shared task libraries
|
|
14
|
+
includes:
|
|
15
|
+
# Flatten common tasks (accessible as `task build`, `task lint`, etc.)
|
|
16
|
+
# common:
|
|
17
|
+
# taskfile: '{{.TASK_LIB}}/{{PROJECT_TYPE}}.yml'
|
|
18
|
+
# flatten: true
|
|
19
|
+
# optional: true
|
|
20
|
+
|
|
21
|
+
# Namespaced specialized tasks (accessible as `task git:hooks`, etc.)
|
|
22
|
+
git:
|
|
23
|
+
taskfile: '{{.TASK_LIB}}/git.yml'
|
|
24
|
+
optional: true
|
|
25
|
+
|
|
26
|
+
yaml:
|
|
27
|
+
taskfile: '{{.TASK_LIB}}/yaml.yml'
|
|
28
|
+
optional: true
|
|
29
|
+
|
|
30
|
+
markdown:
|
|
31
|
+
taskfile: '{{.TASK_LIB}}/markdown.yml'
|
|
32
|
+
optional: true
|
|
33
|
+
|
|
34
|
+
json:
|
|
35
|
+
taskfile: '{{.TASK_LIB}}/json.yml'
|
|
36
|
+
optional: true
|
|
37
|
+
|
|
38
|
+
bash:
|
|
39
|
+
taskfile: '{{.TASK_LIB}}/bash.yml'
|
|
40
|
+
optional: true
|
|
41
|
+
|
|
42
|
+
gotask:
|
|
43
|
+
taskfile: '{{.TASK_LIB}}/gotask.yml'
|
|
44
|
+
optional: true
|
|
45
|
+
|
|
46
|
+
claude:
|
|
47
|
+
taskfile: '{{.TASK_LIB}}/claude.yml'
|
|
48
|
+
optional: true
|
|
49
|
+
|
|
50
|
+
op:
|
|
51
|
+
taskfile: '{{.TASK_LIB}}/onepassword.yml'
|
|
52
|
+
aliases: [1p]
|
|
53
|
+
optional: true
|
|
54
|
+
|
|
55
|
+
{{GXP_SECTION}}
|
|
56
|
+
# Project-specific tasks
|
|
57
|
+
tasks:
|
|
58
|
+
default:
|
|
59
|
+
desc: 'Show available tasks'
|
|
60
|
+
aliases: [help, h]
|
|
61
|
+
silent: true
|
|
62
|
+
cmds:
|
|
63
|
+
- |
|
|
64
|
+
echo "Available Tasks"
|
|
65
|
+
echo ""
|
|
66
|
+
echo "Run 'task --list' to see all available tasks from ai-toolkit"
|
|
67
|
+
echo "Run 'task NAMESPACE:COMMAND' to execute a specific task"
|
|
68
|
+
echo ""
|
|
69
|
+
echo "Example commands:"
|
|
70
|
+
echo " task git:hooks # Install git hooks"
|
|
71
|
+
echo " task yaml:lint # Lint YAML files"
|
|
72
|
+
echo " task markdown:lint # Lint Markdown files"
|
|
73
|
+
echo ""
|
|
74
|
+
echo "Add your project-specific tasks to this Taskfile.yml"
|