loki-mode 8.37.0 → 8.38.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/dashboard/__init__.py +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +6 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/tests/detect-invariant-violations.sh +359 -0
- package/tests/detect-mock-problems.sh +473 -0
- package/tests/detect-semantic-test-problems.sh +332 -0
- package/tests/detect-test-mutations.sh +455 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Semantic Test-Authenticity Detector - Quality Gate #10
|
|
3
|
+
# Catches the HARDER class of fake tests that the regex detectors (gates 5+6,
|
|
4
|
+
# tests/detect-mock-problems.sh and tests/detect-test-mutations.sh) miss: tests
|
|
5
|
+
# that LOOK like real assertions but verify nothing because the asserted value
|
|
6
|
+
# never flows through the code under test.
|
|
7
|
+
#
|
|
8
|
+
# Usage: ./tests/detect-semantic-test-problems.sh [--strict] [--block-high]
|
|
9
|
+
# --strict: Exit 1 if ANY CRITICAL or HIGH finding exists (for CI gates).
|
|
10
|
+
# Mirrors the --strict block in tests/detect-mock-problems.sh.
|
|
11
|
+
# --block-high: Exit 2 when one or more HIGH-severity findings are present, 0
|
|
12
|
+
# otherwise (clean exit-code contract for a run.sh wrapper that
|
|
13
|
+
# does not want to grep stdout). --strict takes precedence.
|
|
14
|
+
#
|
|
15
|
+
# Output contract: every HIGH-severity finding prints a line beginning with the
|
|
16
|
+
# literal token "[HIGH]" on stdout (ANSI-colored), so a wrapper may grep
|
|
17
|
+
# '\[HIGH\]' as an alternative to --block-high. Same severity tokens as the
|
|
18
|
+
# sibling detectors: [CRITICAL] / [HIGH] / [MEDIUM] / [LOW].
|
|
19
|
+
#
|
|
20
|
+
# ----------------------------------------------------------------------------
|
|
21
|
+
# WHAT THIS CATCHES (the gap left by gates 5 and 6)
|
|
22
|
+
# ----------------------------------------------------------------------------
|
|
23
|
+
# 1. Literal-via-variable echo (HIGH): a variable is bound to a literal, then
|
|
24
|
+
# asserted to equal the SAME literal text, with NO function call between the
|
|
25
|
+
# binding and the assertion. Example (genuinely fake):
|
|
26
|
+
# const x = "hello";
|
|
27
|
+
# expect(x).toBe("hello"); // verifies nothing -- x never changed
|
|
28
|
+
# The regex detectors only catch the inline form expect("hello").toBe("hello")
|
|
29
|
+
# or expect(1).toBe(1); the indirection through a variable defeats them.
|
|
30
|
+
#
|
|
31
|
+
# 2. Mock-return echo (MEDIUM): within a small window, a mock is configured to
|
|
32
|
+
# return a literal and an assertion checks that SAME literal. The test then
|
|
33
|
+
# only proves the mock returns what you told it to, not that real code works.
|
|
34
|
+
# mockFn.mockReturnValue("ok");
|
|
35
|
+
# expect(result).toBe("ok"); // possibly just echoing the mock
|
|
36
|
+
# MEDIUM (not HIGH) because provenance is uncertain: "result" may legitimately
|
|
37
|
+
# be a transform of the mock output. Conservative by design.
|
|
38
|
+
#
|
|
39
|
+
# 3. Deleted/weakened assertions across a commit (MEDIUM): a commit that REMOVES
|
|
40
|
+
# more assertion lines than it adds from a test file (the test still "passes"
|
|
41
|
+
# because the check that could fail is gone). Detected from git history, like
|
|
42
|
+
# Check 5 of detect-test-mutations.sh. Degrades silently with no findings
|
|
43
|
+
# outside a git repo or when git returns nothing.
|
|
44
|
+
#
|
|
45
|
+
# ----------------------------------------------------------------------------
|
|
46
|
+
# WHAT THIS DELIBERATELY DOES NOT CATCH (honesty -- read before trusting)
|
|
47
|
+
# ----------------------------------------------------------------------------
|
|
48
|
+
# - Legitimate computed literal assertions. expect(add(2,2)).toBe(4) is CORRECT
|
|
49
|
+
# testing: 4 is the genuine expected output. Any assertion whose target
|
|
50
|
+
# contains a function call is NEVER flagged -- a call means the value has
|
|
51
|
+
# provenance through code. This is the explicit conservative boundary
|
|
52
|
+
# (prefer false-negatives over false-positives).
|
|
53
|
+
# - Deep dataflow. We cannot prove a literal "is just the input" without real
|
|
54
|
+
# dataflow analysis (impossible in bash). We only flag the textually obvious
|
|
55
|
+
# var = literal; expect(var).toBe(SAME literal) with nothing in between.
|
|
56
|
+
# - Multi-line assertion targets, template literals with interpolation, and
|
|
57
|
+
# computed property chains -- these are skipped rather than guessed at.
|
|
58
|
+
# - The inline tautologies expect(true).toBe(true) / expect(1).toBe(1) -- those
|
|
59
|
+
# are already gate 5's job (detect-mock-problems.sh). No double-reporting.
|
|
60
|
+
# - Reassignment between binding and assertion: if anything rebinds the variable,
|
|
61
|
+
# we do not flag (the value may now carry real behavior).
|
|
62
|
+
# - Python and shell tests for the literal/mock-echo checks (JS/TS only). The
|
|
63
|
+
# deleted-assertion git check covers .test/.spec.{ts,js,tsx,jsx} only.
|
|
64
|
+
#
|
|
65
|
+
# ----------------------------------------------------------------------------
|
|
66
|
+
# HOW TO WIRE INTO run.sh (mirror enforce_mock_integrity; DO NOT wire here --
|
|
67
|
+
# run.sh has another owner this wave). Add alongside the gate-5/6 wrappers:
|
|
68
|
+
#
|
|
69
|
+
# enforce_semantic_integrity() {
|
|
70
|
+
# local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
71
|
+
# local quality_dir="$loki_dir/quality"
|
|
72
|
+
# mkdir -p "$quality_dir"
|
|
73
|
+
# local findings_file="$quality_dir/semantic-findings.txt"
|
|
74
|
+
# local detector="$SCRIPT_DIR/../tests/detect-semantic-test-problems.sh"
|
|
75
|
+
# local gate_timeout="${LOKI_GATE_TIMEOUT:-300}"
|
|
76
|
+
# [ -f "$detector" ] || { log_info "Semantic gate: detector not found, skipping"; rm -f "$findings_file" 2>/dev/null||true; return 0; }
|
|
77
|
+
# local output rc
|
|
78
|
+
# output=$(cd "${TARGET_DIR:-.}" && LOKI_SCAN_DIR="${TARGET_DIR:-.}" \
|
|
79
|
+
# timeout "$gate_timeout" bash "$detector" --strict 2>&1); rc=$?
|
|
80
|
+
# if [ "$rc" -eq 124 ]; then log_warn "Semantic gate: timed out -- inconclusive"; rm -f "$findings_file" 2>/dev/null||true; return 0; fi
|
|
81
|
+
# if [ "$rc" -ne 0 ]; then
|
|
82
|
+
# { echo "# Semantic test-authenticity findings (CRITICAL/HIGH block)"; echo "$output" | grep -E '\[(CRITICAL|HIGH|MEDIUM|LOW)\]' || true; } > "$findings_file"
|
|
83
|
+
# log_warn "Semantic gate: CRITICAL/HIGH problems detected -- BLOCK"; return 1
|
|
84
|
+
# fi
|
|
85
|
+
# local med_low; med_low=$(echo "$output" | grep -E '\[(MEDIUM|LOW)\]' || true)
|
|
86
|
+
# if [ -n "$med_low" ]; then { echo "# Semantic advisory findings (MED/LOW, non-blocking)"; echo "$med_low"; } > "$findings_file"; else rm -f "$findings_file" 2>/dev/null||true; fi
|
|
87
|
+
# log_info "Semantic gate: PASS"; return 0
|
|
88
|
+
# }
|
|
89
|
+
#
|
|
90
|
+
# Then call it from the gate loop next to enforce_mock_integrity /
|
|
91
|
+
# enforce_mutation_integrity (run.sh ~14299). Opt out with LOKI_GATE_SEMANTIC=false
|
|
92
|
+
# gating the call, matching the LOKI_GATE_MOCK / LOKI_GATE_MUTATION pattern.
|
|
93
|
+
# ============================================================================
|
|
94
|
+
|
|
95
|
+
set -uo pipefail
|
|
96
|
+
|
|
97
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
98
|
+
# Directory to scan. Defaults to the repo containing this script (so
|
|
99
|
+
# run-all-tests.sh keeps scanning loki-mode unchanged). A run.sh gate wrapper
|
|
100
|
+
# MUST set LOKI_SCAN_DIR to the target project; cwd is NOT used by find/git here,
|
|
101
|
+
# so `cd TARGET_DIR` alone does not redirect the scan. Git history (Check 3) is
|
|
102
|
+
# also read from this directory.
|
|
103
|
+
PROJECT_DIR="${LOKI_SCAN_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
104
|
+
|
|
105
|
+
STRICT=""
|
|
106
|
+
BLOCK_HIGH=""
|
|
107
|
+
while [ $# -gt 0 ]; do
|
|
108
|
+
case "$1" in
|
|
109
|
+
--strict) STRICT="--strict"; shift ;;
|
|
110
|
+
--block-high) BLOCK_HIGH="--block-high"; shift ;;
|
|
111
|
+
*) shift ;;
|
|
112
|
+
esac
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
RED='\033[0;31m'
|
|
116
|
+
YELLOW='\033[1;33m'
|
|
117
|
+
GREEN='\033[0;32m'
|
|
118
|
+
CYAN='\033[0;36m'
|
|
119
|
+
NC='\033[0m'
|
|
120
|
+
|
|
121
|
+
CRITICAL=0
|
|
122
|
+
HIGH=0
|
|
123
|
+
MEDIUM=0
|
|
124
|
+
LOW=0
|
|
125
|
+
|
|
126
|
+
echo "=========================================="
|
|
127
|
+
echo "Semantic Test-Authenticity Detector - Quality Gate #10"
|
|
128
|
+
echo "=========================================="
|
|
129
|
+
echo ""
|
|
130
|
+
|
|
131
|
+
report() {
|
|
132
|
+
local severity="$1"
|
|
133
|
+
local file="$2"
|
|
134
|
+
local line="$3"
|
|
135
|
+
local message="$4"
|
|
136
|
+
|
|
137
|
+
case "$severity" in
|
|
138
|
+
CRITICAL) echo -e "${RED}[CRITICAL]${NC} $file:$line - $message"; ((CRITICAL++)) ;;
|
|
139
|
+
HIGH) echo -e "${RED}[HIGH]${NC} $file:$line - $message"; ((HIGH++)) ;;
|
|
140
|
+
MEDIUM) echo -e "${YELLOW}[MEDIUM]${NC} $file:$line - $message"; ((MEDIUM++)) ;;
|
|
141
|
+
LOW) echo -e "${CYAN}[LOW]${NC} $file:$line - $message"; ((LOW++)) ;;
|
|
142
|
+
esac
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Shared find filter for JS/TS test files (Python tests use different syntax and
|
|
146
|
+
# are out of scope for the literal/mock-echo checks here).
|
|
147
|
+
find_js_tests() {
|
|
148
|
+
find "$PROJECT_DIR" \( -name "*.test.ts" -o -name "*.test.js" \
|
|
149
|
+
-o -name "*.test.tsx" -o -name "*.test.jsx" \
|
|
150
|
+
-o -name "*.spec.ts" -o -name "*.spec.js" \) 2>/dev/null \
|
|
151
|
+
| grep -v node_modules | grep -v dist
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# A line whose expect(...) target contains a function call has provenance
|
|
155
|
+
# through code and must NEVER be flagged. Returns 0 (true) if the actual value
|
|
156
|
+
# passed to expect() contains a call like foo(...) -- including method calls.
|
|
157
|
+
# Conservative: when in doubt, treat as "has a call" so we do not flag.
|
|
158
|
+
expect_target_has_call() {
|
|
159
|
+
local line="$1"
|
|
160
|
+
# Extract the actual value passed to expect( ... ) up to .toBe / .toEqual.
|
|
161
|
+
local inner
|
|
162
|
+
inner=$(echo "$line" | sed -E 's/.*expect\((.*)\)\.(toBe|toEqual|toStrictEqual|toMatchObject).*/\1/')
|
|
163
|
+
# If sed did not strip anything (no match), be conservative -> treat as call.
|
|
164
|
+
if [ "$inner" = "$line" ]; then
|
|
165
|
+
return 0
|
|
166
|
+
fi
|
|
167
|
+
# A call looks like identifier( ... ). Method chains (.foo()) also count.
|
|
168
|
+
if echo "$inner" | grep -qE '[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*\('; then
|
|
169
|
+
return 0
|
|
170
|
+
fi
|
|
171
|
+
return 1
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# ----------------------------------------------------------------------------
|
|
175
|
+
# Check 1: Literal-via-variable echo (HIGH)
|
|
176
|
+
# const|let|var NAME = <literal>; then expect(NAME).toBe(<same literal>)
|
|
177
|
+
# with NO function call in the assertion target and no reassignment of NAME
|
|
178
|
+
# between the binding and the assertion.
|
|
179
|
+
# ----------------------------------------------------------------------------
|
|
180
|
+
echo -e "${CYAN}Scanning for literal-via-variable echo (fake assertions)...${NC}"
|
|
181
|
+
while IFS= read -r test_file; do
|
|
182
|
+
[ -f "$test_file" ] || continue
|
|
183
|
+
rel_path="${test_file#$PROJECT_DIR/}"
|
|
184
|
+
|
|
185
|
+
# Find variable bindings to a bare string or integer literal.
|
|
186
|
+
# const x = "hello"; let n = 42; var s = 'ok';
|
|
187
|
+
while IFS=: read -r bind_lineno bind_line; do
|
|
188
|
+
[ -n "$bind_lineno" ] || continue
|
|
189
|
+
var_name=$(echo "$bind_line" | sed -E "s/^[[:space:]]*(const|let|var)[[:space:]]+([A-Za-z_\$][A-Za-z0-9_\$]*)[[:space:]]*=.*/\2/")
|
|
190
|
+
lit=$(echo "$bind_line" | sed -E "s/^[[:space:]]*(const|let|var)[[:space:]]+[A-Za-z_\$][A-Za-z0-9_\$]*[[:space:]]*=[[:space:]]*//; s/;[[:space:]]*$//")
|
|
191
|
+
[ -n "$var_name" ] || continue
|
|
192
|
+
[ -n "$lit" ] || continue
|
|
193
|
+
|
|
194
|
+
# Defense-in-depth: the binding grep below already pins the RHS to a pure
|
|
195
|
+
# single string ('..'/"..") or integer literal, so this re-confirms the
|
|
196
|
+
# extracted literal is exactly that shape. We do NOT additionally reject
|
|
197
|
+
# literals that merely contain operator characters (- + * / & | etc.):
|
|
198
|
+
# those are valid string contents (e.g. "loki-mode", a URL, a path), and
|
|
199
|
+
# rejecting them would silently miss real fakes. $lit is only ever used in
|
|
200
|
+
# a string equality test ([ "$expected" = "$lit" ]), never as a regex, so
|
|
201
|
+
# punctuation in the literal is harmless.
|
|
202
|
+
if ! echo "$lit" | grep -qE "^('[^']*'|\"[^\"]*\"|[0-9]+)$"; then
|
|
203
|
+
continue
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# Scan a forward window for reassignment (disqualifies) or an assertion
|
|
207
|
+
# echoing the same literal back. Window lines come with absolute line nums.
|
|
208
|
+
window_end=$((bind_lineno + 8))
|
|
209
|
+
flagged_lineno=""
|
|
210
|
+
reassigned=false
|
|
211
|
+
while IFS=: read -r a_lineno a_line; do
|
|
212
|
+
[ -n "$a_lineno" ] || continue
|
|
213
|
+
# Reassignment of the same var (NAME = ... but not const/let/var NAME =).
|
|
214
|
+
if echo "$a_line" | grep -qE "(^|[^A-Za-z0-9_\$.])${var_name}[[:space:]]*=[^=]" \
|
|
215
|
+
&& ! echo "$a_line" | grep -qE "(const|let|var)[[:space:]]+${var_name}[[:space:]]*="; then
|
|
216
|
+
reassigned=true
|
|
217
|
+
break
|
|
218
|
+
fi
|
|
219
|
+
# Assertion echoing the same literal back.
|
|
220
|
+
if echo "$a_line" | grep -qE "expect\([[:space:]]*${var_name}[[:space:]]*\)\.(toBe|toEqual|toStrictEqual)\("; then
|
|
221
|
+
if expect_target_has_call "$a_line"; then
|
|
222
|
+
continue
|
|
223
|
+
fi
|
|
224
|
+
expected=$(echo "$a_line" | sed -E "s/.*expect\([[:space:]]*${var_name}[[:space:]]*\)\.(toBe|toEqual|toStrictEqual)\((.*)\).*/\2/")
|
|
225
|
+
expected=$(echo "$expected" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
|
|
226
|
+
if [ "$expected" = "$lit" ]; then
|
|
227
|
+
flagged_lineno="$a_lineno"
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
done < <(awk -v s="$((bind_lineno + 1))" -v e="$window_end" 'NR>=s && NR<=e {print NR":"$0}' "$test_file" 2>/dev/null)
|
|
231
|
+
|
|
232
|
+
if [ "$reassigned" = false ] && [ -n "$flagged_lineno" ]; then
|
|
233
|
+
report "HIGH" "$rel_path" "$flagged_lineno" "Literal echo: variable '$var_name' bound to literal $lit and asserted == same literal with no transformation (verifies nothing)"
|
|
234
|
+
fi
|
|
235
|
+
done < <(grep -nE "^[[:space:]]*(const|let|var)[[:space:]]+[A-Za-z_\$][A-Za-z0-9_\$]*[[:space:]]*=[[:space:]]*('[^']*'|\"[^\"]*\"|[0-9]+)[[:space:]]*;[[:space:]]*$" "$test_file" 2>/dev/null)
|
|
236
|
+
done < <(find_js_tests)
|
|
237
|
+
|
|
238
|
+
# ----------------------------------------------------------------------------
|
|
239
|
+
# Check 2: Mock-return echo (MEDIUM)
|
|
240
|
+
# Within a small window, a mock is configured to return a literal and an
|
|
241
|
+
# assertion checks that SAME literal. MEDIUM because provenance is uncertain.
|
|
242
|
+
# ----------------------------------------------------------------------------
|
|
243
|
+
echo -e "${CYAN}Scanning for mock-return echo...${NC}"
|
|
244
|
+
while IFS= read -r test_file; do
|
|
245
|
+
[ -f "$test_file" ] || continue
|
|
246
|
+
rel_path="${test_file#$PROJECT_DIR/}"
|
|
247
|
+
|
|
248
|
+
while IFS=: read -r mret_lineno mret_line; do
|
|
249
|
+
[ -n "$mret_lineno" ] || continue
|
|
250
|
+
mlit=$(echo "$mret_line" | sed -E "s/.*(mockReturnValueOnce|mockResolvedValueOnce|mockReturnValue|mockResolvedValue|\.returns)\((.*)/\2/")
|
|
251
|
+
mlit=$(echo "$mlit" | sed -E 's/\).*$//; s/^[[:space:]]+//; s/[[:space:]]+$//; s/;[[:space:]]*$//')
|
|
252
|
+
# Only bare string/number literals.
|
|
253
|
+
echo "$mlit" | grep -qE "^('[^']*'|\"[^\"]*\"|[0-9]+)$" || continue
|
|
254
|
+
|
|
255
|
+
window_end=$((mret_lineno + 12))
|
|
256
|
+
while IFS= read -r a_line; do
|
|
257
|
+
echo "$a_line" | grep -qE "\.(toBe|toEqual|toStrictEqual)\(" || continue
|
|
258
|
+
aexpected=$(echo "$a_line" | sed -E "s/.*\.(toBe|toEqual|toStrictEqual)\((.*)\).*/\2/")
|
|
259
|
+
aexpected=$(echo "$aexpected" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
|
|
260
|
+
if [ "$aexpected" = "$mlit" ]; then
|
|
261
|
+
report "MEDIUM" "$rel_path" "$mret_lineno" "Mock-return echo: mock configured to return $mlit and an assertion checks the same literal nearby (may assert the mock, not real behavior)"
|
|
262
|
+
break
|
|
263
|
+
fi
|
|
264
|
+
done < <(awk -v s="$((mret_lineno + 1))" -v e="$window_end" 'NR>=s && NR<=e {print}' "$test_file" 2>/dev/null)
|
|
265
|
+
done < <(grep -nE "(mockReturnValue|mockResolvedValue|mockReturnValueOnce|mockResolvedValueOnce|\.returns)\(" "$test_file" 2>/dev/null)
|
|
266
|
+
done < <(find_js_tests)
|
|
267
|
+
|
|
268
|
+
# ----------------------------------------------------------------------------
|
|
269
|
+
# Check 3: Deleted/weakened assertions across commits (MEDIUM)
|
|
270
|
+
# A commit that REMOVES more assertion lines than it adds from a test file
|
|
271
|
+
# (the test still passes because the failing check is gone).
|
|
272
|
+
# Degrades silently outside a git repo (mirrors detect-test-mutations.sh:144).
|
|
273
|
+
# ----------------------------------------------------------------------------
|
|
274
|
+
echo -e "${CYAN}Scanning for deleted assertions in recent commits...${NC}"
|
|
275
|
+
COMMITS_TO_CHECK=$(cd "$PROJECT_DIR" && git log --oneline -5 --format='%H' 2>/dev/null || true)
|
|
276
|
+
if [ -n "$COMMITS_TO_CHECK" ]; then
|
|
277
|
+
for commit in $COMMITS_TO_CHECK; do
|
|
278
|
+
changed_test_files=$(cd "$PROJECT_DIR" && git diff-tree --no-commit-id --name-only -r "$commit" 2>/dev/null \
|
|
279
|
+
| grep -E '\.(test|spec)\.(ts|js|tsx|jsx)$' || true)
|
|
280
|
+
[ -z "$changed_test_files" ] && continue
|
|
281
|
+
|
|
282
|
+
# shellcheck disable=SC2086
|
|
283
|
+
diff_text=$(cd "$PROJECT_DIR" && git diff "$commit^" "$commit" -- $changed_test_files 2>/dev/null || true)
|
|
284
|
+
[ -z "$diff_text" ] && continue
|
|
285
|
+
|
|
286
|
+
assert_re='(\.toBe\(|\.toEqual\(|\.toStrictEqual\(|\.toThrow\(|\.toHaveBeenCalled|strictEqual\(|deepEqual\(|assert\.|assertEqual\()'
|
|
287
|
+
removed=$(echo "$diff_text" | grep -E '^-' | grep -vE '^---' | grep -cE "$assert_re" || true)
|
|
288
|
+
added=$(echo "$diff_text" | grep -E '^\+' | grep -vE '^\+\+\+' | grep -cE "$assert_re" || true)
|
|
289
|
+
removed="${removed:-0}"; added="${added:-0}"
|
|
290
|
+
removed=$(echo "$removed" | tr -d '[:space:]'); added=$(echo "$added" | tr -d '[:space:]')
|
|
291
|
+
|
|
292
|
+
net_removed=$((removed - added))
|
|
293
|
+
if [ "$net_removed" -gt 0 ]; then
|
|
294
|
+
short_hash=$(echo "$commit" | cut -c1-8)
|
|
295
|
+
report "MEDIUM" "commit:$short_hash" "1" "Net $net_removed assertion line(s) removed from test files ($removed removed, $added added) -- weakened tests may still pass"
|
|
296
|
+
fi
|
|
297
|
+
done
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# Summary
|
|
301
|
+
echo ""
|
|
302
|
+
echo "=========================================="
|
|
303
|
+
TOTAL=$((CRITICAL + HIGH + MEDIUM + LOW))
|
|
304
|
+
echo "Results: $TOTAL finding(s)"
|
|
305
|
+
echo " CRITICAL: $CRITICAL"
|
|
306
|
+
echo " HIGH: $HIGH"
|
|
307
|
+
echo " MEDIUM: $MEDIUM"
|
|
308
|
+
echo " LOW: $LOW"
|
|
309
|
+
echo "=========================================="
|
|
310
|
+
|
|
311
|
+
# --strict takes precedence: block on CRITICAL or HIGH (mirrors gate 5).
|
|
312
|
+
if [ "$STRICT" = "--strict" ]; then
|
|
313
|
+
if [ $CRITICAL -gt 0 ] || [ $HIGH -gt 0 ]; then
|
|
314
|
+
echo ""
|
|
315
|
+
echo -e "${RED}GATE FAILED: $CRITICAL critical + $HIGH high findings${NC}"
|
|
316
|
+
exit 1
|
|
317
|
+
fi
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
# --block-high: exit 2 only when HIGH/CRITICAL present (clean wrapper contract).
|
|
321
|
+
if [ "$BLOCK_HIGH" = "--block-high" ]; then
|
|
322
|
+
if [ $CRITICAL -gt 0 ] || [ $HIGH -gt 0 ]; then
|
|
323
|
+
echo -e "${RED}GATE FAILED: $((CRITICAL + HIGH)) HIGH-severity finding(s)${NC}"
|
|
324
|
+
exit 2
|
|
325
|
+
fi
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
if [ $TOTAL -eq 0 ]; then
|
|
329
|
+
echo -e "${GREEN}All tests pass semantic authenticity gate.${NC}"
|
|
330
|
+
fi
|
|
331
|
+
|
|
332
|
+
exit 0
|