@boyingliu01/xp-gate 0.12.3 → 0.12.5
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/adapters/cpp.sh +0 -0
- package/adapters/dart.sh +0 -0
- package/adapters/flutter.sh +0 -0
- package/adapters/go.sh +0 -0
- package/adapters/java.sh +0 -0
- package/adapters/kotlin.sh +0 -0
- package/adapters/objectivec.sh +0 -0
- package/adapters/powershell.sh +0 -0
- package/adapters/python.sh +13 -2
- package/adapters/shell.sh +0 -0
- package/adapters/swift.sh +0 -0
- package/adapters/typescript.sh +0 -0
- package/bin/xp-gate.js +0 -8
- package/gate-3.sh +0 -0
- package/gate-4.sh +0 -0
- package/gate-7.sh +0 -0
- package/gate-8.sh +0 -0
- package/gate-9.sh +13 -13
- package/hooks/gate-9.sh +130 -0
- package/hooks/pre-commit +139 -53
- package/lib/arch.js +42 -10
- package/lib/init.js +9 -6
- package/lib/sprint-status.js +35 -1
- package/lib/update-hooks.js +46 -46
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/scripts/prepack.cjs +0 -15
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/principles/index.ts +15 -2
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
- package/lib/__tests__/next-sprint.test.js +0 -231
- package/lib/next-sprint.js +0 -129
- package/lib/shared-phase-constants.d.ts +0 -17
- package/lib/shared-phase-constants.js +0 -77
- package/plugins/opencode/__tests__/tui-plugin.test.ts +0 -543
- package/plugins/opencode/__tests__/xp-gate-e2e-upgrade.test.ts +0 -562
- package/plugins/opencode/__tests__/xp-gate-update.test.ts +0 -518
- package/plugins/opencode/tui-plugin.ts +0 -414
- package/plugins/opencode/tui-plugin.tsx +0 -306
package/adapters/cpp.sh
CHANGED
|
File without changes
|
package/adapters/dart.sh
CHANGED
|
File without changes
|
package/adapters/flutter.sh
CHANGED
|
File without changes
|
package/adapters/go.sh
CHANGED
|
File without changes
|
package/adapters/java.sh
CHANGED
|
File without changes
|
package/adapters/kotlin.sh
CHANGED
|
File without changes
|
package/adapters/objectivec.sh
CHANGED
|
File without changes
|
package/adapters/powershell.sh
CHANGED
|
File without changes
|
package/adapters/python.sh
CHANGED
|
@@ -47,7 +47,16 @@ run_tests() {
|
|
|
47
47
|
echo "Running Python tests..."
|
|
48
48
|
PYTEST_OUTPUT=$(pytest --exitfirst --tb=short 2>&1)
|
|
49
49
|
PYTEST_EXIT=$?
|
|
50
|
-
|
|
50
|
+
|
|
51
|
+
# Show the short summary tail — this is where FAILED/assert diagnostics live.
|
|
52
|
+
# With --exitfirst we stop at the first failure, so the last ~10 lines are the
|
|
53
|
+
# most actionable: they contain the test name, assert expression, and expected
|
|
54
|
+
# vs actual values. Full output is saved to /tmp/ for post-mortem inspection.
|
|
55
|
+
echo "$PYTEST_OUTPUT" | grep -E "(FAILED|PASSED|passed|failed|error|ERROR|assert)" | tail -10
|
|
56
|
+
echo "$PYTEST_OUTPUT" | tail -5
|
|
57
|
+
|
|
58
|
+
# Save full output for post-mortem inspection
|
|
59
|
+
echo "$PYTEST_OUTPUT" > /tmp/xp-gate-pytest-output-$$.log
|
|
51
60
|
|
|
52
61
|
# Check for collection errors (ModuleNotFoundError / ImportError)
|
|
53
62
|
if echo "$PYTEST_OUTPUT" | grep -qi "ModuleNotFoundError\|ImportError"; then
|
|
@@ -78,7 +87,9 @@ run_coverage() {
|
|
|
78
87
|
echo "Running Python coverage..."
|
|
79
88
|
PYTEST_OUTPUT=$(pytest --exitfirst --tb=short --cov=. --cov-fail-under=80 2>&1)
|
|
80
89
|
PYTEST_EXIT=$?
|
|
81
|
-
echo "$PYTEST_OUTPUT" | tail -
|
|
90
|
+
echo "$PYTEST_OUTPUT" | grep -E "(FAILED|PASSED|passed|failed|error|ERROR|TOTAL|assert)" | tail -10
|
|
91
|
+
echo "$PYTEST_OUTPUT" | tail -5
|
|
92
|
+
echo "$PYTEST_OUTPUT" > /tmp/xp-gate-pytest-cov-output-$$.log
|
|
82
93
|
|
|
83
94
|
# Check for collection errors (ModuleNotFoundError / ImportError)
|
|
84
95
|
if echo "$PYTEST_OUTPUT" | grep -qi "ModuleNotFoundError\|ImportError"; then
|
package/adapters/shell.sh
CHANGED
|
File without changes
|
package/adapters/swift.sh
CHANGED
|
File without changes
|
package/adapters/typescript.sh
CHANGED
|
File without changes
|
package/bin/xp-gate.js
CHANGED
|
@@ -137,14 +137,6 @@ const COMMANDS = {
|
|
|
137
137
|
},
|
|
138
138
|
usage: 'xp-gate sprint-status [--json] [--watch] [--dir <path>]'
|
|
139
139
|
},
|
|
140
|
-
'next-sprint': {
|
|
141
|
-
description: 'Analyze remaining open issues and plan next iteration',
|
|
142
|
-
run: subargs => {
|
|
143
|
-
const { handleNextSprint } = require('../lib/next-sprint.js');
|
|
144
|
-
handleNextSprint(subargs).then(code => process.exit(code));
|
|
145
|
-
},
|
|
146
|
-
usage: 'xp-gate next-sprint [--json] [--plan] [--dir <path>]'
|
|
147
|
-
},
|
|
148
140
|
'update-hooks': {
|
|
149
141
|
description: 'Sync latest hooks from xp-gate package to project',
|
|
150
142
|
run: subargs => {
|
package/gate-3.sh
CHANGED
|
File without changes
|
package/gate-4.sh
CHANGED
|
File without changes
|
package/gate-7.sh
CHANGED
|
File without changes
|
package/gate-8.sh
CHANGED
|
File without changes
|
package/gate-9.sh
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# ============================================================================
|
|
2
|
-
# GATE
|
|
2
|
+
# GATE 10: Semgrep SAST Security Scan
|
|
3
3
|
# Detects security vulnerabilities (SQL injection, XSS, etc.) in staged files
|
|
4
4
|
# Tool: semgrep -- https://semgrep.dev
|
|
5
5
|
# ============================================================================
|
|
6
6
|
|
|
7
7
|
2>&1 echo ""
|
|
8
|
-
2>&1 echo "→ Gate
|
|
9
|
-
|
|
8
|
+
2>&1 echo "→ Gate 10: Semgrep SAST Security Scan..."
|
|
9
|
+
GATE_10_START=$(gate_start_ms)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
GATE_10_STATUS=""
|
|
12
12
|
|
|
13
13
|
# Semgrep availability check
|
|
14
14
|
SEMGREP_CMD=""
|
|
@@ -22,15 +22,15 @@ if [ -z "$SEMGREP_CMD" ]; then
|
|
|
22
22
|
echo " ⚠️ WARN - semgrep not installed — SAST scanning unavailable"
|
|
23
23
|
echo " Install: brew install semgrep (macOS) | pip install semgrep (Linux) | pip install semgrep (Windows)"
|
|
24
24
|
echo " Pre-cache rules: semgrep --config=p/security-audit"
|
|
25
|
-
echo " Gate
|
|
26
|
-
|
|
25
|
+
echo " Gate 10: SAST Security (WARN, semgrep not installed)"
|
|
26
|
+
GATE_10_STATUS="WARN"
|
|
27
27
|
else
|
|
28
28
|
# Get staged files filtered to Semgrep-supported languages
|
|
29
29
|
SEMGREP_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.(ts|tsx|js|jsx|py|go|java|c|cpp|cs|rb|php|scala|swift)$' || true)
|
|
30
30
|
|
|
31
31
|
if [ -z "$SEMGREP_FILES" ]; then
|
|
32
32
|
echo " ⏭️ SKIPPED - SAST (no supported language files changed)"
|
|
33
|
-
|
|
33
|
+
GATE_10_STATUS="SKIP"
|
|
34
34
|
else
|
|
35
35
|
# Run semgrep with JSON output
|
|
36
36
|
# --config=p/security-audit: explicit security ruleset
|
|
@@ -41,7 +41,7 @@ else
|
|
|
41
41
|
|
|
42
42
|
if [ "$SEMGREP_EXIT" -eq 0 ]; then
|
|
43
43
|
echo " ✅ PASSED - No security vulnerabilities found."
|
|
44
|
-
|
|
44
|
+
GATE_10_STATUS="PASS"
|
|
45
45
|
elif [ "$SEMGREP_EXIT" -eq 1 ]; then
|
|
46
46
|
# Findings detected - parse JSON to categorize
|
|
47
47
|
CRITICAL_HIGH=$(echo "$SEMGREP_OUTPUT" | python3 -c "
|
|
@@ -99,7 +99,7 @@ except:
|
|
|
99
99
|
if [ "$CRITICAL_HIGH" -gt 0 ]; then
|
|
100
100
|
echo ""
|
|
101
101
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
102
|
-
echo " GATE
|
|
102
|
+
echo " GATE 10: Semgrep Security Gate"
|
|
103
103
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
104
104
|
echo " CRITICAL/HIGH: ${CRITICAL_HIGH} ❌ BLOCKED"
|
|
105
105
|
echo " MEDIUM/LOW: ${MEDIUM_LOW} ⚠️ warning"
|
|
@@ -108,7 +108,7 @@ except:
|
|
|
108
108
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
109
109
|
echo "$FINDING_DETAILS"
|
|
110
110
|
echo " Run 'semgrep scan --config=p/security-audit' to review all findings."
|
|
111
|
-
|
|
111
|
+
GATE_10_STATUS="FAIL"
|
|
112
112
|
exit 1
|
|
113
113
|
else
|
|
114
114
|
echo ""
|
|
@@ -117,14 +117,14 @@ except:
|
|
|
117
117
|
echo " ⚠️ ${MEDIUM_LOW} medium/low findings (warnings only)"
|
|
118
118
|
echo "$FINDING_DETAILS"
|
|
119
119
|
fi
|
|
120
|
-
|
|
120
|
+
GATE_10_STATUS="PASS"
|
|
121
121
|
fi
|
|
122
122
|
else
|
|
123
123
|
# semgrep runtime error (timeout, config error, etc.)
|
|
124
124
|
echo " ⚠️ semgrep exited with code ${SEMGREP_EXIT} — skipping gate"
|
|
125
125
|
echo " ⏭️ SKIPPED - SAST (semgrep runtime error)"
|
|
126
|
-
|
|
126
|
+
GATE_10_STATUS="SKIP"
|
|
127
127
|
fi
|
|
128
128
|
fi
|
|
129
129
|
fi
|
|
130
|
-
record_gate_audit "gate-
|
|
130
|
+
record_gate_audit "gate-10" "sast-security" "$GATE_10_STATUS" "0" "$GATE_10_START"
|
package/hooks/gate-9.sh
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# GATE 10: Semgrep SAST Security Scan
|
|
3
|
+
# Detects security vulnerabilities (SQL injection, XSS, etc.) in staged files
|
|
4
|
+
# Tool: semgrep -- https://semgrep.dev
|
|
5
|
+
# ============================================================================
|
|
6
|
+
|
|
7
|
+
2>&1 echo ""
|
|
8
|
+
2>&1 echo "→ Gate 10: Semgrep SAST Security Scan..."
|
|
9
|
+
GATE_10_START=$(gate_start_ms)
|
|
10
|
+
|
|
11
|
+
GATE_10_STATUS=""
|
|
12
|
+
|
|
13
|
+
# Semgrep availability check
|
|
14
|
+
SEMGREP_CMD=""
|
|
15
|
+
if command -v semgrep >/dev/null 2>&1; then
|
|
16
|
+
SEMGREP_CMD="semgrep"
|
|
17
|
+
elif [ -f "$HOME/.local/bin/semgrep" ]; then
|
|
18
|
+
SEMGREP_CMD="$HOME/.local/bin/semgrep"
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [ -z "$SEMGREP_CMD" ]; then
|
|
22
|
+
echo " ⚠️ WARN - semgrep not installed — SAST scanning unavailable"
|
|
23
|
+
echo " Install: brew install semgrep (macOS) | pip install semgrep (Linux) | pip install semgrep (Windows)"
|
|
24
|
+
echo " Pre-cache rules: semgrep --config=p/security-audit"
|
|
25
|
+
echo " Gate 10: SAST Security (WARN, semgrep not installed)"
|
|
26
|
+
GATE_10_STATUS="WARN"
|
|
27
|
+
else
|
|
28
|
+
# Get staged files filtered to Semgrep-supported languages
|
|
29
|
+
SEMGREP_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.(ts|tsx|js|jsx|py|go|java|c|cpp|cs|rb|php|scala|swift)$' || true)
|
|
30
|
+
|
|
31
|
+
if [ -z "$SEMGREP_FILES" ]; then
|
|
32
|
+
echo " ⏭️ SKIPPED - SAST (no supported language files changed)"
|
|
33
|
+
GATE_10_STATUS="SKIP"
|
|
34
|
+
else
|
|
35
|
+
# Run semgrep with JSON output
|
|
36
|
+
# --config=p/security-audit: explicit security ruleset
|
|
37
|
+
# --json: machine-readable output
|
|
38
|
+
# --disable-version-check: skip network call
|
|
39
|
+
SEMGREP_OUTPUT=$($SEMGREP_CMD scan --config=p/security-audit --json --disable-version-check $SEMGREP_FILES 2>&1)
|
|
40
|
+
SEMGREP_EXIT=$?
|
|
41
|
+
|
|
42
|
+
if [ "$SEMGREP_EXIT" -eq 0 ]; then
|
|
43
|
+
echo " ✅ PASSED - No security vulnerabilities found."
|
|
44
|
+
GATE_10_STATUS="PASS"
|
|
45
|
+
elif [ "$SEMGREP_EXIT" -eq 1 ]; then
|
|
46
|
+
# Findings detected - parse JSON to categorize
|
|
47
|
+
CRITICAL_HIGH=$(echo "$SEMGREP_OUTPUT" | python3 -c "
|
|
48
|
+
import sys, json
|
|
49
|
+
try:
|
|
50
|
+
data = json.load(sys.stdin)
|
|
51
|
+
results = data.get('results', [])
|
|
52
|
+
count = 0
|
|
53
|
+
for r in results:
|
|
54
|
+
extra = r.get('extra', {})
|
|
55
|
+
severity = extra.get('severity', '').upper()
|
|
56
|
+
if severity in ('CRITICAL', 'HIGH'):
|
|
57
|
+
count += 1
|
|
58
|
+
print(count)
|
|
59
|
+
except:
|
|
60
|
+
print('0')
|
|
61
|
+
" 2>/dev/null || echo "0")
|
|
62
|
+
|
|
63
|
+
MEDIUM_LOW=$(echo "$SEMGREP_OUTPUT" | python3 -c "
|
|
64
|
+
import sys, json
|
|
65
|
+
try:
|
|
66
|
+
data = json.load(sys.stdin)
|
|
67
|
+
results = data.get('results', [])
|
|
68
|
+
count = 0
|
|
69
|
+
for r in results:
|
|
70
|
+
extra = r.get('extra', {})
|
|
71
|
+
severity = extra.get('severity', '').upper()
|
|
72
|
+
if severity in ('MEDIUM', 'LOW'):
|
|
73
|
+
count += 1
|
|
74
|
+
print(count)
|
|
75
|
+
except:
|
|
76
|
+
print('0')
|
|
77
|
+
" 2>/dev/null || echo "0")
|
|
78
|
+
|
|
79
|
+
# Extract top finding details
|
|
80
|
+
FINDING_DETAILS=$(echo "$SEMGREP_OUTPUT" | python3 -c "
|
|
81
|
+
import sys, json
|
|
82
|
+
try:
|
|
83
|
+
data = json.load(sys.stdin)
|
|
84
|
+
results = data.get('results', [])
|
|
85
|
+
for r in results[:5]: # Show top 5
|
|
86
|
+
extra = r.get('extra', {})
|
|
87
|
+
severity = extra.get('severity', 'UNKNOWN').upper()
|
|
88
|
+
rule_id = r.get('check_id', 'unknown')
|
|
89
|
+
path = r.get('path', 'unknown')
|
|
90
|
+
start_line = r.get('start', {}).get('line', '?')
|
|
91
|
+
message = extra.get('message', '')[:80]
|
|
92
|
+
print(f' [{severity}] {rule_id}')
|
|
93
|
+
print(f' {path}:{start_line} → {message}')
|
|
94
|
+
print()
|
|
95
|
+
except:
|
|
96
|
+
print(' (Failed to parse semgrep output)')
|
|
97
|
+
" 2>/dev/null || echo " (Failed to parse semgrep output)")
|
|
98
|
+
|
|
99
|
+
if [ "$CRITICAL_HIGH" -gt 0 ]; then
|
|
100
|
+
echo ""
|
|
101
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
102
|
+
echo " GATE 10: Semgrep Security Gate"
|
|
103
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
104
|
+
echo " CRITICAL/HIGH: ${CRITICAL_HIGH} ❌ BLOCKED"
|
|
105
|
+
echo " MEDIUM/LOW: ${MEDIUM_LOW} ⚠️ warning"
|
|
106
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
107
|
+
echo " ❌ BLOCKED — Critical/High vulnerability found"
|
|
108
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
109
|
+
echo "$FINDING_DETAILS"
|
|
110
|
+
echo " Run 'semgrep scan --config=p/security-audit' to review all findings."
|
|
111
|
+
GATE_10_STATUS="FAIL"
|
|
112
|
+
exit 1
|
|
113
|
+
else
|
|
114
|
+
echo ""
|
|
115
|
+
echo " ✅ PASSED - No critical/high vulnerabilities"
|
|
116
|
+
if [ "$MEDIUM_LOW" -gt 0 ]; then
|
|
117
|
+
echo " ⚠️ ${MEDIUM_LOW} medium/low findings (warnings only)"
|
|
118
|
+
echo "$FINDING_DETAILS"
|
|
119
|
+
fi
|
|
120
|
+
GATE_10_STATUS="PASS"
|
|
121
|
+
fi
|
|
122
|
+
else
|
|
123
|
+
# semgrep runtime error (timeout, config error, etc.)
|
|
124
|
+
echo " ⚠️ semgrep exited with code ${SEMGREP_EXIT} — skipping gate"
|
|
125
|
+
echo " ⏭️ SKIPPED - SAST (semgrep runtime error)"
|
|
126
|
+
GATE_10_STATUS="SKIP"
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
record_gate_audit "gate-10" "sast-security" "$GATE_10_STATUS" "0" "$GATE_10_START"
|
package/hooks/pre-commit
CHANGED
|
@@ -932,11 +932,9 @@ elif [ "$PROJECT_LANG" = "python" ]; then
|
|
|
932
932
|
else
|
|
933
933
|
echo "✅ PASSED - pylint found no duplicates."
|
|
934
934
|
fi
|
|
935
|
-
elif require_tool "ruff" "Gate 2" "pip install ruff"; then
|
|
936
|
-
ruff check --select=DUP $(echo "$CHANGED_FILES" | grep "\.py$") 2>&1 | head -30
|
|
937
|
-
echo "✅ PASSED - ruff duplicate code check completed."
|
|
938
935
|
else
|
|
939
|
-
|
|
936
|
+
echo "ℹ️ pylint not found — skipping Python duplicate code check."
|
|
937
|
+
echo " Install pylint: pip install pylint"
|
|
940
938
|
fi
|
|
941
939
|
|
|
942
940
|
elif [ "$PROJECT_LANG" = "go" ]; then
|
|
@@ -1223,6 +1221,13 @@ else
|
|
|
1223
1221
|
fi
|
|
1224
1222
|
done
|
|
1225
1223
|
|
|
1224
|
+
if [ "$TEST_FOUND" = false ] && [ "$ext" = "py" ]; then
|
|
1225
|
+
module_name="${filename//-/_}"
|
|
1226
|
+
if grep -rq "import $module_name\|from $module_name import" tests/ 2>/dev/null; then
|
|
1227
|
+
TEST_FOUND=true
|
|
1228
|
+
fi
|
|
1229
|
+
fi
|
|
1230
|
+
|
|
1226
1231
|
if [ "$TEST_FOUND" = false ]; then
|
|
1227
1232
|
echo "⚠️ TEST PAIRING WARNING: Source file without corresponding test: $src_file"
|
|
1228
1233
|
echo " Expected patterns for .${ext}: ${patterns[0]}, ${patterns[1]:-...}"
|
|
@@ -1294,22 +1299,52 @@ else
|
|
|
1294
1299
|
done
|
|
1295
1300
|
fi
|
|
1296
1301
|
|
|
1297
|
-
#
|
|
1302
|
+
# Run tests + coverage in a single pass (optimization: avoids running vitest twice)
|
|
1303
|
+
# For TypeScript with test:coverage script, use coverage-enabled run directly.
|
|
1304
|
+
# For other languages, fall back to the adapter's run_tests + run_coverage pair.
|
|
1298
1305
|
echo "Running tests..."
|
|
1299
1306
|
|
|
1300
1307
|
# Route to appropriate test runner via adapter
|
|
1301
1308
|
ADAPTER_FILE=$(resolve_adapter_path "$PROJECT_LANG")
|
|
1302
1309
|
if [ "${PROJECT_LANG}" != "unknown" ] && [ -n "$ADAPTER_FILE" ]; then
|
|
1303
1310
|
if source "$ADAPTER_FILE" 2>/dev/null; then
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
+
# TypeScript: single vitest run --coverage (combines tests + coverage)
|
|
1312
|
+
if [ "$PROJECT_LANG" = "typescript" ] && [ -f "package.json" ]; then
|
|
1313
|
+
if npx vitest --version >/dev/null 2>&1; then
|
|
1314
|
+
echo "Running TypeScript tests with coverage..."
|
|
1315
|
+
TESTS_OUTPUT=$(npx vitest run --coverage 2>&1)
|
|
1316
|
+
TESTS_EXIT_CODE=$?
|
|
1317
|
+
echo "$TESTS_OUTPUT" | head -60
|
|
1318
|
+
if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
|
|
1319
|
+
echo ""
|
|
1320
|
+
echo "❌ BLOCKED - Tests FAILED"
|
|
1321
|
+
exit 1
|
|
1322
|
+
else
|
|
1323
|
+
echo "✅ PASSED - Unit tests passed (with coverage)."
|
|
1324
|
+
fi
|
|
1325
|
+
else
|
|
1326
|
+
# Fallback: run_tests without coverage
|
|
1327
|
+
TESTS_OUTPUT=$(run_tests 2>&1)
|
|
1328
|
+
TESTS_EXIT_CODE=$?
|
|
1329
|
+
echo "$TESTS_OUTPUT" | head -50
|
|
1330
|
+
if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
|
|
1331
|
+
echo ""
|
|
1332
|
+
echo "❌ BLOCKED - Tests FAILED"
|
|
1333
|
+
exit 1
|
|
1334
|
+
fi
|
|
1335
|
+
fi
|
|
1311
1336
|
else
|
|
1312
|
-
|
|
1337
|
+
# Non-TypeScript: standard adapter test flow
|
|
1338
|
+
TESTS_OUTPUT=$(run_tests 2>&1)
|
|
1339
|
+
TESTS_EXIT_CODE=$?
|
|
1340
|
+
echo "$TESTS_OUTPUT" | head -50
|
|
1341
|
+
if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
|
|
1342
|
+
echo ""
|
|
1343
|
+
echo "❌ BLOCKED - Tests FAILED"
|
|
1344
|
+
exit 1
|
|
1345
|
+
else
|
|
1346
|
+
echo "✅ PASSED - Unit tests passed."
|
|
1347
|
+
fi
|
|
1313
1348
|
fi
|
|
1314
1349
|
else
|
|
1315
1350
|
echo "✅ PASSED - Could not source ${PROJECT_LANG} adapter for tests, assuming none to run."
|
|
@@ -1318,29 +1353,18 @@ else
|
|
|
1318
1353
|
echo "✅ PASSED - No ${PROJECT_LANG} adapter found for tests, assuming none to run."
|
|
1319
1354
|
fi
|
|
1320
1355
|
|
|
1321
|
-
# Next, run coverage check
|
|
1356
|
+
# Next, run coverage check (skip for TypeScript — already done above)
|
|
1322
1357
|
echo "Running coverage check..."
|
|
1323
1358
|
|
|
1324
1359
|
if [ "${PROJECT_LANG}" != "unknown" ] && [ -n "$ADAPTER_FILE" ]; then
|
|
1325
1360
|
if source "$ADAPTER_FILE" 2>/dev/null; then
|
|
1326
1361
|
case "$PROJECT_LANG" in
|
|
1327
1362
|
"typescript")
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
echo ""
|
|
1334
|
-
echo "❌ BLOCKED - Coverage check FAILED (exit code: $COV_EXIT)"
|
|
1335
|
-
echo "Coverage must meet the thresholds defined in vitest.config.ts"
|
|
1336
|
-
exit 1
|
|
1337
|
-
fi
|
|
1338
|
-
else
|
|
1339
|
-
echo "No 'test:coverage' script in package.json, running coverage via adapter..."
|
|
1340
|
-
run_coverage_output=$(run_coverage 2>&1)
|
|
1341
|
-
COV_EXIT=$?
|
|
1342
|
-
echo "$run_coverage_output" | head -30
|
|
1343
|
-
fi
|
|
1363
|
+
# Coverage already collected in the test section above (optimized single vitest run --coverage).
|
|
1364
|
+
# The --coverage flag produces coverage/coverage-summary.json which the unconditional
|
|
1365
|
+
# stage-2 enforcement reads. No need to run vitest again here.
|
|
1366
|
+
echo "TypeScript coverage already collected during test run."
|
|
1367
|
+
COV_EXIT=0
|
|
1344
1368
|
;;
|
|
1345
1369
|
"python")
|
|
1346
1370
|
if command -v pytest &> /dev/null && command -v coverage &> /dev/null; then
|
|
@@ -1793,19 +1817,74 @@ if [ -n "$ORIGINAL_DIR" ]; then
|
|
|
1793
1817
|
fi
|
|
1794
1818
|
|
|
1795
1819
|
# ============================================================================
|
|
1796
|
-
# GATE 9:
|
|
1797
|
-
#
|
|
1820
|
+
# GATE 9: Build Integrity Check (TypeScript compilation + package + imports)
|
|
1821
|
+
# ============================================================================
|
|
1822
|
+
2>&1 echo ""
|
|
1823
|
+
2>&1 echo "→ Gate 9: Build integrity... "
|
|
1824
|
+
GATE_9_START=$(gate_start_ms)
|
|
1825
|
+
GATE_9_STATUS="SKIP"
|
|
1826
|
+
|
|
1827
|
+
if [ -f "package.json" ] && [ -f "tsconfig.json" ]; then
|
|
1828
|
+
GATE_9_SCRIPT=""
|
|
1829
|
+
if [ -f "$PROJECT_ROOT/src/build-integrity/gate-10.ts" ]; then
|
|
1830
|
+
GATE_9_SCRIPT="$PROJECT_ROOT/src/build-integrity/gate-10.ts"
|
|
1831
|
+
elif [ -f ".xp-gate/modules/build-integrity/gate-10.ts" ]; then
|
|
1832
|
+
GATE_9_SCRIPT=".xp-gate/modules/build-integrity/gate-10.ts"
|
|
1833
|
+
elif [ -f "$HOME/.config/xp-gate/modules/build-integrity/gate-10.ts" ]; then
|
|
1834
|
+
GATE_9_SCRIPT="$HOME/.config/xp-gate/modules/build-integrity/gate-10.ts"
|
|
1835
|
+
fi
|
|
1836
|
+
|
|
1837
|
+
if [ -n "$GATE_9_SCRIPT" ]; then
|
|
1838
|
+
CHANGED_FILES_CSV=$(echo "$CHANGED_FILES" | tr ' ' ',')
|
|
1839
|
+
GATE_9_OUTPUT=$(mktemp)
|
|
1840
|
+
if timeout 120s npx tsx "$GATE_9_SCRIPT" --changed-files "$CHANGED_FILES_CSV" --project-root "$PROJECT_ROOT" --timeout 115000 > "$GATE_9_OUTPUT" 2>&1; then
|
|
1841
|
+
GATE_9_STATUS="PASS"
|
|
1842
|
+
echo " ✅ Build integrity check passed."
|
|
1843
|
+
else
|
|
1844
|
+
GATE_9_EXIT=$?
|
|
1845
|
+
if [ $GATE_9_EXIT -eq 124 ]; then
|
|
1846
|
+
GATE_9_STATUS="SKIP"
|
|
1847
|
+
echo " ⏱️ Build integrity check timed out (120s). SKIPPED."
|
|
1848
|
+
else
|
|
1849
|
+
GATE_9_STATUS="BLOCK"
|
|
1850
|
+
cat "$GATE_9_OUTPUT"
|
|
1851
|
+
echo ""
|
|
1852
|
+
echo "❌ BLOCKED - Gate 9: Build integrity check failed"
|
|
1853
|
+
echo "Fix type errors, broken imports, or package manifest issues before committing."
|
|
1854
|
+
rm -f "$GATE_9_OUTPUT"
|
|
1855
|
+
exit 1
|
|
1856
|
+
fi
|
|
1857
|
+
fi
|
|
1858
|
+
rm -f "$GATE_9_OUTPUT"
|
|
1859
|
+
else
|
|
1860
|
+
echo " ℹ️ Build integrity script not found - skipping"
|
|
1861
|
+
echo " ⏭️ SKIPPED - Build integrity (gate-10.ts not available)"
|
|
1862
|
+
fi
|
|
1863
|
+
else
|
|
1864
|
+
echo " ℹ️ Not a TypeScript project - skipping build integrity check"
|
|
1865
|
+
echo " ⏭️ SKIPPED - Build integrity (not a TypeScript project)"
|
|
1866
|
+
fi
|
|
1867
|
+
|
|
1868
|
+
GATE_9_END=$(date +%s)
|
|
1869
|
+
GATE_9_DURATION=$((GATE_9_END - GATE_9_START))
|
|
1870
|
+
echo "✅ Gate 9 completed in ${GATE_9_DURATION}s"
|
|
1871
|
+
echo ""
|
|
1872
|
+
# ============================================================================
|
|
1873
|
+
|
|
1874
|
+
# ============================================================================
|
|
1875
|
+
# GATE 10: Semgrep SAST Security Scan
|
|
1876
|
+
# Extracted to gate-10.sh for maintainability
|
|
1798
1877
|
# ============================================================================
|
|
1799
1878
|
source "$GATE_DIR/gate-9.sh"
|
|
1800
1879
|
# ============================================================================
|
|
1801
1880
|
|
|
1802
1881
|
# ============================================================================
|
|
1803
|
-
# GATE
|
|
1882
|
+
# GATE 11: Sprint Flow Enforcement
|
|
1804
1883
|
# Validates sprint state consistency (delphi-review APPROVED before BUILD)
|
|
1805
1884
|
# Uses sprint-gate.sh for standalone validation logic
|
|
1806
1885
|
# ============================================================================
|
|
1807
|
-
|
|
1808
|
-
|
|
1886
|
+
GATE_11_START=$(date +%s)
|
|
1887
|
+
GATE_11_STATUS="PASS"
|
|
1809
1888
|
|
|
1810
1889
|
SPRINT_GATE_SCRIPT=""
|
|
1811
1890
|
if [ -f "$GATE_DIR/sprint-gate.sh" ]; then
|
|
@@ -1816,19 +1895,19 @@ fi
|
|
|
1816
1895
|
|
|
1817
1896
|
if [ -n "$SPRINT_GATE_SCRIPT" ]; then
|
|
1818
1897
|
if ! bash "$SPRINT_GATE_SCRIPT" --pre-commit; then
|
|
1819
|
-
|
|
1820
|
-
echo "❌ BLOCKED - Gate
|
|
1898
|
+
GATE_11_STATUS="BLOCK"
|
|
1899
|
+
echo "❌ BLOCKED - Gate 11: Sprint Flow Enforcement"
|
|
1821
1900
|
# Sprint gate failure is a hard block — exit immediately
|
|
1822
1901
|
exit 1
|
|
1823
1902
|
fi
|
|
1824
1903
|
else
|
|
1825
|
-
echo "⏭️ SKIPPED - Gate
|
|
1826
|
-
|
|
1904
|
+
echo "⏭️ SKIPPED - Gate 11: Sprint Flow (sprint-gate.sh not found)"
|
|
1905
|
+
GATE_11_STATUS="SKIP"
|
|
1827
1906
|
fi
|
|
1828
1907
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
echo "✅ Gate
|
|
1908
|
+
GATE_11_END=$(date +%s)
|
|
1909
|
+
GATE_11_DURATION=$((GATE_11_END - GATE_11_START))
|
|
1910
|
+
echo "✅ Gate 11 completed in ${GATE_11_DURATION}s"
|
|
1832
1911
|
echo ""
|
|
1833
1912
|
# ============================================================================
|
|
1834
1913
|
|
|
@@ -1838,13 +1917,13 @@ generate_quality_report() {
|
|
|
1838
1917
|
local TIMESTAMP
|
|
1839
1918
|
local BRANCH
|
|
1840
1919
|
local PASSED_COUNT=0
|
|
1841
|
-
local TOTAL_GATES=
|
|
1920
|
+
local TOTAL_GATES=11
|
|
1842
1921
|
|
|
1843
1922
|
COMMIT_HASH=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
1844
1923
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
1845
1924
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1846
1925
|
|
|
1847
|
-
for gate in 1 2 3 4 5 6 7 8 9 10; do
|
|
1926
|
+
for gate in 1 2 3 4 5 6 7 8 9 10 11; do
|
|
1848
1927
|
local status_var="GATE_${gate}_STATUS"
|
|
1849
1928
|
if [ "${!status_var}" = "PASS" ]; then
|
|
1850
1929
|
PASSED_COUNT=$((PASSED_COUNT + 1))
|
|
@@ -1886,7 +1965,7 @@ generate_quality_report() {
|
|
|
1886
1965
|
|
|
1887
1966
|
cat > "$REPORT_FILE" << ENDJSON
|
|
1888
1967
|
{
|
|
1889
|
-
"reportVersion": "
|
|
1968
|
+
"reportVersion": "2.0",
|
|
1890
1969
|
"generatedAt": "$TIMESTAMP",
|
|
1891
1970
|
"branch": "$BRANCH",
|
|
1892
1971
|
"commit": "$COMMIT_HASH",
|
|
@@ -1943,14 +2022,19 @@ generate_quality_report() {
|
|
|
1943
2022
|
"status": "${GATE_8_STATUS:-PASS}",
|
|
1944
2023
|
"tool": "gitleaks"
|
|
1945
2024
|
},
|
|
1946
|
-
"
|
|
1947
|
-
"name": "
|
|
2025
|
+
"gate9_build_integrity": {
|
|
2026
|
+
"name": "Build Integrity",
|
|
1948
2027
|
"status": "${GATE_9_STATUS:-PASS}",
|
|
2028
|
+
"tool": "tsc + npm pack + import check"
|
|
2029
|
+
},
|
|
2030
|
+
"gate10_sast": {
|
|
2031
|
+
"name": "SAST Security Scan",
|
|
2032
|
+
"status": "${GATE_10_STATUS:-PASS}",
|
|
1949
2033
|
"tool": "semgrep"
|
|
1950
2034
|
},
|
|
1951
|
-
"
|
|
2035
|
+
"gate11_sprint_flow": {
|
|
1952
2036
|
"name": "Sprint Flow Enforcement",
|
|
1953
|
-
"status": "${
|
|
2037
|
+
"status": "${GATE_11_STATUS:-PASS}",
|
|
1954
2038
|
"tool": "sprint-gate.sh"
|
|
1955
2039
|
}
|
|
1956
2040
|
}
|
|
@@ -1992,8 +2076,9 @@ PY
|
|
|
1992
2076
|
GATES_JSON="${GATES_JSON}\"gate6\":{\"status\":\"${GATE_6_STATUS:-PASS}\",\"name\":\"Architecture+BoyScout\",\"bsBlocked\":${BS_BLOCKED}},"
|
|
1993
2077
|
GATES_JSON="${GATES_JSON}\"gate7\":{\"status\":\"${GATE_7_STATUS:-PASS}\",\"name\":\"IaC Security\"},"
|
|
1994
2078
|
GATES_JSON="${GATES_JSON}\"gate8\":{\"status\":\"${GATE_8_STATUS:-PASS}\",\"name\":\"Secret Scanning\"},"
|
|
1995
|
-
GATES_JSON="${GATES_JSON}\"gate9\":{\"status\":\"${GATE_9_STATUS:-PASS}\",\"name\":\"
|
|
1996
|
-
GATES_JSON="${GATES_JSON}\"gate10\":{\"status\":\"${GATE_10_STATUS:-PASS}\",\"name\":\"
|
|
2079
|
+
GATES_JSON="${GATES_JSON}\"gate9\":{\"status\":\"${GATE_9_STATUS:-PASS}\",\"name\":\"Build Integrity\"},"
|
|
2080
|
+
GATES_JSON="${GATES_JSON}\"gate10\":{\"status\":\"${GATE_10_STATUS:-PASS}\",\"name\":\"SAST Security\"},"
|
|
2081
|
+
GATES_JSON="${GATES_JSON}\"gate11\":{\"status\":\"${GATE_11_STATUS:-PASS}\",\"name\":\"Sprint Flow\"}}"
|
|
1997
2082
|
GATES_JSON="${GATES_JSON}}"
|
|
1998
2083
|
|
|
1999
2084
|
echo "{\"timestamp\":\"$TIMESTAMP\",\"commit\":\"$COMMIT_HASH\",\"branch\":\"$BRANCH\",\"score\":$SCORE,\"passed\":$PASSED_COUNT,\"total\":$TOTAL_GATES,\"gates\":$GATES_JSON,\"coverage\":\"$COV_PCT\",\"complexityWarnings\":${CC_WARNINGS:-0},\"principleWarnings\":${WARNING_COUNT:-0},\"boyScoutBlocked\":${BS_BLOCKED}}" >> "$HISTORY_FILE"
|
|
@@ -2012,8 +2097,9 @@ PY
|
|
|
2012
2097
|
printf " %-45s %s\n" "Gate 6: Architecture + Boy Scout" "${GATE_6_STATUS:-PASS}"
|
|
2013
2098
|
printf " %-45s %s\n" "Gate 7: IaC Security" "${GATE_7_STATUS:-PASS}"
|
|
2014
2099
|
printf " %-45s %s\n" "Gate 8: Secret Scanning" "${GATE_8_STATUS:-PASS}"
|
|
2015
|
-
printf " %-45s %s\n" "Gate 9:
|
|
2016
|
-
printf " %-45s %s\n" "Gate 10:
|
|
2100
|
+
printf " %-45s %s\n" "Gate 9: Build Integrity" "${GATE_9_STATUS:-PASS}"
|
|
2101
|
+
printf " %-45s %s\n" "Gate 10: SAST Security" "${GATE_10_STATUS:-PASS}"
|
|
2102
|
+
printf " %-45s %s\n" "Gate 11: Sprint Flow" "${GATE_11_STATUS:-PASS}"
|
|
2017
2103
|
echo ""
|
|
2018
2104
|
echo " Overall Score: $SCORE/10 | $PASSED_COUNT/$TOTAL_GATES gates passed"
|
|
2019
2105
|
echo " Branch status: $REPORT_FILE"
|