sequant 1.17.0 → 1.18.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +14 -2
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +21 -0
- package/dist/marketplace/external_plugins/sequant/README.md +38 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +292 -0
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +463 -0
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/prompt-templates.md +350 -0
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +131 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +474 -0
- package/dist/marketplace/external_plugins/sequant/skills/clean/SKILL.md +211 -0
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +337 -0
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +807 -0
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +678 -0
- package/dist/marketplace/external_plugins/sequant/skills/improve/SKILL.md +668 -0
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +374 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +570 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-quality-exemplars.md +107 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +65 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +179 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/semgrep-rules.md +207 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +109 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +622 -0
- package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +175 -0
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +70 -0
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +95 -0
- package/dist/marketplace/external_plugins/sequant/skills/security-review/SKILL.md +358 -0
- package/dist/marketplace/external_plugins/sequant/skills/security-review/references/security-checklists.md +432 -0
- package/dist/marketplace/external_plugins/sequant/skills/solve/SKILL.md +697 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +754 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/parallel-groups.md +72 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +92 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +104 -0
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +600 -0
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +576 -0
- package/dist/marketplace/external_plugins/sequant/skills/verify/SKILL.md +281 -0
- package/dist/src/commands/run.d.ts +13 -280
- package/dist/src/commands/run.js +23 -1956
- package/dist/src/commands/sync.js +3 -0
- package/dist/src/commands/update.js +3 -0
- package/dist/src/lib/plugin-version-sync.d.ts +2 -1
- package/dist/src/lib/plugin-version-sync.js +28 -7
- package/dist/src/lib/solve-comment-parser.d.ts +26 -0
- package/dist/src/lib/solve-comment-parser.js +63 -7
- package/dist/src/lib/workflow/batch-executor.d.ts +117 -0
- package/dist/src/lib/workflow/batch-executor.js +574 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +40 -0
- package/dist/src/lib/workflow/phase-executor.js +381 -0
- package/dist/src/lib/workflow/phase-mapper.d.ts +65 -0
- package/dist/src/lib/workflow/phase-mapper.js +147 -0
- package/dist/src/lib/workflow/pr-operations.d.ts +86 -0
- package/dist/src/lib/workflow/pr-operations.js +326 -0
- package/dist/src/lib/workflow/pr-status.d.ts +9 -7
- package/dist/src/lib/workflow/pr-status.js +13 -11
- package/dist/src/lib/workflow/run-summary.d.ts +36 -0
- package/dist/src/lib/workflow/run-summary.js +142 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +205 -0
- package/dist/src/lib/workflow/worktree-manager.js +918 -0
- package/package.json +3 -1
- package/templates/skills/fullsolve/SKILL.md +11 -1
- package/templates/skills/qa/SKILL.md +41 -1
- package/templates/skills/solve/SKILL.md +86 -0
- package/templates/skills/spec/SKILL.md +53 -0
- package/templates/skills/test/SKILL.md +10 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Quality checks script for /qa command
|
|
3
|
+
# Run these checks before detailed review
|
|
4
|
+
#
|
|
5
|
+
# Supports caching to skip unchanged checks on re-run:
|
|
6
|
+
# --use-cache Enable caching (default when cache exists)
|
|
7
|
+
# --no-cache Force fresh run, ignore cache (AC-3)
|
|
8
|
+
# --cache-dir Custom cache directory (default: .sequant/.cache/qa)
|
|
9
|
+
# --cache-key Custom cache key prefix
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# Configuration
|
|
15
|
+
# =============================================================================
|
|
16
|
+
|
|
17
|
+
USE_CACHE=true
|
|
18
|
+
CACHE_DIR=".sequant/.cache/qa"
|
|
19
|
+
CACHE_KEY=""
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
|
|
22
|
+
# Parse command line arguments
|
|
23
|
+
while [[ $# -gt 0 ]]; do
|
|
24
|
+
case $1 in
|
|
25
|
+
--use-cache)
|
|
26
|
+
USE_CACHE=true
|
|
27
|
+
shift
|
|
28
|
+
;;
|
|
29
|
+
--no-cache)
|
|
30
|
+
USE_CACHE=false
|
|
31
|
+
shift
|
|
32
|
+
;;
|
|
33
|
+
--cache-dir)
|
|
34
|
+
CACHE_DIR="$2"
|
|
35
|
+
shift 2
|
|
36
|
+
;;
|
|
37
|
+
--cache-key)
|
|
38
|
+
CACHE_KEY="$2"
|
|
39
|
+
shift 2
|
|
40
|
+
;;
|
|
41
|
+
*)
|
|
42
|
+
shift
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
# Resolve script directory for cache CLI
|
|
48
|
+
# Try multiple paths since we might be in a worktree or main repo
|
|
49
|
+
CACHE_CLI=""
|
|
50
|
+
if [[ -f "$SCRIPT_DIR/../../../scripts/qa/qa-cache-cli.ts" ]]; then
|
|
51
|
+
CACHE_CLI="$SCRIPT_DIR/../../../scripts/qa/qa-cache-cli.ts"
|
|
52
|
+
elif [[ -f "scripts/qa/qa-cache-cli.ts" ]]; then
|
|
53
|
+
CACHE_CLI="scripts/qa/qa-cache-cli.ts"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# =============================================================================
|
|
57
|
+
# Cache Helper Functions
|
|
58
|
+
# =============================================================================
|
|
59
|
+
|
|
60
|
+
# Check if a check has valid cached result
|
|
61
|
+
# Returns: 0 = cache hit, 1 = cache miss
|
|
62
|
+
cache_check() {
|
|
63
|
+
local check_type=$1
|
|
64
|
+
|
|
65
|
+
if [[ "$USE_CACHE" != "true" ]] || [[ -z "$CACHE_CLI" ]]; then
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if npx tsx "$CACHE_CLI" check "$check_type" 2>/dev/null | grep -q "^HIT"; then
|
|
70
|
+
return 0
|
|
71
|
+
else
|
|
72
|
+
return 1
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Get cached result for a check
|
|
77
|
+
cache_get() {
|
|
78
|
+
local check_type=$1
|
|
79
|
+
|
|
80
|
+
if [[ "$USE_CACHE" != "true" ]] || [[ -z "$CACHE_CLI" ]]; then
|
|
81
|
+
return 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
npx tsx "$CACHE_CLI" get "$check_type" 2>/dev/null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Cache a check result
|
|
88
|
+
cache_set() {
|
|
89
|
+
local check_type=$1
|
|
90
|
+
local passed=$2
|
|
91
|
+
local message=$3
|
|
92
|
+
local details=${4:-"{}"}
|
|
93
|
+
|
|
94
|
+
if [[ "$USE_CACHE" != "true" ]] || [[ -z "$CACHE_CLI" ]]; then
|
|
95
|
+
return 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo "{\"passed\":$passed,\"message\":\"$message\",\"details\":$details}" | \
|
|
99
|
+
npx tsx "$CACHE_CLI" set "$check_type" 2>/dev/null || true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Print cache status report (AC-4)
|
|
103
|
+
print_cache_status() {
|
|
104
|
+
if [[ "$USE_CACHE" != "true" ]] || [[ -z "$CACHE_CLI" ]]; then
|
|
105
|
+
echo "Cache: Disabled"
|
|
106
|
+
return
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
echo ""
|
|
110
|
+
echo "### Cache Status"
|
|
111
|
+
echo ""
|
|
112
|
+
npx tsx "$CACHE_CLI" status 2>/dev/null || echo "Cache: Error reading status"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# =============================================================================
|
|
116
|
+
# Main Script
|
|
117
|
+
# =============================================================================
|
|
118
|
+
|
|
119
|
+
echo "🔍 Running automated quality checks..."
|
|
120
|
+
if [[ "$USE_CACHE" == "true" && -n "$CACHE_CLI" ]]; then
|
|
121
|
+
echo " Cache: Enabled (use --no-cache to force fresh run)"
|
|
122
|
+
else
|
|
123
|
+
echo " Cache: Disabled"
|
|
124
|
+
fi
|
|
125
|
+
echo ""
|
|
126
|
+
|
|
127
|
+
# Track cache hits/misses for final report
|
|
128
|
+
declare -A CACHE_STATUS
|
|
129
|
+
|
|
130
|
+
# =============================================================================
|
|
131
|
+
# 1. Type safety check - detect 'any' type usage
|
|
132
|
+
# =============================================================================
|
|
133
|
+
if cache_check "type-safety"; then
|
|
134
|
+
CACHE_STATUS["type-safety"]="HIT"
|
|
135
|
+
cached_result=$(cache_get "type-safety")
|
|
136
|
+
type_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
|
|
137
|
+
type_message=$(echo "$cached_result" | grep -o '"message":\s*"[^"]*"' | cut -d'"' -f4)
|
|
138
|
+
|
|
139
|
+
if [[ "$type_passed" == "true" ]]; then
|
|
140
|
+
echo "✅ Type safety: $type_message (cached)"
|
|
141
|
+
else
|
|
142
|
+
type_issues=$(echo "$cached_result" | grep -o '"count":\s*[0-9]*' | cut -d: -f2 | tr -d ' ')
|
|
143
|
+
echo "⚠️ WARNING: $type_issues potential 'any' type usages (cached)"
|
|
144
|
+
fi
|
|
145
|
+
else
|
|
146
|
+
CACHE_STATUS["type-safety"]="MISS"
|
|
147
|
+
type_issues=$(git diff main...HEAD | grep -E ":\s*any[,)]|as any" | wc -l | xargs)
|
|
148
|
+
if [[ $type_issues -gt 0 ]]; then
|
|
149
|
+
echo "⚠️ WARNING: $type_issues potential 'any' type usages"
|
|
150
|
+
cache_set "type-safety" false "Found $type_issues potential 'any' type usages" "{\"count\":$type_issues}"
|
|
151
|
+
else
|
|
152
|
+
echo "✅ Type safety: No 'any' type additions"
|
|
153
|
+
cache_set "type-safety" true "No 'any' type additions" "{\"count\":0}"
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# =============================================================================
|
|
158
|
+
# 2. Deleted tests check
|
|
159
|
+
# =============================================================================
|
|
160
|
+
if cache_check "deleted-tests"; then
|
|
161
|
+
CACHE_STATUS["deleted-tests"]="HIT"
|
|
162
|
+
cached_result=$(cache_get "deleted-tests")
|
|
163
|
+
tests_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
|
|
164
|
+
|
|
165
|
+
if [[ "$tests_passed" == "true" ]]; then
|
|
166
|
+
echo "✅ Test coverage: No test files deleted (cached)"
|
|
167
|
+
else
|
|
168
|
+
deleted_count=$(echo "$cached_result" | grep -o '"count":\s*[0-9]*' | cut -d: -f2 | tr -d ' ')
|
|
169
|
+
echo "❌ BLOCKER: $deleted_count test files deleted (cached)"
|
|
170
|
+
fi
|
|
171
|
+
else
|
|
172
|
+
CACHE_STATUS["deleted-tests"]="MISS"
|
|
173
|
+
deleted_tests=$(git diff main...HEAD --diff-filter=D --name-only | grep -E "\\.test\\.|\\spec\\." | wc -l | xargs)
|
|
174
|
+
if [[ $deleted_tests -gt 0 ]]; then
|
|
175
|
+
echo "❌ BLOCKER: $deleted_tests test files deleted"
|
|
176
|
+
cache_set "deleted-tests" false "Found $deleted_tests deleted test files" "{\"count\":$deleted_tests}"
|
|
177
|
+
else
|
|
178
|
+
echo "✅ Test coverage: No test files deleted"
|
|
179
|
+
cache_set "deleted-tests" true "No test files deleted" "{\"count\":0}"
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# =============================================================================
|
|
184
|
+
# 3. Scope check - files changed (always fresh - cheap operation)
|
|
185
|
+
# =============================================================================
|
|
186
|
+
CACHE_STATUS["scope"]="SKIP"
|
|
187
|
+
files_changed=$(git diff main...HEAD --name-only | wc -l | xargs)
|
|
188
|
+
echo "📊 Files changed: $files_changed"
|
|
189
|
+
|
|
190
|
+
# =============================================================================
|
|
191
|
+
# 4. Size check - LOC added/removed (always fresh - cheap operation)
|
|
192
|
+
# =============================================================================
|
|
193
|
+
CACHE_STATUS["size"]="SKIP"
|
|
194
|
+
additions=$(git diff main...HEAD --numstat | awk '{sum+=$1} END {print sum+0}')
|
|
195
|
+
deletions=$(git diff main...HEAD --numstat | awk '{sum+=$2} END {print sum+0}')
|
|
196
|
+
net_change=$((additions - deletions))
|
|
197
|
+
echo "📊 Diff size: +$additions -$deletions (net: $net_change lines)"
|
|
198
|
+
|
|
199
|
+
# 5. AC proportionality assessment
|
|
200
|
+
echo ""
|
|
201
|
+
if [[ $net_change -lt 100 ]]; then
|
|
202
|
+
echo "✅ Size: Small change (<100 net LOC)"
|
|
203
|
+
elif [[ $net_change -lt 300 ]]; then
|
|
204
|
+
echo "✅ Size: Medium change (100-300 net LOC)"
|
|
205
|
+
elif [[ $net_change -lt 500 ]]; then
|
|
206
|
+
echo "⚠️ Size: Large change (300-500 net LOC) - verify proportional to AC"
|
|
207
|
+
else
|
|
208
|
+
echo "❌ Size: Very large (>500 net LOC) - may indicate scope creep"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# =============================================================================
|
|
212
|
+
# 6. Database access check (always fresh - cheap operation)
|
|
213
|
+
# =============================================================================
|
|
214
|
+
echo ""
|
|
215
|
+
echo "🔒 Checking database access patterns..."
|
|
216
|
+
admin_files=$(git diff main...HEAD --name-only | grep -E "^app/admin/" || true)
|
|
217
|
+
if [[ -n "$admin_files" ]]; then
|
|
218
|
+
echo " Admin files modified - manually verify proper database access controls"
|
|
219
|
+
echo " (admin pages should use service/admin clients, not anonymous clients)"
|
|
220
|
+
else
|
|
221
|
+
echo " No admin files modified"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# =============================================================================
|
|
225
|
+
# 7. Integration check - verify new exports are imported somewhere
|
|
226
|
+
# =============================================================================
|
|
227
|
+
echo ""
|
|
228
|
+
echo "🔌 Checking integration of new exports..."
|
|
229
|
+
new_files=$(git diff main...HEAD --name-only --diff-filter=A | grep -E "\.(ts|tsx)$" || true)
|
|
230
|
+
if [[ -n "$new_files" ]]; then
|
|
231
|
+
unintegrated=0
|
|
232
|
+
for file in $new_files; do
|
|
233
|
+
if [[ -f "$file" ]]; then
|
|
234
|
+
exports=$(grep -oE "export (const|function|class|type|interface) ([A-Za-z_][A-Za-z0-9_]*)" "$file" 2>/dev/null | awk '{print $3}' || true)
|
|
235
|
+
for exp in $exports; do
|
|
236
|
+
if [[ -n "$exp" ]]; then
|
|
237
|
+
import_count=$(grep -r "import.*$exp" --include="*.ts" --include="*.tsx" . 2>/dev/null | grep -v "$file" | wc -l | xargs)
|
|
238
|
+
if [[ $import_count -eq 0 ]]; then
|
|
239
|
+
echo "⚠️ WARNING: '$exp' exported from $file but never imported"
|
|
240
|
+
unintegrated=$((unintegrated + 1))
|
|
241
|
+
fi
|
|
242
|
+
fi
|
|
243
|
+
done
|
|
244
|
+
fi
|
|
245
|
+
done
|
|
246
|
+
if [[ $unintegrated -eq 0 ]]; then
|
|
247
|
+
echo "✅ Integration: All exports are imported"
|
|
248
|
+
fi
|
|
249
|
+
else
|
|
250
|
+
echo " No new TypeScript files added"
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# =============================================================================
|
|
254
|
+
# 8. Security scan - OWASP vulnerability checks (cacheable)
|
|
255
|
+
# =============================================================================
|
|
256
|
+
echo ""
|
|
257
|
+
echo "🔒 Running security scan..."
|
|
258
|
+
if cache_check "security"; then
|
|
259
|
+
CACHE_STATUS["security"]="HIT"
|
|
260
|
+
cached_result=$(cache_get "security")
|
|
261
|
+
security_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
|
|
262
|
+
security_message=$(echo "$cached_result" | grep -o '"message":\s*"[^"]*"' | cut -d'"' -f4)
|
|
263
|
+
|
|
264
|
+
if [[ "$security_passed" == "true" ]]; then
|
|
265
|
+
echo " ✅ Security scan: $security_message (cached)"
|
|
266
|
+
else
|
|
267
|
+
echo " ⚠️ Security scan: $security_message (cached)"
|
|
268
|
+
fi
|
|
269
|
+
else
|
|
270
|
+
CACHE_STATUS["security"]="MISS"
|
|
271
|
+
if command -v npx &> /dev/null; then
|
|
272
|
+
security_output=$(npx tsx scripts/lib/__tests__/run-security-scan.ts 2>&1) || security_exit=$?
|
|
273
|
+
if [[ -z "$security_exit" || "$security_exit" -eq 0 ]]; then
|
|
274
|
+
echo " ✅ Security scan: Passed"
|
|
275
|
+
cache_set "security" true "Passed"
|
|
276
|
+
else
|
|
277
|
+
echo " ⚠️ Security scan: Issues found"
|
|
278
|
+
echo "$security_output" | head -5
|
|
279
|
+
cache_set "security" false "Issues found"
|
|
280
|
+
fi
|
|
281
|
+
else
|
|
282
|
+
echo " Security scanner not available, skipping..."
|
|
283
|
+
CACHE_STATUS["security"]="SKIP"
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
# =============================================================================
|
|
288
|
+
# 9. Semgrep static analysis (cacheable - expensive operation)
|
|
289
|
+
# =============================================================================
|
|
290
|
+
echo ""
|
|
291
|
+
echo "🔍 Running Semgrep static analysis..."
|
|
292
|
+
|
|
293
|
+
if cache_check "semgrep"; then
|
|
294
|
+
CACHE_STATUS["semgrep"]="HIT"
|
|
295
|
+
cached_result=$(cache_get "semgrep")
|
|
296
|
+
semgrep_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
|
|
297
|
+
semgrep_message=$(echo "$cached_result" | grep -o '"message":\s*"[^"]*"' | cut -d'"' -f4)
|
|
298
|
+
|
|
299
|
+
echo " $semgrep_message (cached)"
|
|
300
|
+
else
|
|
301
|
+
CACHE_STATUS["semgrep"]="MISS"
|
|
302
|
+
|
|
303
|
+
# Check if Semgrep is available
|
|
304
|
+
semgrep_available=false
|
|
305
|
+
if command -v semgrep &> /dev/null; then
|
|
306
|
+
semgrep_available=true
|
|
307
|
+
semgrep_cmd="semgrep"
|
|
308
|
+
elif command -v npx &> /dev/null && npx semgrep --version &> /dev/null 2>&1; then
|
|
309
|
+
semgrep_available=true
|
|
310
|
+
semgrep_cmd="npx semgrep"
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
if [[ "$semgrep_available" == "true" ]]; then
|
|
314
|
+
# Get changed files for targeted scan
|
|
315
|
+
changed_files=$(git diff main...HEAD --name-only | grep -E '\.(ts|tsx|js|jsx|py|go|rs)$' || true)
|
|
316
|
+
|
|
317
|
+
if [[ -n "$changed_files" ]]; then
|
|
318
|
+
# Run Semgrep with security rules on changed files
|
|
319
|
+
echo " Scanning $(echo "$changed_files" | wc -l | xargs) changed file(s)..."
|
|
320
|
+
|
|
321
|
+
# Run with basic security rules, JSON output for reliable parsing
|
|
322
|
+
# Use --quiet to suppress progress
|
|
323
|
+
semgrep_output=$($semgrep_cmd --config p/security-audit --config p/secrets \
|
|
324
|
+
--quiet --no-git-ignore --json \
|
|
325
|
+
$changed_files 2>&1) || semgrep_exit=$?
|
|
326
|
+
|
|
327
|
+
if [[ -z "$semgrep_output" ]] || ! echo "$semgrep_output" | grep -q '"results"'; then
|
|
328
|
+
echo " ✅ Semgrep: No security issues found"
|
|
329
|
+
cache_set "semgrep" true "✅ Semgrep: No security issues found" "{\"critical\":0,\"warning\":0}"
|
|
330
|
+
else
|
|
331
|
+
# Count findings by severity from JSON output
|
|
332
|
+
critical_count=$(echo "$semgrep_output" | grep -o '"severity":"ERROR"' | wc -l | xargs)
|
|
333
|
+
warning_count=$(echo "$semgrep_output" | grep -o '"severity":"WARNING"' | wc -l | xargs)
|
|
334
|
+
|
|
335
|
+
if [[ "$critical_count" -gt 0 ]]; then
|
|
336
|
+
echo " ❌ Semgrep: $critical_count critical finding(s) - REVIEW REQUIRED"
|
|
337
|
+
cache_set "semgrep" false "❌ Semgrep: $critical_count critical finding(s)" "{\"critical\":$critical_count,\"warning\":$warning_count}"
|
|
338
|
+
elif [[ "$warning_count" -gt 0 ]]; then
|
|
339
|
+
echo " ⚠️ Semgrep: $warning_count warning(s)"
|
|
340
|
+
cache_set "semgrep" true "⚠️ Semgrep: $warning_count warning(s)" "{\"critical\":0,\"warning\":$warning_count}"
|
|
341
|
+
else
|
|
342
|
+
echo " ✅ Semgrep: No security issues found"
|
|
343
|
+
cache_set "semgrep" true "✅ Semgrep: No security issues found" "{\"critical\":0,\"warning\":0}"
|
|
344
|
+
fi
|
|
345
|
+
fi
|
|
346
|
+
else
|
|
347
|
+
echo " No source files changed, skipping Semgrep scan"
|
|
348
|
+
CACHE_STATUS["semgrep"]="SKIP"
|
|
349
|
+
fi
|
|
350
|
+
else
|
|
351
|
+
echo " ⚠️ Semgrep not installed (optional)"
|
|
352
|
+
echo " Install with: pip install semgrep"
|
|
353
|
+
CACHE_STATUS["semgrep"]="SKIP"
|
|
354
|
+
fi
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
# =============================================================================
|
|
358
|
+
# 10. Shell Script Semantic Checks (unused functions, integration)
|
|
359
|
+
# =============================================================================
|
|
360
|
+
echo ""
|
|
361
|
+
echo "🔍 Checking shell script semantics..."
|
|
362
|
+
shell_scripts=$(git diff main...HEAD --name-only | grep -E '\.sh$' || true)
|
|
363
|
+
if [[ -n "$shell_scripts" ]]; then
|
|
364
|
+
for script in $shell_scripts; do
|
|
365
|
+
if [[ -f "$script" ]]; then
|
|
366
|
+
echo " Analyzing: $script"
|
|
367
|
+
# Extract function definitions and check if they're called
|
|
368
|
+
funcs=$(grep -oE "^[a-zA-Z_][a-zA-Z0-9_]*\(\)" "$script" 2>/dev/null | sed 's/()//' || true)
|
|
369
|
+
unused_count=0
|
|
370
|
+
for func in $funcs; do
|
|
371
|
+
# Count calls (excluding the definition line)
|
|
372
|
+
call_count=$(grep -c "\b${func}\b" "$script" 2>/dev/null || echo "0")
|
|
373
|
+
if [[ $call_count -lt 2 ]]; then # Only definition, no calls
|
|
374
|
+
echo " ⚠️ Function '$func' defined but possibly not called"
|
|
375
|
+
unused_count=$((unused_count + 1))
|
|
376
|
+
fi
|
|
377
|
+
done
|
|
378
|
+
if [[ $unused_count -eq 0 && -n "$funcs" ]]; then
|
|
379
|
+
echo " ✅ All functions are called"
|
|
380
|
+
fi
|
|
381
|
+
fi
|
|
382
|
+
done
|
|
383
|
+
else
|
|
384
|
+
echo " No shell scripts changed"
|
|
385
|
+
fi
|
|
386
|
+
|
|
387
|
+
# =============================================================================
|
|
388
|
+
# 11. Build Verification (cacheable - expensive operation)
|
|
389
|
+
# =============================================================================
|
|
390
|
+
|
|
391
|
+
verify_build_against_main() {
|
|
392
|
+
local feature_exit_code=$1
|
|
393
|
+
local feature_error_output=$2
|
|
394
|
+
|
|
395
|
+
echo ""
|
|
396
|
+
echo "🔍 Verifying build failure against main branch..."
|
|
397
|
+
|
|
398
|
+
# Get current directory and branch info
|
|
399
|
+
local current_dir=$(pwd)
|
|
400
|
+
local current_branch=$(git rev-parse --abbrev-ref HEAD)
|
|
401
|
+
local main_repo_dir=""
|
|
402
|
+
|
|
403
|
+
# Find the main repository (parent of worktrees)
|
|
404
|
+
if [[ "$current_dir" == *"/worktrees/"* ]]; then
|
|
405
|
+
main_repo_dir=$(git worktree list | grep -E "\[(main|master)\]" | awk '{print $1}' | head -1)
|
|
406
|
+
if [[ -z "$main_repo_dir" ]]; then
|
|
407
|
+
main_repo_dir=$(git worktree list | head -1 | awk '{print $1}')
|
|
408
|
+
echo " Note: Using fallback worktree detection (no [main] or [master] found)"
|
|
409
|
+
fi
|
|
410
|
+
else
|
|
411
|
+
main_repo_dir="$current_dir"
|
|
412
|
+
fi
|
|
413
|
+
|
|
414
|
+
if [[ -z "$main_repo_dir" || ! -d "$main_repo_dir" ]]; then
|
|
415
|
+
echo " ⚠️ Could not locate main repository for comparison"
|
|
416
|
+
echo " Skipping build verification against main"
|
|
417
|
+
return 3
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
echo " Running build on main branch..."
|
|
421
|
+
|
|
422
|
+
local main_exit_code=0
|
|
423
|
+
local main_error_output=""
|
|
424
|
+
|
|
425
|
+
if command -v timeout &> /dev/null; then
|
|
426
|
+
main_error_output=$(cd "$main_repo_dir" && timeout 120 npm run build 2>&1 | head -30) || main_exit_code=$?
|
|
427
|
+
else
|
|
428
|
+
main_error_output=$(cd "$main_repo_dir" && perl -e 'alarm 120; exec @ARGV' npm run build 2>&1 | head -30) || main_exit_code=$?
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
local feature_first_error=$(echo "$feature_error_output" | grep -E "Error:|error:|ERROR:" | head -1)
|
|
432
|
+
local main_first_error=$(echo "$main_error_output" | grep -E "Error:|error:|ERROR:" | head -1)
|
|
433
|
+
|
|
434
|
+
echo ""
|
|
435
|
+
echo "### Build Verification"
|
|
436
|
+
echo ""
|
|
437
|
+
echo "| Check | Status |"
|
|
438
|
+
echo "|-------|--------|"
|
|
439
|
+
|
|
440
|
+
if [[ $feature_exit_code -ne 0 ]]; then
|
|
441
|
+
echo "| Feature branch build | ❌ Failed |"
|
|
442
|
+
else
|
|
443
|
+
echo "| Feature branch build | ✅ Passed |"
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
if [[ $main_exit_code -ne 0 ]]; then
|
|
447
|
+
echo "| Main branch build | ❌ Failed |"
|
|
448
|
+
|
|
449
|
+
if [[ "$feature_first_error" == "$main_first_error" ]] || \
|
|
450
|
+
[[ -n "$feature_first_error" && -n "$main_first_error" && \
|
|
451
|
+
"$(echo "$feature_first_error" | cut -c1-50)" == "$(echo "$main_first_error" | cut -c1-50)" ]]; then
|
|
452
|
+
echo "| Error match | ✅ Same error |"
|
|
453
|
+
echo "| Regression | **No** (pre-existing) |"
|
|
454
|
+
echo ""
|
|
455
|
+
echo "**Note:** Build failure is pre-existing on main branch. Not blocking this PR."
|
|
456
|
+
return 0
|
|
457
|
+
else
|
|
458
|
+
echo "| Error match | ❌ Different errors |"
|
|
459
|
+
echo "| Regression | **Unknown** (different failure modes) |"
|
|
460
|
+
echo ""
|
|
461
|
+
echo "**Note:** Build failures differ between branches. Manual review recommended."
|
|
462
|
+
echo ""
|
|
463
|
+
echo "Feature branch error:"
|
|
464
|
+
echo "\`\`\`"
|
|
465
|
+
echo "$feature_first_error"
|
|
466
|
+
echo "\`\`\`"
|
|
467
|
+
echo ""
|
|
468
|
+
echo "Main branch error:"
|
|
469
|
+
echo "\`\`\`"
|
|
470
|
+
echo "$main_first_error"
|
|
471
|
+
echo "\`\`\`"
|
|
472
|
+
return 2
|
|
473
|
+
fi
|
|
474
|
+
else
|
|
475
|
+
echo "| Main branch build | ✅ Passed |"
|
|
476
|
+
echo "| Regression | **Yes** (new failure) |"
|
|
477
|
+
echo ""
|
|
478
|
+
echo "⚠️ **REGRESSION DETECTED:** Build passes on main but fails on feature branch."
|
|
479
|
+
echo "This failure was introduced by changes in this PR."
|
|
480
|
+
echo ""
|
|
481
|
+
echo "Feature branch error:"
|
|
482
|
+
echo "\`\`\`"
|
|
483
|
+
echo "$feature_first_error"
|
|
484
|
+
echo "\`\`\`"
|
|
485
|
+
return 1
|
|
486
|
+
fi
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
run_build_with_verification() {
|
|
490
|
+
echo ""
|
|
491
|
+
echo "🏗️ Running build check..."
|
|
492
|
+
|
|
493
|
+
local build_output=""
|
|
494
|
+
local build_exit_code=0
|
|
495
|
+
|
|
496
|
+
if command -v timeout &> /dev/null; then
|
|
497
|
+
build_output=$(timeout 120 npm run build 2>&1) || build_exit_code=$?
|
|
498
|
+
else
|
|
499
|
+
build_output=$(perl -e 'alarm 120; exec @ARGV' npm run build 2>&1) || build_exit_code=$?
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
if [[ $build_exit_code -eq 0 ]]; then
|
|
503
|
+
echo "✅ Build: Passed"
|
|
504
|
+
return 0
|
|
505
|
+
else
|
|
506
|
+
echo "❌ Build: Failed (exit code: $build_exit_code)"
|
|
507
|
+
echo ""
|
|
508
|
+
echo "Build error output (first 20 lines):"
|
|
509
|
+
echo "$build_output" | head -20
|
|
510
|
+
echo ""
|
|
511
|
+
|
|
512
|
+
verify_build_against_main "$build_exit_code" "$build_output"
|
|
513
|
+
local verification_result=$?
|
|
514
|
+
|
|
515
|
+
return $verification_result
|
|
516
|
+
fi
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
# Check build cache
|
|
520
|
+
if cache_check "build"; then
|
|
521
|
+
CACHE_STATUS["build"]="HIT"
|
|
522
|
+
cached_result=$(cache_get "build")
|
|
523
|
+
build_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
|
|
524
|
+
build_message=$(echo "$cached_result" | grep -o '"message":\s*"[^"]*"' | cut -d'"' -f4)
|
|
525
|
+
|
|
526
|
+
echo ""
|
|
527
|
+
echo "🏗️ Build check: $build_message (cached)"
|
|
528
|
+
|
|
529
|
+
if [[ "$build_passed" == "true" ]]; then
|
|
530
|
+
build_verification_result=0
|
|
531
|
+
else
|
|
532
|
+
build_verification_result=1
|
|
533
|
+
fi
|
|
534
|
+
else
|
|
535
|
+
CACHE_STATUS["build"]="MISS"
|
|
536
|
+
build_verification_result=0
|
|
537
|
+
run_build_with_verification || build_verification_result=$?
|
|
538
|
+
|
|
539
|
+
# Cache the result
|
|
540
|
+
if [[ $build_verification_result -eq 0 ]]; then
|
|
541
|
+
cache_set "build" true "Passed"
|
|
542
|
+
else
|
|
543
|
+
cache_set "build" false "Failed (exit code: $build_verification_result)"
|
|
544
|
+
fi
|
|
545
|
+
fi
|
|
546
|
+
|
|
547
|
+
# Report build verification status
|
|
548
|
+
if [[ $build_verification_result -eq 1 ]]; then
|
|
549
|
+
echo ""
|
|
550
|
+
echo "⚠️ Build verification: REGRESSION DETECTED (blocking)"
|
|
551
|
+
elif [[ $build_verification_result -eq 2 ]]; then
|
|
552
|
+
echo ""
|
|
553
|
+
echo "⚠️ Build verification: Different errors detected (needs review)"
|
|
554
|
+
elif [[ $build_verification_result -eq 3 ]]; then
|
|
555
|
+
echo ""
|
|
556
|
+
echo "⚠️ Build verification: SKIPPED (could not locate main repo)"
|
|
557
|
+
build_verification_result=0
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
# =============================================================================
|
|
561
|
+
# Cache Status Report (AC-4)
|
|
562
|
+
# =============================================================================
|
|
563
|
+
echo ""
|
|
564
|
+
echo "=========================================="
|
|
565
|
+
echo "### Cache Status Report"
|
|
566
|
+
echo "=========================================="
|
|
567
|
+
echo ""
|
|
568
|
+
echo "| Check | Cache Status |"
|
|
569
|
+
echo "|-------|--------------|"
|
|
570
|
+
for check in "type-safety" "deleted-tests" "scope" "size" "security" "semgrep" "build"; do
|
|
571
|
+
status="${CACHE_STATUS[$check]:-MISS}"
|
|
572
|
+
if [[ "$status" == "HIT" ]]; then
|
|
573
|
+
echo "| $check | ✅ HIT |"
|
|
574
|
+
elif [[ "$status" == "SKIP" ]]; then
|
|
575
|
+
echo "| $check | ⏭️ SKIP |"
|
|
576
|
+
else
|
|
577
|
+
echo "| $check | ❌ MISS |"
|
|
578
|
+
fi
|
|
579
|
+
done
|
|
580
|
+
echo ""
|
|
581
|
+
|
|
582
|
+
# Count hits and misses
|
|
583
|
+
hit_count=0
|
|
584
|
+
miss_count=0
|
|
585
|
+
skip_count=0
|
|
586
|
+
for check in "type-safety" "deleted-tests" "scope" "size" "security" "semgrep" "build"; do
|
|
587
|
+
status="${CACHE_STATUS[$check]:-MISS}"
|
|
588
|
+
if [[ "$status" == "HIT" ]]; then
|
|
589
|
+
((hit_count++))
|
|
590
|
+
elif [[ "$status" == "SKIP" ]]; then
|
|
591
|
+
((skip_count++))
|
|
592
|
+
else
|
|
593
|
+
((miss_count++))
|
|
594
|
+
fi
|
|
595
|
+
done
|
|
596
|
+
|
|
597
|
+
echo "**Summary:** $hit_count hits, $miss_count misses, $skip_count skipped"
|
|
598
|
+
if [[ $hit_count -gt 0 ]]; then
|
|
599
|
+
echo "**Performance:** Cached checks saved execution time"
|
|
600
|
+
fi
|
|
601
|
+
echo ""
|
|
602
|
+
|
|
603
|
+
# Write structured cache metrics JSON for sequant observability (AC-7)
|
|
604
|
+
# This file is read by run.ts to populate PhaseLog.cacheMetrics
|
|
605
|
+
CACHE_METRICS_DIR=".sequant/.cache/qa"
|
|
606
|
+
mkdir -p "$CACHE_METRICS_DIR"
|
|
607
|
+
|
|
608
|
+
# Build JSON with per-check status
|
|
609
|
+
CACHE_JSON="{\"hits\":$hit_count,\"misses\":$miss_count,\"skipped\":$skip_count,\"checks\":{"
|
|
610
|
+
first=true
|
|
611
|
+
for check in "type-safety" "deleted-tests" "scope" "size" "security" "semgrep" "build"; do
|
|
612
|
+
$first || CACHE_JSON+=","
|
|
613
|
+
CACHE_JSON+="\"$check\":\"${CACHE_STATUS[$check]:-MISS}\""
|
|
614
|
+
first=false
|
|
615
|
+
done
|
|
616
|
+
CACHE_JSON+="}}"
|
|
617
|
+
echo "$CACHE_JSON" > "$CACHE_METRICS_DIR/cache-metrics.json"
|
|
618
|
+
|
|
619
|
+
echo "✅ Quality checks complete"
|
|
620
|
+
|
|
621
|
+
# Exit with build verification result if it indicates a problem
|
|
622
|
+
exit $build_verification_result
|